Skip to content

Commit

Permalink
Merge pull request #3544 from bcgov/NDT-421-Email-notification-System…
Browse files Browse the repository at this point in the history
…-for-Analysts-Assigned-to-Financial-Assessments

chore: enable financial Risk assessments to notifiy assignees
  • Loading branch information
RRanath authored Sep 12, 2024
2 parents 75ef1ff + afc5cde commit 3b021a0
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 69 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## [1.190.11](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.190.10...v1.190.11) (2024-09-12)

## [1.190.10](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.190.9...v1.190.10) (2024-09-10)

## [1.190.9](https://github.com/bcgov/CONN-CCBC-portal/compare/v1.190.8...v1.190.9) (2024-09-06)
Expand Down
15 changes: 2 additions & 13 deletions app/backend/lib/emails/templates/assesmentSecondReviewChange.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import ASSESSMENT_TYPES from '../../../../data/assessmentTypes';
import {
EmailTemplate,
EmailTemplateProvider,
} from '../handleEmailNotification';

const formats = {
projectManagement: {
type: 'Project Management assessment',
slug: 'project-management',
},
permitting: { type: 'Permitting assessment', slug: 'permitting' },
technical: { type: 'Technical assessment', slug: 'technical' },
gis: { type: 'GIS assessment', slug: 'gis' },
financialRisk: { type: 'Financial Risk assessment', slug: 'financial-risk' },
screening: { type: 'Eligibility Screening', slug: 'screening' },
};

const assesmentSecondReviewChange: EmailTemplateProvider = (
applicationId: string,
url: string,
initiator: any,
params: any
): EmailTemplate => {
const { ccbcNumber, assessmentType } = params;
const { type, slug } = formats[assessmentType];
const { type, slug } = ASSESSMENT_TYPES[assessmentType];

return {
emailTo: [34, 71], // Temporary IDs to handle email recipients
Expand Down
8 changes: 4 additions & 4 deletions app/backend/lib/emails/templates/assessmentAssigneeChange.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Context } from 'backend/lib/ches/sendEmailMerge';

import ASSESSMENT_TYPES from '../../../../data/assessmentTypes';
import {
EmailTemplate,
EmailTemplateProvider,
Expand Down Expand Up @@ -77,8 +77,8 @@ const assessmentAssigneeChange: EmailTemplateProvider = async (
([assignor, assignments]) => {
const alerts = (assignments as Array<any>).map((assignment) => {
return {
url: `${url}/analyst/application/${assignment.applicationId}/assessments/${assignment.assessmentType}`,
type: assignment.assessmentType,
url: `${url}/analyst/application/${assignment.applicationId}/assessments/${ASSESSMENT_TYPES[assignment.assessmentType].slug}`,
type: ASSESSMENT_TYPES[assignment.assessmentType].type,
ccbcNumber: assignment.ccbcNumber,
applicationId: assignment.applicationId,
};
Expand Down Expand Up @@ -121,7 +121,7 @@ const assessmentAssigneeChange: EmailTemplateProvider = async (
body: `{% for action in actions %}
{{ action.assignors }} has assigned you the following assessment(s):
<ul>{% for alert in action.alerts %}
<li><a href='{{ alert.url }}'>{{ alert.type | capitalize }}</a> for {{ alert.ccbcNumber }}</li>
<li><a href='{{ alert.url }}'>{{ alert.type }}</a> for {{ alert.ccbcNumber }}</li>
{% endfor %}</ul>
{% endfor %}`,
contexts,
Expand Down
123 changes: 77 additions & 46 deletions app/components/AnalystDashboard/AssessmentAssignmentTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { graphql, useFragment } from 'react-relay';
import styled from 'styled-components';
import cookie from 'js-cookie';
Expand Down Expand Up @@ -148,6 +148,18 @@ const findAssessment = (assessments, assessmentType) => {
};
};

const findNotification = (notifications, notificationType) => {
const data = notifications.find(
({ node }) => node?.notificationType === notificationType
);

return {
jsonData: data?.node?.jsonData,
notificationType,
createdAt: data?.node?.createdAt,
};
};

const StyledLink = styled.a`
color: ${(props) => props.theme.color.links};
text-decoration: none;
Expand Down Expand Up @@ -235,11 +247,7 @@ const AssessmentAssignmentTable: React.FC<Props> = ({ query }) => {
}
}
}
notificationsByApplicationId(
orderBy: CREATED_AT_DESC
first: 1
condition: { notificationType: "assignment_technical" }
) {
assessmentNotifications {
__id
edges {
node {
Expand Down Expand Up @@ -429,9 +437,8 @@ const AssessmentAssignmentTable: React.FC<Props> = ({ query }) => {
zones,
allAnalysts,
assessmentConnection: application.allAssessments.__id,
notifications: application.notificationsByApplicationId.edges,
notificationConnectionId:
application.notificationsByApplicationId.__id,
application.assessmentNotifications?.__id,
pmAssessment: findAssessment(
application.allAssessments.edges,
'projectManagement'
Expand All @@ -440,6 +447,10 @@ const AssessmentAssignmentTable: React.FC<Props> = ({ query }) => {
application.allAssessments.edges,
'technical'
),
techNotification: findNotification(
application.assessmentNotifications.edges,
'assignment_technical'
),
permittingAssessment: findAssessment(
application.allAssessments.edges,
'permitting'
Expand All @@ -456,6 +467,10 @@ const AssessmentAssignmentTable: React.FC<Props> = ({ query }) => {
application.allAssessments.edges,
'financialRisk'
),
financialRiskNotification: findNotification(
application.assessmentNotifications.edges,
'assignment_financialRisk'
),
organizationName,
};
}
Expand All @@ -465,46 +480,62 @@ const AssessmentAssignmentTable: React.FC<Props> = ({ query }) => {
[allApplications, allAnalysts]
);

const getUserEmailByAssignedTo = (assignedTo: string) => {
const analyst = allAnalysts.edges.find(
({ node }) => `${node.givenName} ${node.familyName}` === assignedTo
);
return analyst ? analyst.node.email : null;
};

const assignments = useMemo(
() =>
tableData
.filter((data: any) => {
const lastSentAt = data.notifications[0]?.node?.createdAt
? new Date(data.notifications[0]?.node?.createdAt)
: null;
return new Date(data.techAssessment.updatedAt) >= lastSentAt;
})
.filter(
(data: any) =>
data.techAssessment.jsonData.assignedTo &&
data.techAssessment.jsonData.assignedTo !==
data.notifications[0]?.node?.jsonData?.to
)
.map((data: any) => {
return {
ccbcNumber: data.ccbcNumber,
applicationId: data.applicationId,
notificationConnectionId: data.notificationConnectionId,
updatedBy: data.techAssessment.updatedBy,
updatedAt: data.techAssessment.updatedAt,
assignedTo: data.techAssessment.jsonData?.assignedTo,
assigneeEmail: getUserEmailByAssignedTo(
data.techAssessment.jsonData?.assignedTo
),
assessmentType: 'technical',
};
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[tableData]
const getUserEmailByAssignedTo = useCallback(
(assignedTo: string) => {
const analyst = allAnalysts.edges.find(
({ node }) => `${node.givenName} ${node.familyName}` === assignedTo
);
return analyst ? analyst.node.email : null;
},
[allAnalysts]
);

const assignments = useMemo(() => {
const createAssignment = (
application,
assessmentKey,
assessmentType = assessmentKey
) => {
const { updatedAt, jsonData, updatedBy } =
application[`${assessmentKey}Assessment`];
const notification = application[`${assessmentKey}Notification`];
const lastNotificationSentAt = notification?.createdAt
? new Date(notification.createdAt)
: null;

const assessmentChanged =
jsonData?.assignedTo &&
jsonData.assignedTo !== notification?.jsonData?.to;

if (new Date(updatedAt) >= lastNotificationSentAt && assessmentChanged) {
return {
ccbcNumber: application.ccbcNumber,
applicationId: application.applicationId,
notificationConnectionId: application.notificationConnectionId,
updatedBy,
updatedAt,
assignedTo: jsonData.assignedTo,
assigneeEmail: getUserEmailByAssignedTo(jsonData.assignedTo),
assessmentType,
};
}
return null;
};

return tableData.reduce((assignmentsList, application) => {
const techAssignment = createAssignment(application, 'tech', 'technical');
const financialAssignment = createAssignment(
application,
'financialRisk'
);

if (techAssignment) assignmentsList.push(techAssignment);
if (financialAssignment) assignmentsList.push(financialAssignment);

return assignmentsList;
}, []);
}, [getUserEmailByAssignedTo, tableData]);

const columns = useMemo<MRT_ColumnDef<Application>[]>(() => {
// Sonarcloud duplicate lines
const sharedAssessmentCell = {
Expand Down
13 changes: 13 additions & 0 deletions app/data/assessmentTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const ASSESSMENT_TYPES = {
projectManagement: {
type: 'Project Management assessment',
slug: 'project-management',
},
permitting: { type: 'Permitting assessment', slug: 'permitting' },
technical: { type: 'Technical assessment', slug: 'technical' },
gis: { type: 'GIS assessment', slug: 'gis' },
financialRisk: { type: 'Financial Risk assessment', slug: 'financial-risk' },
screening: { type: 'Eligibility Screening', slug: 'screening' },
};

export default ASSESSMENT_TYPES;
26 changes: 26 additions & 0 deletions app/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -29924,6 +29924,32 @@ type Application implements Node {
"""Computed column that takes the slug to return an assessment form"""
assessmentForm(_assessmentDataType: String!): AssessmentData

"""Computed column to get assessment notifications by assessment type"""
assessmentNotifications(
"""Only read the first `n` values of the set."""
first: Int

"""Only read the last `n` values of the set."""
last: Int

"""
Skip the first `n` values from our `after` cursor, an alternative to cursor
based pagination. May not be used with `last`.
"""
offset: Int

"""Read all values in the set before (above) this cursor."""
before: Cursor

"""Read all values in the set after (below) this cursor."""
after: Cursor

"""
A filter to be used in determining which values should be returned by the collection.
"""
filter: NotificationFilter
): NotificationsConnection!

"""Computed column to return conditional approval data"""
conditionalApproval: ConditionalApprovalData

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('assessmentAssigneeChange template', () => {
},
{
applicationId: 3,
assessmentType: 'technical',
assessmentType: 'financialRisk',
assignedTo: 'Tester 2',
assigneeEmail: 'tester2@mail.com',
ccbcNumber: 'CCBC-000003',
Expand Down Expand Up @@ -93,7 +93,7 @@ describe('assessmentAssigneeChange template', () => {
{
ccbcNumber: 'CCBC-000001',
applicationId: 1,
type: 'technical',
type: 'Technical assessment',
url: 'http://mock_host.ca/analyst/application/1/assessments/technical',
},
],
Expand All @@ -106,7 +106,7 @@ describe('assessmentAssigneeChange template', () => {
{
ccbcNumber: 'CCBC-000002',
applicationId: 2,
type: 'technical',
type: 'Technical assessment',
url: 'http://mock_host.ca/analyst/application/2/assessments/technical',
},
],
Expand All @@ -117,8 +117,8 @@ describe('assessmentAssigneeChange template', () => {
{
ccbcNumber: 'CCBC-000003',
applicationId: 3,
type: 'technical',
url: 'http://mock_host.ca/analyst/application/3/assessments/technical',
type: 'Financial Risk assessment',
url: 'http://mock_host.ca/analyst/application/3/assessments/financial-risk',
},
],
assignors: 'Assignor 3',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Deploy ccbc:computed_columns/application_assessment_notifications to pg

BEGIN;

create or replace function ccbc_public.application_assessment_notifications(application ccbc_public.application) returns setof ccbc_public.notification as
$$
select distinct on (notification_type) *
from ccbc_public.notification
where application_id = application_id
ORDER BY notification_type, created_at DESC;
$$ language sql stable;

grant execute on function ccbc_public.application_assessment_notifications to ccbc_analyst;
grant execute on function ccbc_public.application_assessment_notifications to ccbc_admin;
grant execute on function ccbc_public.application_assessment_notifications to ccbc_auth_user;

comment on function ccbc_public.application_assessment_notifications is 'Computed column to get assessment notifications by assessment type';

COMMIT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

BEGIN;

drop function ccbc_public.application_assessment_notifications;

COMMIT;
2 changes: 2 additions & 0 deletions db/sqitch.plan
Original file line number Diff line number Diff line change
Expand Up @@ -668,3 +668,5 @@ tables/communities_source_data_001_service_account 2024-08-28T16:32:48Z Rafael S
@1.190.8 2024-09-04T23:08:50Z CCBC Service Account <ccbc@button.is> # release v1.190.8
@1.190.9 2024-09-06T20:13:21Z CCBC Service Account <ccbc@button.is> # release v1.190.9
@1.190.10 2024-09-10T23:10:53Z CCBC Service Account <ccbc@button.is> # release v1.190.10
computed_columns/application_assessment_notifications 2024-09-06T21:58:13Z ,,, <ryohani89@NH504670> # Add notifications by assessment type field
@1.190.11 2024-09-12T18:13:42Z CCBC Service Account <ccbc@button.is> # release v1.190.11
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "CONN-CCBC-portal",
"version": "1.190.10",
"version": "1.190.11",
"main": "index.js",
"repository": "https://github.com/bcgov/CONN-CCBC-portal.git",
"author": "Romer, Meherzad CITZ:EX <Meherzad.Romer@gov.bc.ca>",
Expand Down

0 comments on commit 3b021a0

Please sign in to comment.