123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- package service
- import (
- "context"
- "encoding/json"
- "fmt"
- "regexp"
- "strings"
- "time"
- "go-common/app/admin/ep/saga/model"
- "go-common/app/admin/ep/saga/service/utils"
- "go-common/library/log"
- "github.com/xanzy/go-gitlab"
- )
- const _specialMrID = 10199
- // QueryProjectMr query project commit info according to project id.
- func (s *Service) QueryProjectMr(c context.Context, req *model.ProjectDataReq) (resp *model.ProjectDataResp, err error) {
- if resp, err = s.QueryProject(c, model.ObjectMR, req); err != nil {
- return
- }
- return
- }
- // QueryTeamMr query team commit info according to department and business
- func (s *Service) QueryTeamMr(c context.Context, req *model.TeamDataRequest) (resp *model.TeamDataResp, err error) {
- if resp, err = s.QueryTeam(c, model.ObjectMR, req); err != nil {
- return
- }
- return
- }
- // QueryProjectMrReport query mr review
- func (s *Service) QueryProjectMrReport(c context.Context, req *model.ProjectMrReportReq) (resp *model.ProjectMrReportResp, err error) {
- var (
- info []*model.MrInfo
- changeAdd int
- changeDel int
- mrCount int
- stateCount int
- discussionCount int
- discussionResolved int
- mrTime int
- spentTime time.Duration
- avarageTime time.Duration
- reviewers []string
- reviews []string
- reviewChangeAdd int
- reviewChangeDel int
- reviewTime int
- reviewTotalTime time.Duration
- )
- if info, err = s.QueryAllMergeRequestInfo(c, req.ProjectID); err != nil {
- return
- }
- for _, i := range info {
- for _, r := range i.Reviewers {
- if r.Name == req.Member {
- reviews = append(reviews, i.Author)
- reviewChangeAdd += i.ChangeAdd
- reviewChangeDel += i.ChangeDel
- reviewTime += i.SpentTime
- }
- }
- if i.Author == req.Member {
- mrCount++
- if i.State == model.StatusMerged {
- stateCount++
- mrTime += i.SpentTime
- changeAdd += i.ChangeAdd
- changeDel += i.ChangeDel
- discussionCount += i.TotalDiscussion
- discussionResolved += i.SolvedDiscussion
- for _, r := range i.Reviewers {
- reviewers = append(reviewers, r.Name)
- }
- }
- }
- }
- // 判断mr为零的情况
- if mrCount == 0 {
- resp = &model.ProjectMrReportResp{}
- return
- }
- if spentTime, err = time.ParseDuration(fmt.Sprintf("%ds", mrTime)); err != nil {
- return
- }
- if avarageTime, err = time.ParseDuration(fmt.Sprintf("%ds", mrTime/mrCount)); err != nil {
- return
- }
- if reviewTotalTime, err = time.ParseDuration(fmt.Sprintf("%ds", reviewTime)); err != nil {
- return
- }
- resp = &model.ProjectMrReportResp{
- ChangeAdd: changeAdd,
- ChangeDel: changeDel,
- MrCount: mrCount,
- SpentTime: spentTime.String(),
- StateCount: stateCount,
- AverageMerge: avarageTime.String(),
- Reviewers: reviewers,
- Discussion: discussionCount,
- Resolve: discussionResolved,
- ReviewerOther: reviews,
- ReviewChangeAdd: reviewChangeAdd,
- ReviewChangeDel: reviewChangeDel,
- ReviewTotalTime: reviewTotalTime.String(),
- }
- return
- }
- // QueryAllMergeRequestInfo ...
- func (s *Service) QueryAllMergeRequestInfo(c context.Context, projID int) (info []*model.MrInfo, err error) {
- var (
- until = time.Now()
- since = until.AddDate(0, -1, 0)
- mrs []*gitlab.MergeRequest
- resp *gitlab.Response
- )
- for page := 1; ; page++ {
- if mrs, resp, err = s.gitlab.ListProjectMergeRequests(projID, &since, &until, page); err != nil {
- return
- }
- for _, m := range mrs {
- mr := &model.MrInfo{ProjectID: projID, MrID: m.IID, Author: m.Author.Name, State: m.State}
- if m.State == model.StatusMerged {
- spent := m.UpdatedAt.Sub(*m.CreatedAt)
- mr.SpentTime = int(spent.Seconds())
- }
- if mr.ChangeAdd, mr.ChangeDel, err = s.QueryMergeRequestDiff(c, projID, m.IID); err != nil {
- return
- }
- if mr.Reviewers, err = s.QueryMergeRequestReview(c, projID, m.IID); err != nil {
- return
- }
- if mr.TotalDiscussion, mr.SolvedDiscussion, err = s.QueryMergeRequestDiscussion(c, projID, m.IID); err != nil {
- return
- }
- info = append(info, mr)
- }
- if resp.NextPage == 0 {
- break
- }
- }
- return
- }
- // QueryMergeRequestReview 查询mr reviewer信息
- func (s *Service) QueryMergeRequestReview(c context.Context, projectID, mrIID int) (reviewers []*model.MrReviewer, err error) {
- var (
- notes []*model.StatisticsNotes
- emojis []*model.StatisticsMRAwardEmojis
- owners []string
- r *regexp.Regexp
- )
- //query note
- if notes, err = s.dao.NoteByMRIID(c, projectID, mrIID); err != nil {
- return
- }
- // query emoji
- if emojis, err = s.dao.AwardEmojiByMRIID(c, projectID, mrIID); err != nil {
- return
- }
- //评论中解析获取owner
- if len(notes) == 0 {
- return
- }
- if r, err = regexp.Compile("OWNER:.*?@(.*)(;|)"); err == nil {
- matchResult := r.FindStringSubmatch(notes[len(notes)-1].Body)
- if len(matchResult) > 0 {
- owners = strings.Split(matchResult[1], " 或 @")
- }
- }
- mrCreatedAt := notes[len(notes)-1].CreatedAt
- // 从评论和表情中解析 reviewers
- for _, note := range notes {
- if note.Body == "+1" {
- reviewers = append(reviewers, &model.MrReviewer{Name: note.AuthorName, FinishedAt: note.CreatedAt, SpentTime: int(note.CreatedAt.Sub(*mrCreatedAt))})
- }
- }
- for _, emoji := range emojis {
- if emoji.Name == "thumbsup" {
- reviewers = append(reviewers, &model.MrReviewer{Name: emoji.UserName, FinishedAt: emoji.CreatedAt, SpentTime: int(emoji.CreatedAt.Sub(*mrCreatedAt))})
- }
- }
- // 判断reviewer类型
- for _, r := range reviewers {
- for _, owner := range owners {
- if owner == r.Name {
- r.UserType = "owner"
- break
- }
- }
- if r.UserType == "" {
- r.UserType = "other"
- }
- }
- return
- }
- // QueryMergeRequestDiscussion 查询获取mr的discussion
- func (s *Service) QueryMergeRequestDiscussion(c context.Context, projectID, mrIID int) (total, solved int, err error) {
- var discussions []*model.StatisticsDiscussions
- if discussions, err = s.dao.DiscussionsByMRIID(c, projectID, mrIID); err != nil {
- return
- }
- for _, d := range discussions {
- var notesArray []int
- if err = json.Unmarshal([]byte(d.Notes), ¬esArray); err != nil {
- return
- }
- for _, n := range notesArray {
- var note *model.StatisticsNotes
- if note, err = s.dao.NoteByID(c, projectID, mrIID, n); err != nil {
- return
- }
- if note.Resolvable {
- total++
- if note.Resolved {
- solved++
- }
- }
- }
- }
- return
- }
- // QueryMergeRequestDiff 查询获取到mr修改文件的总行数, 参数 p project_ID m MR_ID
- func (s *Service) QueryMergeRequestDiff(c context.Context, p, m int) (ChangeAdd, ChangeDel int, err error) {
- var mr *gitlab.MergeRequest
- // 此MR数据量太大,曾导致过gitlab服务器崩溃,访问会返回502服务器错误,因此特殊处理。
- if m == _specialMrID {
- return 0, 0, nil
- }
- if mr, _, err = s.gitlab.GetMergeRequestDiff(p, m); err != nil {
- return
- }
- for _, change := range mr.Changes {
- rows := strings.Split(change.Diff, "\n")
- if len(rows) < 3 {
- // 处理diff为空
- continue
- }
- for _, row := range rows[3:] {
- if strings.HasPrefix(row, "+") {
- ChangeAdd++
- } else if strings.HasPrefix(row, "-") {
- ChangeDel++
- }
- }
- }
- return
- }
- /*-------------------------------------- sync MR ----------------------------------------*/
- // SyncProjectMR ...
- func (s *Service) SyncProjectMR(c context.Context, projectID int) (result *model.SyncResult, err error) {
- var (
- //syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
- syncAllTime = false
- mrs []*gitlab.MergeRequest
- resp *gitlab.Response
- since *time.Time
- until *time.Time
- projectInfo *model.ProjectInfo
- )
- result = &model.SyncResult{}
- if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
- return
- }
- if !syncAllTime {
- since, until = utils.CalSyncTime()
- }
- log.Info("sync project(%d) MR time since: %v, until: %v", projectID, since, until)
- for page := 1; ; page++ {
- result.TotalPage++
- if mrs, resp, err = s.gitlab.ListProjectMergeRequests(projectID, since, until, page); err != nil {
- return
- }
- for _, mr := range mrs {
- if err = s.structureDatabaseMR(c, projectID, projectInfo.Name, mr); err != nil {
- log.Error("mr Save Database err: projectID(%d), MRIID(%d)", projectID, mr.IID)
- err = nil
- errData := &model.FailData{
- ChildID: mr.IID,
- }
- result.FailData = append(result.FailData, errData)
- continue
- }
- result.TotalNum++
- }
- if resp.NextPage == 0 {
- break
- }
- }
- return
- }
- // structureDatabaseMR ...
- func (s *Service) structureDatabaseMR(c context.Context, projectID int, projectName string, mr *gitlab.MergeRequest) (err error) {
- var (
- milestoneID int
- mrLables string
- mrChanges string
- mrLablesByte []byte
- mrChangesByte []byte
- )
- if mr.Milestone != nil {
- milestoneID = mr.Milestone.ID
- }
- if mrLablesByte, err = json.Marshal(mr.Labels); err != nil {
- mrLables = model.JsonMarshalErrorText
- } else {
- mrLables = string(mrLablesByte)
- }
- if mrChangesByte, err = json.Marshal(mr.Changes); err != nil {
- mrChanges = model.JsonMarshalErrorText
- } else {
- mrChanges = string(mrChangesByte)
- }
- mrDB := &model.StatisticsMrs{
- MRID: mr.ID,
- MRIID: mr.IID,
- TargetBranch: mr.TargetBranch,
- SourceBranch: mr.SourceBranch,
- ProjectID: mr.ProjectID,
- ProjectName: projectName,
- Title: mr.Title,
- State: mr.State,
- CreatedAt: mr.CreatedAt,
- UpdatedAt: mr.UpdatedAt,
- Upvotes: mr.Upvotes,
- Downvotes: mr.Downvotes,
- AuthorID: mr.Author.ID,
- AuthorName: mr.Author.Name,
- AssigneeID: mr.Assignee.ID,
- AssigneeName: mr.Assignee.Name,
- SourceProjectID: mr.SourceProjectID,
- TargetProjectID: mr.TargetProjectID,
- Labels: mrLables,
- Description: mr.Description,
- WorkInProgress: mr.WorkInProgress,
- MilestoneID: milestoneID,
- MergeWhenPipelineSucceeds: mr.MergeWhenPipelineSucceeds,
- MergeStatus: mr.MergeStatus,
- MergedByID: mr.MergedBy.ID,
- MergedByName: mr.MergedBy.Name,
- MergedAt: mr.MergedAt,
- ClosedByID: mr.ClosedBy.ID,
- ClosedAt: mr.ClosedAt,
- Subscribed: mr.Subscribed,
- SHA: mr.SHA,
- MergeCommitSHA: mr.MergeCommitSHA,
- UserNotesCount: mr.UserNotesCount,
- ChangesCount: mr.ChangesCount,
- ShouldRemoveSourceBranch: mr.ShouldRemoveSourceBranch,
- ForceRemoveSourceBranch: mr.ForceRemoveSourceBranch,
- WebURL: mr.WebURL,
- DiscussionLocked: mr.DiscussionLocked,
- Changes: mrChanges,
- TimeStatsHumanTimeEstimate: mr.TimeStats.HumanTimeEstimate,
- TimeStatsHumanTotalTimeSpent: mr.TimeStats.HumanTotalTimeSpent,
- TimeStatsTimeEstimate: mr.TimeStats.TimeEstimate,
- TimeStatsTotalTimeSpent: mr.TimeStats.TotalTimeSpent,
- Squash: mr.Squash,
- PipelineID: mr.Pipeline.ID,
- }
- if len(mrDB.Labels) > model.MessageMaxLen {
- mrDB.Labels = mrDB.Labels[0 : model.MessageMaxLen-1]
- }
- if len(mrDB.Description) > model.MessageMaxLen {
- mrDB.Description = mrDB.Description[0 : model.MessageMaxLen-1]
- }
- return s.SaveDatabaseMR(c, mrDB)
- }
- // SaveDatabaseMR ...
- func (s *Service) SaveDatabaseMR(c context.Context, mrDB *model.StatisticsMrs) (err error) {
- var total int
- if total, err = s.dao.HasMR(c, mrDB.ProjectID, mrDB.MRIID); err != nil {
- log.Error("SaveDatabaseMR HasMR(%+v)", err)
- return
- }
- // found only one, so update
- if total == 1 {
- return s.dao.UpdateMR(c, mrDB.ProjectID, mrDB.MRIID, mrDB)
- } else if total > 1 {
- // found repeated row, this situation will not exist under normal
- log.Warn("SaveDatabaseMR mr has more rows(%d)", total)
- return
- }
- // insert row now
- return s.dao.CreateMR(c, mrDB)
- }
- /*-------------------------------------- agg MR ----------------------------------------*/
- // AggregateProjectMR ...
- func (s *Service) AggregateProjectMR(c context.Context, projectID int) (err error) {
- var (
- //syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
- syncAllTime = false
- mrs []*model.StatisticsMrs
- projectInfo *model.ProjectInfo
- since *time.Time
- until *time.Time
- )
- if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
- return
- }
- if !syncAllTime {
- since, until = utils.CalSyncTime()
- }
- if mrs, err = s.dao.MRByProjectID(c, projectID, since, until); err != nil {
- return
- }
- for _, mr := range mrs {
- if err = s.MRAddedInfoDB(c, projectID, mr); err != nil {
- log.Error("MRAddedInfoDB Save Database err: projectID(%d), MRIID(%d)", projectID, mr.MRIID)
- err = nil
- }
- if err = s.MRReviewerDB(c, projectID, mr.MRIID, projectInfo.Name, mr); err != nil {
- log.Error("MRReviewerDB Save Database err: projectID(%d), MRIID(%d)", projectID, mr.MRIID)
- err = nil
- }
- }
- return
- }
- // MRAddedInfoDB ...
- func (s *Service) MRAddedInfoDB(c context.Context, projectID int, mr *model.StatisticsMrs) (err error) {
- if mr.ChangeAdd, mr.ChangeDel, err = s.QueryMergeRequestDiff(c, projectID, mr.MRIID); err != nil {
- return
- }
- if mr.TotalDiscussion, mr.SolvedDiscussion, err = s.QueryMergeRequestDiscussion(c, projectID, mr.MRIID); err != nil {
- return
- }
- return s.dao.UpdateMR(c, projectID, mr.MRIID, mr)
- }
- // MRReviewerDB 查询mr reviewer信息
- func (s *Service) MRReviewerDB(c context.Context, projectID, mrIID int, projectName string, mr *model.StatisticsMrs) (err error) {
- var (
- notes []*model.StatisticsNotes
- emojis []*model.StatisticsMRAwardEmojis
- owners []string
- r *regexp.Regexp
- reviewers []*model.AggregateMrReviewer
- )
- //query note
- if notes, err = s.dao.NoteByMRIID(c, projectID, mrIID); err != nil {
- return
- }
- // query emoji
- if emojis, err = s.dao.AwardEmojiByMRIID(c, projectID, mrIID); err != nil {
- return
- }
- //评论中解析获取owner
- if len(notes) == 0 {
- return
- }
- if r, err = regexp.Compile("OWNER:.*?@(.*)(;|)"); err == nil {
- matchResult := r.FindStringSubmatch(notes[len(notes)-1].Body)
- if len(matchResult) > 0 {
- owners = strings.Split(matchResult[1], " 或 @")
- }
- }
- mrCreatedAt := notes[len(notes)-1].CreatedAt
- // 从评论和表情中解析 reviewers
- for _, note := range notes {
- if note.Body == "+1" {
- reviewer := &model.AggregateMrReviewer{
- ReviewerID: note.AuthorID,
- ReviewerName: note.AuthorName,
- ReviewType: "note",
- ReviewID: note.NoteID,
- ReviewCommand: "+1",
- CreatedAt: note.CreatedAt,
- ApproveTime: int(note.CreatedAt.Sub(*mrCreatedAt).Seconds()),
- MergeTime: int(mr.UpdatedAt.Sub(*note.CreatedAt).Seconds()),
- }
- reviewers = append(reviewers, reviewer)
- }
- }
- for _, emoji := range emojis {
- if emoji.Name == "thumbsup" {
- reviewer := &model.AggregateMrReviewer{
- ReviewerID: emoji.UserID,
- ReviewerName: emoji.UserName,
- ReviewType: "emoji",
- ReviewID: emoji.AwardEmojiID,
- ReviewCommand: "thumbsup",
- CreatedAt: emoji.CreatedAt,
- ApproveTime: int(emoji.CreatedAt.Sub(*mrCreatedAt).Seconds()),
- MergeTime: int(mr.UpdatedAt.Sub(*emoji.CreatedAt).Seconds()),
- }
- reviewers = append(reviewers, reviewer)
- }
- }
- // 判断reviewer类型
- for _, r := range reviewers {
- r.ProjectID = projectID
- r.ProjectName = projectName
- r.MrIID = mrIID
- r.Title = mr.Title
- r.WebUrl = mr.WebURL
- r.AuthorName = mr.AuthorName
- if utils.InSlice(r.ReviewerName, owners) {
- r.UserType = "owner"
- } else {
- r.UserType = "other"
- }
- if err = s.SaveDatabaseAggMR(c, r); err != nil {
- log.Error("mrReviewer 存数据库报错(%+V)", err)
- continue
- }
- }
- return
- }
- // SaveDatabaseAggMR ...
- func (s *Service) SaveDatabaseAggMR(c context.Context, mrDB *model.AggregateMrReviewer) (err error) {
- var total int
- if total, err = s.dao.HasAggregateReviewer(c, mrDB.ProjectID, mrDB.MrIID, mrDB.ReviewerID, mrDB.ReviewID); err != nil {
- log.Error("SaveDatabaseAggMR HasAggMR(%+v)", err)
- return
- }
- // found only one, so update
- if total == 1 {
- return s.dao.UpdateAggregateReviewer(c, mrDB.ProjectID, mrDB.MrIID, mrDB.ReviewerID, mrDB.ReviewID, mrDB)
- } else if total > 1 {
- // found repeated row, this situation will not exist under normal
- log.Warn("SaveDatabaseAggMR aggMR has more rows(%d)", total)
- return
- }
- // insert row now
- return s.dao.CreateAggregateReviewer(c, mrDB)
- }
|