Skip to content

Commit 3d081b3

Browse files
committed
Fix bug
1 parent 323a24e commit 3d081b3

File tree

6 files changed

+138
-165
lines changed

6 files changed

+138
-165
lines changed

models/project/issue.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error
3333
return err
3434
}
3535

36-
func AddIssueToColumn(ctx context.Context, issueID int64, newColumn *Column) error {
37-
return db.Insert(ctx, &ProjectIssue{
38-
IssueID: issueID,
39-
ProjectID: newColumn.ProjectID,
40-
ProjectColumnID: newColumn.ID,
41-
})
42-
}
43-
4436
func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error {
4537
if c.ProjectID != newColumn.ProjectID {
4638
return errors.New("columns have to be in the same project")

models/project/workflows.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ func GetWorkflowEvents() []WorkflowEvent {
4141
return workflowEvents
4242
}
4343

44+
func IsValidWorkflowEvent(event string) bool {
45+
for _, we := range workflowEvents {
46+
if we.EventID() == event {
47+
return true
48+
}
49+
}
50+
return false
51+
}
52+
4453
func (we WorkflowEvent) LangKey() string {
4554
switch we {
4655
case WorkflowEventItemOpened:

routers/web/projects/workflows.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package projects
55

66
import (
77
stdCtx "context"
8-
"errors"
98
"io"
109
"net/http"
1110
"strconv"
@@ -38,9 +37,15 @@ func getFilterSummary(ctx stdCtx.Context, filters []project_model.WorkflowFilter
3837
case project_model.WorkflowFilterTypeIssueType:
3938
switch filter.Value {
4039
case "issue":
41-
summary.WriteString(" (Issues only)")
40+
if summary.Len() > 0 {
41+
summary.WriteString(" ")
42+
}
43+
summary.WriteString("(Issues only)")
4244
case "pull_request":
43-
summary.WriteString(" (Pull requests only)")
45+
if summary.Len() > 0 {
46+
summary.WriteString(" ")
47+
}
48+
summary.WriteString("(Pull requests only)")
4449
}
4550
case project_model.WorkflowFilterTypeSourceColumn:
4651
columnID, _ := strconv.ParseInt(filter.Value, 10, 64)
@@ -52,7 +57,10 @@ func getFilterSummary(ctx stdCtx.Context, filters []project_model.WorkflowFilter
5257
log.Error("GetColumn: %v", err)
5358
continue
5459
}
55-
summary.WriteString(" (Source Column: " + col.Title + ")")
60+
if summary.Len() > 0 {
61+
summary.WriteString(" ")
62+
}
63+
summary.WriteString("(Source: " + col.Title + ")")
5664
case project_model.WorkflowFilterTypeTargetColumn:
5765
columnID, _ := strconv.ParseInt(filter.Value, 10, 64)
5866
if columnID <= 0 {
@@ -63,7 +71,10 @@ func getFilterSummary(ctx stdCtx.Context, filters []project_model.WorkflowFilter
6371
log.Error("GetColumn: %v", err)
6472
continue
6573
}
66-
summary.WriteString(" (Target Column: " + col.Title + ")")
74+
if summary.Len() > 0 {
75+
summary.WriteString(" ")
76+
}
77+
summary.WriteString("(Target: " + col.Title + ")")
6778
case project_model.WorkflowFilterTypeLabels:
6879
labelID, _ := strconv.ParseInt(filter.Value, 10, 64)
6980
if labelID > 0 {
@@ -76,7 +87,10 @@ func getFilterSummary(ctx stdCtx.Context, filters []project_model.WorkflowFilter
7687
if err != nil {
7788
log.Error("GetLabelsByIDs: %v", err)
7889
} else {
79-
summary.WriteString(" (Labels: ")
90+
if summary.Len() > 0 {
91+
summary.WriteString(" ")
92+
}
93+
summary.WriteString("(Labels: ")
8094
for i, label := range labels {
8195
summary.WriteString(label.Name)
8296
if i < len(labels)-1 {
@@ -225,8 +239,7 @@ func WorkflowsEvents(ctx *context.Context) {
225239
ID int64 `json:"id"`
226240
EventID string `json:"event_id"`
227241
DisplayName string `json:"display_name"`
228-
BaseEventType string `json:"base_event_type"` // Base event type for grouping
229-
WorkflowEvent string `json:"workflow_event"` // The actual workflow event
242+
WorkflowEvent string `json:"workflow_event"` // The workflow event
230243
Capabilities project_model.WorkflowEventCapabilities `json:"capabilities"`
231244
Filters []project_model.WorkflowFilter `json:"filters"`
232245
Actions []project_model.WorkflowAction `json:"actions"`
@@ -255,7 +268,6 @@ func WorkflowsEvents(ctx *context.Context) {
255268
ID: wf.ID,
256269
EventID: strconv.FormatInt(wf.ID, 10),
257270
DisplayName: string(ctx.Tr(wf.WorkflowEvent.LangKey())),
258-
BaseEventType: string(wf.WorkflowEvent),
259271
WorkflowEvent: string(wf.WorkflowEvent),
260272
Capabilities: capabilities[event],
261273
Filters: wf.WorkflowFilters,
@@ -271,7 +283,6 @@ func WorkflowsEvents(ctx *context.Context) {
271283
ID: 0,
272284
EventID: event.EventID(),
273285
DisplayName: string(ctx.Tr(event.LangKey())),
274-
BaseEventType: string(event),
275286
WorkflowEvent: string(event),
276287
Capabilities: capabilities[event],
277288
FilterSummary: "",
@@ -460,6 +471,7 @@ type WorkflowsPostForm struct {
460471
Actions map[string]any `json:"actions"`
461472
}
462473

474+
// WorkflowsPost handles creating or updating a workflow
463475
func WorkflowsPost(ctx *context.Context) {
464476
projectID := ctx.PathParamInt64("id")
465477
p, err := project_model.GetProjectByID(ctx, projectID)
@@ -495,7 +507,7 @@ func WorkflowsPost(ctx *context.Context) {
495507
return
496508
}
497509
if form.EventID == "" {
498-
ctx.ServerError("InvalidEventID", errors.New("EventID is required"))
510+
ctx.JSON(http.StatusBadRequest, map[string]any{"error": "InvalidEventID", "message": "EventID is required"})
499511
return
500512
}
501513

@@ -505,6 +517,12 @@ func WorkflowsPost(ctx *context.Context) {
505517

506518
eventID, _ := strconv.ParseInt(form.EventID, 10, 64)
507519
if eventID == 0 {
520+
// check if workflow event is valid
521+
if !project_model.IsValidWorkflowEvent(form.EventID) {
522+
ctx.JSON(http.StatusBadRequest, map[string]any{"error": "EventID is invalid"})
523+
return
524+
}
525+
508526
// Create a new workflow for the given event
509527
wf := &project_model.Workflow{
510528
ProjectID: projectID,

routers/web/web.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ func registerWebRoutes(m *web.Router) {
10481048
m.Post("/{workflow_id}", projects.WorkflowsPost)
10491049
m.Post("/{workflow_id}/status", projects.WorkflowsStatus)
10501050
m.Post("/{workflow_id}/delete", projects.WorkflowsDelete)
1051-
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
1051+
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true))
10521052
m.Group("", func() { //nolint:dupl // duplicates lines 1421-1441
10531053
m.Get("/new", org.RenderNewProject)
10541054
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
@@ -1437,13 +1437,6 @@ func registerWebRoutes(m *web.Router) {
14371437
m.Group("/{username}/{reponame}/projects", func() {
14381438
m.Get("", repo.Projects)
14391439
m.Get("/{id}", repo.ViewProject)
1440-
m.Group("/{id}/workflows", func() {
1441-
m.Get("", projects.Workflows)
1442-
m.Get("/{workflow_id}", projects.Workflows)
1443-
m.Post("/{workflow_id}", projects.WorkflowsPost)
1444-
m.Post("/{workflow_id}/status", projects.WorkflowsStatus)
1445-
m.Post("/{workflow_id}/delete", projects.WorkflowsDelete)
1446-
})
14471440
m.Group("", func() { //nolint:dupl // duplicates lines 1034-1054
14481441
m.Get("/new", repo.RenderNewProject)
14491442
m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost)
@@ -1463,6 +1456,14 @@ func registerWebRoutes(m *web.Router) {
14631456
m.Post("/default", repo.SetDefaultProjectColumn)
14641457
m.Post("/move", repo.MoveIssues)
14651458
})
1459+
1460+
m.Group("/workflows", func() {
1461+
m.Get("", projects.Workflows)
1462+
m.Get("/{workflow_id}", projects.Workflows)
1463+
m.Post("/{workflow_id}", projects.WorkflowsPost)
1464+
m.Post("/{workflow_id}/status", projects.WorkflowsStatus)
1465+
m.Post("/{workflow_id}/delete", projects.WorkflowsDelete)
1466+
})
14661467
})
14671468
}, reqRepoProjectsWriter, context.RepoMustNotBeArchived())
14681469
}, optSignIn, context.RepoAssignment, reqRepoProjectsReader, repo.MustEnableRepoProjects)

web_src/js/components/projects/ProjectWorkflow.vue

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ const toggleEditMode = () => {
9494
// If we removed a temporary item but have no previous selection, fall back to first workflow
9595
const fallback = store.workflowEvents.find((w) => {
9696
if (!canceledWorkflow) return false;
97-
const baseType = canceledWorkflow.base_event_type || canceledWorkflow.workflow_event;
98-
return baseType && (w.base_event_type === baseType || w.workflow_event === baseType || w.event_id === baseType);
97+
const baseType = canceledWorkflow.workflow_event;
98+
return baseType && (w.workflow_event === baseType || w.event_id === baseType);
9999
}) || store.workflowEvents[0];
100100
if (fallback) {
101101
store.selectedItem = fallback.event_id;
@@ -132,12 +132,6 @@ const deleteWorkflow = async () => {
132132
return;
133133
}
134134
135-
const currentBaseEventType = store.selectedWorkflow.base_event_type || store.selectedWorkflow.workflow_event || store.selectedWorkflow.event_id;
136-
const currentCapabilities = store.selectedWorkflow.capabilities;
137-
// Extract base name without any parenthetical descriptions
138-
const currentDisplayName = (store.selectedWorkflow.display_name || store.selectedWorkflow.workflow_event || store.selectedWorkflow.event_id)
139-
.replace(/\s*\([^)]*\)\s*/g, '');
140-
141135
// If deleting a temporary workflow (new or cloned, unsaved), just remove from list
142136
if (store.selectedWorkflow.id === 0) {
143137
const tempIndex = store.workflowEvents.findIndex((w) =>
@@ -155,7 +149,7 @@ const deleteWorkflow = async () => {
155149
156150
// Find workflows for the same base event type
157151
const sameEventWorkflows = store.workflowEvents.filter((w) =>
158-
(w.base_event_type === currentBaseEventType || w.workflow_event === currentBaseEventType)
152+
(w.workflow_event === store.selectedWorkflow.workflow_event)
159153
);
160154
161155
let workflowToSelect = null;
@@ -198,7 +192,7 @@ const cloneWorkflow = (sourceWorkflow) => {
198192
if (!sourceWorkflow) return;
199193
200194
// Generate a unique temporary ID for the cloned workflow
201-
const tempId = `clone-${sourceWorkflow.base_event_type || sourceWorkflow.workflow_event}-${Date.now()}`;
195+
const tempId = `clone-${sourceWorkflow.workflow_event}-${Date.now()}`;
202196
203197
// Extract base name without any parenthetical descriptions
204198
const baseName = (sourceWorkflow.display_name || sourceWorkflow.workflow_event || sourceWorkflow.event_id)
@@ -209,8 +203,7 @@ const cloneWorkflow = (sourceWorkflow) => {
209203
id: 0, // New workflow
210204
event_id: tempId,
211205
display_name: `${baseName} (Copy)`,
212-
base_event_type: sourceWorkflow.base_event_type || sourceWorkflow.workflow_event || sourceWorkflow.event_id,
213-
workflow_event: sourceWorkflow.workflow_event || sourceWorkflow.base_event_type,
206+
workflow_event: sourceWorkflow.workflow_event,
214207
capabilities: sourceWorkflow.capabilities,
215208
filters: JSON.parse(JSON.stringify(sourceWorkflow.filters || [])), // Deep clone
216209
actions: JSON.parse(JSON.stringify(sourceWorkflow.actions || [])), // Deep clone
@@ -325,12 +318,11 @@ const workflowList = computed(() => {
325318
return workflows.map((workflow) => ({
326319
...workflow,
327320
isConfigured: isWorkflowConfigured(workflow),
328-
base_event_type: workflow.base_event_type || workflow.workflow_event || workflow.event_id,
329321
display_name: workflow.display_name || workflow.workflow_event || workflow.event_id,
330322
}));
331323
});
332324
333-
const createNewWorkflow = (baseEventType, capabilities, displayName) => {
325+
const createNewWorkflow = (eventType, capabilities, displayName) => {
334326
// Store current selection before creating new workflow
335327
if (!isInEditMode.value) {
336328
previousSelection.value = {
@@ -339,7 +331,7 @@ const createNewWorkflow = (baseEventType, capabilities, displayName) => {
339331
};
340332
}
341333
342-
const tempId = `new-${baseEventType}-${Date.now()}`;
334+
const tempId = `new-${eventType}-${Date.now()}`;
343335
const newWorkflow = {
344336
id: 0,
345337
event_id: tempId,
@@ -348,14 +340,13 @@ const createNewWorkflow = (baseEventType, capabilities, displayName) => {
348340
filters: [],
349341
actions: [],
350342
filter_summary: '',
351-
base_event_type: baseEventType,
352-
workflow_event: baseEventType,
343+
workflow_event: eventType,
353344
enabled: true, // Ensure new workflows are enabled by default
354345
};
355346
356347
store.selectedWorkflow = newWorkflow;
357348
// For unconfigured events, use the base event type as selected item for UI consistency
358-
store.selectedItem = baseEventType;
349+
store.selectedItem = eventType;
359350
store.resetWorkflowData();
360351
// Unconfigured workflows are always in edit mode by default
361352
};
@@ -383,21 +374,20 @@ const selectWorkflowItem = async (item) => {
383374
} else {
384375
// This is an unconfigured event - check if we already have a workflow object for it
385376
const existingWorkflow = store.workflowEvents.find((w) =>
386-
w.id === 0 &&
387-
(w.base_event_type === item.base_event_type || w.workflow_event === item.base_event_type),
377+
w.id === 0 && w.workflow_event === item.workflow_event,
388378
);
389379
390380
if (existingWorkflow) {
391381
// We already have an unconfigured workflow for this event type, select it
392382
await selectWorkflowEvent(existingWorkflow);
393383
} else {
394384
// This is truly a new unconfigured event, create new workflow
395-
createNewWorkflow(item.base_event_type, item.capabilities, item.display_name);
385+
createNewWorkflow(item.workflow_event, item.capabilities, item.display_name);
396386
}
397387
398388
// Update URL for workflow
399-
const newUrl = `${props.projectLink}/workflows/${item.base_event_type}`;
400-
window.history.pushState({eventId: item.base_event_type}, '', newUrl);
389+
const newUrl = `${props.projectLink}/workflows/${item.workflow_event}`;
390+
window.history.pushState({eventId: item.workflow_event}, '', newUrl);
401391
}
402392
};
403393
@@ -433,18 +423,17 @@ const isItemSelected = (item) => {
433423
// For configured workflows or temporary workflows (new), match by event_id
434424
return store.selectedItem === item.event_id;
435425
}
436-
// For unconfigured events, match by base_event_type
437-
return store.selectedItem === item.base_event_type;
426+
// For unconfigured events, match by workflow_event
427+
return store.selectedItem === item.workflow_event;
438428
};
439429
440430
// Get display name for workflow with numbering for same types
441431
const getWorkflowDisplayName = (item, index) => {
442432
const list = workflowList.value;
443-
const baseEventType = item.base_event_type || item.workflow_event;
444433
445434
// Find all workflows of the same type
446435
const sameTypeWorkflows = list.filter(w =>
447-
(w.base_event_type || w.workflow_event) === baseEventType &&
436+
w.workflow_event === item.workflow_event &&
448437
(w.isConfigured || w.id === 0) // Only count configured workflows
449438
);
450439
@@ -517,7 +506,7 @@ watch(isInEditMode, async (newVal) => {
517506
518507
const getCurrentDraftKey = () => {
519508
if (!store.selectedWorkflow) return null;
520-
return store.selectedWorkflow.event_id || store.selectedWorkflow.base_event_type;
509+
return store.selectedWorkflow.event_id || store.selectedWorkflow.workflow_event;
521510
};
522511
523512
const persistDraftState = () => {
@@ -576,11 +565,11 @@ onMounted(async () => {
576565
// Check if eventID matches a base event type (unconfigured workflow)
577566
const items = workflowList.value;
578567
const matchingUnconfigured = items.find((item) =>
579-
!item.isConfigured && (item.base_event_type === props.eventID || item.event_id === props.eventID),
568+
!item.isConfigured && (item.workflow_event === props.eventID || item.event_id === props.eventID),
580569
);
581570
if (matchingUnconfigured) {
582571
// Create new workflow for this base event type
583-
createNewWorkflow(matchingUnconfigured.base_event_type, matchingUnconfigured.capabilities, matchingUnconfigured.display_name);
572+
createNewWorkflow(matchingUnconfigured.workflow_event, matchingUnconfigured.capabilities, matchingUnconfigured.display_name);
584573
} else {
585574
// Fallback: select first available item
586575
if (items.length > 0) {
@@ -626,10 +615,10 @@ const popstateHandler = (e) => {
626615
// Check if it's a base event type
627616
const items = workflowList.value;
628617
const matchingUnconfigured = items.find((item) =>
629-
!item.isConfigured && (item.base_event_type === e.state.eventId || item.event_id === e.state.eventId),
618+
!item.isConfigured && (item.workflow_event === e.state.eventId || item.event_id === e.state.eventId),
630619
);
631620
if (matchingUnconfigured) {
632-
createNewWorkflow(matchingUnconfigured.base_event_type, matchingUnconfigured.capabilities, matchingUnconfigured.display_name);
621+
createNewWorkflow(matchingUnconfigured.workflow_event, matchingUnconfigured.capabilities, matchingUnconfigured.display_name);
633622
}
634623
}
635624
}
@@ -829,6 +818,23 @@ onUnmounted(() => {
829818
</div>
830819
</div>
831820

821+
<div class="field" v-if="hasFilter('source_column')">
822+
<label>When moved from column</label>
823+
<select
824+
v-if="isInEditMode"
825+
v-model="store.workflowFilters.source_column"
826+
class="column-select"
827+
>
828+
<option value="">Any column</option>
829+
<option v-for="column in store.projectColumns" :key="column.id" :value="String(column.id)">
830+
{{ column.title }}
831+
</option>
832+
</select>
833+
<div v-else class="readonly-value">
834+
{{ store.projectColumns.find(c => String(c.id) === store.workflowFilters.source_column)?.title || 'Any column' }}
835+
</div>
836+
</div>
837+
832838
<div class="field" v-if="hasFilter('target_column')">
833839
<label>When moved to column</label>
834840
<select

0 commit comments

Comments
 (0)