diff --git a/package-lock.json b/package-lock.json index ea28370..54efa36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "ngx-markdown": "^12.0.1", "protractor": "~7.0.0", "rxjs": "^6.5.5", - "sartography-workflow-lib": "^0.0.538", + "sartography-workflow-lib": "^0.0.540", "tslib": "^2.0.0", "uuid": "^7.0.2", "zone.js": "~0.11.4" @@ -67,7 +67,7 @@ "@typescript-eslint/eslint-plugin": "4.28.2", "@typescript-eslint/parser": "4.28.2", "babel-loader": "^8.2.2", - "eslint": "^7.26.0", + "eslint": "^7.32.0", "eslint-plugin-import": "latest", "eslint-plugin-jsdoc": "latest", "eslint-plugin-prefer-arrow": "latest", @@ -18748,9 +18748,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sartography-workflow-lib": { - "version": "0.0.538", - "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.538.tgz", - "integrity": "sha512-KE6a/ZN9DVlwLXF0A5iq/JYYVIB6T+DaqHQNzGpAAnSSO5vRhQxNeiTqeL30aZhg8js2OzvVivLOAU4i4vKqoQ==", + "version": "0.0.540", + "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.540.tgz", + "integrity": "sha512-ZYpPfjGDlXX8RalKITN4CiQCo4oDEh2Tk81pJECPw/9s3tUPVgD1rKqAXlDIvoopsjGKshFdY49ldGnjSACJcA==", "dependencies": { "tslib": "^2.2.0" } @@ -36093,9 +36093,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sartography-workflow-lib": { - "version": "0.0.538", - "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.538.tgz", - "integrity": "sha512-KE6a/ZN9DVlwLXF0A5iq/JYYVIB6T+DaqHQNzGpAAnSSO5vRhQxNeiTqeL30aZhg8js2OzvVivLOAU4i4vKqoQ==", + "version": "0.0.540", + "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.540.tgz", + "integrity": "sha512-ZYpPfjGDlXX8RalKITN4CiQCo4oDEh2Tk81pJECPw/9s3tUPVgD1rKqAXlDIvoopsjGKshFdY49ldGnjSACJcA==", "requires": { "tslib": "^2.2.0" } diff --git a/package.json b/package.json index fe7db82..bdb2bf4 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "ngx-markdown": "^12.0.1", "protractor": "~7.0.0", "rxjs": "^6.5.5", - "sartography-workflow-lib": "^0.0.538", + "sartography-workflow-lib": "^0.0.540", "tslib": "^2.0.0", "uuid": "^7.0.2", "zone.js": "~0.11.4" @@ -85,7 +85,7 @@ "@typescript-eslint/eslint-plugin": "4.28.2", "@typescript-eslint/parser": "4.28.2", "babel-loader": "^8.2.2", - "eslint": "^7.26.0", + "eslint": "^7.32.0", "eslint-plugin-import": "latest", "eslint-plugin-jsdoc": "latest", "eslint-plugin-prefer-arrow": "latest", 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 2e237b2..0eedb15 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 @@ -37,7 +37,7 @@ import { import {GetIconCodePipe} from '../_pipes/get-icon-code.pipe'; import {FileListComponent} from '../file-list/file-list.component'; import {WorkflowSpecListComponent} from './workflow-spec-list.component'; -import {WorkflowSpecDialogComponent} from '../_dialogs/workflow-spec-dialog/workflow-spec-dialog.component'; + export class MdDialogMock { // When the component calls this.dialog.open(...) we'll return an object @@ -166,8 +166,10 @@ describe('WorkflowSpecListComponent', () => { .and.stub(); const openDialogSpy = spyOn(component.dialog, 'open') .and.returnValue({afterClosed: () => of(mockSpecData)} as any); - - component.editWorkflowSpec(mockWorkflowSpec0); + component.selectedSpec = mockWorkflowSpec1; + component.selectedSpec.parents = []; + component.selectedSpec.libraries = []; + component.editWorkflowSpec(); expect(openDialogSpy).toHaveBeenCalled(); expect(_upsertWorkflowSpecificationSpy).not.toHaveBeenCalled(); @@ -524,11 +526,103 @@ describe('WorkflowSpecListComponent', () => { it('should call editWorkflowSpec, open Dialog & call _upsertWorkflowSpecification when Edit button is clicked', fakeAsync(() => { spyOn(dialog, 'open').and.callThrough(); - const _upsertWorkflowSpecification = spyOn((component as any), '_upsertWorkflowSpecification').and.stub(); const button = fixture.debugElement.nativeElement.querySelector('#add_spec'); button.click(); - const req = httpMock.expectOne(`apiRoot/workflow-specification-category`); + httpMock.expectOne(`apiRoot/workflow-specification-category`); expect(dialog.open).toHaveBeenCalled(); } )); + + it('should disallow deselecting library if being used as library', () => { + let mockSpecData: WorkflowSpecDialogData = { + id: '25', + name: 'name1', + display_name: 'displayname', + description: 'descr', + category_id: 0, + display_order: 0, + standalone: false, + library: false + }; + + const _upsertWorkflowSpecificationSpy = spyOn((component as any), '_upsertWorkflowSpecification') + .and.stub(); + const openDialogSpy = spyOn(component.dialog, 'open') + .and.returnValue({afterClosed: () => of(mockSpecData)} as any); + const canSaveSpy = spyOn(component, 'canSaveWorkflowSpec').and.callThrough(); + const snackBarSpy = spyOn((component as any).snackBar, 'open').and.stub(); + const localSelectedSpec = cloneDeep(mockWorkflowSpec0); + localSelectedSpec.parents = [ + { id: 1234, + display_name: 'test parent', + name: 'parent1' + }] + component.selectedSpec = localSelectedSpec; + component.editWorkflowSpec(localSelectedSpec); + expect(openDialogSpy).toHaveBeenCalled(); + expect(_upsertWorkflowSpecificationSpy).not.toHaveBeenCalled(); + expect(canSaveSpy).toHaveBeenCalled(); + expect(snackBarSpy).toHaveBeenCalled(); + }); + + it('should disallow saving as both library and standalone', () => { + // we need to have id,name and display_name filled out because there is a conditional + // that fails prior to saving if any of these are blank + let mockSpecData: WorkflowSpecDialogData = { + id: '25', + name: 'name1', + display_name: 'displayname', + description: 'descr', + category_id: 0, + display_order: 0, + standalone: true, + library: true + }; + + const _upsertWorkflowSpecificationSpy = spyOn((component as any), '_upsertWorkflowSpecification') + .and.stub(); + const openDialogSpy = spyOn(component.dialog, 'open') + .and.returnValue({afterClosed: () => of(mockSpecData)} as any); + const canSaveSpy = spyOn(component, 'canSaveWorkflowSpec').and.callThrough(); + const snackBarSpy = spyOn((component as any).snackBar, 'open').and.stub(); + const localSelectedSpec = cloneDeep(mockWorkflowSpec0); + localSelectedSpec.parents = [ + { id: 1234, + display_name: 'test parent', + name: 'parent1' + }] + component.selectedSpec = localSelectedSpec; + component.editWorkflowSpec(localSelectedSpec); + expect(openDialogSpy).toHaveBeenCalled(); + expect(_upsertWorkflowSpecificationSpy).not.toHaveBeenCalled(); + expect(canSaveSpy).toHaveBeenCalled(); + expect(snackBarSpy).toHaveBeenCalled(); + }); + + it('should not delete a library if it is being used', () => { + + const badWorkflowSpec = cloneDeep(mockWorkflowSpec0); + badWorkflowSpec.parents=[ + { id: 1234, + display_name: 'test parent', + name: 'parent1' + }] + badWorkflowSpec.library=true; + const mockConfirmDeleteData: DeleteWorkflowSpecDialogData = { + confirm: false, + workflowSpec: badWorkflowSpec + }; + + const _deleteWorkflowSpecSpy = spyOn((component as any), '_deleteWorkflowSpec').and.stub(); + const openDialogSpy = spyOn(component.dialog, 'open') + .and.returnValue({afterClosed: () => of(mockConfirmDeleteData)} as any); + const snackBarSpy = spyOn((component as any).snackBar, 'open').and.stub(); + mockConfirmDeleteData.confirm = true; + component.confirmDeleteWorkflowSpec(badWorkflowSpec); + expect(openDialogSpy).toHaveBeenCalled(); + expect(_deleteWorkflowSpecSpy).not.toHaveBeenCalled(); + expect(snackBarSpy).toHaveBeenCalled(); + }); + + }); 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 747a1b2..026110a 100644 --- a/src/app/workflow-spec-list/workflow-spec-list.component.ts +++ b/src/app/workflow-spec-list/workflow-spec-list.component.ts @@ -73,7 +73,7 @@ export class WorkflowSpecListComponent implements OnInit { this._loadWorkflowSpecCategories(); } }); - // this._loadWorkflowLibraries(); + this.searchField = new FormControl(); this.searchField.valueChanges.subscribe(value => { this._loadWorkflowSpecs(null, value); @@ -109,6 +109,18 @@ export class WorkflowSpecListComponent implements OnInit { return this.selectedSpec != null && this.selectedSpec.category_id === cat.id; } + canSaveWorkflowSpec(proposed: WorkflowSpecDialogData){ + if ((this.selectedSpec.parents.length > 0) && (!proposed.library)){ + this.snackBar.open('This Workflow Specification is still being used as a Library. Please remove references first!', 'Ok', { duration: 5000 }); + return false; + } + if (proposed.standalone && proposed.library){ + this.snackBar.open('A workflow spec cannot be both a standalone and a library!', 'Ok', { duration: 5000 }); + return false; + } + return true; + } + editWorkflowSpec(selectedSpec?: WorkflowSpec) { const hasDisplayOrder = selectedSpec && isNumberDefined(selectedSpec.display_order); @@ -133,8 +145,12 @@ export class WorkflowSpecListComponent implements OnInit { dialogRef.afterClosed().subscribe((data: WorkflowSpecDialogData) => { if (data && data.id && data.name && data.display_name && data.description) { - data.display_order = this.categories.filter(function (entry) { return entry.id === data.category_id; }).length; - this._upsertWorkflowSpecification(selectedSpec == null, data); + if (this.canSaveWorkflowSpec(data)) { + data.display_order = this.categories.filter(function (entry) { + return entry.id === data.category_id; + }).length; + this._upsertWorkflowSpecification(selectedSpec == null, data); + } } }); } @@ -176,6 +192,16 @@ export class WorkflowSpecListComponent implements OnInit { }); } + + canDeleteWorkflowSpec(wfs){ + if ((wfs.parents.length > 0) && (wfs.library)){ + this.snackBar.open('This Workflow Specification is still being used as a Library. Please remove references first!', 'Ok', { duration: 5000 }); + return false; + } + return true; + } + + confirmDeleteWorkflowSpec(wfs: WorkflowSpec) { const dialogRef = this.dialog.open(DeleteWorkflowSpecDialogComponent, { data: { @@ -185,7 +211,7 @@ export class WorkflowSpecListComponent implements OnInit { }); dialogRef.afterClosed().subscribe((data: DeleteWorkflowSpecDialogData) => { - if (data && data.confirm && data.workflowSpec) { + if (data && data.confirm && data.workflowSpec && this.canDeleteWorkflowSpec(data.workflowSpec)) { this._deleteWorkflowSpec(data.workflowSpec); if (typeof this.masterStatusSpec !== 'undefined') { this.selectSpec(this.masterStatusSpec); @@ -391,7 +417,7 @@ export class WorkflowSpecListComponent implements OnInit { delete newCat.workflow_specs; newCat.display_order = j; - this.api.updateWorkflowSpecCategory(cat.id, newCat as WorkflowSpecCategory).subscribe(updatedCat => { + this.api.updateWorkflowSpecCategory(cat.id, newCat as WorkflowSpecCategory).subscribe(() => { numUpdated++; if (numUpdated === cats.length) { this._loadWorkflowSpecCategories();