diff --git a/models/user/user_system.go b/models/user/user_system.go index 3b06e6b7ec..86fbab5745 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -71,7 +71,7 @@ const ( ProjectWorkflowsUserEmail = "workflows@gitea.io" ) -func IsGiteaWorkflowsUserName(name string) bool { +func IsProjectWorkflowsUserName(name string) bool { return strings.EqualFold(name, ProjectWorkflowsUserName) } @@ -103,7 +103,7 @@ func GetSystemUserByName(name string) *User { if IsGiteaActionsUserName(name) { return NewActionsUser() } - if IsGiteaWorkflowsUserName(name) { + if IsProjectWorkflowsUserName(name) { return NewProjectWorkflowsUser() } return nil diff --git a/models/user/user_system_test.go b/models/user/user_system_test.go index 5aa3fa463c..9bc9d4552f 100644 --- a/models/user/user_system_test.go +++ b/models/user/user_system_test.go @@ -25,6 +25,13 @@ func TestSystemUser(t *testing.T) { assert.True(t, u.IsGiteaActions()) assert.True(t, IsGiteaActionsUserName("Gitea-actionS")) - _, err = GetPossibleUserByID(t.Context(), -3) + u, err = GetPossibleUserByID(t.Context(), -3) + require.NoError(t, err) + assert.Equal(t, "project-workflows", u.Name) + assert.Equal(t, "project-workflows", u.LowerName) + assert.True(t, u.IsProjectWorkflows()) + assert.True(t, IsProjectWorkflowsUserName("Project-Workflows")) + + _, err = GetPossibleUserByID(t.Context(), -4) require.Error(t, err) } diff --git a/routers/web/projects/workflows.go b/routers/web/projects/workflows.go index 89edc874e6..11ea90baac 100644 --- a/routers/web/projects/workflows.go +++ b/routers/web/projects/workflows.go @@ -4,7 +4,6 @@ package projects import ( - stdCtx "context" "io" "net/http" "strconv" @@ -17,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/context" + project_service "code.gitea.io/gitea/services/projects" ) var ( @@ -24,85 +24,6 @@ var ( tmplOrgWorkflows = templates.TplName("org/projects/workflows") ) -// getFilterSummary returns a human-readable summary of the filters -func getFilterSummary(ctx stdCtx.Context, filters []project_model.WorkflowFilter) string { - if len(filters) == 0 { - return "" - } - - var summary strings.Builder - labelIDs := make([]int64, 0) - for _, filter := range filters { - switch filter.Type { - case project_model.WorkflowFilterTypeIssueType: - switch filter.Value { - case "issue": - if summary.Len() > 0 { - summary.WriteString(" ") - } - summary.WriteString("(Issues only)") - case "pull_request": - if summary.Len() > 0 { - summary.WriteString(" ") - } - summary.WriteString("(Pull requests only)") - } - 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 - } - if summary.Len() > 0 { - summary.WriteString(" ") - } - summary.WriteString("(Source: " + col.Title + ")") - case project_model.WorkflowFilterTypeTargetColumn: - 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 - } - if summary.Len() > 0 { - summary.WriteString(" ") - } - summary.WriteString("(Target: " + col.Title + ")") - case project_model.WorkflowFilterTypeLabels: - labelID, _ := strconv.ParseInt(filter.Value, 10, 64) - if labelID > 0 { - labelIDs = append(labelIDs, labelID) - } - } - } - if len(labelIDs) > 0 { - labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs) - if err != nil { - log.Error("GetLabelsByIDs: %v", err) - } else { - if summary.Len() > 0 { - summary.WriteString(" ") - } - summary.WriteString("(Labels: ") - for i, label := range labels { - summary.WriteString(label.Name) - if i < len(labels)-1 { - summary.WriteString(", ") - } - } - summary.WriteString(")") - } - } - return summary.String() -} - // convertFormToFilters converts form filters to WorkflowFilter objects func convertFormToFilters(formFilters map[string]any) []project_model.WorkflowFilter { filters := make([]project_model.WorkflowFilter, 0) @@ -111,7 +32,7 @@ func convertFormToFilters(formFilters map[string]any) []project_model.WorkflowFi switch key { case "labels": // Handle labels array - if labelInterfaces, ok := value.([]interface{}); ok && len(labelInterfaces) > 0 { + if labelInterfaces, ok := value.([]any); ok && len(labelInterfaces) > 0 { for _, labelInterface := range labelInterfaces { if label, ok := labelInterface.(string); ok && label != "" { filters = append(filters, project_model.WorkflowFilter{ @@ -152,7 +73,7 @@ func convertFormToActions(formActions map[string]any) []project_model.WorkflowAc } } case "add_labels": - // Handle both []string and []interface{} from JSON unmarshaling + // Handle both []string and []any from JSON unmarshaling if labels, ok := value.([]string); ok && len(labels) > 0 { for _, label := range labels { if label != "" { @@ -162,7 +83,7 @@ func convertFormToActions(formActions map[string]any) []project_model.WorkflowAc }) } } - } else if labelInterfaces, ok := value.([]interface{}); ok && len(labelInterfaces) > 0 { + } else if labelInterfaces, ok := value.([]any); ok && len(labelInterfaces) > 0 { for _, labelInterface := range labelInterfaces { if label, ok := labelInterface.(string); ok && label != "" { actions = append(actions, project_model.WorkflowAction{ @@ -173,7 +94,7 @@ func convertFormToActions(formActions map[string]any) []project_model.WorkflowAc } } case "remove_labels": - // Handle both []string and []interface{} from JSON unmarshaling + // Handle both []string and []any from JSON unmarshaling if labels, ok := value.([]string); ok && len(labels) > 0 { for _, label := range labels { if label != "" { @@ -183,7 +104,7 @@ func convertFormToActions(formActions map[string]any) []project_model.WorkflowAc }) } } - } else if labelInterfaces, ok := value.([]interface{}); ok && len(labelInterfaces) > 0 { + } else if labelInterfaces, ok := value.([]any); ok && len(labelInterfaces) > 0 { for _, labelInterface := range labelInterfaces { if label, ok := labelInterface.(string); ok && label != "" { actions = append(actions, project_model.WorkflowAction{ @@ -243,7 +164,7 @@ func WorkflowsEvents(ctx *context.Context) { Capabilities project_model.WorkflowEventCapabilities `json:"capabilities"` Filters []project_model.WorkflowFilter `json:"filters"` Actions []project_model.WorkflowAction `json:"actions"` - FilterSummary string `json:"filter_summary"` // Human readable filter description + Summary string `json:"summary"` // Human readable filter description Enabled bool `json:"enabled"` IsConfigured bool `json:"isConfigured"` // Whether this workflow is configured/saved } @@ -263,7 +184,7 @@ func WorkflowsEvents(ctx *context.Context) { if len(existingWorkflows) > 0 { // Add all existing workflows for this event for _, wf := range existingWorkflows { - filterSummary := getFilterSummary(ctx, wf.WorkflowFilters) + workflowSummary := project_service.GetWorkflowSummary(ctx, wf) outputWorkflows = append(outputWorkflows, &WorkflowConfig{ ID: wf.ID, EventID: strconv.FormatInt(wf.ID, 10), @@ -272,7 +193,7 @@ func WorkflowsEvents(ctx *context.Context) { Capabilities: capabilities[event], Filters: wf.WorkflowFilters, Actions: wf.WorkflowActions, - FilterSummary: filterSummary, + Summary: workflowSummary, Enabled: wf.Enabled, IsConfigured: true, }) @@ -285,7 +206,7 @@ func WorkflowsEvents(ctx *context.Context) { DisplayName: string(ctx.Tr(event.LangKey())), WorkflowEvent: string(event), Capabilities: capabilities[event], - FilterSummary: "", + Summary: "", Enabled: true, // Default to enabled for new workflows IsConfigured: false, }) @@ -537,17 +458,17 @@ func WorkflowsPost(ctx *context.Context) { } // Return the newly created workflow with filter summary - filterSummary := getFilterSummary(ctx, wf.WorkflowFilters) + workflowSummary := project_service.GetWorkflowSummary(ctx, wf) ctx.JSON(http.StatusOK, map[string]any{ "success": true, "workflow": map[string]any{ - "id": wf.ID, - "event_id": strconv.FormatInt(wf.ID, 10), - "display_name": string(ctx.Tr(wf.WorkflowEvent.LangKey())), - "filters": wf.WorkflowFilters, - "actions": wf.WorkflowActions, - "filter_summary": filterSummary, - "enabled": wf.Enabled, + "id": wf.ID, + "event_id": strconv.FormatInt(wf.ID, 10), + "display_name": string(ctx.Tr(wf.WorkflowEvent.LangKey())), + "filters": wf.WorkflowFilters, + "actions": wf.WorkflowActions, + "summary": workflowSummary, + "enabled": wf.Enabled, }, }) } else { @@ -570,17 +491,17 @@ func WorkflowsPost(ctx *context.Context) { } // Return the updated workflow with filter summary - filterSummary := getFilterSummary(ctx, wf.WorkflowFilters) + workflowSummary := project_service.GetWorkflowSummary(ctx, wf) ctx.JSON(http.StatusOK, map[string]any{ "success": true, "workflow": map[string]any{ - "id": wf.ID, - "event_id": strconv.FormatInt(wf.ID, 10), - "display_name": string(ctx.Tr(wf.WorkflowEvent.LangKey())) + filterSummary, - "filters": wf.WorkflowFilters, - "actions": wf.WorkflowActions, - "filter_summary": filterSummary, - "enabled": wf.Enabled, + "id": wf.ID, + "event_id": strconv.FormatInt(wf.ID, 10), + "display_name": string(ctx.Tr(wf.WorkflowEvent.LangKey())), + "filters": wf.WorkflowFilters, + "actions": wf.WorkflowActions, + "summary": workflowSummary, + "enabled": wf.Enabled, }, }) } diff --git a/services/projects/workflow.go b/services/projects/workflow.go new file mode 100644 index 0000000000..27040f8eed --- /dev/null +++ b/services/projects/workflow.go @@ -0,0 +1,94 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package projects + +import ( + "context" + "strconv" + "strings" + + issues_model "code.gitea.io/gitea/models/issues" + project_model "code.gitea.io/gitea/models/project" + "code.gitea.io/gitea/modules/log" +) + +// GetWorkflowSummary returns a human-readable summary of the workflow +func GetWorkflowSummary(ctx context.Context, wf *project_model.Workflow) string { + filters := wf.WorkflowFilters + if len(filters) == 0 { + return "" + } + + var summary strings.Builder + labelIDs := make([]int64, 0) + for _, filter := range filters { + switch filter.Type { + case project_model.WorkflowFilterTypeIssueType: + switch filter.Value { + case "issue": + if summary.Len() > 0 { + summary.WriteString(" ") + } + summary.WriteString("(Issues only)") + case "pull_request": + if summary.Len() > 0 { + summary.WriteString(" ") + } + summary.WriteString("(Pull requests only)") + } + 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 + } + if summary.Len() > 0 { + summary.WriteString(" ") + } + summary.WriteString("(Source: " + col.Title + ")") + case project_model.WorkflowFilterTypeTargetColumn: + 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 + } + if summary.Len() > 0 { + summary.WriteString(" ") + } + summary.WriteString("(Target: " + col.Title + ")") + case project_model.WorkflowFilterTypeLabels: + labelID, _ := strconv.ParseInt(filter.Value, 10, 64) + if labelID > 0 { + labelIDs = append(labelIDs, labelID) + } + } + } + if len(labelIDs) > 0 { + labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs) + if err != nil { + log.Error("GetLabelsByIDs: %v", err) + } else { + if summary.Len() > 0 { + summary.WriteString(" ") + } + summary.WriteString("(Labels: ") + for i, label := range labels { + summary.WriteString(label.Name) + if i < len(labels)-1 { + summary.WriteString(", ") + } + } + summary.WriteString(")") + } + } + return summary.String() +} diff --git a/web_src/js/components/projects/ProjectWorkflow.vue b/web_src/js/components/projects/ProjectWorkflow.vue index 23bd515945..cbb19b479f 100644 --- a/web_src/js/components/projects/ProjectWorkflow.vue +++ b/web_src/js/components/projects/ProjectWorkflow.vue @@ -351,7 +351,7 @@ const createNewWorkflow = (eventType: any, capabilities: any, displayName: any) capabilities, filters: [] as any[], actions: [] as any[], - filter_summary: '', + summary: '', workflow_event: eventType, enabled: true, // Ensure new workflows are enabled by default }; @@ -683,8 +683,8 @@ onUnmounted(() => {