mirror of
https://github.com/clearml/clearml-web
synced 2025-03-13 07:08:17 +00:00
Added step info panel
This commit is contained in:
parent
c8e15e6f98
commit
b9afdb85b4
@ -13,4 +13,6 @@ export interface PipelinesCreateStepsRequest {
|
||||
parameters?: Array<object>,
|
||||
|
||||
pipeline_id?: string;
|
||||
|
||||
experimentDetails?:Task
|
||||
}
|
||||
|
@ -1,21 +1,20 @@
|
||||
<sm-edit-pipeline-header
|
||||
(settingsPipelineAction)="settings()"
|
||||
(createPipelineStep)="createPipelineStep()"
|
||||
(savePipeline)="savePipeline()"
|
||||
(compilePipeline)="compilePipeline()"
|
||||
(runPipeline)="runPipeline()"
|
||||
[pipelineData]="selectedPipeline"
|
||||
>
|
||||
<sm-edit-pipeline-header (settingsPipelineAction)="settings()" (createPipelineStep)="createPipelineStep()"
|
||||
(savePipeline)="savePipeline()" (compilePipeline)="compilePipeline()" (runPipeline)="runPipeline()"
|
||||
[pipelineData]="selectedPipeline">
|
||||
</sm-edit-pipeline-header>
|
||||
<div class="edit-pipeline-body">
|
||||
<sm-pipeline-step-info [step]="selectedStep" *ngIf="selectedStep"/>
|
||||
<!-- <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)" (edgesChangedInReactFlow)="edgesChangedInReactFlow($event)" />
|
||||
<sm-flow-editor *ngIf="selectedPipeline?.flow_display?.nodes?.length" [pipelineData]="selectedPipeline"
|
||||
(nodesChangedInReactFlow)="nodesChangedInReactFlow($event)"
|
||||
(nodeClicked)="nodeSelected($event)"
|
||||
(edgesChangedInReactFlow)="edgesChangedInReactFlow($event)" />
|
||||
<div class="pipeline-empty">
|
||||
<i class="icon i-fingers-white mx-auto xxl"></i>
|
||||
<br/>
|
||||
<br />
|
||||
<div class="sub-title">
|
||||
Please add atleast one step to get started
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
@ -8,7 +8,7 @@
|
||||
// padding: 30px;
|
||||
// justify-content: flex-start;
|
||||
// height: 100%;
|
||||
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 66px);
|
||||
transition: height 0.3s;
|
||||
@ -27,6 +27,13 @@
|
||||
// margin-right: 20px;
|
||||
// }
|
||||
// }
|
||||
sm-pipeline-step-info {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
.pipeline-empty {
|
||||
display: flex;
|
||||
|
@ -23,6 +23,7 @@ export class EditPipelinePageComponent implements OnInit, OnDestroy {
|
||||
public selectedPipelineId$: Observable<string>;
|
||||
private selectedPipeline$: Observable<Pipeline>;
|
||||
public selectedPipeline: Pipeline;
|
||||
public selectedStep;
|
||||
pipelineId: string;
|
||||
private reactFlowState = {nodes: [], edges: []};
|
||||
|
||||
@ -144,5 +145,9 @@ export class EditPipelinePageComponent implements OnInit, OnDestroy {
|
||||
|
||||
}
|
||||
|
||||
public nodeSelected(data) {
|
||||
this.selectedStep = {...data};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ export class FlowEditorComponent implements OnChanges, OnDestroy, AfterViewInit
|
||||
@Input() public pipelineData: Pipeline;
|
||||
@Output() nodesChangedInReactFlow = new EventEmitter<Array<unknown>>();
|
||||
@Output() edgesChangedInReactFlow = new EventEmitter<Array<unknown>>();
|
||||
@Output() nodeClicked = new EventEmitter<unknown>();
|
||||
|
||||
|
||||
/* initialNodes = [
|
||||
{ id: '1', position: { x: 0, y: 0 }, data: { label: '1' }, type: "normal" },
|
||||
@ -35,6 +37,7 @@ export class FlowEditorComponent implements OnChanges, OnDestroy, AfterViewInit
|
||||
//this.handleDivClicked = this.handleDivClicked.bind(this);
|
||||
this.nodesDataChanged = this.nodesDataChanged.bind(this);
|
||||
this.edgesDataChanged = this.edgesDataChanged.bind(this);
|
||||
this.onNodeClicked = this.onNodeClicked.bind(this);
|
||||
window.React = React;
|
||||
}
|
||||
|
||||
@ -67,6 +70,9 @@ export class FlowEditorComponent implements OnChanges, OnDestroy, AfterViewInit
|
||||
this.edgesChangedInReactFlow?.emit(edges)
|
||||
}
|
||||
|
||||
public onNodeClicked(nodeData) {
|
||||
this.nodeClicked?.emit(nodeData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -80,7 +86,8 @@ export class FlowEditorComponent implements OnChanges, OnDestroy, AfterViewInit
|
||||
initialNodes: this.pipelineData?.flow_display?.nodes,
|
||||
initialEdges: this.pipelineData?.flow_display?.edges,
|
||||
onNodesDataChanged: this.nodesDataChanged,
|
||||
onEdgesDataChanged: this.edgesDataChanged
|
||||
onEdgesDataChanged: this.edgesDataChanged,
|
||||
onNodeClicked: this.onNodeClicked,
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
<mat-expansion-panel [expanded]="true">
|
||||
<mat-expansion-panel-header>
|
||||
<div class="expand-header" data-id="expandInfoPanel">STEP INFO</div>
|
||||
</mat-expansion-panel-header>
|
||||
<div class="panel-body" *ngIf="step">
|
||||
<header *ngIf="step; else controllerHeader">
|
||||
<i
|
||||
class="al-icon lg"
|
||||
[class]="'al-ico-type-' + (step.data.experimentDetails?.type ? (step.data.experimentDetails.type.toString()).replace('_', '-') : 'training')"
|
||||
></i>
|
||||
<div class="name" [smTooltip]="step.data.name" smShowTooltipIfEllipsis>{{step.data?.name}}</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="status" [class]="step.data.experimentDetails?.status" data-id="infoStepStatus">{{step.data.experimentDetails?.status}}</div>
|
||||
<sm-id-badge class="ms-1" [id]="step.id" (copied)="copyToClipboard()"></sm-id-badge>
|
||||
</div>
|
||||
</header>
|
||||
<ng-template #controllerHeader>
|
||||
<div *ngIf="step?.id" class="d-flex justify-content-end my-2">
|
||||
<sm-id-badge [id]="step.id" (copied)="copyToClipboard()"></sm-id-badge>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-container>
|
||||
<div class="section">
|
||||
<div class="header">PARAMETERS</div>
|
||||
<ng-container *ngFor="let section of step?.data?.parameters">
|
||||
<div class="param">
|
||||
<div class="key" [smTooltip]="section.name" smShowTooltipIfEllipsis>{{section.name}}</div>
|
||||
<div class="value" [smTooltip]="section.value" smShowTooltipIfEllipsis>{{section.value}}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="header">METRICS</div>
|
||||
<ng-container *ngIf="(step?.data?.experimentDetails?.last_metrics | keyvalue)?.length > 0; else emptyMsg">
|
||||
<ng-container *ngFor="let metric of step?.data?.experimentDetails.last_metrics | keyvalue">
|
||||
<div *ngFor="let variant of $any($any(metric.value) | keyvalue) | filterMonitorMetric" class="param">
|
||||
<div class="key" [smTooltip]="$any(variant.value).metric" smShowTooltipIfEllipsis>{{$any(variant.value).metric}}/{{$any(variant.value).variant}}</div>
|
||||
<div class="value" [smTooltip]="$any(variant.value).value" smShowTooltipIfEllipsis>{{$any(variant.value).value}}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="header">ARTIFACTS</div>
|
||||
<ng-container *ngIf="step?.data?.experimentDetails?.execution?.artifacts?.length > 0; else emptyMsg">
|
||||
<div
|
||||
*ngFor="let artifact of step?.data?.experimentDetails?.execution.artifacts; trackBy: trackByFn"
|
||||
class="param"
|
||||
>
|
||||
<div class="key" [smTooltip]="artifact.key" smShowTooltipIfEllipsis>{{artifact.key}}</div>
|
||||
<div class="value">{{(artifact?.content_size | filesize : fileSizeConfigStorage) || ''}}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="header">MODELS</div>
|
||||
<ng-container *ngIf="step?.data?.experimentDetails?.models?.output?.length > 0; else emptyMsg">
|
||||
<div *ngFor="let model of step?.data?.experimentDetails?.models?.output" class="param">
|
||||
<div class="key"><a [routerLink]="['/projects', model.model.project.id, 'models', model.model.id]" target="_blank"
|
||||
>{{model.name || model.model.name}}</a></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #emptyMsg>
|
||||
<div class="empty-msg" data-id="emptyMsg">No data to show</div>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
<footer *ngIf="step?.id">
|
||||
<button class="btn btn-icon g-btn d-flex align-items-center" smTooltip="Delete step" (click)="deleteClicked()">
|
||||
<i [class]="'al-icon me-2 ' + icons.REMOVE + ' sm-md'"></i>
|
||||
Delete
|
||||
</button>
|
||||
<!-- <a
|
||||
class="arr-link"
|
||||
target="_blank"
|
||||
[href]="'/projects/' + project + '/experiments/' + entity?.id + '/output/execution'">
|
||||
Full details<i class="al-icon al-ico-link-arrow sm" data-id="fullDetailsArrow"></i>
|
||||
</a> -->
|
||||
</footer>
|
||||
</mat-expansion-panel>
|
@ -0,0 +1,152 @@
|
||||
@import "variables";
|
||||
|
||||
:host {
|
||||
::ng-deep {
|
||||
.mat-expansion-panel-body {
|
||||
padding: 0;
|
||||
}
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
width: 260px;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
background-color: $blue-700;
|
||||
|
||||
.expand-header {
|
||||
height: 32px;
|
||||
line-height: 34px;
|
||||
color: $white;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.panel-body{
|
||||
max-height: calc(70vh - 100px);
|
||||
min-height: 350px;
|
||||
overflow: auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 24px;
|
||||
&:first-child {
|
||||
margin-top:12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
color: $blue-200;
|
||||
font-size: $font-size-small;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.param {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
border-bottom: solid 1px $dark-border;
|
||||
|
||||
.key {
|
||||
flex: 1 1 0;
|
||||
color: $blue-300;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.value {
|
||||
flex: 1 1 0;
|
||||
color: $blue-100;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr;
|
||||
gap: 6px 0;
|
||||
grid-template-areas:
|
||||
"icon name"
|
||||
"icon status";
|
||||
width: 100%;
|
||||
margin: 12px 0 24px;
|
||||
padding-bottom: 12px;
|
||||
color: $blue-100;
|
||||
.al-icon {
|
||||
grid-area: icon;
|
||||
}
|
||||
.name {
|
||||
grid-area: name;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
grid-area: status;
|
||||
margin-right: auto;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
padding: 0 6px;
|
||||
border-radius: 2px;
|
||||
font-size: 10px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-msg {
|
||||
text-align: center;
|
||||
padding-top: 12px;
|
||||
font-size: $font-size-small;
|
||||
color: $blue-400;
|
||||
}
|
||||
|
||||
footer {
|
||||
@extend .fx-ctr;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
border-top: solid 1px $dark-border;
|
||||
.arr-link {
|
||||
i {
|
||||
margin-left:4px;
|
||||
transform: translateY(2px);
|
||||
transition: margin-left 0.3s;
|
||||
}
|
||||
&:hover i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import {Component, EventEmitter, Input, Output} 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, MESSAGES_SEVERITY} from '@common/constants';
|
||||
import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-pipeline-step-info',
|
||||
templateUrl: './pipeline-step-info.component.html',
|
||||
styleUrls: ['./pipeline-step-info.component.scss']
|
||||
})
|
||||
export class PipelineStepInfoComponent {
|
||||
readonly icons = ICONS;
|
||||
//private _entity: IExperimentInfo;
|
||||
public controller: boolean;
|
||||
public fileSizeConfigStorage = fileSizeConfigStorage;
|
||||
private _step;
|
||||
|
||||
/* @Input() set entity(task: IExperimentInfo) {
|
||||
this._entity = task;
|
||||
this.controller = task?.type === TaskTypeEnum.Controller;
|
||||
}
|
||||
get entity() {
|
||||
return this._entity;
|
||||
} */
|
||||
@Output() deleteStep = new EventEmitter<unknown>();
|
||||
@Input() set step(step) {
|
||||
this._step = step ? {...step} : null;
|
||||
}
|
||||
|
||||
get step() {
|
||||
return this._step;
|
||||
}
|
||||
@Input() project: string;
|
||||
|
||||
constructor(private store: Store) {
|
||||
}
|
||||
|
||||
public deleteClicked() {
|
||||
this.deleteStep.emit(this._step);
|
||||
}
|
||||
|
||||
trackByFn = (index: number, artifact: Artifact) => artifact.hash || artifact.uri;
|
||||
|
||||
copyToClipboard() {
|
||||
this.store.dispatch(addMessage(MESSAGES_SEVERITY.SUCCESS, 'ID copied successfully'));
|
||||
}
|
||||
}
|
@ -23,7 +23,8 @@ export interface IMyComponentProps {
|
||||
initialNodes: any;
|
||||
initialEdges: any;
|
||||
onNodesDataChanged: Function,
|
||||
onEdgesDataChanged: Function
|
||||
onEdgesDataChanged: Function,
|
||||
onNodeClicked: Function
|
||||
}
|
||||
|
||||
const edgeOptions = {
|
||||
@ -97,6 +98,7 @@ export const PipelineFlowComponent: FunctionComponent<IMyComponentProps> = (
|
||||
const onNodeClick = (_event, node) => {
|
||||
//setIsShowNodeEditModal(true);
|
||||
// setNodeData(clone(node));
|
||||
props.onNodeClicked(node);
|
||||
};
|
||||
|
||||
const onPaneClick = () => {
|
||||
|
@ -45,7 +45,8 @@ export class PipelineAddStepDialogComponent {
|
||||
name: stepForm.name,
|
||||
description: stepForm.description,
|
||||
experiment:stepForm.experiment.value,
|
||||
parameters: stepForm.parameters
|
||||
parameters: stepForm.parameters,
|
||||
experimentDetails: stepForm.experimentDetails,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -42,11 +42,12 @@ export class PipelineAddStepFormComponent implements OnChanges, OnDestroy {
|
||||
|
||||
public pipelinesNames: Array<string>;
|
||||
public experimentsNames: Array<string>;
|
||||
public step: { name: string; description: string; experiment: { label: string; value: string }, parameters: Array<ParamsItem> } = {
|
||||
public step: { name: string; description: string; experiment: { label: string; value: string }, experimentDetails: Task , parameters: Array<ParamsItem> } = {
|
||||
name: null,
|
||||
description: '',
|
||||
experiment: null,
|
||||
parameters: [],
|
||||
experimentDetails: {}
|
||||
};
|
||||
filterText: string = '';
|
||||
isAutoCompleteOpen: boolean;
|
||||
@ -90,7 +91,7 @@ export class PipelineAddStepFormComponent implements OnChanges, OnDestroy {
|
||||
this._experiments = experiments;
|
||||
this.experimentsOptions = [
|
||||
...((this.rootFiltered || experiments === null) ? [] : [this.experimentsRoot]),
|
||||
...(experiments ? experiments.map(experiment => ({label: experiment.name, value: experiment.id, parameters: experiment.hyperparams})) : [])
|
||||
...(experiments ? experiments.map(experiment => ({label: experiment.name, value: experiment.id, parameters: experiment.hyperparams, otherDetails: {...experiment}})) : [])
|
||||
];
|
||||
this.experimentsNames = this.experimentsOptions.map(experiment => experiment.label);
|
||||
}
|
||||
@ -132,10 +133,13 @@ export class PipelineAddStepFormComponent implements OnChanges, OnDestroy {
|
||||
experimentSelected($event: MatOptionSelectionChange) {
|
||||
this.step.experiment = {label: $event.source.value.label, value: $event.source.value.value};
|
||||
this.step.parameters = [];
|
||||
this.step.experimentDetails = {
|
||||
...cloneDeep($event.source?.value?.otherDetails)
|
||||
};
|
||||
for (const section in $event.source?.value?.parameters) {
|
||||
for (const param in $event.source?.value?.parameters[section]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log($event.source?.value?.parameters[section][param]);
|
||||
// console.log($event.source?.value?.parameters[section][param]);
|
||||
this.step.parameters.push({
|
||||
name: $event.source?.value?.parameters[section][param].name,
|
||||
value: $event.source?.value?.parameters[section][param].value,
|
||||
|
@ -252,7 +252,7 @@ export class PipelinesEffects {
|
||||
},
|
||||
size: 20,
|
||||
// user: this.store.select(selectCurrentUser)?.id,
|
||||
only_fields: ['name', 'status', 'type', 'user.name', 'id', 'hyperparams'],
|
||||
only_fields: ['name', 'status', 'type', 'user.name', 'id', 'hyperparams', 'execution', 'models', 'last_metrics'],
|
||||
// order_by: orderBy,
|
||||
// type: [excludedKey, 'annotation_manual', excludedKey, 'annotation', excludedKey, 'dataset_import'],
|
||||
// system_tags: ['-archived', '-pipeline', '-dataset'],
|
||||
|
@ -59,6 +59,13 @@ import { PipelineAddStepFormComponent } from "./pipeline-add-step-dialog/pipelin
|
||||
import {SortPipe} from '@common/shared/pipes/sort.pipe';
|
||||
import { PipelineParametersComponent } from "./pipeline-parameters/pipeline-parameters.component";
|
||||
import { FlowEditorComponent } from "./edit-pipeline-page/flow-editor.component";
|
||||
import { PipelineStepInfoComponent } from "./edit-pipeline-page/pipeline-step-info/pipeline-step-info.component";
|
||||
import { MatExpansionModule } from "@angular/material/expansion";
|
||||
import { IdBadgeComponent } from "@common/shared/components/id-badge/id-badge.component";
|
||||
import { FilterPipe } from "@common/shared/pipes/filter.pipe";
|
||||
import { FileSizePipe } from "@common/shared/pipes/filesize.pipe";
|
||||
import { RegexPipe } from "@common/shared/pipes/filter-regex.pipe";
|
||||
import { FilterMonitorMetricPipe } from "@common/shared/pipes/filter-monitor-metric.pipe";
|
||||
|
||||
export const pipelinesSyncedKeys = ["projects.showPipelineExamples"];
|
||||
const pipelinesSyncedKeys2 = ['orderBy', 'sortOrder'];
|
||||
@ -113,6 +120,7 @@ const getInitState = (userPreferences: UserPreferences) => ({
|
||||
FlowEditorComponent,
|
||||
PipelineSettingComponent,
|
||||
PipelineSettingFormComponent,
|
||||
PipelineStepInfoComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -158,6 +166,12 @@ const getInitState = (userPreferences: UserPreferences) => ({
|
||||
LabeledFormFieldDirective,
|
||||
SortPipe,
|
||||
MatCheckboxModule,
|
||||
MatExpansionModule,
|
||||
IdBadgeComponent,
|
||||
FilterPipe,
|
||||
FileSizePipe,
|
||||
RegexPipe,
|
||||
FilterMonitorMetricPipe
|
||||
],
|
||||
exports: [PipelinesPageComponent, EditPipelinePageComponent],
|
||||
providers: [
|
||||
|
Loading…
Reference in New Issue
Block a user