From 3ec99a4c341c524b4fa63feba2742f479dad9c4b Mon Sep 17 00:00:00 2001 From: Richard LT Date: Tue, 26 Mar 2024 16:48:38 +0100 Subject: [PATCH] refactor(ui): workflow v2 graph display matrix and gate (#6914) Signed-off-by: richardlt --- engine/api/clean_worker_test.go | 10 +- engine/api/event_v2/event.go | 2 +- engine/api/v2_queue.go | 16 +- engine/api/v2_queue_test.go | 34 +- engine/api/v2_queue_worker.go | 16 +- engine/api/v2_queue_worker_test.go | 20 +- engine/api/v2_workflow_run.go | 26 +- engine/api/v2_workflow_run_craft.go | 10 +- engine/api/v2_workflow_run_craft_test.go | 16 +- engine/api/v2_workflow_run_engine.go | 46 +-- engine/api/v2_workflow_run_engine_context.go | 20 +- engine/api/v2_workflow_run_engine_test.go | 74 ++-- engine/api/v2_workflow_run_job_routines.go | 12 +- .../api/v2_workflow_run_job_routines_test.go | 23 +- engine/api/v2_workflow_run_test.go | 102 ++--- engine/worker/internal/runV2.go | 41 +- engine/worker/internal/runV2_test.go | 32 +- engine/worker/internal/runtime_v2_test.go | 2 +- engine/worker/internal/takeV2.go | 4 +- sdk/action_parser_funcs.go | 8 +- sdk/cdsclient/client_queue_V2.go | 2 +- sdk/common.go | 8 + sdk/contexts.go | 14 +- sdk/event_v2.go | 66 ++-- sdk/hatchery/hatchery.go | 2 +- sdk/v2_workflow_run.go | 125 ++++-- ui/libs/workflow-graph/package-lock.json | 37 +- ui/libs/workflow-graph/package.json | 5 +- .../src/lib}/duration/duration.service.ts | 0 ui/libs/workflow-graph/src/lib/graph.lib.ts | 127 +++---- ui/libs/workflow-graph/src/lib/graph.model.ts | 46 ++- .../src/lib/jobs-graph.component.ts | 50 +-- .../workflow-graph/src/lib/jobs-graph.scss | 16 +- .../src/lib/node/fork-join-node.components.ts | 7 +- .../src/lib/node/gate-node.component.ts | 59 --- .../src/lib/node/gate-node.html | 4 - .../src/lib/node/gate-node.scss | 47 --- .../src/lib/node/job-node.component.ts | 88 ++++- .../workflow-graph/src/lib/node/job-node.html | 34 +- .../workflow-graph/src/lib/node/job-node.scss | 66 ++-- .../src/lib/node/matrix-node.component.ts | 152 ++++++++ .../src/lib/node/matrix-node.html | 32 ++ .../src/lib/node/matrix-node.scss | 179 +++++++++ .../src/lib/stages-graph.component.ts | 355 +++++++----------- .../src/lib/v2.workflow.run.model.ts | 134 +++++-- .../src/lib/workflow-graph.module.ts | 10 +- ui/libs/workflow-graph/src/public-api.ts | 2 +- ui/src/app/model/v2.workflow.run.model.ts | 111 ------ ui/src/app/service/sidebar/sidebar.service.ts | 4 +- .../service/workflowv2/workflow.service.ts | 10 +- ui/src/app/shared/pipes/duration.pipe.ts | 2 +- .../workflow.sidebar.run.component.ts | 2 +- .../projectv2/run-list/run-list.component.ts | 2 +- .../projectv2/run/gate/gate.component.ts | 6 +- .../projectv2/run/project.run.component.ts | 53 ++- .../app/views/projectv2/run/project.run.html | 59 +-- .../app/views/projectv2/run/project.run.scss | 142 +++++-- .../views/projectv2/run/run-hook.component.ts | 2 +- .../projectv2/run/run-job-logs.component.ts | 13 +- .../app/views/projectv2/run/run-job-logs.html | 2 +- .../views/projectv2/run/run-job.component.ts | 4 +- .../run-result-tests.component.ts | 4 +- .../projectv2/run/run-result.component.ts | 2 +- .../show/entity/project.workflow.entity.html | 26 +- .../consumer-details-modal.component.ts | 37 +- .../node/pipeline/node.pipeline.component.ts | 2 +- .../workflow-run-job.component.ts | 30 +- .../workflow-run-job/workflow-run-job.html | 2 +- .../run/node/summary/run.summary.component.ts | 2 +- .../workflowv3-stages-graph.component.ts | 7 - 70 files changed, 1556 insertions(+), 1149 deletions(-) rename ui/{src/app/shared => libs/workflow-graph/src/lib}/duration/duration.service.ts (100%) delete mode 100644 ui/libs/workflow-graph/src/lib/node/gate-node.component.ts delete mode 100644 ui/libs/workflow-graph/src/lib/node/gate-node.html delete mode 100644 ui/libs/workflow-graph/src/lib/node/gate-node.scss create mode 100644 ui/libs/workflow-graph/src/lib/node/matrix-node.component.ts create mode 100644 ui/libs/workflow-graph/src/lib/node/matrix-node.html create mode 100644 ui/libs/workflow-graph/src/lib/node/matrix-node.scss delete mode 100644 ui/src/app/model/v2.workflow.run.model.ts diff --git a/engine/api/clean_worker_test.go b/engine/api/clean_worker_test.go index 413ec04a18..cb75399e6d 100644 --- a/engine/api/clean_worker_test.go +++ b/engine/api/clean_worker_test.go @@ -67,7 +67,7 @@ func TestDeleteDisabledWorkers(t *testing.T) { Username: admin.Username, ProjectKey: wr.ProjectKey, JobID: sdk.RandomString(10), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj)) @@ -88,7 +88,7 @@ func TestDeleteDisabledWorkers(t *testing.T) { Username: admin.Username, ProjectKey: wr.ProjectKey, JobID: sdk.RandomString(10), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj2)) @@ -137,7 +137,7 @@ func TestDisabledDeadWorkers(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -165,7 +165,7 @@ func TestDisabledDeadWorkers(t *testing.T) { Username: admin.Username, ProjectKey: wr.ProjectKey, JobID: sdk.RandomString(10), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj)) @@ -186,7 +186,7 @@ func TestDisabledDeadWorkers(t *testing.T) { Username: admin.Username, ProjectKey: wr.ProjectKey, JobID: sdk.RandomString(10), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj2)) diff --git a/engine/api/event_v2/event.go b/engine/api/event_v2/event.go index f5be59f73f..06dd032daf 100644 --- a/engine/api/event_v2/event.go +++ b/engine/api/event_v2/event.go @@ -138,7 +138,7 @@ func workflowNotifications(ctx context.Context, db *gorp.DbMap, store cache.Stor } title := fmt.Sprintf("%s-%s", event.ProjectKey, run.WorkflowName) - description := run.WorkflowName + ":" + run.Status + description := run.WorkflowName + ":" + string(run.Status) if run.WorkflowData.Workflow.CommitStatus != nil { if run.WorkflowData.Workflow.CommitStatus.Title != "" { title = run.WorkflowData.Workflow.CommitStatus.Title diff --git a/engine/api/v2_queue.go b/engine/api/v2_queue.go index faadfe6a0f..0c6b2247af 100644 --- a/engine/api/v2_queue.go +++ b/engine/api/v2_queue.go @@ -169,7 +169,7 @@ func (api *API) postJobResultHandler() ([]service.RbacChecker, service.Handler) return sdk.NewErrorFrom(sdk.ErrInvalidData, "unknown job %s on region %s", jobRun.ID, regionName) } - if sdk.StatusIsTerminated(jobRun.Status) { + if jobRun.Status.IsTerminated() { return sdk.NewErrorFrom(sdk.ErrWrongRequest, "job %s is already in a final state %s", jobRun.JobID, jobRun.Status) } @@ -188,7 +188,8 @@ func (api *API) postJobResultHandler() ([]service.RbacChecker, service.Handler) } jobRun.Status = result.Status - jobRun.Ended = time.Now() + now := time.Now() + jobRun.Ended = &now tx, err := api.mustDB().Begin() if err != nil { @@ -382,7 +383,7 @@ func (api *API) deleteHatcheryReleaseJobRunHandler() ([]service.RbacChecker, ser return sdk.NewErrorFrom(sdk.ErrInvalidData, "unknown job %s on region %s taken by hatchery %s", jobRun.ID, regionName, hatch.Name) } - jobRun.Status = sdk.StatusWaiting + jobRun.Status = sdk.V2WorkflowRunJobStatusWaiting jobRun.HatcheryName = "" tx, err := api.mustDB().Begin() @@ -460,7 +461,7 @@ func (api *API) postHatcheryTakeJobRunHandler() ([]service.RbacChecker, service. if jobRun.Region != regionName { return sdk.NewErrorFrom(sdk.ErrInvalidData, "unknown job %s on region %s", jobRun.ID, regionName) } - if sdk.StatusIsTerminated(jobRun.Status) { + if jobRun.Status.IsTerminated() { return sdk.NewErrorFrom(sdk.ErrWrongRequest, "job %s is already in a final state %s", jobRun.JobID, jobRun.Status) } @@ -469,7 +470,7 @@ func (api *API) postHatcheryTakeJobRunHandler() ([]service.RbacChecker, service. trace.StringAttribute(telemetry.TagProjectKey, jobRun.ProjectKey), trace.StringAttribute(telemetry.TagWorkflowRunNumber, strconv.FormatInt(jobRun.RunNumber, 10))) - if jobRun.Status != sdk.StatusWaiting { + if jobRun.Status != sdk.V2WorkflowRunJobStatusWaiting { return sdk.WrapError(sdk.ErrNotFound, "job has already been taken by %s", jobRun.HatcheryName) } @@ -483,8 +484,9 @@ func (api *API) postHatcheryTakeJobRunHandler() ([]service.RbacChecker, service. } jobRun.HatcheryName = hatch.Name - jobRun.Status = sdk.StatusScheduling - jobRun.Scheduled = time.Now() + jobRun.Status = sdk.V2WorkflowRunJobStatusScheduling + now := time.Now() + jobRun.Scheduled = &now tx, err := api.mustDB().Begin() if err != nil { diff --git a/engine/api/v2_queue_test.go b/engine/api/v2_queue_test.go index d4d6fa47e8..81c21f5362 100644 --- a/engine/api/v2_queue_test.go +++ b/engine/api/v2_queue_test.go @@ -35,7 +35,7 @@ func TestPostHatcheryTakeAndReleaseJobRunHandler(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -49,7 +49,7 @@ func TestPostHatcheryTakeAndReleaseJobRunHandler(t *testing.T) { jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusWaiting, + Status: sdk.V2WorkflowRunJobStatusWaiting, ModelType: "docker", Region: "default", WorkflowRunID: wr.ID, @@ -97,7 +97,7 @@ hatcheries: require.Equal(t, 200, w.Code) var jobRunResponse sdk.V2WorkflowRunJob require.NoError(t, json.Unmarshal(w.Body.Bytes(), &jobRunResponse)) - require.Equal(t, sdk.StatusScheduling, jobRunResponse.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusScheduling, jobRunResponse.Status) // release @@ -110,8 +110,8 @@ hatcheries: jobRunDB, err := workflow_v2.LoadRunJobByRunIDAndID(ctx, db, wr.ID, jobRun.ID) require.NoError(t, err) - require.Equal(t, jobRunDB.Status, sdk.StatusWaiting) - require.Equal(t, jobRunDB.HatcheryName, "") + require.Equal(t, sdk.V2WorkflowRunJobStatusWaiting, jobRunDB.Status) + require.Equal(t, "", jobRunDB.HatcheryName) } func TestPostJobResultHandler(t *testing.T) { @@ -131,7 +131,7 @@ func TestPostJobResultHandler(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -164,7 +164,7 @@ hatcheries: jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusScheduling, + Status: sdk.V2WorkflowRunJobStatusScheduling, ModelType: "docker", Region: "default", WorkflowRunID: wr.ID, @@ -182,7 +182,7 @@ hatcheries: // Take Job jobResult := sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, Error: "unable to craft job", } vars := map[string]string{"runJobID": jobRun.ID, "regionName": "default"} @@ -195,7 +195,7 @@ hatcheries: jobRunDB, err := workflow_v2.LoadRunJobByRunIDAndID(ctx, db, wr.ID, jobRun.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusFail, jobRunDB.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, jobRunDB.Status) } func TestGetJobsQueuedHandler(t *testing.T) { @@ -213,7 +213,7 @@ func TestGetJobsQueuedHandler(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -227,7 +227,7 @@ func TestGetJobsQueuedHandler(t *testing.T) { jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusWaiting, + Status: sdk.V2WorkflowRunJobStatusWaiting, JobID: "job1", ModelType: "docker", Region: "default", @@ -238,7 +238,7 @@ func TestGetJobsQueuedHandler(t *testing.T) { jobRun2 := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusWaiting, + Status: sdk.V2WorkflowRunJobStatusWaiting, JobID: "job2", ModelType: "docker", Region: "default2", @@ -249,7 +249,7 @@ func TestGetJobsQueuedHandler(t *testing.T) { jobRun3 := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusWaiting, + Status: sdk.V2WorkflowRunJobStatusWaiting, JobID: "job3", ModelType: "openstack", Region: "default", @@ -320,7 +320,7 @@ func TestGetJobHandler(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -334,7 +334,7 @@ func TestGetJobHandler(t *testing.T) { jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusWaiting, + Status: sdk.V2WorkflowRunJobStatusWaiting, JobID: "job1", ModelType: "docker", Region: "default", @@ -405,7 +405,7 @@ func TestPostJobRunInfoHandler(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -419,7 +419,7 @@ func TestPostJobRunInfoHandler(t *testing.T) { jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusWaiting, + Status: sdk.V2WorkflowRunJobStatusWaiting, JobID: "job1", ModelType: "docker", Region: "default", diff --git a/engine/api/v2_queue_worker.go b/engine/api/v2_queue_worker.go index f09ab2a05a..2d8de3f88b 100644 --- a/engine/api/v2_queue_worker.go +++ b/engine/api/v2_queue_worker.go @@ -47,7 +47,7 @@ func (api *API) postV2WorkerTakeJobHandler() ([]service.RbacChecker, service.Han return err } - if jobRun.Status != sdk.StatusScheduling { + if jobRun.Status != sdk.V2WorkflowRunJobStatusScheduling { return sdk.NewErrorFrom(sdk.ErrForbidden, "unable take the job %s, current status %s", jobRunID, jobRun.Status) } @@ -95,8 +95,10 @@ func (api *API) postV2WorkerTakeJobHandler() ([]service.RbacChecker, service.Han return err } - jobRun.Status = sdk.StatusBuilding - jobRun.Started = time.Now() + now := time.Now() + + jobRun.Status = sdk.V2WorkflowRunJobStatusBuilding + jobRun.Started = &now jobRun.WorkerName = wrkWithSecret.Name if err := workflow_v2.UpdateJobRun(ctx, tx, jobRun); err != nil { return err @@ -104,7 +106,7 @@ func (api *API) postV2WorkerTakeJobHandler() ([]service.RbacChecker, service.Han info := sdk.V2WorkflowRunJobInfo{ Level: sdk.WorkflowRunInfoLevelInfo, - IssuedAt: time.Now(), + IssuedAt: now, WorkflowRunJobID: jobRun.ID, WorkflowRunID: jobRun.WorkflowRunID, Message: fmt.Sprintf("Worker %q is starting for job %q", wk.Name, jobRun.JobID), @@ -264,8 +266,8 @@ func computeRunJobContext(ctx context.Context, db gorpmapper.SqlExecutorWithTx, Result: j.Result, Outputs: contexts.Jobs[n].Outputs, } - if j.Result == sdk.StatusFail && run.WorkflowData.Workflow.Jobs[n].ContinueOnError { - needContext.Result = sdk.StatusSuccess + if j.Result == sdk.V2WorkflowRunJobStatusFail && run.WorkflowData.Workflow.Jobs[n].ContinueOnError { + needContext.Result = sdk.V2WorkflowRunJobStatusSuccess } contexts.Needs[n] = needContext } @@ -452,7 +454,7 @@ func (api *API) postV2RegisterWorkerHandler() ([]service.RbacChecker, service.Ha if err != nil { return err } - if runJob.Status != sdk.StatusScheduling || runJob.HatcheryName != hatch.Name || runJob.ID != jobRunID || runJob.Region != regionName { + if runJob.Status != sdk.V2WorkflowRunJobStatusScheduling || runJob.HatcheryName != hatch.Name || runJob.ID != jobRunID || runJob.Region != regionName { return sdk.WrapError(sdk.ErrForbidden, "unable to take job %s, current status: %s, hatchery: %s, region: %s", runJob.ID, runJob.Status, runJob.HatcheryName, runJob.Region) } diff --git a/engine/api/v2_queue_worker_test.go b/engine/api/v2_queue_worker_test.go index 96a1f12d6e..95c347cacc 100644 --- a/engine/api/v2_queue_worker_test.go +++ b/engine/api/v2_queue_worker_test.go @@ -58,7 +58,7 @@ func TestWorkerUnregistered(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -97,7 +97,7 @@ hatcheries: jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusScheduling, + Status: sdk.V2WorkflowRunJobStatusScheduling, ModelType: "docker", Region: "default", WorkflowRunID: wr.ID, @@ -133,7 +133,7 @@ func TestWorkerRefresh(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -172,7 +172,7 @@ hatcheries: jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusScheduling, + Status: sdk.V2WorkflowRunJobStatusScheduling, ModelType: "docker", Region: "default", WorkflowRunID: wr.ID, @@ -226,7 +226,7 @@ func TestWorkerTakeJobHandler(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -266,7 +266,7 @@ hatcheries: jobRunSuccess := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, JobID: "myjob", ModelType: "docker", Region: "default", @@ -298,7 +298,7 @@ hatcheries: jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusScheduling, + Status: sdk.V2WorkflowRunJobStatusScheduling, ModelType: "docker", Region: "default", WorkflowRunID: wr.ID, @@ -323,7 +323,7 @@ hatcheries: var takeJob sdk.V2TakeJobResponse require.NoError(t, json.Unmarshal(w.Body.Bytes(), &takeJob)) - require.Equal(t, sdk.StatusBuilding, takeJob.RunJob.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusBuilding, takeJob.RunJob.Status) require.Equal(t, workerName, takeJob.RunJob.WorkerName) wkDB, err := worker_v2.LoadByID(ctx, db, wkr.ID) @@ -419,7 +419,7 @@ func TestWorkerRegister(t *testing.T) { wkfName := sdk.RandomString(10) wr := sdk.V2WorkflowRun{ - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, ProjectKey: proj.Key, UserID: admin.ID, WorkflowName: wkfName, @@ -465,7 +465,7 @@ hatcheries: jobRun := sdk.V2WorkflowRunJob{ ProjectKey: proj.Key, UserID: admin.ID, - Status: sdk.StatusScheduling, + Status: sdk.V2WorkflowRunJobStatusScheduling, ModelType: "docker", Region: "default", WorkflowRunID: wr.ID, diff --git a/engine/api/v2_workflow_run.go b/engine/api/v2_workflow_run.go index 3b44f8c682..c1fe4b95cd 100644 --- a/engine/api/v2_workflow_run.go +++ b/engine/api/v2_workflow_run.go @@ -277,7 +277,9 @@ func (api *API) postStopJobHandler() ([]service.RbacChecker, service.Handler) { } defer tx.Rollback() // nolint - runJob.Status = sdk.StatusStopped + runJob.Status = sdk.V2WorkflowRunJobStatusStopped + now := time.Now() + runJob.Ended = &now if err := workflow_v2.UpdateJobRun(ctx, tx, runJob); err != nil { return err } @@ -595,7 +597,9 @@ func (api *API) postStopWorkflowRunHandler() ([]service.RbacChecker, service.Han } for _, rj := range runJobs { - rj.Status = sdk.StatusStopped + rj.Status = sdk.V2WorkflowRunJobStatusStopped + now := time.Now() + rj.Ended = &now tx, err := api.mustDB().Begin() if err != nil { @@ -623,7 +627,7 @@ func (api *API) postStopWorkflowRunHandler() ([]service.RbacChecker, service.Han return sdk.WithStack(err) } } - wr.Status = sdk.StatusStopped + wr.Status = sdk.V2WorkflowRunStatusStopped tx, err := api.mustDB().Begin() if err != nil { return err @@ -774,7 +778,7 @@ func (api *API) putWorkflowRunV2Handler() ([]service.RbacChecker, service.Handle return err } - if !sdk.StatusIsTerminated(wr.Status) { + if !wr.Status.IsTerminated() { return sdk.NewErrorFrom(sdk.ErrWrongRequest, "unable to rerun a running workflow") } @@ -787,12 +791,12 @@ func (api *API) putWorkflowRunV2Handler() ([]service.RbacChecker, service.Handle runJobToRestart := make(map[string]sdk.V2WorkflowRunJob) for _, rj := range runJobs { runJobsMap[rj.ID] = rj - if rj.Status == sdk.StatusFail { + if rj.Status == sdk.V2WorkflowRunJobStatusFail || rj.Status == sdk.V2WorkflowRunJobStatusStopped { runJobToRestart[rj.ID] = rj } } if len(runJobToRestart) == 0 { - return sdk.NewErrorFrom(sdk.ErrInvalidData, "workflow doesn't contains failed jobs") + return sdk.NewErrorFrom(sdk.ErrInvalidData, "workflow doesn't contains failed or stopped jobs") } runJobsToKeep := workflow_v2.RetrieveJobToKeep(ctx, wr.WorkflowData.Workflow, runJobsMap, runJobToRestart) @@ -811,7 +815,7 @@ func (api *API) putWorkflowRunV2Handler() ([]service.RbacChecker, service.Handle WorkflowRunID: wr.ID, IssuedAt: time.Now(), Level: sdk.WorkflowRunInfoLevelInfo, - Message: u.GetFullname() + " restarted all failed jobs", + Message: u.GetFullname() + " restarted all failed and stopped jobs", } if err := workflow_v2.InsertRunInfo(ctx, tx, &runInfo); err != nil { return err @@ -831,7 +835,7 @@ func (api *API) putWorkflowRunV2Handler() ([]service.RbacChecker, service.Handle func restartWorkflowRun(ctx context.Context, tx gorpmapper.SqlExecutorWithTx, wr *sdk.V2WorkflowRun, runJobsToKeep map[string]sdk.V2WorkflowRunJob) error { wr.RunAttempt++ - wr.Status = sdk.StatusBuilding + wr.Status = sdk.V2WorkflowRunStatusBuilding wr.Contexts.CDS.RunAttempt = wr.RunAttempt srvs, err := services.LoadAllByType(ctx, tx, sdk.TypeCDN) @@ -898,7 +902,7 @@ func (api *API) putWorkflowRunJobV2Handler() ([]service.RbacChecker, service.Han return err } - if !sdk.StatusIsTerminated(wr.Status) { + if !wr.Status.IsTerminated() { return sdk.NewErrorFrom(sdk.ErrWrongRequest, "unable to start a job on a running workflow") } @@ -920,7 +924,7 @@ func (api *API) putWorkflowRunJobV2Handler() ([]service.RbacChecker, service.Han } // Check job status - if jobToRun.Status != sdk.StatusSkipped { + if jobToRun.Status != sdk.V2WorkflowRunJobStatusSkipped { return sdk.NewErrorFrom(sdk.ErrWrongRequest, "unable to run manually a non skipped job") } @@ -1188,7 +1192,7 @@ func (api *API) startWorkflowV2(ctx context.Context, proj sdk.Project, vcsProjec WorkflowName: wk.Name, WorkflowRef: wkEntity.Ref, WorkflowSha: wkEntity.Commit, - Status: sdk.StatusCrafting, + Status: sdk.V2WorkflowRunStatusCrafting, RunAttempt: 0, Started: time.Now(), LastModified: time.Now(), diff --git a/engine/api/v2_workflow_run_craft.go b/engine/api/v2_workflow_run_craft.go index adaa0a791a..74384baf25 100644 --- a/engine/api/v2_workflow_run_craft.go +++ b/engine/api/v2_workflow_run_craft.go @@ -128,7 +128,7 @@ func (api *API) craftWorkflowRunV2(ctx context.Context, id string) error { ctx = context.WithValue(ctx, cdslog.Project, run.ProjectKey) ctx = context.WithValue(ctx, cdslog.Workflow, run.WorkflowName) - if run.Status != sdk.StatusCrafting { + if run.Status != sdk.V2WorkflowRunStatusCrafting { return nil } @@ -279,7 +279,7 @@ func (api *API) craftWorkflowRunV2(ctx context.Context, id string) error { } defer tx.Rollback() // nolint - run.Status = sdk.StatusBuilding + run.Status = sdk.V2WorkflowRunStatusBuilding if err := workflow_v2.UpdateRun(ctx, tx, run); err != nil { return err } @@ -590,14 +590,14 @@ func stopRun(ctx context.Context, db *gorp.DbMap, store cache.Store, run *sdk.V2 } defer tx.Rollback() // nolint - var status = sdk.StatusSkipped + status := sdk.V2WorkflowRunStatusSkipped for _, msg := range messages { if err := workflow_v2.InsertRunInfo(ctx, tx, &msg); err != nil { return err } - if msg.Level != sdk.WorkflowRunInfoLevelWarning && status == sdk.StatusSkipped { - status = sdk.StatusFail + if msg.Level != sdk.WorkflowRunInfoLevelWarning && status == sdk.V2WorkflowRunStatusSkipped { + status = sdk.V2WorkflowRunStatusFail } } diff --git a/engine/api/v2_workflow_run_craft_test.go b/engine/api/v2_workflow_run_craft_test.go index a6830e0c67..a63cdaa210 100644 --- a/engine/api/v2_workflow_run_craft_test.go +++ b/engine/api/v2_workflow_run_craft_test.go @@ -41,7 +41,7 @@ func TestCraftWorkflowRunNoHatchery(t *testing.T) { wr := sdk.V2WorkflowRun{ UserID: admin.ID, ProjectKey: proj.Key, - Status: sdk.StatusCrafting, + Status: sdk.V2WorkflowRunStatusCrafting, VCSServerID: vcsProject.ID, RepositoryID: repo.ID, RunNumber: 0, @@ -76,7 +76,7 @@ func TestCraftWorkflowRunNoHatchery(t *testing.T) { wrDB, err := workflow_v2.LoadRunByID(ctx, db, wr.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusFail, wrDB.Status) + require.Equal(t, sdk.V2WorkflowRunStatusFail, wrDB.Status) wrInfos, err := workflow_v2.LoadRunInfosByRunID(ctx, db, wr.ID) require.NoError(t, err) require.Equal(t, 1, len(wrInfos)) @@ -103,7 +103,7 @@ func TestCraftWorkflowRunDepsNotFound(t *testing.T) { wr := sdk.V2WorkflowRun{ UserID: admin.ID, ProjectKey: proj.Key, - Status: sdk.StatusCrafting, + Status: sdk.V2WorkflowRunStatusCrafting, VCSServerID: vcsProject.ID, RepositoryID: repo.ID, RunNumber: 0, @@ -157,7 +157,7 @@ func TestCraftWorkflowRunDepsNotFound(t *testing.T) { wrDB, err := workflow_v2.LoadRunByID(ctx, db, wr.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusFail, wrDB.Status) + require.Equal(t, sdk.V2WorkflowRunStatusFail, wrDB.Status) wrInfos, err := workflow_v2.LoadRunInfosByRunID(ctx, db, wr.ID) require.NoError(t, err) require.Equal(t, 1, len(wrInfos)) @@ -184,7 +184,7 @@ func TestCraftWorkflowRunDepsSameRepo(t *testing.T) { wr := sdk.V2WorkflowRun{ UserID: admin.ID, ProjectKey: proj.Key, - Status: sdk.StatusCrafting, + Status: sdk.V2WorkflowRunStatusCrafting, VCSServerID: vcsProject.ID, RepositoryID: repo.ID, RunNumber: 0, @@ -278,7 +278,7 @@ func TestCraftWorkflowRunDepsSameRepo(t *testing.T) { wrDB, err := workflow_v2.LoadRunByID(ctx, db, wr.ID) require.NoError(t, err) t.Logf("%+v", wrDB.WorkflowData.Actions) - require.Equal(t, sdk.StatusBuilding, wrDB.Status) + require.Equal(t, sdk.V2WorkflowRunStatusBuilding, wrDB.Status) wrInfos, err := workflow_v2.LoadRunInfosByRunID(ctx, db, wr.ID) require.NoError(t, err) require.Equal(t, 0, len(wrInfos)) @@ -309,7 +309,7 @@ func TestCraftWorkflowRunDepsDifferentRepo(t *testing.T) { wr := sdk.V2WorkflowRun{ UserID: admin.ID, ProjectKey: proj.Key, - Status: sdk.StatusCrafting, + Status: sdk.V2WorkflowRunStatusCrafting, VCSServerID: vcsProject.ID, RepositoryID: repo.ID, RunNumber: 0, @@ -444,7 +444,7 @@ func TestCraftWorkflowRunDepsDifferentRepo(t *testing.T) { wrDB, err := workflow_v2.LoadRunByID(ctx, db, wr.ID) require.NoError(t, err) - require.Equal(t, wrDB.Status, sdk.StatusBuilding) + require.Equal(t, wrDB.Status, sdk.V2WorkflowRunStatusBuilding) wrInfos, err := workflow_v2.LoadRunInfosByRunID(ctx, db, wr.ID) require.NoError(t, err) require.Equal(t, 0, len(wrInfos)) diff --git a/engine/api/v2_workflow_run_engine.go b/engine/api/v2_workflow_run_engine.go index c201b54676..d6a2f7f7ba 100644 --- a/engine/api/v2_workflow_run_engine.go +++ b/engine/api/v2_workflow_run_engine.go @@ -148,7 +148,7 @@ func (api *API) workflowRunV2Trigger(ctx context.Context, wrEnqueue sdk.V2Workfl return sdk.WrapError(err, "unable to load repository %s", run.RepositoryID) } - if sdk.StatusIsTerminated(run.Status) { + if run.Status.IsTerminated() { log.Debug(ctx, "workflow run already on a final state") return nil } @@ -171,7 +171,7 @@ func (api *API) workflowRunV2Trigger(ctx context.Context, wrEnqueue sdk.V2Workfl return err } } - run.Status = sdk.StatusFail + run.Status = sdk.V2WorkflowRunStatusFail if err := workflow_v2.UpdateRun(ctx, tx, run); err != nil { return err } @@ -253,8 +253,8 @@ func (api *API) workflowRunV2Trigger(ctx context.Context, wrEnqueue sdk.V2Workfl } // End workflow if there is no more job to handle, no running jobs and current status is not terminated - if len(jobsToQueue) == 0 && len(skippedJobs) == 0 && !sdk.StatusIsTerminated(run.Status) { - finalStatus, err := computeJobRunStatus(ctx, tx, run.ID, run.RunAttempt) + if len(jobsToQueue) == 0 && len(skippedJobs) == 0 && !run.Status.IsTerminated() { + finalStatus, err := computeRunStatusFromJobsStatus(ctx, tx, run.ID, run.RunAttempt) if err != nil { return err } @@ -277,7 +277,7 @@ func (api *API) workflowRunV2Trigger(ctx context.Context, wrEnqueue sdk.V2Workfl } }) - if sdk.StatusIsTerminated(run.Status) { + if run.Status.IsTerminated() { event_v2.PublishRunEvent(ctx, api.Cache, sdk.EventRunEnded, *run, *u) } @@ -289,7 +289,7 @@ func (api *API) workflowRunV2Trigger(ctx context.Context, wrEnqueue sdk.V2Workfl // Send to websocket for _, rj := range runJobs { switch rj.Status { - case sdk.StatusFail: + case sdk.V2WorkflowRunJobStatusFail: event_v2.PublishRunJobEvent(ctx, api.Cache, sdk.EventRunJobEnded, run.Contexts.Git.Server, run.Contexts.Git.Repository, rj) default: event_v2.PublishRunJobEvent(ctx, api.Cache, sdk.EventRunJobEnqueued, run.Contexts.Git.Server, run.Contexts.Git.Repository, rj) @@ -519,7 +519,7 @@ func computeRunJobsWorkerModel(ctx context.Context, db *gorp.DbMap, store cache. var mapContexts map[string]interface{} if err := json.Unmarshal(bts, &mapContexts); err != nil { - rj.Status = sdk.StatusFail + rj.Status = sdk.V2WorkflowRunJobStatusFail runJobInfos[rj.JobID] = sdk.V2WorkflowRunJobInfo{ WorkflowRunID: run.ID, Level: sdk.WorkflowRunInfoLevelError, @@ -532,7 +532,7 @@ func computeRunJobsWorkerModel(ctx context.Context, db *gorp.DbMap, store cache. ap := sdk.NewActionParser(mapContexts, sdk.DefaultFuncs) model, err := ap.InterpolateToString(ctx, rj.Job.RunsOn.Model) if err != nil { - rj.Status = sdk.StatusFail + rj.Status = sdk.V2WorkflowRunJobStatusFail runJobInfos[rj.JobID] = sdk.V2WorkflowRunJobInfo{ WorkflowRunID: run.ID, Level: sdk.WorkflowRunInfoLevelError, @@ -545,7 +545,7 @@ func computeRunJobsWorkerModel(ctx context.Context, db *gorp.DbMap, store cache. completeName, msg, err := wref.checkWorkerModel(ctx, db, store, rj.JobID, model, rj.Region, "") if err != nil { - rj.Status = sdk.StatusFail + rj.Status = sdk.V2WorkflowRunJobStatusFail runJobInfos[rj.JobID] = sdk.V2WorkflowRunJobInfo{ WorkflowRunID: run.ID, Level: sdk.WorkflowRunInfoLevelError, @@ -556,7 +556,7 @@ func computeRunJobsWorkerModel(ctx context.Context, db *gorp.DbMap, store cache. continue } if msg != nil { - rj.Status = sdk.StatusFail + rj.Status = sdk.V2WorkflowRunJobStatusFail runJobInfos[rj.JobID] = sdk.V2WorkflowRunJobInfo{ WorkflowRunID: run.ID, Level: sdk.WorkflowRunInfoLevelError, @@ -577,7 +577,7 @@ func computeRunJobsWorkerModel(ctx context.Context, db *gorp.DbMap, store cache. return runJobInfos } -func prepareRunJobs(_ context.Context, proj sdk.Project, run sdk.V2WorkflowRun, wrEnqueue sdk.V2WorkflowRunEnqueue, jobsToQueue map[string]sdk.V2Job, jobStatus string, u sdk.AuthentifiedUser) []sdk.V2WorkflowRunJob { +func prepareRunJobs(_ context.Context, proj sdk.Project, run sdk.V2WorkflowRun, wrEnqueue sdk.V2WorkflowRunEnqueue, jobsToQueue map[string]sdk.V2Job, jobStatus sdk.V2WorkflowRunJobStatus, u sdk.AuthentifiedUser) []sdk.V2WorkflowRunJob { runJobs := make([]sdk.V2WorkflowRunJob, 0) for jobID, jobDef := range jobsToQueue { // Compute job matrix strategy @@ -683,7 +683,7 @@ func retrieveJobToQueue(ctx context.Context, db *gorp.DbMap, run *sdk.V2Workflow for _, rj := range runJobs { // addition check for matrix job, only keep not terminated one if present runjob, has := allrunJobsMap[rj.JobID] - if !has || sdk.StatusIsTerminated(runjob.Status) { + if !has || runjob.Status.IsTerminated() { allrunJobsMap[rj.JobID] = rj } } @@ -786,22 +786,22 @@ func checkJob(ctx context.Context, db gorp.SqlExecutor, u sdk.AuthentifiedUser, return canRun, runInfos, nil } -func computeJobRunStatus(ctx context.Context, db gorp.SqlExecutor, runID string, runAttempt int64) (string, error) { +func computeRunStatusFromJobsStatus(ctx context.Context, db gorp.SqlExecutor, runID string, runAttempt int64) (sdk.V2WorkflowRunStatus, error) { runJobs, err := workflow_v2.LoadRunJobsByRunID(ctx, db, runID, runAttempt) if err != nil { return "", err } - finalStatus := sdk.StatusSuccess + finalStatus := sdk.V2WorkflowRunStatusSuccess for _, rj := range runJobs { - if rj.Status == sdk.StatusFail && finalStatus != sdk.StatusStopped && sdk.StatusIsTerminated(finalStatus) && !rj.Job.ContinueOnError { - finalStatus = rj.Status + if rj.Status == sdk.V2WorkflowRunJobStatusFail && finalStatus != sdk.V2WorkflowRunStatusStopped && finalStatus.IsTerminated() && !rj.Job.ContinueOnError { + finalStatus = sdk.V2WorkflowRunStatusFail } - if rj.Status == sdk.StatusStopped && sdk.StatusIsTerminated(finalStatus) { - finalStatus = sdk.StatusStopped + if rj.Status == sdk.V2WorkflowRunJobStatusStopped && finalStatus.IsTerminated() { + finalStatus = sdk.V2WorkflowRunStatusStopped } - if !sdk.StatusIsTerminated(rj.Status) { - finalStatus = sdk.StatusBuilding + if !rj.Status.IsTerminated() { + finalStatus = sdk.V2WorkflowRunStatusBuilding } } return finalStatus, nil @@ -934,7 +934,7 @@ func (api *API) triggerBlockedWorkflowRun(ctx context.Context, wr sdk.V2Workflow }() log.Info(ctx, "triggerBlockedWorkflowRun: trigger workflow %s for run %d", wr.WorkflowName, wr.RunNumber) - if wr.Status != sdk.StatusBuilding { + if wr.Status != sdk.V2WorkflowRunStatusBuilding { return nil } @@ -945,10 +945,10 @@ func (api *API) triggerBlockedWorkflowRun(ctx context.Context, wr sdk.V2Workflow } var lastJobs sdk.V2WorkflowRunJob for _, rj := range runJobs { - if !sdk.StatusIsTerminated(rj.Status) { + if !rj.Status.IsTerminated() { return nil } - if rj.Started.After(lastJobs.Started) { + if sdk.TimeSafe(rj.Started).After(sdk.TimeSafe(lastJobs.Started)) { lastJobs = rj } } diff --git a/engine/api/v2_workflow_run_engine_context.go b/engine/api/v2_workflow_run_engine_context.go index afa44f5660..885519a759 100644 --- a/engine/api/v2_workflow_run_engine_context.go +++ b/engine/api/v2_workflow_run_engine_context.go @@ -25,7 +25,7 @@ func computeExistingRunJobContexts(runJobs []sdk.V2WorkflowRunJob, runResults [] jobsContext := sdk.JobsResultContext{} matrixJobs := make(map[string][]sdk.JobResultContext) for _, rj := range runJobs { - if sdk.StatusIsTerminated(rj.Status) && len(rj.Matrix) == 0 { + if rj.Status.IsTerminated() && len(rj.Matrix) == 0 { result := sdk.JobResultContext{ Result: rj.Status, Outputs: sdk.JobResultOutput{}, @@ -60,9 +60,9 @@ func computeExistingRunJobContexts(runJobs []sdk.V2WorkflowRunJob, runResults [] nextjob: for k := range matrixJobs { outputs := sdk.JobResultOutput{} - var finalStatus string + var finalStatus sdk.V2WorkflowRunJobStatus for _, rj := range matrixJobs[k] { - if !sdk.StatusIsTerminated(rj.Result) { + if !rj.Result.IsTerminated() { continue nextjob } for outputK, outputV := range rj.Outputs { @@ -70,14 +70,14 @@ nextjob: } switch finalStatus { - case "": + case sdk.V2WorkflowRunJobStatusUnknown: finalStatus = rj.Result - case sdk.StatusSuccess: - if rj.Result == sdk.StatusStopped || rj.Result == sdk.StatusFail { + case sdk.V2WorkflowRunJobStatusSuccess: + if rj.Result == sdk.V2WorkflowRunJobStatusStopped || rj.Result == sdk.V2WorkflowRunJobStatusFail { finalStatus = rj.Result } - case sdk.StatusFail: - if rj.Result == sdk.StatusStopped { + case sdk.V2WorkflowRunJobStatusFail: + if rj.Result == sdk.V2WorkflowRunJobStatusStopped { finalStatus = rj.Result } } @@ -105,8 +105,8 @@ func buildContextForJob(_ context.Context, allJobs map[string]sdk.V2Job, runJobs Outputs: j.Outputs, } // override result if job has continue-on-error - if allJobs[n].ContinueOnError && j.Result == sdk.StatusFail { - needContext.Result = sdk.StatusSuccess + if allJobs[n].ContinueOnError && j.Result == sdk.V2WorkflowRunJobStatusFail { + needContext.Result = sdk.V2WorkflowRunJobStatusSuccess } needsContext[n] = needContext diff --git a/engine/api/v2_workflow_run_engine_test.go b/engine/api/v2_workflow_run_engine_test.go index fecea30d6d..982576686c 100644 --- a/engine/api/v2_workflow_run_engine_test.go +++ b/engine/api/v2_workflow_run_engine_test.go @@ -18,13 +18,13 @@ import ( func TestJobConditionSuccess(t *testing.T) { jobsContext := sdk.JobsResultContext{ "job1": { - Result: sdk.StatusFail, + Result: sdk.V2WorkflowRunJobStatusFail, }, "job2": { - Result: sdk.StatusSuccess, + Result: sdk.V2WorkflowRunJobStatusSuccess, }, "job3": { - Result: sdk.StatusFail, + Result: sdk.V2WorkflowRunJobStatusFail, }, } allJobs := map[string]sdk.V2Job{ @@ -114,19 +114,19 @@ func TestBuildCurrentJobContext(t *testing.T) { jobsContext := sdk.JobsResultContext{ "job1": { - Result: sdk.StatusFail, + Result: sdk.V2WorkflowRunJobStatusFail, }, "job2": { - Result: sdk.StatusSuccess, + Result: sdk.V2WorkflowRunJobStatusSuccess, }, "job3": { - Result: sdk.StatusSuccess, + Result: sdk.V2WorkflowRunJobStatusSuccess, }, "job4": { - Result: sdk.StatusFail, + Result: sdk.V2WorkflowRunJobStatusFail, }, "job5": { - Result: sdk.StatusFail, + Result: sdk.V2WorkflowRunJobStatusFail, }, } @@ -134,8 +134,8 @@ func TestBuildCurrentJobContext(t *testing.T) { buildAncestorJobContext("job6", allJobs, jobsContext, currentJobContext) require.Equal(t, 3, len(currentJobContext)) - require.Equal(t, sdk.StatusFail, currentJobContext["job1"].Result) - require.Equal(t, sdk.StatusFail, currentJobContext["job5"].Result) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, currentJobContext["job1"].Result) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, currentJobContext["job5"].Result) } func TestGenerateMatrix(t *testing.T) { @@ -220,7 +220,7 @@ func TestWorkflowTrigger1Job(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -248,7 +248,7 @@ func TestWorkflowTrigger1Job(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, len(runjobs)) - require.Equal(t, sdk.StatusWaiting, runjobs[0].Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusWaiting, runjobs[0].Status) require.Equal(t, "job1", runjobs[0].JobID) } @@ -302,7 +302,7 @@ func TestWorkflowTriggerWithCondition(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -390,7 +390,7 @@ func TestWorkflowTriggerWithConditionKOSyntax(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -445,7 +445,7 @@ func TestTriggerBlockedWorkflowRuns(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -465,7 +465,7 @@ func TestTriggerBlockedWorkflowRuns(t *testing.T) { ProjectKey: wr.ProjectKey, RunAttempt: wr.RunAttempt, JobID: sdk.RandomString(10), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj)) @@ -477,7 +477,7 @@ func TestTriggerBlockedWorkflowRuns(t *testing.T) { ProjectKey: wr.ProjectKey, RunAttempt: wr.RunAttempt, JobID: sdk.RandomString(10), - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj11)) @@ -494,7 +494,7 @@ func TestTriggerBlockedWorkflowRuns(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -514,7 +514,7 @@ func TestTriggerBlockedWorkflowRuns(t *testing.T) { ProjectKey: wr.ProjectKey, RunAttempt: wr.RunAttempt, JobID: sdk.RandomString(10), - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj2)) @@ -526,7 +526,7 @@ func TestTriggerBlockedWorkflowRuns(t *testing.T) { ProjectKey: wr.ProjectKey, RunAttempt: wr.RunAttempt, JobID: sdk.RandomString(10), - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj3)) @@ -591,7 +591,7 @@ func TestWorkflowTriggerStage(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -627,7 +627,7 @@ func TestWorkflowTriggerStage(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, len(runjobs)) - require.Equal(t, sdk.StatusWaiting, runjobs[0].Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusWaiting, runjobs[0].Status) require.Equal(t, "job1", runjobs[0].JobID) } @@ -680,7 +680,7 @@ func TestWorkflowStageNeeds(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -705,7 +705,7 @@ func TestWorkflowStageNeeds(t *testing.T) { wrj := sdk.V2WorkflowRunJob{ Job: wr.WorkflowData.Workflow.Jobs["job1"], - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, JobID: "job1", WorkflowRunID: wr.ID, ProjectKey: wr.ProjectKey, @@ -734,7 +734,7 @@ func TestWorkflowStageNeeds(t *testing.T) { } require.NotEmpty(t, jobs["job2"]) - require.Equal(t, sdk.StatusWaiting, jobs["job2"].Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusWaiting, jobs["job2"].Status) } func TestWorkflowMatrixNeeds(t *testing.T) { @@ -786,7 +786,7 @@ func TestWorkflowMatrixNeeds(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -809,7 +809,7 @@ func TestWorkflowMatrixNeeds(t *testing.T) { wrjFoo1 := sdk.V2WorkflowRunJob{ Job: wr.WorkflowData.Workflow.Jobs["job1"], - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, JobID: "job1", WorkflowRunID: wr.ID, ProjectKey: wr.ProjectKey, @@ -823,7 +823,7 @@ func TestWorkflowMatrixNeeds(t *testing.T) { wrjFoo2 := sdk.V2WorkflowRunJob{ Job: wr.WorkflowData.Workflow.Jobs["job1"], - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, JobID: "job1", WorkflowRunID: wr.ID, ProjectKey: wr.ProjectKey, @@ -853,7 +853,7 @@ func TestWorkflowMatrixNeeds(t *testing.T) { // END Matrix 2 - It must trigger job 2 - wrjFoo2.Status = sdk.StatusSuccess + wrjFoo2.Status = sdk.V2WorkflowRunJobStatusSuccess require.NoError(t, workflow_v2.UpdateJobRun(context.TODO(), db, &wrjFoo2)) require.NoError(t, api.workflowRunV2Trigger(context.TODO(), sdk.V2WorkflowRunEnqueue{ @@ -916,7 +916,7 @@ func TestWorkflowStageMatrixNeeds(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -947,7 +947,7 @@ func TestWorkflowStageMatrixNeeds(t *testing.T) { wrjFoo1 := sdk.V2WorkflowRunJob{ Job: wr.WorkflowData.Workflow.Jobs["job1"], - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, JobID: "job1", WorkflowRunID: wr.ID, ProjectKey: wr.ProjectKey, @@ -961,7 +961,7 @@ func TestWorkflowStageMatrixNeeds(t *testing.T) { wrjFoo2 := sdk.V2WorkflowRunJob{ Job: wr.WorkflowData.Workflow.Jobs["job1"], - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, JobID: "job1", WorkflowRunID: wr.ID, ProjectKey: wr.ProjectKey, @@ -989,7 +989,7 @@ func TestWorkflowStageMatrixNeeds(t *testing.T) { // END Matrix 2 - It must trigger job 2 - wrjFoo2.Status = sdk.StatusSuccess + wrjFoo2.Status = sdk.V2WorkflowRunJobStatusSuccess require.NoError(t, workflow_v2.UpdateJobRun(context.TODO(), db, &wrjFoo2)) require.NoError(t, api.workflowRunV2Trigger(context.TODO(), sdk.V2WorkflowRunEnqueue{ @@ -1052,7 +1052,7 @@ func TestWorkflowSkippedJob(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -1074,7 +1074,7 @@ func TestWorkflowSkippedJob(t *testing.T) { wrj1 := sdk.V2WorkflowRunJob{ Job: wr.WorkflowData.Workflow.Jobs["job1"], - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, JobID: "job1", WorkflowRunID: wr.ID, ProjectKey: wr.ProjectKey, @@ -1106,7 +1106,7 @@ func TestWorkflowSkippedJob(t *testing.T) { mapJob[rj.JobID] = rj } - require.Equal(t, sdk.StatusSkipped, mapJob["job2"].Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusSkipped, mapJob["job2"].Status) // Trigger again to process job2 require.NoError(t, api.workflowRunV2Trigger(context.TODO(), sdk.V2WorkflowRunEnqueue{ @@ -1123,5 +1123,5 @@ func TestWorkflowSkippedJob(t *testing.T) { } require.Equal(t, 3, len(runjobs)) - require.Equal(t, sdk.StatusWaiting, mapJob["job3"].Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusWaiting, mapJob["job3"].Status) } diff --git a/engine/api/v2_workflow_run_job_routines.go b/engine/api/v2_workflow_run_job_routines.go index e393d4c4ab..b529604ca8 100644 --- a/engine/api/v2_workflow_run_job_routines.go +++ b/engine/api/v2_workflow_run_job_routines.go @@ -149,7 +149,7 @@ func reEnqueueScheduledJob(ctx context.Context, store cache.Store, db *gorp.DbMa if err != nil { return err } - if runJob.Status != sdk.StatusScheduling { + if runJob.Status != sdk.V2WorkflowRunJobStatusScheduling { return nil } @@ -161,9 +161,9 @@ func reEnqueueScheduledJob(ctx context.Context, store cache.Store, db *gorp.DbMa } defer tx.Rollback() // nolint - log.Info(ctx, fmt.Sprintf("reEnqueueScheduledJob: re-enqueue job %s/%s (timeout %s) on workflow %s run %d", runJob.JobID, runJob.ID, time.Now().Sub(runJob.Scheduled).String(), runJob.WorkflowName, runJob.RunNumber)) + log.Info(ctx, fmt.Sprintf("reEnqueueScheduledJob: re-enqueue job %s/%s (timeout %s) on workflow %s run %d", runJob.JobID, runJob.ID, time.Now().Sub(sdk.TimeSafe(runJob.Scheduled)).String(), runJob.WorkflowName, runJob.RunNumber)) - runJob.Status = sdk.StatusWaiting + runJob.Status = sdk.V2WorkflowRunJobStatusWaiting runJob.HatcheryName = "" if err := workflow_v2.UpdateJobRun(ctx, tx, runJob); err != nil { @@ -215,7 +215,7 @@ func (api *API) stopDeadJob(ctx context.Context, store cache.Store, db *gorp.DbM ctx = context.WithValue(ctx, cdslog.Workflow, runJob.WorkflowName) - if sdk.StatusIsTerminated(runJob.Status) { + if runJob.Status.IsTerminated() { return nil } @@ -226,7 +226,9 @@ func (api *API) stopDeadJob(ctx context.Context, store cache.Store, db *gorp.DbM defer tx.Rollback() // nolint log.Info(ctx, fmt.Sprintf("stopDeadJob: stopping job %s/%s on workflow %s run %d", runJob.JobID, runJob.ID, runJob.WorkflowName, runJob.RunNumber)) - runJob.Status = sdk.StatusStopped + runJob.Status = sdk.V2WorkflowRunJobStatusStopped + now := time.Now() + runJob.Ended = &now if err := workflow_v2.UpdateJobRun(ctx, tx, runJob); err != nil { return err diff --git a/engine/api/v2_workflow_run_job_routines_test.go b/engine/api/v2_workflow_run_job_routines_test.go index 994d5fa696..de84260c3d 100644 --- a/engine/api/v2_workflow_run_job_routines_test.go +++ b/engine/api/v2_workflow_run_job_routines_test.go @@ -37,7 +37,7 @@ func TestReEnqueueScheduledJobs(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -52,15 +52,18 @@ func TestReEnqueueScheduledJobs(t *testing.T) { } require.NoError(t, workflow_v2.InsertRun(context.Background(), db, &wr)) + now := time.Now() + nowMinus20Min := now.Add(-20 * time.Minute) + wrj := sdk.V2WorkflowRunJob{ Job: sdk.V2Job{}, WorkflowRunID: wr.ID, UserID: admin.ID, Username: admin.Username, ProjectKey: wr.ProjectKey, - Scheduled: time.Now().Add(-20 * time.Minute), + Scheduled: &nowMinus20Min, JobID: sdk.RandomString(10), - Status: sdk.StatusScheduling, + Status: sdk.V2WorkflowRunJobStatusScheduling, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj)) @@ -70,9 +73,9 @@ func TestReEnqueueScheduledJobs(t *testing.T) { UserID: admin.ID, Username: admin.Username, ProjectKey: wr.ProjectKey, - Scheduled: time.Now(), + Scheduled: &now, JobID: sdk.RandomString(10), - Status: sdk.StatusScheduling, + Status: sdk.V2WorkflowRunJobStatusScheduling, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj2)) @@ -85,7 +88,7 @@ func TestReEnqueueScheduledJobs(t *testing.T) { rjDB, err := workflow_v2.LoadRunJobByID(ctx, db, jobs[0].ID) require.NoError(t, err) - require.Equal(t, sdk.StatusWaiting, rjDB.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusWaiting, rjDB.Status) } func TestStopDeadJobs(t *testing.T) { @@ -112,7 +115,7 @@ func TestStopDeadJobs(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -140,7 +143,7 @@ func TestStopDeadJobs(t *testing.T) { Username: admin.Username, ProjectKey: wr.ProjectKey, JobID: sdk.RandomString(10), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj)) @@ -160,7 +163,7 @@ func TestStopDeadJobs(t *testing.T) { Username: admin.Username, ProjectKey: wr.ProjectKey, JobID: sdk.RandomString(10), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj2)) @@ -173,5 +176,5 @@ func TestStopDeadJobs(t *testing.T) { rjDB, err := workflow_v2.LoadRunJobByID(ctx, db, jobs[0].ID) require.NoError(t, err) - require.Equal(t, sdk.StatusStopped, rjDB.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusStopped, rjDB.Status) } diff --git a/engine/api/v2_workflow_run_test.go b/engine/api/v2_workflow_run_test.go index 1c28f8dfba..357f1c9d13 100644 --- a/engine/api/v2_workflow_run_test.go +++ b/engine/api/v2_workflow_run_test.go @@ -47,7 +47,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunStatusSuccess, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -92,7 +92,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { require.NoError(t, workflow_v2.InsertRun(context.Background(), db, &wr)) wrjJob1 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job1", @@ -101,7 +101,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob1)) wrjJob2 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSkipped, + Status: sdk.V2WorkflowRunJobStatusSkipped, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job2", @@ -111,7 +111,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob2)) wrjJob3 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job3", @@ -120,7 +120,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob3)) wrjJob4 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job4", @@ -129,7 +129,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob4)) wrjJob5 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job5", @@ -138,7 +138,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob5)) wrjJob6 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job6", @@ -147,7 +147,7 @@ func TestRunManualJob_WrongGateReviewer(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob6)) wrjJob7 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job7", @@ -206,7 +206,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunStatusSuccess, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -248,7 +248,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { require.NoError(t, workflow_v2.InsertRun(context.Background(), db, &wr)) wrjJob1 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job1", @@ -257,7 +257,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob1)) wrjJob2 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSkipped, + Status: sdk.V2WorkflowRunJobStatusSkipped, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job2", @@ -267,7 +267,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob2)) wrjJob3 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job3", @@ -276,7 +276,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob3)) wrjJob4 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job4", @@ -285,7 +285,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob4)) wrjJob5 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job5", @@ -294,7 +294,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob5)) wrjJob6 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job6", @@ -303,7 +303,7 @@ func TestRunManualJob_WrongGateCondition(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob6)) wrjJob7 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job7", @@ -368,7 +368,7 @@ func TestRunManualJob(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunStatusSuccess, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -411,7 +411,7 @@ func TestRunManualJob(t *testing.T) { require.NoError(t, workflow_v2.InsertRun(context.Background(), db, &wr)) wrjJob1 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job1", @@ -420,7 +420,7 @@ func TestRunManualJob(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob1)) wrjJob2 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSkipped, + Status: sdk.V2WorkflowRunJobStatusSkipped, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job2", @@ -430,7 +430,7 @@ func TestRunManualJob(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob2)) wrjJob3 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job3", @@ -439,7 +439,7 @@ func TestRunManualJob(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob3)) wrjJob4 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job4", @@ -448,7 +448,7 @@ func TestRunManualJob(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob4)) wrjJob5 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job5", @@ -457,7 +457,7 @@ func TestRunManualJob(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob5)) wrjJob6 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job6", @@ -466,7 +466,7 @@ func TestRunManualJob(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob6)) wrjJob7 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job7", @@ -511,7 +511,7 @@ func TestRunManualJob(t *testing.T) { wrDB, err := workflow_v2.LoadRunByID(context.TODO(), db, wr.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusBuilding, wrDB.Status) + require.Equal(t, sdk.V2WorkflowRunStatusBuilding, wrDB.Status) require.Equal(t, int64(2), wrDB.RunAttempt) runjobs, err := workflow_v2.LoadRunJobsByRunID(context.TODO(), db, wrDB.ID, wrDB.RunAttempt) @@ -564,7 +564,7 @@ func TestPutWorkflowRun(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunStatusFail, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -595,7 +595,7 @@ func TestPutWorkflowRun(t *testing.T) { require.NoError(t, workflow_v2.InsertRun(context.Background(), db, &wr)) wrjJob1 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job1", @@ -604,7 +604,7 @@ func TestPutWorkflowRun(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob1)) wrjJob2 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job2", @@ -613,7 +613,7 @@ func TestPutWorkflowRun(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob2)) wrjJob3 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job3", @@ -622,7 +622,7 @@ func TestPutWorkflowRun(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob3)) wrjJob4 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job4", @@ -631,7 +631,7 @@ func TestPutWorkflowRun(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob4)) wrjJob5 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job5", @@ -640,7 +640,7 @@ func TestPutWorkflowRun(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob5)) wrjJob6 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job6", @@ -649,7 +649,7 @@ func TestPutWorkflowRun(t *testing.T) { require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrjJob6)) wrjJob7 := sdk.V2WorkflowRunJob{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, WorkflowRunID: wr.ID, ProjectKey: proj.Key, JobID: "job7", @@ -686,7 +686,7 @@ func TestPutWorkflowRun(t *testing.T) { wrDB, err := workflow_v2.LoadRunByID(context.TODO(), db, wr.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusBuilding, wrDB.Status) + require.Equal(t, sdk.V2WorkflowRunStatusBuilding, wrDB.Status) require.Equal(t, int64(2), wrDB.RunAttempt) runjobs, err := workflow_v2.LoadRunJobsByRunID(context.TODO(), db, wrDB.ID, wrDB.RunAttempt) @@ -728,7 +728,7 @@ func TestPutWorkflowRun_BuildingRun(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -779,7 +779,7 @@ func TestPutWorkflowRun_NoFailingJob(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunStatusSuccess, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -830,7 +830,7 @@ func TestGetWorkflowRunInfoV2Handler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -892,7 +892,7 @@ func TestGetWorkflowRunJobHandler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -956,7 +956,7 @@ func TestGetWorkflowRunJobInfoHandler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -1035,7 +1035,7 @@ func TestPostJobRunStepHandler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -1106,7 +1106,7 @@ hatcheries: runJobDB, err := workflow_v2.LoadRunJobByRunIDAndID(ctx, db, wr.ID, wrj.ID) require.NoError(t, err) require.Equal(t, 1, len(runJobDB.StepsStatus)) - require.Equal(t, sdk.StatusSuccess, runJobDB.StepsStatus["job1"].Conclusion) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, runJobDB.StepsStatus["job1"].Conclusion) } func TestGetWorkflowRunJobLogsLinksV2Handler(t *testing.T) { @@ -1138,7 +1138,7 @@ func TestGetWorkflowRunJobLogsLinksV2Handler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -1207,7 +1207,7 @@ func TestGetWorkflowRunJobsV2Handler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -1269,7 +1269,7 @@ func TestPostStopWorkflowRunHandler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -1290,7 +1290,7 @@ func TestPostStopWorkflowRunHandler(t *testing.T) { UserID: admin.ID, Username: admin.Username, ProjectKey: wr.ProjectKey, - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj)) @@ -1306,11 +1306,11 @@ func TestPostStopWorkflowRunHandler(t *testing.T) { wrDB, err := workflow_v2.LoadRunByID(context.TODO(), db, wr.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusStopped, wrDB.Status) + require.Equal(t, sdk.V2WorkflowRunStatusStopped, wrDB.Status) rjDB, err := workflow_v2.LoadRunJobByRunIDAndID(context.TODO(), db, wrDB.ID, wrj.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusStopped, rjDB.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusStopped, rjDB.Status) } func TestPostStopJobHandler(t *testing.T) { @@ -1334,7 +1334,7 @@ func TestPostStopJobHandler(t *testing.T) { RunNumber: 1, Started: time.Now(), LastModified: time.Now(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunStatusBuilding, UserID: admin.ID, Username: admin.Username, RunEvent: sdk.V2WorkflowRunEvent{}, @@ -1356,7 +1356,7 @@ func TestPostStopJobHandler(t *testing.T) { UserID: admin.ID, Username: admin.Username, ProjectKey: wr.ProjectKey, - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, RunAttempt: wr.RunAttempt, } require.NoError(t, workflow_v2.InsertRunJob(context.TODO(), db, &wrj)) @@ -1375,11 +1375,11 @@ func TestPostStopJobHandler(t *testing.T) { // Workflow must be re-enqueued wrDB, err := workflow_v2.LoadRunByID(context.TODO(), db, wr.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusBuilding, wrDB.Status) + require.Equal(t, sdk.V2WorkflowRunStatusBuilding, wrDB.Status) rjDB, err := workflow_v2.LoadRunJobByRunIDAndID(context.TODO(), db, wr.ID, wrj.ID) require.NoError(t, err) - require.Equal(t, sdk.StatusStopped, rjDB.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusStopped, rjDB.Status) } func TestPostWorkflowRunHandler(t *testing.T) { diff --git a/engine/worker/internal/runV2.go b/engine/worker/internal/runV2.go index 501a76eced..2d421a0e69 100644 --- a/engine/worker/internal/runV2.go +++ b/engine/worker/internal/runV2.go @@ -40,7 +40,7 @@ func (w *CurrentWorker) V2ProcessJob() (res sdk.V2WorkflowRunJobResult) { ctx = workerruntime.SetRunJobID(ctx, w.currentJobV2.runJob.ID) ctx = workerruntime.SetStepOrder(ctx, 0) defer func() { - if res.Status == sdk.StatusSuccess { + if res.Status == sdk.V2WorkflowRunJobStatusSuccess { log.Warn(ctx, "Status: %s", res.Status) } else { log.Warn(ctx, "Status: %s | Reason: %s", res.Status, res.Error) @@ -57,7 +57,7 @@ func (w *CurrentWorker) V2ProcessJob() (res sdk.V2WorkflowRunJobResult) { log.Debug(ctx, "Setup workspace - %s", wdFile.Name()) // Manage services readiness - if result := w.runJobServicesReadiness(ctx); result.Status != sdk.StatusSuccess { + if result := w.runJobServicesReadiness(ctx); result.Status != sdk.V2WorkflowRunJobStatusSuccess { return w.failJob(ctx, fmt.Sprintf("Error: readiness service command failed: %v", result.Error)) } @@ -124,7 +124,7 @@ func (w *CurrentWorker) runJobServicesReadiness(ctx context.Context) sdk.V2Workf ctx = workerruntime.SetIsReadinessServices(ctx, true) defer workerruntime.SetIsReadinessServices(ctx, false) - result := sdk.V2WorkflowRunJobResult{Status: sdk.StatusSuccess} + result := sdk.V2WorkflowRunJobResult{Status: sdk.V2WorkflowRunJobStatusSuccess} if w.currentJobV2.runJob.Job.Services == nil { return result } @@ -136,7 +136,7 @@ func (w *CurrentWorker) runJobServicesReadiness(ctx context.Context) sdk.V2Workf if err := w.runJobServiceReadiness(ctx, serviceName, service); err != nil { result.Error = fmt.Sprintf("failed on check service readiness: %v", err.Error()) - result.Status = sdk.StatusFail + result.Status = sdk.V2WorkflowRunJobStatusFail return result } } @@ -175,7 +175,7 @@ func (w *CurrentWorker) runJobServiceReadiness(ctx context.Context, serviceName Time: time.Now(), } - if result.Status == sdk.StatusSuccess { + if result.Status == sdk.V2WorkflowRunJobStatusSuccess { info.Level = sdk.WorkflowRunInfoLevelInfo info.Message = fmt.Sprintf("service %s is ready", serviceName) } else { @@ -187,7 +187,7 @@ func (w *CurrentWorker) runJobServiceReadiness(ctx context.Context, serviceName log.Error(ctx, "runJobServiceReadiness> Unable to send spawn info: %v", err) } - if result.Status == sdk.StatusSuccess { + if result.Status == sdk.V2WorkflowRunJobStatusSuccess { return nil } @@ -210,7 +210,7 @@ func (w *CurrentWorker) runJobServiceReadiness(ctx context.Context, serviceName func (w *CurrentWorker) runJobAsCode(ctx context.Context) sdk.V2WorkflowRunJobResult { log.Info(ctx, "runJob> start job %s (%s)", w.currentJobV2.runJob.JobID, w.currentJobV2.runJob.ID) var jobResult = sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, } defer func() { @@ -281,13 +281,13 @@ func (w *CurrentWorker) runJobAsCode(ctx context.Context) sdk.V2WorkflowRunJobRe currentStepStatus.Ended = time.Now() currentStepStatus.Outcome = stepRes.Status - if stepRes.Status == sdk.StatusFail && jobResult.Status != sdk.StatusFail && !step.ContinueOnError { - jobResult.Status = sdk.StatusFail + if stepRes.Status == sdk.V2WorkflowRunJobStatusFail && jobResult.Status != sdk.V2WorkflowRunJobStatusFail && !step.ContinueOnError { + jobResult.Status = sdk.V2WorkflowRunJobStatusFail jobResult.Error = stepRes.Error } if step.ContinueOnError { - currentStepStatus.Conclusion = sdk.StatusSuccess + currentStepStatus.Conclusion = sdk.V2WorkflowRunJobStatusSuccess } else { currentStepStatus.Conclusion = currentStepStatus.Outcome } @@ -312,7 +312,7 @@ func (w *CurrentWorker) runActionStep(ctx context.Context, step sdk.ActionStep, bts, err := json.Marshal(runJobContext) if err != nil { return sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, Time: time.Now(), Error: fmt.Sprintf("unable to parse step %s condition expression: %v", stepName, err), } @@ -320,7 +320,7 @@ func (w *CurrentWorker) runActionStep(ctx context.Context, step sdk.ActionStep, var mapContexts map[string]interface{} if err := json.Unmarshal(bts, &mapContexts); err != nil { return sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, Time: time.Now(), Error: fmt.Sprintf("unable to parse step %s condition expression: %v", stepName, err), } @@ -330,7 +330,7 @@ func (w *CurrentWorker) runActionStep(ctx context.Context, step sdk.ActionStep, booleanResult, err := ap.InterpolateToBool(ctx, step.If) if err != nil { return sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, Time: time.Now(), Error: fmt.Sprintf("unable to interpolate step condition %s into a boolean: %v", step.If, err), } @@ -339,7 +339,7 @@ func (w *CurrentWorker) runActionStep(ctx context.Context, step sdk.ActionStep, if !booleanResult { w.SendLog(ctx, workerruntime.LevelInfo, "not executed") return sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusSkipped, + Status: sdk.V2WorkflowRunJobStatusSkipped, Time: time.Now(), } } @@ -415,14 +415,14 @@ func (w *CurrentWorker) runJobStepAction(ctx context.Context, step sdk.ActionSte // / vcs / my / repo / actionName for stepIndex, step := range w.actions[name].Runs.Steps { stepRes := w.runSubActionStep(ctx, step, stepName, stepIndex, actionContext) - if stepRes.Status == sdk.StatusFail { + if stepRes.Status == sdk.V2WorkflowRunJobStatusFail { return stepRes } } default: } return sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusSuccess, + Status: sdk.V2WorkflowRunJobStatusSuccess, } } @@ -478,15 +478,20 @@ func (w *CurrentWorker) runPlugin(ctx context.Context, pluginName string, opts m return w.failJob(ctx, pluginResult.Details) } + jobStatus, err := sdk.NewV2WorkflowRunJobStatusFromString(pluginResult.Status) + if err != nil { + return w.failJob(ctx, fmt.Sprintf("error running plugin %s: %v", pluginName, err)) + } + return sdk.V2WorkflowRunJobResult{ - Status: pluginResult.Status, + Status: jobStatus, Error: pluginResult.Details, } } func (w *CurrentWorker) failJob(ctx context.Context, reason string) sdk.V2WorkflowRunJobResult { res := sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, Error: reason, } log.Error(ctx, "worker.failJobStep> %v", res.Error) diff --git a/engine/worker/internal/runV2_test.go b/engine/worker/internal/runV2_test.go index e5e9a040c9..305cace50f 100644 --- a/engine/worker/internal/runV2_test.go +++ b/engine/worker/internal/runV2_test.go @@ -21,7 +21,7 @@ func TestRunJobContinueOnError(t *testing.T) { ctx := context.TODO() w.currentJobV2.runJob = &sdk.V2WorkflowRunJob{ ID: sdk.UUID(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, JobID: "myjob", Region: "build", Job: sdk.V2Job{ @@ -59,13 +59,13 @@ func TestRunJobContinueOnError(t *testing.T) { result := w.runJobAsCode(ctx) require.Equal(t, 2, len(w.currentJobV2.runJob.StepsStatus)) - require.Equal(t, sdk.StatusSuccess, w.currentJobV2.runJob.StepsStatus["step-0"].Conclusion) - require.Equal(t, sdk.StatusFail, w.currentJobV2.runJob.StepsStatus["step-0"].Outcome) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, w.currentJobV2.runJob.StepsStatus["step-0"].Conclusion) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, w.currentJobV2.runJob.StepsStatus["step-0"].Outcome) - require.Equal(t, sdk.StatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Conclusion) - require.Equal(t, sdk.StatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Outcome) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Conclusion) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Outcome) - require.Equal(t, sdk.StatusSuccess, result.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, result.Status) } func TestRunJobContinueAlways(t *testing.T) { @@ -74,7 +74,7 @@ func TestRunJobContinueAlways(t *testing.T) { ctx := context.TODO() w.currentJobV2.runJob = &sdk.V2WorkflowRunJob{ ID: sdk.UUID(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, JobID: "myjob", Region: "build", Job: sdk.V2Job{ @@ -112,20 +112,20 @@ func TestRunJobContinueAlways(t *testing.T) { result := w.runJobAsCode(ctx) require.Equal(t, 2, len(w.currentJobV2.runJob.StepsStatus)) - require.Equal(t, sdk.StatusFail, w.currentJobV2.runJob.StepsStatus["step-0"].Conclusion) - require.Equal(t, sdk.StatusFail, w.currentJobV2.runJob.StepsStatus["step-0"].Outcome) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, w.currentJobV2.runJob.StepsStatus["step-0"].Conclusion) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, w.currentJobV2.runJob.StepsStatus["step-0"].Outcome) - require.Equal(t, sdk.StatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Conclusion) - require.Equal(t, sdk.StatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Outcome) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Conclusion) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, w.currentJobV2.runJob.StepsStatus["step-1"].Outcome) - require.Equal(t, sdk.StatusFail, result.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, result.Status) } func TestCurrentWorker_runJobServicesReadinessNoService(t *testing.T) { var w = new(CurrentWorker) w.currentJobV2.runJob = &sdk.V2WorkflowRunJob{} result := w.runJobServicesReadiness(context.TODO()) - require.Equal(t, sdk.StatusSuccess, result.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, result.Status) } func TestCurrentWorker_runJobServicesReadinessWithServiceNoCommand(t *testing.T) { @@ -143,7 +143,7 @@ func TestCurrentWorker_runJobServicesReadinessWithServiceNoCommand(t *testing.T) }, } result := w.runJobServicesReadiness(context.TODO()) - require.Equal(t, sdk.StatusSuccess, result.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, result.Status) } func TestCurrentWorker_runJobServicesReadinessWithServiceCommandNoRetries(t *testing.T) { @@ -167,7 +167,7 @@ func TestCurrentWorker_runJobServicesReadinessWithServiceCommandNoRetries(t *tes t.Log("err:", result.Error) t.Log("status:", result.Status) - require.Equal(t, sdk.StatusFail, result.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusFail, result.Status) } func TestCurrentWorker_runJobServicesReadinessWithServiceWithCommand(t *testing.T) { @@ -227,5 +227,5 @@ func TestCurrentWorker_runJobServicesReadinessWithServiceWithCommand(t *testing. }, } result := w.runJobServicesReadiness(context.TODO()) - require.Equal(t, sdk.StatusSuccess, result.Status) + require.Equal(t, sdk.V2WorkflowRunJobStatusSuccess, result.Status) } diff --git a/engine/worker/internal/runtime_v2_test.go b/engine/worker/internal/runtime_v2_test.go index 51cf6b2a4a..3c372aebed 100644 --- a/engine/worker/internal/runtime_v2_test.go +++ b/engine/worker/internal/runtime_v2_test.go @@ -20,7 +20,7 @@ func TestGetRunResult(t *testing.T) { w.currentJobV2.runJob = &sdk.V2WorkflowRunJob{ ID: sdk.UUID(), - Status: sdk.StatusBuilding, + Status: sdk.V2WorkflowRunJobStatusBuilding, JobID: "myjob", Region: "build", Job: sdk.V2Job{}, diff --git a/engine/worker/internal/takeV2.go b/engine/worker/internal/takeV2.go index 07f2d8e0e1..06119eb2a2 100644 --- a/engine/worker/internal/takeV2.go +++ b/engine/worker/internal/takeV2.go @@ -123,7 +123,7 @@ func (w *CurrentWorker) V2Take(ctx context.Context, region, jobRunID string) err } cancelGetJSON() nbConnrefused = 0 - if j == nil || j.RunJob.Status != sdk.StatusBuilding { + if j == nil || j.RunJob.Status != sdk.V2WorkflowRunJobStatusBuilding { log.Warn(ctx, "V2Take> The job is not more in Building Status. Current Status: %s - Cancelling context - err: %v", j.RunJob.Status, err) cancel() return @@ -138,7 +138,7 @@ func (w *CurrentWorker) V2Take(ctx context.Context, region, jobRunID string) err res.Time = time.Now() // Send the reason as a spawninfo - if res.Status != sdk.StatusSuccess && res.Error != "" { + if res.Status != sdk.V2WorkflowRunJobStatusSuccess && res.Error != "" { info := sdk.V2SendJobRunInfo{ Level: sdk.WorkflowRunInfoLevelError, Message: fmt.Sprintf("âš  An error has occurred: %s", res.Error), diff --git a/sdk/action_parser_funcs.go b/sdk/action_parser_funcs.go index ef1bc5efaa..feb271e8bc 100644 --- a/sdk/action_parser_funcs.go +++ b/sdk/action_parser_funcs.go @@ -161,7 +161,7 @@ func success(_ context.Context, a *ActionParser, inputs ...interface{}) (interfa return nil, NewErrorFrom(ErrInvalidData, "unable to read step context") } for _, v := range steps { - if v.Conclusion != StatusSuccess { + if v.Conclusion != V2WorkflowRunJobStatusSuccess { return false, nil } } @@ -173,7 +173,7 @@ func success(_ context.Context, a *ActionParser, inputs ...interface{}) (interfa return nil, NewErrorFrom(ErrInvalidData, "unable to read step context") } for _, v := range needs { - if v.Result != StatusSuccess { + if v.Result != V2WorkflowRunJobStatusSuccess { return false, nil } } @@ -208,7 +208,7 @@ func failure(_ context.Context, a *ActionParser, inputs ...interface{}) (interfa return nil, NewErrorFrom(ErrInvalidData, "unable to read step context") } for _, v := range steps { - if v.Conclusion == StatusFail { + if v.Conclusion == V2WorkflowRunJobStatusFail { return true, nil } } @@ -220,7 +220,7 @@ func failure(_ context.Context, a *ActionParser, inputs ...interface{}) (interfa return nil, NewErrorFrom(ErrInvalidData, "unable to read jobs context") } for _, v := range jobs { - if v.Result == StatusFail { + if v.Result == V2WorkflowRunJobStatusFail { return true, nil } } diff --git a/sdk/cdsclient/client_queue_V2.go b/sdk/cdsclient/client_queue_V2.go index 5c88fb5096..2dbb9afbd8 100644 --- a/sdk/cdsclient/client_queue_V2.go +++ b/sdk/cdsclient/client_queue_V2.go @@ -139,7 +139,7 @@ func (c *client) V2QueuePolling(ctx context.Context, regionName string, goRoutin continue } // push the job in the channel - if j.RunJob.Status == sdk.StatusWaiting { + if j.RunJob.Status == sdk.V2WorkflowRunJobStatusWaiting { if pendingWorkerCreation.IsJobAlreadyPendingWorkerCreation(wsEvent.JobRunID) { log.Debug(ctx, "skipping job %s", wsEvent.JobRunID) continue diff --git a/sdk/common.go b/sdk/common.go index 788cfa5f38..107b61bb31 100644 --- a/sdk/common.go +++ b/sdk/common.go @@ -18,6 +18,7 @@ import ( "regexp" "runtime" "strings" + "time" "unicode" "github.com/go-gorp/gorp" @@ -403,3 +404,10 @@ func MapHasKeys(i interface{}, expectedKeys ...interface{}) bool { } return true } + +func TimeSafe(t *time.Time) time.Time { + if t == nil { + return time.Time{} + } + return *t +} diff --git a/sdk/contexts.go b/sdk/contexts.go index 067723a228..94f4a094ea 100644 --- a/sdk/contexts.go +++ b/sdk/contexts.go @@ -114,8 +114,8 @@ type JobContextService struct { type JobsResultContext map[string]JobResultContext type JobResultContext struct { - Result string `json:"result"` - Outputs JobResultOutput `json:"outputs"` + Result V2WorkflowRunJobStatus `json:"result"` + Outputs JobResultOutput `json:"outputs"` } type JobResultOutput map[string]string @@ -138,15 +138,15 @@ func (jro *JobResultOutput) Scan(src interface{}) error { type StepsContext map[string]StepContext type StepContext struct { - Conclusion string `json:"conclusion"` // result of a step after 'continue-on-error' - Outcome string `json:"outcome"` // result of a step before 'continue-on-error' - Outputs JobResultOutput `json:"outputs"` + Conclusion V2WorkflowRunJobStatus `json:"conclusion"` // result of a step after 'continue-on-error' + Outcome V2WorkflowRunJobStatus `json:"outcome"` // result of a step before 'continue-on-error' + Outputs JobResultOutput `json:"outputs"` } type NeedsContext map[string]NeedContext type NeedContext struct { - Result string `json:"result"` - Outputs JobResultOutput `json:"outputs"` + Result V2WorkflowRunJobStatus `json:"result"` + Outputs JobResultOutput `json:"outputs"` } func (sc StepsContext) Value() (driver.Value, error) { diff --git a/sdk/event_v2.go b/sdk/event_v2.go index 142cdc653c..5a38788250 100644 --- a/sdk/event_v2.go +++ b/sdk/event_v2.go @@ -264,47 +264,47 @@ type UserGPGEvent struct { type WorkflowRunEvent struct { ProjectEventV2 - VCSName string `json:"vcs_name"` - Repository string `json:"repository"` - Workflow string `json:"workflow"` - RunNumber int64 `json:"run_number"` - RunAttempt int64 `json:"run_attempt"` - Status string `json:"status"` - WorkflowRunID string `json:"workflow_run_id"` - UserID string `json:"user_id"` - Username string `json:"username"` + VCSName string `json:"vcs_name"` + Repository string `json:"repository"` + Workflow string `json:"workflow"` + RunNumber int64 `json:"run_number"` + RunAttempt int64 `json:"run_attempt"` + Status V2WorkflowRunStatus `json:"status"` + WorkflowRunID string `json:"workflow_run_id"` + UserID string `json:"user_id"` + Username string `json:"username"` } type WorkflowRunJobEvent struct { ProjectEventV2 - VCSName string `json:"vcs_name"` - Repository string `json:"repository"` - Workflow string `json:"workflow"` - WorkflowRunID string `json:"workflow_run_id"` - RunJobID string `json:"run_job_id"` - RunNumber int64 `json:"run_number"` - RunAttempt int64 `json:"run_attempt"` - Region string `json:"region"` - Hatchery string `json:"hatchery"` - ModelType string `json:"model_type"` - JobID string `json:"job_id"` - Status string `json:"status"` - UserID string `json:"user_id"` - Username string `json:"username"` + VCSName string `json:"vcs_name"` + Repository string `json:"repository"` + Workflow string `json:"workflow"` + WorkflowRunID string `json:"workflow_run_id"` + RunJobID string `json:"run_job_id"` + RunNumber int64 `json:"run_number"` + RunAttempt int64 `json:"run_attempt"` + Region string `json:"region"` + Hatchery string `json:"hatchery"` + ModelType string `json:"model_type"` + JobID string `json:"job_id"` + Status V2WorkflowRunJobStatus `json:"status"` + UserID string `json:"user_id"` + Username string `json:"username"` } type WorkflowRunJobManualEvent struct { ProjectEventV2 - VCSName string `json:"vcs_name"` - Repository string `json:"repository"` - Workflow string `json:"workflow"` - JobID string `json:"job_id"` - RunNumber int64 `json:"run_number"` - RunAttempt int64 `json:"run_attempt"` - Status string `json:"status"` - WorkflowRunID string `json:"workflow_run_id"` - UserID string `json:"user_id"` - Username string `json:"username"` + VCSName string `json:"vcs_name"` + Repository string `json:"repository"` + Workflow string `json:"workflow"` + JobID string `json:"job_id"` + RunNumber int64 `json:"run_number"` + RunAttempt int64 `json:"run_attempt"` + Status V2WorkflowRunStatus `json:"status"` + WorkflowRunID string `json:"workflow_run_id"` + UserID string `json:"user_id"` + Username string `json:"username"` } type WorkflowRunJobRunResultEvent struct { diff --git a/sdk/hatchery/hatchery.go b/sdk/hatchery/hatchery.go index cf33bc38b7..65fcb49000 100644 --- a/sdk/hatchery/hatchery.go +++ b/sdk/hatchery/hatchery.go @@ -438,7 +438,7 @@ func handleJobV2(_ context.Context, h Interface, jobInfo sdk.V2QueueJobInfo, cac } if nbAttempts > maxAttemptsNumberBeforeFailure { if err := h.CDSClientV2().V2QueueJobResult(ctx, jobInfo.RunJob.Region, jobInfo.RunJob.ID, sdk.V2WorkflowRunJobResult{ - Status: sdk.StatusFail, + Status: sdk.V2WorkflowRunJobStatusFail, Error: fmt.Sprintf("hatchery %q failed to start worker after %d attempts", h.Configuration().Name, maxAttemptsNumberBeforeFailure), }); err != nil { return err diff --git a/sdk/v2_workflow_run.go b/sdk/v2_workflow_run.go index fd7e4e8f5e..e4f7478494 100644 --- a/sdk/v2_workflow_run.go +++ b/sdk/v2_workflow_run.go @@ -42,7 +42,7 @@ type V2WorkflowRun struct { WorkflowName string `json:"workflow_name" db:"workflow_name" cli:"workflow_name"` WorkflowSha string `json:"workflow_sha" db:"workflow_sha"` WorkflowRef string `json:"workflow_ref" db:"workflow_ref"` - Status string `json:"status" db:"status" cli:"status"` + Status V2WorkflowRunStatus `json:"status" db:"status" cli:"status"` RunNumber int64 `json:"run_number" db:"run_number" cli:"run_number"` RunAttempt int64 `json:"run_attempt" db:"run_attempt"` Started time.Time `json:"started" db:"started" cli:"started"` @@ -56,6 +56,25 @@ type V2WorkflowRun struct { RunJobEvent V2WorkflowRunJobEvents `json:"job_events" db:"job_event"` } +type V2WorkflowRunStatus string + +const ( + V2WorkflowRunStatusSkipped V2WorkflowRunStatus = "Skipped" + V2WorkflowRunStatusFail V2WorkflowRunStatus = "Fail" + V2WorkflowRunStatusSuccess V2WorkflowRunStatus = "Success" + V2WorkflowRunStatusStopped V2WorkflowRunStatus = "Stopped" + V2WorkflowRunStatusBuilding V2WorkflowRunStatus = "Building" + V2WorkflowRunStatusCrafting V2WorkflowRunStatus = "Crafting" +) + +func (s V2WorkflowRunStatus) IsTerminated() bool { + switch s { + case V2WorkflowRunStatusBuilding, V2WorkflowRunStatusCrafting: + return false + } + return true +} + type WorkflowRunContext struct { CDS CDSContext `json:"cds,omitempty"` Git GitContext `json:"git,omitempty"` @@ -167,29 +186,60 @@ func (w *V2WorkflowRunEvent) Scan(src interface{}) error { } type V2WorkflowRunJob struct { - ID string `json:"id" db:"id"` - JobID string `json:"job_id" db:"job_id" cli:"job_id"` - WorkflowRunID string `json:"workflow_run_id" db:"workflow_run_id"` - ProjectKey string `json:"project_key" db:"project_key"` - WorkflowName string `json:"workflow_name" db:"workflow_name"` - RunNumber int64 `json:"run_number" db:"run_number"` - RunAttempt int64 `json:"run_attempt" db:"run_attempt"` - Status string `json:"status" db:"status" cli:"status"` - Queued time.Time `json:"queued" db:"queued"` - Scheduled time.Time `json:"scheduled" db:"scheduled"` - Started time.Time `json:"started" db:"started"` - Ended time.Time `json:"ended" db:"ended"` - Job V2Job `json:"job" db:"job"` - WorkerID string `json:"worker_id,omitempty" db:"worker_id"` - WorkerName string `json:"worker_name" db:"worker_name"` - HatcheryName string `json:"hatchery_name" db:"hatchery_name"` - StepsStatus JobStepsStatus `json:"steps_status" db:"steps_status"` - UserID string `json:"user_id" db:"user_id"` - Username string `json:"username" db:"username"` - Region string `json:"region,omitempty" db:"region"` - ModelType string `json:"model_type,omitempty" db:"model_type"` - Matrix JobMatrix `json:"matrix,omitempty" db:"matrix"` - GateInputs GateInputs `json:"gate_inputs,omitempty" db:"gate_inputs"` + ID string `json:"id" db:"id"` + JobID string `json:"job_id" db:"job_id" cli:"job_id"` + WorkflowRunID string `json:"workflow_run_id" db:"workflow_run_id"` + ProjectKey string `json:"project_key" db:"project_key"` + WorkflowName string `json:"workflow_name" db:"workflow_name"` + RunNumber int64 `json:"run_number" db:"run_number"` + RunAttempt int64 `json:"run_attempt" db:"run_attempt"` + Status V2WorkflowRunJobStatus `json:"status" db:"status" cli:"status"` + Queued time.Time `json:"queued" db:"queued"` + Scheduled *time.Time `json:"scheduled,omitempty" db:"scheduled"` + Started *time.Time `json:"started,omitempty" db:"started"` + Ended *time.Time `json:"ended,omitempty" db:"ended"` + Job V2Job `json:"job" db:"job"` + WorkerID string `json:"worker_id,omitempty" db:"worker_id"` + WorkerName string `json:"worker_name" db:"worker_name"` + HatcheryName string `json:"hatchery_name" db:"hatchery_name"` + StepsStatus JobStepsStatus `json:"steps_status" db:"steps_status"` + UserID string `json:"user_id" db:"user_id"` + Username string `json:"username" db:"username"` + Region string `json:"region,omitempty" db:"region"` + ModelType string `json:"model_type,omitempty" db:"model_type"` + Matrix JobMatrix `json:"matrix,omitempty" db:"matrix"` + GateInputs GateInputs `json:"gate_inputs,omitempty" db:"gate_inputs"` +} + +type V2WorkflowRunJobStatus string + +const ( + V2WorkflowRunJobStatusUnknown V2WorkflowRunJobStatus = "" + V2WorkflowRunJobStatusWaiting V2WorkflowRunJobStatus = "Waiting" + V2WorkflowRunJobStatusBuilding V2WorkflowRunJobStatus = "Building" + V2WorkflowRunJobStatusFail V2WorkflowRunJobStatus = "Fail" + V2WorkflowRunJobStatusStopped V2WorkflowRunJobStatus = "Stopped" + V2WorkflowRunJobStatusSuccess V2WorkflowRunJobStatus = "Success" + V2WorkflowRunJobStatusScheduling V2WorkflowRunJobStatus = "Scheduling" + V2WorkflowRunJobStatusSkipped V2WorkflowRunJobStatus = "Skipped" +) + +func NewV2WorkflowRunJobStatusFromString(s string) (V2WorkflowRunJobStatus, error) { + switch s { + case StatusFail: + return V2WorkflowRunJobStatusFail, nil + case StatusSuccess: + return V2WorkflowRunJobStatusSuccess, nil + } + return V2WorkflowRunJobStatusUnknown, errors.Errorf("cannot convert given status value %q to workflow run job v2 status", s) +} + +func (s V2WorkflowRunJobStatus) IsTerminated() bool { + switch s { + case V2WorkflowRunJobStatusUnknown, V2WorkflowRunJobStatusBuilding, V2WorkflowRunJobStatusWaiting, V2WorkflowRunJobStatusScheduling: + return false + } + return true } type JobIntegrationsContext struct { @@ -210,11 +260,11 @@ func (c JobIntegrationsContext) All() []string { type JobStepsStatus map[string]JobStepStatus type JobStepStatus struct { - Conclusion string `json:"conclusion"` // result of a step after 'continue-on-error' - Outcome string `json:"outcome"` // result of a step before 'continue-on-error' - Outputs JobResultOutput `json:"outputs"` - Started time.Time `json:"started"` - Ended time.Time `json:"ended"` + Conclusion V2WorkflowRunJobStatus `json:"conclusion"` // result of a step after 'continue-on-error' + Outcome V2WorkflowRunJobStatus `json:"outcome"` // result of a step before 'continue-on-error' + Outputs JobResultOutput `json:"outputs"` + Started time.Time `json:"started"` + Ended time.Time `json:"ended"` } type GateInputs map[string]interface{} @@ -273,7 +323,7 @@ func (s JobStepsStatus) ToStepContext() StepsContext { stepsContext := StepsContext{} for k, v := range s { // Do not include current step - if v.Conclusion == "" { + if v.Conclusion == V2WorkflowRunJobStatusUnknown { continue } stepsContext[k] = StepContext{ @@ -320,9 +370,9 @@ const ( ) type V2WorkflowRunJobResult struct { - Status string `json:"status"` - Error string `json:"error,omitempty"` - Time time.Time `json:"time"` + Status V2WorkflowRunJobStatus `json:"status"` + Error string `json:"error,omitempty"` + Time time.Time `json:"time"` } type V2SendJobRunInfo struct { @@ -336,7 +386,6 @@ func GetJobStepName(stepID string, stepIndex int) string { return stepID } return fmt.Sprintf("step-%d", stepIndex) - } type WorkflowRunStages map[string]WorkflowRunStage @@ -347,7 +396,7 @@ stageLoop: for name := range wrs { stage := wrs[name] for _, status := range stage.Jobs { - if !StatusIsTerminated(status) { + if !status.IsTerminated() { stage.Ended = false wrs[name] = stage continue stageLoop @@ -376,20 +425,20 @@ stageLoop: type WorkflowRunStage struct { WorkflowStage CanBeRun bool - Jobs map[string]string + Jobs map[string]V2WorkflowRunJobStatus Ended bool } func (w V2WorkflowRun) GetStages() WorkflowRunStages { stages := WorkflowRunStages{} for k, s := range w.WorkflowData.Workflow.Stages { - stages[k] = WorkflowRunStage{WorkflowStage: s, Jobs: make(map[string]string)} + stages[k] = WorkflowRunStage{WorkflowStage: s, Jobs: make(map[string]V2WorkflowRunJobStatus)} } if len(stages) == 0 { return stages } for jobID, job := range w.WorkflowData.Workflow.Jobs { - stages[job.Stage].Jobs[jobID] = "" + stages[job.Stage].Jobs[jobID] = V2WorkflowRunJobStatusUnknown } return stages } diff --git a/ui/libs/workflow-graph/package-lock.json b/ui/libs/workflow-graph/package-lock.json index 3dd88ba043..5f7d391247 100644 --- a/ui/libs/workflow-graph/package-lock.json +++ b/ui/libs/workflow-graph/package-lock.json @@ -1,19 +1,20 @@ { "name": "workflow-graph", - "version": "0.0.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "workflow-graph", - "version": "0.0.1", + "version": "1.0.0", "dependencies": { - "ng-zorro-antd": "16.2.2", - "tslib": "^2.3.0" + "tslib": "2.3.0" }, "peerDependencies": { - "@angular/common": "^16.2.0", - "@angular/core": "^16.2.0" + "@angular/common": "16.2.12", + "@angular/core": "16.2.12", + "@icholy/duration": "5.0.0", + "ng-zorro-antd": "16.2.2" } }, "node_modules/@angular/animations": { @@ -35,6 +36,7 @@ "version": "16.2.14", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.14.tgz", "integrity": "sha512-n6PrGdiVeSTEmM/HEiwIyg6YQUUymZrb5afaNLGFRM5YL0Y8OBqd+XhCjb0OfD/AfgCUtedVEPwNqrfW8KzgGw==", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -141,6 +143,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.0.2.tgz", "integrity": "sha512-7KJkhTiPiLHSu+LmMJnehfJ6242OCxSlR3xHVBecYxnMW8MS/878NXct1GqYARyL59fyeFdKRxXTfvR9SnDgJg==", + "peer": true, "dependencies": { "@ctrl/tinycolor": "^3.6.1" } @@ -149,6 +152,7 @@ "version": "16.0.0", "resolved": "https://registry.npmjs.org/@ant-design/icons-angular/-/icons-angular-16.0.0.tgz", "integrity": "sha512-KWBmWZl2so49R/MdAT7aG+xaBlMKl9SArR3Du/iPA0Am9GI1i9R89KgnnLWz+gkzHTye15S1IBXpgts4GPPU/w==", + "peer": true, "dependencies": { "@ant-design/colors": "^7.0.0", "tslib": "^2.0.0" @@ -164,6 +168,7 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "peer": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -175,14 +180,22 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "peer": true, "engines": { "node": ">=10" } }, + "node_modules/@icholy/duration": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@icholy/duration/-/duration-5.0.0.tgz", + "integrity": "sha512-EgAM5pyM5CXwgZniydzIRPdyrlxFjhGNk4CSI1dAoHwiazN3/xtnP7Q/S+vYpptRUavQjvro51LmsextcPKj2A==", + "peer": true + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "peer": true, "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -199,6 +212,7 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "optional": true, + "peer": true, "engines": { "node": ">=0.12" }, @@ -210,6 +224,7 @@ "version": "16.2.2", "resolved": "https://registry.npmjs.org/ng-zorro-antd/-/ng-zorro-antd-16.2.2.tgz", "integrity": "sha512-Y7ALO+iRjqBfVW9hqnlU4jJUovM5r6wNzXuwTxKIo/5c35/PJFwk++73iwhas6pCb3+kFucwL5XTUWUoLce3wQ==", + "peer": true, "dependencies": { "@angular/cdk": "^16.0.0", "@ant-design/icons-angular": "^16.0.0", @@ -230,6 +245,7 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "optional": true, + "peer": true, "dependencies": { "entities": "^4.4.0" }, @@ -240,7 +256,8 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "peer": true }, "node_modules/rxjs": { "version": "7.8.1", @@ -252,9 +269,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, "node_modules/zone.js": { "version": "0.13.3", diff --git a/ui/libs/workflow-graph/package.json b/ui/libs/workflow-graph/package.json index f0319f6d57..f0ea9e6de0 100644 --- a/ui/libs/workflow-graph/package.json +++ b/ui/libs/workflow-graph/package.json @@ -4,7 +4,8 @@ "peerDependencies": { "@angular/common": "16.2.12", "@angular/core": "16.2.12", - "ng-zorro-antd": "16.2.2" + "ng-zorro-antd": "16.2.2", + "@icholy/duration": "5.0.0" }, "dependencies": { "tslib": "2.3.0" @@ -13,4 +14,4 @@ "ng": "ng" }, "sideEffects": false -} +} \ No newline at end of file diff --git a/ui/src/app/shared/duration/duration.service.ts b/ui/libs/workflow-graph/src/lib/duration/duration.service.ts similarity index 100% rename from ui/src/app/shared/duration/duration.service.ts rename to ui/libs/workflow-graph/src/lib/duration/duration.service.ts diff --git a/ui/libs/workflow-graph/src/lib/graph.lib.ts b/ui/libs/workflow-graph/src/lib/graph.lib.ts index b519113594..a6852d10b0 100644 --- a/ui/libs/workflow-graph/src/lib/graph.lib.ts +++ b/ui/libs/workflow-graph/src/lib/graph.lib.ts @@ -1,16 +1,14 @@ import { ComponentRef } from '@angular/core'; import * as d3 from 'd3'; import * as dagreD3 from 'dagre-d3'; -import { GraphNode } from './graph.model'; +import { GraphNode, GraphNodeType } from './graph.model'; import { GraphForkJoinNodeComponent } from './node/fork-join-node.components'; import { GraphJobNodeComponent } from './node/job-node.component'; -import { GraphGateNodeComponent } from './node/gate-node.component'; import { NodeStatus } from './node/status.model'; export type WorkflowNodeComponent = GraphForkJoinNodeComponent - | GraphJobNodeComponent - | GraphGateNodeComponent; + | GraphJobNodeComponent; export enum GraphDirection { HORIZONTAL = 'horizontal', @@ -20,25 +18,18 @@ export enum GraphDirection { export interface WithHighlight { getNodes(): Array; - setHighlight(active: boolean): void; + setHighlight(active: boolean, options?: any): void; - setSelect(active: boolean): void; + setSelect(active: boolean, options?: any): void; } export type ComponentFactory = (nodes: Array, type: string) => ComponentRef; export class Node { - key: string; - width: number; - height: number; type: string; -} - -export class Gate { key: string; width: number; height: number; - child: string; } export class Edge { @@ -57,7 +48,6 @@ export class WorkflowV2Graph { nodesComponent = new Map>(); nodes = new Array(); - gates = new Array(); edges = new Array(); direction: string; zoom: d3.ZoomBehavior; @@ -95,6 +85,8 @@ export class WorkflowV2Graph { this.render = new dagreD3.render(); this.render.shapes().customRectH = WorkflowV2Graph.customRect(GraphDirection.HORIZONTAL); this.render.shapes().customRectV = WorkflowV2Graph.customRect(GraphDirection.VERTICAL); + this.render.shapes().customRectForMatrixH = WorkflowV2Graph.customRectForMatrix(GraphDirection.HORIZONTAL); + this.render.shapes().customRectForMatrixV = WorkflowV2Graph.customRectForMatrix(GraphDirection.VERTICAL); this.render.shapes().customCircle = WorkflowV2Graph.customCircle; this.render.arrows().customArrow = WorkflowV2Graph.customArrow; } @@ -120,6 +112,27 @@ export class WorkflowV2Graph { return shapeSvg; }; + static customRectForMatrix = (direction: GraphDirection) => (parent, bbox, node) => { + let shapeSvg = parent.insert('rect', ':first-child') + .attr('rx', node.rx) + .attr('ry', node.ry) + .attr('x', -bbox.width / 2) + .attr('y', -bbox.height / 2) + .attr('width', bbox.width) + .attr('height', bbox.height); + + node.intersect = (point) => { + if (direction === GraphDirection.VERTICAL) { + const h = ((node.height) / 2); + return { x: node.x, y: node.y + (point.y < node.y ? -h : h) + 30 }; + } + const w = ((node.width) / 2); + return { x: node.x + (point.x < node.x ? -w : w), y: node.y }; + }; + + return shapeSvg; + }; + static customCircle = (parent, bbox, node) => { let r = Math.max(bbox.width, bbox.height) / 2; let shapeSvg = parent.insert('circle', ':first-child') @@ -269,16 +282,20 @@ export class WorkflowV2Graph { drawNodes(): void { this.nodes.forEach(n => { - this.createGNode(`node-${n.key}`, this.nodesComponent.get(`node-${n.key}`), n.width, n.height, { - class: n.key - }); + switch (n.type) { + case GraphNodeType.Matrix: + this.createGNode(`node-${n.key}`, this.nodesComponent.get(`node-${n.key}`), n.width, n.height, { + class: n.key, + shape: this.direction === GraphDirection.VERTICAL ? 'customRectForMatrixV' : 'customRectForMatrixH' + }); + break; + default: + this.createGNode(`node-${n.key}`, this.nodesComponent.get(`node-${n.key}`), n.width, n.height, { + class: n.key + }); + break; + } }); - - this.gates.forEach(n => { - this.createGGate(`gate-${n.key}`, this.nodesComponent.get(`gate-${n.key}`), n.width, n.height, { - class: n.key - }); - }) } uniqueStrings(a: Array): Array { @@ -416,18 +433,6 @@ export class WorkflowV2Graph { from: e.from, to: e.to, options }); }); - - // Gate edge - if (this.gates) { - this.gates.forEach(g => { - let e = { from: `gate-${g.key}`, to: `node-${g.child}` }; - let options = { class: `${g.key} ${g.child}` }; - options['class'] += ` ` + this.nodeStatusToColor(this.nodeStatus[e.from]); - options['style'] = 'stroke-width: 2px;'; - e.options = options - this.createGEdge(e); - }); - } } nodeStatusToColor(s: string): string { @@ -446,32 +451,13 @@ export class WorkflowV2Graph { } } - createGate(graphNode: GraphNode, type: string, componentRef: ComponentRef, status: string, - width: number = 40, height: number = 40): void { - let key = graphNode.name; - let child = graphNode.gateChild; - this.gates.push({ key, width, height, child }); - this.nodesComponent.set(`gate-${key}`, componentRef); - } - - createGGate(name: string, componentRef: ComponentRef, width: number, height: number, options: {}): void { - this.graph.setNode(name, { - shape: 'customCircle', - label: () => componentRef.location.nativeElement, - labelStyle: `width: ${width}px;height: ${height}px;`, - width, - height, - ...options - }); + createNode(key: string, type: GraphNodeType, componentRef: ComponentRef, width: number = 200, height: number = 60): void { + this.nodes.push({ type, key, width, height }); + this.nodesComponent.set(`node-${key}`, componentRef); } - createNode(key: string, type: string, componentRef: ComponentRef, status: string, - width: number = 130, height: number = 40): void { - this.nodes.push({ key, width, height }); - this.nodesComponent.set(`node-${key}`, componentRef); - if (status) { - this.nodeStatus[`node-${key}`] = status; - } + setNodeStatus(key: string, status: string): void { + this.nodeStatus[`node-${key}`] = status; } createGNode(name: string, componentRef: ComponentRef, width: number, height: number, options: {}): void { @@ -518,17 +504,17 @@ export class WorkflowV2Graph { }); } - nodeMouseEvent(type: string, key: string): void { + nodeMouseEvent(type: string, key: string, options?: any): void { switch (type) { case 'enter': - this.highlightNode(true, key); + this.highlightNode(true, key, options); break; case 'out': - this.highlightNode(false, key); + this.highlightNode(false, key, options); break; case 'click': this.unselectAllNode(); - this.selectNode(key); + this.selectNode(key, options); break; } } @@ -537,16 +523,16 @@ export class WorkflowV2Graph { this.nodesComponent.forEach(n => n.instance.setSelect(false)); } - selectNode(key: string): void { + selectNode(key: string, options?: any): void { if (this.nodesComponent.has(`node-${key}`)) { - this.nodesComponent.get(`node-${key}`).instance.setSelect(true); + this.nodesComponent.get(`node-${key}`).instance.setSelect(true, options); this.currentSelectedNodeKey = key; } else { this.currentSelectedNodeKey = null; } } - highlightNode(active: boolean, key: string) { + highlightNode(active: boolean, key: string, options?: any) { let selectionEdges = d3.selectAll(`.${key} > .path`); if (selectionEdges.size() > 0) { selectionEdges.attr('class', active ? 'path highlight' : 'path'); @@ -555,22 +541,19 @@ export class WorkflowV2Graph { if (selectionEdgeMarkers.size() > 0) { selectionEdgeMarkers.attr('class', active ? 'highlight' : ''); } - if (this.nodesComponent.has(`gate-${key}`)) { - this.nodesComponent.get(`gate-${key}`).instance.setHighlight(active); - } if (this.nodesComponent.has(`node-${key}`)) { - this.nodesComponent.get(`node-${key}`).instance.setHighlight(active); + this.nodesComponent.get(`node-${key}`).instance.setHighlight(active, options); } let inName = this.nodeInNames[`node-${key}`]; if (inName !== `node-${key}`) { if (this.nodesComponent.has(inName)) { - this.nodesComponent.get(inName).instance.setHighlight(active); + this.nodesComponent.get(inName).instance.setHighlight(active, options); } } let outName = this.nodeOutNames[`node-${key}`]; if (outName !== `node-${key}`) { if (this.nodesComponent.has(outName)) { - this.nodesComponent.get(outName).instance.setHighlight(active); + this.nodesComponent.get(outName).instance.setHighlight(active, options); } } } diff --git a/ui/libs/workflow-graph/src/lib/graph.model.ts b/ui/libs/workflow-graph/src/lib/graph.model.ts index 9c60678b8c..035045bd40 100644 --- a/ui/libs/workflow-graph/src/lib/graph.model.ts +++ b/ui/libs/workflow-graph/src/lib/graph.model.ts @@ -1,3 +1,4 @@ +import { V2Job, V2JobGate, V2WorkflowRunJobEvent } from "./v2.workflow.run.model"; import { V2WorkflowRunJob } from "./v2.workflow.run.model"; export class StepStatus { @@ -13,16 +14,47 @@ export class JobRun { } export class GraphNode { + type: GraphNodeType; name: string; depends_on: Array; sub_graph: Array; - gateChild: string; - gateName: string; - gateStatus: string; + job: V2Job; + gate: V2JobGate; run: V2WorkflowRunJob; - type: string; + runs: Array; + event: V2WorkflowRunJobEvent; + + static generateMatrixOptions(matrix: { [key: string]: Array }): Array> { + const generateMatrix = (matrix: { [key: string]: string[] }, keys: string[], keyIndex: number, current: Map, alls: Array>) => { + if (current.size == keys.length) { + let combi = new Map(); + current.forEach((v, k) => { + combi.set(k, v); + }); + alls.push(combi); + return; + } + let key = keys[keyIndex]; + let values = matrix[key]; + values.forEach(v => { + current.set(key, v); + generateMatrix(matrix, keys, keyIndex + 1, current, alls); + current.delete(key); + }); + }; + let alls = new Array>(); + generateMatrix(matrix, Object.keys(matrix), 0, new Map(), alls); + return alls; + } +} + +export enum GraphNodeType { + Job = 'job', + Stage = "stage", + Matrix = "matrix" } -export const GraphNodeTypeJob = "job"; -export const GraphNodeTypeStage = "stage"; -export const GraphNodeTypeGate = "gate"; + + + + diff --git a/ui/libs/workflow-graph/src/lib/jobs-graph.component.ts b/ui/libs/workflow-graph/src/lib/jobs-graph.component.ts index caf701c722..612496f0b0 100644 --- a/ui/libs/workflow-graph/src/lib/jobs-graph.component.ts +++ b/ui/libs/workflow-graph/src/lib/jobs-graph.component.ts @@ -8,11 +8,13 @@ import { ViewChild, ViewContainerRef } from '@angular/core'; -import { GraphNode, GraphNodeTypeGate, GraphNodeTypeJob } from './graph.model'; -import { GraphDirection, WorkflowNodeComponent, WorkflowV2Graph } from './graph.lib'; +import { GraphNode, GraphNodeType } from './graph.model'; +import { GraphDirection, WorkflowV2Graph } from './graph.lib'; import { GraphForkJoinNodeComponent } from './node/fork-join-node.components'; import { GraphJobNodeComponent } from './node/job-node.component'; -import { GraphGateNodeComponent } from './node/gate-node.component'; +import { GraphMatrixNodeComponent } from './node/matrix-node.component'; + +export type WorkflowV2JobsNodeOrMatrixComponent = GraphForkJoinNodeComponent | GraphJobNodeComponent | GraphMatrixNodeComponent; @Component({ selector: 'app-jobs-graph', @@ -36,19 +38,18 @@ export class WorkflowV2JobsGraphComponent implements AfterViewInit { @Input() direction: GraphDirection; @Input() centerCallback: any; @Input() mouseCallback: (type: string, node: GraphNode) => void; - @Input() selectJobCallback: (name: string) => void; + @Input() selectJobCallback: (type: string, node: GraphNode, options?: any) => void; ready: boolean; highlight = false; // workflow graph @ViewChild('svgSubGraph', { read: ViewContainerRef }) svgContainer: ViewContainerRef; - graph: WorkflowV2Graph; + graph: WorkflowV2Graph; constructor( private _cd: ChangeDetectorRef - ) { - } + ) { } getNodes() { return [this.node]; @@ -98,14 +99,21 @@ export class WorkflowV2JobsGraphComponent implements AfterViewInit { } this.nodes.forEach(n => { + let component: ComponentRef; switch (n.type) { - case GraphNodeTypeGate: - this.graph.createNode(`${this.node.name}-${n.name}`, GraphNodeTypeGate, this.createGateNodeComponent(n), - n.run ? n.run.status : null, 20, 20); + case GraphNodeType.Matrix: + component = this.createJobMatrixComponent(n); + const alls = GraphNode.generateMatrixOptions(n.job.strategy.matrix); + let height = 30 * alls.length + 10 * (alls.length - 1) + 60 + 20; + this.graph.createNode(`${this.node.name}-${n.name}`, n.type, component, 240, height); break; default: - this.graph.createNode(`${this.node.name}-${n.name}`, GraphNodeTypeJob, this.createJobNodeComponent(n), - n.run ? n.run.status : null); + component = this.createJobNodeComponent(n); + this.graph.createNode(`${this.node.name}-${n.name}`, n.type, component); + if (n.run) { + this.graph.setNodeStatus(`${this.node.name}-${n.name}`, n.run ? n.run.status : null); + } + break; } }); @@ -123,13 +131,14 @@ export class WorkflowV2JobsGraphComponent implements AfterViewInit { this._cd.markForCheck(); } - createGateNodeComponent(node: GraphNode): ComponentRef { - const componentRef = this.svgContainer.createComponent(GraphGateNodeComponent); + createJobMatrixComponent(node: GraphNode): ComponentRef { + const componentRef = this.svgContainer.createComponent(GraphMatrixNodeComponent); componentRef.instance.node = node; componentRef.instance.mouseCallback = this.nodeMouseEvent.bind(this); componentRef.changeDetectorRef.detectChanges(); return componentRef; } + createJobNodeComponent(node: GraphNode): ComponentRef { const componentRef = this.svgContainer.createComponent(GraphJobNodeComponent); componentRef.instance.node = node; @@ -147,16 +156,11 @@ export class WorkflowV2JobsGraphComponent implements AfterViewInit { return componentRef; } - nodeMouseEvent(type: string, n: GraphNode) { - if (type === 'click' && this.selectJobCallback) { - if (n.run) { - this.selectJobCallback(n.run.id); - } else { - this.selectJobCallback(n.name); - } - + nodeMouseEvent(type: string, n: GraphNode, options?: any) { + if (this.selectJobCallback) { + this.selectJobCallback(type, n, options); } - this.graph.nodeMouseEvent(type, `${this.node.name}-${n.name}`); + this.graph.nodeMouseEvent(type, `${this.node.name}-${n.name}`, options); } clickCenter(): void { diff --git a/ui/libs/workflow-graph/src/lib/jobs-graph.scss b/ui/libs/workflow-graph/src/lib/jobs-graph.scss index 16772da464..8da2b160ee 100644 --- a/ui/libs/workflow-graph/src/lib/jobs-graph.scss +++ b/ui/libs/workflow-graph/src/lib/jobs-graph.scss @@ -14,17 +14,25 @@ display: flex; flex-direction: row; align-items: center; - border: dashed 2px $darkBackground; + border: dashed 2px $polar_grey_3; border-radius: 3px; + :host-context(.night) & { + border-color: $darkTheme_grey_5; + + &.highlight { + border-color: white; + } + } + &.highlight { - border-color: black !important; + border-color: black; } .title { width: 100%; height: 25px; - padding: 0 10px; + padding: 0 5px; position: absolute; top: -25px; display: flex; @@ -105,4 +113,4 @@ } } } -} +} \ No newline at end of file diff --git a/ui/libs/workflow-graph/src/lib/node/fork-join-node.components.ts b/ui/libs/workflow-graph/src/lib/node/fork-join-node.components.ts index 0a58aec3ac..a197a79cf4 100644 --- a/ui/libs/workflow-graph/src/lib/node/fork-join-node.components.ts +++ b/ui/libs/workflow-graph/src/lib/node/fork-join-node.components.ts @@ -1,4 +1,4 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { GraphNode } from '../graph.model'; import { NodeStatus } from './status.model'; @@ -48,11 +48,10 @@ export class GraphForkJoinNodeComponent implements OnInit { } } - setHighlight(active: boolean): void { + setHighlight(active: boolean, options?: any): void { this.highlight = active; this._cd.markForCheck(); } - setSelect(_: boolean): void { - } + setSelect(active: boolean, options?: any): void { } } diff --git a/ui/libs/workflow-graph/src/lib/node/gate-node.component.ts b/ui/libs/workflow-graph/src/lib/node/gate-node.component.ts deleted file mode 100644 index 65abd00d72..0000000000 --- a/ui/libs/workflow-graph/src/lib/node/gate-node.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {GraphNode} from '../graph.model' -import { NodeStatus } from './status.model'; - -@Component({ - selector: 'app-gate-node', - templateUrl: './gate-node.html', - styleUrls: ['./gate-node.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class GraphGateNodeComponent implements OnInit { - @Input() node: GraphNode; - @Input() mouseCallback: (type: string, node: GraphNode) => void; - - highlight = false; - status: string; - nodeStatusEnum = NodeStatus; - - constructor( - private _cd: ChangeDetectorRef - ) { - this.setHighlight.bind(this); - this.setSelect.bind(this); - } - - ngOnInit() { - this.status = this.node.gateStatus; - } - - getNodes() { - return [this.node]; - } - - onMouseEnter(): void { - if (this.mouseCallback) { - this.mouseCallback('enter', this.node); - } - } - - onMouseOut(): void { - if (this.mouseCallback) { - this.mouseCallback('out', this.node); - } - } - - onMouseClick(): void { - if (this.mouseCallback) { - this.mouseCallback('click', this.node); - } - } - - setHighlight(active: boolean): void { - this.highlight = active; - this._cd.markForCheck(); - } - - setSelect(_: boolean): void { - } -} diff --git a/ui/libs/workflow-graph/src/lib/node/gate-node.html b/ui/libs/workflow-graph/src/lib/node/gate-node.html deleted file mode 100644 index 4c7a51d318..0000000000 --- a/ui/libs/workflow-graph/src/lib/node/gate-node.html +++ /dev/null @@ -1,4 +0,0 @@ -
- -
diff --git a/ui/libs/workflow-graph/src/lib/node/gate-node.scss b/ui/libs/workflow-graph/src/lib/node/gate-node.scss deleted file mode 100644 index 29676ef6c3..0000000000 --- a/ui/libs/workflow-graph/src/lib/node/gate-node.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import "../../../../../src/common.scss"; - -:host { - display: block; - height: 100%; - width: 100%; -} - -.node { - width: 40px; - height: 40px; - border-radius: 20px; - border: 2px solid $polar_grey_3; - color: $polar_grey_1; - padding: 2px; - background-color: white; - text-align: center; - - - :host-context(.night) & { - background-color: $darkTheme_grey_1; - border-color: $darkTheme_grey_5; - color: $darkTheme_grey_6; - - a { - color: $polar_grey_4; - } - - &.active, - &.highlight { - border-color: white; - } - } - - &.success { - background-color: $cds_color_light_green; - border-color: $cds_color_green; - } - - &.highlight { - border-color: black; - } - - i { - font-size: 2em; - } -} diff --git a/ui/libs/workflow-graph/src/lib/node/job-node.component.ts b/ui/libs/workflow-graph/src/lib/node/job-node.component.ts index dd2c052302..f22840e48f 100644 --- a/ui/libs/workflow-graph/src/lib/node/job-node.component.ts +++ b/ui/libs/workflow-graph/src/lib/node/job-node.component.ts @@ -1,6 +1,8 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from '@angular/core'; -import {GraphNode} from '../graph.model' -import { NodeStatus } from './status.model'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { GraphNode } from '../graph.model' +import { V2WorkflowRunJobStatus } from '../v2.workflow.run.model'; +import { Subscription, concatMap, from, interval } from 'rxjs'; +import { DurationService } from '../duration/duration.service'; @Component({ selector: 'app-job-node', @@ -8,13 +10,21 @@ import { NodeStatus } from './status.model'; styleUrls: ['./job-node.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class GraphJobNodeComponent { +export class GraphJobNodeComponent implements OnInit, OnDestroy { @Input() node: GraphNode; - @Input() mouseCallback: (type: string, node: GraphNode) => void; + @Input() mouseCallback: (type: string, node: GraphNode, options?: any) => void; highlight = false; selected = false; - nodeStatusEnum = NodeStatus; + statusEnum = V2WorkflowRunJobStatus; + duration: string; + delaySubs: Subscription; + dates: { + queued: Date; + scheduled: Date; + started: Date; + ended: Date; + }; constructor( private _cd: ChangeDetectorRef @@ -23,35 +33,91 @@ export class GraphJobNodeComponent { this.setSelect.bind(this); } + ngOnDestroy(): void { + if (this.delaySubs) { + this.delaySubs.unsubscribe(); + } + } + + ngOnInit(): void { + if (!this.node.run) { + return; + } + this.dates = { + queued: new Date(this.node.run.queued), + scheduled: this.node.run.scheduled ? new Date(this.node.run.scheduled) : null, + started: this.node.run.started ? new Date(this.node.run.started) : null, + ended: this.node.run.ended ? new Date(this.node.run.ended) : null + }; + const isRunning = this.node.run.status === V2WorkflowRunJobStatus.Waiting || + this.node.run.status === V2WorkflowRunJobStatus.Scheduling || + this.node.run.status === V2WorkflowRunJobStatus.Building; + if (isRunning) { + this.delaySubs = interval(1000) + .pipe(concatMap(_ => from(this.refreshDelay()))) + .subscribe(); + } + this.refreshDelay(); + } + + async refreshDelay() { + const now = new Date(); + switch (this.node.run.status) { + case V2WorkflowRunJobStatus.Waiting: + case V2WorkflowRunJobStatus.Scheduling: + this.duration = DurationService.duration(this.dates.queued, now); + break; + case V2WorkflowRunJobStatus.Building: + this.duration = DurationService.duration(this.dates.started, now); + break; + case V2WorkflowRunJobStatus.Fail: + case V2WorkflowRunJobStatus.Stopped: + case V2WorkflowRunJobStatus.Success: + this.duration = DurationService.duration(this.dates.started, this.dates.ended); + break; + default: + break; + } + this._cd.markForCheck(); + } + getNodes() { return [this.node]; } onMouseEnter(): void { if (this.mouseCallback) { - this.mouseCallback('enter', this.node); + this.mouseCallback('enter', this.node, { jobRunID: this.node.run ? this.node.run.id : null }); } } onMouseOut(): void { if (this.mouseCallback) { - this.mouseCallback('out', this.node); + this.mouseCallback('out', this.node, { jobRunID: this.node.run ? this.node.run.id : null }); } } onMouseClick(): void { if (this.mouseCallback) { - this.mouseCallback('click', this.node); + this.mouseCallback('click', this.node, { jobRunID: this.node.run ? this.node.run.id : null }); } } - setHighlight(active: boolean): void { + setHighlight(active: boolean, options?: any): void { this.highlight = active; this._cd.markForCheck(); } - setSelect(active: boolean): void { + setSelect(active: boolean, options?: any): void { this.selected = active; this._cd.markForCheck(); } + + clickRunGate(event: Event): void { + if (this.mouseCallback) { + this.mouseCallback('click', this.node, { gateName: this.node.gate }); + } + event.preventDefault(); + event.stopPropagation(); + } } diff --git a/ui/libs/workflow-graph/src/lib/node/job-node.html b/ui/libs/workflow-graph/src/lib/node/job-node.html index 0ed6f7efcc..ee0f40ef34 100644 --- a/ui/libs/workflow-graph/src/lib/node/job-node.html +++ b/ui/libs/workflow-graph/src/lib/node/job-node.html @@ -1,15 +1,23 @@
-
-
- - {{node?.name}} - -
+ (mouseenter)="onMouseEnter()" (mouseleave)="onMouseOut()" [class.success]="node?.run?.status === statusEnum.Success" + [class.inactive]="node?.run?.status === statusEnum.Skipped" + [class.fail]="node?.run?.status === statusEnum.Fail || node?.run?.status === statusEnum.Stopped" + [class.building]="node?.run?.status === statusEnum.Building || node?.run?.status === statusEnum.Waiting" + [class.scheduling]="node?.run?.status === statusEnum.Scheduling"> + +
+ {{node?.name}}
-
+
+ {{duration}} + +
+
Queued: {{dates.queued | date: 'long'}}
+
Scheduled: {{dates.scheduled | date: 'long'}}
+
Started: {{dates.started | date: 'long'}}
+
Ended: {{dates.ended | date: 'long'}}
+
+
+
+
\ No newline at end of file diff --git a/ui/libs/workflow-graph/src/lib/node/job-node.scss b/ui/libs/workflow-graph/src/lib/node/job-node.scss index e621f1fa69..c6ebdd6ea6 100644 --- a/ui/libs/workflow-graph/src/lib/node/job-node.scss +++ b/ui/libs/workflow-graph/src/lib/node/job-node.scss @@ -14,16 +14,15 @@ color: $polar_grey_1; padding: 2px; background-color: white; + display: flex; + flex-direction: row; + align-items: center; :host-context(.night) & { background-color: $darkTheme_grey_1; border-color: $darkTheme_grey_5; color: $darkTheme_grey_6; - a { - color: $polar_grey_4; - } - &.success { background-color: $darkTheme_night_green; border-color: $darkTheme_green; @@ -54,50 +53,39 @@ } } - .icon { - display: unset; - margin: 0; - } - - .ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - a { + .gate { color: $polar_grey_1; + font-size: 30px; + margin-left: 10px; + cursor: pointer; + + :host-context(.night) & { + color: $darkTheme_grey_6; + } &:hover { - color: $cds_color_teal; + color: grey !important; } } - .title { - height: 100%; - display: flex; - flex-direction: row; + .name { font-size: 1em; - padding: 2px 4px 0 4px; - align-items: center; - text-align: center; - - .name { - flex: 1; - font-weight: bold; - } - - .count { - padding: 0 0px 0 2px; - text-align: right; - } + flex: 1; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 0 10px; } - .details { - flex: 1; + .duration { + font-size: 0.8em; + margin-right: 10px; + } + + .durationTooltip { display: flex; - flex-direction: row; - justify-content: flex-end; + flex-direction: column; } &.success { @@ -132,4 +120,4 @@ &.highlight { border-color: black; } -} +} \ No newline at end of file diff --git a/ui/libs/workflow-graph/src/lib/node/matrix-node.component.ts b/ui/libs/workflow-graph/src/lib/node/matrix-node.component.ts new file mode 100644 index 0000000000..55bef7521e --- /dev/null +++ b/ui/libs/workflow-graph/src/lib/node/matrix-node.component.ts @@ -0,0 +1,152 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { GraphNode } from '../graph.model' +import { V2WorkflowRunJobStatus } from '../v2.workflow.run.model'; +import { Subscription } from 'rxjs'; +import { DurationService } from '../duration/duration.service'; + +@Component({ + selector: 'app-matrix-node', + templateUrl: './matrix-node.html', + styleUrls: ['./matrix-node.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class GraphMatrixNodeComponent implements OnInit, OnDestroy { + @Input() node: GraphNode; + @Input() mouseCallback: (type: string, node: GraphNode, options?: any) => void; + + highlightKey: string; + selectedKey: string; + statusEnum = V2WorkflowRunJobStatus; + durations: { [key: string]: string } = {}; + delaySubs: Subscription; + dates: { + [key: string]: { + queued: Date; + scheduled: Date; + started: Date; + ended: Date; + } + } = {}; + keys: Array = []; + status: { [key: string]: string } = {}; + jobRunIDs: { [key: string]: string } = {}; + + constructor( + private _cd: ChangeDetectorRef + ) { + this.setHighlight.bind(this); + this.setSelect.bind(this); + } + + ngOnDestroy(): void { + if (this.delaySubs) { + this.delaySubs.unsubscribe(); + } + } + + ngOnInit(): void { + const alls = GraphNode.generateMatrixOptions(this.node.job.strategy.matrix); + this.keys = alls.map(option => { + return Array.from(option.keys()).sort().map(key => { + return `${key}:${option.get(key)}`; + }).join(', '); + }); + (this.node.runs ?? []).forEach(r => { + const key = Object.keys(r.matrix).sort().map(key => { + return `${key}:${r.matrix[key]}`; + }).join(', '); + this.dates[key] = { + queued: new Date(r.queued), + scheduled: r.scheduled ? new Date(r.scheduled) : null, + started: r.started ? new Date(r.started) : null, + ended: r.ended ? new Date(r.ended) : null + }; + this.status[key] = r.status; + this.jobRunIDs[key] = r.id; + }); + this.refreshDelay(); + } + + async refreshDelay() { + const now = new Date(); + (this.node.runs ?? []).forEach(r => { + const key = Object.keys(r.matrix).sort().map(key => { + return `${key}:${r.matrix[key]}`; + }).join(', '); + switch (r.status) { + case V2WorkflowRunJobStatus.Waiting: + case V2WorkflowRunJobStatus.Scheduling: + this.durations[key] = DurationService.duration(this.dates[key].queued, now); + break; + case V2WorkflowRunJobStatus.Building: + this.durations[key] = DurationService.duration(this.dates[key].started, now); + break; + case V2WorkflowRunJobStatus.Fail: + case V2WorkflowRunJobStatus.Stopped: + case V2WorkflowRunJobStatus.Success: + this.durations[key] = DurationService.duration(this.dates[key].started, this.dates[key].ended); + break; + default: + break; + } + }); + this._cd.markForCheck(); + } + + getNodes() { + return [this.node]; + } + + onMouseEnter(key: string): void { + if (this.mouseCallback) { + this.mouseCallback('enter', this.node, { + jobRunID: this.jobRunIDs[key] ?? null, + jobMatrixKey: key + }); + } + } + + onMouseOut(key: string): void { + if (this.mouseCallback) { + this.mouseCallback('out', this.node, { + jobRunID: this.jobRunIDs[key] ?? null, + jobMatrixKey: key + }); + } + } + + onMouseClick(key: string): void { + if (this.mouseCallback) { + this.mouseCallback('click', this.node, { + jobRunID: this.jobRunIDs[key] ?? null, + jobMatrixKey: key + }); + } + } + + setHighlight(active: boolean, options?: any): void { + if (options && options['jobMatrixKey'] && active) { + this.highlightKey = options['jobMatrixKey']; + } else { + this.highlightKey = null; + } + this._cd.markForCheck(); + } + + setSelect(active: boolean, options?: any): void { + if (options && options['jobMatrixKey'] && active) { + this.selectedKey = options['jobMatrixKey']; + } else { + this.selectedKey = null; + } + this._cd.markForCheck(); + } + + clickRunGate(event: Event): void { + if (this.mouseCallback) { + this.mouseCallback('click', this.node, { gateName: this.node.gate }); + } + event.preventDefault(); + event.stopPropagation(); + } +} diff --git a/ui/libs/workflow-graph/src/lib/node/matrix-node.html b/ui/libs/workflow-graph/src/lib/node/matrix-node.html new file mode 100644 index 0000000000..5aa9a5f1d7 --- /dev/null +++ b/ui/libs/workflow-graph/src/lib/node/matrix-node.html @@ -0,0 +1,32 @@ +
+ + Matrix: {{node?.name}} +
+
+
+
+
+ {{node?.name}} {{key}} +
+
+ {{durations[key]}} + +
+
Queued: {{dates[key].queued | date: 'long'}}
+
Scheduled: {{dates[key].scheduled | date: 'long'}} +
+
Started: {{dates[key].started | date: 'long'}}
+
Ended: {{dates[key].ended | date: 'long'}}
+
+
+
+
+
+
\ No newline at end of file diff --git a/ui/libs/workflow-graph/src/lib/node/matrix-node.scss b/ui/libs/workflow-graph/src/lib/node/matrix-node.scss new file mode 100644 index 0000000000..98ec98fe7f --- /dev/null +++ b/ui/libs/workflow-graph/src/lib/node/matrix-node.scss @@ -0,0 +1,179 @@ +@import "../../../../../src/common.scss"; + +:host { + display: flex; + height: 100%; + width: 100%; + flex-direction: column; + align-items: flex-start; +} + +.label { + height: 30px; + width: 100%; + overflow: hidden; + display: flex; + flex-direction: row; + align-items: center; + + .gate { + color: $polar_grey_1; + font-size: 16px; + overflow: hidden; + margin-left: 2px; + cursor: pointer; + + :host-context(.night) & { + color: $darkTheme_grey_6; + } + + &:hover { + color: grey !important; + } + } + + .name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 0 2px; + } +} + +.node { + width: 100%; + flex: 1; + border: 2px solid $polar_grey_3; + border-radius: 3px; + color: $polar_grey_1; + padding: 2px; + background-color: white; + margin-bottom: 30px; + display: flex; + flex-direction: row; + align-items: center; + + :host-context(.night) & { + background-color: $darkTheme_grey_1; + border-color: $darkTheme_grey_5; + color: $darkTheme_grey_6; + } + + .jobs { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 10px; + + .job { + height: 30px; + width: 100%; + display: flex; + flex-direction: row; + background-color: white; + align-items: center; + border: 2px solid $polar_grey_3; + border-radius: 3px; + padding: 2px; + + &:not(:first-child) { + margin-top: 10px; + } + + .name { + flex: 1; + font-size: 1em; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 0 10px; + + .key { + font-size: 0.8em; + font-weight: unset; + } + } + + .duration { + font-size: 0.8em; + margin-right: 10px; + } + + .durationTooltip { + display: flex; + flex-direction: column; + } + + &.success { + background-color: $cds_color_light_green; + border-color: $cds_color_green; + } + + &.fail { + background-color: $cds_color_light_red; + border-color: $cds_color_red; + } + + &.building { + background-color: $cds_color_light_teal; + border-color: $cds_color_teal; + } + + &.scheduling { + border-color: $cds_color_teal; + } + + &.inactive { + background-color: $cds_color_light_grey; + border-color: grey; + } + + &.active { + border: 3px solid black; + padding: 1px; + } + + &.highlight { + border-color: black; + } + + :host-context(.night) & { + background-color: $darkTheme_grey_1; + border-color: $darkTheme_grey_5; + color: $darkTheme_grey_6; + + &.success { + background-color: $darkTheme_night_green; + border-color: $darkTheme_green; + } + + &.fail { + background-color: $darkTheme_night_red; + border-color: $darkTheme_red; + } + + &.building { + background-color: $darkTheme_night_blue; + border-color: $darkTheme_blue; + } + + &.scheduling { + border-color: $darkTheme_blue; + } + + &.inactive { + background-color: $darkTheme_night_grey; + border-color: grey; + } + + &.active, + &.highlight { + border-color: white; + } + } + } + } +} \ No newline at end of file diff --git a/ui/libs/workflow-graph/src/lib/stages-graph.component.ts b/ui/libs/workflow-graph/src/lib/stages-graph.component.ts index c6e9ad6b41..760afa5e44 100644 --- a/ui/libs/workflow-graph/src/lib/stages-graph.component.ts +++ b/ui/libs/workflow-graph/src/lib/stages-graph.component.ts @@ -14,14 +14,13 @@ import { import { WorkflowV2JobsGraphComponent } from './jobs-graph.component'; import { GraphForkJoinNodeComponent } from './node/fork-join-node.components'; import { GraphJobNodeComponent } from './node/job-node.component'; -import { GraphNode, GraphNodeTypeGate, GraphNodeTypeJob, GraphNodeTypeStage } from './graph.model'; +import { GraphNode, GraphNodeType } from './graph.model'; import { GraphDirection, WorkflowV2Graph } from './graph.lib'; import { load, LoadOptions } from 'js-yaml'; -import { GraphGateNodeComponent } from './node/gate-node.component'; -import { V2WorkflowRun, V2WorkflowRunJob } from './v2.workflow.run.model'; +import { V2Workflow, V2WorkflowRun, V2WorkflowRunJob } from './v2.workflow.run.model'; +import { GraphMatrixNodeComponent } from './node/matrix-node.component'; -export type WorkflowV2JobsGraphOrNodeComponent = WorkflowV2JobsGraphComponent | - GraphForkJoinNodeComponent | GraphJobNodeComponent | GraphGateNodeComponent; +export type WorkflowV2JobsGraphOrNodeOrMatrixComponent = WorkflowV2JobsGraphComponent | GraphForkJoinNodeComponent | GraphJobNodeComponent | GraphMatrixNodeComponent; @Component({ selector: 'app-stages-graph', @@ -39,7 +38,8 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy hooksOn: any; @Input() set workflow(data: any) { - let workflow: any; + // Parse the workflow + let workflow: V2Workflow; try { workflow = load(data && data !== '' ? data : '{}', { onWarning: (e) => { } @@ -47,136 +47,67 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy } catch (e) { console.error("Invalid workflow:", data, e) } - this.hasStages = !!workflow && !!workflow["stages"]; + + this.hasStages = !!workflow && !!workflow.stages; + this.nodes = []; - if (workflow && workflow["stages"]) { - this.nodes.push(...Object.keys(workflow["stages"]) - .map(k => { - name: k, - depends_on: workflow["stages"][k]?.needs, - sub_graph: [], - type: GraphNodeTypeStage - })); + if (this.hasStages) { + this.nodes.push(...Object.keys(workflow.stages).map(k => { + type: GraphNodeType.Stage, + name: k, + depends_on: workflow.stages[k]?.needs, + sub_graph: [] + })); } - if (workflow && workflow["jobs"] && Object.keys(workflow["jobs"]).length > 0) { - let matrixJobs = new Map() - Object.keys(workflow["jobs"]).forEach(jobID => { - let job = workflow.jobs[jobID]; - let expandMatrixJobs = new Array(); - if (job?.strategy?.matrix) { - let keys = Object.keys(job.strategy.matrix); - let alls = new Array>(); - this.generateMatrix(job.strategy.matrix, keys, 0, new Map(), alls) - alls.forEach(m => { - let suffix = ""; - let mapKeys = Array.from(m.keys()).sort(); - mapKeys.forEach((k, index) => { - if (index !== 0) { - suffix += ','; - } - suffix += m.get(k); - }); - let newJob = Object.assign({}, job); - newJob.matrixName = jobID + '-' + suffix.replaceAll('/', '-'); - expandMatrixJobs.push(newJob); - }); - matrixJobs.set(jobID, expandMatrixJobs); - } - }); - Object.keys(workflow["jobs"]).forEach(k => { - let job = workflow.jobs[k]; - let gateNode = undefined; - if (job?.gate && job.gate !== '') { - gateNode = { name: `${job.gate}-${k}`, type: GraphNodeTypeGate, gateChild: k, gateName: `${job.gate}` } + + if (workflow && workflow.jobs) { + Object.keys(workflow.jobs).forEach(jobName => { + const jobSpec = workflow.jobs[jobName]; + + let node = { + type: jobSpec?.strategy?.matrix ? GraphNodeType.Matrix : GraphNodeType.Job, + name: jobName, + depends_on: jobSpec?.needs, + job: jobSpec + }; + if (jobSpec.gate) { + node.gate = workflow.gates[jobSpec.gate]; } - if (matrixJobs.has(k)) { - matrixJobs.get(k).forEach(j => { - let node = { name: j.matrixName, depends_on: this.getJobNeeds(j, matrixJobs), type: GraphNodeTypeJob }; - if (job?.stage) { - for (let i = 0; i < this.nodes.length; i++) { - if (this.nodes[i].name === job.stage && this.nodes[i].type === GraphNodeTypeStage) { - this.nodes[i].sub_graph.push(node); - if (gateNode) { - this.nodes[i].sub_graph.push(gateNode); - } - break; - } - } - } else { - this.nodes.push(node); - if (gateNode) { - this.nodes.push(gateNode); - } - } - }); - } else { - let node = { name: k, depends_on: this.getJobNeeds(job, matrixJobs), type: GraphNodeTypeJob }; - node.run = this.jobRuns[k]; - if (job?.stage) { - for (let i = 0; i < this.nodes.length; i++) { - if (this.nodes[i].name === job.stage && this.nodes[i].type === GraphNodeTypeStage) { - this.nodes[i].sub_graph.push(node); - if (gateNode) { - this.nodes[i].sub_graph.push(gateNode); - } - break; - } - } - } else { - this.nodes.push(node); - if (gateNode) { - this.nodes.push(gateNode); + + if (jobSpec?.stage) { + for (let i = 0; i < this.nodes.length; i++) { + if (this.nodes[i].name === jobSpec.stage && this.nodes[i].type === GraphNodeType.Stage) { + this.nodes[i].sub_graph.push(node); + break; } } + } else { + this.nodes.push(node); } }); - this.initRunJobs(); } + + this.initRunJobs(); + this.initGate(); + this.hooks = []; this.selectedHook = ''; - if (workflow && workflow['on']) { - this.hooksOn = workflow['on']; + if (workflow && workflow.on) { + this.hooksOn = workflow.on; this.initHooks(); } - this.initGate(); + this.changeDisplay(); this._cd.markForCheck(); } - jobRuns: { [name: string]: V2WorkflowRunJob } = {}; + _runJobs: Array = []; @Input() set runJobs(data: Array) { - if (!data) { - this.jobRuns = {} - if (this.nodes) { - this.nodes.forEach(n => { - if (this.hasStages) { - n.sub_graph.forEach(sub => { - delete sub.run; - }); - } else { - delete n.run; - } - }) - } + this._runJobs = data ?? []; + if (!this.svgContainer) { return; } - this.jobRuns = {}; - data.forEach(j => { - if (j.matrix && Object.keys(j.matrix).length > 0) { - let mapKeys = Object.keys(j.matrix).sort(); - let suffix = ""; - mapKeys.forEach((k, index) => { - if (index !== 0) { - suffix += ','; - } - suffix += j.matrix[k]; - }); - this.jobRuns[j.job_id + '-' + suffix.replaceAll('/', '-')] = j; - } else { - this.jobRuns[j.job_id] = j; - } - }); this.initRunJobs(); this.initGraph(); } @@ -200,9 +131,12 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy // workflow graph @ViewChild('svgGraph', { read: ViewContainerRef }) svgContainer: ViewContainerRef; - graph: WorkflowV2Graph; + graph: WorkflowV2Graph; - constructor(private _cd: ChangeDetectorRef, private host: ElementRef) { + constructor( + private _cd: ChangeDetectorRef, + private host: ElementRef + ) { const observer = new ResizeObserver(entries => { this.onResize(); }); @@ -222,30 +156,6 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy } } - getJobNeeds(j: {}, matrixJobs: Map>) { - if (!j['needs']) { - return []; - } - let needs = []; - j['needs'].forEach(n => { - if (!matrixJobs.has(n)) { - needs.push(n); - } else { - matrixJobs.get(n).forEach(mj => { - needs.push(mj['matrixName'].replaceAll('/', '-')); - }); - } - }); - return needs; - } - - static isJobsGraph = (component: WorkflowV2JobsGraphOrNodeComponent): component is WorkflowV2JobsGraphComponent => { - if ((component as WorkflowV2JobsGraphComponent).direction) { - return true; - } - return false; - }; - ngOnDestroy(): void { } // Should be set to use @AutoUnsubscribe with AOT ngAfterViewInit(): void { @@ -276,44 +186,61 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy if (!this._workflowRun.job_events || this._workflowRun.job_events.length === 0) { return; } - this.nodes.forEach(n => { - if (this.hasStages) { - n.sub_graph.forEach(subN => { - if (subN.gateName !== '') { - let je = this._workflowRun.job_events.filter(je => je.job_id === subN.gateChild); - if (je) { - subN.gateStatus = 'Success'; + this._workflowRun.job_events.forEach(e => { + this.nodes.forEach(n => { + if (n.sub_graph) { + n.sub_graph.forEach(sub => { + if (sub.name === e.job_id) { + sub.event = e; } + }); + } else { + if (n.name === e.job_id) { + n.event = e; } - }); - } else { - if (n.gateName !== '') { - let je = this._workflowRun.job_events.filter(je => je.job_id === n.gateChild); - if (je) { - n.gateStatus = 'Success'; - } - } - } + }; + }); }); } initRunJobs(): void { - if (!this.jobRuns || !this.nodes) { - return; - } + // Clean run job data on nodes this.nodes.forEach(n => { - if (this.hasStages) { + if (n.sub_graph) { n.sub_graph.forEach(sub => { - if (this.jobRuns[sub.name]) { - sub.run = this.jobRuns[sub.name]; - } + delete sub.run; + delete sub.runs; }); } else { - if (this.jobRuns[n.name]) { - n.run = this.jobRuns[n.name]; - } + delete n.run; + delete n.runs; } - }) + }); + + // Add run job data on nodes + this._runJobs.forEach(j => { + this.nodes.forEach(n => { + if (n.sub_graph) { + n.sub_graph.forEach(sub => { + if (sub.name === j.job_id) { + if (sub.type === GraphNodeType.Matrix) { + sub.runs = (sub.runs ?? []).concat(j); + } else { + sub.run = j; + } + } + }); + } else { + if (n.name === j.job_id) { + if (n.type === GraphNodeType.Matrix) { + n.runs = (n.runs ?? []).concat(j); + } else { + n.run = j; + } + } + }; + }); + }); } initGraph() { @@ -327,19 +254,25 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy } this.nodes.forEach(n => { - if (this.hasStages) { - this.graph.createNode(n.name, GraphNodeTypeStage, this.createSubGraphComponent(n), - null, 300, 169); - } else { - switch (n.type) { - case GraphNodeTypeGate: - this.graph.createGate(n, GraphNodeTypeGate, this.createGateNodeComponent(n), - n.run ? n.run.status : null); - break; - default: - this.graph.createNode(n.name, GraphNodeTypeJob, this.createJobNodeComponent(n), - n.run ? n.run.status : null); - } + let component: ComponentRef; + switch (n.type) { + case GraphNodeType.Stage: + component = this.createSubGraphComponent(n); + this.graph.createNode(n.name, n.type, component, 300, 170); + break; + case GraphNodeType.Matrix: + component = this.createJobMatrixComponent(n); + const alls = GraphNode.generateMatrixOptions(n.job.strategy.matrix); + let height = 30 * alls.length + 10 * (alls.length - 1) + 60 + 20; + this.graph.createNode(n.name, n.type, component, 240, height); + break; + default: + component = this.createJobNodeComponent(n); + this.graph.createNode(n.name, n.type, component); + if (n.run) { + this.graph.setNodeStatus(n.name, n.run ? n.run.status : null); + } + break; } }); @@ -382,16 +315,16 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy this.onSelectHook.emit(type); } - createGateNodeComponent(node: GraphNode): ComponentRef { - const componentRef = this.svgContainer.createComponent(GraphGateNodeComponent); + createJobNodeComponent(node: GraphNode): ComponentRef { + const componentRef = this.svgContainer.createComponent(GraphJobNodeComponent); componentRef.instance.node = node; componentRef.instance.mouseCallback = this.nodeJobMouseEvent.bind(this); componentRef.changeDetectorRef.detectChanges(); return componentRef; } - createJobNodeComponent(node: GraphNode): ComponentRef { - const componentRef = this.svgContainer.createComponent(GraphJobNodeComponent); + createJobMatrixComponent(node: GraphNode): ComponentRef { + const componentRef = this.svgContainer.createComponent(GraphMatrixNodeComponent); componentRef.instance.node = node; componentRef.instance.mouseCallback = this.nodeJobMouseEvent.bind(this); componentRef.changeDetectorRef.detectChanges(); @@ -427,52 +360,40 @@ export class WorkflowV2StagesGraphComponent implements AfterViewInit, OnDestroy this.svgContainer.element.nativeElement.offsetHeight); } - nodeMouseEvent(type: string, n: GraphNode) { - this.graph.nodeMouseEvent(type, n.name); + nodeMouseEvent(type: string, n: GraphNode, options?: any) { + this.graph.nodeMouseEvent(type, n.name, options); } - nodeJobMouseEvent(type: string, n: GraphNode) { + nodeJobMouseEvent(type: string, n: GraphNode, options?: any) { if (type === 'click') { - if (n.gateName && n.gateName !== '') { + if (options && options['jobRunID']) { + this.onSelectJobRun.emit(options['jobRunID']); + } else if (options && options['gate']) { this.onSelectJobGate.emit(n); } else { - if (n.run) { - this.onSelectJob.emit(n.run.id); - } else { - this.onSelectJob.emit(n.name); - } + this.onSelectJob.emit(n.name); } } - this.graph.nodeMouseEvent(type, n.name); + this.graph.nodeMouseEvent(type, n.name, options); } - subGraphSelectJob(name: string): void { - this.graph.unselectAllNode(); - this.onSelectJob.emit(name); + subGraphSelectJob(type: string, n: GraphNode, options?: any): void { + if (type === 'click') { + this.graph.unselectAllNode(); + if (options && options['jobRunID']) { + this.onSelectJobRun.emit(options['jobRunID']); + } else if (options && options['gateName']) { + this.onSelectJobGate.emit(n); + } else { + this.onSelectJob.emit(n.name); + } + } } changeDirection(): void { this.direction = this.direction === GraphDirection.HORIZONTAL ? GraphDirection.VERTICAL : GraphDirection.HORIZONTAL; this.changeDisplay(); } - - generateMatrix(matrix: { [key: string]: string[] }, keys: string[], keyIndex: number, current: Map, alls: Map[]) { - if (current.size == keys.length) { - let combi = new Map(); - current.forEach((v, k) => { - combi.set(k, v); - }); - alls.push(combi); - return; - } - let key = keys[keyIndex]; - let values = matrix[key]; - values.forEach(v => { - current.set(key, v); - this.generateMatrix(matrix, keys, keyIndex + 1, current, alls); - current.delete(key); - }); - } } diff --git a/ui/libs/workflow-graph/src/lib/v2.workflow.run.model.ts b/ui/libs/workflow-graph/src/lib/v2.workflow.run.model.ts index 998c01f5a7..49a311e637 100644 --- a/ui/libs/workflow-graph/src/lib/v2.workflow.run.model.ts +++ b/ui/libs/workflow-graph/src/lib/v2.workflow.run.model.ts @@ -2,7 +2,9 @@ export class V2WorkflowRun { id: string; project_key: string; vcs_server_id: string; + vcs_server: string; repository_id: string; + repository: string; workflow_name: string; workflow_sha: string; workflow_ref: string; @@ -12,20 +14,27 @@ export class V2WorkflowRun { started: string; last_modified: string; to_delete: boolean; - workflow_data: WorkflowData; + workflow_data: WorkflowRunData; user_id: string; username: string; contexts: any; event: WorkflowEvent; job_events: V2WorkflowRunJobEvent[]; - results: Array; } - export class V2WorkflowRunJobEvent { + +export class WorkflowRunData { + workflow: V2Workflow; + worker_models: { [key: string]: {} }; + actions: { [key: string]: {} }; +} + +export class V2WorkflowRunJobEvent { + user_id: string; username: string; job_id: string; - inputs: {[key:string]:any}; + inputs: { [key: string]: any }; run_attempt: number; - } +} export class WorkflowEvent { hook_type: string @@ -36,24 +45,69 @@ export class WorkflowEvent { entity_updated: String; } -export class WorkflowData { - workflow: any; - worker_models: {[key:string]: { }}; - actions: {[key:string]: { }}; +export class V2Workflow { + name: string; + repository: WorkflowRepository; + 'commit-status': CommitStatus; + on: WorkflowOn; + stages: { [key: string]: any }; + gates: { [key: string]: V2JobGate }; + jobs: { [key: string]: V2Job }; + env: { [key: string]: string }; + integrations: Array; + vars: Array; +} + +export class WorkflowRepository { + vcs: string; + name: string; +} + +export class CommitStatus { + title: string; + description: string; +} + +export class WorkflowOn { + push: { + branches: Array; + tags: Array; + paths: Array; + }; + 'pull-request': { + branches: Array; + comment: string; + paths: Array; + types: Array; + }; + 'pull-request-comment': { + branches: Array; + comment: string; + paths: Array; + types: Array; + }; + 'model-update': { + models: Array; + target_branch: string; + }; + 'workflow-update': { + target_branch: string; + }; } -export class Gate { - inputs: {[key:string]:GateInput}; - reviewers: GateReviewers; +export class V2JobGate { + if: string; + inputs: { [key: string]: V2JobGateInput }; + reviewers: V2JobGateReviewers; } -export class GateInput { +export class V2JobGateInput { type: string; default: any; - values: string; + values: Array; } -export class GateReviewers { +export class V2JobGateReviewers { groups: string[]; users: string[]; } @@ -66,28 +120,60 @@ export class V2WorkflowRunJob { workflow_name: string; run_number: number run_attempt: number; - status: string; + status: V2WorkflowRunJobStatus; queued: string; scheduled: string; started: string; ended: string; - job: {}; + job: V2Job; worker_id: string; worker_name: string; hatchery_name: string; - outputs: {[key:string]:string}; - steps_status: {[key:string]:StepStatus }; + steps_status: { [key: string]: StepStatus }; user_id: string; username: string; region: string; model_type: string; - matrix: {[key:string]: string}; + matrix: { [key: string]: string }; + gate_inputs: { [key: string]: any }; +} + +export enum V2WorkflowRunJobStatus { + Waiting = 'Waiting', + Building = 'Building', + Fail = 'Fail', + Stopped = 'Stopped', + Success = 'Success', + Scheduling = 'Scheduling', + Skipped = 'Skipped' +} + +export class V2Job { + name: string; + if: string; + gate: string; + inputs: { [key: string]: string }; + steps: Array; + needs: Array; + stage: string; + region: string; + 'continue-on-error': boolean; + 'runs-on': string; + strategy: V2JobStrategy; + integrations: Array; + vars: Array; + env: { [key: string]: string }; + services: { [key: string]: any }; +} + +export class V2JobStrategy { + matrix: { [key: string]: Array }; } export class StepStatus { conclusion: string; outcome: string; - outputs: {[key:string]:string}; + outputs: { [key: string]: string }; started: string; ended: string; } @@ -107,6 +193,6 @@ export class WorkflowRunResult { } export class WorkflowRunResultDetail { - Data: any; - Type: string; -} + data: any; + type: string; +} \ No newline at end of file diff --git a/ui/libs/workflow-graph/src/lib/workflow-graph.module.ts b/ui/libs/workflow-graph/src/lib/workflow-graph.module.ts index 8b3f5724c2..e47eba0474 100644 --- a/ui/libs/workflow-graph/src/lib/workflow-graph.module.ts +++ b/ui/libs/workflow-graph/src/lib/workflow-graph.module.ts @@ -2,23 +2,23 @@ import { NgModule } from '@angular/core'; import { WorkflowV2StagesGraphComponent } from './stages-graph.component'; import { WorkflowV2JobsGraphComponent } from './jobs-graph.component'; import { GraphForkJoinNodeComponent } from './node/fork-join-node.components'; -import { GraphGateNodeComponent } from './node/gate-node.component'; import { GraphJobNodeComponent } from './node/job-node.component'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzAvatarModule } from 'ng-zorro-antd/avatar'; import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; import { CommonModule } from '@angular/common'; import { NzButtonModule } from 'ng-zorro-antd/button'; -import { AimOutline, RotateRightOutline, RotateLeftOutline } from '@ant-design/icons-angular/icons'; +import { AimOutline, RotateRightOutline, RotateLeftOutline, PlayCircleOutline } from '@ant-design/icons-angular/icons'; import { IconDefinition } from '@ant-design/icons-angular'; +import { GraphMatrixNodeComponent } from './node/matrix-node.component'; -const icons: IconDefinition[] = [AimOutline, RotateRightOutline, RotateLeftOutline]; +const icons: IconDefinition[] = [AimOutline, RotateRightOutline, RotateLeftOutline, PlayCircleOutline]; @NgModule({ declarations: [ GraphForkJoinNodeComponent, - GraphGateNodeComponent, GraphJobNodeComponent, + GraphMatrixNodeComponent, WorkflowV2JobsGraphComponent, WorkflowV2StagesGraphComponent ], @@ -31,8 +31,8 @@ const icons: IconDefinition[] = [AimOutline, RotateRightOutline, RotateLeftOutli ], exports: [ GraphForkJoinNodeComponent, - GraphGateNodeComponent, GraphJobNodeComponent, + GraphMatrixNodeComponent, WorkflowV2JobsGraphComponent, WorkflowV2StagesGraphComponent ] diff --git a/ui/libs/workflow-graph/src/public-api.ts b/ui/libs/workflow-graph/src/public-api.ts index 1123e971c8..661e80abf2 100644 --- a/ui/libs/workflow-graph/src/public-api.ts +++ b/ui/libs/workflow-graph/src/public-api.ts @@ -5,6 +5,6 @@ export * from './lib/stages-graph.component'; export * from './lib/jobs-graph.component'; export * from './lib/node/fork-join-node.components'; -export * from './lib/node/gate-node.component'; export * from './lib/node/job-node.component'; +export * from './lib/node/matrix-node.component'; export * from './lib/workflow-graph.module'; diff --git a/ui/src/app/model/v2.workflow.run.model.ts b/ui/src/app/model/v2.workflow.run.model.ts deleted file mode 100644 index 111fbf4b01..0000000000 --- a/ui/src/app/model/v2.workflow.run.model.ts +++ /dev/null @@ -1,111 +0,0 @@ -export class V2WorkflowRun { - id: string; - project_key: string; - vcs_server_id: string; - repository_id: string; - repository: string; - workflow_name: string; - workflow_sha: string; - workflow_ref: string; - status: string; - run_number: number; - run_attempt: number; - started: string; - last_modified: string; - to_delete: boolean; - workflow_data: WorkflowData; - user_id: string; - username: string; - contexts: any; - event: WorkflowEvent; - job_events: V2WorkflowRunJobEvent[]; - vcs_server: string; -} - -export class V2WorkflowRunJobEvent { - username: string; - job_id: string; - inputs: { [key: string]: any }; - run_attempt: number; -} - -export class WorkflowEvent { - workflow_update: { ref: string, workflow_updated: string }; - model_update: { ref: string, workflow_updated: string }; - git: { event_name: string, payload: string, ref: string, sha: string }; -} - -export class WorkflowData { - workflow: any; - worker_models: { [key: string]: {} }; - actions: { [key: string]: {} }; -} - -export class Gate { - inputs: { [key: string]: GateInput }; - reviewers: GateReviewers; -} - -export class GateInput { - type: string; - default: any; - values: string; -} - -export class GateReviewers { - groups: string[]; - users: string[]; -} - -export class V2WorkflowRunJob { - id: string; - job_id: string; - workflow_run_id: string; - project_key: string; - workflow_name: string; - run_number: number - run_attempt: number; - status: string; - queued: string; - scheduled: string; - started: string; - ended: string; - job: {}; - worker_id: string; - worker_name: string; - hatchery_name: string; - outputs: { [key: string]: string }; - steps_status: { [key: string]: StepStatus }; - user_id: string; - username: string; - region: string; - model_type: string; - matrix: { [key: string]: string }; -} - -export class StepStatus { - conclusion: string; - outcome: string; - outputs: { [key: string]: string }; - started: string; - ended: string; -} - -export class WorkflowRunInfo { - id: string; - workflow_run_id: string; - issued_at: string; - level: string; - message: string; -} - -export class WorkflowRunResult { - id: string; - type: string; - detail: WorkflowRunResultDetail; -} - -export class WorkflowRunResultDetail { - data: any; - type: string; -} diff --git a/ui/src/app/service/sidebar/sidebar.service.ts b/ui/src/app/service/sidebar/sidebar.service.ts index b4b8cdd666..e5ebda5735 100644 --- a/ui/src/app/service/sidebar/sidebar.service.ts +++ b/ui/src/app/service/sidebar/sidebar.service.ts @@ -1,9 +1,7 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { FlatNodeItem } from 'app/shared/tree/tree.component'; -import {V2WorkflowRun} from "../../model/v2.workflow.run.model"; -import {V2WorkflowRunService} from "../workflowv2/workflow.service"; +import { V2WorkflowRun } from '../../../../libs/workflow-graph/src/lib/v2.workflow.run.model'; export class SidebarEvent { nodeID: string diff --git a/ui/src/app/service/workflowv2/workflow.service.ts b/ui/src/app/service/workflowv2/workflow.service.ts index eadd9371fe..c82c9bea19 100644 --- a/ui/src/app/service/workflowv2/workflow.service.ts +++ b/ui/src/app/service/workflowv2/workflow.service.ts @@ -1,8 +1,8 @@ import { Injectable } from "@angular/core"; import { HttpClient, HttpParams } from "@angular/common/http"; import { Observable } from "rxjs"; -import { V2WorkflowRun, V2WorkflowRunJob, WorkflowRunInfo, WorkflowRunResult } from "../../model/v2.workflow.run.model"; import { CDNLogLinks } from "../../model/pipeline.model"; +import { V2WorkflowRun, V2WorkflowRunJob, WorkflowRunInfo, WorkflowRunResult } from "../../../../libs/workflow-graph/src/lib/v2.workflow.run.model"; @Injectable() export class V2WorkflowRunService { @@ -14,6 +14,14 @@ export class V2WorkflowRunService { return this._http.get(`/v2/project/${projKey}/run/${runIdentifier}`); } + restart(projKey: string, runIdentifier: string): Observable { + return this._http.put(`/v2/project/${projKey}/run/${runIdentifier}/restart`, null); + } + + stop(projKey: string, runIdentifier: string) { + return this._http.post(`/v2/project/${projKey}/run/${runIdentifier}/stop`, null); + } + getJobs(r: V2WorkflowRun, attempt: number = null): Observable> { let params = new HttpParams(); if (attempt) { diff --git a/ui/src/app/shared/pipes/duration.pipe.ts b/ui/src/app/shared/pipes/duration.pipe.ts index cce8cf6c9e..a6b00d172a 100644 --- a/ui/src/app/shared/pipes/duration.pipe.ts +++ b/ui/src/app/shared/pipes/duration.pipe.ts @@ -1,5 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { DurationService } from '../duration/duration.service'; +import { DurationService } from '../../../../libs/workflow-graph/src/lib/duration/duration.service'; @Pipe({ name: 'durationMs' diff --git a/ui/src/app/shared/workflow/sidebar/run-list/workflow.sidebar.run.component.ts b/ui/src/app/shared/workflow/sidebar/run-list/workflow.sidebar.run.component.ts index 9266da5c2e..d249bf38bb 100644 --- a/ui/src/app/shared/workflow/sidebar/run-list/workflow.sidebar.run.component.ts +++ b/ui/src/app/shared/workflow/sidebar/run-list/workflow.sidebar.run.component.ts @@ -15,13 +15,13 @@ import { Workflow } from 'app/model/workflow.model'; import { WorkflowRunSummary, WorkflowRunTags } from 'app/model/workflow.run.model'; import { WorkflowRunService } from 'app/service/workflow/run/workflow.run.service'; import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe'; -import { DurationService } from 'app/shared/duration/duration.service'; import { ToastService } from 'app/shared/toast/ToastService'; import { ProjectState } from 'app/store/project.state'; import { CleanWorkflowRun, ClearListRuns, SetWorkflowRuns } from 'app/store/workflow.action'; import { WorkflowState } from 'app/store/workflow.state'; import { Subscription } from 'rxjs'; import { finalize, first } from 'rxjs/operators'; +import { DurationService } from '../../../../../../libs/workflow-graph/src/lib/duration/duration.service'; const limitWorkflowRun = 30; diff --git a/ui/src/app/views/projectv2/run-list/run-list.component.ts b/ui/src/app/views/projectv2/run-list/run-list.component.ts index fb55802bed..9450ce93ad 100644 --- a/ui/src/app/views/projectv2/run-list/run-list.component.ts +++ b/ui/src/app/views/projectv2/run-list/run-list.component.ts @@ -2,7 +2,6 @@ import { HttpClient, HttpHeaders } from "@angular/common/http"; import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from "@angular/core"; import { NzMessageService } from "ng-zorro-antd/message"; import { lastValueFrom, map } from "rxjs"; -import { V2WorkflowRun } from "app/model/v2.workflow.run.model"; import { Project } from "app/model/project.model"; import { Store } from "@ngxs/store"; import { ProjectState } from "app/store/project.state"; @@ -11,6 +10,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import * as actionPreferences from 'app/store/preferences.action'; import { PreferencesState } from "app/store/preferences.state"; import { NzPopconfirmDirective } from "ng-zorro-antd/popconfirm"; +import { V2WorkflowRun } from "../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model"; export class WorkflowRunFilter { key: string; diff --git a/ui/src/app/views/projectv2/run/gate/gate.component.ts b/ui/src/app/views/projectv2/run/gate/gate.component.ts index 5e07a98770..958225ab27 100644 --- a/ui/src/app/views/projectv2/run/gate/gate.component.ts +++ b/ui/src/app/views/projectv2/run/gate/gate.component.ts @@ -1,9 +1,9 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { AutoUnsubscribe } from "app/shared/decorator/autoUnsubscribe"; import { finalize, first } from "rxjs/operators"; -import { Gate, V2WorkflowRun, V2WorkflowRunJobEvent } from "app/model/v2.workflow.run.model"; import { V2WorkflowRunService } from "app/service/workflowv2/workflow.service"; import { ToastService } from "app/shared/toast/ToastService"; +import { V2JobGate, V2WorkflowRun, V2WorkflowRunJobEvent } from "../../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model"; @Component({ selector: 'app-run-gate', @@ -18,7 +18,7 @@ export class RunGateComponent implements OnInit { @Input() gateNode: { gate, job }; @Output() onClose = new EventEmitter(); - currentGate: Gate; + currentGate: V2JobGate; jobEvent: V2WorkflowRunJobEvent; request: { [key: string]: any }; loading: boolean; @@ -30,7 +30,7 @@ export class RunGateComponent implements OnInit { ) { } ngOnInit(): void { - this.currentGate = this.run.workflow_data.workflow.gates[this.gateNode.gate]; + this.currentGate = this.run.workflow_data.workflow.gates[this.gateNode.gate]; this.request = {}; Object.keys(this.currentGate.inputs).forEach(k => { if (this.currentGate.inputs[k].default) { diff --git a/ui/src/app/views/projectv2/run/project.run.component.ts b/ui/src/app/views/projectv2/run/project.run.component.ts index d2f26b6e9d..6dfe0a99b5 100644 --- a/ui/src/app/views/projectv2/run/project.run.component.ts +++ b/ui/src/app/views/projectv2/run/project.run.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, ViewChild } from "@angular/core"; import { AutoUnsubscribe } from "app/shared/decorator/autoUnsubscribe"; import { from, interval, lastValueFrom, Subscription } from "rxjs"; -import { V2WorkflowRun, V2WorkflowRunJob, WorkflowRunInfo, WorkflowRunResult } from "app/model/v2.workflow.run.model"; import { dump } from "js-yaml"; import { V2WorkflowRunService } from "app/service/workflowv2/workflow.service"; import { PreferencesState } from "app/store/preferences.state"; @@ -15,6 +14,8 @@ import { NzMessageService } from "ng-zorro-antd/message"; import { WorkflowV2StagesGraphComponent } from "../../../../../libs/workflow-graph/src/public-api"; import { NavigationState } from "app/store/navigation.state"; import { NsAutoHeightTableDirective } from "app/shared/directives/ns-auto-height-table.directive"; +import { V2WorkflowRun, V2WorkflowRunJob, WorkflowRunInfo, WorkflowRunResult } from "../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model"; +import { GraphNode } from "../../../../../libs/workflow-graph/src/lib/graph.model"; @Component({ selector: 'app-projectv2-run', @@ -97,6 +98,7 @@ export class ProjectV2WorkflowRunComponent implements OnDestroy { const projectKey = this._route.snapshot.parent.params['key']; const runIdentifier = this._route.snapshot.params['runIdentifier']; + delete this.selectedItemType; delete this.selectedJobGate; delete this.selectedJobRun; delete this.selectedJobRunInfos; @@ -108,14 +110,6 @@ export class ProjectV2WorkflowRunComponent implements OnDestroy { try { this.workflowRun = await lastValueFrom(this._workflowService.getRun(projectKey, runIdentifier)); this.selectedRunAttempt = this.workflowRun.run_attempt; - this.workflowRunInfos = await lastValueFrom(this._workflowService.getRunInfos(this.workflowRun)); - if (!!this.workflowRunInfos.find(i => i.level === 'warning' || i.level === 'error')) { - this.tabs = [{ - title: 'Problems', - key: 'problems', - default: true - }, ...this.defaultTabs]; - } } catch (e) { this._messageService.error(`Unable to get workflow run: ${e?.error?.error}`, { nzDuration: 2000 }); } @@ -138,16 +132,30 @@ export class ProjectV2WorkflowRunComponent implements OnDestroy { } catch (e) { this._messageService.error(`Unable to get results: ${e?.error?.error}`, { nzDuration: 2000 }); } + try { + this.workflowRunInfos = await lastValueFrom(this._workflowService.getRunInfos(this.workflowRun)); + if (!!this.workflowRunInfos.find(i => i.level === 'warning' || i.level === 'error')) { + this.tabs = [{ + title: 'Problems', + key: 'problems', + default: true + }, ...this.defaultTabs]; + } + } catch (e) { + this._messageService.error(`Unable to get run infos: ${e?.error?.error}`, { nzDuration: 2000 }); + } + + await this.refreshPanel(); - this.refreshPanel(); + const jobsNotTerminated = this.jobs.filter(j => !PipelineStatus.isDone(j.status)).length > 0; - if (!PipelineStatus.isDone(this.workflowRun.status) && !this.pollSubs) { + if (jobsNotTerminated && !this.pollSubs) { this.pollSubs = interval(5000) .pipe(concatMap(_ => from(this.loadJobsAndResults()))) .subscribe(); } - if (PipelineStatus.isDone(this.workflowRun.status) && this.pollSubs) { + if (!jobsNotTerminated && this.pollSubs) { this.pollSubs.unsubscribe(); } @@ -227,7 +235,8 @@ export class ProjectV2WorkflowRunComponent implements OnDestroy { this.selectedHookName = data; break; case 'gate': - this.selectedJobGate = { gate: data.gateName, job: data.gateChild }; + const node = (data); + this.selectedJobGate = { gate: node.job.gate, job: node.name }; break; case 'result': this.selectedRunResult = data; @@ -291,10 +300,24 @@ export class ProjectV2WorkflowRunComponent implements OnDestroy { } } - changeRunAttempt(value: number): void { + async changeRunAttempt(value: number) { this.selectedRunAttempt = value; this._cd.markForCheck(); - this.loadJobsAndResults(); + await this.loadJobsAndResults(); + } + + async clickRestartJobs() { + const projectKey = this._route.snapshot.parent.params['key']; + const runIdentifier = this._route.snapshot.params['runIdentifier']; + await lastValueFrom(this._workflowService.restart(projectKey, runIdentifier)); + await this.load(); + } + + async clickStopRun() { + const projectKey = this._route.snapshot.parent.params['key']; + const runIdentifier = this._route.snapshot.params['runIdentifier']; + await lastValueFrom(this._workflowService.stop(projectKey, runIdentifier)); + await this.load(); } } \ No newline at end of file diff --git a/ui/src/app/views/projectv2/run/project.run.html b/ui/src/app/views/projectv2/run/project.run.html index c85e96d49a..518900d6c8 100644 --- a/ui/src/app/views/projectv2/run/project.run.html +++ b/ui/src/app/views/projectv2/run/project.run.html @@ -1,29 +1,40 @@ -
- - - {{workflowRun.vcs_server}}/{{workflowRun.repository}}/{{workflowRun.workflow_name}} - #{{workflowRun.run_number}} - - - - - - - - Commit {{workflowRun.contexts.git.sha?.substring(0,8)}} by {{workflowRun.contexts.git.username}} on - repository - {{workflowRun.contexts.git.server}}/{{workflowRun.contexts.git.repository}} - - - +
- + + +
+ + + {{workflowRun.vcs_server}}/{{workflowRun.repository}}/{{workflowRun.workflow_name}} + #{{workflowRun.run_number}} + + + + + + + + Commit {{workflowRun.contexts.git.sha?.substring(0,8)}} by {{workflowRun.contexts.git.username}} + on + repository + {{workflowRun.contexts.git.server}}/{{workflowRun.contexts.git.repository}} + + + +
+ + +
+ +
diff --git a/ui/src/app/views/projectv2/run/project.run.scss b/ui/src/app/views/projectv2/run/project.run.scss index f6951deee3..e68830ba63 100644 --- a/ui/src/app/views/projectv2/run/project.run.scss +++ b/ui/src/app/views/projectv2/run/project.run.scss @@ -6,23 +6,7 @@ height: 100%; } -nz-page-header-title { - font-size: 16px; - display: flex; - flex-direction: row; - align-items: center; - - button, - nz-select { - margin-left: 5px; - } -} - -nz-page-header-content { - padding: 0 0 0 32px; -} - -.graph { +.content { flex: 1; display: flex; position: relative; @@ -30,9 +14,87 @@ nz-page-header-content { height: 100%; overflow: hidden; - .title { - position: absolute; - z-index: 1000; + .graph { + flex: 1; + position: relative; + display: flex; + flex-direction: column-reverse; + align-items: center; + width: 100%; + overflow: hidden; + + .title { + position: absolute; + top: 0; + left: 0; + z-index: 1000; + background-color: white; + border-bottom-right-radius: 40px; + + :host-context(.night) & { + background-color: #141414; + } + } + + .controls { + z-index: 1000; + height: 40px; + border: 2px solid $polar_grey_3; + border-radius: 10px; + margin-bottom: 20px; + padding: 10px; + display: flex; + flex-direction: row; + align-items: center; + font-size: 20px; + background-color: white; + + [nz-icon] { + color: $polar_grey_1; + cursor: pointer; + + :host-context(.night) & { + color: $darkTheme_grey_6; + } + + &:hover { + color: grey !important; + } + } + + :host-context(.night) & { + border-color: $darkTheme_grey_5; + background-color: $darkTheme_grey_1; + } + + [nz-icon]:not(:last-child) { + margin-right: 10px; + } + } + + app-stages-graph { + position: absolute; + top: 0; + bottom: 0; + width: 100%; + height: 100%s; + } + + nz-page-header-title { + font-size: 16px; + display: flex; + flex-direction: row; + align-items: center; + + button, + nz-select { + margin-left: 5px; + } + } + + nz-page-header-content { + padding: 0 0 0 32px; + } } &.disableSelection { @@ -72,30 +134,30 @@ nz-page-header-content { color: $darkTheme_blue; } } -} - -.bottom-panel { - height: 100%; - overflow: hidden; - display: flex; - flex-direction: column; - .infos { + .bottom-panel { height: 100%; - overflow-y: auto; - padding-left: 10px; - list-style: none; - - .rightFloat { - float: right; - } + overflow: hidden; + display: flex; + flex-direction: column; + + .infos { + height: 100%; + overflow-y: auto; + padding-left: 10px; + list-style: none; + + .rightFloat { + float: right; + } - .content { - display: inline; + .content { + display: inline; + } } } -} -.result { - cursor: pointer; + .result { + cursor: pointer; + } } \ No newline at end of file diff --git a/ui/src/app/views/projectv2/run/run-hook.component.ts b/ui/src/app/views/projectv2/run/run-hook.component.ts index 753e6b0416..0ed6cbada5 100644 --- a/ui/src/app/views/projectv2/run/run-hook.component.ts +++ b/ui/src/app/views/projectv2/run/run-hook.component.ts @@ -1,11 +1,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild } from "@angular/core"; import { Store } from "@ngxs/store"; -import { V2WorkflowRun } from "app/model/v2.workflow.run.model"; import { AutoUnsubscribe } from "app/shared/decorator/autoUnsubscribe"; import { Tab } from "app/shared/tabs/tabs.component"; import { PreferencesState } from "app/store/preferences.state"; import { EditorOptions, NzCodeEditorComponent } from "ng-zorro-antd/code-editor"; import { Subscription } from "rxjs"; +import { V2WorkflowRun } from "../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model"; @Component({ selector: 'app-run-hook', diff --git a/ui/src/app/views/projectv2/run/run-job-logs.component.ts b/ui/src/app/views/projectv2/run/run-job-logs.component.ts index 24be9a5330..8f79142668 100644 --- a/ui/src/app/views/projectv2/run/run-job-logs.component.ts +++ b/ui/src/app/views/projectv2/run/run-job-logs.component.ts @@ -9,14 +9,13 @@ import { Output } from "@angular/core"; import { AutoUnsubscribe } from "app/shared/decorator/autoUnsubscribe"; -import { V2WorkflowRun, V2WorkflowRunJob } from "app/model/v2.workflow.run.model"; import { LogBlock, ScrollTarget } from "../../workflow/run/node/pipeline/workflow-run-job/workflow-run-job.component"; -import moment from "moment"; -import { DurationService } from "app/shared/duration/duration.service"; import { CDNLine, CDNLogLinkData, CDNLogLinks, PipelineStatus } from "app/model/pipeline.model"; import { V2WorkflowRunService } from "app/service/workflowv2/workflow.service"; import { WorkflowService } from "app/service/workflow/workflow.service"; import { lastValueFrom } from "rxjs"; +import { V2WorkflowRun, V2WorkflowRunJob } from "../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model"; +import { DurationService } from "../../../../../libs/workflow-graph/src/lib/duration/duration.service"; @Component({ selector: 'app-run-job-logs', @@ -169,9 +168,9 @@ export class RunJobLogsComponent implements OnDestroy { if (!stepStatus) { return; } - s.startDate = moment(stepStatus.started); + s.startDate = new Date(stepStatus.started); if (stepStatus.ended && stepStatus.ended !== '0001-01-01T00:00:00Z') { - s.duration = DurationService.duration(s.startDate.toDate(), moment(stepStatus.ended).toDate()); + s.duration = DurationService.duration(s.startDate, new Date(stepStatus.ended)); } } }); @@ -194,10 +193,6 @@ export class RunJobLogsComponent implements OnDestroy { return element ? element.number : null; } - formatDuration(fromM: moment.Moment, to?: moment.Moment): string { - return DurationService.duration(fromM.toDate(), to ? to.toDate() : moment().toDate()); - } - clickScroll(target: ScrollTarget): void { this.onScroll.emit(target); } diff --git a/ui/src/app/views/projectv2/run/run-job-logs.html b/ui/src/app/views/projectv2/run/run-job-logs.html index 3d0c4942a1..7ca75ed969 100644 --- a/ui/src/app/views/projectv2/run/run-job-logs.html +++ b/ui/src/app/views/projectv2/run/run-job-logs.html @@ -14,7 +14,7 @@
{{logBlock.name}}
- {{logBlock.startDate | amTimeAgo: true : formatDuration }} + {{logBlock.startDate | date: 'long' }}
{{logBlock.duration}}
Optional
diff --git a/ui/src/app/views/projectv2/run/run-job.component.ts b/ui/src/app/views/projectv2/run/run-job.component.ts index 8b25a21c41..db23393b11 100644 --- a/ui/src/app/views/projectv2/run/run-job.component.ts +++ b/ui/src/app/views/projectv2/run/run-job.component.ts @@ -14,11 +14,11 @@ import { CDNLine, CDNStreamFilter, PipelineStatus } from 'app/model/pipeline.mod import { WorkflowNodeJobRun } from 'app/model/workflow.run.model'; import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe'; import { Tab } from 'app/shared/tabs/tabs.component'; -import { V2WorkflowRun, V2WorkflowRunJob, WorkflowRunInfo } from "app/model/v2.workflow.run.model"; import { RunJobLogsComponent } from "./run-job-logs.component"; import { WebSocketSubject, webSocket } from 'rxjs/webSocket'; import { Subscription, delay, retryWhen } from 'rxjs'; import { Router } from '@angular/router'; +import { V2WorkflowRun, V2WorkflowRunJob, WorkflowRunInfo } from '../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model'; @Component({ @@ -31,7 +31,7 @@ import { Router } from '@angular/router'; export class RunJobComponent implements OnChanges, OnDestroy { @ViewChild('runJobLogs') runJobLogs: RunJobLogsComponent; - @Input() workflowRun: V2WorkflowRun + @Input() workflowRun: V2WorkflowRun; @Input() jobRun: V2WorkflowRunJob; @Input() jobRunInfos: Array; @Output() onClose = new EventEmitter(); diff --git a/ui/src/app/views/projectv2/run/run-result-tests/run-result-tests.component.ts b/ui/src/app/views/projectv2/run/run-result-tests/run-result-tests.component.ts index e4d5eee49b..2a26f41bf1 100644 --- a/ui/src/app/views/projectv2/run/run-result-tests/run-result-tests.component.ts +++ b/ui/src/app/views/projectv2/run/run-result-tests/run-result-tests.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnInit } from '@angular/core'; import { TestCase, Tests } from 'app/model/pipeline.model'; -import { WorkflowRunResultDetail } from 'app/model/v2.workflow.run.model'; import { NzFormatEmitEvent, NzTreeNodeOptions } from 'ng-zorro-antd/tree'; +import { WorkflowRunResultDetail } from '../../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model'; @Component({ selector: 'app-run-result-tests', @@ -55,7 +55,7 @@ export class RunResultTestsComponent implements OnInit, OnChanges { } initTestTree(): void { - const nodes = this.tests.test_suites.map(ts => { + const nodes = (this.tests.test_suites ?? []).map(ts => { let node = { title: ts.name, key: ts.name, diff --git a/ui/src/app/views/projectv2/run/run-result.component.ts b/ui/src/app/views/projectv2/run/run-result.component.ts index 033d4170ca..c784b02d54 100644 --- a/ui/src/app/views/projectv2/run/run-result.component.ts +++ b/ui/src/app/views/projectv2/run/run-result.component.ts @@ -1,11 +1,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild } from "@angular/core"; import { Store } from "@ngxs/store"; -import { V2WorkflowRun, WorkflowRunResult } from "app/model/v2.workflow.run.model"; import { AutoUnsubscribe } from "app/shared/decorator/autoUnsubscribe"; import { Tab } from "app/shared/tabs/tabs.component"; import { PreferencesState } from "app/store/preferences.state"; import { EditorOptions, NzCodeEditorComponent } from "ng-zorro-antd/code-editor"; import { Subscription } from "rxjs"; +import { WorkflowRunResult } from "../../../../../libs/workflow-graph/src/lib/v2.workflow.run.model"; @Component({ selector: 'app-run-result', diff --git a/ui/src/app/views/projectv2/vcs/repository/workflow/show/entity/project.workflow.entity.html b/ui/src/app/views/projectv2/vcs/repository/workflow/show/entity/project.workflow.entity.html index 765338b77d..f19f0d45b4 100644 --- a/ui/src/app/views/projectv2/vcs/repository/workflow/show/entity/project.workflow.entity.html +++ b/ui/src/app/views/projectv2/vcs/repository/workflow/show/entity/project.workflow.entity.html @@ -1,25 +1,20 @@ -
- - - +
+ [entityType]="actionEntity" [schema]="jobFlatSchema" [data]="jobForm" + (dataChange)="onFormChange($event)">
- - - + (onGrabbingEnd)="panelEndResize($event)"> + +
  • @@ -27,14 +22,11 @@
-
- + + (ngModelChange)="onEditorChange($event)" (nzEditorInitialized)="onEditorInit($event)">
-
- + \ No newline at end of file diff --git a/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts b/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts index 53bcb6645c..8c4ad09a53 100644 --- a/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts +++ b/ui/src/app/views/settings/user/consumer-details-modal/consumer-details-modal.component.ts @@ -14,8 +14,7 @@ import { UserService } from 'app/service/user/user.service'; import { Column, ColumnType, Filter } from 'app/shared/table/data-table.component'; import { ToastService } from 'app/shared/toast/ToastService'; import { AuthenticationState } from 'app/store/authentication.state'; -import moment from 'moment'; -import {NZ_MODAL_DATA, NzModalRef} from 'ng-zorro-antd/modal'; +import { NZ_MODAL_DATA, NzModalRef } from 'ng-zorro-antd/modal'; interface IModalData { user: AuthentifiedUser; @@ -52,7 +51,7 @@ export class ConsumerDetailsModalComponent implements OnInit { columnsConsumers: Array>; filterChildren: Filter; selectedChildDetails: AuthConsumer; - menuItems: Map; + menuItems: Map; selectedItem: string; columnsSessions: Array>; filterSessions: Filter; @@ -78,11 +77,11 @@ export class ConsumerDetailsModalComponent implements OnInit { this.filterChildren = f => { const lowerFilter = f.toLowerCase(); return (c: AuthConsumer) => c.name.toLowerCase().indexOf(lowerFilter) !== -1 || - c.description.toLowerCase().indexOf(lowerFilter) !== -1 || - c.id.toLowerCase().indexOf(lowerFilter) !== -1 || - c.auth_consumer_user.scope_details.map(s => s.scope).join(' ').toLowerCase().indexOf(lowerFilter) !== -1 || - (c.auth_consumer_user.groups && c.auth_consumer_user.groups.map(g => g.name).join(' ').toLowerCase().indexOf(lowerFilter) !== -1) || - (!c.auth_consumer_user.groups && lowerFilter === '*'); + c.description.toLowerCase().indexOf(lowerFilter) !== -1 || + c.id.toLowerCase().indexOf(lowerFilter) !== -1 || + c.auth_consumer_user.scope_details.map(s => s.scope).join(' ').toLowerCase().indexOf(lowerFilter) !== -1 || + (c.auth_consumer_user.groups && c.auth_consumer_user.groups.map(g => g.name).join(' ').toLowerCase().indexOf(lowerFilter) !== -1) || + (!c.auth_consumer_user.groups && lowerFilter === '*'); }; this.columnsConsumers = [ @@ -115,20 +114,20 @@ export class ConsumerDetailsModalComponent implements OnInit { name: 'Action', class: 'rightAlign', selector: (c: AuthConsumer) => ({ - title: 'Details', - buttonDanger: false, - click: () => this.clickConsumerDetails(c) - }) + title: 'Details', + buttonDanger: false, + click: () => this.clickConsumerDetails(c) + }) } ]; this.filterSessions = f => { const lowerFilter = f.toLowerCase(); return (s: AuthSession) => s.consumer.name.toLowerCase().indexOf(lowerFilter) !== -1 || - s.id.toLowerCase().indexOf(lowerFilter) !== -1 || - s.consumer_id.toLowerCase().indexOf(lowerFilter) !== -1 || - s.created.toLowerCase().indexOf(lowerFilter) !== -1 || - s.expire_at.toLowerCase().indexOf(lowerFilter) !== -1; + s.id.toLowerCase().indexOf(lowerFilter) !== -1 || + s.consumer_id.toLowerCase().indexOf(lowerFilter) !== -1 || + s.created.toLowerCase().indexOf(lowerFilter) !== -1 || + s.expire_at.toLowerCase().indexOf(lowerFilter) !== -1; }; this.columnsSessions = [ @@ -164,7 +163,7 @@ export class ConsumerDetailsModalComponent implements OnInit { >{ type: ColumnType.DATE, name: 'Issued at', - selector: (s: AuthConsumerValidityPeriod) => moment(s.issued_at).format() + selector: (s: AuthConsumerValidityPeriod) => new Date(s.issued_at) }, >{ type: ColumnType.TEXT_LABELS, @@ -172,7 +171,7 @@ export class ConsumerDetailsModalComponent implements OnInit { selector: (s: AuthConsumerValidityPeriod) => { let labels = []; if (s.duration === 0) { - return {value: '-', labels}; + return { value: '-', labels }; } let ms = (s.duration / 1000000); @@ -275,7 +274,7 @@ export class ConsumerDetailsModalComponent implements OnInit { clickClose(): void { if (this.regenConsumerSigninToken) { - this._modal.close ({ + this._modal.close({ type: CloseEventType.REGEN, payload: this.consumer.id }); diff --git a/ui/src/app/views/workflow/run/node/pipeline/node.pipeline.component.ts b/ui/src/app/views/workflow/run/node/pipeline/node.pipeline.component.ts index 48af78ec2b..f0bedd6aba 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/node.pipeline.component.ts +++ b/ui/src/app/views/workflow/run/node/pipeline/node.pipeline.component.ts @@ -7,7 +7,6 @@ import { Stage } from 'app/model/stage.model'; import { WorkflowNodeJobRun, WorkflowNodeRun } from 'app/model/workflow.run.model'; import { WorkflowService } from 'app/service/workflow/workflow.service'; import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe'; -import { DurationService } from 'app/shared/duration/duration.service'; import { ProjectState } from 'app/store/project.state'; import { SelectWorkflowNodeRunJob } from 'app/store/workflow.action'; import { WorkflowState, WorkflowStateModel } from 'app/store/workflow.state'; @@ -16,6 +15,7 @@ import { Subscription } from 'rxjs'; import { delay, retryWhen } from 'rxjs/operators'; import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; import { ScrollTarget, WorkflowRunJobComponent } from './workflow-run-job/workflow-run-job.component'; +import { DurationService } from '../../../../../../../libs/workflow-graph/src/lib/duration/duration.service'; @Component({ selector: 'app-node-run-pipeline', diff --git a/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.component.ts b/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.component.ts index 19404ae6f8..7aed23d0e8 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.component.ts +++ b/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.component.ts @@ -5,12 +5,12 @@ import { CDNLine, CDNLogLink, PipelineStatus, SpawnInfo } from 'app/model/pipeli import { WorkflowNodeJobRun } from 'app/model/workflow.run.model'; import { WorkflowService } from 'app/service/workflow/workflow.service'; import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe'; -import { DurationService } from 'app/shared/duration/duration.service'; import moment from 'moment'; import { from, interval, Subject, Subscription } from 'rxjs'; import { concatMap } from 'rxjs/operators'; import { WorkflowRunJobVariableComponent } from '../variables/job.variables.component'; import { NzModalService } from 'ng-zorro-antd/modal'; +import { DurationService } from '../../../../../../../../libs/workflow-graph/src/lib/duration/duration.service'; export enum DisplayMode { ANSI = 'ansi', @@ -35,7 +35,7 @@ export class LogBlock { firstDisplayedLineNumber: number; totalLinesCount: number; link: CDNLogLink; - startDate: moment.Moment; + startDate: Date; duration: string; optional: boolean; disabled: boolean; @@ -204,7 +204,7 @@ export class WorkflowRunJobComponent implements OnInit, OnDestroy { selectTab(i: number): void { this.currentTabIndex = i; this._cd.markForCheck(); - this.loadDataForCurrentTab().then(() => {}); + this.loadDataForCurrentTab().then(() => { }); } clickMode(mode: DisplayMode): void { @@ -310,17 +310,13 @@ export class WorkflowRunJobComponent implements OnInit, OnDestroy { if (PipelineStatus.neverRun(stepStatus.status) || !stepStatus.start) { continue; } - this.steps[i].startDate = moment(stepStatus.start); + this.steps[i].startDate = new Date(stepStatus.start); if (stepStatus.done && stepStatus.done !== '0001-01-01T00:00:00Z') { - this.steps[i].duration = DurationService.duration(this.steps[i].startDate.toDate(), moment(stepStatus.done).toDate()); + this.steps[i].duration = DurationService.duration(this.steps[i].startDate, new Date(stepStatus.done)); } } } - formatDuration(fromM: moment.Moment, to?: moment.Moment): string { - return DurationService.duration(fromM.toDate(), to ? to.toDate() : moment().toDate()); - } - async clickExpandStepDown(index: number, event: MouseEvent) { let step = this.steps[index]; @@ -330,7 +326,7 @@ export class WorkflowRunJobComponent implements OnInit, OnDestroy { } let result = await this._workflowService.getLogLines(step.link, - {offset: `${step.lines[step.lines.length - 1].number + 1}`, limit: limit} + { offset: `${step.lines[step.lines.length - 1].number + 1}`, limit: limit } ).toPromise(); this.steps[index].totalLinesCount = result.totalCount; this.steps[index].lines = step.lines.concat(result.lines.filter(l => !step.endLines.find(line => line.number === l.number))); @@ -375,13 +371,13 @@ export class WorkflowRunJobComponent implements OnInit, OnDestroy { receiveLogs(l: CDNLine): void { if (this.steps) { this.steps.forEach(v => { - if (v?.link?.api_ref === l.api_ref_hash) { - if (!v.lines.find(line => line.number === l.number) - && !v.endLines.find(line => line.number === l.number)) { - v.endLines.push(l); - v.totalLinesCount++; - } - } + if (v?.link?.api_ref === l.api_ref_hash) { + if (!v.lines.find(line => line.number === l.number) + && !v.endLines.find(line => line.number === l.number)) { + v.endLines.push(l); + v.totalLinesCount++; + } + } }); } if (this.services) { diff --git a/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.html b/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.html index 800a1d45b2..8331d5c3ae 100644 --- a/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.html +++ b/ui/src/app/views/workflow/run/node/pipeline/workflow-run-job/workflow-run-job.html @@ -27,7 +27,7 @@
{{step.name}}
- {{step.startDate | amTimeAgo: true : formatDuration }} + {{step.startDate | durationMs }}
{{step.duration}}
Optional
diff --git a/ui/src/app/views/workflow/run/node/summary/run.summary.component.ts b/ui/src/app/views/workflow/run/node/summary/run.summary.component.ts index 86189bf364..7d15fcda90 100644 --- a/ui/src/app/views/workflow/run/node/summary/run.summary.component.ts +++ b/ui/src/app/views/workflow/run/node/summary/run.summary.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni import { TranslateService } from '@ngx-translate/core'; import { Store } from '@ngxs/store'; import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe'; -import { DurationService } from 'app/shared/duration/duration.service'; import { ProjectState } from 'app/store/project.state'; import { WorkflowState, WorkflowStateModel } from 'app/store/workflow.state'; import { Subscription } from 'rxjs'; @@ -14,6 +13,7 @@ import { WorkflowRunService } from 'app/service/workflow/run/workflow.run.servic import { ToastService } from 'app/shared/toast/ToastService'; import { WorkflowNodeRunParamComponent } from 'app/shared/workflow/node/run/node.run.param.component'; import { NzModalService } from 'ng-zorro-antd/modal'; +import { DurationService } from '../../../../../../../libs/workflow-graph/src/lib/duration/duration.service'; @Component({ selector: 'app-workflow-node-run-summary', diff --git a/ui/src/app/views/workflowv3/graph/workflowv3-stages-graph.component.ts b/ui/src/app/views/workflowv3/graph/workflowv3-stages-graph.component.ts index 8fc9f8947a..ee49e05e74 100644 --- a/ui/src/app/views/workflowv3/graph/workflowv3-stages-graph.component.ts +++ b/ui/src/app/views/workflowv3/graph/workflowv3-stages-graph.component.ts @@ -87,13 +87,6 @@ export class WorkflowV3StagesGraphComponent implements AfterViewInit, OnDestroy private _cd: ChangeDetectorRef ) { } - static isJobsGraph = (component: WorkflowV3JobsGraphOrNodeComponent): component is WorkflowV3JobsGraphComponent => { - if ((component as WorkflowV3JobsGraphComponent).direction) { - return true; - } - return false; - }; - ngOnDestroy(): void { } // Should be set to use @AutoUnsubscribe with AOT ngAfterViewInit(): void {