From 323a24e12c83ef46a0360174cea35f83511e3f92 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 24 Oct 2025 18:37:19 -0700 Subject: [PATCH] Fix bug --- models/project/issue.go | 5 ---- models/project/workflows.go | 32 +++++----------------- routers/web/projects/workflows.go | 13 ++++++++- services/notify/notifier.go | 2 +- services/notify/notify.go | 4 +-- services/notify/null.go | 2 +- services/projects/issue.go | 24 ++++++++++------ services/projects/workflow_notifier.go | 38 ++++++++++++++++++-------- 8 files changed, 64 insertions(+), 56 deletions(-) diff --git a/models/project/issue.go b/models/project/issue.go index 936c05ef73..de395eb828 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -41,11 +41,6 @@ func AddIssueToColumn(ctx context.Context, issueID int64, newColumn *Column) err }) } -func MoveIssueToAnotherColumn(ctx context.Context, issueID int64, newColumn *Column) error { - _, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id=? WHERE issue_id=?", newColumn.ID, issueID) - return err -} - func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error { if c.ProjectID != newColumn.ProjectID { return errors.New("columns have to be in the same project") diff --git a/models/project/workflows.go b/models/project/workflows.go index 49ce17c1ce..5a03316bec 100644 --- a/models/project/workflows.go +++ b/models/project/workflows.go @@ -65,34 +65,16 @@ func (we WorkflowEvent) LangKey() string { } func (we WorkflowEvent) EventID() string { - switch we { - case WorkflowEventItemOpened: - return "item_opened" - case WorkflowEventItemAddedToProject: - return "item_added_to_project" - case WorkflowEventItemReopened: - return "item_reopened" - case WorkflowEventItemClosed: - return "item_closed" - case WorkflowEventItemColumnChanged: - return "item_column_changed" - case WorkflowEventCodeChangesRequested: - return "code_changes_requested" - case WorkflowEventCodeReviewApproved: - return "code_review_approved" - case WorkflowEventPullRequestMerged: - return "pull_request_merged" - default: - return string(we) - } + return string(we) } type WorkflowFilterType string const ( - WorkflowFilterTypeIssueType WorkflowFilterType = "issue_type" // issue, pull_request, etc. - WorkflowFilterTypeColumn WorkflowFilterType = "target_column" // target column for item_column_changed event - WorkflowFilterTypeLabels WorkflowFilterType = "labels" // filter by issue/PR labels + WorkflowFilterTypeIssueType WorkflowFilterType = "issue_type" // issue, pull_request, etc. + WorkflowFilterTypeSourceColumn WorkflowFilterType = "source_column" // source column for item_column_changed event + WorkflowFilterTypeTargetColumn WorkflowFilterType = "target_column" // target column for item_column_changed event + WorkflowFilterTypeLabels WorkflowFilterType = "labels" // filter by issue/PR labels ) type WorkflowFilter struct { @@ -129,7 +111,7 @@ func GetWorkflowEventCapabilities() map[WorkflowEvent]WorkflowEventCapabilities }, WorkflowEventItemAddedToProject: { AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeIssueType, WorkflowFilterTypeLabels}, - AvailableActions: []WorkflowActionType{WorkflowActionTypeColumn, WorkflowActionTypeAddLabels, WorkflowActionTypeRemoveLabels}, + AvailableActions: []WorkflowActionType{WorkflowActionTypeColumn, WorkflowActionTypeAddLabels, WorkflowActionTypeRemoveLabels, WorkflowActionTypeIssueState}, }, WorkflowEventItemReopened: { AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeIssueType, WorkflowFilterTypeLabels}, @@ -140,7 +122,7 @@ func GetWorkflowEventCapabilities() map[WorkflowEvent]WorkflowEventCapabilities AvailableActions: []WorkflowActionType{WorkflowActionTypeColumn, WorkflowActionTypeAddLabels, WorkflowActionTypeRemoveLabels}, }, WorkflowEventItemColumnChanged: { - AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeIssueType, WorkflowFilterTypeColumn, WorkflowFilterTypeLabels}, + AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeIssueType, WorkflowFilterTypeSourceColumn, WorkflowFilterTypeTargetColumn, WorkflowFilterTypeLabels}, AvailableActions: []WorkflowActionType{WorkflowActionTypeAddLabels, WorkflowActionTypeRemoveLabels, WorkflowActionTypeIssueState}, }, WorkflowEventCodeChangesRequested: { diff --git a/routers/web/projects/workflows.go b/routers/web/projects/workflows.go index 47e1b7d5e2..79543bd8c3 100644 --- a/routers/web/projects/workflows.go +++ b/routers/web/projects/workflows.go @@ -42,7 +42,18 @@ func getFilterSummary(ctx stdCtx.Context, filters []project_model.WorkflowFilter case "pull_request": summary.WriteString(" (Pull requests only)") } - case project_model.WorkflowFilterTypeColumn: + case project_model.WorkflowFilterTypeSourceColumn: + columnID, _ := strconv.ParseInt(filter.Value, 10, 64) + if columnID <= 0 { + continue + } + col, err := project_model.GetColumn(ctx, columnID) + if err != nil { + log.Error("GetColumn: %v", err) + continue + } + summary.WriteString(" (Source Column: " + col.Title + ")") + case project_model.WorkflowFilterTypeTargetColumn: columnID, _ := strconv.ParseInt(filter.Value, 10, 64) if columnID <= 0 { continue diff --git a/services/notify/notifier.go b/services/notify/notifier.go index c0c6482c26..fcb27450c4 100644 --- a/services/notify/notifier.go +++ b/services/notify/notifier.go @@ -43,7 +43,7 @@ type Notifier interface { IssueChangeLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, addedLabels, removedLabels []*issues_model.Label) IssueChangeProjects(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, newProject *project_model.Project) - IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, newColumnID int64) + IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldColumnID, newColumnID int64) NewPullRequest(ctx context.Context, pr *issues_model.PullRequest, mentions []*user_model.User) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) diff --git a/services/notify/notify.go b/services/notify/notify.go index 159288108f..7d90ccfcf4 100644 --- a/services/notify/notify.go +++ b/services/notify/notify.go @@ -282,9 +282,9 @@ func IssueChangeProjects(ctx context.Context, doer *user_model.User, issue *issu } } -func IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, newColumnID int64) { +func IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldColumnID, newColumnID int64) { for _, notifier := range notifiers { - notifier.IssueChangeProjectColumn(ctx, doer, issue, newColumnID) + notifier.IssueChangeProjectColumn(ctx, doer, issue, oldColumnID, newColumnID) } } diff --git a/services/notify/null.go b/services/notify/null.go index 52db060d0d..9c92af7b3b 100644 --- a/services/notify/null.go +++ b/services/notify/null.go @@ -147,7 +147,7 @@ func (*NullNotifier) IssueChangeLabels(ctx context.Context, doer *user_model.Use func (*NullNotifier) IssueChangeProjects(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, newProject *project_model.Project) { } -func (*NullNotifier) IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, newColumnID int64) { +func (*NullNotifier) IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldColumnID, newColumnID int64) { } // CreateRepository places a place holder function diff --git a/services/projects/issue.go b/services/projects/issue.go index 1832f47447..41736cc8de 100644 --- a/services/projects/issue.go +++ b/services/projects/issue.go @@ -36,6 +36,7 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum if err != nil { return err } + oldColumnIDsMap := make(map[int64]int64, len(issues)) if err := db.WithTx(ctx, func(ctx context.Context) error { if _, err := issues.LoadRepositories(ctx); err != nil { @@ -62,6 +63,7 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum if err != nil { return err } + oldColumnIDsMap[issueID] = projectColumnID if projectColumnID != column.ID { // add timeline to issue @@ -90,7 +92,7 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum } for _, issue := range issues { - notify.IssueChangeProjectColumn(ctx, doer, issue, column.ID) + notify.IssueChangeProjectColumn(ctx, doer, issue, oldColumnIDsMap[issue.ID], column.ID) } return nil @@ -216,13 +218,17 @@ func LoadIssueNumbersForProject(ctx context.Context, project *project_model.Proj return nil } -func MoveIssueToAnotherColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, column *project_model.Column) error { +func MoveIssueToAnotherColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, newColumn *project_model.Column) error { + oldColumnID, err := issue.ProjectColumnID(ctx) + if err != nil { + return err + } if err := db.WithTx(ctx, func(ctx context.Context) error { - if err := project_model.MoveIssueToAnotherColumn(ctx, issue.ID, column); err != nil { + if _, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id=? WHERE issue_id=?", newColumn.ID, issue.ID); err != nil { return err } - if err := column.LoadProject(ctx); err != nil { + if err := newColumn.LoadProject(ctx); err != nil { return err } @@ -232,10 +238,10 @@ func MoveIssueToAnotherColumn(ctx context.Context, doer *user_model.User, issue Doer: doer, Repo: issue.Repo, Issue: issue, - ProjectID: column.ProjectID, - ProjectTitle: column.Project.Title, - ProjectColumnID: column.ID, - ProjectColumnTitle: column.Title, + ProjectID: newColumn.ProjectID, + ProjectTitle: newColumn.Project.Title, + ProjectColumnID: newColumn.ID, + ProjectColumnTitle: newColumn.Title, }); err != nil { return err } @@ -244,6 +250,6 @@ func MoveIssueToAnotherColumn(ctx context.Context, doer *user_model.User, issue return err } - notify.IssueChangeProjectColumn(ctx, doer, issue, column.ID) + notify.IssueChangeProjectColumn(ctx, doer, issue, oldColumnID, newColumn.ID) return nil } diff --git a/services/projects/workflow_notifier.go b/services/projects/workflow_notifier.go index 65ea72a8f8..3866b9c38b 100644 --- a/services/projects/workflow_notifier.go +++ b/services/projects/workflow_notifier.go @@ -55,7 +55,7 @@ func (m *workflowNotifier) NewIssue(ctx context.Context, issue *issues_model.Iss // Find workflows for the ItemOpened event for _, workflow := range workflows { if workflow.WorkflowEvent == project_model.WorkflowEventItemOpened { - fireIssueWorkflow(ctx, workflow, issue, 0) + fireIssueWorkflow(ctx, workflow, issue, 0, 0) } } } @@ -92,7 +92,7 @@ func (m *workflowNotifier) IssueChangeStatus(ctx context.Context, doer *user_mod // Find workflows for the specific event for _, workflow := range workflows { if workflow.WorkflowEvent == workflowEvent { - fireIssueWorkflow(ctx, workflow, issue, 0) + fireIssueWorkflow(ctx, workflow, issue, 0, 0) } } } @@ -124,12 +124,12 @@ func (*workflowNotifier) IssueChangeProjects(ctx context.Context, doer *user_mod // Find workflows for the ItemOpened event for _, workflow := range workflows { if workflow.WorkflowEvent == project_model.WorkflowEventItemAddedToProject { - fireIssueWorkflow(ctx, workflow, issue, 0) + fireIssueWorkflow(ctx, workflow, issue, 0, 0) } } } -func (*workflowNotifier) IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, newColumnID int64) { +func (*workflowNotifier) IssueChangeProjectColumn(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldColumnID, newColumnID int64) { if err := issue.LoadRepo(ctx); err != nil { log.Error("IssueChangeStatus: LoadRepo: %v", err) return @@ -158,7 +158,7 @@ func (*workflowNotifier) IssueChangeProjectColumn(ctx context.Context, doer *use // Find workflows for the ItemColumnChanged event for _, workflow := range workflows { if workflow.WorkflowEvent == project_model.WorkflowEventItemColumnChanged { - fireIssueWorkflow(ctx, workflow, issue, newColumnID) + fireIssueWorkflow(ctx, workflow, issue, oldColumnID, newColumnID) } } } @@ -192,7 +192,7 @@ func (*workflowNotifier) MergePullRequest(ctx context.Context, doer *user_model. // Find workflows for the PullRequestMerged event for _, workflow := range workflows { if workflow.WorkflowEvent == project_model.WorkflowEventPullRequestMerged { - fireIssueWorkflow(ctx, workflow, issue, 0) + fireIssueWorkflow(ctx, workflow, issue, 0, 0) } } } @@ -231,12 +231,12 @@ func (*workflowNotifier) PullRequestReview(ctx context.Context, pr *issues_model for _, workflow := range workflows { if (workflow.WorkflowEvent == project_model.WorkflowEventCodeChangesRequested && review.Type == issues_model.ReviewTypeReject) || (workflow.WorkflowEvent == project_model.WorkflowEventCodeReviewApproved && review.Type == issues_model.ReviewTypeApprove) { - fireIssueWorkflow(ctx, workflow, issue, 0) + fireIssueWorkflow(ctx, workflow, issue, 0, 0) } } } -func fireIssueWorkflow(ctx context.Context, workflow *project_model.Workflow, issue *issues_model.Issue, columnID int64) { +func fireIssueWorkflow(ctx context.Context, workflow *project_model.Workflow, issue *issues_model.Issue, sourceColumnID, targetColumnID int64) { if !workflow.Enabled { return } @@ -247,7 +247,7 @@ func fireIssueWorkflow(ctx context.Context, workflow *project_model.Workflow, is return } - if !matchWorkflowsFilters(workflow, issue, columnID) { + if !matchWorkflowsFilters(workflow, issue, sourceColumnID, targetColumnID) { return } @@ -255,7 +255,7 @@ func fireIssueWorkflow(ctx context.Context, workflow *project_model.Workflow, is } // matchWorkflowsFilters checks if the issue matches all filters of the workflow -func matchWorkflowsFilters(workflow *project_model.Workflow, issue *issues_model.Issue, columnID int64) bool { +func matchWorkflowsFilters(workflow *project_model.Workflow, issue *issues_model.Issue, sourceColumnID, targetColumnID int64) bool { for _, filter := range workflow.WorkflowFilters { switch filter.Type { case project_model.WorkflowFilterTypeIssueType: @@ -270,7 +270,7 @@ func matchWorkflowsFilters(workflow *project_model.Workflow, issue *issues_model if filter.Value == "pull_request" && !issue.IsPull { return false } - case project_model.WorkflowFilterTypeColumn: + case project_model.WorkflowFilterTypeTargetColumn: // If filter value is empty, match all columns if filter.Value == "" { continue @@ -281,7 +281,21 @@ func matchWorkflowsFilters(workflow *project_model.Workflow, issue *issues_model return false } // For column changed event, check against the new column ID - if columnID > 0 && columnID != filterColumnID { + if targetColumnID > 0 && targetColumnID != filterColumnID { + return false + } + case project_model.WorkflowFilterTypeSourceColumn: + // If filter value is empty, match all columns + if filter.Value == "" { + continue + } + filterColumnID, _ := strconv.ParseInt(filter.Value, 10, 64) + if filterColumnID == 0 { + log.Error("Invalid column ID: %s", filter.Value) + return false + } + // For column changed event, check against the new column ID + if sourceColumnID > 0 && sourceColumnID != filterColumnID { return false } case project_model.WorkflowFilterTypeLabels: