From 701945a140777835207261970a26f6a364ac73d7 Mon Sep 17 00:00:00 2001 From: Lucas Li <35748253+yzlucas@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:07:11 -0700 Subject: [PATCH 1/5] handle other attachment type (#594) --- .../add-attachment.component.spec.ts | 2 +- .../add-attachment.component.ts | 3 +- .../project-files.component.spec.ts | 61 +++++++++++++++---- .../project-files/project-files.component.ts | 37 ++++++++--- terraform/.terraform/modules/network | 1 + 5 files changed, 82 insertions(+), 22 deletions(-) create mode 160000 terraform/.terraform/modules/network diff --git a/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.spec.ts b/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.spec.ts index 528cf9a08..0b4c5066c 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.spec.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.spec.ts @@ -137,7 +137,7 @@ describe('AddAttachmentComponent', () => { it('should return correct file types for "Other"', () => { component.attachmentType = 'Other'; const result = component.getAcceptedFileTypes(); - expect(result).toBe('.pdf,.doc,.docx,.jpg,.png'); + expect(result).toBe(''); }); it('should return an empty string for unknown attachmentType', () => { diff --git a/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.ts b/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.ts index 85c1a3c71..ff78ed703 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/add-attachment/add-attachment.component.ts @@ -75,9 +75,10 @@ export class AddAttachmentComponent { getAcceptedFileTypes(): string { switch (this.attachmentType) { case 'Gross Project Area Boundary': + case 'Activity Polygon': return '.kml,.kmz,.shp,.gdb,.zip'; case 'Other': - return '.pdf,.doc,.docx,.jpg,.png'; + return ''; default: return ''; } diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts index 1a1753531..6e47be1f7 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts @@ -198,7 +198,7 @@ describe('ProjectFilesComponent', () => { it('should open file upload modal and call uploadFile if a file is selected', () => { const mockFile = new File(['content'], 'test-file.txt', { type: 'text/plain' }); mockDialog.open.and.returnValue({ - afterClosed: () => of({ file: mockFile }), + afterClosed: () => of({ file: mockFile, type: 'Activity Polygon' }), } as any); spyOn(component, 'uploadFile').and.stub(); @@ -208,7 +208,7 @@ describe('ProjectFilesComponent', () => { width: '1000px', data: { indicator: 'project-files' }, }); - expect(component.uploadFile).toHaveBeenCalledWith(mockFile); + expect(component.uploadFile).toHaveBeenCalledWith(mockFile, 'Activity Polygon'); }); it('should not call uploadFile if modal is closed without a file', () => { @@ -245,17 +245,17 @@ describe('ProjectFilesComponent', () => { spyOn(component, 'uploadAttachment').and.stub(); - component.uploadFile(mockFile); + component.uploadFile(mockFile, 'Activity Polygon'); expect(mockProjectService.uploadDocument).toHaveBeenCalledWith({ file: mockFile }); - expect(component.uploadAttachment).toHaveBeenCalledWith(mockFile, response); + expect(component.uploadAttachment).toHaveBeenCalledWith(mockFile, response, 'Activity Polygon'); }); it('should handle file upload error', () => { const mockFile = new File(['content'], 'test-file.txt', { type: 'text/plain' }); mockProjectService.uploadDocument.and.returnValue(throwError(() => new Error('Upload failed'))); - component.uploadFile(mockFile); + component.uploadFile(mockFile, 'Activity Polygon'); expect(mockProjectService.uploadDocument).toHaveBeenCalledWith({ file: mockFile }); expect(mockSnackbar.open).toHaveBeenCalledWith( @@ -285,7 +285,7 @@ describe('ProjectFilesComponent', () => { spyOn(component, 'updateProjectBoundary').and.stub(); spyOn(component, 'loadProjectAttachments').and.stub(); - component.uploadAttachment(mockFile, response); + component.uploadAttachment(mockFile, response, 'Activity Polygon'); expect(mockAttachmentService.createProjectAttachment).toHaveBeenCalledWith( mockProjectGuid, @@ -314,7 +314,7 @@ describe('ProjectFilesComponent', () => { throwError(() => new Error('Failed to create attachment')) ); - component.uploadAttachment(mockFile, response); + component.uploadAttachment(mockFile, response, 'Activity Polygon'); expect(mockAttachmentService.createProjectAttachment).toHaveBeenCalled(); expect(console.log).toHaveBeenCalledWith('Failed to upload attachment: ', jasmine.any(Error)); @@ -543,7 +543,7 @@ describe('ProjectFilesComponent', () => { it('should show error if uploaded file has no extension', () => { const mockFile = new File(['content'], 'file.', { type: 'text/plain' }); - component.uploadAttachment(mockFile, { fileId: 'some-id' }); + component.uploadAttachment(mockFile, { fileId: 'some-id' }, 'Activity Polygon'); expect(mockSnackbar.open).toHaveBeenCalledWith( 'The spatial file was not uploaded because the file format is not accepted.', @@ -620,7 +620,7 @@ describe('ProjectFilesComponent', () => { const description = 'my description'; mockDialog.open.and.returnValue({ - afterClosed: () => of({ file: mockFile, description }), + afterClosed: () => of({ file: mockFile, description, type: 'Activity Polygon' }), } as any); spyOn(component, 'uploadFile').and.stub(); @@ -628,7 +628,7 @@ describe('ProjectFilesComponent', () => { component.openFileUploadModal(); expect(component.attachmentDescription).toBe(description); - expect(component.uploadFile).toHaveBeenCalledWith(mockFile); + expect(component.uploadFile).toHaveBeenCalledWith(mockFile, 'Activity Polygon'); }); }); @@ -796,7 +796,7 @@ describe('ProjectFilesComponent', () => { mockAttachmentService.createActivityAttachment.and.returnValue(of(mockResponse)); mockSpatialService.extractCoordinates.and.returnValue(Promise.resolve(mockCoordinates)); - await component.uploadAttachment(mockFile, { fileId: 'file-xyz' }); + await component.uploadAttachment(mockFile, { fileId: 'file-xyz' }, 'Activity Polygon'); expect(mockAttachmentService.createActivityAttachment).toHaveBeenCalledWith( 'project-guid', @@ -815,5 +815,44 @@ describe('ProjectFilesComponent', () => { expect(component.updateActivityBoundary).toHaveBeenCalledWith(mockFile, mockCoordinates); }); + describe('finishWithoutGeometry', () => { + beforeEach(() => { + spyOn(component.filesUpdated, 'emit'); + spyOn(component, 'loadActivityAttachments'); + spyOn(component, 'loadProjectAttachments'); + }); + + it('should show snackbar, call loadActivityAttachments and emit event when isActivityContext is true', () => { + component.fiscalGuid = 'fiscal-guid'; + component.activityGuid = 'activity-guid'; // triggers activity context + + (component as any).finishWithoutGeometry(); // or make method public + + expect(mockSnackbar.open).toHaveBeenCalledWith( + 'File uploaded successfully.', + 'Close', + jasmine.objectContaining({ duration: 5000, panelClass: 'snackbar-success' }) + ); + expect(component.loadActivityAttachments).toHaveBeenCalled(); + expect(component.loadProjectAttachments).not.toHaveBeenCalled(); + expect(component.filesUpdated.emit).toHaveBeenCalled(); + }); + + it('should show snackbar, call loadProjectAttachments and emit event when isActivityContext is false', () => { + component.fiscalGuid = ''; + component.activityGuid = ''; // not an activity context + + (component as any).finishWithoutGeometry(); // or make method public + + expect(mockSnackbar.open).toHaveBeenCalledWith( + 'File uploaded successfully.', + 'Close', + jasmine.objectContaining({ duration: 5000, panelClass: 'snackbar-success' }) + ); + expect(component.loadProjectAttachments).toHaveBeenCalled(); + expect(component.loadActivityAttachments).not.toHaveBeenCalled(); + expect(component.filesUpdated.emit).toHaveBeenCalled(); + }); + }); }); \ No newline at end of file diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts index b05be472f..0eec063f8 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts @@ -155,7 +155,8 @@ export class ProjectFilesComponent implements OnInit { dialogRef.afterClosed().subscribe(result => { if (result?.file) { - this.uploadFile(result.file); + const selectedType = result.type; + this.uploadFile(result.file, selectedType); } if (result?.description) { this.attachmentDescription = result.description; @@ -163,11 +164,11 @@ export class ProjectFilesComponent implements OnInit { }) } - uploadFile(file: File): void { + uploadFile(file: File, type: string): void { this.projectService.uploadDocument({ file }).subscribe({ next: (response) => { if (response) { - this.uploadAttachment(file, response); + this.uploadAttachment(file, response, type); } }, error: () => { @@ -179,7 +180,7 @@ export class ProjectFilesComponent implements OnInit { }); } - uploadAttachment(file: File, response: any): void { + uploadAttachment(file: File, response: any, type: string): void { const fileExtension = file.name.split('.').pop()?.toLowerCase(); if (!fileExtension) { this.snackbarService.open('The spatial file was not uploaded because the file format is not accepted.', 'Close', { @@ -206,11 +207,15 @@ export class ProjectFilesComponent implements OnInit { if (response) { this.uploadedBy = response?.uploadedByUserId; - this.spatialService.extractCoordinates(file).then(response => { - if (response) { - this.updateActivityBoundary(file, response) - } - }) + if (type === 'Other') { + this.finishWithoutGeometry(); + } else{ + this.spatialService.extractCoordinates(file).then(response => { + if (response) { + this.updateActivityBoundary(file, response) + } + }) + } } } }) @@ -235,6 +240,20 @@ export class ProjectFilesComponent implements OnInit { } } + finishWithoutGeometry() { + this.snackbarService.open('File uploaded successfully.', 'Close', { + duration: 5000, + panelClass: 'snackbar-success', + }); + + if (this.isActivityContext) { + this.loadActivityAttachments(); + } else { + this.loadProjectAttachments(); + } + this.filesUpdated.emit(); + } + updateProjectBoundary(file: File, response: Position[][][]) { const now = new Date(); const futureDate = new Date(now); diff --git a/terraform/.terraform/modules/network b/terraform/.terraform/modules/network new file mode 160000 index 000000000..211ab9ba8 --- /dev/null +++ b/terraform/.terraform/modules/network @@ -0,0 +1 @@ +Subproject commit 211ab9ba87f8e29f6654fb1286dac8f85f3bc53f From 20707176b6e27fc2545effd3d047a86032139f31 Mon Sep 17 00:00:00 2001 From: Lucas Li <35748253+yzlucas@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:39:21 -0700 Subject: [PATCH 2/5] Upload other (#596) From 61fd5a7f11ef4f9880f4e8b409be929c02fc6db1 Mon Sep 17 00:00:00 2001 From: Lucas Li <35748253+yzlucas@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:43:25 -0700 Subject: [PATCH 3/5] remove modules (#598) --- .../project-files/project-files.component.spec.ts | 8 ++++---- terraform/.terraform/modules/network | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 160000 terraform/.terraform/modules/network diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts index 6e47be1f7..618b9a1c0 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts @@ -824,9 +824,9 @@ describe('ProjectFilesComponent', () => { it('should show snackbar, call loadActivityAttachments and emit event when isActivityContext is true', () => { component.fiscalGuid = 'fiscal-guid'; - component.activityGuid = 'activity-guid'; // triggers activity context + component.activityGuid = 'activity-guid'; - (component as any).finishWithoutGeometry(); // or make method public + (component as any).finishWithoutGeometry(); expect(mockSnackbar.open).toHaveBeenCalledWith( 'File uploaded successfully.', @@ -840,9 +840,9 @@ describe('ProjectFilesComponent', () => { it('should show snackbar, call loadProjectAttachments and emit event when isActivityContext is false', () => { component.fiscalGuid = ''; - component.activityGuid = ''; // not an activity context + component.activityGuid = ''; - (component as any).finishWithoutGeometry(); // or make method public + (component as any).finishWithoutGeometry(); expect(mockSnackbar.open).toHaveBeenCalledWith( 'File uploaded successfully.', diff --git a/terraform/.terraform/modules/network b/terraform/.terraform/modules/network deleted file mode 160000 index 211ab9ba8..000000000 --- a/terraform/.terraform/modules/network +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 211ab9ba87f8e29f6654fb1286dac8f85f3bc53f From 1d7ffe595095dace9cf78e852d144761267221c6 Mon Sep 17 00:00:00 2001 From: Lucas Li <35748253+yzlucas@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:58:13 -0700 Subject: [PATCH 4/5] sonar fix (#599) --- .../project-files/project-files.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts index 0eec063f8..0df1b6f72 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.ts @@ -6,7 +6,7 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { catchError, map, throwError } from 'rxjs'; import { AddAttachmentComponent } from 'src/app/components/add-attachment/add-attachment.component'; import { ConfirmationDialogComponent } from 'src/app/components/confirmation-dialog/confirmation-dialog.component'; -import { ActivityBoundary, FileAttachment, Project, ProjectBoundary, ProjectFile } from 'src/app/components/models'; +import { ActivityBoundary, FileAttachment, ProjectBoundary, ProjectFile } from 'src/app/components/models'; import { AttachmentService } from 'src/app/services/attachment-service'; import { ProjectService } from 'src/app/services/project-services'; import { SpatialService } from 'src/app/services/spatial-services'; @@ -38,7 +38,7 @@ export class ProjectFilesComponent implements OnInit { public readonly dialog: MatDialog, public attachmentService: AttachmentService, public spatialService: SpatialService, - private route: ActivatedRoute, + private readonly route: ActivatedRoute, ) { } messages = Messages; @@ -119,14 +119,14 @@ export class ProjectFilesComponent implements OnInit { loadActivityAttachments(): void { if (!this.fiscalGuid || !this.activityGuid) return; - this.projectGuid = this.route.snapshot?.queryParamMap?.get('projectGuid') || ''; + this.projectGuid = this.route.snapshot?.queryParamMap?.get('projectGuid') ?? ''; // Find projectPlanFiscalGuid from the activityGuid this.attachmentService.getActivityAttachments(this.projectGuid, this.fiscalGuid, this.activityGuid).subscribe({ next: (response) => { if (response?._embedded?.fileAttachment && Array.isArray(response._embedded.fileAttachment)) { const fileAttachments = response._embedded.fileAttachment.sort((a: FileAttachment, b: FileAttachment) => { - const timeA = new Date(a.uploadedByTimestamp || 0).getTime(); - const timeB = new Date(b.uploadedByTimestamp || 0).getTime(); + const timeA = new Date(a.uploadedByTimestamp ?? 0).getTime(); + const timeB = new Date(b.uploadedByTimestamp ?? 0).getTime(); return timeB - timeA; // latest first }); this.projectFiles = fileAttachments; @@ -452,7 +452,7 @@ export class ProjectFilesComponent implements OnInit { const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; - link.download = file.documentPath || 'downloaded-file'; // fallback filename + link.download = file.documentPath ?? 'downloaded-file'; // fallback filename document.body.appendChild(link); link.click(); document.body.removeChild(link); From 13ed9804a354327f0d66986941c3ccb37ddbc83e Mon Sep 17 00:00:00 2001 From: Lucas Li <35748253+yzlucas@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:54:08 -0700 Subject: [PATCH 5/5] Upload other 3 (#600) --- .../project-files.component.spec.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts index 618b9a1c0..f77cf92ae 100644 --- a/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts +++ b/client/wfprev-war/src/main/angular/src/app/components/edit-project/project-details/project-files/project-files.component.spec.ts @@ -99,6 +99,16 @@ describe('ProjectFilesComponent', () => { component.ngOnInit(); expect(component.loadProjectAttachments).toHaveBeenCalled(); }); + it('should call loadActivityAttachments when activityGuid and fiscalGuid are present', () => { + component.activityGuid = 'activity-guid'; + component.fiscalGuid = 'fiscal-guid'; + + spyOn(component, 'loadActivityAttachments'); + + component.ngOnInit(); + + expect(component.loadActivityAttachments).toHaveBeenCalled(); + }); }); describe('loadProjectAttachments', () => { @@ -320,6 +330,24 @@ describe('ProjectFilesComponent', () => { expect(console.log).toHaveBeenCalledWith('Failed to upload attachment: ', jasmine.any(Error)); }); + it('should call finishWithoutGeometry if type is "Other"', async () => { + const mockFile = new File(['test'], 'test-file.txt', { type: 'text/plain' }); + const response = { fileId: 'test-file-id' }; + const uploadResponse = { uploadedByUserId: 'tester' }; + + component.projectGuid = 'project-guid'; + component.fiscalGuid = 'fiscal-guid'; + component.activityGuid = 'activity-guid'; + + spyOn(component as any, 'finishWithoutGeometry'); + mockAttachmentService.createActivityAttachment.and.returnValue(of(uploadResponse)); + + await component.uploadAttachment(mockFile, response, 'Other'); + + expect(mockAttachmentService.createActivityAttachment).toHaveBeenCalled(); + expect(component.uploadedBy).toBe('tester'); + expect(component.finishWithoutGeometry).toHaveBeenCalled(); + }); }); describe('updateProjectBoundary', () => {