mr.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "regexp"
  7. "strings"
  8. "time"
  9. "go-common/app/admin/ep/saga/model"
  10. "go-common/app/admin/ep/saga/service/utils"
  11. "go-common/library/log"
  12. "github.com/xanzy/go-gitlab"
  13. )
  14. const _specialMrID = 10199
  15. // QueryProjectMr query project commit info according to project id.
  16. func (s *Service) QueryProjectMr(c context.Context, req *model.ProjectDataReq) (resp *model.ProjectDataResp, err error) {
  17. if resp, err = s.QueryProject(c, model.ObjectMR, req); err != nil {
  18. return
  19. }
  20. return
  21. }
  22. // QueryTeamMr query team commit info according to department and business
  23. func (s *Service) QueryTeamMr(c context.Context, req *model.TeamDataRequest) (resp *model.TeamDataResp, err error) {
  24. if resp, err = s.QueryTeam(c, model.ObjectMR, req); err != nil {
  25. return
  26. }
  27. return
  28. }
  29. // QueryProjectMrReport query mr review
  30. func (s *Service) QueryProjectMrReport(c context.Context, req *model.ProjectMrReportReq) (resp *model.ProjectMrReportResp, err error) {
  31. var (
  32. info []*model.MrInfo
  33. changeAdd int
  34. changeDel int
  35. mrCount int
  36. stateCount int
  37. discussionCount int
  38. discussionResolved int
  39. mrTime int
  40. spentTime time.Duration
  41. avarageTime time.Duration
  42. reviewers []string
  43. reviews []string
  44. reviewChangeAdd int
  45. reviewChangeDel int
  46. reviewTime int
  47. reviewTotalTime time.Duration
  48. )
  49. if info, err = s.QueryAllMergeRequestInfo(c, req.ProjectID); err != nil {
  50. return
  51. }
  52. for _, i := range info {
  53. for _, r := range i.Reviewers {
  54. if r.Name == req.Member {
  55. reviews = append(reviews, i.Author)
  56. reviewChangeAdd += i.ChangeAdd
  57. reviewChangeDel += i.ChangeDel
  58. reviewTime += i.SpentTime
  59. }
  60. }
  61. if i.Author == req.Member {
  62. mrCount++
  63. if i.State == model.StatusMerged {
  64. stateCount++
  65. mrTime += i.SpentTime
  66. changeAdd += i.ChangeAdd
  67. changeDel += i.ChangeDel
  68. discussionCount += i.TotalDiscussion
  69. discussionResolved += i.SolvedDiscussion
  70. for _, r := range i.Reviewers {
  71. reviewers = append(reviewers, r.Name)
  72. }
  73. }
  74. }
  75. }
  76. // 判断mr为零的情况
  77. if mrCount == 0 {
  78. resp = &model.ProjectMrReportResp{}
  79. return
  80. }
  81. if spentTime, err = time.ParseDuration(fmt.Sprintf("%ds", mrTime)); err != nil {
  82. return
  83. }
  84. if avarageTime, err = time.ParseDuration(fmt.Sprintf("%ds", mrTime/mrCount)); err != nil {
  85. return
  86. }
  87. if reviewTotalTime, err = time.ParseDuration(fmt.Sprintf("%ds", reviewTime)); err != nil {
  88. return
  89. }
  90. resp = &model.ProjectMrReportResp{
  91. ChangeAdd: changeAdd,
  92. ChangeDel: changeDel,
  93. MrCount: mrCount,
  94. SpentTime: spentTime.String(),
  95. StateCount: stateCount,
  96. AverageMerge: avarageTime.String(),
  97. Reviewers: reviewers,
  98. Discussion: discussionCount,
  99. Resolve: discussionResolved,
  100. ReviewerOther: reviews,
  101. ReviewChangeAdd: reviewChangeAdd,
  102. ReviewChangeDel: reviewChangeDel,
  103. ReviewTotalTime: reviewTotalTime.String(),
  104. }
  105. return
  106. }
  107. // QueryAllMergeRequestInfo ...
  108. func (s *Service) QueryAllMergeRequestInfo(c context.Context, projID int) (info []*model.MrInfo, err error) {
  109. var (
  110. until = time.Now()
  111. since = until.AddDate(0, -1, 0)
  112. mrs []*gitlab.MergeRequest
  113. resp *gitlab.Response
  114. )
  115. for page := 1; ; page++ {
  116. if mrs, resp, err = s.gitlab.ListProjectMergeRequests(projID, &since, &until, page); err != nil {
  117. return
  118. }
  119. for _, m := range mrs {
  120. mr := &model.MrInfo{ProjectID: projID, MrID: m.IID, Author: m.Author.Name, State: m.State}
  121. if m.State == model.StatusMerged {
  122. spent := m.UpdatedAt.Sub(*m.CreatedAt)
  123. mr.SpentTime = int(spent.Seconds())
  124. }
  125. if mr.ChangeAdd, mr.ChangeDel, err = s.QueryMergeRequestDiff(c, projID, m.IID); err != nil {
  126. return
  127. }
  128. if mr.Reviewers, err = s.QueryMergeRequestReview(c, projID, m.IID); err != nil {
  129. return
  130. }
  131. if mr.TotalDiscussion, mr.SolvedDiscussion, err = s.QueryMergeRequestDiscussion(c, projID, m.IID); err != nil {
  132. return
  133. }
  134. info = append(info, mr)
  135. }
  136. if resp.NextPage == 0 {
  137. break
  138. }
  139. }
  140. return
  141. }
  142. // QueryMergeRequestReview 查询mr reviewer信息
  143. func (s *Service) QueryMergeRequestReview(c context.Context, projectID, mrIID int) (reviewers []*model.MrReviewer, err error) {
  144. var (
  145. notes []*model.StatisticsNotes
  146. emojis []*model.StatisticsMRAwardEmojis
  147. owners []string
  148. r *regexp.Regexp
  149. )
  150. //query note
  151. if notes, err = s.dao.NoteByMRIID(c, projectID, mrIID); err != nil {
  152. return
  153. }
  154. // query emoji
  155. if emojis, err = s.dao.AwardEmojiByMRIID(c, projectID, mrIID); err != nil {
  156. return
  157. }
  158. //评论中解析获取owner
  159. if len(notes) == 0 {
  160. return
  161. }
  162. if r, err = regexp.Compile("OWNER:.*?@(.*)(;|)"); err == nil {
  163. matchResult := r.FindStringSubmatch(notes[len(notes)-1].Body)
  164. if len(matchResult) > 0 {
  165. owners = strings.Split(matchResult[1], " 或 @")
  166. }
  167. }
  168. mrCreatedAt := notes[len(notes)-1].CreatedAt
  169. // 从评论和表情中解析 reviewers
  170. for _, note := range notes {
  171. if note.Body == "+1" {
  172. reviewers = append(reviewers, &model.MrReviewer{Name: note.AuthorName, FinishedAt: note.CreatedAt, SpentTime: int(note.CreatedAt.Sub(*mrCreatedAt))})
  173. }
  174. }
  175. for _, emoji := range emojis {
  176. if emoji.Name == "thumbsup" {
  177. reviewers = append(reviewers, &model.MrReviewer{Name: emoji.UserName, FinishedAt: emoji.CreatedAt, SpentTime: int(emoji.CreatedAt.Sub(*mrCreatedAt))})
  178. }
  179. }
  180. // 判断reviewer类型
  181. for _, r := range reviewers {
  182. for _, owner := range owners {
  183. if owner == r.Name {
  184. r.UserType = "owner"
  185. break
  186. }
  187. }
  188. if r.UserType == "" {
  189. r.UserType = "other"
  190. }
  191. }
  192. return
  193. }
  194. // QueryMergeRequestDiscussion 查询获取mr的discussion
  195. func (s *Service) QueryMergeRequestDiscussion(c context.Context, projectID, mrIID int) (total, solved int, err error) {
  196. var discussions []*model.StatisticsDiscussions
  197. if discussions, err = s.dao.DiscussionsByMRIID(c, projectID, mrIID); err != nil {
  198. return
  199. }
  200. for _, d := range discussions {
  201. var notesArray []int
  202. if err = json.Unmarshal([]byte(d.Notes), &notesArray); err != nil {
  203. return
  204. }
  205. for _, n := range notesArray {
  206. var note *model.StatisticsNotes
  207. if note, err = s.dao.NoteByID(c, projectID, mrIID, n); err != nil {
  208. return
  209. }
  210. if note.Resolvable {
  211. total++
  212. if note.Resolved {
  213. solved++
  214. }
  215. }
  216. }
  217. }
  218. return
  219. }
  220. // QueryMergeRequestDiff 查询获取到mr修改文件的总行数, 参数 p project_ID m MR_ID
  221. func (s *Service) QueryMergeRequestDiff(c context.Context, p, m int) (ChangeAdd, ChangeDel int, err error) {
  222. var mr *gitlab.MergeRequest
  223. // 此MR数据量太大,曾导致过gitlab服务器崩溃,访问会返回502服务器错误,因此特殊处理。
  224. if m == _specialMrID {
  225. return 0, 0, nil
  226. }
  227. if mr, _, err = s.gitlab.GetMergeRequestDiff(p, m); err != nil {
  228. return
  229. }
  230. for _, change := range mr.Changes {
  231. rows := strings.Split(change.Diff, "\n")
  232. if len(rows) < 3 {
  233. // 处理diff为空
  234. continue
  235. }
  236. for _, row := range rows[3:] {
  237. if strings.HasPrefix(row, "+") {
  238. ChangeAdd++
  239. } else if strings.HasPrefix(row, "-") {
  240. ChangeDel++
  241. }
  242. }
  243. }
  244. return
  245. }
  246. /*-------------------------------------- sync MR ----------------------------------------*/
  247. // SyncProjectMR ...
  248. func (s *Service) SyncProjectMR(c context.Context, projectID int) (result *model.SyncResult, err error) {
  249. var (
  250. //syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
  251. syncAllTime = false
  252. mrs []*gitlab.MergeRequest
  253. resp *gitlab.Response
  254. since *time.Time
  255. until *time.Time
  256. projectInfo *model.ProjectInfo
  257. )
  258. result = &model.SyncResult{}
  259. if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
  260. return
  261. }
  262. if !syncAllTime {
  263. since, until = utils.CalSyncTime()
  264. }
  265. log.Info("sync project(%d) MR time since: %v, until: %v", projectID, since, until)
  266. for page := 1; ; page++ {
  267. result.TotalPage++
  268. if mrs, resp, err = s.gitlab.ListProjectMergeRequests(projectID, since, until, page); err != nil {
  269. return
  270. }
  271. for _, mr := range mrs {
  272. if err = s.structureDatabaseMR(c, projectID, projectInfo.Name, mr); err != nil {
  273. log.Error("mr Save Database err: projectID(%d), MRIID(%d)", projectID, mr.IID)
  274. err = nil
  275. errData := &model.FailData{
  276. ChildID: mr.IID,
  277. }
  278. result.FailData = append(result.FailData, errData)
  279. continue
  280. }
  281. result.TotalNum++
  282. }
  283. if resp.NextPage == 0 {
  284. break
  285. }
  286. }
  287. return
  288. }
  289. // structureDatabaseMR ...
  290. func (s *Service) structureDatabaseMR(c context.Context, projectID int, projectName string, mr *gitlab.MergeRequest) (err error) {
  291. var (
  292. milestoneID int
  293. mrLables string
  294. mrChanges string
  295. mrLablesByte []byte
  296. mrChangesByte []byte
  297. )
  298. if mr.Milestone != nil {
  299. milestoneID = mr.Milestone.ID
  300. }
  301. if mrLablesByte, err = json.Marshal(mr.Labels); err != nil {
  302. mrLables = model.JsonMarshalErrorText
  303. } else {
  304. mrLables = string(mrLablesByte)
  305. }
  306. if mrChangesByte, err = json.Marshal(mr.Changes); err != nil {
  307. mrChanges = model.JsonMarshalErrorText
  308. } else {
  309. mrChanges = string(mrChangesByte)
  310. }
  311. mrDB := &model.StatisticsMrs{
  312. MRID: mr.ID,
  313. MRIID: mr.IID,
  314. TargetBranch: mr.TargetBranch,
  315. SourceBranch: mr.SourceBranch,
  316. ProjectID: mr.ProjectID,
  317. ProjectName: projectName,
  318. Title: mr.Title,
  319. State: mr.State,
  320. CreatedAt: mr.CreatedAt,
  321. UpdatedAt: mr.UpdatedAt,
  322. Upvotes: mr.Upvotes,
  323. Downvotes: mr.Downvotes,
  324. AuthorID: mr.Author.ID,
  325. AuthorName: mr.Author.Name,
  326. AssigneeID: mr.Assignee.ID,
  327. AssigneeName: mr.Assignee.Name,
  328. SourceProjectID: mr.SourceProjectID,
  329. TargetProjectID: mr.TargetProjectID,
  330. Labels: mrLables,
  331. Description: mr.Description,
  332. WorkInProgress: mr.WorkInProgress,
  333. MilestoneID: milestoneID,
  334. MergeWhenPipelineSucceeds: mr.MergeWhenPipelineSucceeds,
  335. MergeStatus: mr.MergeStatus,
  336. MergedByID: mr.MergedBy.ID,
  337. MergedByName: mr.MergedBy.Name,
  338. MergedAt: mr.MergedAt,
  339. ClosedByID: mr.ClosedBy.ID,
  340. ClosedAt: mr.ClosedAt,
  341. Subscribed: mr.Subscribed,
  342. SHA: mr.SHA,
  343. MergeCommitSHA: mr.MergeCommitSHA,
  344. UserNotesCount: mr.UserNotesCount,
  345. ChangesCount: mr.ChangesCount,
  346. ShouldRemoveSourceBranch: mr.ShouldRemoveSourceBranch,
  347. ForceRemoveSourceBranch: mr.ForceRemoveSourceBranch,
  348. WebURL: mr.WebURL,
  349. DiscussionLocked: mr.DiscussionLocked,
  350. Changes: mrChanges,
  351. TimeStatsHumanTimeEstimate: mr.TimeStats.HumanTimeEstimate,
  352. TimeStatsHumanTotalTimeSpent: mr.TimeStats.HumanTotalTimeSpent,
  353. TimeStatsTimeEstimate: mr.TimeStats.TimeEstimate,
  354. TimeStatsTotalTimeSpent: mr.TimeStats.TotalTimeSpent,
  355. Squash: mr.Squash,
  356. PipelineID: mr.Pipeline.ID,
  357. }
  358. if len(mrDB.Labels) > model.MessageMaxLen {
  359. mrDB.Labels = mrDB.Labels[0 : model.MessageMaxLen-1]
  360. }
  361. if len(mrDB.Description) > model.MessageMaxLen {
  362. mrDB.Description = mrDB.Description[0 : model.MessageMaxLen-1]
  363. }
  364. return s.SaveDatabaseMR(c, mrDB)
  365. }
  366. // SaveDatabaseMR ...
  367. func (s *Service) SaveDatabaseMR(c context.Context, mrDB *model.StatisticsMrs) (err error) {
  368. var total int
  369. if total, err = s.dao.HasMR(c, mrDB.ProjectID, mrDB.MRIID); err != nil {
  370. log.Error("SaveDatabaseMR HasMR(%+v)", err)
  371. return
  372. }
  373. // found only one, so update
  374. if total == 1 {
  375. return s.dao.UpdateMR(c, mrDB.ProjectID, mrDB.MRIID, mrDB)
  376. } else if total > 1 {
  377. // found repeated row, this situation will not exist under normal
  378. log.Warn("SaveDatabaseMR mr has more rows(%d)", total)
  379. return
  380. }
  381. // insert row now
  382. return s.dao.CreateMR(c, mrDB)
  383. }
  384. /*-------------------------------------- agg MR ----------------------------------------*/
  385. // AggregateProjectMR ...
  386. func (s *Service) AggregateProjectMR(c context.Context, projectID int) (err error) {
  387. var (
  388. //syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
  389. syncAllTime = false
  390. mrs []*model.StatisticsMrs
  391. projectInfo *model.ProjectInfo
  392. since *time.Time
  393. until *time.Time
  394. )
  395. if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
  396. return
  397. }
  398. if !syncAllTime {
  399. since, until = utils.CalSyncTime()
  400. }
  401. if mrs, err = s.dao.MRByProjectID(c, projectID, since, until); err != nil {
  402. return
  403. }
  404. for _, mr := range mrs {
  405. if err = s.MRAddedInfoDB(c, projectID, mr); err != nil {
  406. log.Error("MRAddedInfoDB Save Database err: projectID(%d), MRIID(%d)", projectID, mr.MRIID)
  407. err = nil
  408. }
  409. if err = s.MRReviewerDB(c, projectID, mr.MRIID, projectInfo.Name, mr); err != nil {
  410. log.Error("MRReviewerDB Save Database err: projectID(%d), MRIID(%d)", projectID, mr.MRIID)
  411. err = nil
  412. }
  413. }
  414. return
  415. }
  416. // MRAddedInfoDB ...
  417. func (s *Service) MRAddedInfoDB(c context.Context, projectID int, mr *model.StatisticsMrs) (err error) {
  418. if mr.ChangeAdd, mr.ChangeDel, err = s.QueryMergeRequestDiff(c, projectID, mr.MRIID); err != nil {
  419. return
  420. }
  421. if mr.TotalDiscussion, mr.SolvedDiscussion, err = s.QueryMergeRequestDiscussion(c, projectID, mr.MRIID); err != nil {
  422. return
  423. }
  424. return s.dao.UpdateMR(c, projectID, mr.MRIID, mr)
  425. }
  426. // MRReviewerDB 查询mr reviewer信息
  427. func (s *Service) MRReviewerDB(c context.Context, projectID, mrIID int, projectName string, mr *model.StatisticsMrs) (err error) {
  428. var (
  429. notes []*model.StatisticsNotes
  430. emojis []*model.StatisticsMRAwardEmojis
  431. owners []string
  432. r *regexp.Regexp
  433. reviewers []*model.AggregateMrReviewer
  434. )
  435. //query note
  436. if notes, err = s.dao.NoteByMRIID(c, projectID, mrIID); err != nil {
  437. return
  438. }
  439. // query emoji
  440. if emojis, err = s.dao.AwardEmojiByMRIID(c, projectID, mrIID); err != nil {
  441. return
  442. }
  443. //评论中解析获取owner
  444. if len(notes) == 0 {
  445. return
  446. }
  447. if r, err = regexp.Compile("OWNER:.*?@(.*)(;|)"); err == nil {
  448. matchResult := r.FindStringSubmatch(notes[len(notes)-1].Body)
  449. if len(matchResult) > 0 {
  450. owners = strings.Split(matchResult[1], " 或 @")
  451. }
  452. }
  453. mrCreatedAt := notes[len(notes)-1].CreatedAt
  454. // 从评论和表情中解析 reviewers
  455. for _, note := range notes {
  456. if note.Body == "+1" {
  457. reviewer := &model.AggregateMrReviewer{
  458. ReviewerID: note.AuthorID,
  459. ReviewerName: note.AuthorName,
  460. ReviewType: "note",
  461. ReviewID: note.NoteID,
  462. ReviewCommand: "+1",
  463. CreatedAt: note.CreatedAt,
  464. ApproveTime: int(note.CreatedAt.Sub(*mrCreatedAt).Seconds()),
  465. MergeTime: int(mr.UpdatedAt.Sub(*note.CreatedAt).Seconds()),
  466. }
  467. reviewers = append(reviewers, reviewer)
  468. }
  469. }
  470. for _, emoji := range emojis {
  471. if emoji.Name == "thumbsup" {
  472. reviewer := &model.AggregateMrReviewer{
  473. ReviewerID: emoji.UserID,
  474. ReviewerName: emoji.UserName,
  475. ReviewType: "emoji",
  476. ReviewID: emoji.AwardEmojiID,
  477. ReviewCommand: "thumbsup",
  478. CreatedAt: emoji.CreatedAt,
  479. ApproveTime: int(emoji.CreatedAt.Sub(*mrCreatedAt).Seconds()),
  480. MergeTime: int(mr.UpdatedAt.Sub(*emoji.CreatedAt).Seconds()),
  481. }
  482. reviewers = append(reviewers, reviewer)
  483. }
  484. }
  485. // 判断reviewer类型
  486. for _, r := range reviewers {
  487. r.ProjectID = projectID
  488. r.ProjectName = projectName
  489. r.MrIID = mrIID
  490. r.Title = mr.Title
  491. r.WebUrl = mr.WebURL
  492. r.AuthorName = mr.AuthorName
  493. if utils.InSlice(r.ReviewerName, owners) {
  494. r.UserType = "owner"
  495. } else {
  496. r.UserType = "other"
  497. }
  498. if err = s.SaveDatabaseAggMR(c, r); err != nil {
  499. log.Error("mrReviewer 存数据库报错(%+V)", err)
  500. continue
  501. }
  502. }
  503. return
  504. }
  505. // SaveDatabaseAggMR ...
  506. func (s *Service) SaveDatabaseAggMR(c context.Context, mrDB *model.AggregateMrReviewer) (err error) {
  507. var total int
  508. if total, err = s.dao.HasAggregateReviewer(c, mrDB.ProjectID, mrDB.MrIID, mrDB.ReviewerID, mrDB.ReviewID); err != nil {
  509. log.Error("SaveDatabaseAggMR HasAggMR(%+v)", err)
  510. return
  511. }
  512. // found only one, so update
  513. if total == 1 {
  514. return s.dao.UpdateAggregateReviewer(c, mrDB.ProjectID, mrDB.MrIID, mrDB.ReviewerID, mrDB.ReviewID, mrDB)
  515. } else if total > 1 {
  516. // found repeated row, this situation will not exist under normal
  517. log.Warn("SaveDatabaseAggMR aggMR has more rows(%d)", total)
  518. return
  519. }
  520. // insert row now
  521. return s.dao.CreateAggregateReviewer(c, mrDB)
  522. }