mirror of
https://github.com/clearml/clearml-web
synced 2025-04-19 05:35:20 +00:00
io mapping of artifacts
This commit is contained in:
parent
cfe2169ee6
commit
c4a20594b0
@ -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
|
||||
|
@ -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";
|
@ -0,0 +1,5 @@
|
||||
import { Artifact } from "../tasks/artifact";
|
||||
|
||||
export interface PipelinesStepInputOutputMappingOptions extends Artifact {
|
||||
stepName: string;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import { PipelinesParameter } from "./pipelinesParameter";
|
||||
|
||||
export interface PipelinesUpdateStepsRequest {
|
||||
/**
|
||||
* Pipeline step name. Unique within the company.
|
||||
*/
|
||||
step?: string;
|
||||
|
||||
parameters?: Array<PipelinesParameter>,
|
||||
}
|
@ -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> DETAILS</div> -->
|
||||
<sm-flow-editor *ngIf="selectedPipeline?.flow_display?.nodes?.length" [pipelineData]="selectedPipeline"
|
||||
(nodesChangedInReactFlow)="nodesChangedInReactFlow($event)"
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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 }>()
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user