diff --git a/package-lock.json b/package-lock.json index 7e5c231..affc570 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12171,9 +12171,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sartography-workflow-lib": { - "version": "0.0.100", - "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.100.tgz", - "integrity": "sha512-4jpjcK8wkbjTujIcsCBco00wUquND3cRMx29GdtuzT5pvBlXy9DWUskhi6tVjNUIVNptmjrQjUbE/7QLNISwdw==" + "version": "0.0.102", + "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.102.tgz", + "integrity": "sha512-XsRht/O/WX7/d9xXlPGjpDjknpkbz6tB+VLyvgPAwquhrpYtJbEQnC0RkLiatrPbhxR5QN6m/gWxvgyPhNvb+g==" }, "sass": { "version": "1.23.3", diff --git a/package.json b/package.json index fa9aea7..5e99920 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "ngx-file-drop": "^8.0.8", "ngx-markdown": "^9.0.0", "rxjs": "~6.5.4", - "sartography-workflow-lib": "^0.0.100", + "sartography-workflow-lib": "^0.0.102", "tslib": "^1.11.1", "uuid": "^7.0.2", "zone.js": "^0.10.3" diff --git a/src/app/_dialogs/workflow-spec-dialog/workflow-spec-dialog.component.ts b/src/app/_dialogs/workflow-spec-dialog/workflow-spec-dialog.component.ts index c61675f..8fa271a 100644 --- a/src/app/_dialogs/workflow-spec-dialog/workflow-spec-dialog.component.ts +++ b/src/app/_dialogs/workflow-spec-dialog/workflow-spec-dialog.component.ts @@ -90,6 +90,18 @@ export class WorkflowSpecDialogComponent { required: true, }, }, + { + key: 'display_order', + type: 'input', + defaultValue: this.data.display_order, + templateOptions: { + type: 'number', + label: 'Display Order', + placeholder: 'Order in which spec will be displayed', + description: 'Sort order that the spec should appear in within its category. Lower numbers will appear first.', + required: true, + }, + }, ]; }); } diff --git a/src/app/_interfaces/dialog-data.ts b/src/app/_interfaces/dialog-data.ts index 946332f..cd0dea0 100644 --- a/src/app/_interfaces/dialog-data.ts +++ b/src/app/_interfaces/dialog-data.ts @@ -25,6 +25,7 @@ export interface WorkflowSpecDialogData { display_name: string; description: string; category_id: number; + display_order: number; } export interface WorkflowSpecCategoryDialogData { diff --git a/src/app/modeler/modeler.component.ts b/src/app/modeler/modeler.component.ts index ce20c02..d1d0556 100644 --- a/src/app/modeler/modeler.component.ts +++ b/src/app/modeler/modeler.component.ts @@ -208,8 +208,6 @@ export class ModelerComponent implements AfterViewInit { } getFileMetaDisplayString(fileMeta: FileMeta) { - - console.log(fileMeta); if (fileMeta) { const lastUpdated = new DatePipe('en-us').transform(fileMeta.file.lastModified); return fileMeta.name + @@ -224,10 +222,7 @@ export class ModelerComponent implements AfterViewInit { const spec = this.workflowSpec; if (spec) { - console.log('fileMeta.file.lastModified', fileMeta.file.lastModified); const lastUpdatedDate = new Date(fileMeta.file.lastModified); - - console.log('lastUpdatedDate', lastUpdatedDate); const lastUpdated = new DatePipe('en-us').transform(lastUpdatedDate); return ` Workflow spec ID: ${spec.id} diff --git a/src/app/workflow-spec-list/workflow-spec-list.component.html b/src/app/workflow-spec-list/workflow-spec-list.component.html index bc8f5a3..f492801 100644 --- a/src/app/workflow-spec-list/workflow-spec-list.component.html +++ b/src/app/workflow-spec-list/workflow-spec-list.component.html @@ -19,13 +19,31 @@ + +
- +
No workflow specs in this category
@@ -33,11 +51,11 @@

Master Status Specification

- +
- + verified_user + + diff --git a/src/app/workflow-spec-list/workflow-spec-list.component.spec.ts b/src/app/workflow-spec-list/workflow-spec-list.component.spec.ts index 2664da9..e2d93e8 100644 --- a/src/app/workflow-spec-list/workflow-spec-list.component.spec.ts +++ b/src/app/workflow-spec-list/workflow-spec-list.component.spec.ts @@ -119,6 +119,7 @@ describe('WorkflowSpecListComponent', () => { display_name: '', description: '', category_id: 0, + display_order: 0, }; const _upsertWorkflowSpecificationSpy = spyOn((component as any), '_upsertWorkflowSpecification') @@ -306,4 +307,10 @@ describe('WorkflowSpecListComponent', () => { expect(_loadWorkflowSpecCategoriesSpy).toHaveBeenCalled(); }); + it('should test validateWorkflowSpec'); + it('should test onWorkflowUpdated'); + it('should test editCategoryDisplayOrder'); + it('should test editSpecDisplayOrder'); + it('should test _loadWorkflowSpecs with is_master_spec'); + }); diff --git a/src/app/workflow-spec-list/workflow-spec-list.component.ts b/src/app/workflow-spec-list/workflow-spec-list.component.ts index bc23b17..9e23629 100644 --- a/src/app/workflow-spec-list/workflow-spec-list.component.ts +++ b/src/app/workflow-spec-list/workflow-spec-list.component.ts @@ -2,7 +2,15 @@ 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'; +import createClone from 'rfdc'; +import { + ApiService, + isNumberDefined, + moveArrayElementDown, + moveArrayElementUp, + WorkflowSpec, + WorkflowSpecCategory +} from 'sartography-workflow-lib'; import {DeleteWorkflowSpecCategoryDialogComponent} from '../_dialogs/delete-workflow-spec-category-dialog/delete-workflow-spec-category-dialog.component'; import {DeleteWorkflowSpecDialogComponent} from '../_dialogs/delete-workflow-spec-dialog/delete-workflow-spec-dialog.component'; import {WorkflowSpecCategoryDialogComponent} from '../_dialogs/workflow-spec-category-dialog/workflow-spec-category-dialog.component'; @@ -15,11 +23,13 @@ import { } from '../_interfaces/dialog-data'; import {ApiErrorsComponent} from '../api-errors/api-errors.component'; -interface WorklflowSpecCategoryGroup { + +interface WorkflowSpecCategoryGroup { id: number; name: string; display_name: string; workflow_specs?: WorkflowSpec[]; + display_order: number; } @Component({ @@ -32,7 +42,7 @@ export class WorkflowSpecListComponent implements OnInit { selectedSpec: WorkflowSpec; masterStatusSpec: WorkflowSpec; selectedCat: WorkflowSpecCategory; - workflowSpecsByCategory: WorklflowSpecCategoryGroup[] = []; + workflowSpecsByCategory: WorkflowSpecCategoryGroup[] = []; categories: WorkflowSpecCategory[]; constructor( @@ -50,7 +60,7 @@ export class WorkflowSpecListComponent implements OnInit { validateWorkflowSpec(wfs: WorkflowSpec) { this.api.validateWorkflowSpecification(wfs.id).subscribe(apiErrors => { if (apiErrors && apiErrors.length > 0) { - this.bottomSheet.open(ApiErrorsComponent, {data: {apiErrors: apiErrors}}); + this.bottomSheet.open(ApiErrorsComponent, {data: {apiErrors: apiErrors}}); } else { this.snackBar.open('Workflow specification is valid!', 'Ok', {duration: 5000}); } @@ -59,12 +69,14 @@ export class WorkflowSpecListComponent implements OnInit { editWorkflowSpec(selectedSpec?: WorkflowSpec) { this.selectedSpec = selectedSpec; + const hasDisplayOrder = this.selectedSpec && isNumberDefined(this.selectedSpec.display_order); 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, + 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, + display_order: hasDisplayOrder ? this.selectedSpec.display_order : 0, }; // Open new filename/workflow spec dialog @@ -81,7 +93,7 @@ export class WorkflowSpecListComponent implements OnInit { }); } - editWorkflowSpecCategory(selectedCat?: WorkflowSpecCategory) { + editWorkflowSpecCategory(selectedCat?: WorkflowSpecCategoryGroup) { this.selectedCat = selectedCat; // Open new filename/workflow spec dialog @@ -133,14 +145,95 @@ export class WorkflowSpecListComponent implements OnInit { }); } + onWorkflowUpdated(spec: WorkflowSpec) { + if (spec.is_master_spec) { + // Mark all other specs as not is_master_spec + let numUpdated = this.workflowSpecs.length - 1; + this.workflowSpecs.forEach(wfs => { + if (wfs.id !== spec.id) { + wfs.is_master_spec = false; + this.api.updateWorkflowSpecification(wfs.id, wfs).subscribe(() => { + numUpdated--; + if (numUpdated === 0) { + this._loadWorkflowSpecCategories(); + } + }); + } + }); + } + this._loadWorkflowSpecCategories(); + } + + editCategoryDisplayOrder(catId: number, direction: number, cats: WorkflowSpecCategoryGroup[]) { + // Remove the fake category with category-less specs + const realCats = cats.filter(cat => isNumberDefined(cat.id)); + const i = realCats.findIndex(spec => spec.id === catId); + if (i !== -1) { + if (direction === 1) { + moveArrayElementDown(realCats, i); + } else if (direction === -1) { + moveArrayElementUp(realCats, i); + } + } else { + this.snackBar.open('Category not found. Reload the page and try again.'); + return; + } + + let numUpdated = 0; + realCats.forEach((cat, j) => { + if (isNumberDefined(cat.id)) { + const newCat: WorkflowSpecCategoryGroup = createClone()(cat); + delete newCat.workflow_specs; + + newCat.display_order = j; + this.api.updateWorkflowSpecCategory(cat.id, newCat as WorkflowSpecCategory).subscribe(() => { + numUpdated++; + if (numUpdated === realCats.length) { + this._loadWorkflowSpecCategories(); + } + }); + } + }); + } + + editSpecDisplayOrder(specId: string, direction: number, specs: WorkflowSpec[]) { + const i = specs.findIndex(spec => spec.id === specId); + if (i !== -1) { + if (direction === 1) { + moveArrayElementDown(specs, i); + } else if (direction === -1) { + moveArrayElementUp(specs, i); + } + } else { + this.snackBar.open('Spec not found. Reload the page and try again.'); + return; + } + + let numUpdated = 0; + specs.forEach((spec, j) => { + spec.display_order = j; + this.api.updateWorkflowSpecification(spec.id, spec).subscribe(() => { + numUpdated++; + if (numUpdated === specs.length) { + this._loadWorkflowSpecCategories(); + } + }); + }); + } + + sortByDisplayOrder = (a, b) => (a.display_order < b.display_order) ? -1 : 1; + private _loadWorkflowSpecCategories() { this.api.getWorkflowSpecCategoryList().subscribe(cats => { - this.categories = cats.sort((a, b) => (a.display_order < b.display_order) ? -1 : 1); + this.categories = cats.sort(this.sortByDisplayOrder); + + // Add a container for specs without a category this.workflowSpecsByCategory = [{ id: null, name: 'none', display_name: 'No category', workflow_specs: [], + display_order: -1, // Display it at the top }]; this.categories.forEach((cat, i) => { @@ -156,13 +249,15 @@ export class WorkflowSpecListComponent implements OnInit { this.api.getWorkflowSpecList().subscribe(wfs => { this.workflowSpecs = wfs; this.workflowSpecsByCategory.forEach(cat => { - cat.workflow_specs = this.workflowSpecs.filter(wf => { - if (wf.is_master_spec) { - this.masterStatusSpec = wf; - } else { - return wf.category_id === cat.id; - } - }); + cat.workflow_specs = this.workflowSpecs + .filter(wf => { + if (wf.is_master_spec) { + this.masterStatusSpec = wf; + } else { + return wf.category_id === cat.id; + } + }) + .sort(this.sortByDisplayOrder); }); }); } @@ -179,6 +274,7 @@ export class WorkflowSpecListComponent implements OnInit { display_name: data.display_name, description: data.description, category_id: data.category_id, + display_order: data.display_order, }; if (specId) { @@ -255,24 +351,5 @@ export class WorkflowSpecListComponent implements OnInit { private _displayMessage(message: string) { this.snackBar.open(message, 'Ok', {duration: 3000}); } - - onWorkflowUpdated(spec: WorkflowSpec) { - if (spec.is_master_spec) { - // Mark all other specs as not is_master_spec - let numUpdated = this.workflowSpecs.length - 1; - this.workflowSpecs.forEach(wfs => { - if (wfs.id !== spec.id) { - wfs.is_master_spec = false; - this.api.updateWorkflowSpecification(wfs.id, wfs).subscribe(() => { - numUpdated--; - if (numUpdated === 0) { - this._loadWorkflowSpecCategories(); - } - }); - } - }); - } - this._loadWorkflowSpecCategories(); - } }