Added the settings dialog

This commit is contained in:
Sumit-solytics 2024-02-22 14:00:51 +05:30
parent a6296624de
commit b34c13a539
16 changed files with 365 additions and 13 deletions

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* pipelines
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
@ -34,7 +35,7 @@ import { BASE_PATH, COLLECTION_FORMATS } from "../variables";
import { Configuration } from "../configuration";
import { PipelinesDeleteRunsRequest } from "~/business-logic/model/pipelines/pipelinesDeleteRunsRequest";
import { PipelinesDeleteRunsResponse } from "~/business-logic/model/pipelines/pipelinesDeleteRunsResponse";
import { PipelinesCreateRequest, PipelinesCreateResponse, PipelinesCreateStepsRequest, PipelinesCreateStepsResponse } from "../model/pipelines/models";
import { PipelinesCreateRequest, PipelinesCreateResponse, PipelinesCreateStepsRequest, PipelinesCreateStepsResponse, pipelinesSettingsModel } from "../model/pipelines/models";
@Injectable()
export class ApiPipelinesService {
@ -279,6 +280,58 @@ export class ApiPipelinesService {
}
);
}
/**
*
* Create a new 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 pipelinesSettingCall(
request: pipelinesSettingsModel,
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 pipelinesCreate."
);
}
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<pipelinesSettingsModel>(
`${this.basePath}/pipelines.settings`,
request,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress,
}
);
}
}

View File

@ -4,5 +4,6 @@ export * from '././pipelinesCreateRequest';
export * from '././pipelinesCreateResponse';
export * from '././pipelinesCreateStepsRequest';
export * from '././pipelinesCreateStepsResponse';
export * from './pipelinesSettingsModel';
export * from "././pipeline";

View File

@ -0,0 +1,24 @@
export interface pipelinesSettingsModel {
/**
* Pipeline name. Unique within the company.
*/
emailList: string;
/**
* User-defined tags list
*/
emailAlert?: boolean;
/**
* User-defined tags list
*/
scheduling?: boolean;
/**
* Free text comment
*/
expression?: string;
/**
* Project ID of the project to which this report is assigned Must exist[ab]
*/
interval?: string;
parameters?: Array<object>
}

View File

@ -10,10 +10,8 @@
<sm-menu-item (itemClicked)="downloadTableAsCSV.emit()" itemLabel="Download on screen items"></sm-menu-item>
<sm-menu-item (itemClicked)="downloadFullTableAsCSV.emit()"
[itemLabel]="'Download first '+ (maxDownloadItems$ | async) +' items'"></sm-menu-item>
</sm-menu> -->
<!-- <sm-experiment-custom-cols-menu
</sm-menu>
<sm-experiment-custom-cols-menu
*ngIf="!minimizedView || tableMode === 'compare'"
[metricVariants]="metricVariants"
[hyperParams]="hyperParams"
@ -45,7 +43,7 @@
<button class="btn btn-icon g-btn" smTooltip="Run pipeline">
<i class="icon i-play lm"></i>
</button>
<button class="btn btn-icon g-btn" smTooltip="Pipeline settings">
<button class="btn btn-icon g-btn" smTooltip="Pipeline settings" (click)="settings()">
<i class="al-icon al-ico-settings lm"></i>
</button>
@ -61,4 +59,4 @@
smTooltip=""></sm-menu>
-->
</div>
</div>

View File

@ -27,6 +27,7 @@ export class EditPipelineHeaderComponent extends BaseEntityHeaderComponent imple
}
@Output() createPipelineStep = new EventEmitter();
@Output() settingsPipelineAction = new EventEmitter();
// @Output() selectedTableColsChanged = new EventEmitter<ISmCol>();
// @Output() removeColFromList = new EventEmitter<ISmCol['id']>();
// @Output() getMetricsToDisplay = new EventEmitter();
@ -52,4 +53,7 @@ export class EditPipelineHeaderComponent extends BaseEntityHeaderComponent imple
addNewStep() {
this.createPipelineStep.emit();
}
settings() {
this.settingsPipelineAction.emit();
}
}

View File

@ -1,5 +1,6 @@
<sm-edit-pipeline-header
(createPipelineStep)="createPipeline()"
(settingsPipelineAction)="settings()"
>
</sm-edit-pipeline-header>
<div class="edit-pipeline-body">

View File

@ -1,8 +1,9 @@
import { Component, inject } from '@angular/core';
import { PipelineAddStepDialogComponent } from '../pipeline-add-step-dialog/pipeline-add-step-dialog.component';
import { PipelineSettingComponent } from '../pipeline-setting/pipeline-setting.component';
import { MatDialog } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { createPipelineStep } from '../pipelines.actions';
import { createPipelineStep,settingsPipelineAction} from '../pipelines.actions';
@Component({
selector: 'sm-edit-pipeline-page',
@ -35,5 +36,18 @@ export class EditPipelinePageComponent {
// });
}
settings() {
this.dialog.open(PipelineSettingComponent, {
data: {defaultExperimentId: ''},
panelClass: 'light-theme',
width: '640px'
})
.afterClosed()
.subscribe(pipeline => {
if (pipeline) {
this.store.dispatch(settingsPipelineAction({pipelinesSettingsRequest: pipeline}));
}
});
}
}

View File

@ -0,0 +1,42 @@
<form (ngSubmit)="send()" #settingsForm='ngForm' class="d-flex flex-column">
<mat-form-field appearance="outline" hideRequiredMarker class="mat-light">
<mat-label>Email List</mat-label>
<mat-error *ngIf="settingsForm.controls.emailList?.touched && settingsForm.controls.emailList.errors?.required">*Please add an email.</mat-error>
<input name="emailList" [(ngModel)]="settingFields.emailList" #emailList="ngModel" matInput autocomplete="off" required>
</mat-form-field>
<mat-checkbox name="emailAlert" [(ngModel)]="settingFields.emailAlert">Send email alert on failure</mat-checkbox>
<mat-checkbox name="scheduling" [(ngModel)]="settingFields.scheduling">Enable Scheduling</mat-checkbox>
<!-- <mat-form-field class="w-100" appearance="outline">
<mat-label>Schedule Interval</mat-label>
<mat-select [(ngModel)]="settingFields.interval" name="intervalName" required (selectionChange)="intervalSelected($event)">
<mat-option value="custom">Custom</mat-option>
<mat-option value="daily">Daily</mat-option>
<mat-option value="hourly">Hourly</mat-option>
<mat-option value="weekly">Weekly</mat-option>
</mat-select>
</mat-form-field> -->
<mat-form-field class="w-100" appearance="outline">
<mat-label>Schedule Interval</mat-label>
<input matInput placeholder="Search for existing intervals" [matAutocomplete]="auto" [(ngModel)]="settingFields.interval" name="intervalName" required>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="intervalSelected($event)">
<mat-option value="custom">Custom</mat-option>
<mat-option value="daily">Daily</mat-option>
<mat-option value="hourly">Hourly</mat-option>
<mat-option value="weekly">Weekly</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field appearance="outline" hideRequiredMarker>
<mat-error *ngIf="expression?.touched && expression?.invalid">*Please add Cron Expression.</mat-error>
<mat-label>Cron Expression</mat-label>
<input class="step-expression" name="cronExpression" matInput [(ngModel)]="settingFields.expression" #expression="ngModel" required>
</mat-form-field>
<div class="w-100 create-step-button">
<button type="submit" class="btn btn-dark-fill center" [disabled]="settingsForm.invalid">Save</button>
</div>
</form>

View File

@ -0,0 +1,14 @@
@import "variables";
:host {
.create-report-button {
padding: 32px 12px 0;
}
mat-form-field {
width: 100%;
.report-description {
min-height: 68px;
}
}
}

View File

@ -0,0 +1,97 @@
import { Component, OnDestroy, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { NgModel } from '@angular/forms';
import { Subscription } from 'rxjs';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'
import {
IOption
} from '@common/shared/ui-components/inputs/select-autocomplete-with-chips/select-autocomplete-with-chips.component';
@Component({
selector: 'sm-pipeline-setting-form',
templateUrl: './pipeline-setting-form.component.html',
styleUrls: ['./pipeline-setting-form.component.scss']
})
export class PipelineSettingFormComponent implements OnDestroy {
public readonly intervalsRoot = {label: 'My interval', value: null};
@ViewChild('intervalInput') intervalInput: NgModel;
@ViewChild('emailList') emailList: ElementRef;
@ViewChild('expression') expression: NgModel;
@Input() readOnlyintervalsNames: string[];
@Input() defaultintervalId: string;
@Output() stepCreated = new EventEmitter();
@Output() filterSearchChanged = new EventEmitter<{ value: string; loadMore?: boolean }>();
settingFields = {
emailList: '',
emailAlert: false,
scheduling: false,
interval: null,
expression: ''
};
intervalsOptions: { label: string; value: string }[];
intervalsNames: string[];
rootFiltered = false;
isAutoCompleteOpen = false;
loading = false;
noMoreOptions = false;
private subs = new Subscription();
constructor() {}
ngOnInit(): void {
this.searchChanged(['*', null].includes(this.defaultintervalId) ? '' : this.defaultintervalId);
setTimeout(() => {
this.subs.add(this.intervalInput.valueChanges.subscribe(searchString => {
if (searchString !== this.settingFields.interval) {
this.searchChanged(searchString?.label || searchString || '');
}
}));
});
}
ngOnDestroy(): void {
this.subs.unsubscribe();
}
intervalSelected(event: MatAutocompleteSelectedEvent): void {
this.settingFields.interval = event.option.viewValue;
}
setIsAutoCompleteOpen(focus: boolean) {
this.isAutoCompleteOpen = focus;
}
displayFn(interval: IOption | string) {
return typeof interval === 'string' ? interval : interval?.label;
}
clear() {
this.intervalInput.control.setValue('');
}
send() {
this.stepCreated.emit(this.settingFields);
}
searchChanged(searchString: string) {
this.intervalsOptions = null;
this.intervalsNames = null;
this.rootFiltered = searchString && !this.intervalsRoot.label.toLowerCase().includes(searchString.toLowerCase());
searchString !== null && this.filterSearchChanged.emit({ value: searchString, loadMore: false });
}
loadMore(searchString) {
this.loading = true;
this.filterSearchChanged.emit({ value: searchString || '', loadMore: true });
}
isFocused(locationRef: HTMLInputElement) {
return document.activeElement === locationRef;
}
}

View File

@ -0,0 +1,10 @@
<sm-dialog-template iconClass="al-ico-settings" header="SETTINGS">
<sm-pipeline-setting-form
[readOnlyintervalsNames]="readOnlyIntervalNames$ | async"
[defaultintervalId]="data?.defaultExperimentId"
(stepCreated)="settingsForm($event)"
(filterSearchChanged)="filterSearchChanged($event)"
>
</sm-pipeline-setting-form>
</sm-dialog-template>

View File

@ -0,0 +1,5 @@
:host{
width: 640px;
display: block;
}

View File

@ -0,0 +1,60 @@
import {Component, Inject} from '@angular/core';
import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
import {Store} from '@ngrx/store';
import {Observable} from 'rxjs';
import {/* getTablesFilterProjectsOptions, */ resetTablesFilterProjectsOptions} from '@common/core/actions/projects.actions';
import {map} from 'rxjs/operators';
import {isReadOnly} from '@common/shared/utils/is-read-only';
import { Task } from '~/business-logic/model/tasks/task';
//import { selectExperimentsList } from '@common/experiments/reducers';
// import { globalFilterChanged } from '@common/experiments/actions/common-experiments-view.actions';
import { pipelinesSettingsModel } from '~/business-logic/model/pipelines/pipelinesSettingsModel';
import { selectExperiments } from '../pipelines.reducer';
import { getAllExperiments } from '../pipelines.actions';
@Component({
selector: 'sm-pipeline-setting',
templateUrl: './pipeline-setting.component.html',
styleUrls: ['./pipeline-setting.component.scss']
})
export class PipelineSettingComponent {
// public experiments$: Observable<Task[]>;
public readOnlyIntervalNames$: Observable<string[]>;
constructor(
private store: Store,
private matDialogRef: MatDialogRef<PipelineSettingComponent>,
@Inject(MAT_DIALOG_DATA) public data: { defaultExperimentId: string}
) {
// this.experiments$ = this.store.select(selectExperiments);
// this.readOnlyIntervalNames$ = this.store.select(selectExperiments)
// .pipe(map(experiments => experiments?.filter(experiment => isReadOnly(experiment)).map(experiment=> experiment.name)));
// this.store.dispatch(resetTablesFilterProjectsOptions());
}
public settingsForm(pipelineForm) {
// eslint-disable-next-line no-console
console.log("i am form", pipelineForm)
const pipeline = this.convertFormToPipeline(pipelineForm);
this.matDialogRef.close(pipeline);
}
private convertFormToPipeline(settingsForm)
: pipelinesSettingsModel {
return {
emailList: settingsForm.emailList,
emailAlert:settingsForm.emailAlert,
scheduling:settingsForm.scheduling,
interval: settingsForm.interval,
expression: settingsForm.expression,
};
}
filterSearchChanged($event: {value: string; loadMore?: boolean}) {
!$event.loadMore && this.store.dispatch(getAllExperiments({query: $event.value}));
this.store.dispatch(getAllExperiments({query: $event.value || '', /* loadMore: $event.loadMore, allowPublic: false */}));
}
}

View File

@ -3,6 +3,7 @@ import {createAction, props} from '@ngrx/store';
// import {IReport} from './reports.consts';
import { Pipeline, PipelinesCreateRequest } from '~/business-logic/model/pipelines/models';
import { PipelinesCreateStepsRequest } from '~/business-logic/model/pipelines/pipelinesCreateStepsRequest';
import { pipelinesSettingsModel } from '~/business-logic/model/pipelines/models';
import { Task } from '~/business-logic/model/tasks/task';
export const PIPELINES_PREFIX = 'PIPELINES_';
@ -16,6 +17,10 @@ export const createPipelineStep = createAction(
PIPELINES_PREFIX + 'CREATE_PIPELINE_STEP',
props<{ pipelinesCreateStepRequest: PipelinesCreateStepsRequest }>()
);
export const settingsPipelineAction= createAction(
PIPELINES_PREFIX + 'SETTINGS_PIPELINE_ACTION',
props<{ pipelinesSettingsRequest: pipelinesSettingsModel }>()
);
export const getAllExperiments = createAction(
PIPELINES_PREFIX + 'GET_EXPERIMENTS',

View File

@ -6,7 +6,7 @@ import {catchError, filter, map, mergeMap, switchMap, /* tap */} from 'rxjs/oper
import {activeLoader, /* addMessage, */ deactivateLoader, setServerError} from '../core/actions/layout.actions';
import {requestFailed} from '../core/actions/http.actions';
import {
createPipeline, createPipelineStep, getAllExperiments, setExperimentsResults
createPipeline, createPipelineStep, getAllExperiments, setExperimentsResults, settingsPipelineAction
} from './pipelines.actions';
// import {ApiReportsService} from '~/business-logic/api-services/reports.service';
/* import {IReport, PAGE_SIZE} from './reports.consts';
@ -34,7 +34,7 @@ import {TABLE_SORT_ORDER} from '../shared/ui-components/data/table/table.consts'
import {escapeRegex} from '../shared/utils/escape-regex';
import {MESSAGES_SEVERITY} from '../constants'; */
import {MatDialog} from '@angular/material/dialog';
import {selectCurrentUser} from '../core/reducers/users-reducer';
// import {selectCurrentUser} from '../core/reducers/users-reducer';
/* import {
ChangeProjectDialogComponent
} from '@common/experiments/shared/components/change-project-dialog/change-project-dialog.component';
@ -45,6 +45,7 @@ import {selectActiveWorkspaceReady} from '~/core/reducers/view.reducer';
// import {ConfirmDialogComponent} from '@common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component';
import {HttpClient} from '@angular/common/http';
import { PipelinesCreateResponse } from '~/business-logic/model/pipelines/pipelinesCreateResponse';
import { pipelinesSettingsModel } from '~/business-logic/model/pipelines/pipelinesSettingsModel';
import { ApiPipelinesService } from '~/business-logic/api-services/pipelines.service';
import { PipelinesCreateStepsResponse } from '~/business-logic/model/pipelines/pipelinesCreateStepsResponse';
import { ApiTasksService } from '~/business-logic/api-services/tasks.service';
@ -70,7 +71,7 @@ export class PipelinesEffects {
}
activeLoader = createEffect(() => this.actions.pipe(
ofType(/* getReports, getReport, */ createPipeline, createPipelineStep, getAllExperiments/* updateReport, restoreReport, archiveReport */),
ofType(/* getReports, getReport, */ createPipeline, createPipelineStep, getAllExperiments, settingsPipelineAction/* updateReport, restoreReport, archiveReport */),
filter(action => !action['refresh']),
map(action => activeLoader(action.type))
));
@ -110,6 +111,24 @@ export class PipelinesEffects {
]
})))
));
settingsPipelineAction$ = createEffect(() => this.actions.pipe(
ofType(settingsPipelineAction),
switchMap((action) => this.pipelinesApiService.pipelinesSettingCall(action.pipelinesSettingsRequest)
.pipe(mergeMap((res: pipelinesSettingsModel) => {
// eslint-disable-next-line no-console
console.log(res)
// this.router.navigate(['pipelines', res.id, 'edit']);
return [deactivateLoader(settingsPipelineAction.type)];
}),
catchError(err => {
return [
requestFailed(err),
setServerError(err, null, 'failed to create a new pipeline step'),
deactivateLoader(settingsPipelineAction.type),
]
})))
));

View File

@ -15,10 +15,13 @@ import { ButtonToggleComponent } from "@common/shared/ui-components/inputs/butto
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MatMenuModule } from "@angular/material/menu";
import {MatCheckboxModule} from '@angular/material/checkbox';
import { MatSidenavModule } from "@angular/material/sidenav";
import { MatInputModule } from "@angular/material/input";
import { PipelineDialogComponent } from "./pipeline-dialog/pipeline-dialog.component";
import { CreateNewPipelineFormComponent } from "./pipeline-dialog/create-new-pipeline-form/create-new-pipeline-form.component";
import { PipelineSettingComponent } from './pipeline-setting/pipeline-setting.component';
import { PipelineSettingFormComponent } from './pipeline-setting/pipeline-setting-form/pipeline-setting-form.component';
import { MatAutocompleteModule } from "@angular/material/autocomplete";
import { ScrollEndDirective } from "@common/shared/ui-components/directives/scroll-end.directive";
import { ClickStopPropagationDirective } from "@common/shared/ui-components/directives/click-stop-propagation.directive";
@ -57,7 +60,6 @@ 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";
export const pipelinesSyncedKeys = ["projects.showPipelineExamples"];
const pipelinesSyncedKeys2 = ['orderBy', 'sortOrder'];
// export const REPORTS_STORE_CONFIG_TOKEN =
@ -108,7 +110,9 @@ const getInitState = (userPreferences: UserPreferences) => ({
EditPipelinePageComponent,
EditPipelineHeaderComponent,
PipelineParametersComponent,
FlowEditorComponent
FlowEditorComponent,
PipelineSettingComponent,
PipelineSettingFormComponent,
],
imports: [
CommonModule,
@ -153,6 +157,7 @@ const getInitState = (userPreferences: UserPreferences) => ({
RefreshButtonComponent,
LabeledFormFieldDirective,
SortPipe,
MatCheckboxModule,
],
exports: [PipelinesPageComponent, EditPipelinePageComponent],
providers: [