Adds button to validate workflow spec. Displays validation errors in a bottom sheet.

This commit is contained in:
Aaron Louie 2020-03-30 12:20:56 -04:00
parent a3c9b83dc6
commit ace852b732
11 changed files with 147 additions and 16 deletions

6
package-lock.json generated
View File

@ -12171,9 +12171,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sartography-workflow-lib": {
"version": "0.0.79",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.79.tgz",
"integrity": "sha512-AFPABwUrnVtjR4qsFX8oyl0ENViLw2RZbf/p+El+wDrOScUXgXKlH4lZl+GBo3i789Kw4xb/DiGicaZtEm3C3g=="
"version": "0.0.83",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.83.tgz",
"integrity": "sha512-cbelOLGgLpYq6I8VRooKBNOxPSBQT/UshdWeC5o6wZ9wf6ooE+Skh6X+awQR6V3cF0Ur0HasRfcdUgGkqgcLCg=="
},
"sass": {
"version": "1.23.3",

View File

@ -49,7 +49,7 @@
"ngx-file-drop": "^8.0.8",
"ngx-markdown": "^9.0.0",
"rxjs": "~6.5.4",
"sartography-workflow-lib": "^0.0.79",
"sartography-workflow-lib": "^0.0.83",
"tslib": "^1.11.1",
"uuid": "^7.0.2",
"zone.js": "^0.10.3"

View File

@ -1,4 +1,5 @@
import {FileMeta, FileType, WorkflowSpec, WorkflowSpecCategory} from 'sartography-workflow-lib';
import {ApiError} from 'sartography-workflow-lib/lib/types/api';
export interface FileMetaDialogData {
id?: number;
@ -23,7 +24,7 @@ export interface WorkflowSpecDialogData {
name: string;
display_name: string;
description: string;
workflow_spec_category_id: number;
category_id: number;
}
export interface WorkflowSpecCategoryDialogData {
@ -47,3 +48,7 @@ export interface DeleteWorkflowSpecCategoryDialogData {
confirm: boolean;
category: WorkflowSpecCategory;
}
export interface ApiErrorsBottomSheetData {
apiErrors: ApiError[];
}

View File

@ -0,0 +1,17 @@
<div fxLayout="row">
<h2>Workflow Specification Errors</h2>
<span fxFlex></span>
<button mat-icon-button (click)="dismiss($event)"><mat-icon>close</mat-icon></button>
</div>
<mat-list>
<mat-list-item *ngFor="let e of apiErrors" class="api-error">
<h3 mat-line *ngIf="e.status_code">{{e.code}}</h3>
<h4 mat-line *ngIf="e.code">{{e.status_code}}</h4>
<div mat-line *ngIf="e.message"><em>{{e.message}}</em></div>
<div mat-line *ngIf="e.task_name && e.task_id">{{e.task_name}} ({{e.task_id}})</div>
<div mat-line *ngIf="e.task_id && !e.task_name">Task ID: {{e.task_id}}</div>
<div mat-line *ngIf="!e.task_id && e.task_name">Task Name: {{e.task_name}}</div>
<div mat-line *ngIf="e.file_name">File: {{e.file_name}}</div>
<div mat-line *ngIf="e.tag">Tag: {{e.tag}}</div>
</mat-list-item>
</mat-list>

View File

@ -0,0 +1,7 @@
@import "../../config";
::ng-deep mat-list-item.api-error, mat-list-item.api-error:hover {
border-left: 4px solid $brand-warning;
background-color: $brand-warning-light;
cursor: default;
}

View File

@ -0,0 +1,37 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetRef} from '@angular/material/bottom-sheet';
import { ApiErrorsComponent } from './api-errors.component';
describe('ApiErrorsComponent', () => {
let component: ApiErrorsComponent;
let fixture: ComponentFixture<ApiErrorsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ApiErrorsComponent ],
providers: [
{
provide: MatBottomSheetRef,
useValue: {
close: (dialogResult: any) => {
}
}
},
{provide: MAT_BOTTOM_SHEET_DATA, useValue: []},
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ApiErrorsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,30 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetRef} from '@angular/material/bottom-sheet';
import {ApiError} from 'sartography-workflow-lib/lib/types/api';
import {ApiErrorsBottomSheetData} from '../_interfaces/dialog-data';
@Component({
selector: 'app-api-errors',
templateUrl: './api-errors.component.html',
styleUrls: ['./api-errors.component.scss']
})
export class ApiErrorsComponent implements OnInit {
apiErrors: ApiError[];
constructor(
@Inject(MAT_BOTTOM_SHEET_DATA) public data: ApiErrorsBottomSheetData,
private _bottomSheetRef: MatBottomSheetRef<ApiErrorsComponent>
) {
if (data && data.apiErrors && data.apiErrors.length > 0) {
this.apiErrors = data.apiErrors;
}
}
ngOnInit(): void {
}
dismiss(event: MouseEvent) {
this._bottomSheetRef.dismiss();
event.preventDefault();
}
}

View File

@ -2,6 +2,7 @@ import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {Injectable, NgModule} from '@angular/core';
import {FlexLayoutModule} from '@angular/flex-layout';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatBottomSheetModule} from '@angular/material/bottom-sheet';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatDialogModule} from '@angular/material/dialog';
@ -47,6 +48,7 @@ import {SignInComponent} from './sign-in/sign-in.component';
import {SignOutComponent} from './sign-out/sign-out.component';
import {WorkflowSpecCardComponent} from './workflow-spec-card/workflow-spec-card.component';
import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component';
import { ApiErrorsComponent } from './api-errors/api-errors.component';
@Injectable()
export class ThisEnvironment implements AppEnvironment {
@ -78,6 +80,7 @@ export class ThisEnvironment implements AppEnvironment {
WorkflowSpecListComponent,
HomeComponent,
WorkflowSpecCardComponent,
ApiErrorsComponent,
],
imports: [
BrowserAnimationsModule,
@ -86,6 +89,7 @@ export class ThisEnvironment implements AppEnvironment {
FormlyModule,
FormsModule,
HttpClientModule,
MatBottomSheetModule,
MatButtonModule,
MatCardModule,
MatDialogModule,
@ -106,6 +110,7 @@ export class ThisEnvironment implements AppEnvironment {
],
bootstrap: [AppComponent],
entryComponents: [
ApiErrorsComponent,
DeleteFileDialogComponent,
DeleteWorkflowSpecDialogComponent,
DeleteWorkflowSpecCategoryDialogComponent,

View File

@ -32,7 +32,10 @@
</app-workflow-spec-card>
<ng-template #actionButtons>
<div class="workflow-spec-actions">
<button mat-mini-fab color="primary" (click)="editWorkflowSpec(wfs)">
<button mat-mini-fab title="Check for errors in this workflow specification" color="accent" (click)="validateWorkflowSpec(wfs)">
<mat-icon>verified_user</mat-icon>
</button>
<button mat-mini-fab title="Edit this workflow specification" color="primary" (click)="editWorkflowSpec(wfs)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button title="Delete this workflow specification" color="warn" (click)="confirmDeleteWorkflowSpec(wfs)">

View File

@ -1,5 +1,6 @@
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetModule, MatBottomSheetRef} from '@angular/material/bottom-sheet';
import {MatCardModule} from '@angular/material/card';
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog';
import {MatIconModule} from '@angular/material/icon';
@ -25,6 +26,7 @@ import {
WorkflowSpecDialogData
} from '../_interfaces/dialog-data';
import {GetIconCodePipe} from '../_pipes/get-icon-code.pipe';
import {ApiErrorsComponent} from '../api-errors/api-errors.component';
import {FileListComponent} from '../file-list/file-list.component';
import {WorkflowSpecListComponent} from './workflow-spec-list.component';
@ -38,6 +40,7 @@ describe('WorkflowSpecListComponent', () => {
imports: [
BrowserAnimationsModule,
HttpClientTestingModule,
MatBottomSheetModule,
MatCardModule,
MatDialogModule,
MatIconModule,
@ -46,6 +49,7 @@ describe('WorkflowSpecListComponent', () => {
RouterTestingModule,
],
declarations: [
ApiErrorsComponent,
DeleteWorkflowSpecDialogComponent,
FileListComponent,
GetIconCodePipe,
@ -62,10 +66,19 @@ describe('WorkflowSpecListComponent', () => {
}
},
{provide: MAT_DIALOG_DATA, useValue: []},
{
provide: MatBottomSheetRef,
useValue: {
dismiss: () => {
},
}
},
{provide: MAT_BOTTOM_SHEET_DATA, useValue: []},
]
}).overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [
ApiErrorsComponent,
DeleteWorkflowSpecDialogComponent,
]
}
@ -105,7 +118,7 @@ describe('WorkflowSpecListComponent', () => {
name: '',
display_name: '',
description: '',
workflow_spec_category_id: 0,
category_id: 0,
};
const _upsertWorkflowSpecificationSpy = spyOn((component as any), '_upsertWorkflowSpecification')

View File

@ -1,4 +1,5 @@
import {Component, OnInit} from '@angular/core';
import {MatBottomSheet} from '@angular/material/bottom-sheet';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {ApiService, isNumberDefined, WorkflowSpec, WorkflowSpecCategory} from 'sartography-workflow-lib';
@ -12,6 +13,7 @@ import {
WorkflowSpecCategoryDialogData,
WorkflowSpecDialogData
} from '../_interfaces/dialog-data';
import {ApiErrorsComponent} from '../api-errors/api-errors.component';
interface WorklflowSpecCategoryGroup {
id: number;
@ -35,6 +37,7 @@ export class WorkflowSpecListComponent implements OnInit {
constructor(
private api: ApiService,
private snackBar: MatSnackBar,
private bottomSheet: MatBottomSheet,
public dialog: MatDialog
) {
this._loadWorkflowSpecCategories();
@ -43,20 +46,31 @@ export class WorkflowSpecListComponent implements OnInit {
ngOnInit() {
}
validateWorkflowSpec(wfs: WorkflowSpec) {
this.api.validateWorkflowSpecification(wfs.id).subscribe(apiErrors => {
if (apiErrors && apiErrors.length > 0) {
this.bottomSheet.open(ApiErrorsComponent, {data: {apiErrors: apiErrors}});
} else {
this.snackBar.open('Workflow specification is valid!', 'Ok', {duration: 5000});
}
});
}
editWorkflowSpec(selectedSpec?: WorkflowSpec) {
this.selectedSpec = selectedSpec;
const dialogData: WorkflowSpecDialogData = {
id: this.selectedSpec ? this.selectedSpec.id : '',
name: this.selectedSpec ? this.selectedSpec.name || this.selectedSpec.id : '',
display_name: this.selectedSpec ? this.selectedSpec.display_name : '',
description: this.selectedSpec ? this.selectedSpec.description : '',
category_id: this.selectedSpec ? this.selectedSpec.category_id : null,
};
// Open new filename/workflow spec dialog
const dialogRef = this.dialog.open(WorkflowSpecDialogComponent, {
height: '65vh',
width: '50vw',
data: {
id: this.selectedSpec ? this.selectedSpec.id : '',
name: this.selectedSpec ? this.selectedSpec.name || this.selectedSpec.id : '',
display_name: this.selectedSpec ? this.selectedSpec.display_name : '',
description: this.selectedSpec ? this.selectedSpec.description : '',
workflow_spec_category_id: this.selectedSpec ? this.selectedSpec.workflow_spec_category_id : '',
},
data: dialogData,
});
dialogRef.afterClosed().subscribe((data: WorkflowSpecDialogData) => {
@ -140,7 +154,7 @@ export class WorkflowSpecListComponent implements OnInit {
this.api.getWorkflowSpecList().subscribe(wfs => {
this.workflowSpecs = wfs;
this.workflowSpecsByCategory.forEach(cat => {
cat.workflow_specs = this.workflowSpecs.filter(wf => wf.workflow_spec_category_id === cat.id);
cat.workflow_specs = this.workflowSpecs.filter(wf => wf.category_id === cat.id);
});
});
}
@ -156,7 +170,7 @@ export class WorkflowSpecListComponent implements OnInit {
name: data.name,
display_name: data.display_name,
description: data.description,
workflow_spec_category_id: data.workflow_spec_category_id,
category_id: data.category_id,
};
if (specId) {