mirror of https://github.com/go-gitea/gitea.git
improvements
parent
5cd2901d1e
commit
af5ba854d9
|
|
@ -393,6 +393,7 @@ func prepareMigrationTasks() []*migration {
|
|||
|
||||
// Gitea 1.24.0 ends at database version 321
|
||||
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
|
||||
newMigration(322, "Add new table project_workflow", v1_25.AddEnabledToProjectWorkflow),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_25
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddEnabledToProjectWorkflow(x *xorm.Engine) error {
|
||||
type ProjectWorkflow struct {
|
||||
ID int64
|
||||
ProjectID int64 `xorm:"INDEX"`
|
||||
WorkflowEvent int `xorm:"INDEX"`
|
||||
WorkflowFilters string `xorm:"TEXT json"`
|
||||
WorkflowActions string `xorm:"TEXT json"`
|
||||
Enabled bool `xorm:"DEFAULT true"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
return x.Sync(&ProjectWorkflow{})
|
||||
}
|
||||
|
|
@ -163,6 +163,7 @@ type Workflow struct {
|
|||
WorkflowEvent WorkflowEvent `xorm:"INDEX"`
|
||||
WorkflowFilters []WorkflowFilter `xorm:"TEXT json"`
|
||||
WorkflowActions []WorkflowAction `xorm:"TEXT json"`
|
||||
Enabled bool `xorm:"DEFAULT true"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
|
@ -223,3 +224,8 @@ func UpdateWorkflow(ctx context.Context, wf *Workflow) error {
|
|||
_, err := db.GetEngine(ctx).ID(wf.ID).Update(wf)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteWorkflow(ctx context.Context, id int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(id).Delete(&Workflow{})
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ func WorkflowsEvents(ctx *context.Context) {
|
|||
Filters []project_model.WorkflowFilter `json:"filters"`
|
||||
Actions []project_model.WorkflowAction `json:"actions"`
|
||||
FilterSummary string `json:"filter_summary"` // Human readable filter description
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
outputWorkflows := make([]*WorkflowConfig, 0)
|
||||
|
|
@ -163,6 +164,7 @@ func WorkflowsEvents(ctx *context.Context) {
|
|||
Filters: wf.WorkflowFilters,
|
||||
Actions: wf.WorkflowActions,
|
||||
FilterSummary: filterSummary,
|
||||
Enabled: wf.Enabled,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
|
@ -175,6 +177,7 @@ func WorkflowsEvents(ctx *context.Context) {
|
|||
Filters: []project_model.WorkflowFilter{},
|
||||
Actions: []project_model.WorkflowAction{},
|
||||
FilterSummary: "",
|
||||
Enabled: true, // Default to enabled for new workflows
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -392,6 +395,7 @@ func WorkflowsPost(ctx *context.Context) {
|
|||
WorkflowEvent: project_model.WorkflowEvent(form.EventID),
|
||||
WorkflowFilters: filters,
|
||||
WorkflowActions: actions,
|
||||
Enabled: true, // New workflows are enabled by default
|
||||
}
|
||||
if err := project_model.CreateWorkflow(ctx, wf); err != nil {
|
||||
ctx.ServerError("CreateWorkflow", err)
|
||||
|
|
@ -409,6 +413,7 @@ func WorkflowsPost(ctx *context.Context) {
|
|||
"filters": wf.WorkflowFilters,
|
||||
"actions": wf.WorkflowActions,
|
||||
"filter_summary": filterSummary,
|
||||
"enabled": wf.Enabled,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
|
|
@ -441,7 +446,98 @@ func WorkflowsPost(ctx *context.Context) {
|
|||
"filters": wf.WorkflowFilters,
|
||||
"actions": wf.WorkflowActions,
|
||||
"filter_summary": filterSummary,
|
||||
"enabled": wf.Enabled,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WorkflowsStatus(ctx *context.Context) {
|
||||
projectID := ctx.PathParamInt64("id")
|
||||
workflowID, _ := strconv.ParseInt(ctx.PathParam("workflow_id"), 10, 64)
|
||||
|
||||
p, err := project_model.GetProjectByID(ctx, projectID)
|
||||
if err != nil {
|
||||
if project_model.IsErrProjectNotExist(err) {
|
||||
ctx.NotFound(nil)
|
||||
} else {
|
||||
ctx.ServerError("GetProjectByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if p.Type == project_model.TypeRepository && p.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
if (p.Type == project_model.TypeOrganization || p.Type == project_model.TypeIndividual) && p.OwnerID != ctx.ContextUser.ID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
wf, err := project_model.GetWorkflowByID(ctx, workflowID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWorkflowByID", err)
|
||||
return
|
||||
}
|
||||
if wf.ProjectID != projectID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get enabled status from form
|
||||
enabledStr := ctx.Req.FormValue("enabled")
|
||||
enabled := enabledStr == "true"
|
||||
|
||||
wf.Enabled = enabled
|
||||
if err := project_model.UpdateWorkflow(ctx, wf); err != nil {
|
||||
ctx.ServerError("UpdateWorkflow", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"success": true,
|
||||
"enabled": wf.Enabled,
|
||||
})
|
||||
}
|
||||
|
||||
func WorkflowsDelete(ctx *context.Context) {
|
||||
projectID := ctx.PathParamInt64("id")
|
||||
workflowID, _ := strconv.ParseInt(ctx.PathParam("workflow_id"), 10, 64)
|
||||
|
||||
p, err := project_model.GetProjectByID(ctx, projectID)
|
||||
if err != nil {
|
||||
if project_model.IsErrProjectNotExist(err) {
|
||||
ctx.NotFound(nil)
|
||||
} else {
|
||||
ctx.ServerError("GetProjectByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if p.Type == project_model.TypeRepository && p.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
if (p.Type == project_model.TypeOrganization || p.Type == project_model.TypeIndividual) && p.OwnerID != ctx.ContextUser.ID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
wf, err := project_model.GetWorkflowByID(ctx, workflowID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWorkflowByID", err)
|
||||
return
|
||||
}
|
||||
if wf.ProjectID != projectID {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := project_model.DeleteWorkflow(ctx, workflowID); err != nil {
|
||||
ctx.ServerError("DeleteWorkflow", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"success": true,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1042,6 +1042,8 @@ func registerWebRoutes(m *web.Router) {
|
|||
m.Get("", projects.Workflows)
|
||||
m.Get("/{workflow_id}", projects.Workflows)
|
||||
m.Post("/{workflow_id}", web.Bind(projects.WorkflowsPostForm{}), projects.WorkflowsPost)
|
||||
m.Post("/{workflow_id}/status", projects.WorkflowsStatus)
|
||||
m.Post("/{workflow_id}/delete", projects.WorkflowsDelete)
|
||||
})
|
||||
m.Group("", func() { //nolint:dupl // duplicates lines 1421-1441
|
||||
m.Get("/new", org.RenderNewProject)
|
||||
|
|
@ -1434,6 +1436,8 @@ func registerWebRoutes(m *web.Router) {
|
|||
m.Get("", projects.Workflows)
|
||||
m.Get("/{workflow_id}", projects.Workflows)
|
||||
m.Post("/{workflow_id}", web.Bind(projects.WorkflowsPostForm{}), projects.WorkflowsPost)
|
||||
m.Post("/{workflow_id}/status", projects.WorkflowsStatus)
|
||||
m.Post("/{workflow_id}/delete", projects.WorkflowsDelete)
|
||||
})
|
||||
m.Group("", func() { //nolint:dupl // duplicates lines 1034-1054
|
||||
m.Get("/new", repo.RenderNewProject)
|
||||
|
|
|
|||
|
|
@ -62,9 +62,11 @@ const deleteWorkflow = async () => {
|
|||
return;
|
||||
}
|
||||
|
||||
const currentBaseEventType = store.selectedWorkflow.base_event_type;
|
||||
const currentBaseEventType = store.selectedWorkflow.base_event_type || store.selectedWorkflow.workflow_event || store.selectedWorkflow.event_id;
|
||||
const currentCapabilities = store.selectedWorkflow.capabilities;
|
||||
const currentDisplayName = store.selectedWorkflow.display_name.split(' (')[0]; // Remove filter suffix
|
||||
// Extract base name without any parenthetical descriptions
|
||||
const currentDisplayName = (store.selectedWorkflow.display_name || store.selectedWorkflow.workflow_event || store.selectedWorkflow.event_id)
|
||||
.replace(/\s*\([^)]*\)\s*/g, '');
|
||||
|
||||
// If deleting a temporary workflow (clone/new), just remove from list
|
||||
if (store.selectedWorkflow.id === 0) {
|
||||
|
|
@ -135,8 +137,7 @@ const selectWorkflowEvent = async (event) => {
|
|||
|
||||
const saveWorkflow = async () => {
|
||||
await store.saveWorkflow();
|
||||
// After saving, refresh the list to show the new workflow
|
||||
store.workflowEvents = await store.loadEvents();
|
||||
// The store.saveWorkflow already handles reloading events
|
||||
|
||||
// Clear previous selection after successful save
|
||||
previousSelection.value = null;
|
||||
|
|
@ -148,6 +149,36 @@ const isWorkflowConfigured = (event) => {
|
|||
return !Number.isNaN(parseInt(event.event_id)) || (event.id !== undefined && event.id > 0);
|
||||
};
|
||||
|
||||
// Generate filter description for display name
|
||||
const getFilterDescription = (workflow) => {
|
||||
if (!workflow.filters || !Array.isArray(workflow.filters) || workflow.filters.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const descriptions = [];
|
||||
for (const filter of workflow.filters) {
|
||||
if (filter.type === 'issue_type' && filter.value) {
|
||||
if (filter.value === 'issue') {
|
||||
descriptions.push('Issues');
|
||||
} else if (filter.value === 'pull_request') {
|
||||
descriptions.push('Pull Requests');
|
||||
}
|
||||
}
|
||||
// Add more filter types here as needed
|
||||
}
|
||||
|
||||
return descriptions.length > 0 ? ` (${descriptions.join(', ')})` : '';
|
||||
};
|
||||
|
||||
// Get display name with filters
|
||||
const getWorkflowDisplayName = (workflow) => {
|
||||
const baseName = workflow.display_name || workflow.workflow_event || workflow.event_id;
|
||||
if (isWorkflowConfigured(workflow)) {
|
||||
return baseName + getFilterDescription(workflow);
|
||||
}
|
||||
return baseName;
|
||||
};
|
||||
|
||||
// Get flat list of all workflows - use cached data to prevent frequent recomputation
|
||||
const workflowList = computed(() => {
|
||||
// Use a stable reference to prevent unnecessary DOM updates
|
||||
|
|
@ -159,7 +190,8 @@ const workflowList = computed(() => {
|
|||
return workflows.map((workflow) => ({
|
||||
...workflow,
|
||||
isConfigured: isWorkflowConfigured(workflow),
|
||||
base_event_type: workflow.event_id,
|
||||
base_event_type: workflow.base_event_type || workflow.workflow_event || workflow.event_id,
|
||||
display_name: getWorkflowDisplayName(workflow),
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
@ -200,15 +232,19 @@ const cloneWorkflow = (sourceWorkflow) => {
|
|||
};
|
||||
|
||||
const tempId = `clone-${sourceWorkflow.base_event_type || sourceWorkflow.workflow_event}-${Date.now()}`;
|
||||
// Extract base name without filter descriptions
|
||||
const baseName = (sourceWorkflow.display_name || sourceWorkflow.workflow_event || sourceWorkflow.event_id)
|
||||
.replace(/\s*\([^)]*\)\s*/g, ''); // Remove any parenthetical descriptions
|
||||
|
||||
const clonedWorkflow = {
|
||||
id: 0,
|
||||
event_id: tempId,
|
||||
display_name: `${sourceWorkflow.display_name.split(' (')[0]} (Copy)`, // Add copy suffix
|
||||
display_name: `${baseName} (Copy)`, // Add copy suffix
|
||||
capabilities: sourceWorkflow.capabilities,
|
||||
filters: Array.from(sourceWorkflow.filters || []),
|
||||
actions: Array.from(sourceWorkflow.actions || []),
|
||||
filter_summary: '',
|
||||
base_event_type: sourceWorkflow.base_event_type || sourceWorkflow.workflow_event,
|
||||
base_event_type: sourceWorkflow.base_event_type || sourceWorkflow.workflow_event || sourceWorkflow.event_id,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -161,27 +161,25 @@ export function createWorkflowStore(props: { projectLink: string, eventID: strin
|
|||
const result = await response.json();
|
||||
console.log('Response result:', result);
|
||||
if (result.success && result.workflow) {
|
||||
// For new workflows, add to the store
|
||||
if (store.selectedWorkflow.id === 0 || store.selectedWorkflow.event_id.startsWith('new-')) {
|
||||
store.workflowEvents.push(result.workflow);
|
||||
// Always reload the events list to get the updated structure
|
||||
// This ensures we have both the base event and the new filtered event
|
||||
const wasNewWorkflow = store.selectedWorkflow.id === 0 ||
|
||||
store.selectedWorkflow.event_id.startsWith('new-') ||
|
||||
store.selectedWorkflow.event_id.startsWith('clone-');
|
||||
|
||||
// Update URL to use the new workflow ID
|
||||
const newUrl = `${props.projectLink}/workflows/${result.workflow.event_id}`;
|
||||
window.history.replaceState({eventId: result.workflow.event_id}, '', newUrl);
|
||||
} else {
|
||||
// Update existing workflow
|
||||
const existingIndex = store.workflowEvents.findIndex((e) => e.event_id === store.selectedWorkflow.event_id);
|
||||
if (existingIndex >= 0) {
|
||||
store.workflowEvents[existingIndex] = {
|
||||
...store.workflowEvents[existingIndex],
|
||||
...result.workflow,
|
||||
};
|
||||
}
|
||||
}
|
||||
// Reload events from server to get the correct event structure
|
||||
await store.loadEvents();
|
||||
|
||||
// Update selected workflow and selectedItem
|
||||
store.selectedWorkflow = result.workflow;
|
||||
store.selectedItem = result.workflow.event_id;
|
||||
|
||||
// Update URL to use the new workflow ID
|
||||
if (wasNewWorkflow) {
|
||||
const newUrl = `${props.projectLink}/workflows/${result.workflow.event_id}`;
|
||||
window.history.replaceState({eventId: result.workflow.event_id}, '', newUrl);
|
||||
}
|
||||
|
||||
alert('Workflow saved successfully!');
|
||||
} else {
|
||||
console.error('Unexpected response format:', result);
|
||||
|
|
|
|||
Loading…
Reference in New Issue