From 4cedab52bcdcff201014ced7bf27b9b5512c1718 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Thu, 5 Mar 2026 21:53:27 +0100 Subject: [PATCH] updated task-execution with all methods --- .../layouts/tasks-executor/tasks-executor.ts | 12 +--- src/app/models/task-execution.ts | 37 ++++++++++- .../task-executions-call.base.ts | 15 ++++- .../task-executions-call.fake.ts | 62 ++++++++++++++++++- .../task-executions/task-executions-call.ts | 40 +++++++++++- .../task-executions/task-executions.ts | 42 ++++++++++++- .../tasks-executions-list.html | 14 ++--- .../tasks-executions-list.ts | 23 ++++--- 8 files changed, 214 insertions(+), 31 deletions(-) diff --git a/src/app/layouts/tasks-executor/tasks-executor.ts b/src/app/layouts/tasks-executor/tasks-executor.ts index 4889dae..2d697ef 100644 --- a/src/app/layouts/tasks-executor/tasks-executor.ts +++ b/src/app/layouts/tasks-executor/tasks-executor.ts @@ -1,8 +1,7 @@ import { Component, computed, effect, inject, signal } from '@angular/core'; -import { TaskExecution } from '@models/task-execution'; +import { normalizeExecutionStatus, TaskExecution } from '@models/task-execution'; import { TaskExecutionListItem, - TaskExecutionStatus, TasksExecutionsListComponent } from '@shared/tasks-executions-list/tasks-executions-list'; import { TaskExecutionViewerComponent } from '@shared/task-execution-viewer/task-execution-viewer'; @@ -24,7 +23,7 @@ export class TasksExecutor { id: execution.id, title: execution.name, flowName: execution.name, - status: this.toListStatus(execution.context.status), + status: normalizeExecutionStatus(execution.context.status), startedAt: this.formatDateTime(execution.creationTime), duration: this.formatDuration(execution.context.startTime ?? null, execution.context.endTime ?? null) })) @@ -53,13 +52,6 @@ export class TasksExecutor { this.selectedExecutionId.set(id); } - private toListStatus(status: string): TaskExecutionStatus { - if (status === 'COMPLETED') return 'COMPLETED'; - if (status === 'FAILED' || status === 'ERROR') return 'FAILED'; - if (status === 'RUNNING' || status === 'WAITING' || status === 'WAITING_FOR_INPUT') return 'RUNNING'; - return 'QUEUED'; - } - private formatDateTime(timestamp: number): string { const date = new Date(timestamp); const yyyy = date.getFullYear(); diff --git a/src/app/models/task-execution.ts b/src/app/models/task-execution.ts index d7f5fe7..799d5bd 100644 --- a/src/app/models/task-execution.ts +++ b/src/app/models/task-execution.ts @@ -1,6 +1,8 @@ import { FlowBlock, FlowPort } from './flow'; -export type ExecutionStatus = 'RUNNING' | 'COMPLETED' | 'ERROR' | 'FAILED' | 'WAITING_FOR_INPUT' | string; +export type TaskExecutionStatus = 'CREATED' | 'READY' | 'RUNNING' | 'WAITING' | 'SUCCESS' | 'ERROR'; +export type TaskExecutionStatusGroup = 'INIT' | 'RUNNING' | 'FINAL'; + export type StepStatus = 'WAITING_FOR_INPUT' | 'FAILED' | 'COMPLETED' | 'RUNNING' | string; export type TaskExecution = { @@ -18,7 +20,7 @@ export type TaskExecutionContext = { errors: Record; warnings: Record; steps: Record; - status: ExecutionStatus; + status: TaskExecutionStatus; waitingSteps: string[]; executionResult: Record; }; @@ -44,3 +46,34 @@ export type TaskExecutionStepOutput = { descriptor: FlowPort; connected: boolean; }; + +export function getExecutionStatusGroup(status: string | null | undefined): TaskExecutionStatusGroup | null { + const normalized = String(status ?? '').toUpperCase(); + if (!normalized) return null; + + if (normalized === 'CREATED' || normalized === 'READY') return 'INIT'; + if ( + normalized === 'RUNNING' || + normalized === 'WAITING' + ) { + return 'RUNNING'; + } + if (normalized === 'SUCCESS' || normalized === 'ERROR') { + return 'FINAL'; + } + + return null; +} + +export function normalizeExecutionStatus(status: string | null | undefined): TaskExecutionStatus { + const normalized = String(status ?? '').toUpperCase(); + if (normalized === 'CREATED') return 'CREATED'; + if (normalized === 'READY') return 'READY'; + if (normalized === 'RUNNING') return 'RUNNING'; + if (normalized === 'WAITING' || normalized === 'WAITING_FOR_INPUT' || normalized === 'WAITING_FOR_INTERACTION') { + return 'WAITING'; + } + if (normalized === 'SUCCESS' || normalized === 'COMPLETED') return 'SUCCESS'; + if (normalized === 'ERROR' || normalized === 'FAILED') return 'ERROR'; + return 'CREATED'; +} diff --git a/src/app/services/task-executions/task-executions-call.base.ts b/src/app/services/task-executions/task-executions-call.base.ts index 0b79769..2b120b2 100644 --- a/src/app/services/task-executions/task-executions-call.base.ts +++ b/src/app/services/task-executions/task-executions-call.base.ts @@ -3,5 +3,18 @@ import { Observable } from 'rxjs'; export abstract class TaskExecutionsCallServiceBase { abstract retrieveAllTaskExecutions(): Observable; + abstract createTaskExecution(flowId: string): Observable; + abstract startTaskExecution(executionId: string): Observable; + abstract prepareStringInput( + executionId: string, + nodeId: string, + inputName: string, + value: string + ): Observable; + abstract prepareFileInput( + executionId: string, + nodeId: string, + inputName: string, + file: File + ): Observable; } - diff --git a/src/app/services/task-executions/task-executions-call.fake.ts b/src/app/services/task-executions/task-executions-call.fake.ts index d6cf5b1..b7db2c8 100644 --- a/src/app/services/task-executions/task-executions-call.fake.ts +++ b/src/app/services/task-executions/task-executions-call.fake.ts @@ -447,5 +447,65 @@ export class TaskExecutionsCallServiceFake extends TaskExecutionsCallServiceBase override retrieveAllTaskExecutions(): Observable { return of(this.data); } -} + override createTaskExecution(flowId: string): Observable { + const execution: TaskExecution = { + id: crypto.randomUUID(), + name: flowId || 'Execution', + creationTime: Date.now(), + context: { + inputs: {}, + result: {}, + startTime: null, + endTime: null, + errors: {}, + warnings: {}, + steps: {}, + status: 'CREATED', + waitingSteps: [], + executionResult: {} + } + }; + this.data.unshift(execution); + return of(execution); + } + + override startTaskExecution(executionId: string): Observable { + const execution = this.findExecution(executionId); + execution.context.status = 'RUNNING'; + execution.context.startTime = execution.context.startTime ?? Date.now(); + return of(execution); + } + + override prepareStringInput( + executionId: string, + nodeId: string, + inputName: string, + value: string + ): Observable { + const execution = this.findExecution(executionId); + execution.context.inputs[`${nodeId}:${inputName}`] = value; + execution.context.status = execution.context.waitingSteps.length ? 'WAITING' : execution.context.status; + return of(execution); + } + + override prepareFileInput( + executionId: string, + nodeId: string, + inputName: string, + file: File + ): Observable { + const execution = this.findExecution(executionId); + execution.context.inputs[`${nodeId}:${inputName}`] = file?.name ?? ''; + execution.context.status = execution.context.waitingSteps.length ? 'WAITING' : execution.context.status; + return of(execution); + } + + private findExecution(executionId: string): TaskExecution { + const execution = this.data.find((item) => item.id === executionId); + if (!execution) { + throw new Error(`Execution with id ${executionId} not found`); + } + return execution; + } +} diff --git a/src/app/services/task-executions/task-executions-call.ts b/src/app/services/task-executions/task-executions-call.ts index 72a2206..7dec288 100644 --- a/src/app/services/task-executions/task-executions-call.ts +++ b/src/app/services/task-executions/task-executions-call.ts @@ -1,10 +1,46 @@ +import { HttpClient } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { environment } from '@environment'; import { TaskExecution } from '@models/task-execution'; import { Observable } from 'rxjs'; import { TaskExecutionsCallServiceBase } from './task-executions-call.base'; export class TaskExecutionsCallService extends TaskExecutionsCallServiceBase { + private readonly http = inject(HttpClient); + override retrieveAllTaskExecutions(): Observable { - throw new Error('Method not implemented.'); + return this.http.get(`${environment.apiUrl}/executions`); + } + + override createTaskExecution(flowId: string): Observable { + return this.http.post(`${environment.apiUrl}/executions`, flowId); + } + + override startTaskExecution(executionId: string): Observable { + return this.http.put(`${environment.apiUrl}/executions/${encodeURIComponent(executionId)}/start`, null); + } + + override prepareStringInput( + executionId: string, + nodeId: string, + inputName: string, + value: string + ): Observable { + const url = `${environment.apiUrl}/executions/${encodeURIComponent(executionId)}/node/${encodeURIComponent(nodeId)}/input/${encodeURIComponent(inputName)}/text`; + return this.http.put(url, value, { + headers: { 'Content-Type': 'text/plain' } + }); + } + + override prepareFileInput( + executionId: string, + nodeId: string, + inputName: string, + file: File + ): Observable { + const url = `${environment.apiUrl}/executions/${encodeURIComponent(executionId)}/node/${encodeURIComponent(nodeId)}/input/${encodeURIComponent(inputName)}/file`; + const formData = new FormData(); + formData.append('file', file); + return this.http.put(url, formData); } } - diff --git a/src/app/services/task-executions/task-executions.ts b/src/app/services/task-executions/task-executions.ts index 9658295..84224da 100644 --- a/src/app/services/task-executions/task-executions.ts +++ b/src/app/services/task-executions/task-executions.ts @@ -1,6 +1,7 @@ import { Injectable, signal } from '@angular/core'; import { environment } from '@environment'; import { TaskExecution } from '@models/task-execution'; +import { catchError, tap, throwError } from 'rxjs'; import { TaskExecutionsCallServiceBase } from './task-executions-call.base'; @Injectable({ @@ -24,5 +25,44 @@ export class TaskExecutionsService { this._taskExecutions.set(taskExecutions); }); } -} + createExecution(flowId: string) { + return this.taskExecutionsCallService.createTaskExecution(flowId).pipe( + tap(() => this.refresh()), + catchError((err) => { + console.error('Create execution failed', err); + return throwError(() => err); + }) + ); + } + + startExecution(executionId: string) { + return this.taskExecutionsCallService.startTaskExecution(executionId).pipe( + tap(() => this.refresh()), + catchError((err) => { + console.error('Start execution failed', err); + return throwError(() => err); + }) + ); + } + + prepareStringInput(executionId: string, nodeId: string, inputName: string, value: string) { + return this.taskExecutionsCallService.prepareStringInput(executionId, nodeId, inputName, value).pipe( + tap(() => this.refresh()), + catchError((err) => { + console.error('Prepare string input failed', err); + return throwError(() => err); + }) + ); + } + + prepareFileInput(executionId: string, nodeId: string, inputName: string, file: File) { + return this.taskExecutionsCallService.prepareFileInput(executionId, nodeId, inputName, file).pipe( + tap(() => this.refresh()), + catchError((err) => { + console.error('Prepare file input failed', err); + return throwError(() => err); + }) + ); + } +} diff --git a/src/app/shared/tasks-executions-list/tasks-executions-list.html b/src/app/shared/tasks-executions-list/tasks-executions-list.html index 9db1b20..be11af9 100644 --- a/src/app/shared/tasks-executions-list/tasks-executions-list.html +++ b/src/app/shared/tasks-executions-list/tasks-executions-list.html @@ -21,18 +21,18 @@ [class.btn-primary]="filter() === 'all'" [class.btn-outline-primary]="filter() !== 'all'" (click)="filter.set('all')">All + - + [class.btn-primary]="filter() === 'FINAL'" + [class.btn-outline-primary]="filter() !== 'FINAL'" + (click)="filter.set('FINAL')">Final diff --git a/src/app/shared/tasks-executions-list/tasks-executions-list.ts b/src/app/shared/tasks-executions-list/tasks-executions-list.ts index 19bcd9e..19ec8ab 100644 --- a/src/app/shared/tasks-executions-list/tasks-executions-list.ts +++ b/src/app/shared/tasks-executions-list/tasks-executions-list.ts @@ -1,10 +1,11 @@ import { CommonModule } from '@angular/common'; import { Component, computed, input, model, output, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { getExecutionStatusGroup, TaskExecutionStatus, TaskExecutionStatusGroup } from '@models/task-execution'; import { OrderEvent, OrderField, Ordering, orderDirType } from '@shared/ordering/ordering'; import { OrderViewState } from '@utilities/list-state-holder'; -export type TaskExecutionStatus = 'RUNNING' | 'COMPLETED' | 'FAILED' | 'ERROR' | 'QUEUED'; +export type TaskExecutionFilter = 'all' | TaskExecutionStatusGroup; export type TaskExecutionListItem = { id: string; @@ -26,7 +27,7 @@ export class TasksExecutionsListComponent { readonly selectedExecutionId = input(null); readonly executionSelected = output(); readonly searchTerm = model(''); - readonly filter = signal('all'); + readonly filter = signal('all'); readonly orderBy = signal('startedAt'); readonly orderDir = signal('desc'); @@ -44,12 +45,12 @@ export class TasksExecutionsListComponent { readonly filteredExecutions = computed(() => { const term = this.searchTerm().trim().toLowerCase(); - const status = this.filter(); + const filter = this.filter(); const orderBy = this.orderBy(); const orderDir = this.orderDir(); const filtered = this.executions().filter((execution) => { - if (status !== 'all' && execution.status !== status) return false; + if (!this.matchesFilter(execution.status, filter)) return false; if (!term) return true; return ( @@ -90,9 +91,17 @@ export class TasksExecutionsListComponent { } statusBadgeClass(status: TaskExecutionStatus) { - if (status === 'RUNNING') return 'bg-blue-100 text-blue-700 border-blue-200'; - if (status === 'COMPLETED') return 'bg-emerald-100 text-emerald-700 border-emerald-200'; - if (status === 'FAILED' || status === 'ERROR') return 'bg-rose-100 text-rose-700 border-rose-200'; + const normalized = String(status).toUpperCase(); + if (normalized === 'SUCCESS') return 'bg-emerald-100 text-emerald-700 border-emerald-200'; + if (normalized === 'ERROR') return 'bg-rose-100 text-rose-700 border-rose-200'; + if (normalized === 'WAITING') return 'bg-amber-100 text-amber-700 border-amber-200'; + if (normalized === 'RUNNING') return 'bg-blue-100 text-blue-700 border-blue-200'; + if (normalized === 'CREATED' || normalized === 'READY') return 'bg-slate-100 text-slate-700 border-slate-200'; return 'bg-slate-100 text-slate-700 border-slate-200'; } + + private matchesFilter(status: TaskExecutionStatus, filter: TaskExecutionFilter): boolean { + if (filter === 'all') return true; + return getExecutionStatusGroup(status) === filter; + } }