mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-24 20:06:46 +02:00
some improvements
This commit is contained in:
parent
bf9511e63c
commit
5481800fe8
@ -43,26 +43,26 @@ func GetWorkflowEvents() []WorkflowEvent {
|
||||
return workflowEvents
|
||||
}
|
||||
|
||||
func (we WorkflowEvent) ToString() string {
|
||||
func (we WorkflowEvent) LangKey() string {
|
||||
switch we {
|
||||
case WorkflowEventItemAddedToProject:
|
||||
return "Item added to project"
|
||||
return "projects.workflows.event.item_added_to_project"
|
||||
case WorkflowEventItemReopened:
|
||||
return "Item reopened"
|
||||
return "projects.workflows.event.item_reopened"
|
||||
case WorkflowEventItemClosed:
|
||||
return "Item closed"
|
||||
return "projects.workflows.event.item_closed"
|
||||
case WorkflowEventCodeChangesRequested:
|
||||
return "Code changes requested"
|
||||
return "projects.workflows.event.code_changes_requested"
|
||||
case WorkflowEventCodeReviewApproved:
|
||||
return "Code review approved"
|
||||
return "projects.workflows.event.code_review_approved"
|
||||
case WorkflowEventPullRequestMerged:
|
||||
return "Pull request merged"
|
||||
return "projects.workflows.event.pull_request_merged"
|
||||
case WorkflowEventAutoArchiveItems:
|
||||
return "Auto archive items"
|
||||
return "projects.workflows.event.auto_archive_items"
|
||||
case WorkflowEventAutoAddToProject:
|
||||
return "Auto add to project"
|
||||
return "projects.workflows.event.auto_add_to_project"
|
||||
case WorkflowEventAutoCloseIssue:
|
||||
return "Auto close issue"
|
||||
return "projects.workflows.event.auto_close_issue"
|
||||
default:
|
||||
return string(we)
|
||||
}
|
||||
@ -179,3 +179,12 @@ func GetWorkflowByID(ctx context.Context, id int64) (*Workflow, error) {
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func CreateWorkflow(ctx context.Context, wf *Workflow) error {
|
||||
return db.Insert(ctx, wf)
|
||||
}
|
||||
|
||||
func UpdateWorkflow(ctx context.Context, wf *Workflow) error {
|
||||
_, err := db.GetEngine(ctx).ID(wf.ID).Update(wf)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -3902,7 +3902,17 @@ type-1.display_name = Individual Project
|
||||
type-2.display_name = Repository Project
|
||||
type-3.display_name = Organization Project
|
||||
enter_fullscreen = Fullscreen
|
||||
workflows = Workflows
|
||||
exit_fullscreen = Exit Fullscreen
|
||||
workflows.event.item_added_to_project = Item added to project
|
||||
workflows.event.item_reopened = Item reopened
|
||||
workflows.event.item_closed = Item closed
|
||||
workflows.event.code_changes_requested = Code changes requested
|
||||
workflows.event.code_review_approved = Code review approved
|
||||
workflows.event.pull_request_merged = Pull request merged
|
||||
workflows.event.auto_archive_items = Auto archive items
|
||||
workflows.event.auto_add_to_project = Auto add to project
|
||||
workflows.event.auto_close_issue = Auto close issue
|
||||
|
||||
[git.filemode]
|
||||
changed_filemode = %[1]s → %[2]s
|
||||
|
||||
@ -4,10 +4,14 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/project"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
@ -16,7 +20,112 @@ var (
|
||||
tmplOrgWorkflows = templates.TplName("org/projects/workflows")
|
||||
)
|
||||
|
||||
func WorkflowsEvents(ctx *context.Context) {
|
||||
projectID := ctx.PathParamInt64("id")
|
||||
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
|
||||
}
|
||||
|
||||
workflows, err := project_model.FindWorkflowEvents(ctx, projectID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWorkflows", err)
|
||||
return
|
||||
}
|
||||
type WorkflowEvent struct {
|
||||
EventID string `json:"event_id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
outputWorkflows := make([]*WorkflowEvent, 0, len(workflows))
|
||||
events := project_model.GetWorkflowEvents()
|
||||
for _, event := range events {
|
||||
var workflow *WorkflowEvent
|
||||
for _, wf := range workflows {
|
||||
if wf.WorkflowEvent == event {
|
||||
workflow = &WorkflowEvent{
|
||||
EventID: fmt.Sprintf("%d", wf.ID),
|
||||
DisplayName: string(ctx.Tr(wf.WorkflowEvent.LangKey())),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if workflow == nil {
|
||||
workflow = &WorkflowEvent{
|
||||
EventID: event.UUID(),
|
||||
DisplayName: string(ctx.Tr(event.LangKey())),
|
||||
}
|
||||
}
|
||||
outputWorkflows = append(outputWorkflows, workflow)
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, outputWorkflows)
|
||||
}
|
||||
|
||||
func WorkflowsColumns(ctx *context.Context) {
|
||||
projectID := ctx.PathParamInt64("id")
|
||||
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
|
||||
}
|
||||
|
||||
columns, err := p.GetColumns(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetProjectColumns", err)
|
||||
return
|
||||
}
|
||||
|
||||
type Column struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
outputColumns := make([]*Column, 0, len(columns))
|
||||
for _, col := range columns {
|
||||
outputColumns = append(outputColumns, &Column{
|
||||
ID: col.ID,
|
||||
Title: col.Title,
|
||||
})
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, outputColumns)
|
||||
}
|
||||
|
||||
func Workflows(ctx *context.Context) {
|
||||
workflowIDStr := ctx.PathParam("workflow_id")
|
||||
if workflowIDStr == "events" {
|
||||
WorkflowsEvents(ctx)
|
||||
return
|
||||
}
|
||||
if workflowIDStr == "columns" {
|
||||
WorkflowsColumns(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["WorkflowEvents"] = project_model.GetWorkflowEvents()
|
||||
|
||||
projectID := ctx.PathParamInt64("id")
|
||||
@ -54,7 +163,6 @@ func Workflows(ctx *context.Context) {
|
||||
}
|
||||
ctx.Data["Workflows"] = workflows
|
||||
|
||||
workflowIDStr := ctx.PathParam("workflow_id")
|
||||
ctx.Data["workflowIDStr"] = workflowIDStr
|
||||
var curWorkflow *project_model.Workflow
|
||||
if workflowIDStr == "" { // get first value workflow or the first workflow
|
||||
@ -76,6 +184,7 @@ func Workflows(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
ctx.Data["CurWorkflow"] = curWorkflow
|
||||
ctx.Data["ProjectLink"] = project.ProjectLinkForRepo(ctx.Repo.Repository, projectID)
|
||||
|
||||
if p.Type == project_model.TypeRepository {
|
||||
ctx.HTML(200, tmplRepoWorkflows)
|
||||
@ -83,3 +192,59 @@ func Workflows(ctx *context.Context) {
|
||||
ctx.HTML(200, tmplOrgWorkflows)
|
||||
}
|
||||
}
|
||||
|
||||
type WorkflowsPostForm struct {
|
||||
EventID string `form:"event_id" binding:"Required"`
|
||||
Filters map[string]string `form:"filters"`
|
||||
Actions map[string]any `form:"actions"`
|
||||
}
|
||||
|
||||
func WorkflowsPost(ctx *context.Context) {
|
||||
projectID := ctx.PathParamInt64("id")
|
||||
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
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*WorkflowsPostForm)
|
||||
eventID, _ := strconv.ParseInt(form.EventID, 10, 64)
|
||||
if eventID == 0 {
|
||||
// Create a new workflow
|
||||
wf := &project_model.Workflow{
|
||||
ProjectID: projectID,
|
||||
WorkflowEvent: project_model.WorkflowEvent(form.EventID),
|
||||
WorkflowFilters: []project_model.WorkflowFilter{},
|
||||
WorkflowActions: []project_model.WorkflowAction{},
|
||||
}
|
||||
if err := project_model.CreateWorkflow(ctx, wf); err != nil {
|
||||
ctx.ServerError("CreateWorkflow", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Update an existing workflow
|
||||
wf, err := project_model.GetWorkflowByID(ctx, eventID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWorkflowByID", err)
|
||||
return
|
||||
}
|
||||
wf.WorkflowFilters = []project_model.WorkflowFilter{}
|
||||
wf.WorkflowActions = []project_model.WorkflowAction{}
|
||||
if err := project_model.UpdateWorkflow(ctx, wf); err != nil {
|
||||
ctx.ServerError("UpdateWorkflow", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1041,6 +1041,7 @@ func registerWebRoutes(m *web.Router) {
|
||||
m.Group("/{id}/workflows", func() {
|
||||
m.Get("", projects.Workflows)
|
||||
m.Get("/{workflow_id}", projects.Workflows)
|
||||
m.Post("/{workflow_id}", web.Bind(projects.WorkflowsPostForm{}), projects.WorkflowsPost)
|
||||
})
|
||||
m.Group("", func() { //nolint:dupl // duplicates lines 1421-1441
|
||||
m.Get("/new", org.RenderNewProject)
|
||||
|
||||
@ -19,6 +19,10 @@
|
||||
</div>
|
||||
{{if $canWriteProject}}
|
||||
<div class="ui compact mini menu">
|
||||
<a class="item" href="{{.Link}}/workflows">
|
||||
{{svg "octicon-workflow"}}
|
||||
{{ctx.Locale.Tr "projects.workflows"}}
|
||||
</a>
|
||||
<a class="item screen-full">
|
||||
{{svg "octicon-screen-full"}}
|
||||
{{ctx.Locale.Tr "projects.enter_fullscreen"}}
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
<div class="ui container fluid padded projects-view">
|
||||
<div class="ui container flex-text-block project-header">
|
||||
<div class="four wide column">
|
||||
{{range .WorkflowEvents}}
|
||||
<div class="ui fluid vertical menu">
|
||||
{{$workflow := (index $.Workflows .)}}
|
||||
<a class="item{{if or (and $workflow $.CurWorkflow (eq $workflow.ID $.CurWorkflow.ID)) (eq $.workflowIDStr .UUID)}} active{{end}}" href="{{$.Project.Link ctx}}/workflows/{{if $workflow}}{{$workflow.ID}}{{else}}{{.UUID}}{{end}}">{{.ToString}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui container padded projects-view">
|
||||
<div class="project-header">
|
||||
<h2>{{.Project.Title}} - {{ctx.Locale.Tr "projects.workflows"}}</h2>
|
||||
</div>
|
||||
<div id="project-workflows"
|
||||
data-project-link="{{.ProjectLink}}"
|
||||
data-event-id="{{.workflowIDStr}}"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
258
web_src/js/components/projects/ProjectWorkflow.vue
Normal file
258
web_src/js/components/projects/ProjectWorkflow.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, useTemplateRef} from 'vue';
|
||||
import {createWorkflowStore} from './WorkflowStore.ts';
|
||||
import {svg} from '../../svg.ts';
|
||||
|
||||
const elRoot = useTemplateRef('elRoot');
|
||||
|
||||
const props = defineProps({
|
||||
projectLink: {type: String, required: true},
|
||||
eventID: {type: String, required: true},
|
||||
});
|
||||
|
||||
const store = createWorkflowStore(props);
|
||||
|
||||
const selectWorkflowEvent = (event) => {
|
||||
store.selectedItem = event.event_id;
|
||||
store.selectedWorkflow = event;
|
||||
store.loadWorkflowData(event.event_id);
|
||||
|
||||
// Update URL without page reload
|
||||
const newUrl = `${props.projectLink}/workflows/${event.event_id}`;
|
||||
window.history.pushState({eventId: event.event_id}, '', newUrl);
|
||||
};
|
||||
|
||||
const saveWorkflow = async () => {
|
||||
await store.saveWorkflow();
|
||||
};
|
||||
|
||||
const resetWorkflow = () => {
|
||||
store.resetWorkflowData();
|
||||
};
|
||||
|
||||
const isWorkflowConfigured = (event) => {
|
||||
// Check if the event_id is a number (saved workflow ID) vs UUID (unconfigured)
|
||||
// If it's a number, it means the workflow has been saved to database
|
||||
return !isNaN(parseInt(event.event_id));
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
store.workflowEvents = await store.loadEvents();
|
||||
|
||||
// Set initial selected workflow if eventID is provided
|
||||
if (props.eventID) {
|
||||
const selectedEvent = store.workflowEvents.find((e) => e.event_id === props.eventID);
|
||||
if (selectedEvent) {
|
||||
store.selectedItem = props.eventID;
|
||||
store.selectedWorkflow = selectedEvent;
|
||||
await store.loadWorkflowData(props.eventID);
|
||||
}
|
||||
}
|
||||
|
||||
elRoot.value.closest('.is-loading')?.classList?.remove('is-loading');
|
||||
|
||||
window.addEventListener('popstate', (e) => {
|
||||
if (e.state?.eventId) {
|
||||
const event = store.workflowEvents.find((ev) => ev.event_id === e.state.eventId);
|
||||
if (event) {
|
||||
selectWorkflowEvent(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="elRoot" class="workflow-container">
|
||||
<div class="workflow-sidebar">
|
||||
<div class="ui fluid vertical menu">
|
||||
<a
|
||||
v-for="event in store.workflowEvents"
|
||||
:key="event.event_id"
|
||||
class="item"
|
||||
:class="{ active: store.selectedItem === event.event_id }"
|
||||
:href="`${props.projectLink}/workflows/${event.event_id}`"
|
||||
@click.prevent="selectWorkflowEvent(event)"
|
||||
>
|
||||
<span class="workflow-status" :class="{ configured: isWorkflowConfigured(event) }">
|
||||
<span v-if="isWorkflowConfigured(event)" v-html="svg('octicon-dot-fill')" class="status-icon configured"></span>
|
||||
<span v-else class="status-icon unconfigured"></span>
|
||||
</span>
|
||||
{{ event.display_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workflow-main">
|
||||
<div class="workflow-content">
|
||||
<div v-if="!store.selectedWorkflow" class="ui placeholder segment">
|
||||
<div class="ui icon header">
|
||||
<i class="settings icon"/>
|
||||
Select a workflow event to configure
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="workflow-editor">
|
||||
<div class="ui header">
|
||||
<i class="settings icon"/>
|
||||
{{ store.selectedWorkflow.display_name }}
|
||||
</div>
|
||||
<div class="workflow-form">
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>When</label>
|
||||
<div class="ui segment">
|
||||
<div class="description">
|
||||
This workflow will run when: <strong>{{ store.selectedWorkflow.display_name }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Filters</label>
|
||||
<div class="ui segment">
|
||||
<div class="field">
|
||||
<label>Apply to</label>
|
||||
<select class="ui dropdown" v-model="store.workflowFilters.scope">
|
||||
<option value="">Issues And Pull Requests</option>
|
||||
<option value="issue">Issues</option>
|
||||
<option value="pull_request">Pull requests</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Actions</label>
|
||||
<div class="ui segment">
|
||||
<div class="field">
|
||||
<label>Move to column</label>
|
||||
<select class="ui dropdown" v-model="store.workflowActions.column">
|
||||
<option value="">Select column...</option>
|
||||
<option v-for="column in store.projectColumns" :key="column.id" :value="column.id">
|
||||
{{ column.title }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="store.workflowActions.closeIssue" id="close-issue">
|
||||
<label for="close-issue">Close issue</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="ui primary button" @click="saveWorkflow" :loading="store.saving">
|
||||
Save workflow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.workflow-container {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.workflow-sidebar {
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.workflow-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workflow-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.workflow-editor {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.workflow-form .field {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.workflow-form .field label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.workflow-form .ui.segment {
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.workflow-form .description {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.workflow-form .actions {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.ui.placeholder.segment {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
.ui.vertical.menu .item.active {
|
||||
background-color: #f0f0f0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.workflow-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.status-icon.configured {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.status-icon.configured svg {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.status-icon.unconfigured {
|
||||
border: 1px solid #6c757d;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.workflow-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.workflow-sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
85
web_src/js/components/projects/WorkflowStore.ts
Normal file
85
web_src/js/components/projects/WorkflowStore.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import {reactive} from 'vue';
|
||||
import {GET, POST} from '../../modules/fetch.ts';
|
||||
|
||||
export function createWorkflowStore(props: { projectLink: string, eventID: string}) {
|
||||
const store = reactive({
|
||||
workflowEvents: [],
|
||||
selectedItem: props.eventID,
|
||||
selectedWorkflow: null,
|
||||
projectColumns: [],
|
||||
saving: false,
|
||||
|
||||
workflowFilters: {
|
||||
scope: '', // 'issue', 'pull_request', or ''
|
||||
},
|
||||
|
||||
workflowActions: {
|
||||
column: '', // column ID to move to
|
||||
closeIssue: false,
|
||||
},
|
||||
|
||||
async loadEvents() {
|
||||
const response = await GET(`${props.projectLink}/workflows/events`);
|
||||
store.workflowEvents = await response.json();
|
||||
return store.workflowEvents;
|
||||
},
|
||||
|
||||
async loadProjectColumns() {
|
||||
try {
|
||||
const response = await GET(`${props.projectLink}/workflows/columns`);
|
||||
store.projectColumns = await response.json();
|
||||
} catch (error) {
|
||||
console.error('Failed to load project columns:', error);
|
||||
store.projectColumns = [];
|
||||
}
|
||||
},
|
||||
|
||||
async loadWorkflowData(eventId: string) {
|
||||
// Load project columns for the dropdown
|
||||
await store.loadProjectColumns();
|
||||
|
||||
// Find the workflow from existing workflowEvents
|
||||
const workflow = store.workflowEvents.find((e) => e.event_id === eventId);
|
||||
if (workflow && workflow.filters && workflow.actions) {
|
||||
// Load existing configuration from the workflow data
|
||||
store.workflowFilters = workflow.filters || {scope: ''};
|
||||
store.workflowActions = workflow.actions || {column: '', closeIssue: false};
|
||||
} else {
|
||||
// Reset to defaults for new workflow
|
||||
store.resetWorkflowData();
|
||||
}
|
||||
},
|
||||
|
||||
resetWorkflowData() {
|
||||
store.workflowFilters = {scope: ''};
|
||||
store.workflowActions = {column: '', closeIssue: false};
|
||||
},
|
||||
|
||||
async saveWorkflow() {
|
||||
if (!store.selectedWorkflow) return;
|
||||
|
||||
store.saving = true;
|
||||
try {
|
||||
const workflowData = {
|
||||
event_id: store.selectedWorkflow.event_id,
|
||||
filters: store.workflowFilters,
|
||||
actions: store.workflowActions,
|
||||
};
|
||||
|
||||
const response = await POST(`${props.projectLink}/workflows/${store.selectedWorkflow.event_id}`, {
|
||||
data: workflowData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save workflow');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving workflow:', error);
|
||||
} finally {
|
||||
store.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
return store;
|
||||
}
|
||||
12
web_src/js/features/projects/workflow.ts
Normal file
12
web_src/js/features/projects/workflow.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {createApp} from 'vue';
|
||||
import ProjectWorkflow from '../../components/projects/ProjectWorkflow.vue';
|
||||
|
||||
export async function initProjectWorkflow() {
|
||||
const workflowDiv = document.querySelector('#project-workflows');
|
||||
if (!workflowDiv) return;
|
||||
|
||||
createApp(ProjectWorkflow, {
|
||||
projectLink: workflowDiv.getAttribute('data-project-link'),
|
||||
eventID: workflowDiv.getAttribute('data-event-id'),
|
||||
}).mount(workflowDiv);
|
||||
}
|
||||
@ -66,6 +66,7 @@ import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton}
|
||||
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
|
||||
import {callInitFunctions} from './modules/init.ts';
|
||||
import {initRepoViewFileTree} from './features/repo-view-file-tree.ts';
|
||||
import {initProjectWorkflow} from './features/projects/workflow.ts';
|
||||
|
||||
const initStartTime = performance.now();
|
||||
const initPerformanceTracer = callInitFunctions([
|
||||
@ -164,6 +165,8 @@ const initPerformanceTracer = callInitFunctions([
|
||||
initOAuth2SettingsDisableCheckbox,
|
||||
|
||||
initRepoFileView,
|
||||
|
||||
initProjectWorkflow,
|
||||
]);
|
||||
|
||||
// it must be the last one, then the "querySelectorAll" only needs to be executed once for global init functions.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user