From 562adc2ce1ec437ad81b3e25fafc98929ff8ad74 Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Tue, 23 Jun 2020 09:30:38 -0700 Subject: [PATCH] NRPT-159: Mines Edit Page + Tests (#444) * NRPT-159: Mines edit page * NRPT-159: Add unit tests * NRPT-159: Code review updates * NRPT-159: code review comments - add url validator - add form validation check on Update button - minor UI tweaks - update unit tests * NRPT-159: Fix merge conflicts, adjust edit navigation * NRPT-159: Ignore necessary lint issue * NRPT-159: fix failing unit test --- .../mines-add-edit.component.html | 178 ++++++++++--- .../mines-add-edit.component.scss | 1 + .../mines-add-edit.component.spec.ts | 207 ++++++++++++++- .../mines-add-edit.component.ts | 239 +++++++++++++----- .../mines-detail/mines-detail.component.html | 2 +- .../src/app/mines/mines-resolver.ts | 16 +- .../src/app/services/factory.service.ts | 2 +- .../src/assets/styles/components/mine.scss | 71 ++++++ .../styles/components/slide-toggle.scss | 25 ++ .../src/assets/styles/themes/default.scss | 18 ++ .../projects/common/src/app/common.module.ts | 11 +- .../src/app/form-validators/validators.ts | 25 ++ .../link-add-edit.component.html | 53 ++++ .../link-add-edit.component.scss | 54 ++++ .../link-add-edit.component.spec.ts | 93 +++++++ .../link-add-edit/link-add-edit.component.ts | 61 +++++ .../common/src/app/models/bcmi/mine.ts | 8 + .../common/src/app/utils/record-constants.ts | 2 + .../assets/styles/components/drag-drop.scss | 31 +++ .../src/assets/styles/themes/default.scss | 5 + api/src/controllers/put/mine.js | 67 ++--- api/src/controllers/record-controller.js | 14 +- api/src/swagger/swagger.yaml | 2 + api/src/utils/business-logic-manager.js | 4 +- api/src/utils/business-logic-manager.test.js | 40 +++ api/src/utils/query-utils.js | 3 +- 26 files changed, 1064 insertions(+), 168 deletions(-) create mode 100644 angular/projects/admin-nrpti/src/assets/styles/components/mine.scss create mode 100644 angular/projects/admin-nrpti/src/assets/styles/components/slide-toggle.scss create mode 100644 angular/projects/common/src/app/form-validators/validators.ts create mode 100644 angular/projects/common/src/app/link-add-edit/link-add-edit.component.html create mode 100644 angular/projects/common/src/app/link-add-edit/link-add-edit.component.scss create mode 100644 angular/projects/common/src/app/link-add-edit/link-add-edit.component.spec.ts create mode 100644 angular/projects/common/src/app/link-add-edit/link-add-edit.component.ts create mode 100644 angular/projects/common/src/assets/styles/components/drag-drop.scss diff --git a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.html b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.html index 690c6dfb6..faa04ea73 100644 --- a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.html +++ b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.html @@ -1,59 +1,153 @@
-
-
-
-
-

Edit Mine Details

-
- - -
-
-
-
-
-
- - + +
+
+
+

Mine Information

+ {{ lastEditedSubText }} +
+
+
+ + {{ (myForm.get('publish').value && 'Published') || 'Unpublished' }} + + + *Changes made will take effect once you click "Update Record" + +
+
+
+
+
+
+
+
+ + {{ (mine && mine.permittee) || '-' }} +
+
+ + {{ (mine && mine.status) || '-' }} +
+
+
+
+ + + {{ (mine && mine.permitNumbers && mine.permitNumbers.length && mine.permitNumbers.join(', ')) || '-' }} + +
+
+ + {{ (mine && mine.tailingsImpoundments) || '-' }} +
+
+ + + {{ (mine && mine.commodities && mine.commodities.length && mine.commodities.join(', ')) || '-' }} + +
+
+
+
+ + {{ (mine && mine.region) || '-' }} +
+
+ + + {{ (mine && mine.location && mine.location['coordinates'] && mine.location['coordinates'][0]) || '-' }} + +
+
+ + + {{ (mine && mine.location && mine.location['coordinates'] && mine.location['coordinates'][1]) || '-' }} + +
+
+
+
+
+
+
+ + +
-
-
-
- - + rows="5" + > +
-
-
-
- - + rows="5" + > +
- -
-
- - + +
+
+
+

External Links & Resources

+
-
+ +
+ +
+
+
+
+

Records

+
+
+
+
+ +
+
+ + +
diff --git a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.scss b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.scss index e69de29bb..43a40fc79 100644 --- a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.scss +++ b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.scss @@ -0,0 +1 @@ +@import 'assets/styles/components/mine.scss'; diff --git a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.spec.ts b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.spec.ts index d0e053031..6fa8bc1cc 100644 --- a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.spec.ts +++ b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.spec.ts @@ -1,15 +1,14 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { async, TestBed } from '@angular/core/testing'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MinesAddEditComponent } from './mines-add-edit.component'; -import { TestBedHelper, ActivatedRouteStub } from '../../../../../common/src/app/spec/spec-utils'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, FormArray } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { Router, ActivatedRoute } from '@angular/router'; -import { GlobalModule } from 'nrpti-angular-components'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgxPaginationModule } from 'ngx-pagination'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { Utils } from 'nrpti-angular-components'; -import { LoadingScreenService } from 'nrpti-angular-components'; +import { GlobalModule, LoadingScreenService, Utils } from 'nrpti-angular-components'; +import { Mine, Link } from '../../../../../common/src/app/models/bcmi'; +import { ActivatedRouteStub, TestBedHelper } from '../../../../../common/src/app/spec/spec-utils'; +import { MinesAddEditComponent } from './mines-add-edit.component'; describe('MinesAddEditComponent', () => { const testBedHelper = new TestBedHelper(MinesAddEditComponent); @@ -53,4 +52,194 @@ describe('MinesAddEditComponent', () => { expect(component).toBeTruthy(); }); + + describe('togglePublish', () => { + it('sets publish control to true', () => { + const { component } = testBedHelper.createComponent(); + + // stub component + component.isFormValid = () => true; + + component.myForm = new FormGroup({ + publish: new FormControl(false) + }); + + component.togglePublish({ checked: true }); + + expect(component.myForm.get('publish').value).toEqual(true); + }); + + it('sets publish control to false', () => { + const { component } = testBedHelper.createComponent(); + + // stub component + component.isFormValid = () => true; + + component.myForm = new FormGroup({ + publish: new FormControl(true) + }); + + component.togglePublish({ checked: false }); + + expect(component.myForm.get('publish').value).toEqual(false); + }); + }); + + describe('getLinksFormGroups', () => { + it('builds an array of form groups from the mine record links', () => { + const { component } = testBedHelper.createComponent(); + + component.mine = new Mine({ + links: [ + { title: 'titleA', url: 'urlA' }, + { title: 'titleB', url: 'urlB' } + ] + }); + + const formGroups: FormGroup[] = component.getLinksFormGroups(); + + expect(formGroups.length).toEqual(2); + expect(formGroups[0].get('title').value).toEqual('titleA'); + expect(formGroups[0].get('url').value).toEqual('urlA'); + expect(formGroups[1].get('title').value).toEqual('titleB'); + expect(formGroups[1].get('url').value).toEqual('urlB'); + }); + + it('builds an empty array when mine links are empty', () => { + const { component } = testBedHelper.createComponent(); + + component.mine = new Mine({ + links: [] + }); + + const formGroups: FormGroup[] = component.getLinksFormGroups(); + + expect(formGroups.length).toEqual(0); + }); + + it('builds an empty array when mine links is null', () => { + const { component } = testBedHelper.createComponent(); + + component.mine = new Mine(); + + const formGroups: FormGroup[] = component.getLinksFormGroups(); + + expect(formGroups.length).toEqual(0); + }); + }); + + describe('parseLinksFormGroups', () => { + it('builds an array of links from links FormArray', () => { + const { component } = testBedHelper.createComponent(); + + // stub component + component.isFormValid = () => true; + + component.myForm = new FormGroup({ + links: new FormArray([ + new FormGroup({ + title: new FormControl('titleA'), + url: new FormControl('urlA') + }), + new FormGroup({ + title: new FormControl('titleB'), + url: new FormControl('urlB') + }), + new FormGroup({ + title: new FormControl(''), + url: new FormControl('') + }) + ]) + }); + + const links: Link[] = component.parseLinksFormGroups() as Link[]; + + expect(links.length).toEqual(2); + expect(links[0].title).toEqual('titleA'); + expect(links[0].url).toEqual('urlA'); + expect(links[1].title).toEqual('titleB'); + expect(links[1].url).toEqual('urlB'); + }); + + it('builds an empty array when formArray is empty', () => { + const { component } = testBedHelper.createComponent(); + + // stub component + component.isFormValid = () => true; + + component.myForm = new FormGroup({ + links: new FormArray([]) + }); + const links: object[] = component.parseLinksFormGroups(); + + expect(links.length).toEqual(0); + }); + }); + + describe('populateTextFields', () => { + it('sets the lastEditedSubText if dateUpdated is not null', () => { + const { component } = testBedHelper.createComponent(); + + component.mine = new Mine({ dateUpdated: new Date() }); + + component.populateTextFields(); + + expect(component.lastEditedSubText).toContain('Last Edited on '); + }); + + it('sets the lastEditedSubText if dateUpdated is null', () => { + const { component } = testBedHelper.createComponent(); + + component.mine = new Mine({ dateUpdated: null }); + + component.populateTextFields(); + + expect(component.lastEditedSubText).toContain('Added on '); + }); + }); + + describe('buildMineObject', () => { + it('parses the dirty form controls into an object', () => { + const { component } = testBedHelper.createComponent(); + + // stub component + component.isFormValid = () => true; + + component.mine = new Mine({ _id: '123' }); + + component.myForm = new FormGroup({ + description: new FormControl('descriptionA'), + summary: new FormControl('summaryB'), + type: new FormControl('typeC'), + links: new FormArray([ + new FormGroup({ + title: new FormControl('title1'), + url: new FormControl('url1') + }), + new FormGroup({ + title: new FormControl('title2'), + url: new FormControl('url2') + }) + ]), + publish: new FormControl(true) + }); + + component.myForm.get('description').markAsDirty(); + component.myForm.get('summary').markAsDirty(); + component.myForm.get('type').markAsDirty(); + component.myForm.get('links').markAsDirty(); + component.myForm.get('publish').markAsDirty(); + + const mineObject = component.buildMineObject(); + + expect(mineObject['description']).toEqual('descriptionA'); + expect(mineObject['summary']).toEqual('summaryB'); + expect(mineObject['type']).toEqual('typeC'); + expect(mineObject['links']).toEqual([ + new Link({ title: 'title1', url: 'url1' }), + new Link({ title: 'title2', url: 'url2' }) + ]); + expect(mineObject['addRole']).toEqual('public'); + }); + }); }); diff --git a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.ts b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.ts index c04bf5036..b565ba409 100644 --- a/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.ts +++ b/angular/projects/admin-nrpti/src/app/mines/mines-add-edit/mines-add-edit.component.ts @@ -1,11 +1,13 @@ -import { Component, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core'; +import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { FormControl, FormGroup, FormArray } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; +import { LoadingScreenService, Utils } from 'nrpti-angular-components'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs/Subject'; -import { FormGroup, FormControl } from '@angular/forms'; -import { LoadingScreenService } from 'nrpti-angular-components'; -import { Mine } from '../../../../../common/src/app/models/bcmi/mine'; +import { Mine, Link } from '../../../../../common/src/app/models/bcmi/mine'; import { FactoryService } from '../../services/factory.service'; +import { Picklists } from '../../../../../common/src/app/utils/record-constants'; +import { UrlValidator } from '../../../../../common/src/app/form-validators/validators'; @Component({ selector: 'app-mines-add-edit', @@ -16,105 +18,216 @@ export class MinesAddEditComponent implements OnInit, OnDestroy { private ngUnsubscribe: Subject = new Subject(); public loading = true; - public isEditing = false; - public record; + public mine: Mine; public myForm: FormGroup; public lastEditedSubText = null; - // Flavour data - public nrcedFlavour = null; - public lngFlavour = null; - public lngPublishSubtext = 'Not published'; - public nrcedPublishSubtext = 'Not published'; - - // mine types - public types = ['Coal', 'Metal', 'Industrial Mineral', 'Sand & Gravel']; + public mineTypes = Picklists.mineTypes; constructor( public route: ActivatedRoute, public router: Router, private factoryService: FactoryService, private loadingScreenService: LoadingScreenService, + private utils: Utils, private _changeDetectionRef: ChangeDetectorRef ) {} ngOnInit() { + this.loadingScreenService.setLoadingState(true, 'main'); + this.route.data.pipe(takeUntil(this.ngUnsubscribe)).subscribe((res: any) => { - this.isEditing = res.breadcrumb !== 'Add Mine'; - if (this.isEditing) { - if (res && res.record && res.record[0] && res.record[0].data) { - this.record = new Mine(res.record[0].data); - } else { - alert('Error: could not load edit mine.'); - this.router.navigate(['mines']); - } + if (res && res.mine && res.mine[0] && res.mine[0].data) { + this.mine = new Mine(res.mine[0].data); } else { - // Add - if (res && res.record) { - this.record = new Mine(res.record); - } + alert('Error: could not load edit mine.'); + this.router.navigate(['mines']); } + this.buildForm(); + this.loading = false; + this.loadingScreenService.setLoadingState(false, 'main'); + this._changeDetectionRef.detectChanges(); }); } - private buildForm() { + /** + * Derive static text strings. + * + * @memberof MinesAddEditComponent + */ + populateTextFields() { + if (this.mine && this.mine.dateUpdated) { + this.lastEditedSubText = `Last Edited on ${this.utils.convertJSDateToString(new Date(this.mine.dateUpdated))}`; + } else { + this.lastEditedSubText = `Added on ${this.utils.convertJSDateToString(new Date(this.mine.dateAdded))}`; + } + } + + /** + * Build the formcontrols. + * + * @memberof MinesAddEditComponent + */ + buildForm() { this.myForm = new FormGroup({ - description: new FormControl((this.record && this.record.description) || ''), - summary: new FormControl((this.record && this.record.summary) || ''), - type: new FormControl((this.record && this.record.type) || ''), - links: new FormControl((this.record && this.record.links.join()) || '') + description: new FormControl((this.mine && this.mine.description) || ''), + summary: new FormControl((this.mine && this.mine.summary) || ''), + type: new FormControl((this.mine && this.mine.type) || ''), + links: new FormArray(this.getLinksFormGroups()), + publish: new FormControl((this.mine && this.mine.read.includes('public')) || false) + }); + } + + /** + * Builds an array of links FormGroups, each with its own set of FormControls. + * + * @returns {FormGroup[]} array of links FormGroup elements + * @memberof MinesAddEditComponent + */ + getLinksFormGroups(): FormGroup[] { + if (!this.mine || !this.mine.links || !this.mine.links.length) { + return []; + } + + const links: FormGroup[] = []; + + this.mine.links.forEach((link: Link) => { + links.push( + new FormGroup({ + title: new FormControl(link.title || ''), + url: new FormControl(link.url || '', UrlValidator) + }) + ); + }); + + return links; + } + + /** + * Parses an array of links FormGroups into objects expected by the API. + * + * @returns {Link[]} array of Links + * @memberof MinesAddEditComponent + */ + parseLinksFormGroups(): Link[] { + const linksFormArray = this.myForm.get('links'); + + if (!linksFormArray || !linksFormArray.value || !linksFormArray.value.length) { + return []; + } + + const links: Link[] = []; + + linksFormArray.value.forEach(linkFormGroup => { + // don't include empty links + if (linkFormGroup.title || linkFormGroup.url) { + links.push( + new Link({ + title: linkFormGroup.title, + url: linkFormGroup.url + }) + ); + } }); + + return links; } + /** + * Tracks type select formcontrol changes. + * + * @param {*} optionA + * @param {*} optionB + * @returns {boolean} + * @memberof MinesAddEditComponent + */ compareTypeSelection(optionA: any, optionB: any): boolean { return optionA === optionB; } - navigateBack() { - if (this.isEditing) { - this.router.navigate(['mines', this.record._id, 'detail']); - } else { - this.router.navigate(['mines']); + cancel() { + const shouldCancel = confirm( + 'Leaving this page will discard unsaved changes. Are you sure you would like to continue?' + ); + if (shouldCancel) { + this.router.navigate(['mines', this.mine._id, 'detail']); } } - async submit() { - this.loadingScreenService.setLoadingState(true, 'main'); + /** + * Toggle the publish formcontrol. + * + * @param {*} event + * @memberof MinesAddEditComponent + */ + togglePublish(event) { + this.myForm.controls.publish.setValue(event.checked); + + this._changeDetectionRef.detectChanges(); + } + + /** + * Return true if there are errors in the form data, false otherwise. + * + * @returns {boolean} + * @memberof MinesAddEditComponent + */ + isFormValid(): boolean { + if (this.myForm && this.myForm.get('links').dirty && this.myForm.get('links').invalid) { + return false; + } + + return true; + } + + /** + * Parses the form data into a mine object. + * + * @returns mine object + * @memberof MinesAddEditComponent + */ + buildMineObject() { const mineItem = {}; - mineItem['_id'] = this.record._id; - mineItem['_schemaName'] = this.record._schemaName; - mineItem['_sourceRefId'] = this.record._sourceRefId; - mineItem['name'] = this.record.name; - mineItem['permitNumbers'] = this.record.permitNumbers; - mineItem['status'] = this.record.status; - mineItem['type'] = this.myForm.controls.type.value; - mineItem['commodities'] = this.record.commodities; - mineItem['tailingsImpoundments'] = this.record.tailingsImpoundments; - mineItem['region'] = this.record.region; - mineItem['location'] = this.record.location; - mineItem['permittee'] = this.record.permitee; - mineItem['summary'] = this.myForm.controls.summary.value; - mineItem['description'] = this.myForm.controls.description.value; - mineItem['links'] = this.myForm.controls.links.value.split(','); - this.loadingScreenService.setLoadingState(false, 'main'); + mineItem['_id'] = this.mine._id; - if (!this.isEditing) { - // Add the mine item. - // Are we manually adding mines or is this import only? - this.router.navigate(['mines', this.record._id, 'detail']); - } else { - // Update the mine item. - this.factoryService.editMine(mineItem).subscribe(async res => { - this.router.navigate(['mines', this.record._id, 'detail']); - }); + this.myForm.get('type').dirty && (mineItem['type'] = this.myForm.get('type').value); + this.myForm.get('description').dirty && (mineItem['description'] = this.myForm.get('description').value); + this.myForm.get('summary').dirty && (mineItem['summary'] = this.myForm.get('summary').value); + this.myForm.get('links').dirty && (mineItem['links'] = this.parseLinksFormGroups()); + + if (this.myForm.get('publish').dirty && this.myForm.get('publish').value) { + mineItem['addRole'] = 'public'; + } else if (this.myForm.get('publish').dirty && !this.myForm.get('publish').value) { + mineItem['removeRole'] = 'public'; } + + return mineItem; + } + + /** + * Transform the form data and save. + * + * @memberof MinesAddEditComponent + */ + async submit() { + this.loadingScreenService.setLoadingState(true, 'main'); + + const mineItem = this.buildMineObject(); + + this.factoryService.editMine(mineItem).subscribe(() => { + this.loadingScreenService.setLoadingState(false, 'main'); + + this.router.navigate(['mines', this.mine._id, 'detail']); + }); } ngOnDestroy(): void { + this.loadingScreenService.setLoadingState(false, 'main'); + this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); } diff --git a/angular/projects/admin-nrpti/src/app/mines/mines-detail/mines-detail.component.html b/angular/projects/admin-nrpti/src/app/mines/mines-detail/mines-detail.component.html index 816471d91..f92ca2764 100644 --- a/angular/projects/admin-nrpti/src/app/mines/mines-detail/mines-detail.component.html +++ b/angular/projects/admin-nrpti/src/app/mines/mines-detail/mines-detail.component.html @@ -4,7 +4,7 @@

{{mine?.name}}

diff --git a/angular/projects/admin-nrpti/src/app/mines/mines-resolver.ts b/angular/projects/admin-nrpti/src/app/mines/mines-resolver.ts index 68236cf14..1a8d3c4c5 100644 --- a/angular/projects/admin-nrpti/src/app/mines/mines-resolver.ts +++ b/angular/projects/admin-nrpti/src/app/mines/mines-resolver.ts @@ -2,22 +2,14 @@ import { Injectable } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { FactoryService } from '../services/factory.service'; -import { Mine } from '../../../../common/src/app/models/bcmi/mine'; -import { of } from 'rxjs'; +import { SearchResults } from 'nrpti-angular-components'; @Injectable() -export class MinesResolver implements Resolve> { +export class MinesResolver implements Resolve> { constructor(private factoryService: FactoryService) {} - resolve(route: ActivatedRouteSnapshot): Observable { + resolve(route: ActivatedRouteSnapshot): Observable { const mineId = route.paramMap.get('mineId'); - - const schemaName = 'Mine'; - - if (mineId === null) { - return of(new Mine({_schemaName: schemaName})); - } - - return this.factoryService.getRecord(mineId, schemaName); + return this.factoryService.getRecord(mineId, 'Mine'); } } diff --git a/angular/projects/admin-nrpti/src/app/services/factory.service.ts b/angular/projects/admin-nrpti/src/app/services/factory.service.ts index 56aab3d58..471cfa9b5 100644 --- a/angular/projects/admin-nrpti/src/app/services/factory.service.ts +++ b/angular/projects/admin-nrpti/src/app/services/factory.service.ts @@ -348,7 +348,7 @@ export class FactoryService { public editMine(mine: any): Observable { const outboundObject = { - mineItem: [mine] + mines: [mine] }; return this.recordService.editRecord(outboundObject).pipe(catchError(error => this.apiService.handleError(error))); } diff --git a/angular/projects/admin-nrpti/src/assets/styles/components/mine.scss b/angular/projects/admin-nrpti/src/assets/styles/components/mine.scss new file mode 100644 index 000000000..6bff35ed3 --- /dev/null +++ b/angular/projects/admin-nrpti/src/assets/styles/components/mine.scss @@ -0,0 +1,71 @@ +@import 'assets/styles/base/base.scss'; +@import 'assets/styles/components/add-edit.scss'; +@import 'assets/styles/components/slide-toggle.scss'; + +h2 { + color: $mine-primary-font-color; + font-size: $mine-header-font-size; + font-weight: $mine-header-font-weight; + text-transform: uppercase; +} + +label { + margin-bottom: 0.25rem; + + &.sm, + :not(.med) { + font-size: $mine-body-font-size-sm; + font-weight: $mine-body-font-weight-sm; + } + + &.med { + font-size: $mine-body-font-size-md; + font-weight: $mine-body-font-weight-md; + } +} + +select { + font-size: $mine-body-font-size-md; + font-weight: $mine-body-font-weight-sm; + border: 1px solid #ababab; + + &:focus { + border-color: #5091cd; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + } +} + +textarea, +input[type='text'] { + font-size: $mine-body-font-size-md; + font-weight: $mine-body-font-weight-sm; + border-radius: 2px; + box-shadow: none; + -webkit-appearance: none; + border: 1px solid #ababab; + + &:focus { + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + border-color: #5091cd; + } +} + +@include slide-toggle($mine-published-font-color, $mine-unpublished-font-color); +.publish-toggle { + font-size: $mine-publish-font-size; + font-weight: $mine-publish-font-weight; + + &.published { + color: $mine-published-font-color; + } + + &.unpublished { + color: $mine-unpublished-font-color; + } +} + +.grey-subtext { + color: $gray6; + font-size: 15px; + font-style: italic; +} diff --git a/angular/projects/admin-nrpti/src/assets/styles/components/slide-toggle.scss b/angular/projects/admin-nrpti/src/assets/styles/components/slide-toggle.scss new file mode 100644 index 000000000..228c57721 --- /dev/null +++ b/angular/projects/admin-nrpti/src/assets/styles/components/slide-toggle.scss @@ -0,0 +1,25 @@ +// Apply custom checked/unchecked colors to mat-slide-toggle +@mixin slide-toggle($checked, $unchecked) { + ::ng-deep { + .mat-slide-toggle.mat-checked .mat-slide-toggle-thumb { + background-color: $checked; + } + .mat-slide-toggle:not(mat-checked) .mat-slide-toggle-thumb { + background-color: $unchecked; + } + + .mat-slide-toggle.mat-checked .mat-ripple-element { + background-color: $checked; + } + .mat-slide-toggle:not(.mat-checked) .mat-ripple-element { + background-color: $unchecked; + } + + .mat-slide-toggle.mat-checked .mat-slide-toggle-bar { + background-color: rgba($checked, 0.5); + } + .mat-slide-toggle:not(mat-checked) .mat-slide-toggle-bar { + background-color: rgba($unchecked, 0.5); + } + } +} diff --git a/angular/projects/admin-nrpti/src/assets/styles/themes/default.scss b/angular/projects/admin-nrpti/src/assets/styles/themes/default.scss index b6d3bee24..e251bc9a4 100644 --- a/angular/projects/admin-nrpti/src/assets/styles/themes/default.scss +++ b/angular/projects/admin-nrpti/src/assets/styles/themes/default.scss @@ -189,3 +189,21 @@ $map-popup-border-color: $white; $map-search-height: 3rem; $map-search-font-family: "myriad-pro"; $map-search-font-size: medium; // 16 px + +// Mines +$mine-header-font-size: 2rem; +$mine-header-font-weight: 700; + +$mine-primary-font-color: #313132; + +$mine-publish-font-size: 1.25rem; +$mine-publish-font-weight: 500; +$mine-published-font-color: #28a854; +$mine-unpublished-font-color: #606060; + +$mine-body-font-size-sm: 0.875rem; +$mine-body-font-size-md: 1rem; + +$mine-body-font-weight-sm: 400; +$mine-body-font-weight-md: 700; + diff --git a/angular/projects/common/src/app/common.module.ts b/angular/projects/common/src/app/common.module.ts index 3eebc4fe0..4b38aa6bb 100644 --- a/angular/projects/common/src/app/common.module.ts +++ b/angular/projects/common/src/app/common.module.ts @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule, MatCheckboxModule } from '@angular/material'; +import { DragDropModule } from '@angular/cdk/drag-drop'; // modules import { GlobalModule } from 'nrpti-angular-components'; @@ -19,6 +20,7 @@ import { EntityDetailComponent } from './entity/entity-detail/entity-detail.comp import { PenaltyAddEditComponent } from './penalty/penalty-add-edit/penalty-add-edit.component'; import { PenaltyDetailComponent as PenaltyDetailAdminComponent } from './penalty/penalty-detail-admin/penalty-detail.component'; import { PenaltyDetailComponent as PenaltyDetailPublicComponent } from './penalty/penalty-detail-public/penalty-detail.component'; +import { LinkAddEditComponent } from './link-add-edit/link-add-edit.component'; // services @@ -34,7 +36,8 @@ import { PenaltyDetailComponent as PenaltyDetailPublicComponent } from './penalt EntityDetailComponent, PenaltyAddEditComponent, PenaltyDetailAdminComponent, - PenaltyDetailPublicComponent + PenaltyDetailPublicComponent, + LinkAddEditComponent ], imports: [ NgbModule, @@ -44,7 +47,8 @@ import { PenaltyDetailComponent as PenaltyDetailPublicComponent } from './penalt FormsModule, ReactiveFormsModule, MatAutocompleteModule, - MatCheckboxModule + MatCheckboxModule, + DragDropModule ], providers: [], exports: [ @@ -58,7 +62,8 @@ import { PenaltyDetailComponent as PenaltyDetailPublicComponent } from './penalt EntityDetailComponent, PenaltyAddEditComponent, PenaltyDetailAdminComponent, - PenaltyDetailPublicComponent + PenaltyDetailPublicComponent, + LinkAddEditComponent ] }) export class CommonModule {} diff --git a/angular/projects/common/src/app/form-validators/validators.ts b/angular/projects/common/src/app/form-validators/validators.ts new file mode 100644 index 000000000..5afa58314 --- /dev/null +++ b/angular/projects/common/src/app/form-validators/validators.ts @@ -0,0 +1,25 @@ +import { AbstractControl } from '@angular/forms'; + +/** + * Validates a URL. + * + * @param {AbstractControl} control control whos value is a URL string + * @returns {({ [key: string]: any } | null)} + */ +export const UrlValidator = (control: AbstractControl): { [key: string]: any } | null => { + if (!control.value) { + // empty urls are valid + return null; + } + + try { + // will throw an error if url is invalid + // tslint:disable-next-line:no-unused-expression + new URL(control.value).href; + } catch (error) { + return { urlInvalid: true }; + } + + // url valid + return null; +}; diff --git a/angular/projects/common/src/app/link-add-edit/link-add-edit.component.html b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.html new file mode 100644 index 000000000..615498487 --- /dev/null +++ b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.html @@ -0,0 +1,53 @@ +
+
+
+ +
+
+
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+ URL is invalid. Be sure to include http:// or https:// as part of your URL. +
+
+
+ +
+
+
+
+
+
+
+ +
diff --git a/angular/projects/common/src/app/link-add-edit/link-add-edit.component.scss b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.scss new file mode 100644 index 000000000..2581003cd --- /dev/null +++ b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.scss @@ -0,0 +1,54 @@ +@import '../../assets/styles/base/base.scss'; +@import '../../assets/styles/components/drag-drop.scss'; + +$link-label-font-size-sm: 0.875rem; +$link-label-font-size-md: 1rem; + +$link-body-font-weight-sm: 400; +$link-body-font-weight-md: 700; + +$link-delete-font-color: #d8292f; +$link-delete-hover-font-color: #ff4a4a; +$link-info-font-color: #949494; + +form { + input { + font-size: $link-label-font-size-md; + font-weight: $link-body-font-weight-sm; + } + + label { + font-size: $link-label-font-size-sm; + font-weight: $link-body-font-weight-sm; + } +} + +.btn-remove { + i { + color: $link-delete-font-color; + vertical-align: none; + } + + &:hover { + color: $link-delete-hover-font-color; + background-color: transparent; + } +} + +.btn-drag { + i { + color: $link-info-font-color; + vertical-align: none; + } + + &:hover { + color: $link-delete-hover-font-color; + background-color: transparent; + } +} + +.validation-error { + font-size: $validator-font-size; + font-weight: $validator-font-weight; + color: $validator-font-color; +} diff --git a/angular/projects/common/src/app/link-add-edit/link-add-edit.component.spec.ts b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.spec.ts new file mode 100644 index 000000000..cf5605b3a --- /dev/null +++ b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.spec.ts @@ -0,0 +1,93 @@ +import { async, TestBed } from '@angular/core/testing'; +import { LinkAddEditComponent } from './link-add-edit.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { FormsModule, FormGroup, FormArray, FormControl, ReactiveFormsModule } from '@angular/forms'; +import { TestBedHelper } from '../../../../common/src/app/spec/spec-utils'; + +describe('LinkAddEditComponent', () => { + const testBedHelper = new TestBedHelper(LinkAddEditComponent); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [LinkAddEditComponent], + imports: [FormsModule, ReactiveFormsModule, NgbModule.forRoot()] + }).compileComponents(); + })); + + it('should create', () => { + const { component, fixture } = testBedHelper.createComponent(false); + + component.formArray = new FormArray([]); + + fixture.detectChanges(); + + expect(component).toBeTruthy(); + }); + + describe('addLink', () => { + it('adds a new link to the array', () => { + const { component, fixture } = testBedHelper.createComponent(false); + + // const defaultLinkFormGroup = new FormGroup({ title: new FormControl(), url: new FormControl() }); + const initialLinkFormGroup = new FormGroup({ title: new FormControl('titleA'), url: new FormControl('urlA') }); + + component.formArray = new FormArray([initialLinkFormGroup]); + + fixture.detectChanges(); + + component.addLink(); + + expect(component.formArray.value.length).toEqual(2); + expect({ ...component.formArray.value[0] }).toEqual({ title: 'titleA', url: 'urlA' }); + expect({ ...component.formArray.value[1] }).toEqual({ title: '', url: '' }); + }); + }); + + describe('dropLink', () => { + it('repositions a link item in the array', () => { + const { component, fixture } = testBedHelper.createComponent(false); + + const linkFormGroup1 = new FormGroup({ title: new FormControl('titleA'), url: new FormControl('urlA') }); + const linkFormGroup2 = new FormGroup({ title: new FormControl('titleB'), url: new FormControl('urlB') }); + const linkFormGroup3 = new FormGroup({ title: new FormControl('titleC'), url: new FormControl('urlC') }); + + component.formArray = new FormArray([linkFormGroup1, linkFormGroup2, linkFormGroup3]); + + fixture.detectChanges(); + + component.dropLink({ + previousIndex: 0, + currentIndex: 2, + item: null, + container: null, + previousContainer: null, + isPointerOverContainer: null + }); + + expect(component.formArray.value.length).toEqual(3); + expect({ ...component.formArray.value[0] }).toEqual({ title: 'titleB', url: 'urlB' }); + expect({ ...component.formArray.value[1] }).toEqual({ title: 'titleC', url: 'urlC' }); + expect({ ...component.formArray.value[2] }).toEqual({ title: 'titleA', url: 'urlA' }); + }); + }); + + describe('removeLink', () => { + it('removes a link from the array', () => { + const { component, fixture } = testBedHelper.createComponent(false); + + const linkFormGroup1 = new FormGroup({ title: new FormControl('titleA'), url: new FormControl('urlA') }); + const linkFormGroup2 = new FormGroup({ title: new FormControl('titleB'), url: new FormControl('urlB') }); + const linkFormGroup3 = new FormGroup({ title: new FormControl('titleC'), url: new FormControl('urlC') }); + + component.formArray = new FormArray([linkFormGroup1, linkFormGroup2, linkFormGroup3]); + + fixture.detectChanges(); + + component.removeLink(1); + + expect(component.formArray.value.length).toEqual(2); + expect({ ...component.formArray.value[0] }).toEqual({ title: 'titleA', url: 'urlA' }); + expect({ ...component.formArray.value[1] }).toEqual({ title: 'titleC', url: 'urlC' }); + }); + }); +}); diff --git a/angular/projects/common/src/app/link-add-edit/link-add-edit.component.ts b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.ts new file mode 100644 index 000000000..f2abe8d3a --- /dev/null +++ b/angular/projects/common/src/app/link-add-edit/link-add-edit.component.ts @@ -0,0 +1,61 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, FormArray, FormGroup } from '@angular/forms'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { UrlValidator } from '../form-validators/validators'; + +/** + * Links component with drag/drop support. + * + * @export + * @class LinkAddEditComponent + */ +@Component({ + selector: 'app-link-add-edit', + templateUrl: './link-add-edit.component.html', + styleUrls: ['./link-add-edit.component.scss'] +}) +export class LinkAddEditComponent { + @Input() formArray: FormArray; + + /** + * Add a new link to the end of the array. + * + * @memberof LinkAddEditComponent + */ + addLink() { + this.formArray.push( + new FormGroup({ + title: new FormControl(''), + url: new FormControl('', UrlValidator) + }) + ); + + this.formArray.markAsDirty(); + } + + /** + * Link has been dragged/dropped. Update the array of links to the new order. + * + * @param {CdkDragDrop} event + * @memberof LinkAddEditComponent + */ + dropLink(event: CdkDragDrop) { + const formArray = this.formArray.value; + + moveItemInArray(formArray, event.previousIndex, event.currentIndex); + + this.formArray.patchValue(formArray); + } + + /** + * Remove link from array. + * + * @param {number} idx + * @memberof LinkAddEditComponent + */ + removeLink(idx: number) { + this.formArray.removeAt(idx); + + this.formArray.markAsDirty(); + } +} diff --git a/angular/projects/common/src/app/models/bcmi/mine.ts b/angular/projects/common/src/app/models/bcmi/mine.ts index 22cb25dc2..6f62df0c9 100644 --- a/angular/projects/common/src/app/models/bcmi/mine.ts +++ b/angular/projects/common/src/app/models/bcmi/mine.ts @@ -1,3 +1,11 @@ +/** + * Link schema-field specification. + * + * Note: This is not itself a schema. This is a field of existing schema(s). + * + * @export + * @class Link + */ export class Link { title: string; url: string; diff --git a/angular/projects/common/src/app/utils/record-constants.ts b/angular/projects/common/src/app/utils/record-constants.ts index 5053b6052..e57d6d5a7 100644 --- a/angular/projects/common/src/app/utils/record-constants.ts +++ b/angular/projects/common/src/app/utils/record-constants.ts @@ -97,6 +97,8 @@ export class Picklists { public static readonly sourceSystemRefPicklist = ['nrpti', 'epic', 'nris-epd', 'cors-csv', 'lng-csv', 'ocers-csv']; + public static readonly mineTypes = ['Coal', 'Metal', 'Industrial Mineral', 'Sand & Gravel']; + /** * Contains a mapping of acts to regulations. * diff --git a/angular/projects/common/src/assets/styles/components/drag-drop.scss b/angular/projects/common/src/assets/styles/components/drag-drop.scss new file mode 100644 index 000000000..ecd97961e --- /dev/null +++ b/angular/projects/common/src/assets/styles/components/drag-drop.scss @@ -0,0 +1,31 @@ +$drag-icon-font-color: #949494; + +.drag-drop-list { + background-color: white; +} + +.cdk-drag-handle { + color: $drag-icon-font-color; + cursor: pointer; +} + +.drag-drop-placeholder { + background: #ccc; + border-radius: 4px; + min-height: 60px; + transition: transform 100ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-animating { + transition: transform 100ms cubic-bezier(0, 0, 0.2, 1); +} + +.drag-drop-container.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) { + transition: transform 100ms cubic-bezier(0, 0, 0.2, 1); +} diff --git a/angular/projects/common/src/assets/styles/themes/default.scss b/angular/projects/common/src/assets/styles/themes/default.scss index 7ffbba418..c723aae7e 100644 --- a/angular/projects/common/src/assets/styles/themes/default.scss +++ b/angular/projects/common/src/assets/styles/themes/default.scss @@ -182,3 +182,8 @@ $map-popup-border-color: $white; $map-search-height: 3rem; $map-search-font-family: "myriad-pro"; $map-search-font-size: medium; // 16 px + +// Form Validators +$validator-font-size: 0.875Rem; +$validator-font-weight: 400; +$validator-font-color: #d8292f; diff --git a/api/src/controllers/put/mine.js b/api/src/controllers/put/mine.js index 84a92b38d..814642164 100644 --- a/api/src/controllers/put/mine.js +++ b/api/src/controllers/put/mine.js @@ -1,6 +1,8 @@ const mongoose = require('mongoose'); +const MinePost = require('../post/mine'); const PutUtils = require('../../utils/put-utils'); const { SYSTEM_USER } = require('../../utils/constants/misc'); +const RECORD_TYPE = require('../../utils/constants/record-type-enum'); /** * Performs all operations necessary to edit a master Mine record and any flavours. @@ -12,23 +14,17 @@ const { SYSTEM_USER } = require('../../utils/constants/misc'); * @returns object containing the operation's status and created records */ exports.editRecord = async function(args, res, next, incomingObj) { - try{ - // TODO: Add any flavour actions here. - - const savedInspection = await this.editMaster(args, res, next, incomingObj); - - return { - status: 'success', - object: savedInspection - }; - } catch (error) { - return { - status: 'failure', - object: null, - errorMessage: error.message - }; - } -} + return await PutUtils.editRecordWithFlavours( + args, + res, + next, + incomingObj, + this.editMaster, + MinePost, + RECORD_TYPE.Mine._schemaName, + {} + ); +}; /** * Performs all operations necessary to edit a master Mine record. @@ -37,14 +33,17 @@ exports.editRecord = async function(args, res, next, incomingObj) { * @param {*} res * @param {*} next * @param {*} incomingObj - * @returns newly created mine record + * @returns object */ -exports.editMaster = async function(args, res, next, incomingObj) { - if (incomingObj._schemaName !== 'Mine') { - throw new Error('editRecord - incorrect schema type, must be Mine'); - } +exports.editMaster = function(args, res, next, incomingObj) { + delete incomingObj._id; - const Mine = mongoose.model('Mine'); + // Reject any changes to permissions + // Publishing must be done via addRole or removeRole + delete incomingObj.read; + delete incomingObj.write; + + const Mine = mongoose.model(RECORD_TYPE.Mine._schemaName); const sanitizedObj = PutUtils.validateObjectAgainstModel(Mine, incomingObj); @@ -54,15 +53,23 @@ exports.editMaster = async function(args, res, next, incomingObj) { } sanitizedObj.dateUpdated = new Date(); - // If there are args it means this is an API request and has a user. If not, this is carried out by the system so + // If there are args it means this is an API request and has a user. If not, this is carried out by the system so // use the system user. - sanitizedObj.updatedBy = args && args.swagger.params.auth_payload.displayName || SYSTEM_USER; + sanitizedObj.updatedBy = (args && args.swagger.params.auth_payload.displayName) || SYSTEM_USER; const dotNotatedObj = PutUtils.getDotNotation(sanitizedObj); - return await Mine.findOneAndUpdate( - { _schemaName: 'Mine', _id: incomingObj._id, write: { $in: args.swagger.params.auth_payload.realm_access.roles } }, - { $set: dotNotatedObj }, - { new: true } - ); + const updateObj = { $set: dotNotatedObj, $addToSet: {}, $pull: {} }; + + if (incomingObj.addRole && incomingObj.addRole === 'public') { + updateObj.$addToSet['read'] = 'public'; + updateObj.$set['datePublished'] = new Date(); + updateObj.$set['publishedBy'] = (args && args.swagger.params.auth_payload.displayName) || SYSTEM_USER; + } else if (incomingObj.removeRole && incomingObj.removeRole === 'public') { + updateObj.$pull['read'] = 'public'; + updateObj.$set['datePublished'] = null; + updateObj.$set['publishedBy'] = ''; + } + + return updateObj; }; diff --git a/api/src/controllers/record-controller.js b/api/src/controllers/record-controller.js index 8a549c42a..e4f3b9ded 100644 --- a/api/src/controllers/record-controller.js +++ b/api/src/controllers/record-controller.js @@ -19,7 +19,7 @@ let AddConstructionPlan = require('./post/construction-plan'); let AddManagementPlan = require('./post/management-plan'); let AddCourtConviction = require('./post/court-conviction'); let AddNewsItem = require('./post/news-item'); -let AddNewMine = require('./post/mine'); +let AddMine = require('./post/mine'); let EditOrder = require('./put/order'); let EditInspection = require('./put/inspection'); @@ -36,7 +36,7 @@ let EditConstructionPlan = require('./put/construction-plan'); let EditManagementPlan = require('./put/management-plan'); let EditCourtConviction = require('./put/court-conviction'); let EditNewsItem = require('./put/news-item'); -let EditMines = require('./put/mine'); +let EditMine = require('./put/mine'); // let allowedFields = ['_createdBy', 'createdDate', 'description', 'publishDate', 'type']; @@ -166,6 +166,9 @@ exports.protectedPost = async function (args, res, next) { if (data.newsItems) { promises.push(processPostRequest(args, res, next, 'newsItems', data.newsItems)); } + if (data.mines) { + promises.push(processPostRequest(args, res, next, 'mines', data.mines)); + } let response = await Promise.all(promises); @@ -248,6 +251,9 @@ exports.protectedPut = async function (args, res, next) { if (data.newsItems) { promises.push(processPutRequest(args, res, next, 'newsItems', data.newsItems)); } + if (data.mines) { + promises.push(processPutRequest(args, res, next, 'mines', data.mines)); + } let response = await Promise.all(promises); @@ -444,7 +450,7 @@ const processPostRequest = async function (args, res, next, property, data) { promises.push(AddNewsItem.createRecord(args, res, next, data[i])); break; case 'mines': - promises.push(AddNewMine.createRecord(args, res, next, data[i])); + promises.push(AddMine.createRecord(args, res, next, data[i])); break; default: return { @@ -525,7 +531,7 @@ const processPutRequest = async function (args, res, next, property, data) { promises.push(EditNewsItem.editRecord(args, res, next, data[i])); break; case 'mines': - promises.push(EditMines.editRecord(args, res, next, data[i])); + promises.push(EditMine.editRecord(args, res, next, data[i])); break; default: return { diff --git a/api/src/swagger/swagger.yaml b/api/src/swagger/swagger.yaml index a2b21df8c..d3993d877 100644 --- a/api/src/swagger/swagger.yaml +++ b/api/src/swagger/swagger.yaml @@ -67,6 +67,7 @@ definitions: - CourtConviction - Inspection - ManagementPlan + - Mine - Order - Permit - RestorativeJustice @@ -104,6 +105,7 @@ definitions: - CourtConviction - Inspection - ManagementPlan + - Mine - Order - Permit - RestorativeJustice diff --git a/api/src/utils/business-logic-manager.js b/api/src/utils/business-logic-manager.js index bf952629d..bf7acbeba 100644 --- a/api/src/utils/business-logic-manager.js +++ b/api/src/utils/business-logic-manager.js @@ -134,7 +134,7 @@ exports.isIssuedToConsideredAnonymous = isIssuedToConsideredAnonymous; * @returns true if the document is considered anonymous, false otherwise. */ function isDocumentConsideredAnonymous(masterRecord) { - if (!masterRecord) { + if (!masterRecord || !masterRecord.documents || !masterRecord.documents.length) { // can't determine if document is anonymous or not, must assume anonymous return true; } @@ -152,7 +152,7 @@ exports.isDocumentConsideredAnonymous = isDocumentConsideredAnonymous; * @returns savedDocuments */ exports.updateDocumentRoles = async function(masterRecord, auth_payload) { - if (!masterRecord) { + if (!masterRecord || !masterRecord.documents || !masterRecord.documents.length) { return null; } diff --git a/api/src/utils/business-logic-manager.test.js b/api/src/utils/business-logic-manager.test.js index a2a72c386..fbd6297f1 100644 --- a/api/src/utils/business-logic-manager.test.js +++ b/api/src/utils/business-logic-manager.test.js @@ -25,3 +25,43 @@ describe('applyBusinessLogicToAct', () => { expect(result).toEqual('Fisheries Act (Canada)'); }); }); + +describe('updateDocumentRoles', () => { + it('returns null if null masterRecord paramter provided ', async () => { + const result = await BusinessLogicManager.updateDocumentRoles(null); + + expect(result).toBe(null); + }); + + it('returns null if masterRecord.documents is null', async () => { + const result = await BusinessLogicManager.updateDocumentRoles({ documents: null }); + + expect(result).toBe(null); + }); + + it('returns null if masterRecord.documents is empty', async () => { + const result = await BusinessLogicManager.updateDocumentRoles({ documents: [] }); + + expect(result).toBe(null); + }); +}); + +describe('isDocumentConsideredAnonymous', () => { + it('returns true if null masterRecord paramter provided ', async () => { + const result = await BusinessLogicManager.isDocumentConsideredAnonymous(null); + + expect(result).toBe(true); + }); + + it('returns true if masterRecord.documents is null', async () => { + const result = await BusinessLogicManager.isDocumentConsideredAnonymous({ documents: null }); + + expect(result).toBe(true); + }); + + it('returns true if masterRecord.documents is empty', async () => { + const result = await BusinessLogicManager.isDocumentConsideredAnonymous({ documents: [] }); + + expect(result).toBe(true); + }); +}); diff --git a/api/src/utils/query-utils.js b/api/src/utils/query-utils.js index d48dedf87..d5e4d5903 100644 --- a/api/src/utils/query-utils.js +++ b/api/src/utils/query-utils.js @@ -125,5 +125,6 @@ exports.recordTypes = [ 'Warning', 'ConstructionPlan', 'ManagementPlan', - 'CourtConviction' + 'CourtConviction', + 'Mine' ];