Skip to content

Commit

Permalink
Merge pull request #3574 from bcgov/NDT-514-bug-budget-details-number…
Browse files Browse the repository at this point in the history
…s-are-inconsistent-between-application-and-template

feat: email notification on failed template read
  • Loading branch information
ccbc-service-account authored Oct 1, 2024
2 parents 162aea0 + 4f64119 commit adc077b
Show file tree
Hide file tree
Showing 14 changed files with 439 additions and 21 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# [1.195.0](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.194.2...v1.195.0) (2024-10-01)

### Features

- email notification when failed template scrape ([2b3757d](https://github.com/bcgov/CONN-CCBC-portal/commit/2b3757dcc84f48f36368c87560b3223aa37670ef))

## [1.194.2](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.194.1...v1.194.2) (2024-10-01)

## [1.194.1](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.194.0...v1.194.1) (2024-10-01)
Expand Down
11 changes: 11 additions & 0 deletions app/backend/lib/emails/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import householdCountUpdate from './templates/householdCountUpdate';
import rfiCoverageMapKmzUploaded from './templates/rfiCoverageMapKmzUploaded';
import notifyConditionallyApproved from './templates/notifyConditionallyApproved';
import notifyApplicationSubmission from './templates/notifyApplicationSubmission';
import notifyFailedReadOfTemplateData from './templates/notifyFailedReadOfTemplateData';

const email = Router();

Expand Down Expand Up @@ -78,4 +79,14 @@ email.post('/api/email/notifyApplicationSubmission', limiter, (req, res) => {
});
});

email.post('/api/email/notifyFailedReadOfTemplateData', limiter, (req, res) => {
const { params } = req.body;
return handleEmailNotification(
req,
res,
notifyFailedReadOfTemplateData,
params
);
});

export default email;
1 change: 0 additions & 1 deletion app/backend/lib/emails/handleEmailNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ const handleEmailNotification = async (
return res.status(404).end();
}
const eventInitiator = getAuthUser(req);

const { applicationId, host } = req.body;
const {
emailTo,
Expand Down
48 changes: 48 additions & 0 deletions app/backend/lib/emails/templates/notifyFailedReadOfTemplateData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import getConfig from 'next/config';
import {
EmailTemplate,
EmailTemplateProvider,
} from '../handleEmailNotification';

interface EmailTemplateParams {
templateNumber: string;
uuid: string;
organizationName: string | undefined;
projectTitle: string | undefined;
uploadedAt: string | undefined;
}

const notifyFailedReadOfTemplateData: EmailTemplateProvider = (
applicationId: string,
url: string,
initiator,
params: EmailTemplateParams
): EmailTemplate => {
const namespace = getConfig()?.publicRuntimeConfig?.OPENSHIFT_APP_NAMESPACE;
let env = 'Dev';
if (namespace?.endsWith('-prod')) {
env = 'Prod';
} else if (namespace?.endsWith('-test')) {
env = 'Test';
}

return {
emailTo: [111, 112, 113, 114, 115],
emailCC: [],
tag: 'failed-read-of-template-data',
subject: `Template ${params.templateNumber} - Failed Response`,
body: `
<p>
The following template upload by an applicant had a failed response at ${params.uploadedAt}:
</p>
<ul>
<li>Environment: ${env}</li>
<li>File UUID: ${params.uuid}</li>
<li>Template Number: ${params.templateNumber}</li>
<li>Application ID: ${applicationId}</li>
</ul>
`,
};
};

export default notifyFailedReadOfTemplateData;
4 changes: 2 additions & 2 deletions app/components/Analyst/RFI/RFIAnalystUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const RfiAnalystUpload = ({ query }) => {
useRfiCoverageMapKmzUploadedEmail();

useEffect(() => {
if (templateData?.templateNumber === 1) {
if (templateData?.templateNumber === 1 && !templateData?.error) {
setExcelImportFields([...excelImportFields, 'Template 1']);
const newFormDataWithTemplateOne = {
...newFormData,
Expand All @@ -86,7 +86,7 @@ const RfiAnalystUpload = ({ query }) => {
},
};
setNewFormData(newFormDataWithTemplateOne);
} else if (templateData?.templateNumber === 2) {
} else if (templateData?.templateNumber === 2 && !templateData?.error) {
setExcelImportFields([...excelImportFields, 'Template 2']);
const newFormDataWithTemplateTwo = {
...newFormData,
Expand Down
36 changes: 34 additions & 2 deletions app/components/Form/ApplicationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ const ApplicationForm: React.FC<Props> = ({
}

if (templateData) {
if (templateData.templateNumber === 1) {
if (templateData.templateNumber === 1 && !templateData.error) {
newFormData = {
...newFormData,
benefits: {
Expand All @@ -571,7 +571,7 @@ const ApplicationForm: React.FC<Props> = ({
templateData.data.result.finalEligibleHouseholds,
},
};
} else if (templateData.templateNumber === 2) {
} else if (templateData.templateNumber === 2 && !templateData.error) {
newFormData = {
...newFormData,
budgetDetails: {
Expand All @@ -580,6 +580,38 @@ const ApplicationForm: React.FC<Props> = ({
totalProjectCost: templateData.data.result.totalProjectCosts,
},
};
} else if (templateData.error && templateData.templateNumber === 1) {
fetch(`/api/email/notifyFailedReadOfTemplateData`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
applicationId: rowId,
host: window.location.origin,
params: {
uuid: newFormData.templateUploads
?.eligibilityAndImpactsCalculator?.[0]?.uuid,
uploadedAt:
newFormData.templateUploads
?.eligibilityAndImpactsCalculator?.[0]?.uploadedAt,
templateNumber: templateData.templateNumber,
},
}),
});
} else if (templateData.error && templateData.templateNumber === 2) {
fetch(`/api/email/notifyFailedReadOfTemplateData`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
applicationId: rowId,
host: window.location.origin,
params: {
uuid: newFormData.templateUploads?.detailedBudget?.[0]?.uuid,
uploadedAt:
newFormData.templateUploads?.detailedBudget?.[0]?.uploadedAt,
templateNumber: templateData.templateNumber,
},
}),
});
}
}

Expand Down
36 changes: 23 additions & 13 deletions app/lib/theme/widgets/FileWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,34 @@ const FileWidget: React.FC<FileWidgetProps> = ({
if (file) {
fileFormData.append('file', file);
if (setTemplateData) {
await fetch(
`/api/applicant/template?templateNumber=${templateNumber}`,
{
method: 'POST',
body: fileFormData,
}
).then((response) => {
try {
const response = await fetch(
`/api/applicant/template?templateNumber=${templateNumber}`,
{
method: 'POST',
body: fileFormData,
}
);
if (response.ok) {
response.json().then((data) => {
setTemplateData({
templateNumber,
data,
});
const data = await response.json();
setTemplateData({
templateNumber,
data,
});
} else {
isTemplateValid = false;
setTemplateData({
templateNumber,
error: true,
});
}
});
} catch (error) {
isTemplateValid = false;
setTemplateData({
templateNumber,
error: true,
});
}
}
}
}
Expand Down
44 changes: 42 additions & 2 deletions app/pages/applicantportal/form/[id]/rfi/[applicantRfiId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const ApplicantRfiPage = ({
useRfiCoverageMapKmzUploadedEmail();

useEffect(() => {
if (templateData?.templateNumber === 1) {
if (templateData?.templateNumber === 1 && !templateData.error) {
const newFormDataWithTemplateOne = {
...newFormData,
benefits: {
Expand All @@ -89,7 +89,7 @@ const ApplicantRfiPage = ({
},
};
setNewFormData(newFormDataWithTemplateOne);
} else if (templateData?.templateNumber === 2) {
} else if (templateData?.templateNumber === 2 && !templateData.error) {
const newFormDataWithTemplateTwo = {
...newFormData,
budgetDetails: {
Expand All @@ -99,6 +99,46 @@ const ApplicantRfiPage = ({
},
};
setNewFormData(newFormDataWithTemplateTwo);
} else if (templateData?.error && templateData?.templateNumber === 1) {
const fileArrayLength =
newFormData.templateUploads?.eligibilityAndImpactsCalculator?.length;
fetch(`/api/email/notifyFailedReadOfTemplateData`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
applicationId,
host: window.location.origin,
params: {
templateNumber: templateData.templateNumber,
uuid: newFormData.templateUploads
?.eligibilityAndImpactsCalculator?.[fileArrayLength - 1]?.uuid,
uploadedAt:
newFormData.templateUploads?.eligibilityAndImpactsCalculator?.[
fileArrayLength - 1
]?.uploadedAt,
},
}),
});
} else if (templateData?.error && templateData?.templateNumber === 2) {
const fileArrayLength =
newFormData.templateUploads?.detailedBudget?.length;
fetch(`/api/email/notifyFailedReadOfTemplateData`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
applicationId,
host: window.location.origin,
params: {
templateNumber: templateData.templateNumber,
uuid: newFormData.templateUploads?.detailedBudget?.[
fileArrayLength - 1
]?.uuid,
uploadedAt:
newFormData.templateUploads?.detailedBudget?.[fileArrayLength - 1]
?.uploadedAt,
},
}),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [templateData]);
Expand Down
17 changes: 17 additions & 0 deletions app/tests/backend/lib/emails/email.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import householdCountUpdate from 'backend/lib/emails/templates/householdCountUpd
import rfiCoverageMapKmzUploaded from 'backend/lib/emails/templates/rfiCoverageMapKmzUploaded';
import notifyConditionallyApproved from 'backend/lib/emails/templates/notifyConditionallyApproved';
import notifyApplicationSubmission from 'backend/lib/emails/templates/notifyApplicationSubmission';
import notifyFailedReadOfTemplateData from 'backend/lib/emails/templates/notifyFailedReadOfTemplateData';

jest.mock('backend/lib/emails/handleEmailNotification');

Expand Down Expand Up @@ -210,4 +211,20 @@ describe('Email API Endpoints', () => {
{}
);
});

it('calls notifyFailedReadOfTemplateData with correct parameters once notifyFailedReadOfTemplateData called', async () => {
const reqBody = {
applicationId: '',
params: {},
};
await request(app)
.post('/api/email/notifyFailedReadOfTemplateData')
.send(reqBody);
expect(handleEmailNotification).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
notifyFailedReadOfTemplateData,
{}
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import notifyFailedReadOfTemplateData from 'backend/lib/emails/templates/notifyFailedReadOfTemplateData';

describe('notifyApplicationSubmission template', () => {
it('should return an email template with correct properties', () => {
const applicationId = '1';
const url = 'http://mock_host.ca';

const emailTemplate = notifyFailedReadOfTemplateData(
applicationId,
url,
{},
{ templateNumber: 1 }
);

expect(emailTemplate).toEqual(
expect.objectContaining({
emailTo: [111, 112, 113, 114, 115],
emailCC: [],
tag: 'failed-read-of-template-data',
subject: 'Template 1 - Failed Response',
body: expect.anything(),
})
);
});

it('should format parameters in body', () => {
const applicationId = '321';
const url = 'http://mock_host.ca';

const emailTemplate = notifyFailedReadOfTemplateData(
applicationId,
url,
{},
{ uuid: '123', templateNumber: 1, uploadedAt: 'asdf' }
);

expect(emailTemplate.body).toContain(`Environment: Dev`);
expect(emailTemplate.body).toContain(`Application ID: 321`);
expect(emailTemplate.body).toContain(`File UUID: 123`);
expect(emailTemplate.body).toContain(`Template Number: 1`);
expect(emailTemplate.body).toContain(`a failed response at asdf`);
});
});
Loading

0 comments on commit adc077b

Please sign in to comment.