io mapping of artifacts

This commit is contained in:
Shubham Takode 2024-02-28 23:05:38 +05:30
parent cfe2169ee6
commit c4a20594b0
12 changed files with 234 additions and 43 deletions

View File

@ -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<any> {
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<any>(
`${this.basePath}/pipelines.update_node`,
request,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress,
}
);
}
/**
*
* Create a new pipeline step

View File

@ -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";

View File

@ -0,0 +1,5 @@
import { Artifact } from "../tasks/artifact";
export interface PipelinesStepInputOutputMappingOptions extends Artifact {
stepName: string;
}

View File

@ -0,0 +1,10 @@
import { PipelinesParameter } from "./pipelinesParameter";
export interface PipelinesUpdateStepsRequest {
/**
* Pipeline step name. Unique within the company.
*/
step?: string;
parameters?: Array<PipelinesParameter>,
}

View File

@ -3,7 +3,7 @@
[pipelineData]="selectedPipeline">
</sm-edit-pipeline-header>
<div class="edit-pipeline-body">
<sm-pipeline-step-info [step]="selectedStep" *ngIf="selectedStep" (stepParamsChanged)="selectedStepParamsChanged($event)"/>
<sm-pipeline-step-info [step]="selectedStep" *ngIf="selectedStep" [ioOptions]="selectedStepInputOutputOptions" (stepParamsChanged)="selectedStepParamsChanged($event)"/>
<!-- <div class="details"><i class="icon no-output-icon i-no-code-dark"></i>&nbsp;DETAILS</div> -->
<sm-flow-editor *ngIf="selectedPipeline?.flow_display?.nodes?.length" [pipelineData]="selectedPipeline"
(nodesChangedInReactFlow)="nodesChangedInReactFlow($event)"

View File

@ -4,13 +4,18 @@ import { PipelineAddStepDialogComponent } from '../pipeline-add-step-dialog/pipe
import { PipelineSettingDialogComponent } from '../pipeline-setting/pipeline-setting.dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { createPipelineStep, pipelineSettings, getPipelineById, resetPipelines, resetPipelinesSearchQuery, updatePipeline, compilePipeline, runPipeline, setSelectedPipeline } from '../pipelines.actions';
import { createPipelineStep, pipelineSettings, getPipelineById, resetPipelines, resetPipelinesSearchQuery, updatePipeline, compilePipeline, runPipeline, setSelectedPipeline, updatePipelineStep } from '../pipelines.actions';
import { selectRouterParams } from '@common/core/reducers/router-reducer';
import { Observable, Subscription, map } from 'rxjs';
import { Params } from '@angular/router';
import { selectSelectedPipeline } from '../pipelines.reducer';
import { Pipeline, PipelinesCompileRequest } from '~/business-logic/model/pipelines/models';
import { Pipeline, PipelinesCompileRequest, PipelinesStepInputOutputMappingOptions } from '~/business-logic/model/pipelines/models';
import { cloneDeep } from 'lodash-es';
import { ArtifactModeEnum } from '~/business-logic/model/tasks/models';
@Component({
selector: 'sm-edit-pipeline-page',
@ -23,21 +28,52 @@ export class EditPipelinePageComponent implements OnInit, OnDestroy {
public subs = new Subscription();
public selectedPipelineId$: Observable<string>;
private selectedPipeline$: Observable<Pipeline>;
public selectedPipeline: Pipeline;
public selectedStep;
public selectedPipeline: Pipeline; // do not update this variable, maintain it readonly.
private _selectedStep;
public selectedStepInputOutputOptions: Array<PipelinesStepInputOutputMappingOptions>;
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<PipelinesStepInputOutputMappingOptions> = [];
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;
}
}

View File

@ -29,12 +29,12 @@
matInput required #optsInput="ngModel" />
<mat-autocomplete #auto="matAutocomplete" class="light-theme" [displayWith]="displayFn"
(opened)="setIsAutoCompleteOpen(true)" (closed)="setIsAutoCompleteOpen(false)" autoActiveFirstOption>
<mat-option *ngFor="let option of incommingInputOptions; trackBy: trackByValue"
<mat-option *ngFor="let option of ioOptions; trackBy: trackByValue"
[value]="option.value"
[smTooltip]="option.label"
smShowTooltipIfEllipsis
[smTooltip]="option.label + ' (' + option.type + ')'"
class="option"
(onSelectionChange)="paramSelected($event)">
<div [smSearchText]="optsInput.value">{{option.label}}</div>
<div [smSearchText]="optsInput.value" style="text-overflow: ellipsis; overflow: hidden;">{{option.label}}</div>
</mat-option>
<mat-option disabled style="height: 0; min-height: 0;"></mat-option>

View File

@ -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;
}
}
}
}
}

View File

@ -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<unknown>();
@Output() stepParamsChanged = new EventEmitter<unknown>();
@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;
}

View File

@ -143,7 +143,7 @@ export const PipelineFlowComponent: FunctionComponent<IMyComponentProps> = (
nodeTypes={nodeTypes}
>
{/* <Background variant={BackgroundVariant.Lines} gap={20} size={0.4} /> */}
<MiniMap nodeStrokeWidth={3} />
{/* <MiniMap nodeStrokeWidth={3} /> */}
<Controls />
</ReactFlow>
{/* <ReactFlow nodes={nodes}

View File

@ -1,7 +1,7 @@
import {createAction, props} from '@ngrx/store';
// import {ReportsGetAllExResponse} from '~/business-logic/model/reports/reportsGetAllExResponse';
// import {IReport} from './reports.consts';
import { Pipeline, PipelinesCompileRequest, PipelinesCreateRequest, PipelinesRunRequest, PipelinesUpdateRequest, PipelinesUpdateResponse, pipelinesSettingsModel } from '~/business-logic/model/pipelines/models';
import { Pipeline, PipelinesCompileRequest, PipelinesCreateRequest, PipelinesRunRequest, PipelinesUpdateRequest, PipelinesUpdateResponse, PipelinesUpdateStepsRequest, pipelinesSettingsModel } from '~/business-logic/model/pipelines/models';
import { PipelinesCreateStepsRequest } from '~/business-logic/model/pipelines/pipelinesCreateStepsRequest';
import { TasksGetByIdRequest } from '~/business-logic/model/tasks/models';
import { Task } from '~/business-logic/model/tasks/task';
@ -17,6 +17,11 @@ export const createPipelineStep = createAction(
PIPELINES_PREFIX + 'CREATE_PIPELINE_STEP',
props<{ pipelinesCreateStepRequest: PipelinesCreateStepsRequest }>()
);
export const updatePipelineStep = createAction(
PIPELINES_PREFIX + 'UPDATE_PIPELINE_STEP',
props<{changes: Partial<PipelinesUpdateStepsRequest>}>()
);
export const pipelineSettings= createAction(
PIPELINES_PREFIX + 'SETTINGS_PIPELINE_ACTION',
props<{ pipelinesSettingsRequest: pipelinesSettingsModel }>()

View File

@ -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),