Skip to content

Commit

Permalink
optimization of issue summary state (erda-project#3208)
Browse files Browse the repository at this point in the history
  • Loading branch information
chengjoey authored and erda-bot committed Nov 30, 2021
1 parent 50abce9 commit 6c5b017
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
20 changes: 20 additions & 0 deletions modules/dop/dao/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ func (Issue) TableName() string {
return "dice_issues"
}

type IssueSummary struct {
Total int `json:"total,omitempty"`
IssueType apistructs.IssueType `json:"issue_type,omitempty"`
State int64 `json:"state,omitempty"`
IterationID int64 `json:"iteration_id,omitempty"`
}

// GetCanUpdateFields 获取所有可以被主动更新的字段
func (i *Issue) GetCanUpdateFields() map[string]interface{} {
return map[string]interface{}{
Expand Down Expand Up @@ -434,6 +441,19 @@ func (client *DBClient) GetIssueSummary(iterationID int64, task, bug, requiremen
}
}

func (client *DBClient) ListIssueSummaryStates(projectID uint64, iterationIDS []int64) ([]IssueSummary, error) {
var summaries []IssueSummary
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
Expand Down
19 changes: 10 additions & 9 deletions modules/dop/endpoints/iteration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions modules/dop/endpoints/iteration_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
90 changes: 90 additions & 0 deletions modules/dop/services/iteration/iteration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 6c5b017

Please sign in to comment.