corrected mistake in drawer component

This commit is contained in:
Shubham Takode 2024-03-07 12:12:12 +05:30
parent 8905347cce
commit 579b6a34ab
10 changed files with 810 additions and 259 deletions

View File

@ -1,57 +0,0 @@
<div class="console-button">
<button class="btn btn-cml-primary d-flex align-items-center" (click)="openDetailsDrawer()">
<i class="al-icon al-ico-console sm me-3"></i>DETAILS
</button>
</div>
<mat-drawer #drawer mode="over" position="end" class="drawer-container" [ngClass]="{'maximized': isMaximized, 'closing': isDrawerClosing }">
<div class="header">
<div class="left-section">
<span></span>
</div>
<div class="center-section">
<button class="toggle-button" [class.active]="currentTab === 'parameter'" (click)="toggleTab('parameter')">PARAMETERS</button>
<button class="toggle-button" [class.active]="currentTab === 'code'" (click)="toggleTab('code')">CODE</button>
</div>
<div class="right-section">
<button type="button" class="btn btn-icon g-btn" (click)="toggleMaximize()">
<i tabindex="1002" class="al-icon pointer" [class]="isMaximized ? 'al-ico-min-panel' : 'al-ico-max-panel'"></i>
</button>
<button type="button" class="btn btn-icon g-btn" (click)="close()">
<i class="al-icon al-ico-dialog-x sm-md"></i>
</button>
</div>
</div>
<div class="tabs-content">
<div *ngIf="currentTab === 'parameter' && pipelineData?.parameters?.length > 0; else noParameterContent">
<div *ngFor="let parameter of pipelineData.parameters">
<div class="parameter-data"><p>Name:&nbsp; {{ parameter.name }}</p>
<div class="parameter-input">Value:&nbsp;
<input class="full-width-textarea" type="text"
[ngModel]="parameter.value"
[placeholder]="!parameter.value ? 'Enter value' : ''"
(ngModelChange)="onInputChange($event, parameter)"
(blur)="updateParameterValue(parameter)">
</div></div>
</div>
</div>
<ng-template #noParameterContent>
<div *ngIf="currentTab === 'parameter' && !pipelineParameters" class="no-data-container">
<i class="icon i-pipeline-no-code mx-auto xxl no-data-icon"></i>
<span class="no-data-text">NO PARAMETER</span>
</div>
</ng-template>
<div *ngIf="currentTab === 'code' && pipelineCode; else noCodeContent">
{{ pipelineCode }}
</div>
<ng-template #noCodeContent>
<div *ngIf="currentTab === 'code' && !pipelineCode" class="no-data-container">
<i class="icon i-pipeline-no-code mx-auto xxl no-data-icon"></i>
<span class="no-data-text">NO CODE</span>
</div>
</ng-template>
</div>
</mat-drawer>

View File

@ -1,128 +0,0 @@
// // :host {
// // display: block;
// // width: 100%;
@import "variables";
mat-drawer{
display: grid;
}
.drawer-container {
position: fixed;
bottom: 0;
transition: bottom 0.35s, visibility 0.35s, height ease-in-out 0.5s;
height: 60vh; /* Default height */
width: calc(100% - 64px); /* Assuming full width */
right: 0;
overflow: hidden;
z-index: 2;
}
.drawer-container.maximized {
position: fixed;
bottom: 0;
transition: bottom 0.35s, visibility 0.35s, height ease-in-out 0.5s;
height: 100vh; /* Default height */
width: calc(100% - 64px); /* Assuming full width */
right: 0;
overflow: hidden;
z-index: 2;
}
.drawer-container.closing {
height: 0;
transition: bottom 0.35s, visibility 0.35s, ease-in-out 0.5s; /* Apply transition when closing */
}
.drawer {
width: 100vw; // Make drawer cover the entire viewport width
bottom: 0; // Position drawer at the bottom of the screen
}
.console-button {
display: block;
position: absolute;
left: 24px;
top: 24px;
z-index: 2;
}
.dialog-container {
position: fixed;
bottom: 0;
right: 0;
width: 100%;
height: 40%; /* Adjust as needed */
max-height: 100%;
background-color: #141722;
transition: bottom 0.35s, visibility 0.35s, height ease-in-out 0.5s; /* Add transition for smooth animation */
z-index: -1 !important;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #202432;
background-color: #202432;
}
.tabs-content {
padding: 10px;
background-color: #141722;
color:#9fabc9;
height: 100%;
width: 100%;
}
.full-width-textarea {
width: 100%;
background-color: #384161;
}
.parameter-data{
background-color: #202432;
padding: 5px;
}
.parameter-input {
display: flex;
align-items: center;
}
.toggle-button {
border: none;
outline: none;
cursor: pointer;
background-color: #2c3246;
padding: 15px 25px;
transition: background-color 0.3s;
color: white;
margin-right: -3px;
}
.toggle-button:last-child {
margin-right: 0; /* Remove margin for the last button */
}
.toggle-button:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.toggle-button:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.toggle-button.active {
background-color: #384161; /* Adjust the background color for active state */
}
.no-data-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.no-data-icon {
font-size: 6rem;
color: #ccc;
}
.no-data-text {
margin-top: 15px;
font-size: 1.5rem;
color: #aaa;
}
// /* Add more styles as needed */
// // }

View File

@ -1,71 +0,0 @@
import { Component, OnInit, ViewChild, Input } from '@angular/core';
import { MatDrawer } from '@angular/material/sidenav';
import { Store } from '@ngrx/store';
import { cloneDeep } from 'lodash';
import {
updatePipeline,
} from "../pipelines.actions";
@Component({
selector: 'sm-pipeline-details-drawer',
templateUrl: './pipeline-details-drawer.component.html',
styleUrls: ['./pipeline-details-drawer.component.scss']
})
export class PipelineDetailsDrawerComponent implements OnInit {
@ViewChild('drawer') drawer: MatDrawer;
@Input() pipelineId: string;
@Input() pipelineData: any;
currentTab: 'parameter' | 'code' = 'parameter'; // Default tab
isMaximized: boolean = false;
isDrawerClosing: boolean = false;
pipelineParameters: string = '';
pipelineCode: string = '';
parameterValue!: string;
constructor(private store: Store) { }
ngOnInit(): void {
this.pipelineParameters = this.pipelineData?.parameters;
this.pipelineCode = '';
}
openDetailsDrawer(): void {
this.drawer.open();
}
toggleMaximize(): void {
this.isMaximized = !this.isMaximized;
}
close(): void {
this.isDrawerClosing = true;
this.drawer.close();
this.isMaximized = false;
setTimeout(() => {
this.isDrawerClosing = false;
}, 50);
}
toggleTab(tab: 'parameter' | 'code'): void {
this.currentTab = tab;
}
onInputChange(newValue: any, parameter: any): void {
// Update the parameter value when the input changes
this.parameterValue = newValue;
}
updateParameterValue(parameter: any): void {
// Perform deep clone to avoid mutating original object
const updatedPipelineData = cloneDeep(this.pipelineData);
// Find the index of the parameter in the parameters array
const index = updatedPipelineData.parameters.findIndex(p => p.id === parameter.id);
// Update the parameter value if found
if (index !== -1) {
updatedPipelineData.parameters[index].value = this.parameterValue;
}
// Dispatch action to update pipeline data in the store
this.store.dispatch(updatePipeline({ changes: updatedPipelineData }));
}
}

View File

@ -7,7 +7,7 @@
(deleteStep)="stepDelete($event)"
[ioOptions]="selectedStepInputOutputOptions"
(stepParamsChanged)="selectedStepParamsChanged($event)"/>
<sm-pipeline-details-drawer [pipelineId]="pipelineId" [pipelineData]="selectedPipeline"></sm-pipeline-details-drawer>
<sm-pipeline-details-drawer [pipelineData]="selectedPipeline" (pipelineParamsChanged)="pipelineParamsChanged($event)"></sm-pipeline-details-drawer>
<sm-flow-editor *ngIf="selectedPipeline?.flow_display?.nodes?.length" [pipelineData]="selectedPipeline"
(nodesChangedInReactFlow)="nodesChangedInReactFlow($event)"
(nodeClicked)="nodeSelected($event)"

View File

@ -9,13 +9,21 @@
transition: height 0.3s;
background-color: #0D0E15;
sm-flow-editor {
position: absolute;
top: 0;
width: 100%;
bottom: 0;
}
.console-button {
display: block;
position: absolute;
left: 24px;
top: 24px;
z-index: 2;
}
}
sm-pipeline-step-info {
display: block;
position: absolute;

View File

@ -15,6 +15,7 @@ import {
runPipeline,
updatePipelineStep,
deletePipelineStep,
setSelectedPipeline,
} from "../pipelines.actions";
import { selectRouterParams } from "@common/core/reducers/router-reducer";
import { Observable, Subscription, combineLatest, map } from "rxjs";
@ -335,6 +336,13 @@ export class EditPipelinePageComponent implements OnInit, OnDestroy {
//this.store.dispatch(setSelectedPipeline({data: cloneDeep(pipelineState)}))
}
public pipelineParamsChanged(changedParams) {
const pipelineState = cloneDeep(this.selectedPipeline);
pipelineState.flow_display.nodes = this.reactFlowState.nodes;
pipelineState.flow_display.edges = this.reactFlowState.edges;
pipelineState.parameters = cloneDeep(changedParams);
this.store.dispatch(setSelectedPipeline({data: pipelineState}))
}
/**
* @function getIncomingNodeIds
* @description It is used to get the incoming node ids.

View File

@ -0,0 +1,108 @@
<div class="console-button">
<button class="btn btn-cml-primary d-flex align-items-center" (click)="selectStep(); openLog()" data-id="consoleDetailsButton">
<i class="al-icon al-ico-console sm me-3"></i>DETAILS
</button>
</div>
<div
#diagramContainer
class="pipeline-container"
tabindex="1"
[class.extend]="showLog"
(click)="selectStep()"
(keyup)="selectStep()"
data-id="pipelineContainer"
>
<ng-container *ngIf="dagModel$ | async as dagModel">
<!-- <div *ngFor="let row of dagModel" class="level" [style.width.px]="chartWidth">
<sm-pipeline-controller-step
#taskEl
*ngFor="let step of row; trackBy: trackByStepId" [step]="step"
[selected]="selectedEntity && selectedEntity.id === step?.id"
(click)="$event.stopPropagation(); !taskEl.selected && selectStep(step)"
(openConsole)="openLog()"
></sm-pipeline-controller-step>
</div> -->
<svg class="arrows"
*ngIf="chartWidth"
[attr.viewBox]="'0 0 ' + chartWidth + ' ' + (50 + 132 * dagModel?.length)"
[style.width.px]="chartWidth"
[style.height.px]="50 + 132 * dagModel?.length"
>
<g
*ngFor="let arrow of arrows; trackBy: trackArrows"
[class.selected]="arrow.selected"
>
<path [attr.d]="arrow.path" fill="none" stroke-width="2"></path>
<polygon
points="0,-6 12,0, 0,6"
[attr.transform]="arrow.headTransform"
/>
</g>
</svg>
</ng-container>
</div>
<div class="results-panel" [class.extend]="showLog" [class.maximized]="maximizeResults" data-id="resultsPanel">
<ng-container *ngIf="showLog">
<div class="header toggle" >
<div class="log-name">
<i class="al-icon al-ico-console me-2"></i>
<span>{{(selected$ | async)?.name}}</span>
</div>
<sm-button-toggle
[value]="detailsPanelMode"
[options]="[
{label: 'PARAMETERS', value: statusOption.parameters},
{label: 'CODE', value: statusOption.code}
]"
(valueChanged)="detailsPanelMode = $event"
></sm-button-toggle>
<div class="close">
<i tabindex="1002" class="al-icon pointer" [class]="maximizeResults ? 'al-ico-min-panel' : 'al-ico-max-panel'" (click)="toggleResultSize()" (keyup)="toggleResultSize()" data-id="maxResultPanel"></i>
<i tabindex="1003" class="al-icon al-ico-dialog-x pointer ms-4" (click)="openLog(false)" (keyup)="openLog(false)" data-id="closeResultPanel"></i>
</div>
</div>
<div [ngSwitch]="detailsPanelMode" class="content">
<div *ngSwitchCase="statusOption.parameters" >
<ng-container *ngFor="
let param of pipelineData?.parameters;
let index = index
">
<div class="param">
<div class="key" [smTooltip]="param.name" smShowTooltipIfEllipsis>
{{ param.name }}
</div>
<div class="value">
<input [(ngModel)]="param['value']" placeholder="Enter parameter value"
(ngModelChange)="paramsChanged()" name="parameterKey-{{ param.id }}-{{ index }}" matInput required
#optsInput="ngModel" />
</div>
</div>
</ng-container>
<p class=" ms-3 mt-4 mb-4" style="font-size: smaller;">
Note: Make sure to hit the <i>save</i> button to save pipeline parameters.
</p>
</div>
<!-- <sm-simple-dataset-version-preview
*ngSwitchCase="statusOption.preview" [selected]="(selected$ | async)"
></sm-simple-dataset-version-preview> -->
<!-- <sm-experiment-output-log
*ngSwitchCase="statusOption.log"
[experiment]="selected$ | async"
[isDarkTheme]="true"
[showHeader]="false"
></sm-experiment-output-log> -->
<ng-container *ngSwitchCase="statusOption.code" >
<div *ngIf="stepDiff; else empty" class="ace" #aceEditor></div>
<ng-template #empty>
<div class="no-code" data-id="noCode">
<i class="icon no-output-icon i-no-code-dark"></i>
<h4>NO CODE</h4>
</div>
</ng-template>
</ng-container>
</div>
</ng-container>
</div>

View File

@ -0,0 +1,221 @@
@import "variables";
$log-height: 300px;
$log-header-height: 48px;
:host {
display: block;
position: relative;
height: 100%;
background-color: $blue-950;
.results-panel {
&.maximized {
height: 100%;
.content {
flex: 1;
}
}
}
sm-pipeline-info {
display: block;
position: absolute;
z-index: 2;
top: 24px;
right: 24px;
}
.console-button {
display: block;
position: absolute;
left: 24px;
top: 24px;
z-index: 2;
}
.results-panel {
display: flex;
flex-direction: column;
width: 100%;
height: $log-height + $log-header-height;
background-color: $blue-900;
position: absolute;
bottom: -($log-height + $log-header-height);
left: 0;
z-index: 3;
visibility: hidden;
transition: bottom 0.35s, visibility 0.35s, height ease-in-out 0.5s;
&.extend {
visibility: visible;
bottom: 0;
}
.header {
height: $log-header-height;
display: grid;
align-items: center;
grid-template-columns: 1fr 1fr;
column-gap: 24px;
padding: 0 24px;
background-color: $blue-700;
border-bottom: 1px solid $dark-border;
color: $blue-100;
box-shadow: 0 -2px 6px rgb(0 0 0 / 40%);
&.toggle {
grid-template-columns: 1fr auto 1fr;
}
}
.content {
height: $log-height;
overflow: auto;
.param {
margin-top: 10px;
display: flex;
align-items: center;
height: 32px;
font-size: 12px;
border-bottom: solid 1px $dark-border;
padding: 14px;
.key {
flex: 0.5 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;
}
}
}
::ng-deep sm-experiment-output-log {
.no-output {
position: absolute;
top: 60%;
left: 50%;
transform: translateY(-50%) translateX(-50%);
display: block;
text-align: center;
h4 {
color: $blue-250;
}
.no-output-icon {
height: 100px;
width: 100px;
color: $blue-250;
}
}
}
.ace {
height: 100%;
&.ace-monokai {
background-color: $blue-900;
}
}
}
}
.level {
min-width: 100%;
margin: 50px 0;
display: flex;
justify-content: center;
&:first-child {
margin-top: 74px;
}
}
.pipeline-container {
height: calc(100% + 14px);
width: calc(100% + 14px);
position: relative;
overflow: scroll;
cursor: grab;
user-select: none;
&:active {
cursor: grabbing;
}
&.extend {
padding-bottom: $log-height;
}
.arrows {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
pointer-events: none;
stroke: $blue-400;
fill: $blue-400;
.selected {
stroke: $blue-200;
fill: $blue-200;
}
}
}
sm-pipeline-controller-step {
margin: 0 50px;
position: relative;
z-index: 1;
}
.log-name {
font-weight: 500;
display: flex;
align-items: center;
overflow: hidden;
> span {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.close {
display: flex;
align-items: center;
margin-left: auto;
.al-icon {
color: $blue-300;
&:hover {
color: $blue-100;
}
}
}
.no-code {
position: absolute;
top: 60%;
left: 50%;
transform: translateY(-50%) translateX(-50%);
text-align: center;
h3 {
font-weight: 500;
font-size: 14px;
}
.icon {
width: 100px;
height: 100px;
}
}

View File

@ -0,0 +1,462 @@
import {
AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef,
Component,
ElementRef, EventEmitter, HostListener, inject,
Input,
NgZone,
OnDestroy,
OnInit,
Output,
QueryList,
ViewChild,
ViewChildren
} from '@angular/core';
import {DagModelItem} from '@ngneat/dag';
import {fromEvent, Observable, Subscription} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, map, mergeMap, takeUntil, tap, throttleTime} from 'rxjs/operators';
import {Store} from '@ngrx/store';
import {selectSelectedExperiment} from '~/features/experiments/reducers';
import {selectRouterParams} from '../../../core/reducers/router-reducer';
import * as commonInfoActions from '../../../experiments/actions/common-experiments-info.actions';
import {
getSelectedPipelineStep,
setSelectedPipelineStep
} from '../../../experiments/actions/common-experiments-info.actions';
import {selectPipelineSelectedStepWithFallback} from '../../../experiments/reducers';
import {IExperimentInfo} from '~/features/experiments/shared/experiment-info.model';
import {getBoxToBoxArrow} from 'curved-arrows';
import {Ace, edit} from 'ace-builds';
import {TaskStatusEnum} from '~/business-logic/model/tasks/taskStatusEnum';
import {selectScaleFactor} from '@common/core/reducers/view.reducer';
import {DagManagerUnsortedService} from '@common/shared/services/dag-manager-unsorted.service';
import {TaskTypeEnum} from '~/business-logic/model/tasks/taskTypeEnum';
import { cloneDeep } from 'lodash-es';
export enum StepStatusEnum {
queued = 'queued',
pending = 'pending',
skipped = 'skipped',
cached = 'cached',
executed = 'executed',
running = 'running',
failed = 'failed',
aborted = 'aborted',
completed = 'completed'
}
export interface TreeStep {
base_task_id: string,
queue: string,
parents: string[],
executed: string;
status: StepStatusEnum;
job_type: TaskTypeEnum;
job_started: number;
job_ended: number;
job_code_section: string;
job_id: string;
}
export interface PipelineItem extends DagModelItem {
name: string;
id: string;
data: TreeStep;
}
export interface Arrow {
path: string;
headTransform: string;
selected: boolean;
targetId: string;
}
export enum StatusOption {
code = 'Code',
parameters = 'Parameters',
}
@Component({
selector: 'sm-pipeline-details-drawer',
templateUrl: './pipeline-details-drawer.component.html',
styleUrls: ['./pipeline-details-drawer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [DagManagerUnsortedService]
})
export class PipelineDetailsDrawerComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChildren('taskEl', {read: ElementRef}) tasksElements!: QueryList<ElementRef>;
@ViewChild('start', {read: ElementRef}) startingElement: ElementRef;
@ViewChild('end', {read: ElementRef}) endingElement: ElementRef;
@ViewChild('diagramContainer') diagramContainer: ElementRef<HTMLDivElement>;
@ViewChildren('aceEditor') private aceEditorElements: QueryList<ElementRef>;
//@Input() pipelineData: any;
@Output() pipelineParamsChanged = new EventEmitter<unknown>();
private _pipelineData;
@Input() set pipelineData(pipelineData) {
this._pipelineData = pipelineData ? cloneDeep(pipelineData) : null;
}
get pipelineData() {
return this._pipelineData;
}
private aceEditor: Ace.Editor;
private sub = new Subscription();
private dragging: boolean;
public chartWidth = 0;
public arrows: Arrow[];
public diagramRect: DOMRect;
public dagModel$: Observable<PipelineItem[][]>;
public selected$: Observable<IExperimentInfo>;
public selectedEntity: PipelineItem;
public projectId$: Observable<string>;
protected pipelineController: PipelineItem[];
private skipAutoCenter: boolean;
private scale: number;
showLog: boolean;
public infoData: IExperimentInfo;
public stepDiff: string;
statusOption = StatusOption;
detailsPanelMode = StatusOption.parameters;
defaultDetailsMode = StatusOption.parameters;
trackArrows = (index: number, arrow: Arrow) => arrow.path;
trackByStepId = (index: number, step) => step.stepId;
public maximizeResults: boolean;
protected _dagManager: DagManagerUnsortedService<PipelineItem>;
protected store: Store;
protected cdr: ChangeDetectorRef;
protected zone: NgZone;
@HostListener('document:keydown', ['$event'])
onKeyDown(e: KeyboardEvent) {
if ((e.key == 'Escape')) {
this.showLog = false;
}
}
constructor() {
this._dagManager = inject(DagManagerUnsortedService<PipelineItem>);
this.store = inject(Store);
this.cdr = inject(ChangeDetectorRef);
this.zone = inject(NgZone);
this.sub.add(this.store.select(selectRouterParams)
.pipe(
debounceTime(150),
map(params => this.getEntityId(params)),
filter(id => !!id),
distinctUntilChanged()
)
.subscribe(id => {
this.skipAutoCenter = false;
this.store.dispatch(commonInfoActions.resetExperimentInfo());
this.store.dispatch(commonInfoActions.getExperimentInfo({id}));
this.store.dispatch(setSelectedPipelineStep({step: null}));
})
);
this.sub.add(this.store.select(selectScaleFactor).subscribe(factor => this.scale = 100 / factor));
this.selected$ = this.store.select(selectPipelineSelectedStepWithFallback);
this.projectId$ = this.store.select(selectRouterParams)
.pipe(map(params => params?.projectId));
}
ngOnInit(): void {
this.sub.add(this.store.select(selectSelectedExperiment)
.subscribe(task => {
if (task?.id !== this.infoData?.id) {
this.selectedEntity = null;
this.stepDiff = null;
this.detailsPanelMode = this.getPanelMode();
}
this.infoData = task;
const width = this.diagramContainer?.nativeElement.getBoundingClientRect().width;
this.chartWidth = Math.max(Number.isNaN(width) ? 0 : width, 4000);
this.dagModel$ = this._dagManager.dagModel$
.pipe(filter(model => model?.length > 0), tap(model =>
model.forEach(row => this.chartWidth = Math.max(this.chartWidth, row.length * 300))));
const pipelineObject = this.getTreeObject(task);
this.pipelineController = this.convertPipelineToDagModel(pipelineObject);
this.resetUninitializedRunningFields();
this._dagManager.setNewItemsArrayAsDagModel(this.pipelineController);
window.setTimeout(() => {
if (!this.skipAutoCenter) {
const element = this.diagramContainer.nativeElement;
element.scroll({left: (element.scrollWidth - element.getBoundingClientRect().width) / 2});
}
this.removeLines();
this.drawLines();
this.cdr.markForCheck();
}, 0);
if(this.selectedEntity) {
this.selectStep(this.selectedEntity);
}
})
);
}
paramsChanged() {
setTimeout(() => {
this.pipelineParamsChanged.emit(this.pipelineData.parameters);
}, 2000);
}
protected resetUninitializedRunningFields() {
if (!this.infoData?.runtime?._pipeline_hash) {
this.pipelineController.forEach(step => {
step.data.job_started = null;
step.data.job_ended = null;
step.data.status = StepStatusEnum.pending;
});
}
}
getEntityId(params) {
return params?.controllerId;
}
convertPipelineToDagModel(pipeline): PipelineItem[] {
let pipelineObj;
try {
pipelineObj = JSON.parse(pipeline);
pipelineObj = Object.entries(pipelineObj).reduce((acc, [key, val]: [string, TreeStep]) => {
acc[key] = {...val, parents: val.parents.map(parent => `${parent}`)};
return acc;
}, {});
} catch {
return [];
}
const pipelineKeysArr = Object.keys(pipelineObj).reverse();
// sort nodes to prevent @ngneat/dag from crashing
const sortedNodes = [];
let i = 0;
while (pipelineKeysArr.length > 0 || i > pipelineKeysArr.length) {
const node = pipelineObj[pipelineKeysArr[i]];
const parents = node?.parents ?? [];
if (parents.every(parent => sortedNodes.includes(parent))) {
sortedNodes.push(pipelineKeysArr[i]);
pipelineKeysArr.splice(i, 1);
i = 0;
continue;
}
i++;
}
return sortedNodes.map((key, index) => ({
id: key,
stepId: index + 1,
parentIds: [0].concat(pipelineObj[key].parents.map(parent => sortedNodes.indexOf(parent) + 1)),
name: key,
branchPath: 1,
data: {
...pipelineObj[key],
...(
this.infoData.status === TaskStatusEnum.Stopped &&
[TaskStatusEnum.Queued, TaskStatusEnum.InProgress, 'running'].includes(pipelineObj[key].status) &&
{status: 'aborted'}
)
}
} as PipelineItem));
}
ngAfterViewInit(): void {
this.sub.add(this.aceEditorElements.changes.subscribe(() => this.initAceEditor()));
fromEvent(this.diagramContainer.nativeElement, 'wheel')
.pipe(throttleTime(1000))
.subscribe(() => {
this.skipAutoCenter = true;
});
const mousedown$ = fromEvent(this.diagramContainer.nativeElement, 'mousedown');
const mouseup$ = fromEvent(document, 'mouseup');
const mousemove$ = fromEvent(this.diagramContainer.nativeElement, 'mousemove');
this.sub.add(mousedown$
.pipe(
mergeMap(() => mousemove$.pipe(
tap(() => {
this.dragging = true;
this.skipAutoCenter = true;
}),
map((e: MouseEvent) => {
e.preventDefault(); // prevent selecting text while dragging
return {
x: e.movementX,
y: e.movementY
};
}),
takeUntil(mouseup$)
))
)
.subscribe(({x, y}: { x: number; y: number }) => {
this.diagramContainer.nativeElement.scrollTo({
top: this.diagramContainer.nativeElement.scrollTop - y,
left: this.diagramContainer.nativeElement.scrollLeft - x,
});
})
);
}
removeLines() {
this.arrows = [];
}
drawLines() {
const pipeLineItems = this._dagManager.getSingleDimensionalArrayFromModel();
this.arrows = [];
this.diagramRect = this.diagramContainer.nativeElement.getBoundingClientRect();
pipeLineItems.forEach((pipeLineItem) => {
pipeLineItem.parentIds.forEach((parentId: number) => {
if (parentId > 0) {
const parent: ElementRef<HTMLDivElement> = this.tasksElements.find(
(b: ElementRef) => parentId === +b.nativeElement.children[0].id
);
const self: ElementRef<HTMLDivElement> = this.tasksElements.find(
(b: ElementRef) => +b.nativeElement.children[0].id === pipeLineItem.stepId
);
if (parent?.nativeElement && self?.nativeElement) {
const fromRect = parent.nativeElement.getBoundingClientRect();
const toRect = self.nativeElement.getBoundingClientRect();
const [sx, sy, c1x, c1y, c2x, c2y, ex, ey, ae] = getBoxToBoxArrow(
parent.nativeElement.offsetLeft, parent.nativeElement.offsetTop, fromRect.width / this.scale, fromRect.height / this.scale,
self.nativeElement.offsetLeft, self.nativeElement.offsetTop, toRect.width / this.scale, toRect.height / this.scale,
{padStart: 0, padEnd: 7}
);
this.arrows.push({
path: `M${sx} ${sy} C${c1x} ${c1y}, ${c2x} ${c2y}, ${ex} ${ey}`,
headTransform: `translate(${ex},${ey}) rotate(${ae})`,
selected: false,
targetId: pipeLineItem.id
});
}
}
});
});
}
ngOnDestroy(): void {
this.store.dispatch(commonInfoActions.setExperiment({experiment: null}));
this.store.dispatch(setSelectedPipelineStep({step: null}));
this.removeLines();
this.sub.unsubscribe();
}
selectStep(step?: PipelineItem) {
if (step) {
try {
this.stepDiff = JSON.parse(this.infoData.configuration[step.data?.job_code_section || step.id]?.value)?.script?.diff;
this.aceEditor?.getSession().setValue(this.stepDiff);
} catch (e) {
this.stepDiff = null;
if (this.defaultDetailsMode === StatusOption.code) {
this.detailsPanelMode = this.defaultDetailsMode;
}
}
const id = this.infoData.runtime._pipeline_hash ? (step?.data?.job_id) : null;
if (id) {
this.store.dispatch(getSelectedPipelineStep({id}));
} else {
this.store.dispatch(setSelectedPipelineStep({step: {
id,
type: step.data.job_type,
status: step.data.status as unknown as TaskStatusEnum,
name: step.name
}}));
this.showLog = false;
}
this.selectedEntity = step;
this.highlightArrows();
} else if (!this.dragging) {
this.stepDiff = null;
this.detailsPanelMode = this.defaultDetailsMode;
this.store.dispatch(setSelectedPipelineStep({step: null}));
this.selectedEntity = null;
this.highlightArrows();
} else {
this.dragging = false;
}
}
openLog(show = true) {
this.showLog = show;
this.initAceEditor();
}
private initAceEditor() {
const aceEditorElement = this.aceEditorElements.first;
if (!aceEditorElement) {
this.aceEditor = null;
return;
}
this.zone.runOutsideAngular(() => {
const aceEditor = edit(aceEditorElement.nativeElement) as Ace.Editor;
this.aceEditor = aceEditor;
aceEditor.setOptions({
readOnly: true,
showLineNumbers: false,
showGutter: false,
fontFamily: 'SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
fontSize: 13,
highlightActiveLine: false,
highlightSelectedWord: false,
showPrintMargin: false,
});
aceEditor.renderer.setScrollMargin(12, 12, 12, 12);
aceEditor.renderer.setPadding(16);
(aceEditor.renderer.container.querySelector('.ace_cursor') as HTMLElement).style.color = 'white';
aceEditor.container.style.lineHeight = '1.6';
aceEditor.renderer.updateFontSize();
aceEditor.session.setMode('ace/mode/python');
aceEditor.setTheme('ace/theme/monokai');
aceEditor.getSession().setValue(this.stepDiff);
this.aceEditor = aceEditor;
});
}
toggleResultSize() {
// this.maximizeResults = ! this.maximizeResults;
// if (this.detailsPanelMode === StatusOption.content) {
// this.detailsPanelMode = null;
// window.setTimeout(() => {
// this.detailsPanelMode = StatusOption.content;
// this.cdr.markForCheck();
// }, 450);
// }
}
protected highlightArrows() {
this.arrows = this.arrows
?.map(arrow => ({...arrow, selected: arrow.targetId === this.selectedEntity?.id}))
.sort((a, b) => a.selected && !b.selected ? 1 : -1);
}
protected getTreeObject(task) {
return task?.configuration?.Pipeline?.value;
}
protected getPanelMode() {
return this.defaultDetailsMode;
}
}

View File

@ -76,7 +76,7 @@ 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";
import { PipelineStepParametersEditDialogComponent } from "./edit-pipeline-page/pipeline-step-parameters-edit-dialog/pipeline-step-parameters-edit-dialog.component";
import { PipelineDetailsDrawerComponent } from './details-dialog/pipeline-details-drawer.component';
import { PipelineDetailsDrawerComponent } from './edit-pipeline-page/pipeline-details-drawer/pipeline-details-drawer.component';
export const pipelinesSyncedKeys = ["projects.showPipelineExamples"];
const pipelinesSyncedKeys2 = ["orderBy", "sortOrder"];