From 2da67c2bd038aa30f9f012f6f09de7a5f6b4488e Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 3 May 2022 15:27:22 +0100 Subject: [PATCH 01/33] feat(bulkUploadTrainingValidationRefactor) Created a addValidationError helper function and began refactoring/adding tests --- .../classes/trainingCSVValidator.js | 65 +++++---- .../unit/classes/trainingCSVValidator.spec.js | 138 ++++++++---------- 2 files changed, 97 insertions(+), 106 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 6b9a2d0489..ab07de0b56 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -336,35 +336,31 @@ class TrainingCsvValidator { if (this._currentLine.ACCREDITED) { const myAccredited = parseInt(this._currentLine.ACCREDITED, 10); const ALLOWED_VALUES = [0, 1, 999]; + if (Number.isNaN(myAccredited) || !ALLOWED_VALUES.includes(myAccredited)) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.ACCREDITED_ERROR, - errType: 'ACCREDITED_ERROR', - error: 'ACCREDITED is invalid', - source: this._currentLine.ACCREDITED, - column: 'ACCREDITED', - }); + this._addValidationError( + 'ACCREDITED_ERROR', + 'ACCREDITED is invalid', + this._currentLine.ACCREDITED, + 'ACCREDITED', + ); return false; - } else { - switch (myAccredited) { - case 0: - this._accredited = 'No'; - break; - case 1: - this._accredited = 'Yes'; - break; - case 999: - this._accredited = "Don't know"; - break; - } - return true; } - } else { - return true; + + switch (myAccredited) { + case 0: + this._accredited = 'No'; + break; + case 1: + this._accredited = 'Yes'; + break; + case 999: + this._accredited = "Don't know"; + break; + } } + + return true; } _transformTrainingCategory() { @@ -411,6 +407,19 @@ class TrainingCsvValidator { } } + _addValidationError(errorType, errorMessage, errorSource, columnName) { + this._validationErrors.push({ + worker: this._currentLine.UNIQUEWORKERID, + name: this._currentLine.LOCALESTID, + lineNumber: this._lineNumber, + errCode: TrainingCsvValidator[errorType], + errType: errorType, + error: errorMessage, + source: errorSource, + column: columnName, + }); + } + // returns true on success, false is any attribute of TrainingCsvValidator fails validate() { let status = true; @@ -427,11 +436,7 @@ class TrainingCsvValidator { } transform() { - let status = true; - - status = !this._transformTrainingCategory() ? false : status; - - return status; + this._transformTrainingCategory(); } toJSON() { diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index eaba3e6db0..3569b5fc37 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -7,10 +7,12 @@ const TrainingCsvValidator = require('../../../classes/trainingCSVValidator').Tr const mappings = require('../../../../../reference/BUDIMappings').mappings; describe('trainingCSVValidator', () => { - describe('validations', () => { - it('should pass validation if no ACCREDITED is provided', async () => { - const validator = new TrainingCsvValidator( - { + describe('validate()', () => { + describe('accredited', () => { + let trainingCsv; + + beforeEach(() => { + trainingCsv = { LOCALESTID: 'foo', UNIQUEWORKERID: 'bar', CATEGORY: 1, @@ -19,86 +21,70 @@ describe('trainingCSVValidator', () => { EXPIRYDATE: '', ACCREDITED: '', NOTES: '', - }, - 2, - mappings, - ); + }; + }); - // Regular validation has to run first for the establishment to populate the internal properties correctly - await validator.validate(); + it('should pass validation if no ACCREDITED is provided', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); - // call the method - await validator.transform(); + await validator.validate(); - // assert a error was returned - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._validationErrors.length).to.equal(0); - }); + expect(validator._validationErrors).to.deep.equal([]); + }); - it('should pass validation if ACCREDITED is provided', async () => { - const validator = new TrainingCsvValidator( - { - LOCALESTID: 'foo', - UNIQUEWORKERID: 'bar', - CATEGORY: 1, - DESCRIPTION: 'training', - DATECOMPLETED: '', - EXPIRYDATE: '', - ACCREDITED: '1', - NOTES: '', - }, - 2, - mappings, - ); + it('should pass validation and set accredited to Yes if ACCREDITED is 1', async () => { + trainingCsv.ACCREDITED = '1'; - // Regular validation has to run first for the establishment to populate the internal properties correctly - await validator.validate(); + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); - // call the method - await validator.transform(); + await validator.validate(); - // assert a error was returned - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._validationErrors.length).to.equal(0); - expect(validator.accredited).to.equal('Yes'); - }); + expect(validator._validationErrors).to.deep.equal([]); + expect(validator.accredited).to.equal('Yes'); + }); - it('should fail validation if invalid ACCREDITED is provided', async () => { - const validator = new TrainingCsvValidator( - { - LOCALESTID: 'foo', - UNIQUEWORKERID: 'bar', - CATEGORY: 1, - DESCRIPTION: 'training', - DATECOMPLETED: '', - EXPIRYDATE: '', - ACCREDITED: '3', - NOTES: '', - }, - 1, - mappings, - ); - - // Regular validation has to run first for the establishment to populate the internal properties correctly - await validator.validate(); - - // call the method - await validator.transform(); - - // assert a error was returned - expect(validator._validationErrors).to.deep.equal([ - { - errCode: 1060, - errType: 'ACCREDITED_ERROR', - error: 'ACCREDITED is invalid', - lineNumber: 1, - name: 'foo', - source: '3', - column: 'ACCREDITED', - worker: 'bar', - }, - ]); - expect(validator._validationErrors.length).to.equal(1); + it('should pass validation and set accredited to No if ACCREDITED is 0', async () => { + trainingCsv.ACCREDITED = '0'; + + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator.validate(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator.accredited).to.equal('No'); + }); + + it("should pass validation and set ACCREDITED to Don't know if ACCREDITED is 999", async () => { + trainingCsv.ACCREDITED = '999'; + + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator.validate(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator.accredited).to.equal("Don't know"); + }); + + it('should add ACCREDITED_ERROR to validationErrors if invalid ACCREDITED is provided', async () => { + trainingCsv.ACCREDITED = '3'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator.validate(); + + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1060, + errType: 'ACCREDITED_ERROR', + error: 'ACCREDITED is invalid', + lineNumber: 1, + name: 'foo', + source: '3', + column: 'ACCREDITED', + worker: 'bar', + }, + ]); + }); }); }); }); From 80789312e814ba63871e83dbbe95f579229cbfd3 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 3 May 2022 16:19:59 +0100 Subject: [PATCH 02/33] feat(bulkUploadTrainingValidationRefactor) refactored _validateCategory and added tests --- .../classes/trainingCSVValidator.js | 21 ++-- .../unit/classes/trainingCSVValidator.spec.js | 101 +++++++++++++----- 2 files changed, 85 insertions(+), 37 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index ab07de0b56..6db8cfa33c 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -315,21 +315,16 @@ class TrainingCsvValidator { const myCategory = parseInt(this._currentLine.CATEGORY, 10); if (Number.isNaN(myCategory) || this.BUDI.trainingCategory(this.BUDI.TO_ASC, myCategory) === null) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.CATEGORY_ERROR, - errType: 'CATEGORY_ERROR', - error: 'CATEGORY has not been supplied', - source: this._currentLine.CATEGORY, - column: 'CATEGORY', - }); + this._addValidationError( + 'CATEGORY_ERROR', + 'CATEGORY has not been supplied', + this._currentLine.CATEGORY, + 'CATEGORY', + ); return false; - } else { - this._category = myCategory; - return true; } + this._category = myCategory; + return true; } _validateAccredited() { diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 3569b5fc37..ff75ee48cb 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -7,27 +7,27 @@ const TrainingCsvValidator = require('../../../classes/trainingCSVValidator').Tr const mappings = require('../../../../../reference/BUDIMappings').mappings; describe('trainingCSVValidator', () => { - describe('validate()', () => { - describe('accredited', () => { - let trainingCsv; - - beforeEach(() => { - trainingCsv = { - LOCALESTID: 'foo', - UNIQUEWORKERID: 'bar', - CATEGORY: 1, - DESCRIPTION: 'training', - DATECOMPLETED: '', - EXPIRYDATE: '', - ACCREDITED: '', - NOTES: '', - }; - }); + describe('Validation', () => { + let trainingCsv; + + beforeEach(() => { + trainingCsv = { + LOCALESTID: 'foo', + UNIQUEWORKERID: 'bar', + CATEGORY: 1, + DESCRIPTION: 'training', + DATECOMPLETED: '', + EXPIRYDATE: '', + ACCREDITED: '', + NOTES: '', + }; + }); + describe('_validateAccredited()', () => { it('should pass validation if no ACCREDITED is provided', async () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); - await validator.validate(); + await validator._validateAccredited(); expect(validator._validationErrors).to.deep.equal([]); }); @@ -37,10 +37,10 @@ describe('trainingCSVValidator', () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); - await validator.validate(); + await validator._validateAccredited(); expect(validator._validationErrors).to.deep.equal([]); - expect(validator.accredited).to.equal('Yes'); + expect(validator._accredited).to.equal('Yes'); }); it('should pass validation and set accredited to No if ACCREDITED is 0', async () => { @@ -48,10 +48,10 @@ describe('trainingCSVValidator', () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); - await validator.validate(); + await validator._validateAccredited(); expect(validator._validationErrors).to.deep.equal([]); - expect(validator.accredited).to.equal('No'); + expect(validator._accredited).to.equal('No'); }); it("should pass validation and set ACCREDITED to Don't know if ACCREDITED is 999", async () => { @@ -59,10 +59,10 @@ describe('trainingCSVValidator', () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); - await validator.validate(); + await validator._validateAccredited(); expect(validator._validationErrors).to.deep.equal([]); - expect(validator.accredited).to.equal("Don't know"); + expect(validator._accredited).to.equal("Don't know"); }); it('should add ACCREDITED_ERROR to validationErrors if invalid ACCREDITED is provided', async () => { @@ -70,7 +70,7 @@ describe('trainingCSVValidator', () => { const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); - await validator.validate(); + await validator._validateAccredited(); expect(validator._validationErrors).to.deep.equal([ { @@ -86,5 +86,58 @@ describe('trainingCSVValidator', () => { ]); }); }); + + describe('_validateCategory()', () => { + it('should add CATEGORY_ERROR to validationErrors if string containing letters is provided for Category', async () => { + trainingCsv.CATEGORY = 'bob'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateCategory(); + + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1050, + errType: 'CATEGORY_ERROR', + error: 'CATEGORY has not been supplied', + lineNumber: 1, + name: 'foo', + source: 'bob', + column: 'CATEGORY', + worker: 'bar', + }, + ]); + }); + + it('should add CATEGORY_ERROR to validationErrors if the Category provided is not a valid category number', async () => { + trainingCsv.CATEGORY = 41; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateCategory(); + + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1050, + errType: 'CATEGORY_ERROR', + error: 'CATEGORY has not been supplied', + lineNumber: 1, + name: 'foo', + source: 41, + column: 'CATEGORY', + worker: 'bar', + }, + ]); + }); + + it('should pass validation and set CATEGORY if CATEGORY provided is a valid category', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateCategory(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._category).to.equal(1); + }); + }); }); }); From aa1463f139f9b7fee3fc43df16908448033cb0e2 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 3 May 2022 16:35:29 +0100 Subject: [PATCH 03/33] feat(bulkUploadTrainingValidationRefactor) created _convertAccreditedValue function --- .../classes/trainingCSVValidator.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 6db8cfa33c..38c5976799 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -342,22 +342,22 @@ class TrainingCsvValidator { return false; } - switch (myAccredited) { - case 0: - this._accredited = 'No'; - break; - case 1: - this._accredited = 'Yes'; - break; - case 999: - this._accredited = "Don't know"; - break; - } + this._accredited = this._convertAccreditedValue(myAccredited); } return true; } + _convertAccreditedValue(key) { + const accreditedValues = { + 0: 'No', + 1: 'Yes', + 999: "Don't know", + }; + + return accreditedValues[key] || ''; + } + _transformTrainingCategory() { if (this._category) { const mappedCategory = this.BUDI.trainingCategory(this.BUDI.TO_ASC, this._category); From 44d3eff795cd3d95bf91b6f61fe57036feafd58b Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 3 May 2022 16:41:57 +0100 Subject: [PATCH 04/33] feat(bulkUploadTrainingValidationRefactor) removed irrelevant boolean returns from validate category and validate accredited --- .../classes/trainingCSVValidator.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 38c5976799..5d7d4d5b9a 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -312,40 +312,38 @@ class TrainingCsvValidator { } _validateCategory() { - const myCategory = parseInt(this._currentLine.CATEGORY, 10); + const category = parseInt(this._currentLine.CATEGORY, 10); - if (Number.isNaN(myCategory) || this.BUDI.trainingCategory(this.BUDI.TO_ASC, myCategory) === null) { + if (Number.isNaN(category) || this.BUDI.trainingCategory(this.BUDI.TO_ASC, category) === null) { this._addValidationError( 'CATEGORY_ERROR', 'CATEGORY has not been supplied', this._currentLine.CATEGORY, 'CATEGORY', ); - return false; + return; } - this._category = myCategory; - return true; + + this._category = category; } _validateAccredited() { if (this._currentLine.ACCREDITED) { - const myAccredited = parseInt(this._currentLine.ACCREDITED, 10); + const accredited = parseInt(this._currentLine.ACCREDITED, 10); const ALLOWED_VALUES = [0, 1, 999]; - if (Number.isNaN(myAccredited) || !ALLOWED_VALUES.includes(myAccredited)) { + if (Number.isNaN(accredited) || !ALLOWED_VALUES.includes(accredited)) { this._addValidationError( 'ACCREDITED_ERROR', 'ACCREDITED is invalid', this._currentLine.ACCREDITED, 'ACCREDITED', ); - return false; + return; } - this._accredited = this._convertAccreditedValue(myAccredited); + this._accredited = this._convertAccreditedValue(accredited); } - - return true; } _convertAccreditedValue(key) { From c9334c0471844485ad3d1cede50cb81ad7589dbc Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 3 May 2022 17:06:17 +0100 Subject: [PATCH 05/33] feat(bulkUploadTrainingValidationRefactor) refactored _validateNotes and added tests --- .../classes/trainingCSVValidator.js | 29 +++++------- .../unit/classes/trainingCSVValidator.spec.js | 45 +++++++++++++++++++ 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 5d7d4d5b9a..af6246f2b4 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -377,26 +377,21 @@ class TrainingCsvValidator { } _validateNotes() { - const myNotes = this._currentLine.NOTES; + const notes = this._currentLine.NOTES; const MAX_LENGTH = 1000; - if (myNotes && myNotes.length > 0) { - if (myNotes.length > MAX_LENGTH) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.NOTES_ERROR, - errType: 'NOTES_ERROR', - error: `NOTES is longer than ${MAX_LENGTH} characters`, - source: this._currentLine.NOTES, - column: 'NOTES', - }); - return false; - } else { - this._notes = myNotes; - return true; + if (notes && notes.length > 0) { + if (notes.length > MAX_LENGTH) { + this._addValidationError( + 'NOTES_ERROR', + `NOTES is longer than ${MAX_LENGTH} characters`, + this._currentLine.NOTES, + 'NOTES', + ); + return; } + + this._notes = notes; } } diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index ff75ee48cb..3ecd0246d0 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -139,5 +139,50 @@ describe('trainingCSVValidator', () => { expect(validator._category).to.equal(1); }); }); + + describe('_validateNotes()', () => { + it('should add NOTES_ERROR to validationErrors and leave notes as null if NOTES is longer than 1000 characters', async () => { + trainingCsv.NOTES = + 'LLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. N'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateNotes(); + + expect(validator._notes).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1070, + errType: 'NOTES_ERROR', + error: 'NOTES is longer than 1000 characters', + source: trainingCsv.NOTES, + column: 'NOTES', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + + it('should not add NOTES_ERROR to validationErrors and set notes to csv NOTES if NOTES is shorter than 1000 characters', async () => { + trainingCsv.NOTES = 'valid short note'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateNotes(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._notes).to.equal('valid short note'); + }); + + it('should leave notes as null and not add error if NOTES empty string', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateNotes(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._notes).to.equal(null); + }); + }); }); }); From 9e73c426f44b79f01ab5a13b802e66ae82e382b1 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Wed, 4 May 2022 12:26:24 +0100 Subject: [PATCH 06/33] feat(bulkUploadTrainingValidationRefactor) refactored _transformTrainingCategory and transform() --- .../classes/trainingCSVValidator.js | 27 +------------------ .../unit/classes/trainingCSVValidator.spec.js | 4 +-- lambdas/bulkUpload/validateTraining/index.js | 1 - 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index af6246f2b4..c76874cbe1 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -323,8 +323,7 @@ class TrainingCsvValidator { ); return; } - - this._category = category; + this._category = this.BUDI.trainingCategory(this.BUDI.TO_ASC, category); } _validateAccredited() { @@ -356,26 +355,6 @@ class TrainingCsvValidator { return accreditedValues[key] || ''; } - _transformTrainingCategory() { - if (this._category) { - const mappedCategory = this.BUDI.trainingCategory(this.BUDI.TO_ASC, this._category); - if (mappedCategory === null) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.CATEGORY_ERROR, - errType: 'CATEGORY_ERROR', - error: 'CATEGORY has not been supplied', - source: this._currentLine.CATEGORY, - column: 'CATEGORY', - }); - } else { - this._category = mappedCategory; - } - } - } - _validateNotes() { const notes = this._currentLine.NOTES; const MAX_LENGTH = 1000; @@ -423,10 +402,6 @@ class TrainingCsvValidator { return status; } - transform() { - this._transformTrainingCategory(); - } - toJSON() { return { localId: this._localeStId, diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 3ecd0246d0..1f41746730 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -130,13 +130,13 @@ describe('trainingCSVValidator', () => { ]); }); - it('should pass validation and set CATEGORY if CATEGORY provided is a valid category', async () => { + it('should pass validation and set BUDI CATEGORY to ASC CATEGORY if the BUDI CATEGORY is a valid category', async () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); await validator._validateCategory(); expect(validator._validationErrors).to.deep.equal([]); - expect(validator._category).to.equal(1); + expect(validator._category).to.equal(8); }); }); diff --git a/lambdas/bulkUpload/validateTraining/index.js b/lambdas/bulkUpload/validateTraining/index.js index a976dbb542..f458708d12 100644 --- a/lambdas/bulkUpload/validateTraining/index.js +++ b/lambdas/bulkUpload/validateTraining/index.js @@ -12,7 +12,6 @@ const runValidator = async (thisLine, currentLineNumber, mappings) => { const lineValidator = new TrainingCsvValidator(thisLine, currentLineNumber, mappings); lineValidator.validate(); - lineValidator.transform(); const APITrainingRecord = lineValidator.toAPI(); const JSONTrainingRecord = lineValidator.toJSON(); From a478210a0ba422610b1f6c7855c5fe682fe40959 Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Thu, 5 May 2022 12:20:33 +0100 Subject: [PATCH 07/33] feat(targetedEmailsReport): Add rendering of report download link after uploading id file --- .../targeted-emails.component.html | 12 +++++++++ .../targeted-emails.component.spec.ts | 25 +++++++++++++++++++ .../targeted-emails.component.ts | 6 +++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html index e010fe93fa..5fabe2cf9c 100644 --- a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html +++ b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html @@ -36,6 +36,18 @@
diff --git a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts index cf61322747..fc69839e5e 100644 --- a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts +++ b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts @@ -5,6 +5,7 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; import { WindowRef } from '@core/services/window.ref'; +import { AdminModule } from '@features/admin/admin.module'; import { SearchModule } from '@features/search/search.module'; import { SharedModule } from '@shared/shared.module'; import { fireEvent, render, within } from '@testing-library/angular'; @@ -27,6 +28,7 @@ describe('TargetedEmailsComponent', () => { FormsModule, ReactiveFormsModule, NgxDropzoneModule, + AdminModule, ], providers: [ { @@ -263,6 +265,29 @@ describe('TargetedEmailsComponent', () => { expect(getTargetedTotalValidEmailsSpy).toHaveBeenCalledOnceWith(fileFormData); }); + it('should only display Download targeted emails report link after file uploaded when Multiple accounts selected', async () => { + const { fixture, getByLabelText, getAllByLabelText, queryByText } = await setup(); + + const emailCampaignService = TestBed.inject(EmailCampaignService); + spyOn(emailCampaignService, 'getTargetedTotalValidEmails').and.callFake(() => of({ totalEmails: 3 })); + + const groupSelect = getByLabelText('Email group', { exact: false }); + fireEvent.change(groupSelect, { target: { value: 'multipleAccounts' } }); + fixture.detectChanges(); + + expect(queryByText('Download targeted emails report')).toBeFalsy(); + + const fileInput = getAllByLabelText('upload files here'); + const file = new File(['some file content'], 'establishments.csv', { type: 'text/csv' }); + const fileFormData: FormData = new FormData(); + fileFormData.append('targetedRecipientsFile', file); + + userEvent.upload(fileInput[1], file); + fixture.detectChanges(); + + expect(queryByText('Download targeted emails report')).toBeTruthy(); + }); + it('should call createTargetedEmailsCampaign with multipleAccounts, template id and file when file uploaded and sending emails confirmed', async () => { const { fixture, getByText, getByLabelText, getAllByLabelText } = await setup(); diff --git a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts index e3c9263b83..789bcae6bc 100644 --- a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts +++ b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts @@ -1,4 +1,4 @@ -import { DecimalPipe } from '@angular/common'; +import { DatePipe, DecimalPipe } from '@angular/common'; import { Component, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { EmailType, TotalEmailsResponse } from '@core/model/emails.model'; @@ -22,7 +22,8 @@ export class TargetedEmailsComponent implements OnDestroy { private subscriptions: Subscription = new Subscription(); public emailType = EmailType; public showDragAndDrop = false; - private nmdsIdsFileData: FormData | null = null; + public nmdsIdsFileData: FormData | null = null; + public now = this.datePipe.transform(new Date(), 'yyyy-MM-dd'); constructor( public alertService: AlertService, @@ -30,6 +31,7 @@ export class TargetedEmailsComponent implements OnDestroy { private route: ActivatedRoute, private emailCampaignService: EmailCampaignService, private decimalPipe: DecimalPipe, + private datePipe: DatePipe, ) {} public updateTotalEmails(groupType: string): void { From 44412d057e0455a5b51b0fa4a06d97a982664d46 Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Thu, 5 May 2022 14:38:16 +0100 Subject: [PATCH 08/33] feat(targetedEmailsReport): Set up getTargetedEmailsReport in emailCampaign service --- .../services/admin/email-campaign.service.ts | 8 +++++ .../targeted-emails.component.html | 3 +- .../targeted-emails.component.spec.ts | 30 +++++++++++++++++++ .../targeted-emails.component.ts | 25 ++++++++++++++-- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/app/core/services/admin/email-campaign.service.ts b/src/app/core/services/admin/email-campaign.service.ts index 63a17c9ce9..32f2c0da2d 100644 --- a/src/app/core/services/admin/email-campaign.service.ts +++ b/src/app/core/services/admin/email-campaign.service.ts @@ -54,6 +54,14 @@ export class EmailCampaignService { }); } + getTargetedEmailsReport(fileFormData: FormData): Observable { + return this.http.post('/api/admin/email-campaigns/targeted-emails/report', fileFormData, { + headers: { InterceptorSkipHeader: 'true' }, + observe: 'response', + responseType: 'blob' as 'json', + }); + } + createTargetedEmailsCampaign(groupType: string, templateId: string, nmdsIdsFileData?: FormData): Observable { const payload = { groupType, diff --git a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html index 5fabe2cf9c..89297a6669 100644 --- a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html +++ b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.html @@ -42,7 +42,8 @@ class="govuk-list govuk-list--inline govuk__flex govuk-!-margin-top-5" role="button" draggable="false" - href="/download/{{ now | date: 'yyyy-MM-dd' }}-targeted-emails-report.xls" + href="#" + (click)="downloadTargetedEmailsReport($event)" > Download targeted emails report diff --git a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts index fc69839e5e..60b9d8f272 100644 --- a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts +++ b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts @@ -288,6 +288,36 @@ describe('TargetedEmailsComponent', () => { expect(queryByText('Download targeted emails report')).toBeTruthy(); }); + it('should call getTargetedEmailsReport in emailCampaign service when download button clicked', async () => { + const { fixture, getByText, getByLabelText, getAllByLabelText, queryByText } = await setup(); + + const emailCampaignService = TestBed.inject(EmailCampaignService); + spyOn(emailCampaignService, 'getTargetedTotalValidEmails').and.callFake(() => of({ totalEmails: 3 })); + const getTargetedEmailsReportSpy = spyOn(emailCampaignService, 'getTargetedEmailsReport').and.callFake(() => + of(null), + ); + const saveSpy = spyOn(fixture.componentInstance, 'saveFile').and.callFake(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function + + const groupSelect = getByLabelText('Email group', { exact: false }); + fireEvent.change(groupSelect, { target: { value: 'multipleAccounts' } }); + fixture.detectChanges(); + + expect(queryByText('Download targeted emails report')).toBeFalsy(); + + const fileInput = getAllByLabelText('upload files here'); + const file = new File(['some file content'], 'establishments.csv', { type: 'text/csv' }); + const fileFormData: FormData = new FormData(); + fileFormData.append('targetedRecipientsFile', file); + + userEvent.upload(fileInput[1], file); + fixture.detectChanges(); + + fireEvent.click(getByText('Download targeted emails report')); + + expect(getTargetedEmailsReportSpy).toHaveBeenCalledWith(fileFormData); + expect(saveSpy).toHaveBeenCalled(); + }); + it('should call createTargetedEmailsCampaign with multipleAccounts, template id and file when file uploaded and sending emails confirmed', async () => { const { fixture, getByText, getByLabelText, getAllByLabelText } = await setup(); diff --git a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts index 789bcae6bc..dcab33f3e5 100644 --- a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts +++ b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.ts @@ -1,10 +1,12 @@ -import { DatePipe, DecimalPipe } from '@angular/common'; +import { DecimalPipe } from '@angular/common'; +import { HttpResponse } from '@angular/common/http'; import { Component, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { EmailType, TotalEmailsResponse } from '@core/model/emails.model'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; import { AlertService } from '@core/services/alert.service'; import { DialogService } from '@core/services/dialog.service'; +import saveAs from 'file-saver'; import { Subscription } from 'rxjs'; import { SendEmailsConfirmationDialogComponent } from '../dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component'; @@ -23,7 +25,6 @@ export class TargetedEmailsComponent implements OnDestroy { public emailType = EmailType; public showDragAndDrop = false; public nmdsIdsFileData: FormData | null = null; - public now = this.datePipe.transform(new Date(), 'yyyy-MM-dd'); constructor( public alertService: AlertService, @@ -31,7 +32,6 @@ export class TargetedEmailsComponent implements OnDestroy { private route: ActivatedRoute, private emailCampaignService: EmailCampaignService, private decimalPipe: DecimalPipe, - private datePipe: DatePipe, ) {} public updateTotalEmails(groupType: string): void { @@ -84,6 +84,15 @@ export class TargetedEmailsComponent implements OnDestroy { ); } + public downloadTargetedEmailsReport(event: Event): void { + event.preventDefault(); + this.subscriptions.add( + this.emailCampaignService + .getTargetedEmailsReport(this.nmdsIdsFileData) + .subscribe((response) => this.saveFile(response)), + ); + } + public validateFile(file: File): void { this.nmdsIdsFileData = new FormData(); this.nmdsIdsFileData.append('targetedRecipientsFile', file, file.name); @@ -92,6 +101,16 @@ export class TargetedEmailsComponent implements OnDestroy { .subscribe((res: TotalEmailsResponse) => (this.totalEmails = res.totalEmails)); } + public saveFile(response: HttpResponse): void { + const filenameRegEx = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; + const header = response.headers.get('content-disposition'); + const filenameMatches = header && header.match(filenameRegEx); + const filename = filenameMatches && filenameMatches.length > 1 ? filenameMatches[1] : null; + const blob = new Blob([response.body], { type: 'text/plain;charset=utf-8' }); + + saveAs(blob, filename); + } + ngOnDestroy(): void { this.subscriptions.unsubscribe(); } From f9638b0b4fe62b41ca966d31dcfe36587e4ce66e Mon Sep 17 00:00:00 2001 From: popey2700 Date: Thu, 5 May 2022 14:56:23 +0100 Subject: [PATCH 09/33] feat(bulkUploadTrainingValidationRefactor) Moved validate, toJSON and toAPI functions to the top of traingCSVValidator file, refactored _validateLocaleStId() --- .../classes/trainingCSVValidator.js | 123 ++++++++---------- .../unit/classes/trainingCSVValidator.spec.js | 79 +++++++++++ 2 files changed, 133 insertions(+), 69 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index c76874cbe1..f04365dca3 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -103,38 +103,67 @@ class TrainingCsvValidator { return this._notes; } + validate() { + let status = true; + status = !this._validateLocaleStId() ? false : status; + status = !this._validateUniqueWorkerId() ? false : status; + status = !this._validateDateCompleted() ? false : status; + status = !this._validateExpiry() ? false : status; + status = !this._validateDescription() ? false : status; + status = !this._validateCategory() ? false : status; + status = !this._validateAccredited() ? false : status; + status = !this._validateNotes() ? false : status; + + return status; + } + + toJSON() { + return { + localId: this._localeStId, + uniqueWorkerId: this._uniqueWorkerId, + completed: this._dateCompleted ? this._dateCompleted.format('DD/MM/YYYY') : undefined, + expiry: this._expiry ? this._expiry.format('DD/MM/YYYY') : undefined, + description: this._description, + category: this._category, + accredited: this._accredited, + notes: this._notes, + lineNumber: this._lineNumber, + }; + } + + toAPI() { + const changeProperties = { + trainingCategory: { + id: this._category, + }, + completed: this._dateCompleted ? this._dateCompleted.format('YYYY-MM-DD') : undefined, + expires: this._expiry ? this._expiry.format('YYYY-MM-DD') : undefined, + title: this._description ? this._description : undefined, + notes: this._notes ? this._notes : undefined, + accredited: this._accredited ? this._accredited : undefined, + }; + + return changeProperties; + } + _validateLocaleStId() { const myLocaleStId = this._currentLine.LOCALESTID; const MAX_LENGTH = 50; + const errMessage = this._getValidateLocaleStIdErrMessage(myLocaleStId, MAX_LENGTH); + if (!errMessage) { + this._localeStId = myLocaleStId; + return; + } + this._addValidationError('LOCALESTID_ERROR', errMessage, this._currentLine.LOCALESTID, 'LOCALESTID'); + } + _getValidateLocaleStIdErrMessage(myLocaleStId, MAX_LENGTH) { if (!myLocaleStId || myLocaleStId.length === 0) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.LOCALESTID_ERROR, - errType: 'LOCALESTID_ERROR', - error: 'LOCALESTID has not been supplied', - source: this._currentLine.LOCALESTID, - column: 'LOCALESTID', - }); - return false; + return 'LOCALESTID has not been supplied'; } else if (myLocaleStId.length > MAX_LENGTH) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.LOCALESTID_ERROR, - errType: 'LOCALESTID_ERROR', - error: `LOCALESTID is longer than ${MAX_LENGTH} characters`, - source: this._currentLine.LOCALESTID, - column: 'LOCALESTID', - }); - return false; - } else { - this._localeStId = myLocaleStId; - return true; + return `LOCALESTID is longer than ${MAX_LENGTH} characters`; } + return; } _validateUniqueWorkerId() { @@ -387,50 +416,6 @@ class TrainingCsvValidator { }); } - // returns true on success, false is any attribute of TrainingCsvValidator fails - validate() { - let status = true; - status = !this._validateLocaleStId() ? false : status; - status = !this._validateUniqueWorkerId() ? false : status; - status = !this._validateDateCompleted() ? false : status; - status = !this._validateExpiry() ? false : status; - status = !this._validateDescription() ? false : status; - status = !this._validateCategory() ? false : status; - status = !this._validateAccredited() ? false : status; - status = !this._validateNotes() ? false : status; - - return status; - } - - toJSON() { - return { - localId: this._localeStId, - uniqueWorkerId: this._uniqueWorkerId, - completed: this._dateCompleted ? this._dateCompleted.format('DD/MM/YYYY') : undefined, - expiry: this._expiry ? this._expiry.format('DD/MM/YYYY') : undefined, - description: this._description, - category: this._category, - accredited: this._accredited, - notes: this._notes, - lineNumber: this._lineNumber, - }; - } - - toAPI() { - const changeProperties = { - trainingCategory: { - id: this._category, - }, - completed: this._dateCompleted ? this._dateCompleted.format('YYYY-MM-DD') : undefined, - expires: this._expiry ? this._expiry.format('YYYY-MM-DD') : undefined, - title: this._description ? this._description : undefined, - notes: this._notes ? this._notes : undefined, - accredited: this._accredited ? this._accredited : undefined, - }; - - return changeProperties; - } - get validationErrors() { // include the "origin" of validation error return this._validationErrors.map((thisValidation) => { diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 1f41746730..ecde752fcb 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -184,5 +184,84 @@ describe('trainingCSVValidator', () => { expect(validator._notes).to.equal(null); }); }); + + describe('_getValidateLocaleStIdErrorStatus()', () => { + it('should add LOCALESTID_ERROR to validationErrors and set _localStId as null if myLocaleStId length === 0', async () => { + trainingCsv.LOCALESTID = ''; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateLocaleStId(); + + expect(validator._localeStId).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1000, + errType: 'LOCALESTID_ERROR', + error: 'LOCALESTID has not been supplied', + source: trainingCsv.LOCALESTID, + column: 'LOCALESTID', + lineNumber: 1, + name: '', + worker: 'bar', + }, + ]); + }); + + it("should add LOCALESTID_ERROR to validationErrors and leave _localStId as null if myLocaleStId doesn't exist", async () => { + trainingCsv.LOCALESTID = null; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateLocaleStId(); + + expect(validator._localeStId).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1000, + errType: 'LOCALESTID_ERROR', + error: 'LOCALESTID has not been supplied', + source: trainingCsv.LOCALESTID, + column: 'LOCALESTID', + lineNumber: 1, + name: null, + worker: 'bar', + }, + ]); + }); + + it("should add LOCALESTID_ERROR to validationErrors and leave _localStId as null if myLocaleStId's length is greater than MAX_LENGTH", async () => { + trainingCsv.LOCALESTID = 'Lorem ipsum dolor sit amet, consectetuer adipiscing'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateLocaleStId(); + + expect(validator._localeStId).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1000, + errType: 'LOCALESTID_ERROR', + error: 'LOCALESTID is longer than 50 characters', + source: trainingCsv.LOCALESTID, + column: 'LOCALESTID', + lineNumber: 1, + name: 'Lorem ipsum dolor sit amet, consectetuer adipiscing', + worker: 'bar', + }, + ]); + }); + }); + + describe('_validateLocaleStId()', async () => { + it('should pass validation and set _uniqueWorkerId if a valid LOCALESTID is provided', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateLocaleStId(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._localeStId).to.equal('foo'); + }); + }); }); }); From 4d74c0246882c3bb634ab782bd95373c4f74b7eb Mon Sep 17 00:00:00 2001 From: popey2700 Date: Thu, 5 May 2022 16:51:51 +0100 Subject: [PATCH 10/33] feat(bulkUploadTrainingValidationRefactor) refactored _validateUniqueWorkerId() --- .../classes/trainingCSVValidator.js | 36 ++---- .../unit/classes/trainingCSVValidator.spec.js | 103 +++++++++++++++++- 2 files changed, 113 insertions(+), 26 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index f04365dca3..6553a43365 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -169,35 +169,21 @@ class TrainingCsvValidator { _validateUniqueWorkerId() { const myUniqueId = this._currentLine.UNIQUEWORKERID; const MAX_LENGTH = 50; + const errMessage = this._getValidateUniqueWorkerIdErrMessage(myUniqueId, MAX_LENGTH); + if (!errMessage) { + this._uniqueWorkerId = myUniqueId; + return; + } + this._addValidationError('UNIQUE_WORKER_ID_ERROR', errMessage, this._currentLine.UNIQUEWORKERID, 'UNIQUEWORKERID'); + } + _getValidateUniqueWorkerIdErrMessage(myUniqueId, MAX_LENGTH) { if (!myUniqueId || myUniqueId.length === 0) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.UNIQUE_WORKER_ID_ERROR, - errType: 'UNIQUE_WORKER_ID_ERROR', - error: 'UNIQUEWORKERID has not been supplied', - source: this._currentLine.UNIQUEWORKERID, - column: 'UNIQUEWORKERID', - }); - return false; + return 'UNIQUEWORKERID has not been supplied'; } else if (myUniqueId.length > MAX_LENGTH) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.UNIQUE_WORKER_ID_ERROR, - errType: 'UNIQUE_WORKER_ID_ERROR', - error: `UNIQUEWORKERID is longer than ${MAX_LENGTH} characters`, - source: this._currentLine.UNIQUEWORKERID, - column: 'UNIQUEWORKERID', - }); - return false; - } else { - this._uniqueWorkerId = myUniqueId; - return true; + return `UNIQUEWORKERID is longer than ${MAX_LENGTH} characters`; } + return; } _validateDateCompleted() { diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index ecde752fcb..996a4b57e2 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -16,7 +16,7 @@ describe('trainingCSVValidator', () => { UNIQUEWORKERID: 'bar', CATEGORY: 1, DESCRIPTION: 'training', - DATECOMPLETED: '', + DATECOMPLETED: '01/01/2022', EXPIRYDATE: '', ACCREDITED: '', NOTES: '', @@ -263,5 +263,106 @@ describe('trainingCSVValidator', () => { expect(validator._localeStId).to.equal('foo'); }); }); + + describe('_validateUniqueWorkerId()', async () => { + it('should pass validation and set _uniqueWorkerId if a valid UNIQUEWORKERID is provided', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateUniqueWorkerId(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._uniqueWorkerId).to.equal('bar'); + }); + }); + + describe('_getValidateUniqueWorkerIdErrMessage()', () => { + it('should add UNIQUE_WORKER_ID_ERROR to validationErrors and set _uniqueWorkerId as null if myUniqueId length === 0', async () => { + trainingCsv.UNIQUEWORKERID = ''; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateUniqueWorkerId(); + + expect(validator._uniqueWorkerId).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1010, + errType: 'UNIQUE_WORKER_ID_ERROR', + error: 'UNIQUEWORKERID has not been supplied', + source: trainingCsv.UNIQUEWORKERID, + column: 'UNIQUEWORKERID', + lineNumber: 1, + name: 'foo', + worker: '', + }, + ]); + }); + + it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave _uniqueWorkerId as null if myUniqueId doesn't exist", async () => { + trainingCsv.UNIQUEWORKERID = null; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateUniqueWorkerId(); + + expect(validator._uniqueWorkerId).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1010, + errType: 'UNIQUE_WORKER_ID_ERROR', + error: 'UNIQUEWORKERID has not been supplied', + source: trainingCsv.UNIQUEWORKERID, + column: 'UNIQUEWORKERID', + lineNumber: 1, + name: 'foo', + worker: null, + }, + ]); + }); + + it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave _uniqueWorkerId as null if myUniqueId's length is greater than MAX_LENGTH", async () => { + trainingCsv.UNIQUEWORKERID = 'Lorem ipsum dolor sit amet, consectetuer adipiscing'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateUniqueWorkerId(); + + expect(validator._uniqueWorkerId).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1010, + errType: 'UNIQUE_WORKER_ID_ERROR', + error: 'UNIQUEWORKERID is longer than 50 characters', + source: trainingCsv.UNIQUEWORKERID, + column: 'UNIQUEWORKERID', + lineNumber: 1, + name: 'foo', + worker: 'Lorem ipsum dolor sit amet, consectetuer adipiscing', + }, + ]); + }); + }); + + // describe('_validateDateCompleted()', async () => { + // it.only('should pass validation and set _dateCompleted if a valid DATECOMPLETED is provided', async () => { + // const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + // await validator._validateDateCompleted(); + + // expect(validator._validationErrors).to.deep.equal([]); + // expect(validator._dateCompleted).to.equal(); + // }); + // }); + + // describe('_getValidateDateCompletedErrMessage()', async () => { + // it.only('should pass validation and set _dateCompleted if a valid DATECOMPLETED is provided', async () => { + // const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + // await validator._validateDateCompleted(); + + // expect(validator._validationErrors).to.deep.equal([]); + // expect(validator._dateCompleted).to.equal(); + // }); + // }); }); }); From a04ca9d4e960ad729995e1201c22a0180934af75 Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Thu, 5 May 2022 17:01:59 +0100 Subject: [PATCH 11/33] feat(targetedEmailsReport): Set up report generation passing in returned users and original list --- server/reports/targeted-emails/index.js | 18 ++++++++ .../targeted-emails/workplaces-to-email.js | 33 +++++++++++++++ .../email-campaigns/targeted-emails/index.js | 33 +++++++++++++++ .../targeted-emails/index.spec.js | 42 +++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 server/reports/targeted-emails/index.js create mode 100644 server/reports/targeted-emails/workplaces-to-email.js diff --git a/server/reports/targeted-emails/index.js b/server/reports/targeted-emails/index.js new file mode 100644 index 0000000000..d7b75698c5 --- /dev/null +++ b/server/reports/targeted-emails/index.js @@ -0,0 +1,18 @@ +const excelUtils = require('../../utils/excelUtils'); +const { generateWorkplacesToEmailTab } = require('./workplaces-to-email'); + +const generateTargetedEmailsReport = async (workbook, usersToEmail, establishmentNmdsIdList) => { + console.log(usersToEmail); + generateWorkplacesToEmailTab(workbook, usersToEmail); + //generateWorkplacesWithoutEmailTab(workbook, establishmentNmdsIdList); + + workbook.eachSheet((sheet) => { + excelUtils.fitColumnsToSize(sheet); + }); + + return workbook; +}; + +module.exports = { + generateTargetedEmailsReport, +}; diff --git a/server/reports/targeted-emails/workplaces-to-email.js b/server/reports/targeted-emails/workplaces-to-email.js new file mode 100644 index 0000000000..1137ab9e6b --- /dev/null +++ b/server/reports/targeted-emails/workplaces-to-email.js @@ -0,0 +1,33 @@ +const generateWorkplacesToEmailTab = (workbook, usersToEmail) => { + const workplacesToEmailTab = addWorksheet(workbook); + const rows = buildRows(usersToEmail); + workplacesToEmailTab.addRows(rows); +}; + +const addWorksheet = (workbook) => { + const worksheet = workbook.addWorksheet('Found Workplaces'); + worksheet.columns = [ + { header: 'NMDS ID', key: 'nmdsId' }, + { header: 'Email Address', key: 'emailAddress' }, + ]; + + const headerRow = worksheet.getRow(1); + headerRow.font = { bold: true, name: 'Calibri' }; + + return worksheet; +}; + +const buildRows = (usersToEmail) => { + return usersToEmail.map(buildRow); +}; + +const buildRow = (user) => { + return { + nmdsId: user.establishment.nmdsId, + emailAddress: user.get('email'), + }; +}; + +module.exports = { + generateWorkplacesToEmailTab, +}; diff --git a/server/routes/admin/email-campaigns/targeted-emails/index.js b/server/routes/admin/email-campaigns/targeted-emails/index.js index 043c15d175..43e969cd66 100644 --- a/server/routes/admin/email-campaigns/targeted-emails/index.js +++ b/server/routes/admin/email-campaigns/targeted-emails/index.js @@ -9,6 +9,9 @@ const sendEmail = require('../../../../services/email-campaigns/targeted-emails/ const models = require('../../../../models/'); const { getTargetedEmailTemplates } = require('./templates'); const { sanitizeFilePath } = require('../../../../utils/security/sanitizeFilePath'); +const moment = require('moment'); +const excelJS = require('exceljs'); +const targetedEmailsReport = require('../../../../reports/targeted-emails'); const router = express.Router(); @@ -102,6 +105,33 @@ const createEmailCampaign = async (userID) => { }); }; +const createTargetedEmailsReport = async (req, res) => { + try { + const establishmentNmdsIdList = parseNmdsIdsIfFileExists(req.file); + const users = await getGroupOfUsers('multipleAccounts', establishmentNmdsIdList); + + const workbook = new excelJS.Workbook(); + + workbook.creator = 'Skills-For-Care'; + workbook.properties.date1904 = true; + + await targetedEmailsReport.generateTargetedEmailsReport(workbook, users, establishmentNmdsIdList); + + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader( + 'Content-Disposition', + 'attachment; filename=' + moment().format('DD-MM-YYYY') + '-targetedEmails.xlsx', + ); + + await workbook.xlsx.write(res); + + return res.status(200).end(); + } catch (error) { + console.error(error); + return res.status(500).send(); + } +}; + const parseNmdsIdsIfFileExists = (file) => { if (!file) return null; @@ -158,7 +188,10 @@ router.route('/').post( createTargetedEmailsCampaign, ); +router.route('/report').post(upload.single('targetedRecipientsFile'), createTargetedEmailsReport); + module.exports = router; module.exports.getTargetedTotalEmails = getTargetedTotalEmails; module.exports.createTargetedEmailsCampaign = createTargetedEmailsCampaign; module.exports.getGroupOfUsers = getGroupOfUsers; +module.exports.createTargetedEmailsReport = createTargetedEmailsReport; diff --git a/server/test/unit/routes/admin/email-campaigns/targeted-emails/index.spec.js b/server/test/unit/routes/admin/email-campaigns/targeted-emails/index.spec.js index b24dbd0de9..98cbb0db25 100644 --- a/server/test/unit/routes/admin/email-campaigns/targeted-emails/index.spec.js +++ b/server/test/unit/routes/admin/email-campaigns/targeted-emails/index.spec.js @@ -7,6 +7,7 @@ const models = require('../../../../../../models'); const { build, fake, sequence } = require('@jackfranklin/test-data-bot/build'); const sendEmail = require('../../../../../../services/email-campaigns/targeted-emails/sendEmail'); const { Op } = require('sequelize'); +const excelJS = require('exceljs'); const user = build('User', { fields: { @@ -243,4 +244,45 @@ describe('server/routes/admin/email-campaigns/targeted-emails', () => { }); }); }); + + describe('createTargetedEmailsReport()', () => { + let req; + let res; + + beforeEach(() => { + sinon.stub(models.user, 'allPrimaryUsers').returns([]); + sinon.stub(fs, 'readFileSync'); + sinon.stub(fs, 'unlinkSync').returns(null); + + req = httpMocks.createRequest({ + method: 'POST', + url: '/api/admin/email-campaigns/targeted-emails/report', + role: 'Admin', + file: { filename: 'file' }, + }); + + res = httpMocks.createResponse(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should generate a report', async () => { + await targetedEmailsRoutes.createTargetedEmailsReport(req, res); + + expect(res.statusCode).to.equal(200); + expect(res._headers['content-type']).to.equal( + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + }); + + it('should return 500 status when an error is thrown', async () => { + sinon.stub(excelJS, 'Workbook').throws(); + + await targetedEmailsRoutes.createTargetedEmailsReport(req, res); + + expect(res.statusCode).to.equal(500); + }); + }); }); From c1adaa0a8bd856bb90ee67e6362d5bc8af160bab Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Fri, 6 May 2022 09:31:38 +0100 Subject: [PATCH 12/33] feat(targetedEmailsReport): Change workplacesToEmail to have addContent function to add content tests --- server/reports/targeted-emails/index.js | 3 +- ...laces-to-email.js => workplacesToEmail.js} | 18 ++++--- .../targeted-emails/workplacesToEmail.spec.js | 51 +++++++++++++++++++ 3 files changed, 63 insertions(+), 9 deletions(-) rename server/reports/targeted-emails/{workplaces-to-email.js => workplacesToEmail.js} (56%) create mode 100644 server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js diff --git a/server/reports/targeted-emails/index.js b/server/reports/targeted-emails/index.js index d7b75698c5..a9f9c72949 100644 --- a/server/reports/targeted-emails/index.js +++ b/server/reports/targeted-emails/index.js @@ -1,8 +1,7 @@ const excelUtils = require('../../utils/excelUtils'); -const { generateWorkplacesToEmailTab } = require('./workplaces-to-email'); +const { generateWorkplacesToEmailTab } = require('./workplacesToEmail'); const generateTargetedEmailsReport = async (workbook, usersToEmail, establishmentNmdsIdList) => { - console.log(usersToEmail); generateWorkplacesToEmailTab(workbook, usersToEmail); //generateWorkplacesWithoutEmailTab(workbook, establishmentNmdsIdList); diff --git a/server/reports/targeted-emails/workplaces-to-email.js b/server/reports/targeted-emails/workplacesToEmail.js similarity index 56% rename from server/reports/targeted-emails/workplaces-to-email.js rename to server/reports/targeted-emails/workplacesToEmail.js index 1137ab9e6b..a953d776d7 100644 --- a/server/reports/targeted-emails/workplaces-to-email.js +++ b/server/reports/targeted-emails/workplacesToEmail.js @@ -1,20 +1,23 @@ const generateWorkplacesToEmailTab = (workbook, usersToEmail) => { - const workplacesToEmailTab = addWorksheet(workbook); + const workplacesToEmailTab = workbook.addWorksheet('Found Workplaces'); + + addContentToWorkplacesToEmailTab(workplacesToEmailTab, usersToEmail); +}; + +const addContentToWorkplacesToEmailTab = (workplacesToEmailTab, usersToEmail) => { + addHeaders(workplacesToEmailTab); const rows = buildRows(usersToEmail); workplacesToEmailTab.addRows(rows); }; -const addWorksheet = (workbook) => { - const worksheet = workbook.addWorksheet('Found Workplaces'); - worksheet.columns = [ +const addHeaders = (workplacesToEmailTab) => { + workplacesToEmailTab.columns = [ { header: 'NMDS ID', key: 'nmdsId' }, { header: 'Email Address', key: 'emailAddress' }, ]; - const headerRow = worksheet.getRow(1); + const headerRow = workplacesToEmailTab.getRow(1); headerRow.font = { bold: true, name: 'Calibri' }; - - return worksheet; }; const buildRows = (usersToEmail) => { @@ -30,4 +33,5 @@ const buildRow = (user) => { module.exports = { generateWorkplacesToEmailTab, + addContentToWorkplacesToEmailTab, }; diff --git a/server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js b/server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js new file mode 100644 index 0000000000..fd0e21d5d1 --- /dev/null +++ b/server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js @@ -0,0 +1,51 @@ +const expect = require('chai').expect; +const excelJS = require('exceljs'); + +const { addContentToWorkplacesToEmailTab } = require('../../../../reports/targeted-emails/workplacesToEmail'); + +describe('addContentToWorkplacesToEmailTab', () => { + let mockWorkplacesToEmailTab; + const mockUsers = [ + { + get() { + return 'mock@email.com'; + }, + establishment: { + nmdsId: 'A123456', + }, + }, + { + get() { + return 'mock2@email.com'; + }, + establishment: { + nmdsId: 'A123459', + }, + }, + ]; + + beforeEach(() => { + mockWorkplacesToEmailTab = new excelJS.Workbook().addWorksheet('Found Workplaces'); + }); + + it('should add tab NMDS ID and Email Address headers to top row', async () => { + addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockUsers); + + expect(mockWorkplacesToEmailTab.getCell('A1').value).to.equal('NMDS ID'); + expect(mockWorkplacesToEmailTab.getCell('B1').value).to.equal('Email Address'); + }); + + it('should add the NMDS ID and Email Address of first user to second row', async () => { + addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockUsers); + + expect(mockWorkplacesToEmailTab.getCell('A2').value).to.equal('A123456'); + expect(mockWorkplacesToEmailTab.getCell('B2').value).to.equal('mock@email.com'); + }); + + it('should add the NMDS ID and Email Address of second user to third row', async () => { + addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockUsers); + + expect(mockWorkplacesToEmailTab.getCell('A3').value).to.equal('A123459'); + expect(mockWorkplacesToEmailTab.getCell('B3').value).to.equal('mock2@email.com'); + }); +}); From 64d6be053a2f3133cfaedfb4c6e16d7a48b843be Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Fri, 6 May 2022 11:50:17 +0100 Subject: [PATCH 13/33] feat(targetedEmailsReport): Move formatting of workplaces from user data into generateTargetedEmailsReport --- server/reports/targeted-emails/index.js | 18 +++++++- .../targeted-emails/workplacesToEmail.js | 21 +++------- .../reports/targeted-emails/index.spec.js | 41 +++++++++++++++++++ .../targeted-emails/workplacesToEmail.spec.js | 28 +++++-------- 4 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 server/test/unit/reports/targeted-emails/index.spec.js diff --git a/server/reports/targeted-emails/index.js b/server/reports/targeted-emails/index.js index a9f9c72949..7062b530ac 100644 --- a/server/reports/targeted-emails/index.js +++ b/server/reports/targeted-emails/index.js @@ -1,8 +1,10 @@ const excelUtils = require('../../utils/excelUtils'); const { generateWorkplacesToEmailTab } = require('./workplacesToEmail'); -const generateTargetedEmailsReport = async (workbook, usersToEmail, establishmentNmdsIdList) => { - generateWorkplacesToEmailTab(workbook, usersToEmail); +const generateTargetedEmailsReport = async (workbook, users, establishmentNmdsIdList) => { + const workplacesToEmail = formatWorkplacesToEmail(users); + + generateWorkplacesToEmailTab(workbook, workplacesToEmail); //generateWorkplacesWithoutEmailTab(workbook, establishmentNmdsIdList); workbook.eachSheet((sheet) => { @@ -12,6 +14,18 @@ const generateTargetedEmailsReport = async (workbook, usersToEmail, establishmen return workbook; }; +const formatWorkplacesToEmail = (users) => { + return users.map(formatWorkplace); +}; + +const formatWorkplace = (user) => { + return { + nmdsId: user.establishment.nmdsId, + emailAddress: user.get('email'), + }; +}; + module.exports = { generateTargetedEmailsReport, + formatWorkplacesToEmail, }; diff --git a/server/reports/targeted-emails/workplacesToEmail.js b/server/reports/targeted-emails/workplacesToEmail.js index a953d776d7..0750e574c7 100644 --- a/server/reports/targeted-emails/workplacesToEmail.js +++ b/server/reports/targeted-emails/workplacesToEmail.js @@ -1,13 +1,13 @@ -const generateWorkplacesToEmailTab = (workbook, usersToEmail) => { +const generateWorkplacesToEmailTab = (workbook, workplacesToEmail) => { const workplacesToEmailTab = workbook.addWorksheet('Found Workplaces'); - addContentToWorkplacesToEmailTab(workplacesToEmailTab, usersToEmail); + addContentToWorkplacesToEmailTab(workplacesToEmailTab, workplacesToEmail); }; -const addContentToWorkplacesToEmailTab = (workplacesToEmailTab, usersToEmail) => { +const addContentToWorkplacesToEmailTab = (workplacesToEmailTab, workplacesToEmail) => { addHeaders(workplacesToEmailTab); - const rows = buildRows(usersToEmail); - workplacesToEmailTab.addRows(rows); + + workplacesToEmailTab.addRows(workplacesToEmail); }; const addHeaders = (workplacesToEmailTab) => { @@ -20,17 +20,6 @@ const addHeaders = (workplacesToEmailTab) => { headerRow.font = { bold: true, name: 'Calibri' }; }; -const buildRows = (usersToEmail) => { - return usersToEmail.map(buildRow); -}; - -const buildRow = (user) => { - return { - nmdsId: user.establishment.nmdsId, - emailAddress: user.get('email'), - }; -}; - module.exports = { generateWorkplacesToEmailTab, addContentToWorkplacesToEmailTab, diff --git a/server/test/unit/reports/targeted-emails/index.spec.js b/server/test/unit/reports/targeted-emails/index.spec.js new file mode 100644 index 0000000000..ad9c8d0ca8 --- /dev/null +++ b/server/test/unit/reports/targeted-emails/index.spec.js @@ -0,0 +1,41 @@ +const expect = require('chai').expect; + +const { formatWorkplacesToEmail } = require('../../../../reports/targeted-emails'); + +describe('reports/targetedEmails/index', () => { + const mockUsers = [ + { + get() { + return 'mock@email.com'; + }, + establishment: { + nmdsId: 'A123456', + }, + }, + { + get() { + return 'mock2@email.com'; + }, + establishment: { + nmdsId: 'A123459', + }, + }, + ]; + + describe('formatWorkplacesToEmail()', () => { + it('should return array with objects containing nmdsId and email', async () => { + const data = formatWorkplacesToEmail(mockUsers); + + expect(data).to.deep.equal([ + { + nmdsId: 'A123456', + emailAddress: 'mock@email.com', + }, + { + nmdsId: 'A123459', + emailAddress: 'mock2@email.com', + }, + ]); + }); + }); +}); diff --git a/server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js b/server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js index fd0e21d5d1..1629ec3e15 100644 --- a/server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js +++ b/server/test/unit/reports/targeted-emails/workplacesToEmail.spec.js @@ -5,22 +5,14 @@ const { addContentToWorkplacesToEmailTab } = require('../../../../reports/target describe('addContentToWorkplacesToEmailTab', () => { let mockWorkplacesToEmailTab; - const mockUsers = [ + const mockWorkplaces = [ { - get() { - return 'mock@email.com'; - }, - establishment: { - nmdsId: 'A123456', - }, + nmdsId: 'A123456', + emailAddress: 'mock@email.com', }, { - get() { - return 'mock2@email.com'; - }, - establishment: { - nmdsId: 'A123459', - }, + nmdsId: 'A123459', + emailAddress: 'mock2@email.com', }, ]; @@ -29,21 +21,21 @@ describe('addContentToWorkplacesToEmailTab', () => { }); it('should add tab NMDS ID and Email Address headers to top row', async () => { - addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockUsers); + addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockWorkplaces); expect(mockWorkplacesToEmailTab.getCell('A1').value).to.equal('NMDS ID'); expect(mockWorkplacesToEmailTab.getCell('B1').value).to.equal('Email Address'); }); - it('should add the NMDS ID and Email Address of first user to second row', async () => { - addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockUsers); + it('should add the NMDS ID and Email Address of first workplace to second row', async () => { + addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockWorkplaces); expect(mockWorkplacesToEmailTab.getCell('A2').value).to.equal('A123456'); expect(mockWorkplacesToEmailTab.getCell('B2').value).to.equal('mock@email.com'); }); - it('should add the NMDS ID and Email Address of second user to third row', async () => { - addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockUsers); + it('should add the NMDS ID and Email Address of second workplace to third row', async () => { + addContentToWorkplacesToEmailTab(mockWorkplacesToEmailTab, mockWorkplaces); expect(mockWorkplacesToEmailTab.getCell('A3').value).to.equal('A123459'); expect(mockWorkplacesToEmailTab.getCell('B3').value).to.equal('mock2@email.com'); From 9ec28170c3267ee3406aabcdd992afc47d669868 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Fri, 6 May 2022 13:03:18 +0100 Subject: [PATCH 14/33] feat(bulkUploadTrainingValidationRefactor) Began refactoring _validateDateCompleted() --- .../classes/trainingCSVValidator.js | 59 +++++----------- .../unit/classes/trainingCSVValidator.spec.js | 68 ++++++++++++++----- 2 files changed, 68 insertions(+), 59 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 6553a43365..3c6977a647 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -191,51 +191,24 @@ class TrainingCsvValidator { const myDateCompleted = this._currentLine.DATECOMPLETED; const dateFormatRegex = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/\d{4}$/; const actualDate = moment.utc(myDateCompleted, 'DD/MM/YYYY'); + const errMessage = this._getValidateDateCompletedErrMessage(myDateCompleted, dateFormatRegex, actualDate); - if (myDateCompleted) { - if (!dateFormatRegex.test(myDateCompleted)) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.DATE_COMPLETED_ERROR, - errType: 'DATE_COMPLETED_ERROR', - error: 'DATECOMPLETED is incorrectly formatted', - source: this._currentLine.DATECOMPLETED, - column: 'DATECOMPLETED', - }); - return false; - } else if (!actualDate.isValid()) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.DATE_COMPLETED_ERROR, - errType: 'DATE_COMPLETED_ERROR', - error: 'DATECOMPLETED is invalid', - source: this._currentLine.DATECOMPLETED, - column: 'DATECOMPLETED', - }); - return false; - } else if (actualDate.isAfter(moment())) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.DATE_COMPLETED_ERROR, - errType: 'DATE_COMPLETED_ERROR', - error: 'DATECOMPLETED is in the future', - source: this._currentLine.DATECOMPLETED, - column: 'DATECOMPLETED', - }); - return false; - } else { - this._dateCompleted = actualDate; - return true; - } - } else { - return true; + if (!errMessage) { + this._dateCompleted = actualDate; + return; } + this._addValidationError('DATE_COMPLETED_ERROR', errMessage, this._currentLine.DATECOMPLETED, 'DATECOMPLETED'); + } + + _getValidateDateCompletedErrMessage(myDateCompleted, dateFormatRegex, actualDate) { + if (!dateFormatRegex.test(myDateCompleted)) { + return 'DATECOMPLETED is incorrectly formatted'; + } else if (!actualDate.isValid()) { + return 'DATECOMPLETED is invalid'; + } else if (actualDate.isAfter(moment())) { + return 'DATECOMPLETED is in the future'; + } + return; } _validateExpiry() { diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 996a4b57e2..8e1ed9501f 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -1,5 +1,6 @@ const expect = require('chai').expect; const sinon = require('sinon'); +const moment = require('moment'); const dbmodels = require('../../../../../server/models'); sinon.stub(dbmodels.status, 'ready').value(false); @@ -343,26 +344,61 @@ describe('trainingCSVValidator', () => { }); }); - // describe('_validateDateCompleted()', async () => { - // it.only('should pass validation and set _dateCompleted if a valid DATECOMPLETED is provided', async () => { - // const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + describe('_validateDateCompleted()', async () => { + it('should pass validation and set _dateCompleted if a valid DATECOMPLETED is provided', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateDateCompleted(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._dateCompleted).to.deep.equal(moment.utc('01/01/2022', 'DD/MM/YYYY')); + }); + }); + + describe('_getValidateDateCompletedErrMessage()', async () => { + it('should add DATE_COMPLETED_ERROR to validationErrors and set _dateCompleted as null if DATECOMPLETED is incorrectly formatted', async () => { + trainingCsv.DATECOMPLETED = '12323423423'; - // await validator._validateDateCompleted(); + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateDateCompleted(); + + expect(validator._dateCompleted).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1020, + errType: 'DATE_COMPLETED_ERROR', + error: 'DATECOMPLETED is incorrectly formatted', + source: trainingCsv.DATECOMPLETED, + column: 'DATECOMPLETED', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); - // expect(validator._validationErrors).to.deep.equal([]); - // expect(validator._dateCompleted).to.equal(); - // }); - // }); + it('should add DATE_COMPLETED_ERROR to validationErrors and set _dateCompleted as null if DATECOMPLETED is a date set in the future', async () => { + trainingCsv.DATECOMPLETED = '01/01/2099'; - // describe('_getValidateDateCompletedErrMessage()', async () => { - // it.only('should pass validation and set _dateCompleted if a valid DATECOMPLETED is provided', async () => { - // const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); - // await validator._validateDateCompleted(); + await validator._validateDateCompleted(); - // expect(validator._validationErrors).to.deep.equal([]); - // expect(validator._dateCompleted).to.equal(); - // }); - // }); + expect(validator._dateCompleted).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1020, + errType: 'DATE_COMPLETED_ERROR', + error: 'DATECOMPLETED is in the future', + source: trainingCsv.DATECOMPLETED, + column: 'DATECOMPLETED', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + }); }); }); From 9bdcd678d563e6475b49c7953ac31df2b22bbe90 Mon Sep 17 00:00:00 2001 From: Richard Pentecost Date: Fri, 6 May 2022 15:39:40 +0100 Subject: [PATCH 15/33] remove old admin pages, and unused getAllRegistrations endpoint --- .../registrations/getAllRegistrations.js | 210 ------- server/routes/admin/registrations/index.js | 1 - .../core/services/registrations.service.ts | 5 +- .../cqc-confirmation-dialog.component.html | 10 - .../cqc-confirmation-dialog.component.ts | 20 - .../cqc-status-change.component.html | 66 --- .../cqc-status-change.component.spec.ts | 285 --------- .../cqc-status-change.component.ts | 111 ---- .../cqc-status-changes.component.html | 6 - .../cqc-status-changes.component.spec.ts | 165 ------ .../cqc-status-changes.component.ts | 33 -- ...-emails-confirmation-dialog.component.html | 12 - ...ails-confirmation-dialog.component.spec.ts | 33 -- ...nd-emails-confirmation-dialog.component.ts | 20 - .../search/emails/emails.component.html | 160 ----- .../search/emails/emails.component.spec.ts | 371 ------------ .../search/emails/emails.component.ts | 184 ------ .../parent-confirmation-dialog.component.html | 10 - .../parent-confirmation-dialog.component.ts | 20 - .../parent-request.component.html | 45 -- .../parent-request.component.spec.ts | 286 --------- .../parent-request.component.ts | 111 ---- .../parent-requests.component.html | 6 - .../parent-requests.component.spec.ts | 109 ---- .../parent-requests.component.ts | 33 -- .../registration/registration.component.html | 142 ----- .../registration.component.spec.ts | 254 -------- .../registration/registration.component.ts | 79 --- .../registrations.component.html | 8 - .../registrations.component.spec.ts | 95 --- .../registrations/registrations.component.ts | 31 - .../features/search/search-routing.module.ts | 26 - src/app/features/search/search.component.html | 558 ------------------ .../features/search/search.component.spec.ts | 300 ---------- src/app/features/search/search.component.ts | 190 ------ src/app/features/search/search.module.ts | 51 -- src/app/shared/shared.module.ts | 54 +- 37 files changed, 38 insertions(+), 4062 deletions(-) delete mode 100644 server/routes/admin/registrations/getAllRegistrations.js delete mode 100644 src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.html delete mode 100644 src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.ts delete mode 100644 src/app/features/search/cqc-status-change/cqc-status-change.component.html delete mode 100644 src/app/features/search/cqc-status-change/cqc-status-change.component.spec.ts delete mode 100644 src/app/features/search/cqc-status-change/cqc-status-change.component.ts delete mode 100644 src/app/features/search/cqc-status-changes/cqc-status-changes.component.html delete mode 100644 src/app/features/search/cqc-status-changes/cqc-status-changes.component.spec.ts delete mode 100644 src/app/features/search/cqc-status-changes/cqc-status-changes.component.ts delete mode 100644 src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.html delete mode 100644 src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.spec.ts delete mode 100644 src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.ts delete mode 100644 src/app/features/search/emails/emails.component.html delete mode 100644 src/app/features/search/emails/emails.component.spec.ts delete mode 100644 src/app/features/search/emails/emails.component.ts delete mode 100644 src/app/features/search/parent-request/parent-confirmation-dialog.component.html delete mode 100644 src/app/features/search/parent-request/parent-confirmation-dialog.component.ts delete mode 100644 src/app/features/search/parent-request/parent-request.component.html delete mode 100644 src/app/features/search/parent-request/parent-request.component.spec.ts delete mode 100644 src/app/features/search/parent-request/parent-request.component.ts delete mode 100644 src/app/features/search/parent-requests/parent-requests.component.html delete mode 100644 src/app/features/search/parent-requests/parent-requests.component.spec.ts delete mode 100644 src/app/features/search/parent-requests/parent-requests.component.ts delete mode 100644 src/app/features/search/registration/registration.component.html delete mode 100644 src/app/features/search/registration/registration.component.spec.ts delete mode 100644 src/app/features/search/registration/registration.component.ts delete mode 100644 src/app/features/search/registrations/registrations.component.html delete mode 100644 src/app/features/search/registrations/registrations.component.spec.ts delete mode 100644 src/app/features/search/registrations/registrations.component.ts delete mode 100644 src/app/features/search/search-routing.module.ts delete mode 100644 src/app/features/search/search.component.html delete mode 100644 src/app/features/search/search.component.spec.ts delete mode 100644 src/app/features/search/search.component.ts delete mode 100644 src/app/features/search/search.module.ts diff --git a/server/routes/admin/registrations/getAllRegistrations.js b/server/routes/admin/registrations/getAllRegistrations.js deleted file mode 100644 index 0db0456c1e..0000000000 --- a/server/routes/admin/registrations/getAllRegistrations.js +++ /dev/null @@ -1,210 +0,0 @@ -const moment = require('moment-timezone'); -const { Op } = require('sequelize'); - -const models = require('../../../models'); -const config = require('../../../config/config'); - -const getAllRegistrations = async (req, res) => { - try { - // Get the login, user and establishment records - const loginResults = await models.login.findAll({ - attributes: ['id', 'username'], - where: { - isActive: false, - status: { [Op.or]: ['PENDING', 'IN PROGRESS'] }, - }, - order: [['id', 'DESC']], - include: [ - { - model: models.user, - attributes: [ - 'EmailValue', - 'PhoneValue', - 'FullNameValue', - 'SecurityQuestionValue', - 'SecurityQuestionAnswerValue', - 'created', - ], - include: [ - { - model: models.establishment, - attributes: [ - 'NameValue', - 'IsRegulated', - 'LocationID', - 'ProvID', - 'Address1', - 'Address2', - 'Address3', - 'Town', - 'County', - 'PostCode', - 'NmdsID', - 'EstablishmentID', - 'Status', - 'EstablishmentUID', - ], - include: [ - { - model: models.services, - as: 'mainService', - attributes: ['id', 'name'], - }, - ], - }, - ], - }, - ], - }); - // Get the pending workplace records - const workplaceResults = await models.establishment.findAll({ - attributes: [ - 'NameValue', - 'IsRegulated', - 'LocationID', - 'ProvID', - 'Address1', - 'Address2', - 'Address3', - 'Town', - 'County', - 'PostCode', - 'NmdsID', - 'EstablishmentID', - 'ParentID', - 'ParentUID', - 'created', - 'updatedBy', - 'Status', - 'EstablishmentUID', - ], - where: { - ustatus: { [Op.or]: ['PENDING', 'IN PROGRESS'] }, - }, - order: [['id', 'DESC']], - include: [ - { - model: models.services, - as: 'mainService', - attributes: ['id', 'name'], - }, - ], - }); - - let arrToReturn, loginReturnArr, workplaceReturnArr; - if (loginResults) { - // Reply with mapped results - loginReturnArr = loginResults.map((registration) => { - registration = registration.toJSON(); - return { - name: registration.user.FullNameValue, - username: registration.username, - securityQuestion: registration.user.SecurityQuestionValue, - securityQuestionAnswer: registration.user.SecurityQuestionAnswerValue, - email: registration.user.EmailValue, - phone: registration.user.PhoneValue, - created: registration.user.created, - establishment: { - id: registration.user.establishment.EstablishmentID, - name: registration.user.establishment.NameValue, - isRegulated: registration.user.establishment.IsRegulated, - nmdsId: registration.user.establishment.NmdsID, - address: registration.user.establishment.Address1, - address2: registration.user.establishment.Address2, - address3: registration.user.establishment.Address3, - postcode: registration.user.establishment.PostCode, - town: registration.user.establishment.Town, - county: registration.user.establishment.County, - locationId: registration.user.establishment.LocationID, - provid: registration.user.establishment.ProvID, - mainService: registration.user.establishment.mainService.name, - status: registration.user.establishment.Status, - uid: registration.user.establishment.EstablishmentUID, - }, - }; - }); - } - if (workplaceResults) { - workplaceReturnArr = workplaceResults.map((registration) => { - registration = registration.toJSON(); - return { - created: registration.created, - username: registration.updatedBy, - establishment: { - id: registration.EstablishmentID, - name: registration.NameValue, - isRegulated: registration.IsRegulated, - nmdsId: registration.NmdsID, - address: registration.Address1, - address2: registration.Address2, - address3: registration.Address3, - postcode: registration.PostCode, - town: registration.Town, - county: registration.County, - locationId: registration.LocationID, - provid: registration.ProvID, - mainService: registration.mainService.name, - parentId: registration.ParentID, - parentUid: registration.ParentUID, - status: registration.Status, - uid: registration.EstablishmentUID, - }, - }; - }); - } - - if (loginResults && workplaceResults) { - let loginWorkplaceIds = new Set(loginReturnArr.map((d) => d.establishment.id)); - arrToReturn = [ - ...loginReturnArr, - ...workplaceReturnArr.filter((d) => !loginWorkplaceIds.has(d.establishment.id)), - ]; - - arrToReturn.sort(function (a, b) { - var dateA = new Date(a.created).getTime(); - var dateB = new Date(b.created).getTime(); - return dateB > dateA ? 1 : -1; - }); - - for (let i = 0; i < arrToReturn.length; i++) { - arrToReturn[i].created = moment.utc(arrToReturn[i].created).tz(config.get('timezone')).format('D/M/YYYY h:mma'); - //get parent establishment details - if (!arrToReturn[i].email) { - let fetchQuery = { - where: { - id: arrToReturn[i].establishment.parentId, - }, - }; - let parentEstablishment = await models.establishment.findOne(fetchQuery); - if (parentEstablishment) { - arrToReturn[i].establishment.parentEstablishmentId = parentEstablishment.nmdsId; - } - } - } - - res.status(200).send(arrToReturn); - } else if (loginReturnArr && !workplaceReturnArr) { - loginReturnArr.map((registration) => { - registration.created = moment.utc(registration.created).tz(config.get('timezone')).format('D/M/YYYY h:mma'); - }); - - res.status(200).send(loginReturnArr); - } else if (!loginReturnArr && workplaceReturnArr) { - workplaceReturnArr.map((registration) => { - registration.created = moment.utc(registration.created).tz(config.get('timezone')).format('D/M/YYYY h:mma'); - }); - - res.status(200).send(workplaceReturnArr); - } else { - res.status(200); - } - } catch (error) { - res.status(500); - } -}; - -const router = require('express').Router(); - -router.route('/').get(getAllRegistrations); - -module.exports = router; diff --git a/server/routes/admin/registrations/index.js b/server/routes/admin/registrations/index.js index 166580722a..85b8ed224a 100644 --- a/server/routes/admin/registrations/index.js +++ b/server/routes/admin/registrations/index.js @@ -2,7 +2,6 @@ const express = require('express'); const router = express.Router(); -router.use('/', require('./getAllRegistrations')); router.use('/', require('./getRegistrations')); router.use('/status', require('./getSingleRegistration')); router.use('/updateWorkplaceId', require('./updateWorkplaceId')); diff --git a/src/app/core/services/registrations.service.ts b/src/app/core/services/registrations.service.ts index 0f11afdb36..b2bfa1bc99 100644 --- a/src/app/core/services/registrations.service.ts +++ b/src/app/core/services/registrations.service.ts @@ -14,11 +14,8 @@ import { Observable } from 'rxjs'; }) export class RegistrationsService { constructor(private http: HttpClient) {} - public getAllRegistrations(): Observable { - return this.http.get('/api/admin/registrations'); - } - public getRegistrations(status: string): Observable { + public getRegistrations(status: string): Observable { return this.http.get(`/api/admin/registrations/${status}`); } diff --git a/src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.html b/src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.html deleted file mode 100644 index 8e6d957cbb..0000000000 --- a/src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.html +++ /dev/null @@ -1,10 +0,0 @@ -

- {{data.headingText}} -

- -

{{ data.paragraphText }}

- - - diff --git a/src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.ts b/src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.ts deleted file mode 100644 index 6d2a274f23..0000000000 --- a/src/app/features/search/cqc-status-change/cqc-confirmation-dialog.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, Inject } from '@angular/core'; -import { DialogComponent } from '@core/components/dialog.component'; -import { Dialog, DIALOG_DATA } from '@core/services/dialog.service'; - -@Component({ - selector: 'app-cqc-confirmation-dialog', - templateUrl: './cqc-confirmation-dialog.component.html', -}) -export class CqcConfirmationDialogComponent extends DialogComponent { - constructor( - @Inject(DIALOG_DATA) public data: { headingText: string, paragraphText: string, buttonText: string }, - public dialog: Dialog - ) { - super(data, dialog); - } - - public close(confirmed: boolean) { - this.dialog.close(confirmed); - } -} diff --git a/src/app/features/search/cqc-status-change/cqc-status-change.component.html b/src/app/features/search/cqc-status-change/cqc-status-change.component.html deleted file mode 100644 index 35c90eb968..0000000000 --- a/src/app/features/search/cqc-status-change/cqc-status-change.component.html +++ /dev/null @@ -1,66 +0,0 @@ -

CQC status change for {{ cqcStatusChange.orgName }}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Requested - {{ cqcStatusChange.requested }} - Username - {{ cqcStatusChange.username }} -
Workplace ID - {{ cqcStatusChange.workplaceId }} - Workplace - - {{ cqcStatusChange.orgName }} - -
Requested service - {{ cqcStatusChange.data.requestedService.name }} - Current service - {{ cqcStatusChange.data.currentService.name }} -
- Requested service name - {{ cqcStatusChange.data.requestedService.other }} - Current service name - {{ cqcStatusChange.data.currentService.other }} -
-
- - -
-
-
diff --git a/src/app/features/search/cqc-status-change/cqc-status-change.component.spec.ts b/src/app/features/search/cqc-status-change/cqc-status-change.component.spec.ts deleted file mode 100644 index cf78c5adef..0000000000 --- a/src/app/features/search/cqc-status-change/cqc-status-change.component.spec.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { SharedModule } from '@shared/shared.module'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { render, within } from '@testing-library/angular'; -import { spy } from 'sinon'; -import { of } from 'rxjs'; -import { RouterTestingModule } from '@angular/router/testing'; -import { WindowRef } from '@core/services/window.ref'; - -import { CqcStatusChangeComponent } from './cqc-status-change.component'; -import { CqcChangeData } from '@core/model/cqc-change-data.model'; -import { ApprovalRequest } from '@core/model/approval-request.model'; -import { FeatureFlagsService } from '@shared/services/feature-flags.service'; -import { MockFeatureFlagsService } from '@core/test-utils/MockFeatureFlagService'; - -const testChangeRequestId = 9999; -const testParentRequestUuid = '360c62a1-2e20-410d-a72b-9d4100a11f4e'; -const testUsername = 'Mary Poppins'; -const testOrgname = 'Fawlty Towers'; -const testUserId = 1111; -const testEstablishmentId = 2222; -const testEstablishmentUid = '9efce151-6167-4e99-9cbf-0b9f8ab987fa'; -const testWorkplaceId = 'B1234567'; -const testRequestedDate = new Date(); - -const approveButtonText = 'Approve'; -const rejectButtonText = 'Reject'; -const modalApproveText = 'Approve this change'; -const modalRejectText = 'Reject this change'; - -function cqcStatusChangeGenerator(otherCurrentService = false, otherRequestedService = false, usernameNull = false) { - const payload = { - requestId: testChangeRequestId, - requestUUID: testParentRequestUuid, - establishmentId: testEstablishmentId, - establishmentUid: testEstablishmentUid, - userId: testUserId, - workplaceId: testWorkplaceId, - userName: testUsername, - orgName: testOrgname, - requested: testRequestedDate, - status: 'Pending', - data: { - currentService: { - ID: 1, - name: 'Carers support', - }, - requestedService: { - ID: 2, - name: 'Service Name' - } - } as CqcChangeData - } as ApprovalRequest; - if (otherCurrentService) { - payload.data.currentService = { - ID: 5, - name: 'Other Service', - other: 'Other Service Name' - }; - } - if (otherRequestedService) { - payload.data.requestedService = { - ID: 6, - name: 'Other Service', - other: 'Other Service Name' - }; - if (usernameNull) { - payload.userName = null; - } - } - return payload; -} - -describe('CqcStatusChangeComponent', () => { - - async function getCqcStatusChangeComponent(otherCurrentService = false, otherRequestedService= false, usernameNull = false) { - return render(CqcStatusChangeComponent, { - imports: [ - ReactiveFormsModule, - HttpClientTestingModule, - SharedModule, - RouterTestingModule - ], - providers: [ - { provide: FeatureFlagsService, useClass: MockFeatureFlagsService}, - { - provide: WindowRef, - useClass: WindowRef - }, - ], - componentProperties: { - index: 0, - removeCqcStatusChanges: { - emit: spy(), - } as any, - cqcStatusChange: cqcStatusChangeGenerator(otherCurrentService, otherRequestedService, usernameNull), - }, - }); - } - async function clickFirstApproveButton() { - const component = await getCqcStatusChangeComponent(); - component.getByText(approveButtonText).click(); - const modalConfirmationDialog = await within(document.body).findByRole('dialog'); - return { component, modalConfirmationDialog }; - } - - async function clickFirstRejectButton() { - const component = await getCqcStatusChangeComponent(); - component.getByText(rejectButtonText).click(); - const modalConfirmationDialog = await within(document.body).findByRole('dialog'); - return { component, modalConfirmationDialog }; - } - - it('should create', async () => { - // Act - const component = await getCqcStatusChangeComponent(); - - // Assert - expect(component).toBeTruthy(); - }); - - it('should show up requested service name if requested service has an \'other\' type ', async () => { - const otherCurrentService = false; - const otherRequestedService = true; - const component = await getCqcStatusChangeComponent(otherCurrentService , otherRequestedService); - const otherServiceTitle = await within(document.body).findByTestId('cqc-requested-service-other-title'); - const otherServiceValue = await within(document.body).findByTestId('cqc-requested-service-other-value'); - const cqcStatusChange = cqcStatusChangeGenerator(otherCurrentService , otherRequestedService); - expect(otherServiceTitle.innerHTML).toContain(`Requested service name`); - expect(otherServiceValue.innerHTML).toContain(cqcStatusChange.data.requestedService.other); - - }); - it('shouldn\'t show up current service name if current service doesnt an \'other\' type ', async () => { - - const component = await getCqcStatusChangeComponent(); - - const currentServiceTitle = await within(document.body).queryByTestId('cqc-current-service-other-title'); - const currentServiceValue = await within(document.body).queryByTestId('cqc-current-service-other-value'); - - expect(currentServiceTitle).toBeNull(); - expect(currentServiceValue).toBeNull(); - - }); - it('should be able to approve a CQC Status Change request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstApproveButton(); - const cqcStatusChangeApproval = spyOn(component.fixture.componentInstance.cqcStatusChangeService, 'CqcStatusChangeApproval').and.callThrough(); - - // Act - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(cqcStatusChangeApproval).toHaveBeenCalledWith({ - approvalId: testChangeRequestId, - establishmentId: testEstablishmentId, - userId: testUserId, - rejectionReason: 'Approved', - approve: true, - }); - }); - - it('should be able to reject a CQC Status Change request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstRejectButton(); - const cqcSatusChangeApproval = spyOn(component.fixture.componentInstance.cqcStatusChangeService, 'CqcStatusChangeApproval').and.callThrough(); - - // Act - within(modalConfirmationDialog).getByText(modalRejectText).click(); - - // Assert - expect(cqcSatusChangeApproval).toHaveBeenCalledWith({ - approvalId: testChangeRequestId, - establishmentId: testEstablishmentId, - userId: testUserId, - rejectionReason: 'Rejected', - approve: false, - }); - }); - - it('should show confirmation modal when approving a request', async () => { - // Arrange - const component = await getCqcStatusChangeComponent(); - const confirmationModal = spyOn(component.fixture.componentInstance.dialogService, 'open').and.callThrough(); - - // Act - component.getByText(approveButtonText).click(); - - // Teardown - const modalConfirmationDialog = await within(document.body).findByRole('dialog'); - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(confirmationModal).toHaveBeenCalled(); - }); - - it('confirmation modal should display org name and requested service when approving a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstApproveButton(); - const paragraph = within(modalConfirmationDialog).getByTestId('cqc-confirm-para'); - const cqcStatusChange = cqcStatusChangeGenerator(); - - // Teardown - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(paragraph.innerHTML).toContain(`If you do this, ${testOrgname} will be flagged as CQC regulated and their new main service will be ${cqcStatusChange.data.requestedService.name}.`); - }); - - it('confirmation modal should display org name when rejecting a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstRejectButton(); - const paragraph = within(modalConfirmationDialog).getByTestId('cqc-confirm-para'); - const cqcStatusChange = cqcStatusChangeGenerator(); - - // Teardown - within(modalConfirmationDialog).getByText(modalRejectText).click(); - - // Assert - expect(paragraph.innerHTML).toContain(`If you do this, ${testOrgname} will not be flagged as CQC regulated and their main service will still be ${cqcStatusChange.data.currentService.name}.`); - }); - - it('confirmation modal should show approval message when approving a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstApproveButton(); - const approveHeading = within(modalConfirmationDialog).getByTestId('CQC-confirm-heading'); - const submitButton = within(modalConfirmationDialog).getByText(modalApproveText); - - // Teardown - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(approveHeading.innerHTML).toContain('You\'re about to approve this request.'); - expect(submitButton).toBeTruthy(); - }); - - it('confirmation modal should show rejection message" when rejecting a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstRejectButton(); - const rejectHeading = within(modalConfirmationDialog).getByTestId('CQC-confirm-heading'); - const submitButton = within(modalConfirmationDialog).getByText(modalRejectText); - - // Teardown - within(modalConfirmationDialog).getByText(modalRejectText).click(); - - // Assert - expect(rejectHeading.innerHTML).toContain('You\'re about to reject this request.'); - expect(submitButton).toBeTruthy(); - }); - - it('confirmation message should be shown after approving a request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstApproveButton(); - const addAlert = spyOn(component.fixture.componentInstance.alertService, 'addAlert').and.callThrough(); - spyOn(component.fixture.componentInstance.cqcStatusChangeService, 'CqcStatusChangeApproval').and.returnValue(of({})); - - // Act - within(modalConfirmationDialog).getByText(modalApproveText).click(); - component.fixture.detectChanges(); - - // Assert - expect(addAlert).toHaveBeenCalledWith({ - type: 'success', - message: `You\'ve approved the main service change for ${testOrgname}.` - }); - }); - - it('confirmation message should be shown after rejecting a request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstRejectButton(); - const addAlert = spyOn(component.fixture.componentInstance.alertService, 'addAlert').and.callThrough(); - spyOn(component.fixture.componentInstance.cqcStatusChangeService, 'CqcStatusChangeApproval').and.returnValue(of({})); - - // Act - within(modalConfirmationDialog).getByText(modalRejectText).click(); - component.fixture.detectChanges(); - - // Assert - expect(addAlert).toHaveBeenCalledWith({ - type: 'success', - message: `You\'ve rejected the main service change for ${testOrgname}.`, - }); - }); - -}); - diff --git a/src/app/features/search/cqc-status-change/cqc-status-change.component.ts b/src/app/features/search/cqc-status-change/cqc-status-change.component.ts deleted file mode 100644 index db539c48bc..0000000000 --- a/src/app/features/search/cqc-status-change/cqc-status-change.component.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { HttpErrorResponse } from '@angular/common/http'; -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { AlertService } from '@core/services/alert.service'; -import { CqcStatusChangeService } from '@core/services/cqc-status-change.service'; -import { DialogService } from '@core/services/dialog.service'; -import { SwitchWorkplaceService } from '@core/services/switch-workplace.service'; -import { CqcConfirmationDialogComponent } from '@features/search/cqc-status-change/cqc-confirmation-dialog.component'; - -@Component({ - selector: 'app-cqc-status-change', - templateUrl: './cqc-status-change.component.html', -}) -export class CqcStatusChangeComponent implements OnInit { - @Output() removeCqcStatusChanges: EventEmitter = new EventEmitter(); - @Input() index: number; - @Input() cqcStatusChange: any; - public cqcStatusChangeForm: FormGroup; - public approve: boolean; - public rejectionReason: string; - - constructor( - public cqcStatusChangeService: CqcStatusChangeService, - public switchWorkplaceService: SwitchWorkplaceService, - public dialogService: DialogService, - public alertService: AlertService, - ) {} - ngOnInit() { - this.cqcStatusChangeForm = new FormGroup({}); - } - - get establishmentId() { - return this.cqcStatusChange.establishmentId; - } - - public approveCQCRequest(approve: boolean, rejectionReason: string) { - this.approve = approve; - this.rejectionReason = rejectionReason; - - event.preventDefault(); - - this.dialogService - .open(CqcConfirmationDialogComponent, { - orgName: this.cqcStatusChange.orgName, - headingText: approve ? "You're about to approve this request." : "You're about to reject this request.", - paragraphText: approve - ? `If you do this, ${this.cqcStatusChange.orgName} will be flagged as CQC regulated and their new main service will be ${this.cqcStatusChange.data.requestedService.name}.` - : `If you do this, ${this.cqcStatusChange.orgName} will not be flagged as CQC regulated and their main service will still be ${this.cqcStatusChange.data.currentService.name}.`, - buttonText: approve ? 'Approve this change' : 'Reject this change', - }) - .afterClosed.subscribe((approveConfirmed) => { - if (approveConfirmed) { - this.approveOrRejectRequest(); - } - }); - } - - public navigateToWorkplace(id, username, nmdsId, e): void { - e.preventDefault(); - this.switchWorkplaceService.navigateToWorkplace(id, username, nmdsId); - } - - public onSubmit() { - // Nothing to do here - it's all done via the confirmation dialog. - } - - private approveOrRejectRequest() { - let data; - data = { - approvalId: this.cqcStatusChange.requestId, - establishmentId: this.cqcStatusChange.establishmentId, - userId: this.cqcStatusChange.userId, - rejectionReason: this.rejectionReason, - approve: this.approve, - }; - - this.cqcStatusChangeService.CqcStatusChangeApproval(data).subscribe( - () => { - this.removeCqcStatusChanges.emit(this.index); - this.showConfirmationMessage(); - }, - (err) => { - if (err instanceof HttpErrorResponse) { - this.populateErrorFromServer(err); - } - }, - ); - } - - private showConfirmationMessage() { - const approvedOrRejected = this.approve ? 'approved' : 'rejected'; - - this.alertService.addAlert({ - type: 'success', - message: `You've ${approvedOrRejected} the main service change for ${this.cqcStatusChange.orgName}.`, - }); - } - - private populateErrorFromServer(err) { - const validationErrors = err.error; - - Object.keys(validationErrors).forEach((prop) => { - const formControl = this.cqcStatusChangeForm.get(prop); - if (formControl) { - formControl.setErrors({ - serverError: validationErrors[prop], - }); - } - }); - } -} diff --git a/src/app/features/search/cqc-status-changes/cqc-status-changes.component.html b/src/app/features/search/cqc-status-changes/cqc-status-changes.component.html deleted file mode 100644 index cf6696cad0..0000000000 --- a/src/app/features/search/cqc-status-changes/cqc-status-changes.component.html +++ /dev/null @@ -1,6 +0,0 @@ -

- CQC status changes -

-
- -
diff --git a/src/app/features/search/cqc-status-changes/cqc-status-changes.component.spec.ts b/src/app/features/search/cqc-status-changes/cqc-status-changes.component.spec.ts deleted file mode 100644 index c498a4effc..0000000000 --- a/src/app/features/search/cqc-status-changes/cqc-status-changes.component.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { inject, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; -import { CqcStatusChangeService } from '@core/services/cqc-status-change.service'; -import { WindowRef } from '@core/services/window.ref'; -import { SharedModule } from '@shared/shared.module'; -import { render, RenderResult } from '@testing-library/angular'; -import { of } from 'rxjs'; - -import { CqcStatusChangeComponent } from '../cqc-status-change/cqc-status-change.component'; -import { CqcStatusChangesComponent } from '../cqc-status-changes/cqc-status-changes.component'; -import { FeatureFlagsService } from '@shared/services/feature-flags.service'; -import { MockFeatureFlagsService } from '@core/test-utils/MockFeatureFlagService'; - -describe('CqcStatusChangesComponent', () => { - let component: RenderResult; - - it('can get a CQC Status Change request', () => { - inject([HttpClientTestingModule], async () => { - const cqcStatusChanges = [ - { - requestId: 1, - requestUUID: '360c62a1-2e20-410d-a72b-9d4100a11f4e', - establishmentId: 1, - establishmentUid: '9efce151-6167-4e99-9cbf-0b9f8ab987fa', - userId: 222, - workplaceId: 'I1234567', - username: 'testuser', - orgName: 'testOrgname', - requested: '2019-08-27 16:04:35.914', - status: 'Pending', - data: { - currentService: { - ID: 1, - name: 'Carers support', - }, - requestedService: { - ID: 2, - name: 'Service Name', - }, - }, - }, - { - requestId: 2, - requestUUID: '360c62a1-2e20-410d-a72b-9d4100a11f2a', - establishmentId: 2, - establishmentUid: '9eece151-6167-4e99-9cbf-0b9f8ab111ba', - userId: '333', - workplaceId: 'E1234567', - username: 'testUsername2', - orgName: 'testOrgname2', - requested: '2020-05-20 16:04:35.914', - status: 'Pending', - data: { - currentService: { - ID: 4, - name: ' Some Service', - }, - requestedService: { - ID: 3, - name: 'Service Name', - }, - }, - }, - ]; - - const cqcStatusChangeService = TestBed.inject(CqcStatusChangeService); - spyOn(cqcStatusChangeService, 'getCqcStatusChanges').and.returnValue(of(cqcStatusChanges)); - - const { fixture } = await render(CqcStatusChangesComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, SharedModule, RouterTestingModule], - declarations: [CqcStatusChangeComponent], - providers: [ - { - provide: WindowRef, - useClass: WindowRef, - }, - { provide: FeatureFlagsService, useClass: MockFeatureFlagsService}, - { - provide: CqcStatusChangeService, - useClass: cqcStatusChangeService, - }, - ], - }); - - const { componentInstance } = fixture; - - expect(componentInstance.cqcStatusChanges).toEqual(cqcStatusChanges); - }); - }); - - it('should remove parent requests', async () => { - const cqcStatusChanges = [ - { - requestId: 1, - requestUUID: '360c62a1-2e20-410d-a72b-9d4100a11f4e', - establishmentId: 1, - establishmentUid: '9efce151-6167-4e99-9cbf-0b9f8ab987fa', - userId: 222, - workplaceId: 'I1234567', - username: 'testuser', - orgName: 'testOrgname', - requested: '2019-08-27 16:04:35.914', - status: 'Pending', - data: { - currentService: { - ID: 1, - name: 'Carers support', - }, - requestedService: { - ID: 2, - name: 'Service Name', - }, - }, - }, - { - requestId: 2, - requestUUID: '360c62a1-2e20-410d-a72b-9d4100a11f2a', - establishmentId: 2, - establishmentUid: '9eece151-6167-4e99-9cbf-0b9f8ab111ba', - userId: '333', - workplaceId: 'E1234567', - username: 'testUsername2', - orgName: 'testOrgname2', - requested: '2020-05-20 16:04:35.914', - status: 'Pending', - data: { - currentService: { - ID: 4, - name: ' Some Service', - }, - requestedService: { - ID: 3, - name: 'Service Name', - }, - }, - }, - ]; - - const { fixture } = await render(CqcStatusChangesComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, SharedModule, RouterTestingModule], - declarations: [CqcStatusChangeComponent], - providers: [ - { - provide: WindowRef, - useClass: WindowRef, - }, - { provide: FeatureFlagsService, useClass: MockFeatureFlagsService}, - - ], - componentProperties: { - cqcStatusChanges, - }, - }); - - const { componentInstance } = fixture; - - componentInstance.removeCqcStatusChanges(0); - - expect(componentInstance.cqcStatusChanges).toContain(cqcStatusChanges[0]); - expect(componentInstance.cqcStatusChanges).not.toContain(cqcStatusChanges[1]); - expect(componentInstance.cqcStatusChanges.length).toBe(1); - }); -}); diff --git a/src/app/features/search/cqc-status-changes/cqc-status-changes.component.ts b/src/app/features/search/cqc-status-changes/cqc-status-changes.component.ts deleted file mode 100644 index afca5fe17a..0000000000 --- a/src/app/features/search/cqc-status-changes/cqc-status-changes.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { CqcStatusChangeService } from '@core/services/cqc-status-change.service'; - -@Component({ - selector: 'app-cqc-status-changes', - templateUrl: './cqc-status-changes.component.html' -}) -export class CqcStatusChangesComponent implements OnInit { - public cqcStatusChanges = []; - - constructor( - public cqcStatusChangeService: CqcStatusChangeService - ) {} - - ngOnInit() { - this.getCqcStatusChanges(); - } - - public getCqcStatusChanges() { - this.cqcStatusChangeService.getCqcStatusChanges().subscribe( - data => { - this.cqcStatusChanges = data; - }, - error => this.onError(error) - ); - } - - public removeCqcStatusChanges(index: number) { - this.cqcStatusChanges.splice(index, 1); - } - - private onError(error) {} -} diff --git a/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.html b/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.html deleted file mode 100644 index 3009ad0d2b..0000000000 --- a/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.html +++ /dev/null @@ -1,12 +0,0 @@ -

Are you sure you want to send these emails?

- -

- This will send an email to {{ data.emailCount | number }} - - user. - users. - -

- - - diff --git a/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.spec.ts b/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.spec.ts deleted file mode 100644 index c0b7abeeae..0000000000 --- a/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Dialog, DIALOG_DATA } from '@core/services/dialog.service'; -import { SharedModule } from '@shared/shared.module'; -import { render } from '@testing-library/angular'; - -import { SendEmailsConfirmationDialogComponent } from './send-emails-confirmation-dialog.component'; - -describe('SendEmailsConfirmationDialogComponent', () => { - async function setup() { - return render(SendEmailsConfirmationDialogComponent, { - imports: [SharedModule], - providers: [ - { provide: DIALOG_DATA, useValue: {} }, - { provide: Dialog, useValue: {} }, - ], - }); - } - - it('should display confirmation dialog', async () => { - const component = await setup(); - component.fixture.componentInstance.data.emailCount = 10; - component.fixture.detectChanges(true); - const p = component.fixture.nativeElement.querySelector('p'); - expect(p.innerText).toContain('This will send an email to 10 users.'); - }); - - it('should pluralise the confirmation dialog', async () => { - const component = await setup(); - component.fixture.componentInstance.data.emailCount = 1; - component.fixture.detectChanges(true); - const p = component.fixture.nativeElement.querySelector('p'); - expect(p.innerText).toContain('This will send an email to 1 user.'); - }); -}); diff --git a/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.ts b/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.ts deleted file mode 100644 index 01751b3817..0000000000 --- a/src/app/features/search/emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, Inject } from '@angular/core'; -import { DialogComponent } from '@core/components/dialog.component'; -import { Dialog, DIALOG_DATA } from '@core/services/dialog.service'; - -@Component({ - selector: 'app-send-emails-confirmation-dialog', - templateUrl: './send-emails-confirmation-dialog.component.html', -}) -export class SendEmailsConfirmationDialogComponent extends DialogComponent { - constructor( - @Inject(DIALOG_DATA) public data: { emailCount: number }, - public dialog: Dialog, - ) { - super(data, dialog); - } - - public close(hasConfirmed: boolean) { - this.dialog.close(hasConfirmed); - } -} diff --git a/src/app/features/search/emails/emails.component.html b/src/app/features/search/emails/emails.component.html deleted file mode 100644 index 03a9d86086..0000000000 --- a/src/app/features/search/emails/emails.component.html +++ /dev/null @@ -1,160 +0,0 @@ -

Inactive Workplaces

- -
-
-
-
Total number of inactive workplaces to email
-
- {{ inactiveWorkplaces | number }} -
-
- -
-
- -
- -
- -
-

History

- - - - - - - - - - - - - -
DateTotal emails sent
{{ row.date | date: 'dd/MM/yyyy' }}{{ row.emails | number }}
- - -

No emails have been sent yet.

-
-
-
- - -
- -

Targeted Emails

- -
-
-
-
Total number of users to be emailed
-
- {{ totalEmails | number }} -
-
- -
-
-
- - -
-
- - -
-
-
- - -
-
diff --git a/src/app/features/search/emails/emails.component.spec.ts b/src/app/features/search/emails/emails.component.spec.ts deleted file mode 100644 index e4b74abc6f..0000000000 --- a/src/app/features/search/emails/emails.component.spec.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; -import { ReportService } from '@core/services/report.service'; -import { WindowRef } from '@core/services/window.ref'; -import { SharedModule } from '@shared/shared.module'; -import { fireEvent, render, within } from '@testing-library/angular'; -import { of } from 'rxjs'; - -import { SearchModule } from '../search.module'; -import { EmailsComponent } from './emails.component'; - -describe('EmailsComponent', () => { - async function setup() { - return render(EmailsComponent, { - imports: [SharedModule, SearchModule, HttpClientTestingModule, RouterTestingModule], - providers: [ - { - provide: WindowRef, - useClass: WindowRef, - }, - { - provide: ActivatedRoute, - useValue: { - snapshot: { - data: { - emailCampaignHistory: [], - inactiveWorkplaces: { inactiveWorkplaces: 0 }, - emailTemplates: { - templates: [], - }, - }, - }, - }, - }, - ], - }); - } - - describe('Inactive workplaces', () => { - it('should create', async () => { - const component = await setup(); - expect(component).toBeTruthy(); - }); - - it('should display the total number of inactive workplaces', async () => { - const component = await setup(); - - component.fixture.componentInstance.inactiveWorkplaces = 1250; - component.fixture.detectChanges(); - - const numInactiveWorkplaces = component.getByTestId('inactiveWorkplaces'); - const sendEmailsButton = component.fixture.nativeElement.querySelectorAll('button'); - expect(numInactiveWorkplaces.innerHTML).toContain('1,250'); - expect(sendEmailsButton[0].disabled).toBeFalsy(); - }); - - it('should disable the "Send emails" button when there are no inactive workplaces', async () => { - const component = await setup(); - - component.fixture.componentInstance.inactiveWorkplaces = 0; - component.fixture.detectChanges(); - - const sendEmailsButton = component.fixture.nativeElement.querySelectorAll('button'); - expect(sendEmailsButton[0].disabled).toBeTruthy(); - }); - - it('should display all existing history', async () => { - const component = await setup(); - - component.fixture.componentInstance.history = [ - { - date: '2021-01-05', - emails: 351, - }, - { - date: '2020-12-05', - emails: 772, - }, - ]; - - component.fixture.detectChanges(); - - const numInactiveWorkplaces = component.getByTestId('emailCampaignHistory'); - expect(numInactiveWorkplaces.innerHTML).toContain('05/01/2021'); - expect(numInactiveWorkplaces.innerHTML).toContain('772'); - expect(numInactiveWorkplaces.innerHTML).toContain('05/12/2020'); - expect(numInactiveWorkplaces.innerHTML).toContain('351'); - }); - - it('should display a message when no emails have been sent', async () => { - const component = await setup(); - - const numInactiveWorkplaces = component.getByTestId('emailCampaignHistory'); - expect(numInactiveWorkplaces.innerHTML).toContain('No emails have been sent yet.'); - }); - - it('should call confirmSendEmails when the "Send emails" button is clicked', async () => { - const component = await setup(); - - component.fixture.componentInstance.inactiveWorkplaces = 25; - component.fixture.detectChanges(); - - fireEvent.click(component.getByText('Send emails', { exact: true })); - - const dialog = await within(document.body).findByRole('dialog'); - const dialogHeader = within(dialog).getByTestId('send-emails-confirmation-header'); - - expect(dialogHeader).toBeTruthy(); - }); - - [ - { - emails: 2500, - expectedMessage: '2,500 emails have been scheduled to be sent.', - }, - { - emails: 1, - expectedMessage: '1 email has been scheduled to be sent.', - }, - ].forEach(({ emails, expectedMessage }) => { - it('should display an alert when the "Send emails" button is clicked', async () => { - const component = await setup(); - - component.fixture.componentInstance.inactiveWorkplaces = 2500; - component.fixture.detectChanges(); - - fireEvent.click(component.getByText('Send emails', { exact: true })); - - const emailCampaignService = TestBed.inject(EmailCampaignService); - spyOn(emailCampaignService, 'createInactiveWorkplacesCampaign').and.returnValue( - of({ - emails, - }), - ); - - spyOn(emailCampaignService, 'getInactiveWorkplaces').and.returnValue( - of({ - inactiveWorkplaces: 0, - }), - ); - - const addAlert = spyOn(component.fixture.componentInstance.alertService, 'addAlert').and.callThrough(); - - const dialog = await within(document.body).findByRole('dialog'); - within(dialog).getByText('Send emails').click(); - - expect(addAlert).toHaveBeenCalledWith({ - type: 'success', - message: expectedMessage, - }); - - const numInactiveWorkplaces = component.getByTestId('inactiveWorkplaces'); - expect(numInactiveWorkplaces.innerHTML).toContain('0'); - }); - }); - - it('should download a report when the "Download report" button is clicked', async () => { - const component = await setup(); - - const emailCampaignService = TestBed.inject(EmailCampaignService); - const getReport = spyOn(emailCampaignService, 'getInactiveWorkplacesReport').and.callFake(() => of(null)); - const saveAs = spyOn(component.fixture.componentInstance, 'saveFile').and.callFake(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function - - component.fixture.componentInstance.inactiveWorkplaces = 25; - component.fixture.detectChanges(); - - fireEvent.click(component.getByText('Download report', { exact: false })); - - expect(getReport).toHaveBeenCalled(); - expect(saveAs).toHaveBeenCalled(); - }); - }); - - describe('Targeted emails', () => { - it('should display the total number of emails to be sent', async () => { - const component = await setup(); - - component.fixture.componentInstance.totalEmails = 1500; - component.fixture.detectChanges(); - - const numInactiveWorkplaces = component.getByTestId('totalEmails'); - expect(numInactiveWorkplaces.innerHTML).toContain('1,500'); - }); - - it("should display 0 emails to be sent when the group and template haven't been selected", async () => { - const component = await setup(); - - component.fixture.componentInstance.emailGroup = null; - component.fixture.componentInstance.selectedTemplateId = null; - component.fixture.detectChanges(); - - const numInactiveWorkplaces = component.getByTestId('totalEmails'); - expect(numInactiveWorkplaces.innerHTML).toContain('0'); - }); - - it("should disable the Send emails button when the group and template haven't been selected", async () => { - const component = await setup(); - - component.fixture.componentInstance.emailGroup = ''; - component.fixture.componentInstance.selectedTemplateId = ''; - component.fixture.detectChanges(); - - const sendEmailsButton = component.fixture.nativeElement.querySelectorAll('button'); - expect(sendEmailsButton[1].disabled).toBeTruthy(); - }); - - it('should update the total emails when updateTotalEmails() is called', async () => { - const component = await setup(); - const emailCampaignService = TestBed.inject(EmailCampaignService); - const getTargetedTotalEmailsSpy = spyOn(emailCampaignService, 'getTargetedTotalEmails').and.callFake(() => - of({ totalEmails: 1500 }), - ); - - component.fixture.componentInstance.updateTotalEmails('primaryUsers'); - component.fixture.detectChanges(); - - expect(component.fixture.componentInstance.totalEmails).toEqual(1500); - expect(getTargetedTotalEmailsSpy).toHaveBeenCalled(); - }); - - it('should display the template names as options', async () => { - const component = await setup(); - const templates = [ - { - id: 1, - name: 'Template 1', - }, - { - id: 2, - name: 'Template 2', - }, - ]; - - component.fixture.componentInstance.templates = templates; - component.fixture.detectChanges(); - - const templateDropdown = component.getByTestId('selectedTemplateId'); - - expect(templateDropdown.childNodes[1].textContent).toEqual('Template 1'); - expect(templateDropdown.childNodes[2].textContent).toEqual('Template 2'); - }); - - it('should call confirmSendEmails when the "Send emails to selected group" button is clicked', async () => { - const component = await setup(); - - component.fixture.componentInstance.totalEmails = 45; - component.fixture.componentInstance.emailGroup = 'primaryUsers'; - component.fixture.componentInstance.selectedTemplateId = '1'; - component.fixture.detectChanges(); - - fireEvent.click(component.getByText('Send emails to selected group', { exact: true })); - - const dialog = await within(document.body).findByRole('dialog'); - const dialogHeader = within(dialog).getByTestId('send-emails-confirmation-header'); - - expect(dialogHeader).toBeTruthy(); - }); - - [ - { - emails: 1500, - expectedMessage: '1,500 emails have been scheduled to be sent.', - }, - { - emails: 1, - expectedMessage: '1 email has been scheduled to be sent.', - }, - ].forEach(({ emails, expectedMessage }) => { - it('should display an alert when the "Send emails to selected group" button is clicked', async () => { - const component = await setup(); - - component.fixture.componentInstance.totalEmails = emails; - component.fixture.componentInstance.emailGroup = 'primaryUsers'; - component.fixture.componentInstance.selectedTemplateId = '1'; - component.fixture.detectChanges(); - - fireEvent.click(component.getByText('Send emails to selected group', { exact: true })); - - const emailCampaignService = TestBed.inject(EmailCampaignService); - spyOn(emailCampaignService, 'createTargetedEmailsCampaign').and.returnValue( - of({ - emails, - }), - ); - - const addAlert = spyOn(component.fixture.componentInstance.alertService, 'addAlert').and.callThrough(); - - const dialog = await within(document.body).findByRole('dialog'); - within(dialog).getByText('Send emails').click(); - - expect(addAlert).toHaveBeenCalledWith({ - type: 'success', - message: expectedMessage, - }); - component.fixture.detectChanges(); - expect(component.fixture.componentInstance.emailGroup).toEqual(''); - expect(component.fixture.componentInstance.selectedTemplateId).toEqual(''); - }); - }); - }); - - describe('Reports', () => { - it('should download a registration survey report when the "Registration survey" button is clicked', async () => { - const component = await setup(); - - const reportService = TestBed.inject(ReportService); - const getReport = spyOn(reportService, 'getRegistrationSurveyReport').and.callFake(() => of(null)); - const saveAs = spyOn(component.fixture.componentInstance, 'saveFile').and.callFake(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function - - fireEvent.click(component.getByText('Registration survey', { exact: false })); - - expect(getReport).toHaveBeenCalled(); - expect(saveAs).toHaveBeenCalled(); - }); - - it('should download a satisfaction survey report when the "Satisfaction survey" button is clicked', async () => { - const component = await setup(); - - const reportService = TestBed.inject(ReportService); - const getReport = spyOn(reportService, 'getSatisfactionSurveyReport').and.callFake(() => of(null)); - const saveAs = spyOn(component.fixture.componentInstance, 'saveFile').and.callFake(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function - - fireEvent.click(component.getByText('Satisfaction survey', { exact: false })); - - expect(getReport).toHaveBeenCalled(); - expect(saveAs).toHaveBeenCalled(); - }); - - it('should download a delete report when the "Delete report" button is clicked', async () => { - const component = await setup(); - - const reportService = TestBed.inject(ReportService); - const getReport = spyOn(reportService, 'getDeleteReport').and.callFake(() => of(null)); - const saveAs = spyOn(component.fixture.componentInstance, 'saveFile').and.callFake(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function - - fireEvent.click(component.getByText('Delete report', { exact: false })); - - expect(getReport).toHaveBeenCalled(); - expect(saveAs).toHaveBeenCalled(); - }); - - it('should download a local authority progress report when the "Local admin authority progress" button is clicked', async () => { - const component = await setup(); - - const reportService = TestBed.inject(ReportService); - const getReport = spyOn(reportService, 'getLocalAuthorityAdminReport').and.callFake(() => of(null)); - const saveAs = spyOn(component.fixture.componentInstance, 'saveFile').and.callFake(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function - - fireEvent.click(component.getByText('Local admin authority progress', { exact: false })); - - expect(getReport).toHaveBeenCalled(); - expect(saveAs).toHaveBeenCalled(); - }); - - it('should download a WDF Summary report when the "WDF Summary Report" button is clicked', async () => { - const component = await setup(); - - const reportService = TestBed.inject(ReportService); - const getReport = spyOn(reportService, 'getWdfSummaryReport').and.callFake(() => of(null)); - const saveAs = spyOn(component.fixture.componentInstance, 'saveFile').and.callFake(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function - - fireEvent.click(component.getByText('WDF summary report', { exact: false })); - - expect(getReport).toHaveBeenCalled(); - expect(saveAs).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/app/features/search/emails/emails.component.ts b/src/app/features/search/emails/emails.component.ts deleted file mode 100644 index 0ce158ebf2..0000000000 --- a/src/app/features/search/emails/emails.component.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { DecimalPipe } from '@angular/common'; -import { HttpResponse } from '@angular/common/http'; -import { Component, OnDestroy } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { EmailType, TotalEmailsResponse } from '@core/model/emails.model'; -import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; -import { AlertService } from '@core/services/alert.service'; -import { DialogService } from '@core/services/dialog.service'; -import { ReportService } from '@core/services/report.service'; -import { saveAs } from 'file-saver'; -import { Subscription } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; - -import { - SendEmailsConfirmationDialogComponent, -} from './dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component'; - -@Component({ - selector: 'app-emails', - templateUrl: './emails.component.html', -}) -export class EmailsComponent implements OnDestroy { - public inactiveWorkplaces = this.route.snapshot.data.inactiveWorkplaces.inactiveWorkplaces; - public totalEmails = 0; - public emailGroup = ''; - public selectedTemplateId = ''; - public templates = this.route.snapshot.data.emailTemplates.templates; - public history = this.route.snapshot.data.emailCampaignHistory; - public isAdmin: boolean; - public now: Date = new Date(); - private subscriptions: Subscription = new Subscription(); - public emailType = EmailType; - - constructor( - public alertService: AlertService, - public dialogService: DialogService, - private route: ActivatedRoute, - private emailCampaignService: EmailCampaignService, - private decimalPipe: DecimalPipe, - private reportsService: ReportService, - ) {} - - ngOnDestroy(): void { - this.subscriptions.unsubscribe(); - } - - public updateTotalEmails(groupType: string): void { - if (groupType) { - this.subscriptions.add( - this.emailCampaignService - .getTargetedTotalEmails(groupType) - .subscribe((totalEmails: TotalEmailsResponse) => (this.totalEmails = totalEmails.totalEmails)), - ); - } else { - this.totalEmails = 0; - } - } - - public confirmSendEmails(event: Event, emailCount: number, type: EmailType): void { - event.preventDefault(); - - this.subscriptions.add( - this.dialogService - .open(SendEmailsConfirmationDialogComponent, { emailCount }) - .afterClosed.subscribe((hasConfirmed) => { - if (hasConfirmed) { - this.sendEmails(type); - } - }), - ); - } - - private sendEmails(type: EmailType): void { - switch (type) { - case EmailType.InactiveWorkplaces: - this.sendInactiveEmails(); - break; - - case EmailType.TargetedEmails: - this.sendTargetedEmails(); - break; - } - } - - private sendInactiveEmails(): void { - this.subscriptions.add( - this.emailCampaignService - .createInactiveWorkplacesCampaign() - .pipe( - switchMap((latestCampaign) => { - return this.emailCampaignService.getInactiveWorkplaces().pipe( - map(({ inactiveWorkplaces }) => ({ - latestCampaign, - inactiveWorkplaces, - })), - ); - }), - ) - .subscribe(({ latestCampaign, inactiveWorkplaces }) => { - this.history.unshift(latestCampaign); - - this.alertService.addAlert({ - type: 'success', - message: `${this.decimalPipe.transform(latestCampaign.emails)} ${ - latestCampaign.emails > 1 ? 'emails have' : 'email has' - } been scheduled to be sent.`, - }); - - this.inactiveWorkplaces = inactiveWorkplaces; - }), - ); - } - - private sendTargetedEmails(): void { - this.subscriptions.add( - this.emailCampaignService.createTargetedEmailsCampaign(this.emailGroup, this.selectedTemplateId).subscribe(() => { - this.alertService.addAlert({ - type: 'success', - message: `${this.decimalPipe.transform(this.totalEmails)} ${ - this.totalEmails > 1 ? 'emails have' : 'email has' - } been scheduled to be sent.`, - }); - this.emailGroup = ''; - this.selectedTemplateId = ''; - this.totalEmails = 0; - }), - ); - } - - public downloadReport(event: Event): void { - event.preventDefault(); - - this.subscriptions.add( - this.emailCampaignService.getInactiveWorkplacesReport().subscribe((response) => { - this.saveFile(response); - }), - ); - } - - public downloadRegistrationSurveyReport(event: Event): void { - event.preventDefault(); - this.subscriptions.add( - this.reportsService.getRegistrationSurveyReport().subscribe((response) => { - this.saveFile(response); - }), - ); - } - - public downloadSatisfactionSurveyReport(event: Event) { - event.preventDefault(); - this.subscriptions.add( - this.reportsService.getSatisfactionSurveyReport().subscribe((response) => { - this.saveFile(response); - }), - ); - } - - public downloadLocalAuthorityAdminReport(event: Event) { - event.preventDefault(); - this.subscriptions.add( - this.reportsService.getLocalAuthorityAdminReport().subscribe((response) => this.saveFile(response)), - ); - } - - public downloadDeleteReport(event: Event) { - event.preventDefault(); - this.subscriptions.add(this.reportsService.getDeleteReport().subscribe((response) => this.saveFile(response))); - } - - public downloadWdfSummaryReport(event: Event) { - event.preventDefault(); - this.subscriptions.add(this.reportsService.getWdfSummaryReport().subscribe((response) => this.saveFile(response))); - } - - public saveFile(response: HttpResponse) { - const filenameRegEx = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; - const header = response.headers.get('content-disposition'); - const filenameMatches = header && header.match(filenameRegEx); - const filename = filenameMatches && filenameMatches.length > 1 ? filenameMatches[1] : null; - const blob = new Blob([response.body], { type: 'text/plain;charset=utf-8' }); - - saveAs(blob, filename); - } -} diff --git a/src/app/features/search/parent-request/parent-confirmation-dialog.component.html b/src/app/features/search/parent-request/parent-confirmation-dialog.component.html deleted file mode 100644 index 3e3b3fb178..0000000000 --- a/src/app/features/search/parent-request/parent-confirmation-dialog.component.html +++ /dev/null @@ -1,10 +0,0 @@ -

- {{data.headingText}} -

- -

{{ data.paragraphText }}

- - - diff --git a/src/app/features/search/parent-request/parent-confirmation-dialog.component.ts b/src/app/features/search/parent-request/parent-confirmation-dialog.component.ts deleted file mode 100644 index 518545077c..0000000000 --- a/src/app/features/search/parent-request/parent-confirmation-dialog.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, Inject } from '@angular/core'; -import { DialogComponent } from '@core/components/dialog.component'; -import { Dialog, DIALOG_DATA } from '@core/services/dialog.service'; - -@Component({ - selector: 'app-parent-confirmation-dialog', - templateUrl: './parent-confirmation-dialog.component.html', -}) -export class ParentConfirmationDialogComponent extends DialogComponent { - constructor( - @Inject(DIALOG_DATA) public data: { headingText: string, paragraphText: string, buttonText: string }, - public dialog: Dialog - ) { - super(data, dialog); - } - - public close(confirmed: boolean) { - this.dialog.close(confirmed); - } -} diff --git a/src/app/features/search/parent-request/parent-request.component.html b/src/app/features/search/parent-request/parent-request.component.html deleted file mode 100644 index 532747a816..0000000000 --- a/src/app/features/search/parent-request/parent-request.component.html +++ /dev/null @@ -1,45 +0,0 @@ -

Parent Request from {{ parentRequest.orgName }}

-
- - - - - - - - - - - - - - - - - - -
Requested - {{ parentRequest.requested }} - Username - {{ parentRequest.userName }} -
Workplace ID - {{ parentRequest.workplaceId }} - Workplace - - {{ parentRequest.orgName }} - -
-
- - -
-
-
diff --git a/src/app/features/search/parent-request/parent-request.component.spec.ts b/src/app/features/search/parent-request/parent-request.component.spec.ts deleted file mode 100644 index 441df5abbc..0000000000 --- a/src/app/features/search/parent-request/parent-request.component.spec.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { HttpResponse } from '@angular/common/http'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; -import { WindowRef } from '@core/services/window.ref'; -import { MockFeatureFlagsService } from '@core/test-utils/MockFeatureFlagService'; -import { FeatureFlagsService } from '@shared/services/feature-flags.service'; -import { SharedModule } from '@shared/shared.module'; -import { render, within } from '@testing-library/angular'; -import { of } from 'rxjs'; -import { spy } from 'sinon'; - -import { ParentRequestComponent } from './parent-request.component'; - -const testParentRequestId = 9999; -const testParentRequestUuid = '360c62a1-2e20-410d-a72b-9d4100a11f4e'; -const testUsername = 'Mary Poppins'; -const testOrgname = 'Fawlty Towers'; -const testUserId = 1111; -const testEstablishmentId = 2222; -const testEstablishmentUid = '9efce151-6167-4e99-9cbf-0b9f8ab987fa'; -const testWorkplaceId = 'B1234567'; -const testRequestedDate = new Date(); -const parentRequest = { - requestId: testParentRequestId, - requestUUID: testParentRequestUuid, - establishmentId: testEstablishmentId, - establishmentUid: testEstablishmentUid, - userId: testUserId, - workplaceId: testWorkplaceId, - username: testUsername, - orgName: testOrgname, - requested: testRequestedDate, -}; - -const approveButtonText = 'Approve'; -const rejectButtonText = 'Reject'; -const modalApproveText = 'Approve request'; -const modalRejectText = 'Reject request'; - -describe('ParentRequestComponent', () => { - async function setupForSwitchWorkplace() { - const component = await getParentRequestComponent(); - const authToken = 'This is an auth token'; - const swappedEstablishmentData = { - headers: { - get: (header) => { - return header === 'authorization' ? authToken : null; - }, - }, - body: { - establishment: { - uid: testEstablishmentUid, - }, - }, - } as HttpResponse; - const getNewEstablishmentId = spyOn( - component.fixture.componentInstance.switchWorkplaceService, - 'getNewEstablishmentId', - ).and.returnValue(of(swappedEstablishmentData)); - const workplace = { uid: testEstablishmentUid }; - parentRequest.username = testUsername; - - return { - component, - authToken, - workplace, - getNewEstablishmentId, - }; - } - - async function getParentRequestComponent() { - return render(ParentRequestComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, SharedModule, RouterTestingModule], - providers: [ - { - provide: WindowRef, - useClass: WindowRef, - }, - { provide: FeatureFlagsService, useClass: MockFeatureFlagsService }, - ], - componentProperties: { - index: 0, - removeParentRequest: { - emit: spy(), - } as any, - parentRequest, - }, - }); - } - - async function clickFirstApproveButton() { - const component = await getParentRequestComponent(); - component.getByText(approveButtonText).click(); - const modalConfirmationDialog = await within(document.body).findByRole('dialog'); - return { component, modalConfirmationDialog }; - } - - async function clickFirstRejectButton() { - const component = await getParentRequestComponent(); - component.getByText(rejectButtonText).click(); - const modalConfirmationDialog = await within(document.body).findByRole('dialog'); - return { component, modalConfirmationDialog }; - } - - it('should create', async () => { - // Act - const component = await getParentRequestComponent(); - - // Assert - expect(component).toBeTruthy(); - }); - - it('should be able to approve a become-a-parent request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstApproveButton(); - const parentRequestApproval = spyOn( - component.fixture.componentInstance.parentRequestsService, - 'parentApproval', - ).and.callThrough(); - - // Act - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(parentRequestApproval).toHaveBeenCalledWith({ - parentRequestId: testParentRequestId, - establishmentId: testEstablishmentId, - userId: testUserId, - rejectionReason: 'Approved', - approve: true, - }); - }); - - it('should be able to reject a become-a-parent request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstRejectButton(); - const parentRequestApproval = spyOn( - component.fixture.componentInstance.parentRequestsService, - 'parentApproval', - ).and.callThrough(); - - // Act - within(modalConfirmationDialog).getByText(modalRejectText).click(); - - // Assert - expect(parentRequestApproval).toHaveBeenCalledWith({ - parentRequestId: testParentRequestId, - establishmentId: testEstablishmentId, - userId: testUserId, - rejectionReason: 'Rejected', - approve: false, - }); - }); - - it('should show confirmation modal when approving a request', async () => { - // Arrange - const component = await getParentRequestComponent(); - const confirmationModal = spyOn(component.fixture.componentInstance.dialogService, 'open').and.callThrough(); - - // Act - component.getByText(approveButtonText).click(); - - // Teardown - const modalConfirmationDialog = await within(document.body).findByRole('dialog'); - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(confirmationModal).toHaveBeenCalled(); - }); - - it('confirmation modal should display org name when approving a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstApproveButton(); - const paragraph = within(modalConfirmationDialog).getByTestId('parent-confirm-para'); - - // Teardown - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(paragraph.innerHTML).toContain(`If you do this, ${testOrgname} will become a parent workplace`); - }); - - it('confirmation modal should display org name when rejecting a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstRejectButton(); - const paragraph = within(modalConfirmationDialog).getByTestId('parent-confirm-para'); - - // Teardown - within(modalConfirmationDialog).getByText(modalRejectText).click(); - - // Assert - expect(paragraph.innerHTML).toContain(`If you do this, ${testOrgname} will not become a parent workplace`); - }); - - it('confirmation modal should show "Approve request" when approving a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstApproveButton(); - const approveHeading = within(modalConfirmationDialog).getByTestId('parent-confirm-heading'); - const submitButton = within(modalConfirmationDialog).getByText(modalApproveText); - - // Teardown - within(modalConfirmationDialog).getByText(modalApproveText).click(); - - // Assert - expect(approveHeading.innerHTML).toContain("You're about to approve this request."); - expect(submitButton).toBeTruthy(); - }); - - it('confirmation modal should show "Reject request" when rejecting a request', async () => { - // Act - const { modalConfirmationDialog } = await clickFirstRejectButton(); - const rejectHeading = within(modalConfirmationDialog).getByTestId('parent-confirm-heading'); - const submitButton = within(modalConfirmationDialog).getByText(modalRejectText); - - // Teardown - within(modalConfirmationDialog).getByText(modalRejectText).click(); - - // Assert - expect(rejectHeading.innerHTML).toContain("You're about to reject this request."); - expect(submitButton).toBeTruthy(); - }); - - it('confirmation message should be shown after approving a request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstApproveButton(); - const addAlert = spyOn(component.fixture.componentInstance.alertService, 'addAlert').and.callThrough(); - spyOn(component.fixture.componentInstance.parentRequestsService, 'parentApproval').and.returnValue(of({})); - - // Act - within(modalConfirmationDialog).getByText(modalApproveText).click(); - component.fixture.detectChanges(); - - // Assert - expect(addAlert).toHaveBeenCalledWith({ - type: 'success', - message: `Parent request approved for ${testOrgname}.`, - }); - }); - - it('confirmation message should be shown after rejecting a request', async () => { - // Arrange - const { component, modalConfirmationDialog } = await clickFirstRejectButton(); - const addAlert = spyOn(component.fixture.componentInstance.alertService, 'addAlert').and.callThrough(); - spyOn(component.fixture.componentInstance.parentRequestsService, 'parentApproval').and.returnValue(of({})); - - // Act - within(modalConfirmationDialog).getByText(modalRejectText).click(); - component.fixture.detectChanges(); - - // Assert - expect(addAlert).toHaveBeenCalledWith({ - type: 'success', - message: `Parent request rejected for ${testOrgname}.`, - }); - }); - - it('should load workplace-specific notifications if user name not populated when switching to new workplace.', async () => { - // Arrange - const { component } = await setupForSwitchWorkplace(); - parentRequest.username = null; - const notificationData = { dummyNotification: 'I am a notification' }; - const getAllNotificationWorkplace = spyOn( - component.fixture.componentInstance.switchWorkplaceService, - 'getAllNotificationWorkplace', - ).and.returnValue(of(notificationData)); - - // Act - component.getByText(testOrgname).click(); - component.fixture.detectChanges(); - - // Assert - expect(getAllNotificationWorkplace).toHaveBeenCalled(); - }); - - it('should swap establishments when switching to new workplace', async () => { - const { component, getNewEstablishmentId } = await setupForSwitchWorkplace(); - - // Act - component.getByText(testOrgname).click(); - component.fixture.detectChanges(); - - // Assert - expect(getNewEstablishmentId).toHaveBeenCalled(); - }); -}); diff --git a/src/app/features/search/parent-request/parent-request.component.ts b/src/app/features/search/parent-request/parent-request.component.ts deleted file mode 100644 index 956b7bfd39..0000000000 --- a/src/app/features/search/parent-request/parent-request.component.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { HttpErrorResponse } from '@angular/common/http'; -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormGroup } from '@angular/forms'; -import { AlertService } from '@core/services/alert.service'; -import { DialogService } from '@core/services/dialog.service'; -import { ParentRequestsService } from '@core/services/parent-requests.service'; -import { SwitchWorkplaceService } from '@core/services/switch-workplace.service'; -import { ParentConfirmationDialogComponent } from '@features/search/parent-request/parent-confirmation-dialog.component'; - -@Component({ - selector: 'app-parent-request', - templateUrl: './parent-request.component.html', -}) -export class ParentRequestComponent implements OnInit { - @Output() removeParentRequest: EventEmitter = new EventEmitter(); - @Input() index: number; - @Input() parentRequest: any; - public parentRequestForm: FormGroup; - public approve: boolean; - public rejectionReason: string; - - constructor( - public parentRequestsService: ParentRequestsService, - public switchWorkplaceService: SwitchWorkplaceService, - public dialogService: DialogService, - public alertService: AlertService, - ) {} - ngOnInit() { - this.parentRequestForm = new FormGroup({}); - } - - get establishmentId() { - return this.parentRequest.establishmentId; - } - - public approveParentRequest(approve: boolean, rejectionReason: string) { - this.approve = approve; - this.rejectionReason = rejectionReason; - - event.preventDefault(); - - this.dialogService - .open(ParentConfirmationDialogComponent, { - orgName: this.parentRequest.orgName, - headingText: approve ? "You're about to approve this request." : "You're about to reject this request.", - paragraphText: approve - ? `If you do this, ${this.parentRequest.orgName} will become a parent workplace.` - : `If you do this, ${this.parentRequest.orgName} will not become a parent workplace.`, - buttonText: approve ? 'Approve request' : 'Reject request', - }) - .afterClosed.subscribe((approveConfirmed) => { - if (approveConfirmed) { - this.approveOrRejectRequest(); - } - }); - } - - public navigateToWorkplace(id, username, nmdsId, e): void { - e.preventDefault(); - this.switchWorkplaceService.navigateToWorkplace(id, username, nmdsId); - } - - public onSubmit() { - // Nothing to do here - it's all done via the confirmation dialog. - } - - private approveOrRejectRequest() { - let data; - data = { - parentRequestId: this.parentRequest.requestId, - establishmentId: this.parentRequest.establishmentId, - userId: this.parentRequest.userId, - rejectionReason: this.rejectionReason, - approve: this.approve, - }; - - this.parentRequestsService.parentApproval(data).subscribe( - () => { - this.removeParentRequest.emit(this.index); - this.showConfirmationMessage(); - }, - (err) => { - if (err instanceof HttpErrorResponse) { - this.populateErrorFromServer(err); - } - }, - ); - } - - private showConfirmationMessage() { - const approvedOrRejected = this.approve ? 'approved' : 'rejected'; - - this.alertService.addAlert({ - type: 'success', - message: `Parent request ${approvedOrRejected} for ${this.parentRequest.orgName}.`, - }); - } - - private populateErrorFromServer(err) { - const validationErrors = err.error; - - Object.keys(validationErrors).forEach((prop) => { - const formControl = this.parentRequestForm.get(prop); - if (formControl) { - formControl.setErrors({ - serverError: validationErrors[prop], - }); - } - }); - } -} diff --git a/src/app/features/search/parent-requests/parent-requests.component.html b/src/app/features/search/parent-requests/parent-requests.component.html deleted file mode 100644 index 1bd182f35b..0000000000 --- a/src/app/features/search/parent-requests/parent-requests.component.html +++ /dev/null @@ -1,6 +0,0 @@ -

- Requests to Become a Parent -

-
- -
diff --git a/src/app/features/search/parent-requests/parent-requests.component.spec.ts b/src/app/features/search/parent-requests/parent-requests.component.spec.ts deleted file mode 100644 index 44df2c2d43..0000000000 --- a/src/app/features/search/parent-requests/parent-requests.component.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { inject, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; -import { ParentRequestsService } from '@core/services/parent-requests.service'; -import { WindowRef } from '@core/services/window.ref'; -import { SharedModule } from '@shared/shared.module'; -import { render, RenderResult } from '@testing-library/angular'; -import { of } from 'rxjs'; - -import { ParentRequestComponent } from '../parent-request/parent-request.component'; -import { ParentRequestsComponent } from './parent-requests.component'; -import { FeatureFlagsService } from '@shared/services/feature-flags.service'; -import { MockFeatureFlagsService } from '@core/test-utils/MockFeatureFlagService'; - -describe('ParentRequestsComponent', () => { - let component: RenderResult; - - it('can get parent requests', () => { - inject([HttpClientTestingModule], async () => { - const parentRequests = [ - { - establishmentId: 1111, - workplaceId: 'I1234567', - userName: 'Magnificent Maisie', - orgName: 'Marvellous Mansions', - requested: '2019-08-27 16:04:35.914', - }, - { - establishmentId: 3333, - workplaceId: 'B9999999', - userName: 'Everso Stupid', - orgName: 'Everly Towers', - requested: '2020-05-20 16:04:35.914', - }, - ]; - - const parentRequestsService = TestBed.inject(ParentRequestsService); - spyOn(parentRequestsService, 'getParentRequests').and.returnValue(of(parentRequests)); - - const { fixture } = await render(ParentRequestsComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, SharedModule, RouterTestingModule], - declarations: [ParentRequestComponent], - providers: [ - { - provide: WindowRef, - useClass: WindowRef, - }, - { - provide: ParentRequestsService, - useClass: parentRequestsService, - }, - { provide: FeatureFlagsService, - useClass: MockFeatureFlagsService - }, - ], - }); - - const { componentInstance } = fixture; - - expect(componentInstance.parentRequests).toEqual(parentRequests); - }); - }); - - it('should remove parent requests', async () => { - const parentRequests = [ - { - establishmentId: 1111, - workplaceId: 'I1234567', - userName: 'Magnificent Maisie', - orgName: 'Marvellous Mansions', - requested: '2019-08-27 16:04:35.914', - }, - { - establishmentId: 3333, - workplaceId: 'B9999999', - userName: 'Everso Stupid', - orgName: 'Everly Towers', - requested: '2020-05-20 16:04:35.914', - }, - ]; - - const { fixture } = await render(ParentRequestsComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, SharedModule, RouterTestingModule], - declarations: [ParentRequestComponent], - providers: [ - { - provide: WindowRef, - useClass: WindowRef, - }, - { provide: FeatureFlagsService, - useClass: MockFeatureFlagsService - }, - - ], - componentProperties: { - parentRequests, - }, - }); - - const { componentInstance } = fixture; - - componentInstance.removeParentRequest(0); - - expect(componentInstance.parentRequests).toContain(parentRequests[0]); - expect(componentInstance.parentRequests).not.toContain(parentRequests[1]); - expect(componentInstance.parentRequests.length).toBe(1); - }); -}); diff --git a/src/app/features/search/parent-requests/parent-requests.component.ts b/src/app/features/search/parent-requests/parent-requests.component.ts deleted file mode 100644 index 53f7311f8e..0000000000 --- a/src/app/features/search/parent-requests/parent-requests.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ParentRequestsService } from '@core/services/parent-requests.service'; - -@Component({ - selector: 'app-parent-requests', - templateUrl: './parent-requests.component.html' -}) -export class ParentRequestsComponent implements OnInit { - public parentRequests = []; - - constructor( - public parentRequestsService: ParentRequestsService, - ) {} - - ngOnInit() { - this.getParentRequests(); - } - - public getParentRequests() { - this.parentRequestsService.getParentRequests().subscribe( - data => { - this.parentRequests = data; - }, - error => this.onError(error) - ); - } - - public removeParentRequest(index: number) { - this.parentRequests.splice(index, 1); - } - - private onError(error) {} -} diff --git a/src/app/features/search/registration/registration.component.html b/src/app/features/search/registration/registration.component.html deleted file mode 100644 index bc5ce1ae1a..0000000000 --- a/src/app/features/search/registration/registration.component.html +++ /dev/null @@ -1,142 +0,0 @@ -

Registration from {{ registration.establishment.name }}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Created - {{ registration.created }} - Username - {{ registration.username }} - Parent Establishment ID - {{ registration.establishment.parentEstablishmentId }} -
Name - {{ registration.name }} -
Email - {{ registration.email }} - Phone number - {{ registration.phone }} -
Security question - {{ registration.securityQuestion }} - Security question answer - {{ registration.securityQuestionAnswer }} -
Regulated - {{ registration.establishment.isRegulated }} - Provider ID - {{ registration.establishment.provId }} -
Workplace ID - - - - Error: - - Enter a workplace ID. - - - Workplace ID must be between 1 and 8 characters. - - - Workplace ID must be between 1 and 8 characters. - - - Workplace ID must start with an uppercase letter. - - - {{ nmdsid.errors?.serverError }} - - - Location ID - {{ registration.establishment.locationId }} -
Address - {{ registration.establishment.name }}
- {{ registration.establishment.address }}
- {{ registration.establishment.address2 }}
- {{ registration.establishment.address3 }}
- {{ registration.establishment.town }}
- {{ registration.establishment.postcode }} -
County - {{ registration.establishment.county }} - Main Service - {{ registration.establishment.mainService }} -
-
- - -
-
- - -
-
-
diff --git a/src/app/features/search/registration/registration.component.spec.ts b/src/app/features/search/registration/registration.component.spec.ts deleted file mode 100644 index fac87bc966..0000000000 --- a/src/app/features/search/registration/registration.component.spec.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { HttpErrorResponse } from '@angular/common/http'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { RegistrationsService } from '@core/services/registrations.service'; -import { FirstErrorPipe } from '@shared/pipes/first-error.pipe'; -import { fireEvent, render } from '@testing-library/angular'; -import userEvent from '@testing-library/user-event'; -import { throwError } from 'rxjs'; -import { spy } from 'sinon'; - -import { RegistrationComponent } from './registration.component'; - -const testUsername = 'mr-twiggy'; -const testEmail = 'mr.twiggy@cats.com'; -const testNmdsId = 'W1234567'; -const testWorkplaceId = 12345; -const newUserAccount = 'New-User-Account'; -const registrationTypeIrrelevant = 'Registration-Type-Irrelevant'; -const workplaceAddedByParent = 'Workplace-Added-By-Parent'; - -describe('RegistrationComponent', () => { - async function getRegistrationComponent(registrationType) { - const registration = { - email: null, - username: null, - establishment: { - id: testWorkplaceId, - nmdsId: testNmdsId, - }, - }; - - if (registrationType === newUserAccount) { - registration.username = testUsername; - registration.email = testEmail; - } - - return await render(RegistrationComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule], - providers: [RegistrationsService], - declarations: [FirstErrorPipe], - componentProperties: { - index: 0, - handleRegistration: { - emit: spy(), - } as any, - registration, - }, - }); - } - - it('should create', async () => { - const component = await getRegistrationComponent(registrationTypeIrrelevant); - - expect(component).toBeTruthy(); - }); - - it('should be able to approve the registration of a workplace created by a parent', async () => { - const { getByText, fixture } = await getRegistrationComponent(workplaceAddedByParent); - - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - fireEvent.click(getByText('Approve')); - - expect(registrationApproval).toHaveBeenCalledWith({ - establishmentId: testWorkplaceId, - nmdsId: testNmdsId, - approve: true, - }); - }); - - it('should be able to reject the registration of a workplace created by a parent', async () => { - const { getByText, fixture } = await getRegistrationComponent(workplaceAddedByParent); - - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - fireEvent.click(getByText('Reject')); - - expect(registrationApproval).toHaveBeenCalledWith({ - establishmentId: testWorkplaceId, - nmdsId: testNmdsId, - approve: false, - }); - }); - - it('should be able to approve the registration of a workplace created via a new user account', async () => { - const { getByText, fixture } = await getRegistrationComponent(newUserAccount); - - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - fireEvent.click(getByText('Approve')); - - expect(registrationApproval).toHaveBeenCalledWith({ - username: testUsername, - nmdsId: testNmdsId, - approve: true, - }); - }); - - it('should be able to reject the registration of a workplace created via a new user account', async () => { - const { getByText, fixture } = await getRegistrationComponent(newUserAccount); - - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - fireEvent.click(getByText('Reject')); - - expect(registrationApproval).toHaveBeenCalledWith({ - username: testUsername, - nmdsId: testNmdsId, - approve: false, - }); - }); - - it('should change the nmdsID for the registration of a Workplace', async () => { - const { getByText, fixture, container } = await getRegistrationComponent(registrationTypeIrrelevant); - - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - const nmdsIdInput = container.querySelector('input[name=nmdsid]') as HTMLElement; - userEvent.clear(nmdsIdInput); - - const newNmdsId = 'G1234567'; - userEvent.type(nmdsIdInput, newNmdsId); - fireEvent.click(getByText('Approve')); - - expect(registrationApproval).toHaveBeenCalledWith({ - establishmentId: testWorkplaceId, - nmdsId: newNmdsId, - approve: true, - }); - }); - - describe('FormValidation', () => { - it('validates a Workplace ID is required', async () => { - const { getByText, fixture, container } = await getRegistrationComponent(registrationTypeIrrelevant); - const { componentInstance: component } = fixture; - - spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - spyOn(component.handleRegistration, 'emit').and.callThrough(); - - const nmdsIdInput = container.querySelector('input[name=nmdsid]') as HTMLElement; - userEvent.clear(nmdsIdInput); - - userEvent.type(nmdsIdInput, ''); - - fireEvent.click(getByText('Approve')); - - expect(getByText('Enter a workplace ID.')); - expect(component.registrationsService.registrationApproval).toHaveBeenCalledTimes(0); - }); - - it('validates the min length of a Workplace ID', async () => { - const { getByText, fixture, container } = await getRegistrationComponent(registrationTypeIrrelevant); - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - const nmdsIdInput = container.querySelector('input[name=nmdsid]') as HTMLElement; - userEvent.clear(nmdsIdInput); - - userEvent.type(nmdsIdInput, 'W123456'); - - fireEvent.click(getByText('Approve')); - - expect(getByText('Workplace ID must be between 1 and 8 characters.')); - expect(registrationApproval).toHaveBeenCalledTimes(0); - }); - - it('validates the max length of a Workplace ID', async () => { - const { getByText, fixture, container } = await getRegistrationComponent(registrationTypeIrrelevant); - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - const nmdsIdInput = container.querySelector('input[name=nmdsid]') as HTMLElement; - userEvent.clear(nmdsIdInput); - - userEvent.type(nmdsIdInput, 'W12345678910'); - - fireEvent.click(getByText('Approve')); - - expect(getByText('Workplace ID must be between 1 and 8 characters.')); - expect(registrationApproval).toHaveBeenCalledTimes(0); - }); - - it('validates that a Workplace ID must start with a letter', async () => { - const { getByText, fixture, container } = await getRegistrationComponent(registrationTypeIrrelevant); - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - const nmdsIdInput = container.querySelector('input[name=nmdsid]') as HTMLElement; - userEvent.clear(nmdsIdInput); - - userEvent.type(nmdsIdInput, '12345678'); - - fireEvent.click(getByText('Approve')); - - expect(getByText('Workplace ID must start with an uppercase letter.')); - expect(registrationApproval).toHaveBeenCalledTimes(0); - }); - - it('validates that a Workplace ID must start with an uppercase letter', async () => { - const { getByText, fixture, container } = await getRegistrationComponent(registrationTypeIrrelevant); - const { componentInstance: component } = fixture; - - const registrationApproval = spyOn(component.registrationsService, 'registrationApproval').and.callThrough(); - - const nmdsIdInput = container.querySelector('input[name=nmdsid]') as HTMLElement; - userEvent.clear(nmdsIdInput); - - userEvent.type(nmdsIdInput, 'w1234567'); - - fireEvent.click(getByText('Approve')); - - expect(getByText('Workplace ID must start with an uppercase letter.')); - expect(registrationApproval).toHaveBeenCalledTimes(0); - }); - - it('validates that a Workplace ID cannot be the same as an existing Workplace ID', async () => { - const { getByText, fixture, container } = await getRegistrationComponent(registrationTypeIrrelevant); - const { componentInstance: component } = fixture; - - const mockErrorResponse = new HttpErrorResponse({ - status: 400, - statusText: 'Bad Request', - error: { - nmdsId: 'This workplace ID (W1234567) belongs to another workplace. Enter a different workplace ID.', - }, - }); - - spyOn(component.registrationsService, 'registrationApproval').and.returnValue(throwError(mockErrorResponse)); - - const nmdsIdInput = container.querySelector('input[name=nmdsid]') as HTMLElement; - userEvent.clear(nmdsIdInput); - - userEvent.type(nmdsIdInput, testNmdsId); - fireEvent.click(getByText('Approve')); - - expect( - getByText(`This workplace ID (${testNmdsId}) belongs to another workplace. Enter a different workplace ID.`), - ); - }); - }); -}); diff --git a/src/app/features/search/registration/registration.component.ts b/src/app/features/search/registration/registration.component.ts deleted file mode 100644 index 686cfc48d2..0000000000 --- a/src/app/features/search/registration/registration.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { HttpErrorResponse } from '@angular/common/http'; -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { RegistrationsService } from '@core/services/registrations.service'; - -@Component({ - selector: 'app-registration', - templateUrl: './registration.component.html', -}) -export class RegistrationComponent implements OnInit { - @Output() handleRegistration: EventEmitter = new EventEmitter(); - @Input() index: number; - @Input() registration: any; - public registrationForm: FormGroup; - public username: string; - public approve: boolean; - - constructor(public registrationsService: RegistrationsService) {} - ngOnInit() { - this.registrationForm = new FormGroup({ - nmdsId: new FormControl(this.registration.establishment.nmdsId, [ - Validators.required, - Validators.minLength(8), - Validators.maxLength(8), - ]), - }); - } - - get nmdsid() { - return this.registrationForm.get('nmdsId'); - } - - public approveRegistration(username: string, approve: boolean) { - this.username = username; - this.approve = approve; - } - public onSubmit() { - if (this.registrationForm.valid) { - let data; - data = { - username: this.username, - nmdsId: this.registrationForm.get('nmdsId').value, - approve: this.approve, - }; - - if (!this.registration.email) { - data = { - establishmentId: this.username, - nmdsId: this.registrationForm.get('nmdsId').value, - approve: this.approve, - }; - } - - this.registrationsService.registrationApproval(data).subscribe( - () => { - this.handleRegistration.emit(this.index); - }, - (err) => { - if (err instanceof HttpErrorResponse) { - this.populateErrorFromServer(err); - } - }, - ); - } - } - - private populateErrorFromServer(err) { - const validationErrors = err.error; - - Object.keys(validationErrors).forEach((prop) => { - const formControl = this.registrationForm.get(prop); - if (formControl) { - formControl.setErrors({ - serverError: validationErrors[prop], - }); - } - }); - } -} diff --git a/src/app/features/search/registrations/registrations.component.html b/src/app/features/search/registrations/registrations.component.html deleted file mode 100644 index 8015f5bd7f..0000000000 --- a/src/app/features/search/registrations/registrations.component.html +++ /dev/null @@ -1,8 +0,0 @@ -

Registrations

-
- -
diff --git a/src/app/features/search/registrations/registrations.component.spec.ts b/src/app/features/search/registrations/registrations.component.spec.ts deleted file mode 100644 index a662ad9a33..0000000000 --- a/src/app/features/search/registrations/registrations.component.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { inject, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; -import { RegistrationsService } from '@core/services/registrations.service'; -import { FirstErrorPipe } from '@shared/pipes/first-error.pipe'; -import { render, RenderResult } from '@testing-library/angular'; -import { of } from 'rxjs'; - -import { RegistrationComponent } from '../registration/registration.component'; -import { RegistrationsComponent } from './registrations.component'; - -describe('RegistrationsComponent', () => { - let component: RenderResult; - - it('should create', async () => { - component = await render(RegistrationsComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, RouterTestingModule], - declarations: [FirstErrorPipe, RegistrationComponent], - providers: [RegistrationsService], - }); - - expect(component).toBeTruthy(); - }); - - it('can get registrations', () => { - inject([HttpClientTestingModule], async () => { - const registrations = [ - { - establishment: { - id: 1, - nmdsId: 'J1234567', - }, - }, - { - establishment: { - id: 2, - nmdsId: 'J5678910', - }, - }, - ]; - - const registrationService = TestBed.inject(RegistrationsService); - spyOn(registrationService, 'getRegistrations').and.returnValue(of(registrations)); - - const { fixture } = await render(RegistrationsComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, RouterTestingModule], - declarations: [FirstErrorPipe, RegistrationComponent], - providers: [ - { - provide: RegistrationsService, - useClass: registrationService, - }, - ], - }); - - const { componentInstance } = fixture; - - expect(componentInstance.registrations).toEqual(registrations); - }); - }); - - it('should remove registrations', async () => { - const registrations = [ - { - establishment: { - id: 1, - nmdsId: 'J1234567', - }, - }, - { - establishment: { - id: 2, - nmdsId: 'J5678910', - }, - }, - ]; - - const { fixture } = await render(RegistrationsComponent, { - imports: [ReactiveFormsModule, HttpClientTestingModule, RouterTestingModule], - declarations: [FirstErrorPipe, RegistrationComponent], - providers: [RegistrationsService], - componentProperties: { - registrations, - }, - }); - - const { componentInstance } = fixture; - - componentInstance.handleRegistration(0); - - expect(componentInstance.registrations).toContain(registrations[0]); - expect(componentInstance.registrations.length).toBe(1); - }); -}); diff --git a/src/app/features/search/registrations/registrations.component.ts b/src/app/features/search/registrations/registrations.component.ts deleted file mode 100644 index 458df3e355..0000000000 --- a/src/app/features/search/registrations/registrations.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { RegistrationsService } from '@core/services/registrations.service'; - -@Component({ - selector: 'app-registrations', - templateUrl: './registrations.component.html', -}) -export class RegistrationsComponent implements OnInit { - public registrations = []; - - constructor(public registrationsService: RegistrationsService) {} - - ngOnInit() { - this.getRegistrations(); - } - - public getRegistrations() { - this.registrationsService.getAllRegistrations().subscribe( - (data) => { - this.registrations = data; - }, - (error) => this.onError(error), - ); - } - - public handleRegistration(index: number) { - this.registrations.splice(index, 1); - } - - private onError(error) {} -} diff --git a/src/app/features/search/search-routing.module.ts b/src/app/features/search/search-routing.module.ts deleted file mode 100644 index 7eb482a4bf..0000000000 --- a/src/app/features/search/search-routing.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { EmailCampaignHistoryResolver } from '@core/resolvers/admin/email-campaign-history.resolver'; -import { EmailTemplateResolver } from '@core/resolvers/admin/email-template.resolver'; -import { InactiveWorkplacesResolver } from '@core/resolvers/admin/inactive-workplaces.resolver'; - -import { SearchComponent } from './search.component'; - -const routes: Routes = [ - { - path: '', - component: SearchComponent, - data: { title: 'Search' }, - resolve: { - emailCampaignHistory: EmailCampaignHistoryResolver, - inactiveWorkplaces: InactiveWorkplacesResolver, - emailTemplates: EmailTemplateResolver, - }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class SearchRoutingModule {} diff --git a/src/app/features/search/search.component.html b/src/app/features/search/search.component.html deleted file mode 100644 index 6599e0ab59..0000000000 --- a/src/app/features/search/search.component.html +++ /dev/null @@ -1,558 +0,0 @@ -
-
-
- - -
-
-
- - {{ form.subTitle }} -

- {{ form.title }} -

-
- - - -
-
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- -
-
- - -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
- - - Information - Your search returned no results. Please refine your search criteria. - -
-
- -

- Your search returned {{ results.length | number }} results. -

- -
- - - - - - - - - - - - - - - - - - - - - - - - -
NameUsernameWorkplace IDPostcode 
{{ item.name }}{{ item.username }} - {{ - item.establishment.nmdsId - }} - Workplace is pending - {{ item.establishment.postcode }} - {{ - workerDetailsLabel[item.uid] ? workerDetailsLabel[item.uid] : 'Open' - }} -
-
- - - - - - - - - - - - - - - -
Location IDWorkplace 
- {{ item.establishment.locationId }} - - {{ item.establishment.name }} -  
-
-
- - - - - - - - - - - - - - - -
Security questionAnswerLocked
- {{ item.securityQuestion }} - - {{ item.securityQuestionAnswer }} - - Yes, unlock - No -
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Workplace namePostcodeWorkplace IDLocation IDProvider ID 
- {{ item.name }} - {{ item.postcode }}{{ item.nmdsId || '-' }}{{ item.locationId || '-' }}- - {{ - workerDetailsLabel[item.uid] ? workerDetailsLabel[item.uid] : 'Open' - }} -
-
- - - - - - - - - - - - - - - - - -
AddressParent IDRegulatedData Owner
- {{ displayAddress(item) }} - - - {{ - item.parent.nmdsId - }} - - - - - - {{ item.isRegulated ? 'Yes' : 'No' }} - - {{ item.dataOwner }} -
-
-
- - - - - - - - - - - - - - - - - -
UsersSecurity questionAnswerLocked
- {{ user.name }} - - {{ user.securityQuestion }} - - {{ user.securityAnswer }} - - Yes, unlock - No -
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - -
Workplace nameWorkplace IDEmployer Type 
- {{ item.name }} - {{ item.nmdsId || '-' }} - {{ item.employerType?.other ? item.employerType.other : item.employerType?.value || '-' }} - - {{ - workerDetailsLabel[item.uid] ? workerDetailsLabel[item.uid] : 'Open' - }} -
-
- - - - - - - - - - - - - - - - - -
AddressParent IDRegulatedData Owner
- {{ displayAddressForGroups(item) }} - - - {{ - item.parent.nmdsId - }} - - - - - - {{ item.isRegulated ? 'Yes' : 'No' }} - - {{ item.dataOwner }} -
-
-
- - - - - - - - - - - - - - - - - -
UsersSecurity questionAnswerLocked
- {{ user.name }} - - {{ user.securityQuestion }} - - {{ user.securityAnswer }} - - Yes, unlock - No -
-
-
-
- - - - - -
-
diff --git a/src/app/features/search/search.component.spec.ts b/src/app/features/search/search.component.spec.ts deleted file mode 100644 index 00f7f64de6..0000000000 --- a/src/app/features/search/search.component.spec.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { RouterTestingModule } from '@angular/router/testing'; -import { RegistrationsService } from '@core/services/registrations.service'; -import { SwitchWorkplaceService } from '@core/services/switch-workplace.service'; -import { WindowRef } from '@core/services/window.ref'; -import { MockFeatureFlagsService } from '@core/test-utils/MockFeatureFlagService'; -import { MockSwitchWorkplaceService } from '@core/test-utils/MockSwitchWorkplaceService'; -import { FeatureFlagsService } from '@shared/services/feature-flags.service'; -import { SharedModule } from '@shared/shared.module'; -import { fireEvent, render, within } from '@testing-library/angular'; -import { of } from 'rxjs'; - -import { SearchComponent } from './search.component'; - -const getSearchComponent = async () => { - return render(SearchComponent, { - detectChanges: false, - imports: [ - FormsModule, - ReactiveFormsModule, - HttpClientTestingModule, - SharedModule, - RouterTestingModule.withRoutes([ - { path: 'search-users', component: SearchComponent }, - { path: 'search-groups', component: SearchComponent }, - ]), - ], - providers: [ - { - provide: WindowRef, - useClass: WindowRef, - }, - { provide: FeatureFlagsService, useClass: MockFeatureFlagsService }, - { - provide: SwitchWorkplaceService, - useClass: MockSwitchWorkplaceService, - }, - [RegistrationsService], - ], - }); -}; - -const setup = async (fixture, navigate, getByText, isLocked = false) => { - const httpTestingController = TestBed.inject(HttpTestingController); - - await navigate('search-users'); - - fixture.detectChanges(); - - fireEvent.click(getByText('Search users')); - - const req = httpTestingController.expectOne('/api/admin/search/users'); - req.flush([ - { - uid: 'ad3bbca7-2913-4ba7-bb2d-01014be5c48f', - name: 'John Doe', - username: 'Username', - securityQuestion: 'Question', - securityQuestionAnswer: 'Answer', - isLocked, - establishment: { - uid: 'ad3bbca7-2913-4ba7-bb2d-01014be5c48f', - name: 'My workplace', - nmdsId: 'G1001376', - }, - }, - ]); - - fixture.detectChanges(); -}; - -describe('SearchComponent', () => { - afterEach(() => { - const httpTestingController = TestBed.inject(HttpTestingController); - httpTestingController.verify(); - }); - - describe('Users', () => { - it('should show the Users tab', async () => { - const { fixture, navigate, getByText } = await getSearchComponent(); - - await navigate('search-users'); - - fixture.detectChanges(); - - expect(getByText('Search for a user')); - }); - - it('should render the search results when searching for a user', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - await setup(fixture, navigate, getByText); - - await within(getByTestId('user-search-results')).getByText('John Doe'); - }); - it("should show a flag when user's workplace is pending", async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - await setup(fixture, navigate, getByText); - - fixture.componentInstance.results[0].establishment.ustatus = 'PENDING'; - fixture.detectChanges(); - - const result = getByTestId('user-search-results').querySelector('img'); - expect(result.src).toContain('flag-orange'); - }); - - it('should expand the User details when clicking Open', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - await setup(fixture, navigate, getByText); - - const searchResults = within(getByTestId('user-search-results')); - fireEvent.click(searchResults.getByText('Open')); - - expect(searchResults.getByText('My workplace')); - }); - - it('should navigate to workplace when clicking workplace id link', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - await setup(fixture, navigate, getByText); - - const switchWorkplaceService = TestBed.inject(SwitchWorkplaceService); - - const spy = spyOn(switchWorkplaceService, 'navigateToWorkplace'); - - const searchResults = within(getByTestId('user-search-results')); - fireEvent.click(searchResults.getByText('G1001376')); - - await expect(spy).toHaveBeenCalled(); - }); - - it('should navigate to workplace when clicking workplace name link', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - await setup(fixture, navigate, getByText); - - const switchWorkplaceService = TestBed.inject(SwitchWorkplaceService); - - const spy = spyOn(switchWorkplaceService, 'navigateToWorkplace'); - - const searchResults = within(getByTestId('user-search-results')); - fireEvent.click(searchResults.getByText('Open')); - fireEvent.click(searchResults.getByText('My workplace')); - - await expect(spy).toHaveBeenCalled(); - }); - - it('should open unlock user dialog when clicking unlock button', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - await setup(fixture, navigate, getByText, true); - - const registrationsService = TestBed.inject(RegistrationsService); - - const spy = spyOn(registrationsService, 'unlockAccount').and.returnValue(of({})); - - const searchResults = within(getByTestId('user-search-results')); - fireEvent.click(searchResults.getByText('Open')); - fireEvent.click(searchResults.getByText('Yes, unlock')); - - const adminUnlockModal = within(document.body).getByRole('dialog'); - const confirm = within(adminUnlockModal).getByText('Unlock account'); - confirm.click(); - - await expect(spy).toHaveBeenCalled(); - }); - }); - - describe('Groups', () => { - it('should show the Groups tab', async () => { - const { fixture, navigate, getByText } = await getSearchComponent(); - - await navigate('search-groups'); - - fixture.detectChanges(); - - expect(getByText('Search for a group')); - }); - - it('should render the search results when searching for a group', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - const httpTestingController = TestBed.inject(HttpTestingController); - - await navigate('search-groups'); - - fixture.detectChanges(); - - fireEvent.click(getByText('Search groups')); - - const req = httpTestingController.expectOne('/api/admin/search/groups'); - req.flush([ - { - uid: 'ad3bbca7-2913-4ba7-bb2d-01014be5c48f', - name: 'Workplace Name', - }, - ]); - - fixture.detectChanges(); - - await within(getByTestId('group-search-results')).getByText('Workplace Name'); - }); - - it('should expand the Workplace details when clicking Open', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - const httpTestingController = TestBed.inject(HttpTestingController); - - await navigate('search-groups'); - - fixture.detectChanges(); - - fireEvent.click(getByText('Search groups')); - - const req = httpTestingController.expectOne('/api/admin/search/groups'); - req.flush([ - { - uid: 'ad3bbca7-2913-4ba7-bb2d-01014be5c48f', - name: 'Workplace Name', - address1: '44', - address2: 'Grace St', - town: 'Leeds', - county: 'West Yorkshire', - postcode: 'WF14 9TS', - }, - ]); - - fixture.detectChanges(); - - const searchResults = within(getByTestId('group-search-results')); - fireEvent.click(searchResults.getByText('Open')); - - expect(searchResults.getByText('44 Grace St, Leeds, West Yorkshire, WF14 9TS')); - }); - - it('should collapse the Workplace details when clicking Close', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - const httpTestingController = TestBed.inject(HttpTestingController); - - await navigate('search-groups'); - - fixture.detectChanges(); - - fireEvent.click(getByText('Search groups')); - - const req = httpTestingController.expectOne('/api/admin/search/groups'); - req.flush([ - { - uid: 'ad3bbca7-2913-4ba7-bb2d-01014be5c48f', - name: 'Workplace Name', - address1: '44', - address2: 'Grace St', - town: 'Leeds', - county: 'West Yorkshire', - postcode: 'WF14 9TS', - }, - ]); - - fixture.detectChanges(); - - const searchResults = within(getByTestId('group-search-results')); - fireEvent.click(searchResults.getByText('Open')); - fireEvent.click(searchResults.getByText('Close')); - - expect(searchResults.queryByTestId('groups-workplace-details')).toBeNull(); - }); - - it('should show a warning when there are no search results', async () => { - const { fixture, navigate, getByText, getByTestId } = await getSearchComponent(); - - const httpTestingController = TestBed.inject(HttpTestingController); - - await navigate('search-groups'); - - fixture.detectChanges(); - - fireEvent.click(getByText('Search groups')); - - const req = httpTestingController.expectOne('/api/admin/search/groups'); - req.flush([]); - - fixture.detectChanges(); - - expect(getByTestId('no-search-results')); - }); - }); - - describe('Emails', () => { - it('should click the Emails tab', async () => { - const { getByText } = await getSearchComponent(); - - fireEvent.click(getByText('Emails')); - }); - }); -}); diff --git a/src/app/features/search/search.component.ts b/src/app/features/search/search.component.ts deleted file mode 100644 index cd2632b66f..0000000000 --- a/src/app/features/search/search.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { Overlay } from '@angular/cdk/overlay'; -import { HttpClient } from '@angular/common/http'; -import { AfterViewInit, Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; -import { AuthService } from '@core/services/auth.service'; -import { BackService } from '@core/services/back.service'; -import { DialogService } from '@core/services/dialog.service'; -import { SwitchWorkplaceService } from '@core/services/switch-workplace.service'; -import { - AdminUnlockConfirmationDialogComponent, -} from '@shared/components/admin-unlock-confirmation/admin-unlock-confirmation'; - -@Component({ - selector: 'app-search', - templateUrl: './search.component.html', - providers: [DialogService, AdminUnlockConfirmationDialogComponent, Overlay], -}) -export class SearchComponent implements OnInit, AfterViewInit { - public results = []; - public selectedWorkplaceUid: string; - public form = { - type: '', - title: '', - subTitle: '', - buttonText: '', - valid: true, - submitted: false, - username: '', - usernameLabel: '', - name: '', - nameLabel: '', - locationId: '', - employerType: 'All', - parent: false, - errors: [], - }; - - public workerDetails = []; - public workerDetailsLabel = []; - - constructor( - public router: Router, - public http: HttpClient, - public switchWorkplaceService: SwitchWorkplaceService, - private dialogService: DialogService, - protected backService: BackService, - protected authService: AuthService, - ) {} - - ngAfterViewInit() { - this.authService.isOnAdminScreen = true; - } - - ngOnInit() { - this.setBackLink(); - - if (this.router.url === '/search-users') { - this.form.type = 'users'; - this.form.usernameLabel = 'Username'; - this.form.nameLabel = 'Name'; - this.form.title = 'Search for a user'; - this.form.buttonText = 'Search users'; - } else if (this.router.url === '/search-establishments') { - this.form.type = 'establishments'; - this.form.usernameLabel = 'Postcode'; - this.form.nameLabel = 'Workplace ID'; - this.form.title = 'Search for a workplace'; - this.form.buttonText = 'Search workplaces'; - } else if (this.router.url === '/search-groups') { - this.form.type = 'groups'; - this.form.title = 'Search for a group'; - this.form.buttonText = 'Search groups'; - } else if (this.router.url === '/registrations') { - this.form.type = 'registrations'; - } else if (this.router.url === '/cqc-status-changes') { - this.form.type = 'cqc-status-changes'; - } else if (this.router.url === '/parent-requests') { - this.form.type = 'parent-requests'; - } else if (this.router.url === '/emails') { - this.form.type = 'emails'; - } - } - - public unlockUser(username: string, index: number, e) { - e.preventDefault(); - const data = { - username, - index, - removeUnlock: () => { - this.results[index].isLocked = false; - }, - }; - this.dialogService.open(AdminUnlockConfirmationDialogComponent, data); - } - - public unlockWorkplaceUser(username: string, workplaceIndex: number, userIndex: number, e) { - e.preventDefault(); - const data = { - username, - removeUnlock: () => { - this.results[workplaceIndex].users[userIndex].isLocked = false; - }, - }; - - this.dialogService.open(AdminUnlockConfirmationDialogComponent, data); - } - - public searchType(data, type) { - return this.http.post('/api/admin/search/' + type, data, { observe: 'response' }); - } - - public setEstablishmentId(id, username, nmdsId, e): void { - e.preventDefault(); - this.switchWorkplaceService.navigateToWorkplace(id, username, nmdsId); - } - - public onSubmit(): void { - this.form.errors = []; - this.form.submitted = true; - // this.errorSummaryService.syncFormErrorsEvent.next(true); - - if ( - this.form.username.length === 0 && - this.form.name.length === 0 && - this.form.locationId.length === 0 && - this.form.employerType.length === 0 - ) { - this.form.errors.push({ - error: 'Please enter at least 1 search value', - id: 'username', - }); - this.form.submitted = false; - } else { - let data = {}; - - if (this.form.type === 'users') { - data = { - username: this.form.username, - name: this.form.name, - }; - } else if (this.form.type === 'groups') { - data = { - employerType: this.form.employerType, - parent: this.form.parent, - }; - } else { - data = { - postcode: this.form.username, - nmdsId: this.form.name, - locationId: this.form.locationId, - }; - } - - this.searchType(data, this.form.type).subscribe( - (response) => this.onSuccess(response), - (error) => this.onError(error), - ); - } - } - - private onSuccess(data) { - this.results = data.body; - } - - private onError(error) {} - - protected setBackLink(): void { - this.backService.setBackLink({ url: ['/dashboard'] }); - } - - protected displayAddress(workplace) { - const secondaryAddress = - ' ' + [workplace.address2, workplace.town, workplace.county].filter(Boolean).join(', ') || ''; - - return workplace.address1 + secondaryAddress; - } - - protected displayAddressForGroups(workplace) { - const secondaryAddress = - ' ' + [workplace.address2, workplace.town, workplace.county, workplace.postcode].filter(Boolean).join(', ') || ''; - - return workplace.address1 + secondaryAddress; - } - - public toggleDetails(uid: string, event) { - event.preventDefault(); - this.workerDetails[uid] = !this.workerDetails[uid]; - this.workerDetailsLabel[uid] = this.workerDetailsLabel[uid] === 'Close' ? 'Open' : 'Close'; - } -} diff --git a/src/app/features/search/search.module.ts b/src/app/features/search/search.module.ts deleted file mode 100644 index 25d00fbdca..0000000000 --- a/src/app/features/search/search.module.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { OverlayModule } from '@angular/cdk/overlay'; -import { CommonModule, DecimalPipe } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { EmailCampaignHistoryResolver } from '@core/resolvers/admin/email-campaign-history.resolver'; -import { EmailTemplateResolver } from '@core/resolvers/admin/email-template.resolver'; -import { InactiveWorkplacesResolver } from '@core/resolvers/admin/inactive-workplaces.resolver'; -import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; -import { DialogService } from '@core/services/dialog.service'; -import { - AdminUnlockConfirmationDialogComponent, -} from '@shared/components/admin-unlock-confirmation/admin-unlock-confirmation'; -import { SharedModule } from '@shared/shared.module'; - -import { CqcStatusChangeComponent } from './cqc-status-change/cqc-status-change.component'; -import { CqcStatusChangesComponent } from './cqc-status-changes/cqc-status-changes.component'; -import { - SendEmailsConfirmationDialogComponent, -} from './emails/dialogs/send-emails-confirmation-dialog/send-emails-confirmation-dialog.component'; -import { EmailsComponent } from './emails/emails.component'; -import { ParentRequestComponent } from './parent-request/parent-request.component'; -import { ParentRequestsComponent } from './parent-requests/parent-requests.component'; -import { RegistrationComponent } from './registration/registration.component'; -import { RegistrationsComponent } from './registrations/registrations.component'; -import { SearchRoutingModule } from './search-routing.module'; -import { SearchComponent } from './search.component'; - -@NgModule({ - imports: [CommonModule, OverlayModule, ReactiveFormsModule, SharedModule, SearchRoutingModule, FormsModule], - providers: [ - DialogService, - EmailCampaignHistoryResolver, - InactiveWorkplacesResolver, - EmailCampaignService, - DecimalPipe, - EmailTemplateResolver, - ], - declarations: [ - SearchComponent, - AdminUnlockConfirmationDialogComponent, - RegistrationComponent, - RegistrationsComponent, - ParentRequestComponent, - ParentRequestsComponent, - CqcStatusChangeComponent, - CqcStatusChangesComponent, - EmailsComponent, - SendEmailsConfirmationDialogComponent, - ], -}) -export class SearchModule {} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index e745cc7529..f896e492e5 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -9,9 +9,9 @@ import { ArticleListResolver } from '@core/resolvers/article-list.resolver'; import { PageResolver } from '@core/resolvers/page.resolver'; import { DialogService } from '@core/services/dialog.service'; import { ArticleListComponent } from '@features/articles/article-list/article-list.component'; -import { CqcConfirmationDialogComponent } from '@features/search/cqc-status-change/cqc-confirmation-dialog.component'; -import { ParentConfirmationDialogComponent } from '@features/search/parent-request/parent-confirmation-dialog.component'; -import { DeleteWorkplaceDialogComponent } from '@features/workplace/delete-workplace-dialog/delete-workplace-dialog.component'; +import { + DeleteWorkplaceDialogComponent, +} from '@features/workplace/delete-workplace-dialog/delete-workplace-dialog.component'; import { AlertComponent } from '@shared/components/alert/alert.component'; import { CheckCQCDetailsComponent } from '@shared/components/check-cqc-details/check-cqc-details.component'; import { SummaryRecordValueComponent } from '@shared/components/summary-record-value/summary-record-value.component'; @@ -29,30 +29,42 @@ import { ChangeDataOwnerDialogComponent } from './components/change-data-owner-d import { CharacterCountComponent } from './components/character-count/character-count.component'; import { DatePickerComponent } from './components/date-picker/date-picker.component'; import { DetailsComponent } from './components/details/details.component'; -import { ValidationErrorMessageComponent } from './components/drag-and-drop/validation-error-message/validation-error-message.component'; +import { + ValidationErrorMessageComponent, +} from './components/drag-and-drop/validation-error-message/validation-error-message.component'; import { EligibilityIconComponent } from './components/eligibility-icon/eligibility-icon.component'; import { ErrorSummaryComponent } from './components/error-summary/error-summary.component'; import { InsetTextComponent } from './components/inset-text/inset-text.component'; -import { LinkToParentCancelDialogComponent } from './components/link-to-parent-cancel/link-to-parent-cancel-dialog.component'; -import { LinkToParentRemoveDialogComponent } from './components/link-to-parent-remove/link-to-parent-remove-dialog.component'; +import { + LinkToParentCancelDialogComponent, +} from './components/link-to-parent-cancel/link-to-parent-cancel-dialog.component'; +import { + LinkToParentRemoveDialogComponent, +} from './components/link-to-parent-remove/link-to-parent-remove-dialog.component'; import { LinkToParentDialogComponent } from './components/link-to-parent/link-to-parent-dialog.component'; import { MessagesComponent } from './components/messages/messages.component'; import { MoveWorkplaceDialogComponent } from './components/move-workplace/move-workplace-dialog.component'; -import { OwnershipChangeMessageDialogComponent } from './components/ownership-change-message/ownership-change-message-dialog.component'; +import { + OwnershipChangeMessageDialogComponent, +} from './components/ownership-change-message/ownership-change-message-dialog.component'; import { PageComponent } from './components/page/page.component'; import { PaginationComponent } from './components/pagination/pagination.component'; import { PanelComponent } from './components/panel/panel.component'; import { PhaseBannerComponent } from './components/phase-banner/phase-banner.component'; import { ProgressComponent } from './components/progress/progress.component'; import { RejectRequestDialogComponent } from './components/reject-request-dialog/reject-request-dialog.component'; -import { RemoveParentConfirmationComponent } from './components/remove-parent-confirmation/remove-parent-confirmation.component'; +import { + RemoveParentConfirmationComponent, +} from './components/remove-parent-confirmation/remove-parent-confirmation.component'; import { ReviewCheckboxComponent } from './components/review-checkbox/review-checkbox.component'; import { SearchInputComponent } from './components/search-input/search-input.component'; import { SetDataPermissionDialogComponent } from './components/set-data-permission/set-data-permission-dialog.component'; import { BasicRecordComponent } from './components/staff-record-summary/basic-record/basic-record.component'; import { EmploymentComponent } from './components/staff-record-summary/employment/employment.component'; import { PersonalDetailsComponent } from './components/staff-record-summary/personal-details/personal-details.component'; -import { QualificationsAndTrainingComponent } from './components/staff-record-summary/qualifications-and-training/qualifications-and-training.component'; +import { + QualificationsAndTrainingComponent, +} from './components/staff-record-summary/qualifications-and-training/qualifications-and-training.component'; import { StaffRecordSummaryComponent } from './components/staff-record-summary/staff-record-summary.component'; import { StaffRecordsTabComponent } from './components/staff-records-tab/staff-records-tab.component'; import { StaffSummaryComponent } from './components/staff-summary/staff-summary.component'; @@ -65,15 +77,23 @@ import { TabComponent } from './components/tabs/tab.component'; import { TabsComponent } from './components/tabs/tabs.component'; import { TotalStaffPanelComponent } from './components/total-staff-panel/total-staff-panel.component'; import { TotalStaffComponent } from './components/total-staff/total-staff.component'; -import { TrainingAndQualificationsCategoriesComponent } from './components/training-and-qualifications-categories/training-and-qualifications-categories.component'; -import { TrainingAndQualificationsSummaryComponent } from './components/training-and-qualifications-summary/training-and-qualifications-summary.component'; -import { TrainingAndQualificationsTabComponent } from './components/training-and-qualifications-tab/training-and-qualifications-tab.component'; +import { + TrainingAndQualificationsCategoriesComponent, +} from './components/training-and-qualifications-categories/training-and-qualifications-categories.component'; +import { + TrainingAndQualificationsSummaryComponent, +} from './components/training-and-qualifications-summary/training-and-qualifications-summary.component'; +import { + TrainingAndQualificationsTabComponent, +} from './components/training-and-qualifications-tab/training-and-qualifications-tab.component'; import { TrainingInfoPanelComponent } from './components/training-info-panel/training-info-panel.component'; import { TrainingLinkPanelComponent } from './components/training-link-panel/training-link-panel.component'; import { UserAccountsSummaryComponent } from './components/user-accounts-summary/user-accounts-summary.component'; import { WdfConfirmationPanelComponent } from './components/wdf-confirmation-panel/wdf-confirmation-panel.component'; import { WdfFieldConfirmationComponent } from './components/wdf-field-confirmation/wdf-field-confirmation.component'; -import { WdfStaffMismatchMessageComponent } from './components/wdf-staff-mismatch-message/wdf-staff-mismatch-message.component'; +import { + WdfStaffMismatchMessageComponent, +} from './components/wdf-staff-mismatch-message/wdf-staff-mismatch-message.component'; import { WdfTabComponent } from './components/wdf-tab/wdf-tab.component'; import { WhyCollectingFluJabComponent } from './components/why-collecting-flu-jab/why-collecting-flu-jab.component'; import { WorkplaceSummaryComponent } from './components/workplace-summary/workplace-summary.component'; @@ -163,8 +183,8 @@ import { WorkplacePermissionsBearerPipe } from './pipes/workplace-permissions-be BecomeAParentDialogComponent, OwnershipChangeMessageDialogComponent, DeleteWorkplaceDialogComponent, - ParentConfirmationDialogComponent, - CqcConfirmationDialogComponent, + // ParentConfirmationDialogComponent, + // CqcConfirmationDialogComponent, TotalStaffComponent, MoveWorkplaceDialogComponent, WdfFieldConfirmationComponent, @@ -248,8 +268,8 @@ import { WorkplacePermissionsBearerPipe } from './pipes/workplace-permissions-be BecomeAParentDialogComponent, OwnershipChangeMessageDialogComponent, DeleteWorkplaceDialogComponent, - ParentConfirmationDialogComponent, - CqcConfirmationDialogComponent, + // ParentConfirmationDialogComponent, + // CqcConfirmationDialogComponent, TotalStaffComponent, BulkUploadFileTypePipePipe, SanitizeVideoUrlPipe, From 7c6282e2e1debb3ba63b63d9895ab1aa53b51a17 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Fri, 6 May 2022 15:47:18 +0100 Subject: [PATCH 16/33] feat(bulkUploadTrainingValidationRefactor) Created _checkForEmptyorNullDate() helper function --- .../classes/trainingCSVValidator.js | 25 ++++++++++++------- .../unit/classes/trainingCSVValidator.spec.js | 24 ++++++++++++++++-- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 3c6977a647..3075ae1181 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -187,11 +187,13 @@ class TrainingCsvValidator { } _validateDateCompleted() { - // optional - const myDateCompleted = this._currentLine.DATECOMPLETED; - const dateFormatRegex = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/\d{4}$/; - const actualDate = moment.utc(myDateCompleted, 'DD/MM/YYYY'); - const errMessage = this._getValidateDateCompletedErrMessage(myDateCompleted, dateFormatRegex, actualDate); + if (this._checkForEmptyOrNullDate(this._currentLine.DATECOMPLETED)) { + this._dateCompleted = this._currentLine.DATECOMPLETED; + return; + } + + const actualDate = moment.utc(this._currentLine.DATECOMPLETED, 'DD/MM/YYYY', true); + const errMessage = this._getValidateDateCompletedErrMessage(actualDate); if (!errMessage) { this._dateCompleted = actualDate; @@ -200,17 +202,22 @@ class TrainingCsvValidator { this._addValidationError('DATE_COMPLETED_ERROR', errMessage, this._currentLine.DATECOMPLETED, 'DATECOMPLETED'); } - _getValidateDateCompletedErrMessage(myDateCompleted, dateFormatRegex, actualDate) { - if (!dateFormatRegex.test(myDateCompleted)) { + _getValidateDateCompletedErrMessage(actualDate) { + if (!actualDate.isValid()) { return 'DATECOMPLETED is incorrectly formatted'; - } else if (!actualDate.isValid()) { - return 'DATECOMPLETED is invalid'; } else if (actualDate.isAfter(moment())) { return 'DATECOMPLETED is in the future'; } return; } + _checkForEmptyOrNullDate(date) { + if (!date || date === '') { + return true; + } + return false; + } + _validateExpiry() { // optional const myDateExpiry = this._currentLine.EXPIRYDATE; diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 8e1ed9501f..2b84cdd35a 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -345,13 +345,33 @@ describe('trainingCSVValidator', () => { }); describe('_validateDateCompleted()', async () => { - it('should pass validation and set _dateCompleted if a valid DATECOMPLETED is provided', async () => { + it('should pass validation and set _dateCompleted to DATECOMPLETED if a valid DATECOMPLETED is provided', async () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); await validator._validateDateCompleted(); expect(validator._validationErrors).to.deep.equal([]); - expect(validator._dateCompleted).to.deep.equal(moment.utc('01/01/2022', 'DD/MM/YYYY')); + expect(validator._dateCompleted).to.deep.equal(moment.utc('01/01/2022', 'DD/MM/YYYY', true)); + }); + + it('should pass validation and set _dateCompleted to an empty string if the DATECOMPLETED is a empty string', async () => { + trainingCsv.DATECOMPLETED = ''; + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateDateCompleted(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._dateCompleted).to.equal(''); + }); + + it('should pass validation and set _dateCompleted to null if the DATECOMPLETED is null', async () => { + trainingCsv.DATECOMPLETED = null; + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateDateCompleted(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._dateCompleted).to.equal(null); }); }); From 4a5dffd7f4fd0a03a3b63c7d941d4b06e945a017 Mon Sep 17 00:00:00 2001 From: Richard Pentecost Date: Fri, 6 May 2022 16:02:17 +0100 Subject: [PATCH 17/33] fix failing tests --- .../admin/email-campaign-history.resolver.spec.ts | 5 ++--- .../core/resolvers/admin/email-template.resolver.spec.ts | 5 ++--- .../inactive-workplaces-for-deletion.resolver.spec.ts | 5 ++--- .../resolvers/admin/inactive-workplaces.resolver.spec.ts | 5 ++--- .../inactive-emails/inactive-emails.component.spec.ts | 6 ++++-- .../targeted-emails/targeted-emails.component.spec.ts | 9 +++++---- .../features/admin/report/admin-report.component.spec.ts | 3 +-- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/app/core/resolvers/admin/email-campaign-history.resolver.spec.ts b/src/app/core/resolvers/admin/email-campaign-history.resolver.spec.ts index fd532ad535..ed6c36acae 100644 --- a/src/app/core/resolvers/admin/email-campaign-history.resolver.spec.ts +++ b/src/app/core/resolvers/admin/email-campaign-history.resolver.spec.ts @@ -3,7 +3,6 @@ import { TestBed } from '@angular/core/testing'; import { ActivatedRouteSnapshot } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; -import { SearchModule } from '@features/search/search.module'; import { EmailCampaignHistoryResolver } from './email-campaign-history.resolver'; @@ -12,8 +11,8 @@ describe('EmailCampaignHistoryResolver', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [SearchModule, HttpClientTestingModule, RouterTestingModule.withRoutes([])], - providers: [EmailCampaignHistoryResolver], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + providers: [EmailCampaignHistoryResolver, EmailCampaignService], }); resolver = TestBed.inject(EmailCampaignHistoryResolver); }); diff --git a/src/app/core/resolvers/admin/email-template.resolver.spec.ts b/src/app/core/resolvers/admin/email-template.resolver.spec.ts index fd7d5169b6..8f1bedad7d 100644 --- a/src/app/core/resolvers/admin/email-template.resolver.spec.ts +++ b/src/app/core/resolvers/admin/email-template.resolver.spec.ts @@ -3,7 +3,6 @@ import { TestBed } from '@angular/core/testing'; import { ActivatedRouteSnapshot } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; -import { SearchModule } from '@features/search/search.module'; import { EmailTemplateResolver } from './email-template.resolver'; @@ -12,8 +11,8 @@ describe('EmailTemplateResolver', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [SearchModule, HttpClientTestingModule, RouterTestingModule.withRoutes([])], - providers: [EmailTemplateResolver], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + providers: [EmailTemplateResolver, EmailCampaignService], }); resolver = TestBed.inject(EmailTemplateResolver); }); diff --git a/src/app/core/resolvers/admin/inactive-workplaces-for-deletion.resolver.spec.ts b/src/app/core/resolvers/admin/inactive-workplaces-for-deletion.resolver.spec.ts index dc11122e8c..20a370d993 100644 --- a/src/app/core/resolvers/admin/inactive-workplaces-for-deletion.resolver.spec.ts +++ b/src/app/core/resolvers/admin/inactive-workplaces-for-deletion.resolver.spec.ts @@ -3,7 +3,6 @@ import { TestBed } from '@angular/core/testing'; import { ActivatedRouteSnapshot } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; -import { SearchModule } from '@features/search/search.module'; import { InactiveWorkplacesForDeletionResolver } from './inactive-workplaces-for-deletion.resolver'; @@ -12,8 +11,8 @@ describe('InactiveWorkplacesForDeletionResolver', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [SearchModule, HttpClientTestingModule, RouterTestingModule.withRoutes([])], - providers: [InactiveWorkplacesForDeletionResolver], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + providers: [InactiveWorkplacesForDeletionResolver, EmailCampaignService], }); resolver = TestBed.inject(InactiveWorkplacesForDeletionResolver); }); diff --git a/src/app/core/resolvers/admin/inactive-workplaces.resolver.spec.ts b/src/app/core/resolvers/admin/inactive-workplaces.resolver.spec.ts index 188369aa77..26b928bc9b 100644 --- a/src/app/core/resolvers/admin/inactive-workplaces.resolver.spec.ts +++ b/src/app/core/resolvers/admin/inactive-workplaces.resolver.spec.ts @@ -3,7 +3,6 @@ import { TestBed } from '@angular/core/testing'; import { ActivatedRouteSnapshot } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; -import { SearchModule } from '@features/search/search.module'; import { InactiveWorkplacesResolver } from './inactive-workplaces.resolver'; @@ -12,8 +11,8 @@ describe('InactiveWorkplacesResolver', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [SearchModule, HttpClientTestingModule, RouterTestingModule.withRoutes([])], - providers: [InactiveWorkplacesResolver], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], + providers: [InactiveWorkplacesResolver, EmailCampaignService], }); resolver = TestBed.inject(InactiveWorkplacesResolver); }); diff --git a/src/app/features/admin/emails/inactive-emails/inactive-emails.component.spec.ts b/src/app/features/admin/emails/inactive-emails/inactive-emails.component.spec.ts index fcf34ce1d8..c36e0e82f4 100644 --- a/src/app/features/admin/emails/inactive-emails/inactive-emails.component.spec.ts +++ b/src/app/features/admin/emails/inactive-emails/inactive-emails.component.spec.ts @@ -1,10 +1,10 @@ +import { DecimalPipe } from '@angular/common'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; import { WindowRef } from '@core/services/window.ref'; -import { SearchModule } from '@features/search/search.module'; import { SharedModule } from '@shared/shared.module'; import { fireEvent, render, within } from '@testing-library/angular'; import { of } from 'rxjs'; @@ -14,8 +14,10 @@ import { InactiveEmailsComponent } from './inactive-emails.component'; describe('InactiveEmailsComponent', () => { async function setup() { return render(InactiveEmailsComponent, { - imports: [SharedModule, SearchModule, HttpClientTestingModule, RouterTestingModule], + imports: [SharedModule, HttpClientTestingModule, RouterTestingModule], providers: [ + EmailCampaignService, + DecimalPipe, { provide: WindowRef, useClass: WindowRef, diff --git a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts index cf61322747..155cf39d53 100644 --- a/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts +++ b/src/app/features/admin/emails/targeted-emails/targeted-emails.component.spec.ts @@ -1,3 +1,4 @@ +import { DecimalPipe } from '@angular/common'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -5,7 +6,6 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { EmailCampaignService } from '@core/services/admin/email-campaign.service'; import { WindowRef } from '@core/services/window.ref'; -import { SearchModule } from '@features/search/search.module'; import { SharedModule } from '@shared/shared.module'; import { fireEvent, render, within } from '@testing-library/angular'; import userEvent from '@testing-library/user-event'; @@ -23,12 +23,13 @@ describe('TargetedEmailsComponent', () => { SharedModule, HttpClientTestingModule, RouterTestingModule, - SearchModule, FormsModule, ReactiveFormsModule, NgxDropzoneModule, ], providers: [ + EmailCampaignService, + DecimalPipe, { provide: WindowRef, useClass: WindowRef, @@ -82,7 +83,7 @@ describe('TargetedEmailsComponent', () => { expect(totalEmail.innerHTML).toContain('1,500'); }); - it("should display 0 emails to be sent when the group and template haven't been selected", async () => { + it(`should display 0 emails to be sent when the group and template haven't been selected`, async () => { const component = await setup(); component.fixture.componentInstance.emailGroup = null; @@ -93,7 +94,7 @@ describe('TargetedEmailsComponent', () => { expect(totalEmails.innerHTML).toContain('0'); }); - it("should disable the Send emails button when the group and template haven't been selected", async () => { + it(`should disable the Send emails button when the group and template haven't been selected`, async () => { const component = await setup(); component.fixture.componentInstance.emailGroup = ''; diff --git a/src/app/features/admin/report/admin-report.component.spec.ts b/src/app/features/admin/report/admin-report.component.spec.ts index 059e81371e..901f02c20d 100644 --- a/src/app/features/admin/report/admin-report.component.spec.ts +++ b/src/app/features/admin/report/admin-report.component.spec.ts @@ -2,7 +2,6 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { ReportService } from '@core/services/report.service'; -import { SearchModule } from '@features/search/search.module'; import { SharedModule } from '@shared/shared.module'; import { fireEvent, render } from '@testing-library/angular'; import { of } from 'rxjs'; @@ -16,7 +15,7 @@ describe('ReportComponent', () => { async function setup() { return render(ReportComponent, { - imports: [SharedModule, SearchModule, HttpClientTestingModule, RouterTestingModule, AdminModule], + imports: [SharedModule, HttpClientTestingModule, RouterTestingModule, AdminModule], }); } From a8b20b489fa74dde0f16b0678034bddf5ddaae87 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Fri, 6 May 2022 16:54:07 +0100 Subject: [PATCH 18/33] feat(bulkUploadTrainingValidationRefactor) began refactoring _validateExpiry() --- .../classes/trainingCSVValidator.js | 99 +++++++---------- .../unit/classes/trainingCSVValidator.spec.js | 103 +++++++++++++++++- 2 files changed, 140 insertions(+), 62 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 3075ae1181..7f0d73b3f8 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -192,84 +192,54 @@ class TrainingCsvValidator { return; } - const actualDate = moment.utc(this._currentLine.DATECOMPLETED, 'DD/MM/YYYY', true); - const errMessage = this._getValidateDateCompletedErrMessage(actualDate); + const dateCompleted = moment.utc(this._currentLine.DATECOMPLETED, 'DD/MM/YYYY', true); + const errMessage = this._getValidateDateCompletedErrMessage(dateCompleted); if (!errMessage) { - this._dateCompleted = actualDate; + this._dateCompleted = dateCompleted; return; } this._addValidationError('DATE_COMPLETED_ERROR', errMessage, this._currentLine.DATECOMPLETED, 'DATECOMPLETED'); } - _getValidateDateCompletedErrMessage(actualDate) { - if (!actualDate.isValid()) { + _getValidateDateCompletedErrMessage(dateCompleted) { + if (!dateCompleted.isValid()) { return 'DATECOMPLETED is incorrectly formatted'; - } else if (actualDate.isAfter(moment())) { + } else if (dateCompleted.isAfter(moment())) { return 'DATECOMPLETED is in the future'; } return; } - _checkForEmptyOrNullDate(date) { - if (!date || date === '') { - return true; + _validateExpiry() { + if (this._checkForEmptyOrNullDate(this._currentLine.EXPIRYDATE)) { + this._expiry = this._currentLine.EXPIRYDATE; + return; } - return false; + + const expiredDate = moment.utc(this._currentLine.EXPIRYDATE, 'DD/MM/YYYY', true); + const validationErrorDetails = this._getValidateExpiryErrDetails(expiredDate); + + if (!validationErrorDetails) { + this._expiry = expiredDate; + return; + } + + this._addValidationError( + 'EXPIRY_DATE_ERROR', + validationErrorDetails.errMessage, + this._currentLine.EXPIRYDATE, + validationErrorDetails.errColumnName, + ); } - _validateExpiry() { - // optional - const myDateExpiry = this._currentLine.EXPIRYDATE; - const dateFormatRegex = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/\d{4}$/; - const actualDate = moment.utc(myDateExpiry, 'DD/MM/YYYY'); - const myDateCompleted = this._currentLine.DATECOMPLETED; - const actualDateCompleted = moment.utc(myDateCompleted, 'DD/MM/YYYY'); - - if (myDateExpiry) { - if (!dateFormatRegex.test(myDateExpiry)) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.EXPIRY_DATE_ERROR, - errType: 'EXPIRY_DATE_ERROR', - error: 'EXPIRYDATE is incorrectly formatted', - source: this._currentLine.EXPIRYDATE, - column: 'EXPIRYDATE', - }); - return false; - } else if (!actualDate.isValid()) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.EXPIRY_DATE_ERROR, - errType: 'EXPIRY_DATE_ERROR', - error: 'EXPIRYDATE is invalid', - source: this._currentLine.EXPIRYDATE, - column: 'EXPIRYDATE', - }); - return false; - } else if (actualDate.isSameOrBefore(actualDateCompleted, 'day')) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.EXPIRY_DATE_ERROR, - errType: 'EXPIRY_DATE_ERROR', - error: 'EXPIRYDATE must be after DATECOMPLETED', - source: this._currentLine.EXPIRYDATE, - column: 'EXPIRYDATE/DATECOMPLETED', - }); - return false; - } else { - this._expiry = actualDate; - return true; - } - } else { - return true; + _getValidateExpiryErrDetails(expiredDate) { + if (!expiredDate.isValid()) { + return { errMessage: 'EXPIRYDATE is incorrectly formatted', errColumnName: 'EXPIRYDATE' }; + } else if (expiredDate.isSameOrBefore(this._dateCompleted, 'day')) { + return { errMessage: 'EXPIRYDATE must be after DATECOMPLETED', errColumnName: 'EXPIRYDATE/DATECOMPLETED' }; } + return; } _validateDescription() { @@ -369,6 +339,13 @@ class TrainingCsvValidator { } } + _checkForEmptyOrNullDate(date) { + if (!date || date === '') { + return true; + } + return false; + } + _addValidationError(errorType, errorMessage, errorSource, columnName) { this._validationErrors.push({ worker: this._currentLine.UNIQUEWORKERID, diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 2b84cdd35a..28545bf9ee 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -18,7 +18,7 @@ describe('trainingCSVValidator', () => { CATEGORY: 1, DESCRIPTION: 'training', DATECOMPLETED: '01/01/2022', - EXPIRYDATE: '', + EXPIRYDATE: '15/04/2022', ACCREDITED: '', NOTES: '', }; @@ -420,5 +420,106 @@ describe('trainingCSVValidator', () => { ]); }); }); + + describe('_validateExpiry()', async () => { + it('should pass validation and set _expiry to EXPIRYDATE if a valid EXPIRYDATE is provided', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateExpiry(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._expiry).to.deep.equal(moment.utc('15/04/2022', 'DD/MM/YYYY', true)); + }); + + it('should pass validation and set _expiry to an empty string if EXPIRYDATE is an empty string', async () => { + trainingCsv.EXPIRYDATE = ''; + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateExpiry(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._expiry).to.deep.equal(''); + }); + + it('should pass validation and set _expiry to null if EXPIRYDATE is null', async () => { + trainingCsv.EXPIRYDATE = null; + const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); + + await validator._validateExpiry(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._expiry).to.deep.equal(null); + }); + }); + + describe('_getValidateExpiryErrMessage()', async () => { + it('should add EXPIRY_DATE_ERROR to validationErrors and set _expiry as null if EXPIRYDATE is incorrectly formatted', async () => { + trainingCsv.EXPIRYDATE = '12323423423'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateExpiry(); + + expect(validator._expiry).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1030, + errType: 'EXPIRY_DATE_ERROR', + error: 'EXPIRYDATE is incorrectly formatted', + source: trainingCsv.EXPIRYDATE, + column: 'EXPIRYDATE', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + + it('should add EXPIRY_DATE_ERROR to validationErrors and set _expiry as null if EXPIRYDATE is a date set before DATECOMPLETED ', async () => { + trainingCsv.EXPIRYDATE = '01/01/2000'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateDateCompleted(); + await validator._validateExpiry(); + + expect(validator._expiry).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1030, + errType: 'EXPIRY_DATE_ERROR', + error: 'EXPIRYDATE must be after DATECOMPLETED', + source: trainingCsv.EXPIRYDATE, + column: 'EXPIRYDATE/DATECOMPLETED', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + + it('should add EXPIRY_DATE_ERROR to validationErrors and set _expiry as null if EXPIRYDATE is the same date as DATECOMPLETED ', async () => { + trainingCsv.EXPIRYDATE = '01/01/2022'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateDateCompleted(); + await validator._validateExpiry(); + + expect(validator._expiry).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1030, + errType: 'EXPIRY_DATE_ERROR', + error: 'EXPIRYDATE must be after DATECOMPLETED', + source: trainingCsv.EXPIRYDATE, + column: 'EXPIRYDATE/DATECOMPLETED', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + }); }); }); From 95daabc4b06f6685848fef4861c5a997a43137af Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Mon, 9 May 2022 14:12:31 +0100 Subject: [PATCH 19/33] feat(targetedEmailsReport): Add function to get workplaces which did not have email returned --- server/reports/targeted-emails/index.js | 9 +++- .../reports/targeted-emails/index.spec.js | 52 +++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/server/reports/targeted-emails/index.js b/server/reports/targeted-emails/index.js index 7062b530ac..b0498dc917 100644 --- a/server/reports/targeted-emails/index.js +++ b/server/reports/targeted-emails/index.js @@ -1,11 +1,13 @@ const excelUtils = require('../../utils/excelUtils'); const { generateWorkplacesToEmailTab } = require('./workplacesToEmail'); +const { generateWorkplacesWithoutEmailTab } = require('./workplacesWithoutEmail'); const generateTargetedEmailsReport = async (workbook, users, establishmentNmdsIdList) => { const workplacesToEmail = formatWorkplacesToEmail(users); + const workplacesWithoutEmail = getWorkplacesWithoutEmail(workplacesToEmail, establishmentNmdsIdList); generateWorkplacesToEmailTab(workbook, workplacesToEmail); - //generateWorkplacesWithoutEmailTab(workbook, establishmentNmdsIdList); + //generateWorkplacesWithoutEmailTab(workbook, workplacesWithoutEmail); workbook.eachSheet((sheet) => { excelUtils.fitColumnsToSize(sheet); @@ -25,7 +27,12 @@ const formatWorkplace = (user) => { }; }; +const getWorkplacesWithoutEmail = (workplacesToEmail, establishmentNmdsIdList) => { + return establishmentNmdsIdList.filter((id) => workplacesToEmail.find((workplace) => workplace.nmdsId === id)); +}; + module.exports = { generateTargetedEmailsReport, formatWorkplacesToEmail, + getWorkplacesWithoutEmail, }; diff --git a/server/test/unit/reports/targeted-emails/index.spec.js b/server/test/unit/reports/targeted-emails/index.spec.js index ad9c8d0ca8..8e0729a4fb 100644 --- a/server/test/unit/reports/targeted-emails/index.spec.js +++ b/server/test/unit/reports/targeted-emails/index.spec.js @@ -1,6 +1,6 @@ const expect = require('chai').expect; -const { formatWorkplacesToEmail } = require('../../../../reports/targeted-emails'); +const { formatWorkplacesToEmail, getWorkplacesWithoutEmail } = require('../../../../reports/targeted-emails'); describe('reports/targetedEmails/index', () => { const mockUsers = [ @@ -22,20 +22,66 @@ describe('reports/targetedEmails/index', () => { }, ]; + const mockWorkplacesToEmail = [ + { + nmdsId: 'A123456', + emailAddress: 'mock@email.com', + }, + { + nmdsId: 'A123459', + emailAddress: 'mock2@email.com', + }, + ]; + describe('formatWorkplacesToEmail()', () => { it('should return array with objects containing nmdsId and email', async () => { const data = formatWorkplacesToEmail(mockUsers); - expect(data).to.deep.equal([ + expect(data).to.deep.equal(mockWorkplacesToEmail); + }); + }); + + describe('getWorkplacesWithoutEmail()', () => { + const nmdsIdsList = ['A123456', 'A123459']; + + it('should return array with all nmdsIds when all IDs in workplacesToEmail', async () => { + const data = getWorkplacesWithoutEmail(mockWorkplacesToEmail, nmdsIdsList); + + expect(data).to.deep.equal(['A123456', 'A123459']); + }); + + it('should return empty array when workplacesToEmail is empty', async () => { + const workplacesToEmail = []; + + const data = getWorkplacesWithoutEmail(workplacesToEmail, nmdsIdsList); + + expect(data).to.deep.equal([]); + }); + + it('should return array with first nmdsId when second ID not in workplacesToEmail', async () => { + const workplacesToEmail = [ { nmdsId: 'A123456', emailAddress: 'mock@email.com', }, + ]; + + const data = getWorkplacesWithoutEmail(workplacesToEmail, nmdsIdsList); + + expect(data).to.deep.equal(['A123456']); + }); + + it('should return array with second nmdsId when first ID not in workplacesToEmail', async () => { + const workplacesToEmail = [ { nmdsId: 'A123459', emailAddress: 'mock2@email.com', }, - ]); + ]; + + const data = getWorkplacesWithoutEmail(workplacesToEmail, nmdsIdsList); + + expect(data).to.deep.equal(['A123459']); }); }); }); From 8e140600e657cfd1ab1f29187398049ea26c1c89 Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Mon, 9 May 2022 14:23:41 +0100 Subject: [PATCH 20/33] feat(targetedEmailsReport): Fix getWorkplacesWithoutEmail to return ids which are not found --- server/reports/targeted-emails/index.js | 2 +- .../reports/targeted-emails/index.spec.js | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server/reports/targeted-emails/index.js b/server/reports/targeted-emails/index.js index b0498dc917..f6b3f66646 100644 --- a/server/reports/targeted-emails/index.js +++ b/server/reports/targeted-emails/index.js @@ -28,7 +28,7 @@ const formatWorkplace = (user) => { }; const getWorkplacesWithoutEmail = (workplacesToEmail, establishmentNmdsIdList) => { - return establishmentNmdsIdList.filter((id) => workplacesToEmail.find((workplace) => workplace.nmdsId === id)); + return establishmentNmdsIdList.filter((id) => !workplacesToEmail.find((workplace) => workplace.nmdsId === id)); }; module.exports = { diff --git a/server/test/unit/reports/targeted-emails/index.spec.js b/server/test/unit/reports/targeted-emails/index.spec.js index 8e0729a4fb..e7e2cfa7f5 100644 --- a/server/test/unit/reports/targeted-emails/index.spec.js +++ b/server/test/unit/reports/targeted-emails/index.spec.js @@ -44,25 +44,25 @@ describe('reports/targetedEmails/index', () => { describe('getWorkplacesWithoutEmail()', () => { const nmdsIdsList = ['A123456', 'A123459']; - it('should return array with all nmdsIds when all IDs in workplacesToEmail', async () => { + it('should return empty array when workplacesToEmail has all IDs', async () => { const data = getWorkplacesWithoutEmail(mockWorkplacesToEmail, nmdsIdsList); - expect(data).to.deep.equal(['A123456', 'A123459']); + expect(data).to.deep.equal([]); }); - it('should return empty array when workplacesToEmail is empty', async () => { + it('should return array with all nmdsIds from nmdsIdsList when workplacesToEmail is empty', async () => { const workplacesToEmail = []; const data = getWorkplacesWithoutEmail(workplacesToEmail, nmdsIdsList); - expect(data).to.deep.equal([]); + expect(data).to.deep.equal(['A123456', 'A123459']); }); - it('should return array with first nmdsId when second ID not in workplacesToEmail', async () => { + it('should return array with first nmdsId when first ID not in workplacesToEmail', async () => { const workplacesToEmail = [ { - nmdsId: 'A123456', - emailAddress: 'mock@email.com', + nmdsId: 'A123459', + emailAddress: 'mock2@email.com', }, ]; @@ -71,11 +71,11 @@ describe('reports/targetedEmails/index', () => { expect(data).to.deep.equal(['A123456']); }); - it('should return array with second nmdsId when first ID not in workplacesToEmail', async () => { + it('should return array with second nmdsId when second ID not in workplacesToEmail', async () => { const workplacesToEmail = [ { - nmdsId: 'A123459', - emailAddress: 'mock2@email.com', + nmdsId: 'A123456', + emailAddress: 'mock@email.com', }, ]; From 89f915b417081932153276d87d9ccae3901b176d Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Mon, 9 May 2022 14:30:11 +0100 Subject: [PATCH 21/33] feat(targetedEmailsReport): Create workplacesWithoutEmail tab --- server/reports/targeted-emails/index.js | 2 +- .../targeted-emails/workplacesWithoutEmail.js | 25 +++++++++++++++ .../workplacesWithoutEmail.spec.js | 31 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 server/reports/targeted-emails/workplacesWithoutEmail.js create mode 100644 server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js diff --git a/server/reports/targeted-emails/index.js b/server/reports/targeted-emails/index.js index f6b3f66646..7c21132da5 100644 --- a/server/reports/targeted-emails/index.js +++ b/server/reports/targeted-emails/index.js @@ -7,7 +7,7 @@ const generateTargetedEmailsReport = async (workbook, users, establishmentNmdsId const workplacesWithoutEmail = getWorkplacesWithoutEmail(workplacesToEmail, establishmentNmdsIdList); generateWorkplacesToEmailTab(workbook, workplacesToEmail); - //generateWorkplacesWithoutEmailTab(workbook, workplacesWithoutEmail); + generateWorkplacesWithoutEmailTab(workbook, workplacesWithoutEmail); workbook.eachSheet((sheet) => { excelUtils.fitColumnsToSize(sheet); diff --git a/server/reports/targeted-emails/workplacesWithoutEmail.js b/server/reports/targeted-emails/workplacesWithoutEmail.js new file mode 100644 index 0000000000..12ace9b818 --- /dev/null +++ b/server/reports/targeted-emails/workplacesWithoutEmail.js @@ -0,0 +1,25 @@ +const generateWorkplacesWithoutEmailTab = (workbook, workplacesWithoutEmail) => { + const workplacesWithoutEmailTab = workbook.addWorksheet('Workplaces without Email'); + + addContentToWorkplacesWithoutEmailTab(workplacesWithoutEmailTab, workplacesWithoutEmail); +}; + +const addContentToWorkplacesWithoutEmailTab = (workplacesWithoutEmailTab, workplacesWithoutEmail) => { + addHeaders(workplacesWithoutEmailTab); + + for (const id of workplacesWithoutEmail) { + workplacesWithoutEmailTab.addRow([id]); + } +}; + +const addHeaders = (workplacesWithoutEmailTab) => { + workplacesWithoutEmailTab.columns = [{ header: 'NMDS ID', key: 'nmdsId' }]; + + const headerRow = workplacesWithoutEmailTab.getRow(1); + headerRow.font = { bold: true, name: 'Calibri' }; +}; + +module.exports = { + generateWorkplacesWithoutEmailTab, + addContentToWorkplacesWithoutEmailTab, +}; diff --git a/server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js b/server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js new file mode 100644 index 0000000000..54100f5a9f --- /dev/null +++ b/server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js @@ -0,0 +1,31 @@ +const expect = require('chai').expect; +const excelJS = require('exceljs'); + +const { addContentToWorkplacesWithoutEmailTab } = require('../../../../reports/targeted-emails/WorkplacesWithoutEmail'); + +describe('addContentToWorkplacesWithoutEmailTab', () => { + let mockWorkplacesWithoutEmailTab; + const workplacesWithoutEmail = ['A123456', 'A123459']; + + beforeEach(() => { + mockWorkplacesWithoutEmailTab = new excelJS.Workbook().addWorksheet('Found Workplaces'); + }); + + it('should add NMDS ID header to top row', async () => { + addContentToWorkplacesWithoutEmailTab(mockWorkplacesWithoutEmailTab, workplacesWithoutEmail); + + expect(mockWorkplacesWithoutEmailTab.getCell('A1').value).to.equal('NMDS ID'); + }); + + it('should add the first NMDS ID in workplacesWithoutEmail to second row', async () => { + addContentToWorkplacesWithoutEmailTab(mockWorkplacesWithoutEmailTab, workplacesWithoutEmail); + + expect(mockWorkplacesWithoutEmailTab.getCell('A2').value).to.equal('A123456'); + }); + + it('should add the second NMDS ID in workplacesWithoutEmail to third row', async () => { + addContentToWorkplacesWithoutEmailTab(mockWorkplacesWithoutEmailTab, workplacesWithoutEmail); + + expect(mockWorkplacesWithoutEmailTab.getCell('A3').value).to.equal('A123459'); + }); +}); From 944d6ff27c272e76e5106b14af8a111d2482a5d6 Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Mon, 9 May 2022 14:48:15 +0100 Subject: [PATCH 22/33] feat(targetedEmailsReport): Use makeRowBold util in tabs --- server/reports/targeted-emails/workplacesToEmail.js | 5 +++-- server/reports/targeted-emails/workplacesWithoutEmail.js | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/reports/targeted-emails/workplacesToEmail.js b/server/reports/targeted-emails/workplacesToEmail.js index 0750e574c7..2d9e5ae691 100644 --- a/server/reports/targeted-emails/workplacesToEmail.js +++ b/server/reports/targeted-emails/workplacesToEmail.js @@ -1,3 +1,5 @@ +const { makeRowBold } = require('../../utils/excelUtils'); + const generateWorkplacesToEmailTab = (workbook, workplacesToEmail) => { const workplacesToEmailTab = workbook.addWorksheet('Found Workplaces'); @@ -16,8 +18,7 @@ const addHeaders = (workplacesToEmailTab) => { { header: 'Email Address', key: 'emailAddress' }, ]; - const headerRow = workplacesToEmailTab.getRow(1); - headerRow.font = { bold: true, name: 'Calibri' }; + makeRowBold(workplacesToEmailTab, 1); }; module.exports = { diff --git a/server/reports/targeted-emails/workplacesWithoutEmail.js b/server/reports/targeted-emails/workplacesWithoutEmail.js index 12ace9b818..c94110a7fd 100644 --- a/server/reports/targeted-emails/workplacesWithoutEmail.js +++ b/server/reports/targeted-emails/workplacesWithoutEmail.js @@ -1,3 +1,5 @@ +const { makeRowBold } = require('../../utils/excelUtils'); + const generateWorkplacesWithoutEmailTab = (workbook, workplacesWithoutEmail) => { const workplacesWithoutEmailTab = workbook.addWorksheet('Workplaces without Email'); @@ -15,8 +17,7 @@ const addContentToWorkplacesWithoutEmailTab = (workplacesWithoutEmailTab, workpl const addHeaders = (workplacesWithoutEmailTab) => { workplacesWithoutEmailTab.columns = [{ header: 'NMDS ID', key: 'nmdsId' }]; - const headerRow = workplacesWithoutEmailTab.getRow(1); - headerRow.font = { bold: true, name: 'Calibri' }; + makeRowBold(workplacesWithoutEmailTab, 1); }; module.exports = { From fc71129697adb328dcb7d20202852eda87e2047a Mon Sep 17 00:00:00 2001 From: duncanc19 Date: Mon, 9 May 2022 15:07:15 +0100 Subject: [PATCH 23/33] feat(targetedEmailsReport): Update import --- .../unit/reports/targeted-emails/workplacesWithoutEmail.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js b/server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js index 54100f5a9f..1ac0230c8f 100644 --- a/server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js +++ b/server/test/unit/reports/targeted-emails/workplacesWithoutEmail.spec.js @@ -1,7 +1,7 @@ const expect = require('chai').expect; const excelJS = require('exceljs'); -const { addContentToWorkplacesWithoutEmailTab } = require('../../../../reports/targeted-emails/WorkplacesWithoutEmail'); +const { addContentToWorkplacesWithoutEmailTab } = require('../../../../reports/targeted-emails/workplacesWithoutEmail'); describe('addContentToWorkplacesWithoutEmailTab', () => { let mockWorkplacesWithoutEmailTab; From 7a8fc3b4496d0d0fdc52dbea0089285ebff343ab Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 11:22:44 +0100 Subject: [PATCH 24/33] feat(bulkUploadTrainingValidationRefactor) refactored _validateDescription() --- .../classes/trainingCSVValidator.js | 37 +++------ .../unit/classes/trainingCSVValidator.spec.js | 81 +++++++++++++++++++ 2 files changed, 93 insertions(+), 25 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 7f0d73b3f8..28d75f0be1 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -245,35 +245,22 @@ class TrainingCsvValidator { _validateDescription() { const myDescription = this._currentLine.DESCRIPTION; const MAX_LENGTH = 120; + const errMessage = this._getValidateDescriptionErrMessage(myDescription, MAX_LENGTH); + if (!errMessage) { + this._description = myDescription; + return; + } + this._addValidationError('DESCRIPTION_ERROR', errMessage, this._currentLine.DESCRIPTION, 'DESCRIPTION'); + } + + _getValidateDescriptionErrMessage(myDescription, MAX_LENGTH) { if (!myDescription || myDescription.length === 0) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.DESCRIPTION_ERROR, - errType: 'DESCRIPTION_ERROR', - error: 'DESCRIPTION has not been supplied', - source: this._currentLine.DESCRIPTION, - column: 'DESCRIPTION', - }); - return false; + return 'DESCRIPTION has not been supplied'; } else if (myDescription.length > MAX_LENGTH) { - this._validationErrors.push({ - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, - errCode: TrainingCsvValidator.DESCRIPTION_ERROR, - errType: 'DESCRIPTION_ERROR', - error: `DESCRIPTION is longer than ${MAX_LENGTH} characters`, - source: this._currentLine.DESCRIPTION, - column: 'DESCRIPTION', - }); - return false; - } else { - this._description = myDescription; - return true; + return `DESCRIPTION is longer than ${MAX_LENGTH} characters`; } + return; } _validateCategory() { diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 28545bf9ee..9a566c6c46 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -521,5 +521,86 @@ describe('trainingCSVValidator', () => { ]); }); }); + + describe('_validateDescription()', async () => { + it('should pass validation and set _description to DESCRIPTION if a valid DESCRIPTION is provided', async () => { + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateDescription(); + + expect(validator._validationErrors).to.deep.equal([]); + expect(validator._description).to.equal('training'); + }); + }); + + describe('_getValidateDescriptionErrMessage()', async () => { + it('should add DESCRIPTION_ERROR to validationErrors and set _description as null if DESCRIPTION is an empty string', async () => { + trainingCsv.DESCRIPTION = ''; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateDescription(); + + expect(validator._description).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1040, + errType: 'DESCRIPTION_ERROR', + error: 'DESCRIPTION has not been supplied', + source: trainingCsv.DESCRIPTION, + column: 'DESCRIPTION', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + + it('should add DESCRIPTION_ERROR to validationErrors and set _description as null if DESCRIPTION is null', async () => { + trainingCsv.DESCRIPTION = null; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateDescription(); + + expect(validator._description).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1040, + errType: 'DESCRIPTION_ERROR', + error: 'DESCRIPTION has not been supplied', + source: trainingCsv.DESCRIPTION, + column: 'DESCRIPTION', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + + it('should add DESCRIPTION_ERROR to validationErrors and set _description as null if DESCRIPTION is longer than MAX_LENGTH', async () => { + trainingCsv.DESCRIPTION = + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis nato'; + + const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); + + await validator._validateDescription(); + + expect(validator._description).to.equal(null); + expect(validator._validationErrors).to.deep.equal([ + { + errCode: 1040, + errType: 'DESCRIPTION_ERROR', + error: 'DESCRIPTION is longer than 120 characters', + source: trainingCsv.DESCRIPTION, + column: 'DESCRIPTION', + lineNumber: 1, + name: 'foo', + worker: 'bar', + }, + ]); + }); + }); }); }); +// From 65a88a5707a4a55d2a437aa082034bbec5a948b3 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 11:53:25 +0100 Subject: [PATCH 25/33] feat(bulkUploadTrainingValidationRefactor) refactored validate() by removing status --- .../classes/trainingCSVValidator.js | 20 ++++++++----------- .../unit/classes/trainingCSVValidator.spec.js | 1 - 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 28d75f0be1..5853b0f277 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -7,7 +7,6 @@ class TrainingCsvValidator { this._currentLine = currentLine; this._lineNumber = lineNumber; this._validationErrors = []; - this._localeStId = null; this._uniqueWorkerId = null; this._dateCompleted = null; @@ -104,17 +103,14 @@ class TrainingCsvValidator { } validate() { - let status = true; - status = !this._validateLocaleStId() ? false : status; - status = !this._validateUniqueWorkerId() ? false : status; - status = !this._validateDateCompleted() ? false : status; - status = !this._validateExpiry() ? false : status; - status = !this._validateDescription() ? false : status; - status = !this._validateCategory() ? false : status; - status = !this._validateAccredited() ? false : status; - status = !this._validateNotes() ? false : status; - - return status; + this._validateLocaleStId(); + this._validateUniqueWorkerId(); + this._validateDateCompleted(); + this._validateExpiry(); + this._validateDescription(); + this._validateCategory(); + this._validateAccredited(); + this._validateNotes(); } toJSON() { diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 9a566c6c46..60a1e29bf1 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -603,4 +603,3 @@ describe('trainingCSVValidator', () => { }); }); }); -// From 4f5acaaf9eab77029671990428d6c9b6fac28769 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 12:25:34 +0100 Subject: [PATCH 26/33] feat(bulkUploadTrainingValidationRefactor) removed irrelevant length checks --- .../classes/trainingCSVValidator.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 5853b0f277..1a5917997f 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -154,7 +154,7 @@ class TrainingCsvValidator { } _getValidateLocaleStIdErrMessage(myLocaleStId, MAX_LENGTH) { - if (!myLocaleStId || myLocaleStId.length === 0) { + if (!myLocaleStId) { return 'LOCALESTID has not been supplied'; } else if (myLocaleStId.length > MAX_LENGTH) { return `LOCALESTID is longer than ${MAX_LENGTH} characters`; @@ -174,7 +174,7 @@ class TrainingCsvValidator { } _getValidateUniqueWorkerIdErrMessage(myUniqueId, MAX_LENGTH) { - if (!myUniqueId || myUniqueId.length === 0) { + if (!myUniqueId) { return 'UNIQUEWORKERID has not been supplied'; } else if (myUniqueId.length > MAX_LENGTH) { return `UNIQUEWORKERID is longer than ${MAX_LENGTH} characters`; @@ -183,7 +183,7 @@ class TrainingCsvValidator { } _validateDateCompleted() { - if (this._checkForEmptyOrNullDate(this._currentLine.DATECOMPLETED)) { + if (!this._currentLine.DATECOMPLETED) { this._dateCompleted = this._currentLine.DATECOMPLETED; return; } @@ -208,7 +208,7 @@ class TrainingCsvValidator { } _validateExpiry() { - if (this._checkForEmptyOrNullDate(this._currentLine.EXPIRYDATE)) { + if (!this._currentLine.EXPIRYDATE) { this._expiry = this._currentLine.EXPIRYDATE; return; } @@ -251,7 +251,7 @@ class TrainingCsvValidator { } _getValidateDescriptionErrMessage(myDescription, MAX_LENGTH) { - if (!myDescription || myDescription.length === 0) { + if (!myDescription) { return 'DESCRIPTION has not been supplied'; } else if (myDescription.length > MAX_LENGTH) { return `DESCRIPTION is longer than ${MAX_LENGTH} characters`; @@ -307,7 +307,7 @@ class TrainingCsvValidator { const notes = this._currentLine.NOTES; const MAX_LENGTH = 1000; - if (notes && notes.length > 0) { + if (notes) { if (notes.length > MAX_LENGTH) { this._addValidationError( 'NOTES_ERROR', @@ -322,13 +322,6 @@ class TrainingCsvValidator { } } - _checkForEmptyOrNullDate(date) { - if (!date || date === '') { - return true; - } - return false; - } - _addValidationError(errorType, errorMessage, errorSource, columnName) { this._validationErrors.push({ worker: this._currentLine.UNIQUEWORKERID, From 728ba0b414583c9dafa1e16a24f10d0f7ccad13d Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 12:33:16 +0100 Subject: [PATCH 27/33] feat(bulkUploadTrainingValidationRefactor) refactored validationErrors() --- .../bulkUpload/classes/trainingCSVValidator.js | 9 ++------- .../unit/classes/trainingCSVValidator.spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 1a5917997f..97b0e3270e 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -324,6 +324,7 @@ class TrainingCsvValidator { _addValidationError(errorType, errorMessage, errorSource, columnName) { this._validationErrors.push({ + origin: 'Training', worker: this._currentLine.UNIQUEWORKERID, name: this._currentLine.LOCALESTID, lineNumber: this._lineNumber, @@ -336,13 +337,7 @@ class TrainingCsvValidator { } get validationErrors() { - // include the "origin" of validation error - return this._validationErrors.map((thisValidation) => { - return { - origin: 'Training', - ...thisValidation, - }; - }); + return this._validationErrors; } } diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 60a1e29bf1..ca9482f799 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -75,6 +75,7 @@ describe('trainingCSVValidator', () => { expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1060, errType: 'ACCREDITED_ERROR', error: 'ACCREDITED is invalid', @@ -98,6 +99,7 @@ describe('trainingCSVValidator', () => { expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1050, errType: 'CATEGORY_ERROR', error: 'CATEGORY has not been supplied', @@ -119,6 +121,7 @@ describe('trainingCSVValidator', () => { expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1050, errType: 'CATEGORY_ERROR', error: 'CATEGORY has not been supplied', @@ -153,6 +156,7 @@ describe('trainingCSVValidator', () => { expect(validator._notes).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1070, errType: 'NOTES_ERROR', error: 'NOTES is longer than 1000 characters', @@ -197,6 +201,7 @@ describe('trainingCSVValidator', () => { expect(validator._localeStId).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1000, errType: 'LOCALESTID_ERROR', error: 'LOCALESTID has not been supplied', @@ -219,6 +224,7 @@ describe('trainingCSVValidator', () => { expect(validator._localeStId).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1000, errType: 'LOCALESTID_ERROR', error: 'LOCALESTID has not been supplied', @@ -241,6 +247,7 @@ describe('trainingCSVValidator', () => { expect(validator._localeStId).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1000, errType: 'LOCALESTID_ERROR', error: 'LOCALESTID is longer than 50 characters', @@ -287,6 +294,7 @@ describe('trainingCSVValidator', () => { expect(validator._uniqueWorkerId).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1010, errType: 'UNIQUE_WORKER_ID_ERROR', error: 'UNIQUEWORKERID has not been supplied', @@ -309,6 +317,7 @@ describe('trainingCSVValidator', () => { expect(validator._uniqueWorkerId).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1010, errType: 'UNIQUE_WORKER_ID_ERROR', error: 'UNIQUEWORKERID has not been supplied', @@ -331,6 +340,7 @@ describe('trainingCSVValidator', () => { expect(validator._uniqueWorkerId).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1010, errType: 'UNIQUE_WORKER_ID_ERROR', error: 'UNIQUEWORKERID is longer than 50 characters', @@ -386,6 +396,7 @@ describe('trainingCSVValidator', () => { expect(validator._dateCompleted).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1020, errType: 'DATE_COMPLETED_ERROR', error: 'DATECOMPLETED is incorrectly formatted', @@ -408,6 +419,7 @@ describe('trainingCSVValidator', () => { expect(validator._dateCompleted).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1020, errType: 'DATE_COMPLETED_ERROR', error: 'DATECOMPLETED is in the future', @@ -463,6 +475,7 @@ describe('trainingCSVValidator', () => { expect(validator._expiry).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1030, errType: 'EXPIRY_DATE_ERROR', error: 'EXPIRYDATE is incorrectly formatted', @@ -486,6 +499,7 @@ describe('trainingCSVValidator', () => { expect(validator._expiry).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1030, errType: 'EXPIRY_DATE_ERROR', error: 'EXPIRYDATE must be after DATECOMPLETED', @@ -509,6 +523,7 @@ describe('trainingCSVValidator', () => { expect(validator._expiry).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1030, errType: 'EXPIRY_DATE_ERROR', error: 'EXPIRYDATE must be after DATECOMPLETED', @@ -544,6 +559,7 @@ describe('trainingCSVValidator', () => { expect(validator._description).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1040, errType: 'DESCRIPTION_ERROR', error: 'DESCRIPTION has not been supplied', @@ -566,6 +582,7 @@ describe('trainingCSVValidator', () => { expect(validator._description).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1040, errType: 'DESCRIPTION_ERROR', error: 'DESCRIPTION has not been supplied', @@ -589,6 +606,7 @@ describe('trainingCSVValidator', () => { expect(validator._description).to.equal(null); expect(validator._validationErrors).to.deep.equal([ { + origin: 'Training', errCode: 1040, errType: 'DESCRIPTION_ERROR', error: 'DESCRIPTION is longer than 120 characters', From 5c82e5f1dfad248df8972ce6fce1a46597d57fbe Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 12:42:48 +0100 Subject: [PATCH 28/33] feat(bulkUploadTrainingValidationRefactor) refactored tests to use getters --- .../classes/trainingCSVValidator.js | 2 +- .../unit/classes/trainingCSVValidator.spec.js | 124 +++++++++--------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 97b0e3270e..0951fc1f37 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -78,7 +78,7 @@ class TrainingCsvValidator { return this._uniqueWorkerId; } - get completed() { + get dateCompleted() { return this._dateCompleted; } diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index ca9482f799..682430eac2 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -30,7 +30,7 @@ describe('trainingCSVValidator', () => { await validator._validateAccredited(); - expect(validator._validationErrors).to.deep.equal([]); + expect(validator.validationErrors).to.deep.equal([]); }); it('should pass validation and set accredited to Yes if ACCREDITED is 1', async () => { @@ -40,8 +40,8 @@ describe('trainingCSVValidator', () => { await validator._validateAccredited(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._accredited).to.equal('Yes'); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.accredited).to.equal('Yes'); }); it('should pass validation and set accredited to No if ACCREDITED is 0', async () => { @@ -51,8 +51,8 @@ describe('trainingCSVValidator', () => { await validator._validateAccredited(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._accredited).to.equal('No'); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.accredited).to.equal('No'); }); it("should pass validation and set ACCREDITED to Don't know if ACCREDITED is 999", async () => { @@ -62,8 +62,8 @@ describe('trainingCSVValidator', () => { await validator._validateAccredited(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._accredited).to.equal("Don't know"); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.accredited).to.equal("Don't know"); }); it('should add ACCREDITED_ERROR to validationErrors if invalid ACCREDITED is provided', async () => { @@ -73,7 +73,7 @@ describe('trainingCSVValidator', () => { await validator._validateAccredited(); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1060, @@ -97,7 +97,7 @@ describe('trainingCSVValidator', () => { await validator._validateCategory(); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1050, @@ -119,7 +119,7 @@ describe('trainingCSVValidator', () => { await validator._validateCategory(); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1050, @@ -139,8 +139,8 @@ describe('trainingCSVValidator', () => { await validator._validateCategory(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._category).to.equal(8); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.category).to.equal(8); }); }); @@ -153,8 +153,8 @@ describe('trainingCSVValidator', () => { await validator._validateNotes(); - expect(validator._notes).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.notes).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1070, @@ -176,8 +176,8 @@ describe('trainingCSVValidator', () => { await validator._validateNotes(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._notes).to.equal('valid short note'); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.notes).to.equal('valid short note'); }); it('should leave notes as null and not add error if NOTES empty string', async () => { @@ -185,8 +185,8 @@ describe('trainingCSVValidator', () => { await validator._validateNotes(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._notes).to.equal(null); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.notes).to.equal(null); }); }); @@ -198,8 +198,8 @@ describe('trainingCSVValidator', () => { await validator._validateLocaleStId(); - expect(validator._localeStId).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.localeStId).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1000, @@ -221,8 +221,8 @@ describe('trainingCSVValidator', () => { await validator._validateLocaleStId(); - expect(validator._localeStId).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.localeStId).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1000, @@ -244,8 +244,8 @@ describe('trainingCSVValidator', () => { await validator._validateLocaleStId(); - expect(validator._localeStId).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.localeStId).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1000, @@ -267,8 +267,8 @@ describe('trainingCSVValidator', () => { await validator._validateLocaleStId(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._localeStId).to.equal('foo'); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.localeStId).to.equal('foo'); }); }); @@ -278,8 +278,8 @@ describe('trainingCSVValidator', () => { await validator._validateUniqueWorkerId(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._uniqueWorkerId).to.equal('bar'); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.uniqueWorkerId).to.equal('bar'); }); }); @@ -291,8 +291,8 @@ describe('trainingCSVValidator', () => { await validator._validateUniqueWorkerId(); - expect(validator._uniqueWorkerId).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.uniqueWorkerId).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1010, @@ -314,8 +314,8 @@ describe('trainingCSVValidator', () => { await validator._validateUniqueWorkerId(); - expect(validator._uniqueWorkerId).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.uniqueWorkerId).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1010, @@ -337,8 +337,8 @@ describe('trainingCSVValidator', () => { await validator._validateUniqueWorkerId(); - expect(validator._uniqueWorkerId).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.uniqueWorkerId).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1010, @@ -360,8 +360,8 @@ describe('trainingCSVValidator', () => { await validator._validateDateCompleted(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._dateCompleted).to.deep.equal(moment.utc('01/01/2022', 'DD/MM/YYYY', true)); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.dateCompleted).to.deep.equal(moment.utc('01/01/2022', 'DD/MM/YYYY', true)); }); it('should pass validation and set _dateCompleted to an empty string if the DATECOMPLETED is a empty string', async () => { @@ -370,8 +370,8 @@ describe('trainingCSVValidator', () => { await validator._validateDateCompleted(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._dateCompleted).to.equal(''); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.dateCompleted).to.equal(''); }); it('should pass validation and set _dateCompleted to null if the DATECOMPLETED is null', async () => { @@ -380,8 +380,8 @@ describe('trainingCSVValidator', () => { await validator._validateDateCompleted(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._dateCompleted).to.equal(null); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.dateCompleted).to.equal(null); }); }); @@ -393,8 +393,8 @@ describe('trainingCSVValidator', () => { await validator._validateDateCompleted(); - expect(validator._dateCompleted).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.dateCompleted).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1020, @@ -416,8 +416,8 @@ describe('trainingCSVValidator', () => { await validator._validateDateCompleted(); - expect(validator._dateCompleted).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.dateCompleted).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1020, @@ -439,8 +439,8 @@ describe('trainingCSVValidator', () => { await validator._validateExpiry(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._expiry).to.deep.equal(moment.utc('15/04/2022', 'DD/MM/YYYY', true)); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.expiry).to.deep.equal(moment.utc('15/04/2022', 'DD/MM/YYYY', true)); }); it('should pass validation and set _expiry to an empty string if EXPIRYDATE is an empty string', async () => { @@ -449,8 +449,8 @@ describe('trainingCSVValidator', () => { await validator._validateExpiry(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._expiry).to.deep.equal(''); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.expiry).to.deep.equal(''); }); it('should pass validation and set _expiry to null if EXPIRYDATE is null', async () => { @@ -459,8 +459,8 @@ describe('trainingCSVValidator', () => { await validator._validateExpiry(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._expiry).to.deep.equal(null); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.expiry).to.deep.equal(null); }); }); @@ -496,8 +496,8 @@ describe('trainingCSVValidator', () => { await validator._validateDateCompleted(); await validator._validateExpiry(); - expect(validator._expiry).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.expiry).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1030, @@ -520,8 +520,8 @@ describe('trainingCSVValidator', () => { await validator._validateDateCompleted(); await validator._validateExpiry(); - expect(validator._expiry).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.expiry).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1030, @@ -543,8 +543,8 @@ describe('trainingCSVValidator', () => { await validator._validateDescription(); - expect(validator._validationErrors).to.deep.equal([]); - expect(validator._description).to.equal('training'); + expect(validator.validationErrors).to.deep.equal([]); + expect(validator.description).to.equal('training'); }); }); @@ -556,8 +556,8 @@ describe('trainingCSVValidator', () => { await validator._validateDescription(); - expect(validator._description).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.description).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1040, @@ -579,8 +579,8 @@ describe('trainingCSVValidator', () => { await validator._validateDescription(); - expect(validator._description).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.description).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1040, @@ -603,8 +603,8 @@ describe('trainingCSVValidator', () => { await validator._validateDescription(); - expect(validator._description).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.description).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1040, From b5a3edf36f570f0d97a9f57a27ca40dd9b02f275 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 12:56:15 +0100 Subject: [PATCH 29/33] feat(bulkUploadTrainingValidationRefactor) removed irrelvant getter methods --- .../classes/trainingCSVValidator.js | 164 +++++++----------- .../unit/classes/trainingCSVValidator.spec.js | 50 +++--- 2 files changed, 85 insertions(+), 129 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 0951fc1f37..fa379e4840 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -4,17 +4,17 @@ const BUDI = require('../classes/BUDI').BUDI; class TrainingCsvValidator { constructor(currentLine, lineNumber, mappings) { - this._currentLine = currentLine; - this._lineNumber = lineNumber; - this._validationErrors = []; - this._localeStId = null; - this._uniqueWorkerId = null; - this._dateCompleted = null; - this._expiry = null; - this._description = null; - this._category = null; - this._accredited = null; - this._notes = null; + this.currentLine = currentLine; + this.lineNumber = lineNumber; + this.validationErrors = []; + this.localeStId = null; + this.uniqueWorkerId = null; + this.dateCompleted = null; + this.expiry = null; + this.description = null; + this.category = null; + this.accredited = null; + this.notes = null; this.BUDI = new BUDI(mappings); } @@ -62,46 +62,6 @@ class TrainingCsvValidator { return 2070; } - get lineNumber() { - return this._lineNumber; - } - - get currentLine() { - return this._currentLine; - } - - get localeStId() { - return this._localeStId; - } - - get uniqueWorkerId() { - return this._uniqueWorkerId; - } - - get dateCompleted() { - return this._dateCompleted; - } - - get expiry() { - return this._expiry; - } - - get description() { - return this._description; - } - - get category() { - return this._category; - } - - get accredited() { - return this._accredited; - } - - get notes() { - return this._notes; - } - validate() { this._validateLocaleStId(); this._validateUniqueWorkerId(); @@ -115,42 +75,42 @@ class TrainingCsvValidator { toJSON() { return { - localId: this._localeStId, - uniqueWorkerId: this._uniqueWorkerId, - completed: this._dateCompleted ? this._dateCompleted.format('DD/MM/YYYY') : undefined, - expiry: this._expiry ? this._expiry.format('DD/MM/YYYY') : undefined, - description: this._description, - category: this._category, - accredited: this._accredited, - notes: this._notes, - lineNumber: this._lineNumber, + localId: this.localeStId, + uniqueWorkerId: this.uniqueWorkerId, + completed: this.dateCompleted ? this.dateCompleted.format('DD/MM/YYYY') : undefined, + expiry: this.expiry ? this.expiry.format('DD/MM/YYYY') : undefined, + description: this.description, + category: this.category, + accredited: this.accredited, + notes: this.notes, + lineNumber: this.lineNumber, }; } toAPI() { const changeProperties = { trainingCategory: { - id: this._category, + id: this.category, }, - completed: this._dateCompleted ? this._dateCompleted.format('YYYY-MM-DD') : undefined, - expires: this._expiry ? this._expiry.format('YYYY-MM-DD') : undefined, - title: this._description ? this._description : undefined, - notes: this._notes ? this._notes : undefined, - accredited: this._accredited ? this._accredited : undefined, + completed: this.dateCompleted ? this.dateCompleted.format('YYYY-MM-DD') : undefined, + expires: this.expiry ? this.expiry.format('YYYY-MM-DD') : undefined, + title: this.description ? this.description : undefined, + notes: this.notes ? this.notes : undefined, + accredited: this.accredited ? this.accredited : undefined, }; return changeProperties; } _validateLocaleStId() { - const myLocaleStId = this._currentLine.LOCALESTID; + const myLocaleStId = this.currentLine.LOCALESTID; const MAX_LENGTH = 50; const errMessage = this._getValidateLocaleStIdErrMessage(myLocaleStId, MAX_LENGTH); if (!errMessage) { - this._localeStId = myLocaleStId; + this.localeStId = myLocaleStId; return; } - this._addValidationError('LOCALESTID_ERROR', errMessage, this._currentLine.LOCALESTID, 'LOCALESTID'); + this._addValidationError('LOCALESTID_ERROR', errMessage, this.currentLine.LOCALESTID, 'LOCALESTID'); } _getValidateLocaleStIdErrMessage(myLocaleStId, MAX_LENGTH) { @@ -163,14 +123,14 @@ class TrainingCsvValidator { } _validateUniqueWorkerId() { - const myUniqueId = this._currentLine.UNIQUEWORKERID; + const myUniqueId = this.currentLine.UNIQUEWORKERID; const MAX_LENGTH = 50; const errMessage = this._getValidateUniqueWorkerIdErrMessage(myUniqueId, MAX_LENGTH); if (!errMessage) { - this._uniqueWorkerId = myUniqueId; + this.uniqueWorkerId = myUniqueId; return; } - this._addValidationError('UNIQUE_WORKER_ID_ERROR', errMessage, this._currentLine.UNIQUEWORKERID, 'UNIQUEWORKERID'); + this._addValidationError('UNIQUE_WORKER_ID_ERROR', errMessage, this.currentLine.UNIQUEWORKERID, 'UNIQUEWORKERID'); } _getValidateUniqueWorkerIdErrMessage(myUniqueId, MAX_LENGTH) { @@ -183,19 +143,19 @@ class TrainingCsvValidator { } _validateDateCompleted() { - if (!this._currentLine.DATECOMPLETED) { - this._dateCompleted = this._currentLine.DATECOMPLETED; + if (!this.currentLine.DATECOMPLETED) { + this.dateCompleted = this.currentLine.DATECOMPLETED; return; } - const dateCompleted = moment.utc(this._currentLine.DATECOMPLETED, 'DD/MM/YYYY', true); + const dateCompleted = moment.utc(this.currentLine.DATECOMPLETED, 'DD/MM/YYYY', true); const errMessage = this._getValidateDateCompletedErrMessage(dateCompleted); if (!errMessage) { - this._dateCompleted = dateCompleted; + this.dateCompleted = dateCompleted; return; } - this._addValidationError('DATE_COMPLETED_ERROR', errMessage, this._currentLine.DATECOMPLETED, 'DATECOMPLETED'); + this._addValidationError('DATE_COMPLETED_ERROR', errMessage, this.currentLine.DATECOMPLETED, 'DATECOMPLETED'); } _getValidateDateCompletedErrMessage(dateCompleted) { @@ -208,23 +168,23 @@ class TrainingCsvValidator { } _validateExpiry() { - if (!this._currentLine.EXPIRYDATE) { - this._expiry = this._currentLine.EXPIRYDATE; + if (!this.currentLine.EXPIRYDATE) { + this.expiry = this.currentLine.EXPIRYDATE; return; } - const expiredDate = moment.utc(this._currentLine.EXPIRYDATE, 'DD/MM/YYYY', true); + const expiredDate = moment.utc(this.currentLine.EXPIRYDATE, 'DD/MM/YYYY', true); const validationErrorDetails = this._getValidateExpiryErrDetails(expiredDate); if (!validationErrorDetails) { - this._expiry = expiredDate; + this.expiry = expiredDate; return; } this._addValidationError( 'EXPIRY_DATE_ERROR', validationErrorDetails.errMessage, - this._currentLine.EXPIRYDATE, + this.currentLine.EXPIRYDATE, validationErrorDetails.errColumnName, ); } @@ -232,22 +192,22 @@ class TrainingCsvValidator { _getValidateExpiryErrDetails(expiredDate) { if (!expiredDate.isValid()) { return { errMessage: 'EXPIRYDATE is incorrectly formatted', errColumnName: 'EXPIRYDATE' }; - } else if (expiredDate.isSameOrBefore(this._dateCompleted, 'day')) { + } else if (expiredDate.isSameOrBefore(this.dateCompleted, 'day')) { return { errMessage: 'EXPIRYDATE must be after DATECOMPLETED', errColumnName: 'EXPIRYDATE/DATECOMPLETED' }; } return; } _validateDescription() { - const myDescription = this._currentLine.DESCRIPTION; + const myDescription = this.currentLine.DESCRIPTION; const MAX_LENGTH = 120; const errMessage = this._getValidateDescriptionErrMessage(myDescription, MAX_LENGTH); if (!errMessage) { - this._description = myDescription; + this.description = myDescription; return; } - this._addValidationError('DESCRIPTION_ERROR', errMessage, this._currentLine.DESCRIPTION, 'DESCRIPTION'); + this._addValidationError('DESCRIPTION_ERROR', errMessage, this.currentLine.DESCRIPTION, 'DESCRIPTION'); } _getValidateDescriptionErrMessage(myDescription, MAX_LENGTH) { @@ -260,36 +220,36 @@ class TrainingCsvValidator { } _validateCategory() { - const category = parseInt(this._currentLine.CATEGORY, 10); + const category = parseInt(this.currentLine.CATEGORY, 10); if (Number.isNaN(category) || this.BUDI.trainingCategory(this.BUDI.TO_ASC, category) === null) { this._addValidationError( 'CATEGORY_ERROR', 'CATEGORY has not been supplied', - this._currentLine.CATEGORY, + this.currentLine.CATEGORY, 'CATEGORY', ); return; } - this._category = this.BUDI.trainingCategory(this.BUDI.TO_ASC, category); + this.category = this.BUDI.trainingCategory(this.BUDI.TO_ASC, category); } _validateAccredited() { - if (this._currentLine.ACCREDITED) { - const accredited = parseInt(this._currentLine.ACCREDITED, 10); + if (this.currentLine.ACCREDITED) { + const accredited = parseInt(this.currentLine.ACCREDITED, 10); const ALLOWED_VALUES = [0, 1, 999]; if (Number.isNaN(accredited) || !ALLOWED_VALUES.includes(accredited)) { this._addValidationError( 'ACCREDITED_ERROR', 'ACCREDITED is invalid', - this._currentLine.ACCREDITED, + this.currentLine.ACCREDITED, 'ACCREDITED', ); return; } - this._accredited = this._convertAccreditedValue(accredited); + this.accredited = this._convertAccreditedValue(accredited); } } @@ -304,7 +264,7 @@ class TrainingCsvValidator { } _validateNotes() { - const notes = this._currentLine.NOTES; + const notes = this.currentLine.NOTES; const MAX_LENGTH = 1000; if (notes) { @@ -312,22 +272,22 @@ class TrainingCsvValidator { this._addValidationError( 'NOTES_ERROR', `NOTES is longer than ${MAX_LENGTH} characters`, - this._currentLine.NOTES, + this.currentLine.NOTES, 'NOTES', ); return; } - this._notes = notes; + this.notes = notes; } } _addValidationError(errorType, errorMessage, errorSource, columnName) { - this._validationErrors.push({ + this.validationErrors.push({ origin: 'Training', - worker: this._currentLine.UNIQUEWORKERID, - name: this._currentLine.LOCALESTID, - lineNumber: this._lineNumber, + worker: this.currentLine.UNIQUEWORKERID, + name: this.currentLine.LOCALESTID, + lineNumber: this.lineNumber, errCode: TrainingCsvValidator[errorType], errType: errorType, error: errorMessage, @@ -335,10 +295,6 @@ class TrainingCsvValidator { column: columnName, }); } - - get validationErrors() { - return this._validationErrors; - } } module.exports.TrainingCsvValidator = TrainingCsvValidator; diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 682430eac2..26893683da 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -191,7 +191,7 @@ describe('trainingCSVValidator', () => { }); describe('_getValidateLocaleStIdErrorStatus()', () => { - it('should add LOCALESTID_ERROR to validationErrors and set _localStId as null if myLocaleStId length === 0', async () => { + it('should add LOCALESTID_ERROR to validationErrors and set localStId as null if myLocaleStId length === 0', async () => { trainingCsv.LOCALESTID = ''; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -214,7 +214,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add LOCALESTID_ERROR to validationErrors and leave _localStId as null if myLocaleStId doesn't exist", async () => { + it("should add LOCALESTID_ERROR to validationErrors and leave localStId as null if myLocaleStId doesn't exist", async () => { trainingCsv.LOCALESTID = null; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -237,7 +237,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add LOCALESTID_ERROR to validationErrors and leave _localStId as null if myLocaleStId's length is greater than MAX_LENGTH", async () => { + it("should add LOCALESTID_ERROR to validationErrors and leave localStId as null if myLocaleStId's length is greater than MAX_LENGTH", async () => { trainingCsv.LOCALESTID = 'Lorem ipsum dolor sit amet, consectetuer adipiscing'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -262,7 +262,7 @@ describe('trainingCSVValidator', () => { }); describe('_validateLocaleStId()', async () => { - it('should pass validation and set _uniqueWorkerId if a valid LOCALESTID is provided', async () => { + it('should pass validation and set uniqueWorkerId if a valid LOCALESTID is provided', async () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); await validator._validateLocaleStId(); @@ -273,7 +273,7 @@ describe('trainingCSVValidator', () => { }); describe('_validateUniqueWorkerId()', async () => { - it('should pass validation and set _uniqueWorkerId if a valid UNIQUEWORKERID is provided', async () => { + it('should pass validation and set uniqueWorkerId if a valid UNIQUEWORKERID is provided', async () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); await validator._validateUniqueWorkerId(); @@ -284,7 +284,7 @@ describe('trainingCSVValidator', () => { }); describe('_getValidateUniqueWorkerIdErrMessage()', () => { - it('should add UNIQUE_WORKER_ID_ERROR to validationErrors and set _uniqueWorkerId as null if myUniqueId length === 0', async () => { + it('should add UNIQUE_WORKER_ID_ERROR to validationErrors and set uniqueWorkerId as null if myUniqueId length === 0', async () => { trainingCsv.UNIQUEWORKERID = ''; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -307,7 +307,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave _uniqueWorkerId as null if myUniqueId doesn't exist", async () => { + it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave uniqueWorkerId as null if myUniqueId doesn't exist", async () => { trainingCsv.UNIQUEWORKERID = null; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -330,7 +330,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave _uniqueWorkerId as null if myUniqueId's length is greater than MAX_LENGTH", async () => { + it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave uniqueWorkerId as null if myUniqueId's length is greater than MAX_LENGTH", async () => { trainingCsv.UNIQUEWORKERID = 'Lorem ipsum dolor sit amet, consectetuer adipiscing'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -355,7 +355,7 @@ describe('trainingCSVValidator', () => { }); describe('_validateDateCompleted()', async () => { - it('should pass validation and set _dateCompleted to DATECOMPLETED if a valid DATECOMPLETED is provided', async () => { + it('should pass validation and set dateCompleted to DATECOMPLETED if a valid DATECOMPLETED is provided', async () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); await validator._validateDateCompleted(); @@ -364,7 +364,7 @@ describe('trainingCSVValidator', () => { expect(validator.dateCompleted).to.deep.equal(moment.utc('01/01/2022', 'DD/MM/YYYY', true)); }); - it('should pass validation and set _dateCompleted to an empty string if the DATECOMPLETED is a empty string', async () => { + it('should pass validation and set dateCompleted to an empty string if the DATECOMPLETED is a empty string', async () => { trainingCsv.DATECOMPLETED = ''; const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); @@ -374,7 +374,7 @@ describe('trainingCSVValidator', () => { expect(validator.dateCompleted).to.equal(''); }); - it('should pass validation and set _dateCompleted to null if the DATECOMPLETED is null', async () => { + it('should pass validation and set dateCompleted to null if the DATECOMPLETED is null', async () => { trainingCsv.DATECOMPLETED = null; const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); @@ -386,7 +386,7 @@ describe('trainingCSVValidator', () => { }); describe('_getValidateDateCompletedErrMessage()', async () => { - it('should add DATE_COMPLETED_ERROR to validationErrors and set _dateCompleted as null if DATECOMPLETED is incorrectly formatted', async () => { + it('should add DATE_COMPLETED_ERROR to validationErrors and set dateCompleted as null if DATECOMPLETED is incorrectly formatted', async () => { trainingCsv.DATECOMPLETED = '12323423423'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -409,7 +409,7 @@ describe('trainingCSVValidator', () => { ]); }); - it('should add DATE_COMPLETED_ERROR to validationErrors and set _dateCompleted as null if DATECOMPLETED is a date set in the future', async () => { + it('should add DATE_COMPLETED_ERROR to validationErrors and set dateCompleted as null if DATECOMPLETED is a date set in the future', async () => { trainingCsv.DATECOMPLETED = '01/01/2099'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -434,7 +434,7 @@ describe('trainingCSVValidator', () => { }); describe('_validateExpiry()', async () => { - it('should pass validation and set _expiry to EXPIRYDATE if a valid EXPIRYDATE is provided', async () => { + it('should pass validation and set expiry to EXPIRYDATE if a valid EXPIRYDATE is provided', async () => { const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); await validator._validateExpiry(); @@ -443,7 +443,7 @@ describe('trainingCSVValidator', () => { expect(validator.expiry).to.deep.equal(moment.utc('15/04/2022', 'DD/MM/YYYY', true)); }); - it('should pass validation and set _expiry to an empty string if EXPIRYDATE is an empty string', async () => { + it('should pass validation and set expiry to an empty string if EXPIRYDATE is an empty string', async () => { trainingCsv.EXPIRYDATE = ''; const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); @@ -453,7 +453,7 @@ describe('trainingCSVValidator', () => { expect(validator.expiry).to.deep.equal(''); }); - it('should pass validation and set _expiry to null if EXPIRYDATE is null', async () => { + it('should pass validation and set expiry to null if EXPIRYDATE is null', async () => { trainingCsv.EXPIRYDATE = null; const validator = new TrainingCsvValidator(trainingCsv, 2, mappings); @@ -465,15 +465,15 @@ describe('trainingCSVValidator', () => { }); describe('_getValidateExpiryErrMessage()', async () => { - it('should add EXPIRY_DATE_ERROR to validationErrors and set _expiry as null if EXPIRYDATE is incorrectly formatted', async () => { + it('should add EXPIRY_DATE_ERROR to validationErrors and set expiry as null if EXPIRYDATE is incorrectly formatted', async () => { trainingCsv.EXPIRYDATE = '12323423423'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); await validator._validateExpiry(); - expect(validator._expiry).to.equal(null); - expect(validator._validationErrors).to.deep.equal([ + expect(validator.expiry).to.equal(null); + expect(validator.validationErrors).to.deep.equal([ { origin: 'Training', errCode: 1030, @@ -488,7 +488,7 @@ describe('trainingCSVValidator', () => { ]); }); - it('should add EXPIRY_DATE_ERROR to validationErrors and set _expiry as null if EXPIRYDATE is a date set before DATECOMPLETED ', async () => { + it('should add EXPIRY_DATE_ERROR to validationErrors and set expiry as null if EXPIRYDATE is a date set before DATECOMPLETED ', async () => { trainingCsv.EXPIRYDATE = '01/01/2000'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -512,7 +512,7 @@ describe('trainingCSVValidator', () => { ]); }); - it('should add EXPIRY_DATE_ERROR to validationErrors and set _expiry as null if EXPIRYDATE is the same date as DATECOMPLETED ', async () => { + it('should add EXPIRY_DATE_ERROR to validationErrors and set expiry as null if EXPIRYDATE is the same date as DATECOMPLETED ', async () => { trainingCsv.EXPIRYDATE = '01/01/2022'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -538,7 +538,7 @@ describe('trainingCSVValidator', () => { }); describe('_validateDescription()', async () => { - it('should pass validation and set _description to DESCRIPTION if a valid DESCRIPTION is provided', async () => { + it('should pass validation and set description to DESCRIPTION if a valid DESCRIPTION is provided', async () => { const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); await validator._validateDescription(); @@ -549,7 +549,7 @@ describe('trainingCSVValidator', () => { }); describe('_getValidateDescriptionErrMessage()', async () => { - it('should add DESCRIPTION_ERROR to validationErrors and set _description as null if DESCRIPTION is an empty string', async () => { + it('should add DESCRIPTION_ERROR to validationErrors and set description as null if DESCRIPTION is an empty string', async () => { trainingCsv.DESCRIPTION = ''; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -572,7 +572,7 @@ describe('trainingCSVValidator', () => { ]); }); - it('should add DESCRIPTION_ERROR to validationErrors and set _description as null if DESCRIPTION is null', async () => { + it('should add DESCRIPTION_ERROR to validationErrors and set description as null if DESCRIPTION is null', async () => { trainingCsv.DESCRIPTION = null; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -595,7 +595,7 @@ describe('trainingCSVValidator', () => { ]); }); - it('should add DESCRIPTION_ERROR to validationErrors and set _description as null if DESCRIPTION is longer than MAX_LENGTH', async () => { + it('should add DESCRIPTION_ERROR to validationErrors and set description as null if DESCRIPTION is longer than MAX_LENGTH', async () => { trainingCsv.DESCRIPTION = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis nato'; From ef213cd3c0d53918e3439d6c1941ce8ff21bd79a Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 14:28:54 +0100 Subject: [PATCH 30/33] feat(bulkUploadTrainingValidationRefactor) renamed various variables --- .../classes/trainingCSVValidator.js | 36 +++++++++---------- .../unit/classes/trainingCSVValidator.spec.js | 12 +++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index fa379e4840..5ce29d9d6e 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -103,40 +103,40 @@ class TrainingCsvValidator { } _validateLocaleStId() { - const myLocaleStId = this.currentLine.LOCALESTID; + const localeStId = this.currentLine.LOCALESTID; const MAX_LENGTH = 50; - const errMessage = this._getValidateLocaleStIdErrMessage(myLocaleStId, MAX_LENGTH); + const errMessage = this._getValidateLocaleStIdErrMessage(localeStId, MAX_LENGTH); if (!errMessage) { - this.localeStId = myLocaleStId; + this.localeStId = localeStId; return; } this._addValidationError('LOCALESTID_ERROR', errMessage, this.currentLine.LOCALESTID, 'LOCALESTID'); } - _getValidateLocaleStIdErrMessage(myLocaleStId, MAX_LENGTH) { - if (!myLocaleStId) { + _getValidateLocaleStIdErrMessage(localeStId, MAX_LENGTH) { + if (!localeStId) { return 'LOCALESTID has not been supplied'; - } else if (myLocaleStId.length > MAX_LENGTH) { + } else if (localeStId.length > MAX_LENGTH) { return `LOCALESTID is longer than ${MAX_LENGTH} characters`; } return; } _validateUniqueWorkerId() { - const myUniqueId = this.currentLine.UNIQUEWORKERID; + const uniqueId = this.currentLine.UNIQUEWORKERID; const MAX_LENGTH = 50; - const errMessage = this._getValidateUniqueWorkerIdErrMessage(myUniqueId, MAX_LENGTH); + const errMessage = this._getValidateUniqueWorkerIdErrMessage(uniqueId, MAX_LENGTH); if (!errMessage) { - this.uniqueWorkerId = myUniqueId; + this.uniqueWorkerId = uniqueId; return; } this._addValidationError('UNIQUE_WORKER_ID_ERROR', errMessage, this.currentLine.UNIQUEWORKERID, 'UNIQUEWORKERID'); } - _getValidateUniqueWorkerIdErrMessage(myUniqueId, MAX_LENGTH) { - if (!myUniqueId) { + _getValidateUniqueWorkerIdErrMessage(uniqueId, MAX_LENGTH) { + if (!uniqueId) { return 'UNIQUEWORKERID has not been supplied'; - } else if (myUniqueId.length > MAX_LENGTH) { + } else if (uniqueId.length > MAX_LENGTH) { return `UNIQUEWORKERID is longer than ${MAX_LENGTH} characters`; } return; @@ -199,21 +199,21 @@ class TrainingCsvValidator { } _validateDescription() { - const myDescription = this.currentLine.DESCRIPTION; + const description = this.currentLine.DESCRIPTION; const MAX_LENGTH = 120; - const errMessage = this._getValidateDescriptionErrMessage(myDescription, MAX_LENGTH); + const errMessage = this._getValidateDescriptionErrMessage(description, MAX_LENGTH); if (!errMessage) { - this.description = myDescription; + this.description = description; return; } this._addValidationError('DESCRIPTION_ERROR', errMessage, this.currentLine.DESCRIPTION, 'DESCRIPTION'); } - _getValidateDescriptionErrMessage(myDescription, MAX_LENGTH) { - if (!myDescription) { + _getValidateDescriptionErrMessage(description, MAX_LENGTH) { + if (!description) { return 'DESCRIPTION has not been supplied'; - } else if (myDescription.length > MAX_LENGTH) { + } else if (description.length > MAX_LENGTH) { return `DESCRIPTION is longer than ${MAX_LENGTH} characters`; } return; diff --git a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js index 26893683da..65ab292fd0 100644 --- a/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js +++ b/lambdas/bulkUpload/test/unit/classes/trainingCSVValidator.spec.js @@ -191,7 +191,7 @@ describe('trainingCSVValidator', () => { }); describe('_getValidateLocaleStIdErrorStatus()', () => { - it('should add LOCALESTID_ERROR to validationErrors and set localStId as null if myLocaleStId length === 0', async () => { + it('should add LOCALESTID_ERROR to validationErrors and set localStId as null if localeStId length === 0', async () => { trainingCsv.LOCALESTID = ''; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -214,7 +214,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add LOCALESTID_ERROR to validationErrors and leave localStId as null if myLocaleStId doesn't exist", async () => { + it("should add LOCALESTID_ERROR to validationErrors and leave localStId as null if localeStId doesn't exist", async () => { trainingCsv.LOCALESTID = null; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -237,7 +237,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add LOCALESTID_ERROR to validationErrors and leave localStId as null if myLocaleStId's length is greater than MAX_LENGTH", async () => { + it("should add LOCALESTID_ERROR to validationErrors and leave localStId as null if localeStId's length is greater than MAX_LENGTH", async () => { trainingCsv.LOCALESTID = 'Lorem ipsum dolor sit amet, consectetuer adipiscing'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -284,7 +284,7 @@ describe('trainingCSVValidator', () => { }); describe('_getValidateUniqueWorkerIdErrMessage()', () => { - it('should add UNIQUE_WORKER_ID_ERROR to validationErrors and set uniqueWorkerId as null if myUniqueId length === 0', async () => { + it('should add UNIQUE_WORKER_ID_ERROR to validationErrors and set uniqueWorkerId as null if localeStId length === 0', async () => { trainingCsv.UNIQUEWORKERID = ''; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -307,7 +307,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave uniqueWorkerId as null if myUniqueId doesn't exist", async () => { + it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave uniqueWorkerId as null if localeStId doesn't exist", async () => { trainingCsv.UNIQUEWORKERID = null; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); @@ -330,7 +330,7 @@ describe('trainingCSVValidator', () => { ]); }); - it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave uniqueWorkerId as null if myUniqueId's length is greater than MAX_LENGTH", async () => { + it("should add UNIQUE_WORKER_ID_ERROR to validationErrors and leave uniqueWorkerId as null if localeStId's length is greater than MAX_LENGTH", async () => { trainingCsv.UNIQUEWORKERID = 'Lorem ipsum dolor sit amet, consectetuer adipiscing'; const validator = new TrainingCsvValidator(trainingCsv, 1, mappings); From c6aa88f37d455d7e159e96334d92db77793113b2 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Tue, 10 May 2022 15:27:03 +0100 Subject: [PATCH 31/33] feat(bulkUploadTrainingValidationRefactor) moved out errMessage functions to an error file --- .../classes/trainingCSVValidator.js | 56 ++----------------- lambdas/bulkUpload/validateTraining/errors.js | 41 ++++++++++++++ 2 files changed, 47 insertions(+), 50 deletions(-) create mode 100644 lambdas/bulkUpload/validateTraining/errors.js diff --git a/lambdas/bulkUpload/classes/trainingCSVValidator.js b/lambdas/bulkUpload/classes/trainingCSVValidator.js index 5ce29d9d6e..1649fc145a 100644 --- a/lambdas/bulkUpload/classes/trainingCSVValidator.js +++ b/lambdas/bulkUpload/classes/trainingCSVValidator.js @@ -1,6 +1,7 @@ const moment = require('moment'); const BUDI = require('../classes/BUDI').BUDI; +const errors = require('../validateTraining/errors'); class TrainingCsvValidator { constructor(currentLine, lineNumber, mappings) { @@ -105,7 +106,7 @@ class TrainingCsvValidator { _validateLocaleStId() { const localeStId = this.currentLine.LOCALESTID; const MAX_LENGTH = 50; - const errMessage = this._getValidateLocaleStIdErrMessage(localeStId, MAX_LENGTH); + const errMessage = errors._getValidateLocaleStIdErrMessage(localeStId, MAX_LENGTH); if (!errMessage) { this.localeStId = localeStId; return; @@ -113,19 +114,10 @@ class TrainingCsvValidator { this._addValidationError('LOCALESTID_ERROR', errMessage, this.currentLine.LOCALESTID, 'LOCALESTID'); } - _getValidateLocaleStIdErrMessage(localeStId, MAX_LENGTH) { - if (!localeStId) { - return 'LOCALESTID has not been supplied'; - } else if (localeStId.length > MAX_LENGTH) { - return `LOCALESTID is longer than ${MAX_LENGTH} characters`; - } - return; - } - _validateUniqueWorkerId() { const uniqueId = this.currentLine.UNIQUEWORKERID; const MAX_LENGTH = 50; - const errMessage = this._getValidateUniqueWorkerIdErrMessage(uniqueId, MAX_LENGTH); + const errMessage = errors._getValidateUniqueWorkerIdErrMessage(uniqueId, MAX_LENGTH); if (!errMessage) { this.uniqueWorkerId = uniqueId; return; @@ -133,15 +125,6 @@ class TrainingCsvValidator { this._addValidationError('UNIQUE_WORKER_ID_ERROR', errMessage, this.currentLine.UNIQUEWORKERID, 'UNIQUEWORKERID'); } - _getValidateUniqueWorkerIdErrMessage(uniqueId, MAX_LENGTH) { - if (!uniqueId) { - return 'UNIQUEWORKERID has not been supplied'; - } else if (uniqueId.length > MAX_LENGTH) { - return `UNIQUEWORKERID is longer than ${MAX_LENGTH} characters`; - } - return; - } - _validateDateCompleted() { if (!this.currentLine.DATECOMPLETED) { this.dateCompleted = this.currentLine.DATECOMPLETED; @@ -149,7 +132,7 @@ class TrainingCsvValidator { } const dateCompleted = moment.utc(this.currentLine.DATECOMPLETED, 'DD/MM/YYYY', true); - const errMessage = this._getValidateDateCompletedErrMessage(dateCompleted); + const errMessage = errors._getValidateDateCompletedErrMessage(dateCompleted); if (!errMessage) { this.dateCompleted = dateCompleted; @@ -158,15 +141,6 @@ class TrainingCsvValidator { this._addValidationError('DATE_COMPLETED_ERROR', errMessage, this.currentLine.DATECOMPLETED, 'DATECOMPLETED'); } - _getValidateDateCompletedErrMessage(dateCompleted) { - if (!dateCompleted.isValid()) { - return 'DATECOMPLETED is incorrectly formatted'; - } else if (dateCompleted.isAfter(moment())) { - return 'DATECOMPLETED is in the future'; - } - return; - } - _validateExpiry() { if (!this.currentLine.EXPIRYDATE) { this.expiry = this.currentLine.EXPIRYDATE; @@ -174,7 +148,7 @@ class TrainingCsvValidator { } const expiredDate = moment.utc(this.currentLine.EXPIRYDATE, 'DD/MM/YYYY', true); - const validationErrorDetails = this._getValidateExpiryErrDetails(expiredDate); + const validationErrorDetails = errors._getValidateExpiryErrDetails(expiredDate, this.dateCompleted); if (!validationErrorDetails) { this.expiry = expiredDate; @@ -189,19 +163,10 @@ class TrainingCsvValidator { ); } - _getValidateExpiryErrDetails(expiredDate) { - if (!expiredDate.isValid()) { - return { errMessage: 'EXPIRYDATE is incorrectly formatted', errColumnName: 'EXPIRYDATE' }; - } else if (expiredDate.isSameOrBefore(this.dateCompleted, 'day')) { - return { errMessage: 'EXPIRYDATE must be after DATECOMPLETED', errColumnName: 'EXPIRYDATE/DATECOMPLETED' }; - } - return; - } - _validateDescription() { const description = this.currentLine.DESCRIPTION; const MAX_LENGTH = 120; - const errMessage = this._getValidateDescriptionErrMessage(description, MAX_LENGTH); + const errMessage = errors._getValidateDescriptionErrMessage(description, MAX_LENGTH); if (!errMessage) { this.description = description; @@ -210,15 +175,6 @@ class TrainingCsvValidator { this._addValidationError('DESCRIPTION_ERROR', errMessage, this.currentLine.DESCRIPTION, 'DESCRIPTION'); } - _getValidateDescriptionErrMessage(description, MAX_LENGTH) { - if (!description) { - return 'DESCRIPTION has not been supplied'; - } else if (description.length > MAX_LENGTH) { - return `DESCRIPTION is longer than ${MAX_LENGTH} characters`; - } - return; - } - _validateCategory() { const category = parseInt(this.currentLine.CATEGORY, 10); diff --git a/lambdas/bulkUpload/validateTraining/errors.js b/lambdas/bulkUpload/validateTraining/errors.js new file mode 100644 index 0000000000..91629d7bcf --- /dev/null +++ b/lambdas/bulkUpload/validateTraining/errors.js @@ -0,0 +1,41 @@ +const moment = require('moment'); + +exports._getValidateLocaleStIdErrMessage = (localeStId, MAX_LENGTH) => { + if (!localeStId) { + return 'LOCALESTID has not been supplied'; + } else if (localeStId.length > MAX_LENGTH) { + return `LOCALESTID is longer than ${MAX_LENGTH} characters`; + } +}; + +exports._getValidateUniqueWorkerIdErrMessage = (uniqueId, MAX_LENGTH) => { + if (!uniqueId) { + return 'UNIQUEWORKERID has not been supplied'; + } else if (uniqueId.length > MAX_LENGTH) { + return `UNIQUEWORKERID is longer than ${MAX_LENGTH} characters`; + } +}; + +exports._getValidateDateCompletedErrMessage = (dateCompleted) => { + if (!dateCompleted.isValid()) { + return 'DATECOMPLETED is incorrectly formatted'; + } else if (dateCompleted.isAfter(moment())) { + return 'DATECOMPLETED is in the future'; + } +}; + +exports._getValidateExpiryErrDetails = (expiredDate, dateCompleted) => { + if (!expiredDate.isValid()) { + return { errMessage: 'EXPIRYDATE is incorrectly formatted', errColumnName: 'EXPIRYDATE' }; + } else if (expiredDate.isSameOrBefore(dateCompleted, 'day')) { + return { errMessage: 'EXPIRYDATE must be after DATECOMPLETED', errColumnName: 'EXPIRYDATE/DATECOMPLETED' }; + } +}; + +exports._getValidateDescriptionErrMessage = (description, MAX_LENGTH) => { + if (!description) { + return 'DESCRIPTION has not been supplied'; + } else if (description.length > MAX_LENGTH) { + return `DESCRIPTION is longer than ${MAX_LENGTH} characters`; + } +}; From 94ee4959620085413d346ff5090f5b72dc339aa9 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Wed, 11 May 2022 11:24:38 +0100 Subject: [PATCH 32/33] techDebt(moveLambdaInCircleWorkflow): Changed requires to wait for blue green --- .circleci/config.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4917cbd671..eb804822d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -529,9 +529,7 @@ workflows: folder: bulkUpload context: serverless-deployment requires: - - test_frontend - - test_backend - - performance + - blue_green_benchmarks filters: branches: only: @@ -541,9 +539,7 @@ workflows: folder: bulkUpload context: serverless-deployment requires: - - test_frontend - - test_backend - - performance + - blue_green_staging filters: branches: only: @@ -553,9 +549,7 @@ workflows: folder: bulkUpload context: serverless-deployment requires: - - test_frontend - - test_backend - - performance + - preprod_deploy filters: branches: only: @@ -565,9 +559,7 @@ workflows: folder: bulkUpload context: serverless-deployment requires: - - test_frontend - - test_backend - - performance + - prod_deploy filters: # ignore any commit on any branch by default branches: From 6fbfabfce02fefd6352f78d15dd9d7ac0b7f4f85 Mon Sep 17 00:00:00 2001 From: popey2700 Date: Wed, 11 May 2022 11:29:17 +0100 Subject: [PATCH 33/33] techDebt(moveLambdaInCircleWorkflow): Moved serverless deployment location in file --- .circleci/config.yml | 86 ++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index eb804822d8..7e991cde94 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -524,49 +524,6 @@ workflows: only: /^v[0-9]+(\.[0-9]+)*$/ requires: - install - - serverless_deployment: - appname: sfcbenchmarks - folder: bulkUpload - context: serverless-deployment - requires: - - blue_green_benchmarks - filters: - branches: - only: - - feat/benchmarks - - serverless_deployment: - appname: sfcstaging - folder: bulkUpload - context: serverless-deployment - requires: - - blue_green_staging - filters: - branches: - only: - - test - - serverless_deployment: - appname: sfcpreprod - folder: bulkUpload - context: serverless-deployment - requires: - - preprod_deploy - filters: - branches: - only: - - live - - serverless_deployment: - appname: sfcprod - folder: bulkUpload - context: serverless-deployment - requires: - - prod_deploy - filters: - # ignore any commit on any branch by default - branches: - ignore: /.*/ - # only act on version tags - tags: - only: /^v[0-9]+(\.[0-9]+)*$/ - blue_green: name: blue_green_benchmarks appname: sfcbenchmarks @@ -702,6 +659,49 @@ workflows: # org: dhsc-skills-for-care-nmds-sc-2 # space: production # workspace_path: . + - serverless_deployment: + appname: sfcbenchmarks + folder: bulkUpload + context: serverless-deployment + requires: + - blue_green_benchmarks + filters: + branches: + only: + - feat/benchmarks + - serverless_deployment: + appname: sfcstaging + folder: bulkUpload + context: serverless-deployment + requires: + - blue_green_staging + filters: + branches: + only: + - test + - serverless_deployment: + appname: sfcpreprod + folder: bulkUpload + context: serverless-deployment + requires: + - preprod_deploy + filters: + branches: + only: + - live + - serverless_deployment: + appname: sfcprod + folder: bulkUpload + context: serverless-deployment + requires: + - prod_deploy + filters: + # ignore any commit on any branch by default + branches: + ignore: /.*/ + # only act on version tags + tags: + only: /^v[0-9]+(\.[0-9]+)*$/ - sentry_release: requires: - blue_green_benchmarks