improvements

pull/30205/head
Lunny Xiao 2025-10-23 22:24:52 -07:00
parent 623388cef9
commit c2419f8c5b
No known key found for this signature in database
GPG Key ID: C3B7C91B632F738A
7 changed files with 61 additions and 39 deletions

View File

@ -70,7 +70,6 @@ func (err ErrProjectNotExist) Unwrap() error {
type ErrProjectColumnNotExist struct { type ErrProjectColumnNotExist struct {
ColumnID int64 ColumnID int64
ProjectID int64 ProjectID int64
Name string
} }
// IsErrProjectColumnNotExist checks if an error is a ErrProjectColumnNotExist // IsErrProjectColumnNotExist checks if an error is a ErrProjectColumnNotExist
@ -80,8 +79,8 @@ func IsErrProjectColumnNotExist(err error) bool {
} }
func (err ErrProjectColumnNotExist) Error() string { func (err ErrProjectColumnNotExist) Error() string {
if err.ProjectID > 0 && len(err.Name) > 0 { if err.ProjectID > 0 {
return fmt.Sprintf("project column does not exist [project_id: %d, name: %s]", err.ProjectID, err.Name) return fmt.Sprintf("project column does not exist [project_id: %d, column_id: %d]", err.ProjectID, err.ColumnID)
} }
return fmt.Sprintf("project column does not exist [id: %d]", err.ColumnID) return fmt.Sprintf("project column does not exist [id: %d]", err.ColumnID)
} }

View File

@ -106,7 +106,7 @@ const (
WorkflowActionTypeColumn WorkflowActionType = "column" // add the item to the project's column WorkflowActionTypeColumn WorkflowActionType = "column" // add the item to the project's column
WorkflowActionTypeAddLabels WorkflowActionType = "add_labels" // choose one or more labels WorkflowActionTypeAddLabels WorkflowActionType = "add_labels" // choose one or more labels
WorkflowActionTypeRemoveLabels WorkflowActionType = "remove_labels" // choose one or more labels WorkflowActionTypeRemoveLabels WorkflowActionType = "remove_labels" // choose one or more labels
WorkflowActionTypeClose WorkflowActionType = "close" // close the issue WorkflowActionTypeIssueState WorkflowActionType = "issue_state" // change the issue state (reopen/close)
) )
type WorkflowAction struct { type WorkflowAction struct {
@ -141,7 +141,7 @@ func GetWorkflowEventCapabilities() map[WorkflowEvent]WorkflowEventCapabilities
}, },
WorkflowEventItemColumnChanged: { WorkflowEventItemColumnChanged: {
AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeIssueType, WorkflowFilterTypeColumn, WorkflowFilterTypeLabels}, AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeIssueType, WorkflowFilterTypeColumn, WorkflowFilterTypeLabels},
AvailableActions: []WorkflowActionType{WorkflowActionTypeAddLabels, WorkflowActionTypeRemoveLabels, WorkflowActionTypeClose}, AvailableActions: []WorkflowActionType{WorkflowActionTypeAddLabels, WorkflowActionTypeRemoveLabels, WorkflowActionTypeIssueState},
}, },
WorkflowEventCodeChangesRequested: { WorkflowEventCodeChangesRequested: {
AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeLabels}, // only applies to pull requests AvailableFilters: []WorkflowFilterType{WorkflowFilterTypeLabels}, // only applies to pull requests

View File

@ -4,6 +4,7 @@
package projects package projects
import ( import (
stdCtx "context"
"errors" "errors"
"io" "io"
"net/http" "net/http"
@ -25,22 +26,56 @@ var (
) )
// getFilterSummary returns a human-readable summary of the filters // getFilterSummary returns a human-readable summary of the filters
func getFilterSummary(filters []project_model.WorkflowFilter) string { func getFilterSummary(ctx stdCtx.Context, filters []project_model.WorkflowFilter) string {
if len(filters) == 0 { if len(filters) == 0 {
return "" return ""
} }
var summary strings.Builder
labelIDs := make([]int64, 0)
for _, filter := range filters { for _, filter := range filters {
if filter.Type == "scope" { switch filter.Type {
case project_model.WorkflowFilterTypeIssueType:
switch filter.Value { switch filter.Value {
case "issue": case "issue":
return " (Issues only)" summary.WriteString(" (Issues only)")
case "pull_request": case "pull_request":
return " (Pull requests only)" summary.WriteString(" (Pull requests only)")
}
case project_model.WorkflowFilterTypeColumn:
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(" (Column: " + col.Title + ")")
case project_model.WorkflowFilterTypeLabels:
labelID, _ := strconv.ParseInt(filter.Value, 10, 64)
if labelID > 0 {
labelIDs = append(labelIDs, labelID)
} }
} }
} }
return "" if len(labelIDs) > 0 {
labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs)
if err != nil {
log.Error("GetLabelsByIDs: %v", err)
} else {
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 // convertFormToFilters converts form filters to WorkflowFilter objects
@ -133,28 +168,16 @@ func convertFormToActions(formActions map[string]any) []project_model.WorkflowAc
} }
} }
} }
case "issueState": case "issue_state":
if strValue, ok := value.(string); ok { if strValue, ok := value.(string); ok {
switch strings.ToLower(strValue) { v := strings.ToLower(strValue)
case "close", "closed", "true": if v == "close" || v == "reopen" {
actions = append(actions, project_model.WorkflowAction{ actions = append(actions, project_model.WorkflowAction{
Type: project_model.WorkflowActionTypeClose, Type: project_model.WorkflowActionTypeIssueState,
Value: "close", Value: v,
})
case "reopen", "open", "false":
actions = append(actions, project_model.WorkflowAction{
Type: project_model.WorkflowActionTypeClose,
Value: "reopen",
}) })
} }
} }
case "closeIssue":
if boolValue, ok := value.(bool); ok && boolValue {
actions = append(actions, project_model.WorkflowAction{
Type: project_model.WorkflowActionTypeClose,
Value: "close",
})
}
} }
} }
@ -216,7 +239,7 @@ func WorkflowsEvents(ctx *context.Context) {
if len(existingWorkflows) > 0 { if len(existingWorkflows) > 0 {
// Add all existing workflows for this event // Add all existing workflows for this event
for _, wf := range existingWorkflows { for _, wf := range existingWorkflows {
filterSummary := getFilterSummary(wf.WorkflowFilters) filterSummary := getFilterSummary(ctx, wf.WorkflowFilters)
outputWorkflows = append(outputWorkflows, &WorkflowConfig{ outputWorkflows = append(outputWorkflows, &WorkflowConfig{
ID: wf.ID, ID: wf.ID,
EventID: strconv.FormatInt(wf.ID, 10), EventID: strconv.FormatInt(wf.ID, 10),
@ -485,7 +508,7 @@ func WorkflowsPost(ctx *context.Context) {
} }
// Return the newly created workflow with filter summary // Return the newly created workflow with filter summary
filterSummary := getFilterSummary(wf.WorkflowFilters) filterSummary := getFilterSummary(ctx, wf.WorkflowFilters)
ctx.JSON(http.StatusOK, map[string]any{ ctx.JSON(http.StatusOK, map[string]any{
"success": true, "success": true,
"workflow": map[string]any{ "workflow": map[string]any{
@ -518,7 +541,7 @@ func WorkflowsPost(ctx *context.Context) {
} }
// Return the updated workflow with filter summary // Return the updated workflow with filter summary
filterSummary := getFilterSummary(wf.WorkflowFilters) filterSummary := getFilterSummary(ctx, wf.WorkflowFilters)
ctx.JSON(http.StatusOK, map[string]any{ ctx.JSON(http.StatusOK, map[string]any{
"success": true, "success": true,
"workflow": map[string]any{ "workflow": map[string]any{

View File

@ -447,7 +447,7 @@ func UpdateIssueProject(ctx *context.Context) {
if issue.Project != nil && issue.Project.ID == projectID { if issue.Project != nil && issue.Project.ID == projectID {
continue continue
} }
if err := issues_servie.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil { if err := issues_servie.AssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil {
if errors.Is(err, util.ErrPermissionDenied) { if errors.Is(err, util.ErrPermissionDenied) {
continue continue
} }

View File

@ -12,7 +12,7 @@ import (
"code.gitea.io/gitea/services/notify" "code.gitea.io/gitea/services/notify"
) )
func IssueAssignOrRemoveProject(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, projectID int64, position int) error { func AssignOrRemoveProject(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, projectID int64, position int) error {
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, doer, projectID, 0); err != nil { if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, doer, projectID, 0); err != nil {
return err return err
} }

View File

@ -424,8 +424,8 @@ func executeWorkflowActions(ctx context.Context, workflow *project_model.Workflo
} }
continue continue
} }
case project_model.WorkflowActionTypeClose: case project_model.WorkflowActionTypeIssueState:
if strings.EqualFold(action.Value, "reopen") || strings.EqualFold(action.Value, "false") { if strings.EqualFold(action.Value, "reopen") {
if issue.IsClosed { if issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, user_model.NewProjectWorkflowsUser(), ""); err != nil { if err := issue_service.ReopenIssue(ctx, issue, user_model.NewProjectWorkflowsUser(), ""); err != nil {
log.Error("ReopenIssue: %v", err) log.Error("ReopenIssue: %v", err)
@ -433,7 +433,7 @@ func executeWorkflowActions(ctx context.Context, workflow *project_model.Workflo
} }
issue.IsClosed = false issue.IsClosed = false
} }
} else { } else if strings.EqualFold(action.Value, "close") {
if !issue.IsClosed { if !issue.IsClosed {
if err := issue_service.CloseIssue(ctx, issue, user_model.NewProjectWorkflowsUser(), ""); err != nil { if err := issue_service.CloseIssue(ctx, issue, user_model.NewProjectWorkflowsUser(), ""); err != nil {
log.Error("CloseIssue: %v", err) log.Error("CloseIssue: %v", err)

View File

@ -977,21 +977,21 @@ onUnmounted(() => {
</div> </div>
</div> </div>
<div class="field" v-if="hasAction('close')"> <div class="field" v-if="hasAction('issue_state')">
<label for="issue-state-action">Issue state</label> <label for="issue-state-action">Issue state</label>
<select <select
v-if="isInEditMode" v-if="isInEditMode"
id="issue-state-action" id="issue-state-action"
class="column-select" class="column-select"
v-model="store.workflowActions.issueState" v-model="store.workflowActions.issue_state"
> >
<option value="">No change</option> <option value="">No change</option>
<option value="close">Close issue</option> <option value="close">Close issue</option>
<option value="reopen">Reopen issue</option> <option value="reopen">Reopen issue</option>
</select> </select>
<div v-else class="readonly-value"> <div v-else class="readonly-value">
{{ store.workflowActions.issueState === 'close' ? 'Close issue' : {{ store.workflowActions.issue_state === 'close' ? 'Close issue' :
store.workflowActions.issueState === 'reopen' ? 'Reopen issue' : 'No change' }} store.workflowActions.issue_state === 'reopen' ? 'Reopen issue' : 'No change' }}
</div> </div>
</div> </div>
</div> </div>