updated task-execution with all methods
This commit is contained in:
parent
b639133401
commit
4cedab52bc
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<string, string>;
|
||||
warnings: Record<string, string>;
|
||||
steps: Record<string, TaskExecutionStep>;
|
||||
status: ExecutionStatus;
|
||||
status: TaskExecutionStatus;
|
||||
waitingSteps: string[];
|
||||
executionResult: Record<string, unknown>;
|
||||
};
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,18 @@ import { Observable } from 'rxjs';
|
|||
|
||||
export abstract class TaskExecutionsCallServiceBase {
|
||||
abstract retrieveAllTaskExecutions(): Observable<TaskExecution[]>;
|
||||
abstract createTaskExecution(flowId: string): Observable<TaskExecution>;
|
||||
abstract startTaskExecution(executionId: string): Observable<TaskExecution>;
|
||||
abstract prepareStringInput(
|
||||
executionId: string,
|
||||
nodeId: string,
|
||||
inputName: string,
|
||||
value: string
|
||||
): Observable<TaskExecution>;
|
||||
abstract prepareFileInput(
|
||||
executionId: string,
|
||||
nodeId: string,
|
||||
inputName: string,
|
||||
file: File
|
||||
): Observable<TaskExecution>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -447,5 +447,65 @@ export class TaskExecutionsCallServiceFake extends TaskExecutionsCallServiceBase
|
|||
override retrieveAllTaskExecutions(): Observable<TaskExecution[]> {
|
||||
return of(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
override createTaskExecution(flowId: string): Observable<TaskExecution> {
|
||||
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<TaskExecution> {
|
||||
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<TaskExecution> {
|
||||
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<TaskExecution> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TaskExecution[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
return this.http.get<TaskExecution[]>(`${environment.apiUrl}/executions`);
|
||||
}
|
||||
|
||||
override createTaskExecution(flowId: string): Observable<TaskExecution> {
|
||||
return this.http.post<TaskExecution>(`${environment.apiUrl}/executions`, flowId);
|
||||
}
|
||||
|
||||
override startTaskExecution(executionId: string): Observable<TaskExecution> {
|
||||
return this.http.put<TaskExecution>(`${environment.apiUrl}/executions/${encodeURIComponent(executionId)}/start`, null);
|
||||
}
|
||||
|
||||
override prepareStringInput(
|
||||
executionId: string,
|
||||
nodeId: string,
|
||||
inputName: string,
|
||||
value: string
|
||||
): Observable<TaskExecution> {
|
||||
const url = `${environment.apiUrl}/executions/${encodeURIComponent(executionId)}/node/${encodeURIComponent(nodeId)}/input/${encodeURIComponent(inputName)}/text`;
|
||||
return this.http.put<TaskExecution>(url, value, {
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
});
|
||||
}
|
||||
|
||||
override prepareFileInput(
|
||||
executionId: string,
|
||||
nodeId: string,
|
||||
inputName: string,
|
||||
file: File
|
||||
): Observable<TaskExecution> {
|
||||
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<TaskExecution>(url, formData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,18 +21,18 @@
|
|||
[class.btn-primary]="filter() === 'all'"
|
||||
[class.btn-outline-primary]="filter() !== 'all'"
|
||||
(click)="filter.set('all')">All</button>
|
||||
<button type="button" class="btn btn-sm w-full transition"
|
||||
[class.btn-primary]="filter() === 'INIT'"
|
||||
[class.btn-outline-primary]="filter() !== 'INIT'"
|
||||
(click)="filter.set('INIT')">Init</button>
|
||||
<button type="button" class="btn btn-sm w-full transition"
|
||||
[class.btn-primary]="filter() === 'RUNNING'"
|
||||
[class.btn-outline-primary]="filter() !== 'RUNNING'"
|
||||
(click)="filter.set('RUNNING')">Running</button>
|
||||
<button type="button" class="btn btn-sm w-full transition"
|
||||
[class.btn-primary]="filter() === 'COMPLETED'"
|
||||
[class.btn-outline-primary]="filter() !== 'COMPLETED'"
|
||||
(click)="filter.set('COMPLETED')">Completed</button>
|
||||
<button type="button" class="btn btn-sm w-full transition"
|
||||
[class.btn-primary]="filter() === 'FAILED'"
|
||||
[class.btn-outline-primary]="filter() !== 'FAILED'"
|
||||
(click)="filter.set('FAILED')">Failed</button>
|
||||
[class.btn-primary]="filter() === 'FINAL'"
|
||||
[class.btn-outline-primary]="filter() !== 'FINAL'"
|
||||
(click)="filter.set('FINAL')">Final</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string | null>(null);
|
||||
readonly executionSelected = output<string>();
|
||||
readonly searchTerm = model<string>('');
|
||||
readonly filter = signal<TaskExecutionStatus | 'all'>('all');
|
||||
readonly filter = signal<TaskExecutionFilter>('all');
|
||||
readonly orderBy = signal<string | null>('startedAt');
|
||||
readonly orderDir = signal<orderDirType>('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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue