Skip to content

Issue gantt chart set up, issue inclusion relation #3259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .erda/migrations/cmdb/20211129-issue-relation-type.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE dice_issue_relation DROP INDEX issue_related;

ALTER TABLE dice_issue_relation ADD `type` VARCHAR(40) NOT NULL DEFAULT '' COMMENT 'issue relation type';

UPDATE dice_issue_relation SET `type` = 'connection' WHERE `type` = '';
4 changes: 4 additions & 0 deletions apistructs/component_protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ const (

// workbench
SubmitOrgOperationKey OperationKey = "submitOrg"

// issue gantt
Update OperationKey = "update"
ExpandNode OperationKey = "expandNode"
)

type ComponentProtocolParams interface{}
Expand Down
16 changes: 9 additions & 7 deletions apistructs/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,9 @@ func (r *IssueUpdateRequest) GetChangedFields(manHour string) map[string]interfa
if r.Severity != nil {
fields["severity"] = *r.Severity
}
if r.PlanStartedAt != nil {
fields["plan_started_at"] = r.PlanStartedAt
}
fields["plan_finished_at"] = r.PlanFinishedAt
if r.Assignee != nil {
fields["assignee"] = *r.Assignee
Expand All @@ -978,12 +981,12 @@ func (r *IssueUpdateRequest) GetChangedFields(manHour string) map[string]interfa
fields["stage"] = *r.TaskType
}
if r.ManHour != nil {
if r.ManHour.ThisElapsedTime != 0 {
// 开始时间为当天0点
timeStr := time.Now().Format("2006-01-02")
t, _ := time.ParseInLocation("2006-01-02", timeStr, time.Local)
fields["plan_started_at"] = t
}
// if r.ManHour.ThisElapsedTime != 0 {
// // 开始时间为当天0点
// timeStr := time.Now().Format("2006-01-02")
// t, _ := time.ParseInLocation("2006-01-02", timeStr, time.Local)
// fields["plan_started_at"] = t
// }
// ManHour 是否改变提前特殊处理
// 只有当预期时间或剩余时间发生改变时,才认为工时信息发生了改变
// 所花时间,开始时间,工作内容是实时内容,只在事件动态里记录就好,在dice_issue表中是没有意义的数据
Expand All @@ -1000,7 +1003,6 @@ func (r *IssueUpdateRequest) GetChangedFields(manHour string) map[string]interfa
fields["man_hour"] = r.ManHour.Convert2String()
}
}

return fields
}

Expand Down
16 changes: 16 additions & 0 deletions apistructs/issue_related.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ type IssueRelationCreateRequest struct {
RelatedIssue uint64 `json:"relatedIssues"`
Comment string `json:"comment"`
ProjectID int64 `json:"projectId"`
Type string `json:"type"`
}

const IssueRelationConnection = "connection"
const IssueRelationInclusion = "inclusion"

// Check 检查请求参数是否合法
func (irc *IssueRelationCreateRequest) Check() error {
if irc.IssueID == 0 {
Expand All @@ -37,6 +41,14 @@ func (irc *IssueRelationCreateRequest) Check() error {
return errors.New("projectId is required")
}

if len(irc.Type) == 0 {
return errors.New("type is required")
}

if irc.Type != IssueRelationConnection && irc.Type != IssueRelationInclusion {
return errors.New("invalid issue relation type")
}

return nil
}

Expand All @@ -52,3 +64,7 @@ type IssueRelations struct {
RelatingIssues []Issue
RelatedIssues []Issue
}

type IssueRelationRequest struct {
RelationTypes []string `schema:"type"`
}
13 changes: 13 additions & 0 deletions apistructs/issue_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ const (
IssueStateBelongClosed IssueStateBelong = "CLOSED" // 已关闭
)

func (s IssueStateBelong) GetFrontEndStatus() string {
switch s {
case IssueStateBelongOpen:
return "warning"
case IssueStateBelongWorking:
return "processing"
case IssueStateBelongDone:
return "success"
default:
return ""
}
}

type IssueStateRelation struct {
IssueStatus
StateRelation []int64 `json:"stateRelation"`
Expand Down
8 changes: 8 additions & 0 deletions conf/dop/dop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,11 @@ component-protocol.components.auto-test-scenes.scenesStagesOperations:
component-protocol.components.auto-test-scenes.addScenesButton:
component-protocol.components.auto-test-scenes.exportScenesButton:
component-protocol.components.auto-test-scenes.referSceneSetButton:

## issue-gantt
component-protocol.components.issue-gantt.page:
component-protocol.components.issue-gantt.filter:
component-protocol.components.issue-gantt.gantt:
component-protocol.components.issue-gantt.ganttContainer:
component-protocol.components.issue-gantt.topHead:
component-protocol.components.issue-gantt.issueAddButton:
1 change: 1 addition & 0 deletions conf/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ openapi-v1-routes:
- addon-mysql-account
- addon-mysql-consumer
- auto-test-scenes
- issue-gantt
- addr: http://localhost:8080
scenarios:
- demo
Expand Down
1 change: 1 addition & 0 deletions modules/dop/component-protocol/components/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
_ "github.com/erda-project/erda/modules/dop/component-protocol/components/auto-test-scenes"
_ "github.com/erda-project/erda/modules/dop/component-protocol/components/code-coverage"
_ "github.com/erda-project/erda/modules/dop/component-protocol/components/issue-dashboard"
_ "github.com/erda-project/erda/modules/dop/component-protocol/components/issue-gantt"
_ "github.com/erda-project/erda/modules/dop/component-protocol/components/issue-manage"
_ "github.com/erda-project/erda/modules/dop/component-protocol/components/scenes-import-record"
_ "github.com/erda-project/erda/modules/dop/component-protocol/components/test-dashboard"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 filter

import (
"github.com/erda-project/erda-infra/providers/component-protocol/cptype"
"github.com/erda-project/erda/apistructs"
"github.com/erda-project/erda/bundle"
"github.com/erda-project/erda/modules/dop/services/issue"
"github.com/erda-project/erda/modules/openapi/component-protocol/components/base"
"github.com/erda-project/erda/modules/openapi/component-protocol/components/filter"
)

type ComponentFilter struct {
sdk *cptype.SDK
bdl *bundle.Bundle
issueSvc *issue.Issue
filter.CommonFilter
State State `json:"state,omitempty"`
base.DefaultProvider

projectID uint64 `json:"-"`
Iterations []apistructs.Iteration `json:"-"`
Members []apistructs.Member `json:"-"`
}

type State struct {
Conditions []filter.PropCondition `json:"conditions,omitempty"`
Values FrontendConditions `json:"values,omitempty"`
}

type FrontendConditions struct {
IterationIDs []int64 `json:"iteration,omitempty"`
AssigneeIDs []string `json:"member,omitempty"`
LabelIDs []uint64 `json:"label,omitempty"`
}

const OperationKeyFilter filter.OperationKey = "filter"
185 changes: 185 additions & 0 deletions modules/dop/component-protocol/components/issue-gantt/filter/render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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 filter

import (
"context"
"strconv"
"time"

"github.com/erda-project/erda-infra/base/servicehub"
"github.com/erda-project/erda-infra/providers/component-protocol/cptype"
"github.com/erda-project/erda-infra/providers/component-protocol/utils/cputil"
"github.com/erda-project/erda/apistructs"
"github.com/erda-project/erda/bundle"
"github.com/erda-project/erda/modules/dop/component-protocol/types"
"github.com/erda-project/erda/modules/dop/services/issue"
"github.com/erda-project/erda/modules/openapi/component-protocol/components/base"
"github.com/erda-project/erda/modules/openapi/component-protocol/components/filter"
)

func init() {
base.InitProviderWithCreator("issue-gantt", "filter",
func() servicehub.Provider { return &ComponentFilter{} })
}

func (f *ComponentFilter) Render(ctx context.Context, c *cptype.Component, scenario cptype.Scenario, event cptype.ComponentEvent, gs *cptype.GlobalStateData) error {
f.sdk = cputil.SDK(ctx)
f.bdl = ctx.Value(types.GlobalCtxKeyBundle).(*bundle.Bundle)
f.issueSvc = ctx.Value(types.IssueService).(*issue.Issue)
projectID, err := strconv.ParseUint(cputil.GetInParamByKey(ctx, "projectId").(string), 10, 64)
if err != nil {
return err
}
f.projectID = projectID
iterations, iterationOptions, err := f.getPropIterationsOptions()
if err != nil {
return err
}
projectMemberOptions, err := f.getProjectMemberOptions()
if err != nil {
return err
}
labelOptions, err := f.getProjectLabelsOptions()
if err != nil {
return err
}

switch event.Operation {
case cptype.InitializeOperation:
f.Props = filter.Props{
Delay: 1000,
}
f.Operations = map[filter.OperationKey]filter.Operation{
OperationKeyFilter: {
Key: OperationKeyFilter,
Reload: true,
},
}
f.State.Values.IterationIDs = []int64{defaultIterationRetriever(iterations)}
}

f.State.Conditions = []filter.PropCondition{
{
EmptyText: "全部",
Fixed: true,
Key: "iteration",
Label: "迭代",
Options: iterationOptions,
Type: filter.PropConditionTypeSelect,
HaveFilter: true,
},
{
EmptyText: "全部",
Fixed: true,
Key: "member",
Label: "成员",
Options: projectMemberOptions,
Type: filter.PropConditionTypeSelect,
HaveFilter: true,
},
{
EmptyText: "全部",
Fixed: true,
Key: "label",
Label: "标签",
Options: labelOptions,
Type: filter.PropConditionTypeSelect,
HaveFilter: true,
},
}
return nil
}

func (f *ComponentFilter) getPropIterationsOptions() (map[int64]apistructs.Iteration, []filter.PropConditionOption, error) {
iterations, err := f.bdl.ListProjectIterations(apistructs.IterationPagingRequest{
PageNo: 1, PageSize: 1000,
ProjectID: f.projectID, State: apistructs.IterationStateUnfiled,
WithoutIssueSummary: true,
}, f.sdk.Identity.OrgID)
if err != nil {
return nil, nil, err
}
f.Iterations = append(iterations, apistructs.Iteration{
ID: -1,
Title: "待处理",
})
var options []filter.PropConditionOption
iterationMap := make(map[int64]apistructs.Iteration)
for _, iteration := range iterations {
options = append(options, filter.PropConditionOption{
Label: iteration.Title,
Value: iteration.ID,
})
iterationMap[iteration.ID] = iteration
}
return iterationMap, options, nil
}

func (f *ComponentFilter) getProjectMemberOptions() ([]filter.PropConditionOption, error) {
members, err := f.bdl.ListMembers(apistructs.MemberListRequest{
ScopeType: apistructs.ProjectScope,
ScopeID: int64(f.projectID),
PageNo: 1,
PageSize: 500,
})
if err != nil {
return nil, err
}
f.Members = members
var results []filter.PropConditionOption
for _, member := range members {
results = append(results, filter.PropConditionOption{
Label: func() string {
if member.Nick != "" {
return member.Nick
}
if member.Name != "" {
return member.Name
}
return member.Mobile
}(),
Value: member.UserID,
})
}
return results, nil
}

func defaultIterationRetriever(iterations map[int64]apistructs.Iteration) int64 {
for i, iteration := range iterations {
if !time.Now().Before(*iteration.StartedAt) && !time.Now().After(*iteration.FinishedAt) {
return i
}
}
return -1
}

func (f *ComponentFilter) getProjectLabelsOptions() ([]filter.PropConditionOption, error) {
labels, err := f.bdl.Labels(string(apistructs.LabelTypeIssue), f.projectID, f.sdk.Identity.UserID)
if err != nil {
return nil, err
}
if labels == nil {
return nil, nil
}
var options []filter.PropConditionOption
for _, label := range labels.List {
options = append(options, filter.PropConditionOption{
Label: label.Name,
Value: label.ID,
})
}
return options, nil
}
Loading