diff --git a/.circleci/config.yml b/.circleci/config.yml
index 738a9c1490..40caefd886 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -427,7 +427,7 @@ parameters:
default: "lr/ttahub-1521/display-regions-in-AR-header"
type: string
sandbox_git_branch: # change to feature branch to test deployment
- default: "al-ttahub-2726-goals-and-objectives-review-redesign"
+ default: "al-ttahub-add-fei-root-cause-to-review"
type: string
prod_new_relic_app_id:
default: "877570491"
diff --git a/frontend/src/components/ReadOnlyContent.scss b/frontend/src/components/ReadOnlyContent.scss
index 6fc81d57e4..34cb0b2d77 100644
--- a/frontend/src/components/ReadOnlyContent.scss
+++ b/frontend/src/components/ReadOnlyContent.scss
@@ -31,10 +31,11 @@
// print styles
@media print{
- .ttahub-read-only-content-section {
- padding: 0;
- margin-bottom: 2rem;
- page-break-inside: avoid;
+ .ttahub-read-only-content-section,
+ .ttahub-read-only-content-section h3 {
+ padding: 0 !important;
+ margin-bottom: 2rem !important;
+ page-break-inside: avoid !important;
}
}
diff --git a/frontend/src/components/filter/__tests__/goalFilters.js b/frontend/src/components/filter/__tests__/goalFilters.js
index 17bd778001..f95b17ebc9 100644
--- a/frontend/src/components/filter/__tests__/goalFilters.js
+++ b/frontend/src/components/filter/__tests__/goalFilters.js
@@ -109,6 +109,7 @@ describe('goalFilters', () => {
const grantFilter = grantNumberFilter([{
numberWithProgramTypes: 'number EHS',
number: 'number',
+ status: 'Active',
}]);
const grantFilterWithNoPossibleGrantsYet = grantNumberFilter([]);
@@ -139,7 +140,7 @@ describe('goalFilters', () => {
const apply = jest.fn();
renderFilter(() => grantFilter.renderInput('1', 'test', [], apply));
const grantNumberInput = await screen.findByLabelText('Select grant numbers to filter by');
- await selectEvent.select(grantNumberInput, ['number EHS']);
+ await selectEvent.select(grantNumberInput, ['number EHS - Active']);
expect(apply).toHaveBeenCalled();
});
});
diff --git a/frontend/src/components/filter/goalFilters.js b/frontend/src/components/filter/goalFilters.js
index c56fec5df3..85ffd57a27 100644
--- a/frontend/src/components/filter/goalFilters.js
+++ b/frontend/src/components/filter/goalFilters.js
@@ -149,7 +149,7 @@ export const grantNumberFilter = (possibleGrants) => ({
labelText="Select grant numbers to filter by"
options={possibleGrants.map((g) => ({
value: g.number,
- label: g.numberWithProgramTypes,
+ label: `${g.numberWithProgramTypes} - ${g.status}`,
}))}
selectedValues={query}
mapByValue
diff --git a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js
index 1ac97379c2..2c1bcd41cd 100644
--- a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js
+++ b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js
@@ -1,4 +1,6 @@
import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
import { v4 as uuidv4 } from 'uuid';
import { useFormContext } from 'react-hook-form';
import { reportIsEditable } from '../../../../utils';
@@ -24,6 +26,8 @@ const RecipientReviewSection = () => {
items: [
{ label: 'Recipient\'s goal', name: 'name' },
{ label: 'Goal source', name: 'source' },
+ { label: 'Goal number', name: 'goalNumber' },
+ { label: 'Root cause', name: 'promptsForReview' },
{
label: 'Anticipated close date', name: 'endDate',
},
@@ -56,8 +60,50 @@ const RecipientReviewSection = () => {
},
];
- const buildGoalReview = (goal) => goalSection[0].items.map((item) => (
- <>
+ const buildFeiRootCauseReviewSection = (item, goal) => {
+ const promptsForReview = goal.promptsForReview || [];
+ return (promptsForReview.length > 0 && (
+
+ {promptsForReview.map((v) => (
+ <>
+
+ {item.label}
+
+
+
+ {
+ v.responses.length
+ ? v.responses.join(', ')
+ : (
+
+
+ {' '}
+ Missing Information
+
+ )
+ }
+
+
+
+ {v.recipients.map((r) => (
+ - {r.name}
+ ))}
+
+
+
+ >
+ ))}
+
+ )
+ );
+ };
+
+ const buildGoalReview = (goal) => goalSection[0].items.map((item) => {
+ if (item.label === 'Root cause') {
+ return buildFeiRootCauseReviewSection(item, goal);
+ }
+
+ return (
{
sortValues={item.sort}
customValue={goal}
/>
- >
- ));
+ );
+ });
const buildObjectiveReview = (objectives, isLastGoal) => {
const returnObjectives = objectives.map(
diff --git a/frontend/src/pages/ActivityReport/Pages/components/__tests__/RecipientReviewSection.js b/frontend/src/pages/ActivityReport/Pages/components/__tests__/RecipientReviewSection.js
index 7725c232e7..1c1f35f269 100644
--- a/frontend/src/pages/ActivityReport/Pages/components/__tests__/RecipientReviewSection.js
+++ b/frontend/src/pages/ActivityReport/Pages/components/__tests__/RecipientReviewSection.js
@@ -187,4 +187,55 @@ describe('RecipientReviewSection', () => {
expect(screen.queryAllByText(/Resource links/i).length).toBe(3);
expect(screen.queryAllByText(/Resource attachments/i).length).toBe(3);
});
+
+ it('renders fei response correctly', async () => {
+ RenderReviewSection([{
+ ...defaultGoalsAndObjectives[0],
+ promptsForReview: [
+ {
+ key: 'Root Cause 1',
+ promptId: 1,
+ prompt: 'Prompt 1',
+ recipients: [{ id: 1, name: 'Recipient 1' }, { id: 2, name: 'Recipient 2' }],
+ responses: ['Response 1', 'Response 2'],
+ },
+ {
+ key: 'Root Cause 2',
+ promptId: 1,
+ prompt: 'Prompt 2',
+ recipients: [{ id: 3, name: 'Recipient 3' }, { id: 4, name: 'Recipient 4' }],
+ responses: ['Response 3'],
+ },
+ {
+ key: 'Root Cause 3',
+ promptId: 1,
+ prompt: 'Prompt 3',
+ recipients: [{ id: 3, name: 'Recipient 5' }],
+ responses: [],
+ },
+ ],
+ }]);
+
+ // Assert generic goal information is displayed.
+ expect(screen.queryAllByText(/Goal summary/i).length).toBe(1);
+ expect(screen.getByText(/this is my 1st goal title/i)).toBeInTheDocument();
+
+ // Expect the text 'Root cause' to be displayed 3 times.
+ expect(screen.queryAllByText(/Root cause/i).length).toBe(3);
+
+ // Assert Response 1 and Response 2 are displayed.
+ expect(screen.getByText(/response 1, response 2/i)).toBeInTheDocument();
+
+ // Assert Response 3 is displayed.
+ expect(screen.getByText('Response 3')).toBeInTheDocument();
+
+ // Assert that the correct number of recipients are displayed.
+ expect(screen.queryAllByText(/Recipient 1/).length).toBe(1);
+ expect(screen.queryAllByText(/Recipient 2/).length).toBe(1);
+ expect(screen.queryAllByText(/Recipient 3/).length).toBe(1);
+ expect(screen.queryAllByText(/Recipient 5/).length).toBe(1);
+
+ // Assert 'Missing information' is displayed once.
+ expect(screen.queryAllByText(/Missing Information/).length).toBe(1);
+ });
});
diff --git a/similarity_api/src/requirements.txt b/similarity_api/src/requirements.txt
index 89a46b2912..ec0b0315f2 100644
--- a/similarity_api/src/requirements.txt
+++ b/similarity_api/src/requirements.txt
@@ -26,7 +26,7 @@ psycopg2==2.9.7
pydantic==2.4.0
pydantic-core==2.10.0
requests==2.32.0
-scikit-learn==1.3.0
+scikit-learn==1.5.0
scipy==1.11.1
smart-open==6.3.0
spacy==3.6.1
@@ -39,7 +39,7 @@ threadpoolctl==3.2.0
tqdm==4.66.3
typer==0.9.0
typing_extensions==4.7.1
-urllib3==2.0.7
+urllib3==2.2.2
wasabi==1.1.2
Werkzeug==3.0.3
zipp==3.16.2
diff --git a/src/goalServices/getFeiGoalsForReport.test.js b/src/goalServices/getFeiGoalsForReport.test.js
new file mode 100644
index 0000000000..3f72c7874f
--- /dev/null
+++ b/src/goalServices/getFeiGoalsForReport.test.js
@@ -0,0 +1,345 @@
+import faker from '@faker-js/faker';
+import { REPORT_STATUSES, SUPPORT_TYPES } from '@ttahub/common';
+import getGoalsForReport from './getGoalsForReport';
+import {
+ Goal,
+ ActivityReport,
+ ActivityReportGoal,
+ User,
+ GoalTemplateFieldPrompt,
+ GoalFieldResponse,
+ Grant,
+ Recipient,
+ sequelize,
+} from '../models';
+import {
+ createGoal,
+ createGoalTemplate,
+ createRecipient,
+ createGrant,
+} from '../testUtils';
+import { CREATION_METHOD, GOAL_STATUS } from '../constants';
+
+describe('getFeiGoalsForReport', () => {
+ let user;
+ let report;
+
+ let goalOne;
+ let goalTwo;
+ let goalThree;
+ let goalFour; // Missing root causes.
+ let goalFive; // Missing root causes.
+ let recipientOne;
+ let recipientTwo;
+ let recipientThree;
+ let recipientFour;
+ let recipientFive;
+ let activeGrantOne;
+ let activeGrantTwo;
+ let activeGrantThree;
+ let activeGrantFour;
+ let activeGrantFive;
+
+ let template;
+ let prompt;
+ beforeAll(async () => {
+ // Create User.
+ const userName = faker.random.word();
+
+ // User.
+ user = await User.create({
+ id: faker.datatype.number({ min: 1000 }),
+ homeRegionId: 1,
+ name: userName,
+ hsesUsername: userName,
+ hsesUserId: userName,
+ lastLogin: new Date(),
+ });
+
+ // Recipients.
+ recipientOne = await createRecipient();
+ recipientTwo = await createRecipient();
+ recipientThree = await createRecipient();
+ recipientFour = await createRecipient();
+ recipientFive = await createRecipient();
+
+ // Grants.
+ activeGrantOne = await createGrant({
+ recipientId: recipientOne.id,
+ status: 'Active',
+ });
+
+ activeGrantTwo = await createGrant({
+ recipientId: recipientTwo.id,
+ status: 'Active',
+ });
+
+ activeGrantThree = await createGrant({
+ recipientId: recipientThree.id,
+ status: 'Active',
+ });
+
+ activeGrantFour = await createGrant({
+ recipientId: recipientFour.id,
+ status: 'Active',
+ });
+
+ activeGrantFive = await createGrant({
+ recipientId: recipientFive.id,
+ status: 'Active',
+ });
+
+ // Template.
+ template = await createGoalTemplate({
+ name: faker.lorem.sentence(),
+ creationMethod: CREATION_METHOD.CURATED,
+ });
+
+ // Goals.
+ const feiGoalText = faker.lorem.sentence();
+ goalOne = await createGoal({
+ status: GOAL_STATUS.IN_PROGRESS,
+ name: feiGoalText,
+ grantId: activeGrantOne.id,
+ goalTemplateId: template.id,
+ });
+
+ goalTwo = await createGoal({
+ status: GOAL_STATUS.IN_PROGRESS,
+ name: feiGoalText,
+ grantId: activeGrantTwo.id,
+ goalTemplateId: template.id,
+ });
+
+ goalThree = await createGoal({
+ status: GOAL_STATUS.IN_PROGRESS,
+ name: feiGoalText,
+ grantId: activeGrantThree.id,
+ goalTemplateId: template.id,
+ });
+
+ goalFour = await createGoal({
+ status: GOAL_STATUS.IN_PROGRESS,
+ name: feiGoalText,
+ grantId: activeGrantFour.id,
+ goalTemplateId: template.id,
+ });
+
+ goalFive = await createGoal({
+ status: GOAL_STATUS.IN_PROGRESS,
+ name: feiGoalText,
+ grantId: activeGrantFive.id,
+ goalTemplateId: template.id,
+ });
+
+ // Prompt.
+ prompt = await GoalTemplateFieldPrompt.create({
+ goalTemplateId: template.id,
+ ordinal: 1,
+ title: faker.lorem.sentence(),
+ prompt: faker.lorem.sentence(),
+ type: 'text',
+ hint: faker.lorem.sentence(),
+ caution: faker.lorem.sentence(),
+ options: [],
+ });
+
+ // Goal Response.
+ await GoalFieldResponse.create({
+ goalTemplateFieldPromptId: prompt.id,
+ goalId: goalOne.id,
+ response: ['response 1', 'response 2'],
+ onAR: true,
+ onApprovedAR: false,
+ });
+
+ await GoalFieldResponse.create({
+ goalTemplateFieldPromptId: prompt.id,
+ goalId: goalTwo.id,
+ response: ['response 1', 'response 2'],
+ onAR: true,
+ onApprovedAR: false,
+ });
+
+ await GoalFieldResponse.create({
+ goalTemplateFieldPromptId: prompt.id,
+ goalId: goalThree.id,
+ response: ['response 4'],
+ onAR: true,
+ onApprovedAR: false,
+ });
+
+ // create report
+ report = await ActivityReport.create({
+ activityRecipientType: 'recipient',
+ submissionStatus: REPORT_STATUSES.DRAFT,
+ userId: user.id,
+ regionId: 1,
+ lastUpdatedById: user.id,
+ ECLKCResourcesUsed: ['test'],
+ activityRecipients: [
+ { activityRecipientId: recipientOne.id },
+ { activityRecipientId: recipientTwo.id },
+ { activityRecipientId: recipientThree.id },
+ { activityRecipientId: recipientFour.id },
+ { activityRecipientId: recipientFive.id }],
+ version: 2,
+ });
+
+ // ActivityReportGoals.
+ await ActivityReportGoal.create({
+ activityReportId: report.id,
+ goalId: goalOne.id,
+ isActivelyEdited: false,
+ });
+
+ await ActivityReportGoal.create({
+ activityReportId: report.id,
+ goalId: goalTwo.id,
+ isActivelyEdited: false,
+ });
+
+ await ActivityReportGoal.create({
+ activityReportId: report.id,
+ goalId: goalThree.id,
+ isActivelyEdited: false,
+ });
+
+ await ActivityReportGoal.create({
+ activityReportId: report.id,
+ goalId: goalFour.id,
+ isActivelyEdited: false,
+ });
+
+ await ActivityReportGoal.create({
+ activityReportId: report.id,
+ goalId: goalFive.id,
+ isActivelyEdited: false,
+ });
+ });
+ afterAll(async () => {
+ // Delete ActivityReportGoals.
+ await ActivityReportGoal.destroy({
+ where: {
+ activityReportId: report.id,
+ },
+ });
+
+ // Delete ActivityReport.
+ await ActivityReport.destroy({
+ where: {
+ id: report.id,
+ },
+ });
+
+ // Delete GoalFieldResponses.
+ await GoalFieldResponse.destroy({
+ where: {
+ goalId: [goalOne.id, goalTwo.id, goalThree.id, goalFour.id, goalFive.id],
+ },
+ });
+
+ // Delete GoalTemplateFieldPrompts.
+ await GoalTemplateFieldPrompt.destroy({
+ where: {
+ goalTemplateId: template.id,
+ },
+ });
+
+ // Delete template.
+ await Goal.destroy({
+ where: {
+ id: template.id,
+ },
+ });
+
+ // Delete Goals.
+ await Goal.destroy({
+ where: {
+ id: [goalOne.id, goalTwo.id, goalThree.id, goalFour.id, goalFive.id],
+ },
+ force: true,
+ });
+
+ // Delete Grants.
+ await Grant.destroy({
+ where: {
+ id: [
+ activeGrantOne.id,
+ activeGrantTwo.id,
+ activeGrantThree.id,
+ activeGrantFour.id,
+ activeGrantFive.id,
+ ],
+ },
+ individualHooks: true,
+ force: true,
+ });
+
+ // Delete Recipients.
+ await Recipient.destroy({
+ where: {
+ id: [
+ recipientOne.id,
+ recipientTwo.id,
+ recipientThree.id,
+ recipientFour.id,
+ recipientFive.id,
+ ],
+ },
+ force: true,
+ });
+
+ // Delete User.
+ await User.destroy({
+ where: {
+ id: user.id,
+ },
+ });
+ await sequelize.close();
+ });
+
+ it('returns the correct number of goals and objectives', async () => {
+ const goalsForReport = await getGoalsForReport(report.id);
+ expect(goalsForReport).toHaveLength(1);
+ expect(goalsForReport[0].promptsForReview).toHaveLength(3);
+
+ // Check if the recipients are in the correct grant.
+ const assertRecipients = goalsForReport[0].promptsForReview.filter((g) => g.responses.includes('response 1') && g.responses.includes('response 2'));
+ expect(assertRecipients.length).toBe(1);
+ expect(assertRecipients[0].recipients.length).toBe(2);
+
+ // Recipient 1.
+ const recipient1 = assertRecipients[0].recipients.filter((r) => r.id === recipientOne.id);
+ expect(recipient1.length).toBe(1);
+ expect(recipient1[0].name).toBe(`${recipientOne.name} - ${activeGrantOne.number}`);
+
+ // Recipient 2.
+ const recipient2 = assertRecipients[0].recipients.filter((r) => r.id === recipientTwo.id);
+ expect(recipient2.length).toBe(1);
+ expect(recipient2[0].name).toBe(`${recipientTwo.name} - ${activeGrantTwo.number}`);
+
+ // Check if the recipients are in the correct grant.
+ const assertRecipients2 = goalsForReport[0].promptsForReview.filter((g) => g.responses.includes('response 4'));
+ expect(assertRecipients2.length).toBe(1);
+
+ // Recipient 3.
+ const recipient3 = assertRecipients2[0].recipients.filter((r) => r.id === recipientThree.id);
+ expect(recipient3.length).toBe(1);
+ expect(recipient3[0].name).toBe(`${recipientThree.name} - ${activeGrantThree.number}`);
+
+ // Recipients missing responses.
+ const assertRecipients3 = goalsForReport[0].promptsForReview.filter(
+ (g) => g.responses.length === 0,
+ );
+
+ // Recipient 4 and Recipient 5 (no responses).
+ const recipient4 = assertRecipients3[0].recipients.filter((r) => r.id === recipientFour.id);
+ expect(recipient4.length).toBe(1);
+ expect(recipient4[0].name).toBe(`${recipientFour.name} - ${activeGrantFour.number}`);
+
+ const recipient5 = assertRecipients3[0].recipients.filter((r) => r.id === recipientFive.id);
+ expect(recipient5.length).toBe(1);
+ expect(recipient5[0].name).toBe(`${recipientFive.name} - ${activeGrantFive.number}`);
+ });
+});
diff --git a/src/goalServices/getGoalsForReport.ts b/src/goalServices/getGoalsForReport.ts
index 2d0f180904..de882c5dc8 100644
--- a/src/goalServices/getGoalsForReport.ts
+++ b/src/goalServices/getGoalsForReport.ts
@@ -26,6 +26,7 @@ const {
Topic,
Course,
File,
+ Program,
} = db;
export default async function getGoalsForReport(reportId: number) {
@@ -48,7 +49,8 @@ export default async function getGoalsForReport(reportId: number) {
'options', gtfp.options,
'validations', gtfp.validations,
'response', gfr.response,
- 'reportResponse', argfr.response
+ 'reportResponse', argfr.response,
+ 'grantId', "Goal"."grantId"
))
FROM "GoalTemplateFieldPrompts" gtfp
LEFT JOIN "GoalFieldResponses" gfr
@@ -87,6 +89,11 @@ export default async function getGoalsForReport(reportId: number) {
model: Grant,
as: 'grant',
required: true,
+ include: [{
+ model: Program,
+ as: 'programs',
+ attributes: ['programType'],
+ }],
},
{
separate: true,
diff --git a/src/goalServices/reduceGoals.ts b/src/goalServices/reduceGoals.ts
index 6005329501..b599774989 100644
--- a/src/goalServices/reduceGoals.ts
+++ b/src/goalServices/reduceGoals.ts
@@ -13,6 +13,7 @@ import {
IReducedGoal,
IReducedObjective,
IPrompt,
+ IReviewPrompt,
} from './types';
// this is the reducer called when not getting objectives for a report, IE, the RTR table
@@ -351,6 +352,64 @@ function reducePrompts(
}, promptsToReduce);
}
+function reducePromptsForReview(
+ recipientId: number,
+ fullGrantName: string,
+ newPrompts:IPrompt[] = [],
+ previousPrompts:IReviewPrompt[] = [],
+) {
+ const updatedPrompts = [...previousPrompts];
+ newPrompts.forEach((currentPrompt) => {
+ // Get the prompt Id.
+ const promptToUse = currentPrompt.promptId ? currentPrompt : currentPrompt.dataValues;
+ const { promptId, response } = promptToUse;
+
+ if (!response || !response.length) {
+ // Add empty response.
+ const missingPromptKey = `${promptId}-missing`;
+
+ // Check if prompt already exists.
+ const existingPrompt = updatedPrompts.find((pp) => pp.key === missingPromptKey);
+ if (!existingPrompt) {
+ updatedPrompts.push({
+ key: missingPromptKey,
+ promptId,
+ recipients: [{ id: recipientId, name: fullGrantName }],
+ responses: [],
+ });
+ return;
+ }
+ // If missing exists add it.
+ existingPrompt.recipients.push({ id: recipientId, name: fullGrantName });
+ // Sort recipients alphabetically.
+ existingPrompt.recipients.sort((a, b) => a.name.localeCompare(b.name));
+ return;
+ }
+
+ // Sort report responses alphabetically.
+ response.sort();
+
+ // Create a key of promptId and responses.
+ const promptKey = `${promptId}-${response.map((r) => r).join('-')}`;
+ const existingPrompt = updatedPrompts.find((pp) => pp.key === promptKey);
+
+ if (!existingPrompt) {
+ updatedPrompts.push({
+ key: promptKey,
+ promptId,
+ recipients: [{ id: recipientId, name: fullGrantName }],
+ responses: response,
+ });
+ } else {
+ // It exists add this recipient.
+ existingPrompt.recipients.push({ id: recipientId, name: fullGrantName });
+ // Sort recipients alphabetically.
+ existingPrompt.recipients.sort((a, b) => a.name.localeCompare(b.name));
+ }
+ });
+ return updatedPrompts;
+}
+
/**
* Dedupes goals by name + status, as well as objectives by title + status
* @param {Object[]} goals
@@ -425,6 +484,13 @@ export function reduceGoals(
currentValue.dataValues.prompts || [],
(existingGoal.prompts || []) as IPrompt[],
);
+ // This will be for review on the report.
+ existingGoal.promptsForReview = reducePromptsForReview(
+ currentValue.dataValues.grant.recipientId,
+ currentValue.dataValues.grant.recipientNameWithPrograms,
+ currentValue.dataValues.prompts || [],
+ existingGoal.promptsForReview,
+ );
} else {
existingGoal.prompts = {
...existingGoal.prompts,
@@ -461,6 +527,13 @@ export function reduceGoals(
[],
);
+ const promptsForReview = reducePromptsForReview(
+ currentValue.dataValues.grant.recipientId,
+ currentValue.dataValues.grant.recipientNameWithPrograms,
+ currentValue.dataValues.prompts || [],
+ [],
+ );
+
let sourceForRTR: { [key: string]: string };
let sourceForPrompts: { [key: string]: IPrompt[] };
@@ -490,6 +563,7 @@ export function reduceGoals(
createdVia: currentValue.dataValues.createdVia,
source: forReport ? sourceForReport : sourceForRTR,
prompts: forReport ? promptsForReport : sourceForPrompts,
+ promptsForReview: forReport ? promptsForReview : [],
isNew: false,
onAR: currentValue.dataValues.onAR,
onApprovedAR: currentValue.dataValues.onApprovedAR,
diff --git a/src/goalServices/types.ts b/src/goalServices/types.ts
index 72a3ea4430..26eb91fc20 100644
--- a/src/goalServices/types.ts
+++ b/src/goalServices/types.ts
@@ -18,6 +18,18 @@ interface IPrompt {
dataValues?: IPrompt;
toJSON?: () => IPrompt;
allGoalsHavePromptResponse?: boolean;
+ grantId?: number;
+}
+
+interface IReviewPrompt {
+ key: string;
+ promptId: number;
+ responses: string[];
+ recipients:
+ {
+ id: number;
+ name: string;
+ }[];
}
interface ITopic {
@@ -117,6 +129,7 @@ interface IGrant {
}
}
goalId?: number;
+ recipientNameWithPrograms: string;
}
interface IGrantModelInstance extends IGrant {
@@ -253,6 +266,7 @@ interface IReducedGoal {
prompts : {
[x: string]: IPrompt[];
} | IPrompt[];
+ promptsForReview: IReviewPrompt[];
statusChanges?: { oldStatus: string }[];
goalNumber: string;
goalNumbers: string[];
@@ -339,4 +353,5 @@ export {
// -- other entity objective -- //
IOtherEntityObjective,
IOtherEntityObjectiveModelInstance,
+ IReviewPrompt,
};
diff --git a/src/models/grant.js b/src/models/grant.js
index 888f7c3084..31a4238cd0 100644
--- a/src/models/grant.js
+++ b/src/models/grant.js
@@ -136,7 +136,7 @@ export default (sequelize, DataTypes) => {
get() {
const programsList = this.programTypes.length > 0 ? `${this.programTypes.join(', ')}` : '';
return this.recipient
- ? `${this.recipient.name} - ${this.number} - ${programsList}`
+ ? `${this.recipient.name} - ${this.number}${programsList ? ` - ${programsList}` : ''}`
: `${this.number} - ${this.recipientId}`;
},
},
diff --git a/tests/api/goals.spec.ts b/tests/api/goals.spec.ts
index c623f804ed..15df14ff11 100644
--- a/tests/api/goals.spec.ts
+++ b/tests/api/goals.spec.ts
@@ -71,6 +71,14 @@ test('get /goals?goalIds[]=&reportId', async ({ request }) => {
isNew: Joi.boolean(),
collaborators: Joi.array().items(Joi.any().allow(null)),
prompts: Joi.object(),
+ promptsForReview: Joi.array().items(Joi.object({
+ key: Joi.string(),
+ recipients: Joi.array().items(Joi.object({
+ id: Joi.number(),
+ name: Joi.string(),
+ })),
+ responses: Joi.array().items(Joi.string()),
+ })),
source: Joi.any(),
statusChanges: Joi.array().items(Joi.object({
oldStatus: Joi.string(),
diff --git a/tests/api/recipient.spec.ts b/tests/api/recipient.spec.ts
index ac56a3066c..a1f577506b 100644
--- a/tests/api/recipient.spec.ts
+++ b/tests/api/recipient.spec.ts
@@ -186,6 +186,14 @@ test.describe('get /recipient', () => {
id: Joi.number(),
isCurated: Joi.boolean(),
prompts: Joi.object(),
+ promptsForReview: Joi.array().items(Joi.object({
+ key: Joi.string(),
+ recipients: Joi.array().items(Joi.object({
+ id: Joi.number(),
+ name: Joi.string(),
+ })),
+ responses: Joi.array().items(Joi.string()),
+ })),
name: Joi.string(),
source: Joi.object(),
goalTemplateId: Joi.number().allow(null),