Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TTAHUB-3095] Don't remove expired grants from the recipients dropdown when an AR is unlocked #2265

Merged
merged 11 commits into from
Jul 15, 2024
Merged
6 changes: 6 additions & 0 deletions frontend/src/fetchers/activityReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ export const getRecipients = async (region) => {
return recipients.json();
};

export const getRecipientsForExistingAR = async (region, reportId) => {
const url = join(activityReportUrl, 'activity-recipients/', `${reportId}`, '/', `?region=${region}`);
const recipients = await get(url);
return recipients.json();
};

export const getGoals = async (grantIds) => {
const params = grantIds.map((grantId) => `grantIds=${grantId}`);
const url = join(activityReportUrl, 'goals', `?${params.join('&')}`);
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/pages/ActivityReport/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('ActivityReport', () => {

beforeEach(() => {
fetchMock.get('/api/activity-reports/activity-recipients?region=1', recipients);
fetchMock.get('/api/activity-reports/activity-recipients/1/?region=1', recipients);
fetchMock.get('/api/activity-reports/groups?region=1', [{
id: 110,
name: 'Group 1',
Expand Down Expand Up @@ -155,6 +156,7 @@ describe('ActivityReport', () => {
};

fetchMock.get('/api/activity-reports/activity-recipients?region=1', groupRecipients, { overwriteRoutes: true });
fetchMock.get('/api/activity-reports/activity-recipients/1/?region=1', groupRecipients, { overwriteRoutes: true });

const data = formData();
fetchMock.get('/api/activity-reports/1', { ...data, activityRecipients: [] });
Expand Down Expand Up @@ -226,6 +228,7 @@ describe('ActivityReport', () => {
};

fetchMock.get('/api/activity-reports/activity-recipients?region=1', groupRecipients, { overwriteRoutes: true });
fetchMock.get('/api/activity-reports/activity-recipients/1/?region=1', groupRecipients, { overwriteRoutes: true });

const data = formData();
fetchMock.get('/api/activity-reports/1', { ...data, activityRecipients: [] });
Expand Down Expand Up @@ -363,6 +366,7 @@ describe('ActivityReport', () => {

describe('resetToDraft', () => {
it('navigates to the correct page', async () => {
fetchMock.get('/api/activity-reports/activity-recipients/3/?region=1', recipients);
const data = formData();
// load the report
fetchMock.get('/api/activity-reports/3', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('Local storage fallbacks', () => {

beforeEach(() => {
fetchMock.get('/api/activity-reports/activity-recipients?region=1', recipients);
fetchMock.get('/api/activity-reports/activity-recipients/1/?region=1', recipients);
fetchMock.get('/api/activity-reports/groups?region=1', []);
fetchMock.get('/api/users/collaborators?region=1', []);
fetchMock.get('/api/activity-reports/approvers?region=1', []);
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/pages/ActivityReport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ import {
submitReport,
saveReport,
getReport,
getRecipients,
getRecipientsForExistingAR,
createReport,
getCollaborators,
getApprovers,
reviewReport,
resetToDraft,
getGroupsForActivityReport,
getRecipients,
} from '../../fetchers/activityReports';
import useLocalStorage, { setConnectionActiveWithError } from '../../hooks/useLocalStorage';
import NetworkContext, { isOnlineMode } from '../../NetworkContext';
Expand Down Expand Up @@ -277,8 +278,16 @@ function ActivityReport({
};
}

const getRecips = async () => {
if (reportId.current && reportId.current !== 'new') {
return getRecipientsForExistingAR(report.regionId, reportId.current);
}

return getRecipients(report.regionId);
};

const apiCalls = [
getRecipients(report.regionId),
getRecips(),
getCollaborators(report.regionId),
getApprovers(report.regionId),
getGroupsForActivityReport(report.regionId),
Expand Down
8 changes: 8 additions & 0 deletions src/routes/activityReports/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,14 @@ export async function getActivityRecipients(req, res) {
res.json(activityRecipients);
}

export async function getActivityRecipientsForExistingReport(req, res) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check permissions here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not actually sure. What do you think? What we're potentially exposing (to already-authenticated users) is which grant recipients were previously associated with a given AR. My initial thought is that this isn't sensitive - but maybe I"m wrong

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose without context that info doesn't feel particularly sensitive. The only counterargument is that all the other report-related data is confined to a region. I'm ok with site-access level permissions here, personally. Although, I am not the decider.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up adding AR read permissions to this handler: 440a55a

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good. One thing to consider is that if you are fetching the report, you no longer need to rely on the region ID being passed as a query parameter, since the report has a regionID.

const { region } = req.query;
const { reportId } = req.params;
const targetRegion = parseInt(region, DECIMAL_BASE);
const activityRecipients = await possibleRecipients(targetRegion, reportId);
res.json(activityRecipients);
}

/**
* Retrieve an activity report
*
Expand Down
2 changes: 2 additions & 0 deletions src/routes/activityReports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getReports,
getReportAlerts,
getActivityRecipients,
getActivityRecipientsForExistingReport,
getGoals,
reviewReport,
resetToDraft,
Expand Down Expand Up @@ -40,6 +41,7 @@ router.post('/', transactionWrapper(createReport));
router.get('/approvers', transactionWrapper(getApprovers));
router.get('/groups', transactionWrapper(getGroups));
router.get('/activity-recipients', transactionWrapper(getActivityRecipients));
router.get('/activity-recipients/:reportId', transactionWrapper(getActivityRecipientsForExistingReport));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be a useless change, but how do we feel about flipping this to be /:reportId/activity-recipients (i.e /api/activity-reports/1/activity-recipients)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.. I had this same thought. I'll make this change 👍

router.get('/goals', transactionWrapper(getGoals));
router.post('/goals', transactionWrapper(createGoalsForReport));
router.post('/objectives', transactionWrapper(saveOtherEntityObjectivesForReport));
Expand Down
56 changes: 39 additions & 17 deletions src/services/activityReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ActivityReportCollaborator,
ActivityReportFile,
sequelize,
Sequelize,
ActivityRecipient,
File,
Grant,
Expand Down Expand Up @@ -1125,33 +1126,54 @@ export async function setStatus(report, status) {
* @param {number} [regionId] - A region id to query against
* @returns {*} Grants and Other entities
*/
export async function possibleRecipients(regionId) {
const where = { status: 'Active', regionId };

export async function possibleRecipients(regionId, activityReportId = null) {
const grants = await Recipient.findAll({
attributes: ['id', 'name'],
attributes: [
'id',
'name',
],
order: ['name'],
include: [{
where,
model: Grant,
as: 'grants',
attributes: [['id', 'activityRecipientId'], 'name', 'number'],
include: [{
model: Recipient,
as: 'recipient',
},
include: [
{
model: Program,
as: 'programs',
attributes: ['programType'],
model: Grant,
as: 'grants',
attributes: ['number', ['id', 'activityRecipientId'], 'name'],
required: true,
include: [
{
model: Program,
as: 'programs',
attributes: ['programType'],
required: false,
},
{
model: Recipient,
as: 'recipient',
required: true,
},
{
model: ActivityRecipient,
as: 'activityRecipients',
attributes: [],
required: false,
},
],
},
],
where: {
'$grants.regionId$': regionId,
[Op.or]: [
{ '$grants.status$': 'Active' },
{ '$grants->activityRecipients.activityReportId$': activityReportId },
],
}],
},
});

const otherEntities = await OtherEntity.findAll({
raw: true,
attributes: [['id', 'activityRecipientId'], 'name'],
});

return { grants, otherEntities };
}

Expand Down