Updates sartography-workflow-lib. Fixes all kinds of test failures resulting from change in file data API method.

This commit is contained in:
Aaron Louie 2020-04-08 17:15:08 -04:00
parent fff410fe13
commit 42c268bcc2
7 changed files with 95 additions and 30 deletions

6
package-lock.json generated
View File

@ -12171,9 +12171,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"sartography-workflow-lib": { "sartography-workflow-lib": {
"version": "0.0.88", "version": "0.0.99",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.88.tgz", "resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.99.tgz",
"integrity": "sha512-oZsPktnir7p44w0I11j524TtUZZCNo1x25WBaoxrdiUtswL66DyDrrbpfz/QQbDyCbvaew1uvfG8bEzgwuyjRA==" "integrity": "sha512-9Hdd3Ro7UjAPdcFdySxG4Q/EATUT3kk48+DXDa+R/pD1CnfFgagkIY7tlXVy23JYhZeLXJiKGdRDma2V90MnCQ=="
}, },
"sass": { "sass": {
"version": "1.23.3", "version": "1.23.3",

View File

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

View File

@ -4,6 +4,8 @@ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/materia
import {MatFormFieldModule} from '@angular/material/form-field'; import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
import {FormlyModule} from '@ngx-formly/core';
import {FormlyMaterialModule} from '@ngx-formly/material';
import createClone from 'rfdc'; import createClone from 'rfdc';
import {FileType} from 'sartography-workflow-lib'; import {FileType} from 'sartography-workflow-lib';
import {FileMetaDialogData} from '../../_interfaces/dialog-data'; import {FileMetaDialogData} from '../../_interfaces/dialog-data';
@ -19,6 +21,8 @@ describe('EditFileMetaDialogComponent', () => {
imports: [ imports: [
BrowserAnimationsModule, BrowserAnimationsModule,
FormsModule, FormsModule,
FormlyModule.forRoot(),
FormlyMaterialModule,
MatDialogModule, MatDialogModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,

View File

@ -1,3 +1,4 @@
import {HttpHeaders} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog'; import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog';
@ -91,8 +92,12 @@ describe('FileListComponent', () => {
fmsNoFiles.forEach((fm, i) => { fmsNoFiles.forEach((fm, i) => {
const fReq = httpMock.expectOne(`apiRoot/file/${fm.id}/data`); const fReq = httpMock.expectOne(`apiRoot/file/${fm.id}/data`);
const mockHeaders = new HttpHeaders()
.append('last-modified', justFiles[i].lastModified.toString())
.append('content-type', justFiles[i].type);
fReq.flush(new ArrayBuffer(8), {headers: mockHeaders});
expect(fReq.request.method).toEqual('GET'); expect(fReq.request.method).toEqual('GET');
fReq.flush(justFiles[i]);
expect(component.fileMetas[i].file).toBeTruthy(); expect(component.fileMetas[i].file).toBeTruthy();
}); });
}); });
@ -186,11 +191,18 @@ describe('FileListComponent', () => {
mockDocMeta.type = FileType.DOCX; mockDocMeta.type = FileType.DOCX;
component.editFileMeta(mockDocMeta); component.editFileMeta(mockDocMeta);
const fakeBlob = new Blob(['I am a fake blob. A real blob says "blorp blorp blorp."']); const expectedFile = new File([], mockDocMeta.name, {
const expectedFile = new File([fakeBlob], mockDocMeta.name, {type: mockDocMeta.content_type}); type: mockDocMeta.content_type,
lastModified: mockDocMeta.file.lastModified
});
const fReq = httpMock.expectOne(`apiRoot/file/${mockDocMeta.id}/data`); const fReq = httpMock.expectOne(`apiRoot/file/${mockDocMeta.id}/data`);
const mockHeaders = new HttpHeaders()
.append('last-modified', expectedFile.lastModified.toString())
.append('content-type', mockDocMeta.content_type);
expect(fReq.request.method).toEqual('GET');
fReq.flush(new ArrayBuffer(8), {headers: mockHeaders});
expect(fReq.request.method).toEqual('GET'); expect(fReq.request.method).toEqual('GET');
fReq.flush(fakeBlob);
expect(_openFileDialogSpy).toHaveBeenCalledWith(mockDocMeta, expectedFile); expect(_openFileDialogSpy).toHaveBeenCalledWith(mockDocMeta, expectedFile);
_openFileDialogSpy.calls.reset(); _openFileDialogSpy.calls.reset();

View File

@ -8,7 +8,7 @@ import {
FileParams, FileParams,
FileType, FileType,
getFileType, getFileType,
isNumberDefined, isNumberDefined, newFileFromResponse,
WorkflowSpec WorkflowSpec
} from 'sartography-workflow-lib'; } from 'sartography-workflow-lib';
import {DeleteFileDialogComponent} from '../_dialogs/delete-file-dialog/delete-file-dialog.component'; import {DeleteFileDialogComponent} from '../_dialogs/delete-file-dialog/delete-file-dialog.component';
@ -50,8 +50,8 @@ export class FileListComponent implements OnInit {
editFileMeta(fm: FileMeta) { editFileMeta(fm: FileMeta) {
if (fm && isNumberDefined(fm.id)) { if (fm && isNumberDefined(fm.id)) {
this.api.getFileData(fm.id).subscribe(fileData => { this.api.getFileData(fm.id).subscribe(response => {
const file = new File([fileData], fm.name, {type: fm.content_type}); const file = newFileFromResponse(fm, response);
this._openFileDialog(fm, file); this._openFileDialog(fm, file);
}); });
} else { } else {
@ -92,7 +92,6 @@ export class FileListComponent implements OnInit {
} }
private _openFileDialog(fm?: FileMeta, file?: File) { private _openFileDialog(fm?: FileMeta, file?: File) {
console.log('fm.id', fm && fm.id);
const dialogData: OpenFileDialogData = { const dialogData: OpenFileDialogData = {
fileMetaId: fm ? fm.id : undefined, fileMetaId: fm ? fm.id : undefined,
file: file, file: file,
@ -148,13 +147,15 @@ export class FileListComponent implements OnInit {
private _loadFileData() { private _loadFileData() {
this.fileMetas.forEach(fm => { this.fileMetas.forEach(fm => {
this.api.getFileData(fm.id).subscribe((fd: File) => fm.file = fd); this.api.getFileData(fm.id).subscribe(response => {
fm.file = newFileFromResponse(fm, response);
});
}); });
} }
downloadFile(fm: FileMeta) { downloadFile(fm: FileMeta) {
this.api.getFileData(fm.id).subscribe(fileBlob => { this.api.getFileData(fm.id).subscribe(response => {
const blob = new Blob([fileBlob], {type: fm.content_type}); const blob = new Blob([response.body], {type: fm.content_type});
fileSaver.saveAs(blob, fm.name); fileSaver.saveAs(blob, fm.name);
}); });
} }

View File

@ -1,12 +1,14 @@
import {HttpErrorResponse} from '@angular/common/http'; import {HttpErrorResponse, HttpHeaders, HttpResponse} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {DebugNode} from '@angular/core'; import {DebugNode} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetModule, MatBottomSheetRef} from '@angular/material/bottom-sheet';
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog'; import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog';
import {MatFormFieldModule} from '@angular/material/form-field'; import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon'; import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import {MatListModule} from '@angular/material/list';
import {MatMenuModule} from '@angular/material/menu'; import {MatMenuModule} from '@angular/material/menu';
import {MatSnackBarModule} from '@angular/material/snack-bar'; import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatToolbarModule} from '@angular/material/toolbar'; import {MatToolbarModule} from '@angular/material/toolbar';
@ -56,10 +58,12 @@ describe('ModelerComponent', () => {
BrowserAnimationsModule, BrowserAnimationsModule,
FormsModule, FormsModule,
HttpClientTestingModule, HttpClientTestingModule,
MatBottomSheetModule,
MatDialogModule, MatDialogModule,
MatFormFieldModule, MatFormFieldModule,
MatIconModule, MatIconModule,
MatInputModule, MatInputModule,
MatListModule,
MatMenuModule, MatMenuModule,
MatSnackBarModule, MatSnackBarModule,
MatToolbarModule, MatToolbarModule,
@ -79,6 +83,14 @@ describe('ModelerComponent', () => {
} }
}, },
{provide: MAT_DIALOG_DATA, useValue: []}, {provide: MAT_DIALOG_DATA, useValue: []},
{
provide: MatBottomSheetRef,
useValue: {
dismiss: () => {
},
}
},
{provide: MAT_BOTTOM_SHEET_DATA, useValue: []},
{ {
provide: ActivatedRoute, useValue: { provide: ActivatedRoute, useValue: {
queryParams: of(convertToParamMap({ queryParams: of(convertToParamMap({
@ -99,8 +111,10 @@ describe('ModelerComponent', () => {
OpenFileDialogComponent, OpenFileDialogComponent,
] ]
} }
}) }).compileComponents();
.compileComponents(); }));
beforeEach(() => {
httpMock = TestBed.inject(HttpTestingController); httpMock = TestBed.inject(HttpTestingController);
fixture = TestBed.createComponent(ModelerComponent); fixture = TestBed.createComponent(ModelerComponent);
component = fixture.debugElement.componentInstance; component = fixture.debugElement.componentInstance;
@ -118,10 +132,13 @@ describe('ModelerComponent', () => {
mockFileMetas.forEach((fm, i) => { mockFileMetas.forEach((fm, i) => {
const fmReq = httpMock.expectOne(`apiRoot/file/${fm.id}/data`); const fmReq = httpMock.expectOne(`apiRoot/file/${fm.id}/data`);
const mockHeaders = new HttpHeaders()
.append('last-modified', mockFileMetas[i].file.lastModified.toString())
.append('content-type', mockFileMetas[i].content_type);
expect(fmReq.request.method).toEqual('GET'); expect(fmReq.request.method).toEqual('GET');
fmReq.flush(mockFileMetas[i].file); fmReq.flush(new ArrayBuffer(8), {headers: mockHeaders});
});
}); });
}));
afterEach(() => { afterEach(() => {
httpMock.verify(); httpMock.verify();
@ -374,12 +391,20 @@ describe('ModelerComponent', () => {
}); });
it('should load files from the database', () => { it('should load files from the database', () => {
const mockHeaders = new HttpHeaders()
.append('last-modified', mockFileMeta0.file.lastModified.toString())
.append('content-type', mockFileMeta0.content_type);
const mockResponse = new HttpResponse<ArrayBuffer>({
body: new ArrayBuffer(8),
headers: mockHeaders,
});
const getWorkflowSpecSpy = spyOn(component.api, 'getWorkflowSpecification') const getWorkflowSpecSpy = spyOn(component.api, 'getWorkflowSpecification')
.and.returnValue(of(mockWorkflowSpec0)); .and.returnValue(of(mockWorkflowSpec0));
const getFileMetasSpy = spyOn(component.api, 'getFileMetas') const getFileMetasSpy = spyOn(component.api, 'getFileMetas')
.and.returnValue(of(mockFileMetas)); .and.returnValue(of(mockFileMetas));
const getFileDataSpy = spyOn(component.api, 'getFileData') const getFileDataSpy = spyOn(component.api, 'getFileData')
.and.returnValue(of(mockFileMeta0)); .and.returnValue(of(mockResponse));
component.loadFilesFromDb(); component.loadFilesFromDb();
expect(getWorkflowSpecSpy).toHaveBeenCalled(); expect(getWorkflowSpecSpy).toHaveBeenCalled();
@ -423,6 +448,12 @@ describe('ModelerComponent', () => {
it('should get a file metadata display string', () => { it('should get a file metadata display string', () => {
expect(component.getFileMetaDisplayString(undefined)).toEqual('Loading...'); expect(component.getFileMetaDisplayString(undefined)).toEqual('Loading...');
const expectedString = 'one-fish.bpmn - v1.0 (Jan 23, 2020)'; const expectedString = 'one-fish.bpmn - v1.0 (Jan 23, 2020)';
const file = new File([], 'one-fish.bpmn', {
type: 'text/xml',
lastModified: new Date('2020-01-23T12:34:12.345Z').getTime(),
});
mockFileMeta0.file = file;
expect(component.getFileMetaDisplayString(mockFileMeta0)).toEqual(expectedString); expect(component.getFileMetaDisplayString(mockFileMeta0)).toEqual(expectedString);
}); });
@ -441,6 +472,11 @@ describe('ModelerComponent', () => {
Version: 1.0 Version: 1.0
`; `;
const file = new File([], 'one-fish.bpmn', {
type: 'text/xml',
lastModified: new Date('2020-01-23T12:34:12.345Z').getTime(),
});
mockFileMeta0.file = file;
expect(component.getFileMetaTooltipText(mockFileMeta0)).toEqual(expectedString); expect(component.getFileMetaTooltipText(mockFileMeta0)).toEqual(expectedString);
}); });

View File

@ -1,5 +1,6 @@
import {DatePipe} from '@angular/common'; import {DatePipe} from '@angular/common';
import {AfterViewInit, Component, ViewChild} from '@angular/core'; import {AfterViewInit, Component, ViewChild} from '@angular/core';
import {MatBottomSheet} from '@angular/material/bottom-sheet';
import {MatDialog} from '@angular/material/dialog'; import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar'; import {MatSnackBar} from '@angular/material/snack-bar';
import {ActivatedRoute, Params, Router} from '@angular/router'; import {ActivatedRoute, Params, Router} from '@angular/router';
@ -9,6 +10,7 @@ import {
FileType, FileType,
getDiagramTypeFromXml, getDiagramTypeFromXml,
isNumberDefined, isNumberDefined,
newFileFromResponse,
WorkflowSpec WorkflowSpec
} from 'sartography-workflow-lib'; } from 'sartography-workflow-lib';
import {FileMetaDialogComponent} from '../_dialogs/file-meta-dialog/file-meta-dialog.component'; import {FileMetaDialogComponent} from '../_dialogs/file-meta-dialog/file-meta-dialog.component';
@ -17,6 +19,7 @@ import {OpenFileDialogComponent} from '../_dialogs/open-file-dialog/open-file-di
import {BpmnWarning} from '../_interfaces/bpmn-warning'; import {BpmnWarning} from '../_interfaces/bpmn-warning';
import {FileMetaDialogData, NewFileDialogData, OpenFileDialogData} from '../_interfaces/dialog-data'; import {FileMetaDialogData, NewFileDialogData, OpenFileDialogData} from '../_interfaces/dialog-data';
import {ImportEvent} from '../_interfaces/import-event'; import {ImportEvent} from '../_interfaces/import-event';
import {ApiErrorsComponent} from '../api-errors/api-errors.component';
import {DiagramComponent} from '../diagram/diagram.component'; import {DiagramComponent} from '../diagram/diagram.component';
@Component({ @Component({
@ -46,6 +49,7 @@ export class ModelerComponent implements AfterViewInit {
constructor( constructor(
private api: ApiService, private api: ApiService,
private bottomSheet: MatBottomSheet,
private snackBar: MatSnackBar, private snackBar: MatSnackBar,
public dialog: MatDialog, public dialog: MatDialog,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -76,11 +80,11 @@ export class ModelerComponent implements AfterViewInit {
} = event; } = event;
if (type === 'success') { if (type === 'success') {
console.log(`Rendered diagram (${warnings.length} warnings)`); this.snackBar.open(`Rendered diagram with ${warnings.length} warnings`, 'Ok', {duration: 5000});
} }
if (type === 'error') { if (type === 'error') {
console.error('Failed to render diagram', error); this.bottomSheet.open(ApiErrorsComponent, {data: {apiErrors: [error]}});
} }
this.importError = error; this.importError = error;
@ -120,7 +124,7 @@ export class ModelerComponent implements AfterViewInit {
this.xml = (event.target as FileReader).result.toString(); this.xml = (event.target as FileReader).result.toString();
const diagramType = getDiagramTypeFromXml(this.xml); const diagramType = getDiagramTypeFromXml(this.xml);
this.diagramComponent.openDiagram(this.xml, diagramType); this.diagramComponent.openDiagram(this.xml, diagramType);
} };
readFile(file: File) { readFile(file: File) {
// FileReader must be instantiated this way so unit test can spy on it. // FileReader must be instantiated this way so unit test can spy on it.
@ -204,9 +208,13 @@ export class ModelerComponent implements AfterViewInit {
} }
getFileMetaDisplayString(fileMeta: FileMeta) { getFileMetaDisplayString(fileMeta: FileMeta) {
console.log(fileMeta);
if (fileMeta) { if (fileMeta) {
const lastUpdated = new DatePipe('en-us').transform(fileMeta.last_updated); const lastUpdated = new DatePipe('en-us').transform(fileMeta.file.lastModified);
return `${fileMeta.name} - v${fileMeta.version} (${lastUpdated})`; return fileMeta.name +
(fileMeta.latest_version ? ` - v${fileMeta.latest_version}` : '') +
(lastUpdated ? ` (${lastUpdated})` : '');
} else { } else {
return 'Loading...'; return 'Loading...';
} }
@ -216,7 +224,11 @@ export class ModelerComponent implements AfterViewInit {
const spec = this.workflowSpec; const spec = this.workflowSpec;
if (spec) { if (spec) {
const lastUpdated = new DatePipe('en-us').transform(fileMeta.last_updated); 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 ` return `
Workflow spec ID: ${spec.id} Workflow spec ID: ${spec.id}
Workflow name: ${spec.name} Workflow name: ${spec.name}
@ -224,7 +236,7 @@ export class ModelerComponent implements AfterViewInit {
Description: ${spec.description} Description: ${spec.description}
File name: ${fileMeta.name} File name: ${fileMeta.name}
Last updated: ${lastUpdated} Last updated: ${lastUpdated}
Version: ${fileMeta.version} Version: ${fileMeta.latest_version}
`; `;
} else { } else {
return 'Loading...'; return 'Loading...';
@ -237,10 +249,10 @@ export class ModelerComponent implements AfterViewInit {
this.api.getFileMetas({workflow_spec_id: wfs.id}).subscribe(files => { this.api.getFileMetas({workflow_spec_id: wfs.id}).subscribe(files => {
this.bpmnFiles = []; this.bpmnFiles = [];
files.forEach(f => { files.forEach(f => {
this.api.getFileData(f.id).subscribe(d => { this.api.getFileData(f.id).subscribe(response => {
if ((f.type === FileType.BPMN) || (f.type === FileType.DMN)) { if ((f.type === FileType.BPMN) || (f.type === FileType.DMN)) {
f.content_type = 'text/xml'; f.content_type = 'text/xml';
f.file = new File([d], f.name, {type: f.content_type}); f.file = newFileFromResponse(f, response);
this.bpmnFiles.push(f); this.bpmnFiles.push(f);
if (f.id === this.fileMetaId) { if (f.id === this.fileMetaId) {