Added step info panel

This commit is contained in:
Shubham Takode 2024-02-27 21:53:56 +05:30
parent c8e15e6f98
commit b9afdb85b4
13 changed files with 351 additions and 19 deletions

View File

@ -13,4 +13,6 @@ export interface PipelinesCreateStepsRequest {
parameters?: Array<object>,
pipeline_id?: string;
experimentDetails?:Task
}

View File

@ -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>&nbsp;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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = () => {

View File

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

View File

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

View File

@ -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'],

View File

@ -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: [