From c4a20594b04445f8abc30a38663a3f3ee6212e72 Mon Sep 17 00:00:00 2001 From: Shubham Takode Date: Wed, 28 Feb 2024 23:05:38 +0530 Subject: [PATCH] io mapping of artifacts --- .../api-services/pipelines.service.ts | 55 +++++++++ .../business-logic/model/pipelines/models.ts | 2 + .../pipelinesStepInputOutputMappingOptions.ts | 5 + .../pipelines/pipelinesUpdateStepsRequest.ts | 10 ++ .../edit-pipeline-page.component.html | 2 +- .../edit-pipeline-page.component.ts | 105 ++++++++++++++---- .../pipeline-step-info.component.html | 8 +- .../pipeline-step-info.component.scss | 36 +++++- .../pipeline-step-info.component.ts | 28 +++-- .../react/pipeline-flow.component.tsx | 2 +- .../pipelines/pipelines.actions.ts | 7 +- .../pipelines/pipelines.effects.ts | 17 ++- 12 files changed, 234 insertions(+), 43 deletions(-) create mode 100644 src/app/business-logic/model/pipelines/pipelinesStepInputOutputMappingOptions.ts create mode 100644 src/app/business-logic/model/pipelines/pipelinesUpdateStepsRequest.ts diff --git a/src/app/business-logic/api-services/pipelines.service.ts b/src/app/business-logic/api-services/pipelines.service.ts index 97490261..8c3d3437 100644 --- a/src/app/business-logic/api-services/pipelines.service.ts +++ b/src/app/business-logic/api-services/pipelines.service.ts @@ -46,6 +46,7 @@ import { PipelinesUpdateResponse, PipelinesCompileRequest, PipelinesRunRequest, + PipelinesUpdateStepsRequest, } from "../model/pipelines/models"; @Injectable() @@ -290,6 +291,60 @@ export class ApiPipelinesService { } ); } + + /** + * + * Update pipeline step + * @param request request body + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + + public pipelinesUpdateStep( + request: PipelinesUpdateStepsRequest, + options?: any, + observe: any = "body", + reportProgress: boolean = false + ): Observable { + if (request === null || request === undefined) { + throw new Error( + "Required parameter request was null or undefined when calling pipelinesUpdateStep." + ); + } + + let headers = this.defaultHeaders; + if (options && options.async_enable) { + headers = headers.set(this.configuration.asyncHeader, "1"); + } + + // to determine the Accept header + const httpHeaderAccepts: string[] = ["application/json"]; + const httpHeaderAcceptSelected: string | undefined = + this.configuration.selectHeaderAccept(httpHeaderAccepts); + if (httpHeaderAcceptSelected != undefined) { + headers = headers.set("Accept", httpHeaderAcceptSelected); + } + + // to determine the Content-Type header + const consumes: string[] = []; + const httpContentTypeSelected: string | undefined = + this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected != undefined) { + headers = headers.set("Content-Type", httpContentTypeSelected); + } + + return this.apiRequest.post( + `${this.basePath}/pipelines.update_node`, + request, + { + withCredentials: this.configuration.withCredentials, + headers: headers, + observe: observe, + reportProgress: reportProgress, + } + ); + } + /** * * Create a new pipeline step diff --git a/src/app/business-logic/model/pipelines/models.ts b/src/app/business-logic/model/pipelines/models.ts index 49866365..3af93b02 100644 --- a/src/app/business-logic/model/pipelines/models.ts +++ b/src/app/business-logic/model/pipelines/models.ts @@ -11,5 +11,7 @@ export * from '././pipelinesUpdateRequest'; export * from '././pipelinesUpdateResponse'; export * from '././pipelinesCompileRequest'; export * from '././pipelinesRunRequest'; +export * from '././pipelinesStepInputOutputMappingOptions'; +export * from '././pipelinesUpdateStepsRequest'; export * from "././pipeline"; \ No newline at end of file diff --git a/src/app/business-logic/model/pipelines/pipelinesStepInputOutputMappingOptions.ts b/src/app/business-logic/model/pipelines/pipelinesStepInputOutputMappingOptions.ts new file mode 100644 index 00000000..31afb67e --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesStepInputOutputMappingOptions.ts @@ -0,0 +1,5 @@ +import { Artifact } from "../tasks/artifact"; + +export interface PipelinesStepInputOutputMappingOptions extends Artifact { + stepName: string; +} \ No newline at end of file diff --git a/src/app/business-logic/model/pipelines/pipelinesUpdateStepsRequest.ts b/src/app/business-logic/model/pipelines/pipelinesUpdateStepsRequest.ts new file mode 100644 index 00000000..3d298ee3 --- /dev/null +++ b/src/app/business-logic/model/pipelines/pipelinesUpdateStepsRequest.ts @@ -0,0 +1,10 @@ +import { PipelinesParameter } from "./pipelinesParameter"; + +export interface PipelinesUpdateStepsRequest { + /** + * Pipeline step name. Unique within the company. + */ + step?: string; + + parameters?: Array, +} diff --git a/src/app/webapp-common/pipelines/edit-pipeline-page/edit-pipeline-page.component.html b/src/app/webapp-common/pipelines/edit-pipeline-page/edit-pipeline-page.component.html index aeb0da35..44d10838 100644 --- a/src/app/webapp-common/pipelines/edit-pipeline-page/edit-pipeline-page.component.html +++ b/src/app/webapp-common/pipelines/edit-pipeline-page/edit-pipeline-page.component.html @@ -3,7 +3,7 @@ [pipelineData]="selectedPipeline">
- + ; private selectedPipeline$: Observable; - public selectedPipeline: Pipeline; - public selectedStep; + public selectedPipeline: Pipeline; // do not update this variable, maintain it readonly. + private _selectedStep; + public selectedStepInputOutputOptions: Array; pipelineId: string; - private reactFlowState = {nodes: [], edges: []}; + + //// nodes and edges state should be managed here for local use + // changes will be propagated to store only after clicking on save. + private reactFlowState = {nodes: [], edges: []}; + constructor() { - this.selectedPipelineId$ = this.store.select(selectRouterParams).pipe(map((params: Params) => { - // eslint-disable-next-line @ngrx/avoid-mapping-selectors - //console.log(params); return params?.id })); this.selectedPipeline$ = this.store.select(selectSelectedPipeline) } + private recomputeSelectedStepInputOutputOptions() { + const incommingNodes = this.getIncomingNodesForNode(this._selectedStep, this.reactFlowState .nodes, this.reactFlowState .edges); + const options:Array = []; + incommingNodes.forEach((node) => { + if(node.data?.experimentDetails?.execution?.artifacts?.length) { + // for now we are using only artifacts of i/o mapping. + node.data.experimentDetails.execution.artifacts.forEach((artifact) => { + if(artifact.mode === ArtifactModeEnum.Output) { + options.push({ + ...artifact, + stepName: node.data.name, + }); + } + }) + } + }) + this.selectedStepInputOutputOptions = options; + console.log(options); + } + + set selectedStep(data) { + this._selectedStep = data; + this.recomputeSelectedStepInputOutputOptions(); + + } + get selectedStep() { + return this._selectedStep; + } + ngOnInit() { this.subs.add(this.selectedPipelineId$.pipe( ).subscribe((pipelineId) => { @@ -134,35 +170,62 @@ export class EditPipelinePageComponent implements OnInit, OnDestroy { // eslint-disable-next-line @typescript-eslint/no-unused-vars public nodesChangedInReactFlow(data) { - this.reactFlowState.nodes = data; - //console.log("nodes changed", data); + this.reactFlowState.nodes = cloneDeep(data); + this.recomputeSelectedStepInputOutputOptions(); } // eslint-disable-next-line @typescript-eslint/no-unused-vars public edgesChangedInReactFlow(data) { - this.reactFlowState.edges = data; - // eslint-disable-next-line no-console - console.log("edges changed", data); + this.reactFlowState.edges = cloneDeep(data); + this.recomputeSelectedStepInputOutputOptions(); } public nodeSelected(data) { - // eslint-disable-next-line no-console - console.log(this.selectedStep); - this.selectedStep = {...data}; + this.selectedStep = cloneDeep(data); } public selectedStepParamsChanged(changedParams) { - const pipelineState = cloneDeep(this.selectedPipeline); - pipelineState.flow_display?.nodes.map((node) => { + this.reactFlowState.nodes.map((node, index) => { if(node.id === this.selectedStep.id) { - node.data.parameters = cloneDeep(changedParams) + this.reactFlowState.nodes[index].data.parameters = cloneDeep(changedParams) } return node; }); - console.log(pipelineState); - this.store.dispatch(setSelectedPipeline({data: cloneDeep(pipelineState)})) + + //update node API call here. Update silently. + this.store.dispatch(updatePipelineStep({changes: { + step: this.selectedStep.id, + parameters: cloneDeep(changedParams) + }})) + + //console.log(pipelineState); + // pipelineState.flow_display?.nodes.map((node) => { + // if(node.id === this.selectedStep.id) { + // node.data.parameters = cloneDeep(changedParams) + // } + // return node; + // }); + // console.log(pipelineState); + //this.store.dispatch(setSelectedPipeline({data: cloneDeep(pipelineState)})) } + /** + * @function getIncomingNodeIds + * @description It is used to get the incoming node ids. + * @param {object} node - Object of the node. + * @param {array} edges - list of all edges. + * @returns {array} list of incoming node ids. + */ +private getIncomingNodesForNode (node, nodes, edges) { + const incomingNodes = []; + edges.forEach((edge) => { + if (node?.id === edge.target) { + incomingNodes.push(nodes.find((node) => node.id == edge?.source)); + } + }); + return incomingNodes; +} + } diff --git a/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.html b/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.html index caa610aa..19dcd53b 100644 --- a/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.html +++ b/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.html @@ -29,12 +29,12 @@ matInput required #optsInput="ngModel" /> - -
{{option.label}}
+
{{option.label}}
diff --git a/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.scss b/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.scss index 96a117ba..e9ee1647 100644 --- a/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.scss +++ b/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.scss @@ -1,14 +1,25 @@ @import "variables"; +::ng-deep { + .option { + background-color: red; + + span { + overflow: hidden; + } + } +} + :host { ::ng-deep { .mat-expansion-panel-body { padding: 0; } + .mat-expansion-panel-header { padding: 0 16px; } -} + } width: 300px; z-index: 1; @@ -23,7 +34,7 @@ font-weight: 500; } - .panel-body{ + .panel-body { max-height: calc(70vh - 100px); min-height: 350px; overflow: auto; @@ -32,8 +43,9 @@ .section { margin-bottom: 24px; + &:first-child { - margin-top:12px; + margin-top: 12px; } .header { @@ -43,6 +55,7 @@ display: flex; justify-content: space-between; } + .param { display: flex; align-items: center; @@ -58,6 +71,7 @@ text-overflow: ellipsis; white-space: nowrap; } + .value { flex: 1 1 0; color: $blue-100; @@ -79,9 +93,11 @@ margin: 12px 0 24px; padding-bottom: 12px; color: $blue-100; + .al-icon { grid-area: icon; } + .name { grid-area: name; overflow: hidden; @@ -100,27 +116,35 @@ font-weight: 500; text-transform: uppercase; background-color: $pipeline-pending; + &.queued { background-color: $pipeline-queued; } + &.skipped { background-color: $pipeline-skipped; } + &.cached { background-color: $pipeline-cached; } + &.executed { background-color: $pipeline-executed; } + &.running { background-color: $pipeline-running; } + &.failed { background-color: $pipeline-failed; } + &.aborted { background-color: $pipeline-aborted; } + &.completed { background-color: $pipeline-completed; } @@ -138,15 +162,17 @@ height: 48px; padding: 0 16px; border-top: solid 1px $dark-border; + .arr-link { i { - margin-left:4px; + margin-left: 4px; transform: translateY(2px); transition: margin-left 0.3s; } + &:hover i { margin-left: 8px; } } } -} +} \ No newline at end of file diff --git a/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.ts b/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.ts index bfcef025..c1761771 100644 --- a/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.ts +++ b/src/app/webapp-common/pipelines/edit-pipeline-page/pipeline-step-info/pipeline-step-info.component.ts @@ -1,16 +1,10 @@ import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; import {Store} from '@ngrx/store'; import {Artifact} from '~/business-logic/model/tasks/artifact'; -import { - PipelineItem, - StepStatusEnum, - TreeStep -} from '@common/pipelines-controller/pipeline-controller-info/pipeline-controller-info.component'; -import {TaskTypeEnum} from '~/business-logic/model/tasks/taskTypeEnum'; + import {addMessage} from '@common/core/actions/layout.actions'; import {fileSizeConfigStorage} from '@common/shared/pipes/filesize.pipe'; import {ICONS, IOption, MESSAGES_SEVERITY} from '@common/constants'; -import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model'; import { MatOptionSelectionChange } from '@angular/material/core'; import { NgModel } from '@angular/forms'; import { trackByValue } from '@common/shared/utils/forms-track-by'; @@ -27,10 +21,26 @@ export class PipelineStepInfoComponent { public controller: boolean; public fileSizeConfigStorage = fileSizeConfigStorage; private _step; + private _ioOptions: Array<{ label: string; value: string, type: string }> ; @Output() deleteStep = new EventEmitter(); @Output() stepParamsChanged = new EventEmitter(); + @Input() set ioOptions(options: any) { + const opts = options.map((op) => { + return { + value: "${"+op.stepName+"."+op.key+"}", + label: `${op.stepName}.${op.key}`, + type: op.type + } + }); + + this._ioOptions = cloneDeep(opts); + } + get ioOptions() { + return this._ioOptions; + } + @Input() set step(step) { this._step = step ? cloneDeep(step) : null; } @@ -42,9 +52,9 @@ export class PipelineStepInfoComponent { @ViewChild('optsInput') optsInput: NgModel; public trackByValue = trackByValue; isAutoCompleteOpen: boolean; - public incommingInputOptions: { label: string; value: string }[] = [ +/* public incommingInputOptions: { label: string; value: string }[] = [ {label: "test", value: "test1"} - ]; + ]; */ setIsAutoCompleteOpen(focus: boolean) { this.isAutoCompleteOpen = focus; } diff --git a/src/app/webapp-common/pipelines/edit-pipeline-page/react/pipeline-flow.component.tsx b/src/app/webapp-common/pipelines/edit-pipeline-page/react/pipeline-flow.component.tsx index 6fb553c5..03e49c7f 100644 --- a/src/app/webapp-common/pipelines/edit-pipeline-page/react/pipeline-flow.component.tsx +++ b/src/app/webapp-common/pipelines/edit-pipeline-page/react/pipeline-flow.component.tsx @@ -143,7 +143,7 @@ export const PipelineFlowComponent: FunctionComponent = ( nodeTypes={nodeTypes} > {/* */} - + {/* */} {/* () ); +export const updatePipelineStep = createAction( + PIPELINES_PREFIX + 'UPDATE_PIPELINE_STEP', + props<{changes: Partial}>() +); + export const pipelineSettings= createAction( PIPELINES_PREFIX + 'SETTINGS_PIPELINE_ACTION', props<{ pipelinesSettingsRequest: pipelinesSettingsModel }>() diff --git a/src/app/webapp-common/pipelines/pipelines.effects.ts b/src/app/webapp-common/pipelines/pipelines.effects.ts index 9a6ea5a6..8fb76f75 100644 --- a/src/app/webapp-common/pipelines/pipelines.effects.ts +++ b/src/app/webapp-common/pipelines/pipelines.effects.ts @@ -6,7 +6,7 @@ import {catchError, filter, map, mergeMap, switchMap, /* tap */} from 'rxjs/oper import {activeLoader, addMessage, /* addMessage, */ deactivateLoader, setServerError} from '../core/actions/layout.actions'; import {requestFailed} from '../core/actions/http.actions'; import {pipelineSettings, - createPipeline, createPipelineStep, getAllExperiments, getExperimentById, getPipelineById, setExperimentsResults, setSelectedPipeline, updatePipeline, updatePipelineSuccess, compilePipeline, runPipeline + createPipeline, createPipelineStep, getAllExperiments, getExperimentById, getPipelineById, setExperimentsResults, setSelectedPipeline, updatePipeline, updatePipelineSuccess, compilePipeline, runPipeline, updatePipelineStep } from './pipelines.actions'; // import {ApiReportsService} from '~/business-logic/api-services/reports.service'; /* import {IReport, PAGE_SIZE} from './reports.consts'; @@ -202,6 +202,21 @@ export class PipelinesEffects { ] }))) )); + + updatePipelineStep$ = createEffect(() => this.actions.pipe( + ofType(updatePipelineStep), + switchMap((action) => this.pipelinesApiService.pipelinesUpdateStep(action.changes) + .pipe(mergeMap(() => { + return [deactivateLoader(updatePipelineStep.type)]; + }), + catchError(err => { + return [ + requestFailed(err), + setServerError(err, null, 'failed to update a pipeline step'), + deactivateLoader(updatePipelineStep.type), + ] + }))) + )); pipelineSettings$ = createEffect(() => this.actions.pipe( ofType(pipelineSettings),