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),