From 2c476de1fa966990859ea2a64be066f96b152fb5 Mon Sep 17 00:00:00 2001 From: joey Date: Mon, 29 Nov 2021 15:22:10 +0800 Subject: [PATCH] optimization of issue summary state --- apistructs/iteration.go | 8 +- .../components/test-report/filter/render.go | 2 +- modules/dop/dao/issue.go | 13 +++ modules/dop/endpoints/iteration.go | 19 ++-- modules/dop/endpoints/iteration_test.go | 60 +++++++++++++ modules/dop/services/iteration/iteration.go | 90 +++++++++++++++++++ 6 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 modules/dop/endpoints/iteration_test.go diff --git a/apistructs/iteration.go b/apistructs/iteration.go index 58152aa295a..d959e996ce0 100644 --- a/apistructs/iteration.go +++ b/apistructs/iteration.go @@ -147,6 +147,10 @@ type ISummary struct { } type ISummaryState struct { - Done int `json:"done"` - UnDone int `json:"undone"` + Done int `json:"done"` + UnDone int `json:"undone"` + Total int `json:"total,omitempty"` + IssueType IssueType `json:"issue_type,omitempty"` + State int64 `json:"state,omitempty"` + IterationID int64 `json:"iteration_id,omitempty"` } diff --git a/modules/dop/component-protocol/components/test-report/filter/render.go b/modules/dop/component-protocol/components/test-report/filter/render.go index 7e0ef26b83b..37f800366eb 100644 --- a/modules/dop/component-protocol/components/test-report/filter/render.go +++ b/modules/dop/component-protocol/components/test-report/filter/render.go @@ -104,7 +104,7 @@ func (ca *ComponentAction) Render(ctx context.Context, c *cptype.Component, scen ca.Props = map[string]interface{}{ "delay": 1000, } - iterations, err := bdl.ListProjectIterations(apistructs.IterationPagingRequest{ProjectID: ca.InParams.ProjectID, PageSize: 999}, sdk.Identity.OrgID) + iterations, err := bdl.ListProjectIterations(apistructs.IterationPagingRequest{ProjectID: ca.InParams.ProjectID, PageSize: 999, WithoutIssueSummary: true}, sdk.Identity.OrgID) if err != nil { return err } diff --git a/modules/dop/dao/issue.go b/modules/dop/dao/issue.go index da941ad917e..1f95fa9be68 100644 --- a/modules/dop/dao/issue.go +++ b/modules/dop/dao/issue.go @@ -434,6 +434,19 @@ func (client *DBClient) GetIssueSummary(iterationID int64, task, bug, requiremen } } +func (client *DBClient) ListIssueSummaryStates(projectID uint64, iterationIDS []int64) ([]apistructs.ISummaryState, error) { + var summaries []apistructs.ISummaryState + if err := client.Model(Issue{}).Where("project_id = ?", projectID). + Where("iteration_id in (?)", iterationIDS). + Where("deleted = ?", 0). + Group("iteration_id,state,type"). + Select("count(*) as total,state,type as issue_type,iteration_id"). + Scan(&summaries).Error; err != nil { + return nil, err + } + return summaries, nil +} + // GetIssueByIssueIDs 通过issueid获取issue func (client *DBClient) GetIssueByIssueIDs(issueIDs []uint64) ([]Issue, error) { var issues []Issue diff --git a/modules/dop/endpoints/iteration.go b/modules/dop/endpoints/iteration.go index f89692bf4e8..911e6c42718 100644 --- a/modules/dop/endpoints/iteration.go +++ b/modules/dop/endpoints/iteration.go @@ -296,18 +296,19 @@ func (e *Endpoints) PagingIterations(ctx context.Context, r *http.Request, vars if err != nil { return errorresp.ErrResp(err) } - iterations := make([]apistructs.Iteration, 0, len(iterationModels)) + iterationMap := make(map[int64]*apistructs.Iteration, len(iterationModels)) for _, itr := range iterationModels { iteration := itr.Convert() - // 默认不查询 summary - // TODO 这里面的逻辑需要优化,调用了太多次数据库 @周子曰 - if !pageReq.WithoutIssueSummary { - iteration.IssueSummary, err = e.iteration.GetIssueSummary(iteration.ID, pageReq.ProjectID) - if err != nil { - return apierrors.ErrPagingIterations.InternalError(err).ToResp(), nil - } + iterationMap[iteration.ID] = &iteration + } + if !pageReq.WithoutIssueSummary { + if err := e.iteration.SetIssueSummaries(pageReq.ProjectID, iterationMap); err != nil { + return errorresp.ErrResp(err) } - iterations = append(iterations, iteration) + } + iterations := make([]apistructs.Iteration, 0, len(iterationModels)) + for _, itr := range iterationMap { + iterations = append(iterations, *itr) } // userIDs var userIDs []string diff --git a/modules/dop/endpoints/iteration_test.go b/modules/dop/endpoints/iteration_test.go new file mode 100644 index 00000000000..6cd484ec9ee --- /dev/null +++ b/modules/dop/endpoints/iteration_test.go @@ -0,0 +1,60 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package endpoints + +import ( + "context" + "net/http" + "net/url" + "reflect" + "testing" + + "bou.ke/monkey" + "github.com/gorilla/schema" + "github.com/stretchr/testify/assert" + + "github.com/erda-project/erda/apistructs" + "github.com/erda-project/erda/modules/dop/dao" + "github.com/erda-project/erda/modules/dop/services/iteration" + "github.com/erda-project/erda/modules/pkg/user" + "github.com/erda-project/erda/pkg/database/dbengine" +) + +func TestPagingIterations(t *testing.T) { + pm1 := monkey.Patch(user.GetIdentityInfo, func(r *http.Request) (apistructs.IdentityInfo, error) { + return apistructs.IdentityInfo{UserID: "1", InternalClient: "bundle"}, nil + }) + defer pm1.Unpatch() + + iterationSvc := &iteration.Iteration{} + pm2 := monkey.PatchInstanceMethod(reflect.TypeOf(iterationSvc), "Paging", func(itr *iteration.Iteration, req apistructs.IterationPagingRequest) ([]dao.Iteration, uint64, error) { + return []dao.Iteration{{BaseModel: dbengine.BaseModel{ID: 1}, ProjectID: 1}}, 1, nil + }) + defer pm2.Unpatch() + + pm3 := monkey.PatchInstanceMethod(reflect.TypeOf(iterationSvc), "SetIssueSummaries", func(itr *iteration.Iteration, projectID uint64, iterationMap map[int64]*apistructs.Iteration) error { + return nil + }) + defer pm3.Unpatch() + + ep := Endpoints{iteration: iterationSvc, queryStringDecoder: schema.NewDecoder()} + r := &http.Request{Header: http.Header{}, URL: &url.URL{}} + r.Header.Set("Org-ID", "1") + q := r.URL.Query() + q.Add("projectID", "1") + r.URL.RawQuery = q.Encode() + _, err := ep.PagingIterations(context.Background(), r, map[string]string{"projectID": "1"}) + assert.NoError(t, err) +} diff --git a/modules/dop/services/iteration/iteration.go b/modules/dop/services/iteration/iteration.go index f44351a21c4..e7ad387f5dd 100644 --- a/modules/dop/services/iteration/iteration.go +++ b/modules/dop/services/iteration/iteration.go @@ -210,3 +210,93 @@ func (itr *Iteration) GetIssueSummary(iterationID int64, projectID uint64) (apis return itr.db.GetIssueSummary(iterationID, taskState, bugState, requirementState), nil } + +func (itr *Iteration) SetIssueSummaries(projectID uint64, iterationMap map[int64]*apistructs.Iteration) error { + if projectID == 0 { + return apierrors.ErrPagingIterations.InvalidParameter("missing projectID") + } + if len(iterationMap) == 0 { + return apierrors.ErrPagingIterations.InvalidParameter("missing iteration ids") + } + iterationIDS := make([]int64, 0, len(iterationMap)) + for _, iteration := range iterationMap { + iterationIDS = append(iterationIDS, iteration.ID) + iterationMap[iteration.ID] = iteration + } + summaryStates, err := itr.db.ListIssueSummaryStates(projectID, iterationIDS) + if err != nil { + return err + } + + bugCloseStateIDS, err := itr.getDoneStateIDSByType(projectID, apistructs.IssueTypeBug) + if err != nil { + return err + } + taskDoneStateIDS, err := itr.getDoneStateIDSByType(projectID, apistructs.IssueTypeTask) + if err != nil { + return err + } + reqDoneStateIDS, err := itr.getDoneStateIDSByType(projectID, apistructs.IssueTypeRequirement) + if err != nil { + return err + } + + for _, summary := range summaryStates { + iteration, ok := iterationMap[summary.IterationID] + if ok { + switch summary.IssueType { + case apistructs.IssueTypeBug: + if itr.isContainStateID(bugCloseStateIDS, summary.State) { + iteration.IssueSummary.Bug.Done += summary.Total + continue + } + iteration.IssueSummary.Bug.UnDone += summary.Total + case apistructs.IssueTypeTask: + if itr.isContainStateID(taskDoneStateIDS, summary.State) { + iteration.IssueSummary.Task.Done += summary.Total + continue + } + iteration.IssueSummary.Task.UnDone += summary.Total + case apistructs.IssueTypeRequirement: + if itr.isContainStateID(reqDoneStateIDS, summary.State) { + iteration.IssueSummary.Requirement.Done += summary.Total + continue + } + iteration.IssueSummary.Requirement.UnDone += summary.Total + } + } + } + return nil +} + +func (itr *Iteration) getDoneStateIDSByType(projectID uint64, issueType apistructs.IssueType) ([]int64, error) { + states, err := itr.db.GetIssuesStatesByProjectID(projectID, issueType) + if err != nil { + return nil, err + } + stateIDS := make([]int64, 0) + for _, v := range states { + switch issueType { + case apistructs.IssueTypeTask, apistructs.IssueTypeRequirement: + if v.Belong == apistructs.IssueStateBelongDone { + stateIDS = append(stateIDS, int64(v.ID)) + } + case apistructs.IssueTypeBug: + if v.Belong == apistructs.IssueStateBelongClosed { + stateIDS = append(stateIDS, int64(v.ID)) + } + default: + continue + } + } + return stateIDS, nil +} + +func (itr *Iteration) isContainStateID(stateIDS []int64, targetID int64) bool { + for _, stateID := range stateIDS { + if stateID == targetID { + return true + } + } + return false +}