From 66d09c47c95ed07d05f3cdd61926f4d6cbc3a183 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Thu, 13 Jun 2024 16:37:10 -0400 Subject: [PATCH 01/16] BE update for fei by grant --- src/goalServices/getFeiGoalsForReport.test.js | 305 ++++++++++++++++++ src/goalServices/getGoalsForReport.ts | 9 +- src/goalServices/reduceGoals.ts | 53 +++ src/goalServices/types.ts | 15 + src/models/grant.js | 2 +- 5 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 src/goalServices/getFeiGoalsForReport.test.js diff --git a/src/goalServices/getFeiGoalsForReport.test.js b/src/goalServices/getFeiGoalsForReport.test.js new file mode 100644 index 0000000000..0173761a1d --- /dev/null +++ b/src/goalServices/getFeiGoalsForReport.test.js @@ -0,0 +1,305 @@ +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; + let recipientOne; + let recipientTwo; + let recipientThree; + let recipientFour; + let activeGrantOne; + let activeGrantTwo; + let activeGrantThree; + let activeGrantFour; + + 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(); + + // 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', + }); + + // 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, + }); + + // 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, + }); + + /* + await GoalFieldResponse.create({ + goalTemplateFieldPromptId: prompt.id, + goalId: goalFour.id, + response: [], + 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 }], + 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, + }); + }); + 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], + }, + }); + + // 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], + }, + force: true, + }); + + // Delete Grants. + await Grant.destroy({ + where: { + id: [activeGrantOne.id, activeGrantTwo.id, activeGrantThree.id, activeGrantFour.id], + }, + individualHooks: true, + force: true, + }); + + // Delete Recipients. + await Recipient.destroy({ + where: { + id: [recipientOne.id, recipientTwo.id, recipientThree.id, recipientFour.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(2); + + // 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}`); + }); +}); 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..ce5c608fff 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,43 @@ function reducePrompts( }, promptsToReduce); } +function reducePromptsForReview( + recipientId: number, + fullGrantName: string, + newPrompts:IPrompt[] = [], + previousPrompts:IReviewPrompt[] = [], +) { + const updatedPrompts = [...previousPrompts]; + newPrompts.forEach((currentPrompt) => { + // Get the prompt Id. + const promptId = currentPrompt.promptId + ? currentPrompt.promptId : currentPrompt.dataValues.promptId; + + // Get the responses for the prompt sorted alphabetically. + const { response } = currentPrompt; + + // 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 }); + } + }); + return updatedPrompts; +} + /** * Dedupes goals by name + status, as well as objectives by title + status * @param {Object[]} goals @@ -425,6 +463,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 +506,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 +542,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}`; }, }, From 87dfd9062f9cb283a6c4ecd6a92cd40350914952 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Fri, 14 Jun 2024 11:30:03 -0400 Subject: [PATCH 02/16] add missing to BE and add FE with tests --- .../components/RecipientReviewSection.js | 54 +++++++++++++++++-- .../components/RecipientReviewSection.scss | 6 ++- .../__tests__/RecipientReviewSection.js | 51 ++++++++++++++++++ src/goalServices/getFeiGoalsForReport.test.js | 12 ++++- src/goalServices/reduceGoals.ts | 11 ++++ 5 files changed, 128 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js index 1ac97379c2..b5e3d30633 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, index) => ( + <> +
+ {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/RecipientReviewSection.scss b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.scss index aaca972d24..e945deaa71 100644 --- a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.scss +++ b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.scss @@ -1,3 +1,7 @@ .smart-hub-review-section .public-DraftStyleDefault-block { margin: 0; -} \ No newline at end of file +} + +.smart-hub-review-section svg { + margin-right: .5rem; +} 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/src/goalServices/getFeiGoalsForReport.test.js b/src/goalServices/getFeiGoalsForReport.test.js index 0173761a1d..9bac22f133 100644 --- a/src/goalServices/getFeiGoalsForReport.test.js +++ b/src/goalServices/getFeiGoalsForReport.test.js @@ -273,10 +273,11 @@ describe('getFeiGoalsForReport', () => { }); 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(2); + 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')); @@ -301,5 +302,14 @@ describe('getFeiGoalsForReport', () => { 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}`); + + const assertRecipients3 = goalsForReport[0].promptsForReview.filter( + (g) => g.responses.length === 0, + ); + + // Recipient 4 (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}`); }); }); diff --git a/src/goalServices/reduceGoals.ts b/src/goalServices/reduceGoals.ts index ce5c608fff..0840ecb6b8 100644 --- a/src/goalServices/reduceGoals.ts +++ b/src/goalServices/reduceGoals.ts @@ -366,6 +366,17 @@ function reducePromptsForReview( // Get the responses for the prompt sorted alphabetically. const { response } = currentPrompt; + if (!response || !response.length) { + // Add empty response. + const missingPromptKey = `${promptId}-${fullGrantName}`; + updatedPrompts.push({ + key: missingPromptKey, + promptId, + recipients: [{ id: recipientId, name: fullGrantName }], + responses: [], + }); + return; + } // Sort report responses alphabetically. response.sort(); From 8f89e81618a19d185fc7e0f62ea37f6fbd875a17 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Fri, 14 Jun 2024 11:39:43 -0400 Subject: [PATCH 03/16] put on sandbox --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From a45133be149e12162dfdeb4c738e86c185fc1784 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Fri, 14 Jun 2024 11:55:22 -0400 Subject: [PATCH 04/16] update api joi test --- tests/api/goals.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/goals.spec.ts b/tests/api/goals.spec.ts index c623f804ed..a22c395a30 100644 --- a/tests/api/goals.spec.ts +++ b/tests/api/goals.spec.ts @@ -71,6 +71,7 @@ test('get /goals?goalIds[]=&reportId', async ({ request }) => { isNew: Joi.boolean(), collaborators: Joi.array().items(Joi.any().allow(null)), prompts: Joi.object(), + promptsForReview: Joi.object(), source: Joi.any(), statusChanges: Joi.array().items(Joi.object({ oldStatus: Joi.string(), From a67e5a13f55ea9ff5063cde8ff6e21e881c03838 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Fri, 14 Jun 2024 13:29:34 -0400 Subject: [PATCH 05/16] update property def --- tests/api/goals.spec.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/api/goals.spec.ts b/tests/api/goals.spec.ts index a22c395a30..15df14ff11 100644 --- a/tests/api/goals.spec.ts +++ b/tests/api/goals.spec.ts @@ -71,7 +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.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(), From b2083edfd06b3ae7e7223b4032f7d9f1dfa19f52 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Fri, 14 Jun 2024 15:10:19 -0400 Subject: [PATCH 06/16] update joi --- tests/api/recipient.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) 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), From e3ddb439c11268ce7bd7987ebe4ad4257bedfcbb Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Mon, 17 Jun 2024 09:14:01 -0400 Subject: [PATCH 07/16] fixes per Matt and Lauren --- .../components/RecipientReviewSection.js | 26 +++++++++---------- .../components/RecipientReviewSection.scss | 6 +---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js index b5e3d30633..2c1bcd41cd 100644 --- a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js +++ b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.js @@ -64,27 +64,27 @@ const RecipientReviewSection = () => { const promptsForReview = goal.promptsForReview || []; return (promptsForReview.length > 0 && (
- {promptsForReview.map((v, index) => ( + {promptsForReview.map((v) => ( <>
{item.label}
-
+
{ - v.responses.length - ? v.responses.join(', ') - : ( -
- - {' '} - Missing Information -
- ) - } + v.responses.length + ? v.responses.join(', ') + : ( +
+ + {' '} + Missing Information +
+ ) + }
-
    +
      {v.recipients.map((r) => (
    • {r.name}
    • ))} diff --git a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.scss b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.scss index e945deaa71..aaca972d24 100644 --- a/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.scss +++ b/frontend/src/pages/ActivityReport/Pages/components/RecipientReviewSection.scss @@ -1,7 +1,3 @@ .smart-hub-review-section .public-DraftStyleDefault-block { margin: 0; -} - -.smart-hub-review-section svg { - margin-right: .5rem; -} +} \ No newline at end of file From b440b58e64675d34c0e25322bab62b9d60e230fa Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Mon, 17 Jun 2024 10:27:28 -0400 Subject: [PATCH 08/16] fix unwanted space after activity date --- frontend/src/components/ReadOnlyContent.scss | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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; } } From 6ce0d5c088f3a99faac96cfecee5db710d7206e7 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Mon, 17 Jun 2024 12:14:20 -0400 Subject: [PATCH 09/16] make sure we roll up recipients missing info --- src/goalServices/getFeiGoalsForReport.test.js | 64 ++++++++++++++----- src/goalServices/reduceGoals.ts | 22 +++++-- 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/goalServices/getFeiGoalsForReport.test.js b/src/goalServices/getFeiGoalsForReport.test.js index 9bac22f133..3f72c7874f 100644 --- a/src/goalServices/getFeiGoalsForReport.test.js +++ b/src/goalServices/getFeiGoalsForReport.test.js @@ -27,15 +27,18 @@ describe('getFeiGoalsForReport', () => { let goalOne; let goalTwo; let goalThree; - let goalFour; + 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; @@ -58,6 +61,7 @@ describe('getFeiGoalsForReport', () => { recipientTwo = await createRecipient(); recipientThree = await createRecipient(); recipientFour = await createRecipient(); + recipientFive = await createRecipient(); // Grants. activeGrantOne = await createGrant({ @@ -80,6 +84,11 @@ describe('getFeiGoalsForReport', () => { status: 'Active', }); + activeGrantFive = await createGrant({ + recipientId: recipientFive.id, + status: 'Active', + }); + // Template. template = await createGoalTemplate({ name: faker.lorem.sentence(), @@ -116,6 +125,13 @@ describe('getFeiGoalsForReport', () => { 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, @@ -153,16 +169,6 @@ describe('getFeiGoalsForReport', () => { onApprovedAR: false, }); - /* - await GoalFieldResponse.create({ - goalTemplateFieldPromptId: prompt.id, - goalId: goalFour.id, - response: [], - onAR: true, - onApprovedAR: false, - }); - */ - // create report report = await ActivityReport.create({ activityRecipientType: 'recipient', @@ -175,7 +181,8 @@ describe('getFeiGoalsForReport', () => { { activityRecipientId: recipientOne.id }, { activityRecipientId: recipientTwo.id }, { activityRecipientId: recipientThree.id }, - { activityRecipientId: recipientFour.id }], + { activityRecipientId: recipientFour.id }, + { activityRecipientId: recipientFive.id }], version: 2, }); @@ -203,6 +210,12 @@ describe('getFeiGoalsForReport', () => { goalId: goalFour.id, isActivelyEdited: false, }); + + await ActivityReportGoal.create({ + activityReportId: report.id, + goalId: goalFive.id, + isActivelyEdited: false, + }); }); afterAll(async () => { // Delete ActivityReportGoals. @@ -222,7 +235,7 @@ describe('getFeiGoalsForReport', () => { // Delete GoalFieldResponses. await GoalFieldResponse.destroy({ where: { - goalId: [goalOne.id, goalTwo.id, goalThree.id, goalFour.id], + goalId: [goalOne.id, goalTwo.id, goalThree.id, goalFour.id, goalFive.id], }, }); @@ -243,7 +256,7 @@ describe('getFeiGoalsForReport', () => { // Delete Goals. await Goal.destroy({ where: { - id: [goalOne.id, goalTwo.id, goalThree.id, goalFour.id], + id: [goalOne.id, goalTwo.id, goalThree.id, goalFour.id, goalFive.id], }, force: true, }); @@ -251,7 +264,13 @@ describe('getFeiGoalsForReport', () => { // Delete Grants. await Grant.destroy({ where: { - id: [activeGrantOne.id, activeGrantTwo.id, activeGrantThree.id, activeGrantFour.id], + id: [ + activeGrantOne.id, + activeGrantTwo.id, + activeGrantThree.id, + activeGrantFour.id, + activeGrantFive.id, + ], }, individualHooks: true, force: true, @@ -260,7 +279,13 @@ describe('getFeiGoalsForReport', () => { // Delete Recipients. await Recipient.destroy({ where: { - id: [recipientOne.id, recipientTwo.id, recipientThree.id, recipientFour.id], + id: [ + recipientOne.id, + recipientTwo.id, + recipientThree.id, + recipientFour.id, + recipientFive.id, + ], }, force: true, }); @@ -303,13 +328,18 @@ describe('getFeiGoalsForReport', () => { 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 (no responses). + // 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/reduceGoals.ts b/src/goalServices/reduceGoals.ts index 0840ecb6b8..8dfb33b60e 100644 --- a/src/goalServices/reduceGoals.ts +++ b/src/goalServices/reduceGoals.ts @@ -368,13 +368,21 @@ function reducePromptsForReview( const { response } = currentPrompt; if (!response || !response.length) { // Add empty response. - const missingPromptKey = `${promptId}-${fullGrantName}`; - updatedPrompts.push({ - key: missingPromptKey, - promptId, - recipients: [{ id: recipientId, name: fullGrantName }], - responses: [], - }); + 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 }); return; } From b2f0fe9abe171f45d5d6349698b1e5c08cd7b40f Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Mon, 17 Jun 2024 14:45:10 -0400 Subject: [PATCH 10/16] Add check per Garrett --- src/goalServices/reduceGoals.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/goalServices/reduceGoals.ts b/src/goalServices/reduceGoals.ts index 8dfb33b60e..6df048b190 100644 --- a/src/goalServices/reduceGoals.ts +++ b/src/goalServices/reduceGoals.ts @@ -361,11 +361,9 @@ function reducePromptsForReview( const updatedPrompts = [...previousPrompts]; newPrompts.forEach((currentPrompt) => { // Get the prompt Id. - const promptId = currentPrompt.promptId - ? currentPrompt.promptId : currentPrompt.dataValues.promptId; + const promptToUse = currentPrompt.promptId ? currentPrompt : currentPrompt.dataValues; + const { promptId, response } = promptToUse; - // Get the responses for the prompt sorted alphabetically. - const { response } = currentPrompt; if (!response || !response.length) { // Add empty response. const missingPromptKey = `${promptId}-missing`; From 01558243ac0eefd49f2fed812d3806971f83c5a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:18:30 +0000 Subject: [PATCH 11/16] Bump urllib3 from 2.0.7 to 2.2.2 in /similarity_api/src Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.7 to 2.2.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.7...2.2.2) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- similarity_api/src/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/similarity_api/src/requirements.txt b/similarity_api/src/requirements.txt index f8a8021d6b..cbb23ee648 100644 --- a/similarity_api/src/requirements.txt +++ b/similarity_api/src/requirements.txt @@ -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 From 328fef75d12be287e0a8997da469b8977136c4d5 Mon Sep 17 00:00:00 2001 From: GarrettEHill Date: Tue, 18 Jun 2024 01:42:29 -0700 Subject: [PATCH 12/16] Update yarn-audit-known-issues --- yarn-audit-known-issues | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index ff11481921..7e826b2e11 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -3,6 +3,9 @@ {"type":"auditAdvisory","data":{"resolution":{"id":1097109,"path":"@elastic/elasticsearch>@elastic/transport>undici","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.19.1","paths":["@elastic/elasticsearch>@elastic/transport>undici"]}],"metadata":null,"vulnerable_versions":"<5.28.4","module_name":"undici","severity":"low","github_advisory_id":"GHSA-m4v8-wqvr-p9f7","cves":["CVE-2024-30260"],"access":"public","patched_versions":">=5.28.4","cvss":{"score":3.9,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L"},"updated":"2024-04-20T00:31:53.000Z","recommendation":"Upgrade to version 5.28.4 or later","cwe":["CWE-200","CWE-285"],"found_by":null,"deleted":null,"id":1097109,"references":"- https://github.com/nodejs/undici/security/advisories/GHSA-m4v8-wqvr-p9f7\n- https://github.com/nodejs/undici/commit/64e3402da4e032e68de46acb52800c9a06aaea3f\n- https://github.com/nodejs/undici/commit/6805746680d27a5369d7fb67bc05f95a28247d75\n- https://hackerone.com/reports/2408074\n- https://nvd.nist.gov/vuln/detail/CVE-2024-30260\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HQVHWAS6WDXXIU7F72XI55VZ2LTZUB33\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/NC3V3HFZ5MOJRZDY5ZELL6REIRSPFROJ\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/P6Q4RGETHVYVHDIQGTJGU5AV6NJEI67E\n- https://github.com/advisories/GHSA-m4v8-wqvr-p9f7","created":"2024-04-04T14:20:39.000Z","reported_by":null,"title":"Undici's Proxy-Authorization header not cleared on cross-origin redirect for dispatch, request, stream, pipeline","npm_advisory_id":null,"overview":"### Impact\n\nUndici cleared Authorization and Proxy-Authorization headers for `fetch()`, but did not clear them for `undici.request()`.\n\n### Patches\n\nThis has been patched in https://github.com/nodejs/undici/commit/6805746680d27a5369d7fb67bc05f95a28247d75.\nFixes has been released in v5.28.4 and v6.11.1.\n\n### Workarounds\n\nuse `fetch()` or disable `maxRedirections`.\n\n### References\n\nLinzi Shang reported this.\n\n* https://hackerone.com/reports/2408074\n* https://github.com/nodejs/undici/security/advisories/GHSA-3787-6prv-h9w3\n","url":"https://github.com/advisories/GHSA-m4v8-wqvr-p9f7"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1097200,"path":"@elastic/elasticsearch>@elastic/transport>undici","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.19.1","paths":["@elastic/elasticsearch>@elastic/transport>undici"]}],"metadata":null,"vulnerable_versions":"<5.28.4","module_name":"undici","severity":"low","github_advisory_id":"GHSA-9qxr-qj54-h672","cves":["CVE-2024-30261"],"access":"public","patched_versions":">=5.28.4","cvss":{"score":2.6,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:N/I:L/A:N"},"updated":"2024-04-29T05:02:11.000Z","recommendation":"Upgrade to version 5.28.4 or later","cwe":["CWE-284"],"found_by":null,"deleted":null,"id":1097200,"references":"- https://github.com/nodejs/undici/security/advisories/GHSA-9qxr-qj54-h672\n- https://github.com/nodejs/undici/commit/2b39440bd9ded841c93dd72138f3b1763ae26055\n- https://github.com/nodejs/undici/commit/d542b8cd39ec1ba303f038ea26098c3f355974f3\n- https://hackerone.com/reports/2377760\n- https://nvd.nist.gov/vuln/detail/CVE-2024-30261\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HQVHWAS6WDXXIU7F72XI55VZ2LTZUB33\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/P6Q4RGETHVYVHDIQGTJGU5AV6NJEI67E\n- https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/NC3V3HFZ5MOJRZDY5ZELL6REIRSPFROJ\n- https://github.com/advisories/GHSA-9qxr-qj54-h672","created":"2024-04-04T14:20:54.000Z","reported_by":null,"title":"Undici's fetch with integrity option is too lax when algorithm is specified but hash value is in incorrect","npm_advisory_id":null,"overview":"### Impact\n\nIf an attacker can alter the `integrity` option passed to `fetch()`, they can let `fetch()` accept requests as valid even if they have been tampered.\n\n### Patches\n\nFixed in https://github.com/nodejs/undici/commit/d542b8cd39ec1ba303f038ea26098c3f355974f3.\nFixes has been released in v5.28.4 and v6.11.1.\n\n\n### Workarounds\n\nEnsure that `integrity` cannot be tampered with.\n\n### References\n\nhttps://hackerone.com/reports/2377760\n","url":"https://github.com/advisories/GHSA-9qxr-qj54-h672"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1097221,"path":"@elastic/elasticsearch>@elastic/transport>undici","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"5.19.1","paths":["@elastic/elasticsearch>@elastic/transport>undici"]}],"metadata":null,"vulnerable_versions":"<=5.28.2","module_name":"undici","severity":"low","github_advisory_id":"GHSA-3787-6prv-h9w3","cves":["CVE-2024-24758"],"access":"public","patched_versions":">=5.28.3","cvss":{"score":3.9,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:L"},"updated":"2024-05-02T13:15:07.000Z","recommendation":"Upgrade to version 5.28.3 or later","cwe":["CWE-200"],"found_by":null,"deleted":null,"id":1097221,"references":"- https://github.com/nodejs/undici/security/advisories/GHSA-3787-6prv-h9w3\n- https://github.com/nodejs/undici/commit/b9da3e40f1f096a06b4caedbb27c2568730434ef\n- https://github.com/nodejs/undici/commit/d3aa574b1259c1d8d329a0f0f495ee82882b1458\n- https://github.com/nodejs/undici/releases/tag/v5.28.3\n- https://github.com/nodejs/undici/releases/tag/v6.6.1\n- https://nvd.nist.gov/vuln/detail/CVE-2024-24758\n- https://security.netapp.com/advisory/ntap-20240419-0007\n- http://www.openwall.com/lists/oss-security/2024/03/11/1\n- https://github.com/advisories/GHSA-3787-6prv-h9w3","created":"2024-02-16T16:02:52.000Z","reported_by":null,"title":"Undici proxy-authorization header not cleared on cross-origin redirect in fetch","npm_advisory_id":null,"overview":"### Impact\n\nUndici already cleared Authorization headers on cross-origin redirects, but did not clear `Proxy-Authorization` headers. \n\n### Patches\n\nThis is patched in v5.28.3 and v6.6.1\n\n### Workarounds\n\nThere are no known workarounds.\n\n### References\n\n- https://fetch.spec.whatwg.org/#authentication-entries\n- https://github.com/nodejs/undici/security/advisories/GHSA-wqq4-5wpv-mx2g","url":"https://github.com/advisories/GHSA-3787-6prv-h9w3"}}} +{"type":"auditAdvisory","data":{"resolution":{"id":1097615,"path":"jsdom>ws","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"8.16.0","paths":["jsdom>ws"]},{"version":"8.14.2","paths":["newrelic>@newrelic/security-agent>ws"]},{"version":"8.5.0","paths":["ws"]}],"metadata":null,"vulnerable_versions":">=8.0.0 <8.17.1","module_name":"ws","severity":"high","github_advisory_id":"GHSA-3h5v-q93c-6h6q","cves":["CVE-2024-37890"],"access":"public","patched_versions":">=8.17.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2024-06-17T19:09:12.000Z","recommendation":"Upgrade to version 8.17.1 or later","cwe":["CWE-476"],"found_by":null,"deleted":null,"id":1097615,"references":"- https://github.com/websockets/ws/security/advisories/GHSA-3h5v-q93c-6h6q\n- https://github.com/websockets/ws/issues/2230\n- https://github.com/websockets/ws/pull/2231\n- https://github.com/websockets/ws/commit/22c28763234aa75a7e1b76f5c01c181260d7917f\n- https://github.com/websockets/ws/commit/4abd8f6de4b0b65ef80b3ff081989479ed93377e\n- https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c\n- https://github.com/websockets/ws/commit/eeb76d313e2a00dd5247ca3597bba7877d064a63\n- https://github.com/advisories/GHSA-3h5v-q93c-6h6q","created":"2024-06-17T19:09:10.000Z","reported_by":null,"title":"ws affected by a DoS when handling a request with many HTTP headers","npm_advisory_id":null,"overview":"### Impact\n\nA request with a number of headers exceeding the[`server.maxHeadersCount`][] threshold could be used to crash a ws server.\n\n### Proof of concept\n\n```js\nconst http = require('http');\nconst WebSocket = require('ws');\n\nconst wss = new WebSocket.Server({ port: 0 }, function () {\n const chars = \"!#$%&'*+-.0123456789abcdefghijklmnopqrstuvwxyz^_`|~\".split('');\n const headers = {};\n let count = 0;\n\n for (let i = 0; i < chars.length; i++) {\n if (count === 2000) break;\n\n for (let j = 0; j < chars.length; j++) {\n const key = chars[i] + chars[j];\n headers[key] = 'x';\n\n if (++count === 2000) break;\n }\n }\n\n headers.Connection = 'Upgrade';\n headers.Upgrade = 'websocket';\n headers['Sec-WebSocket-Key'] = 'dGhlIHNhbXBsZSBub25jZQ==';\n headers['Sec-WebSocket-Version'] = '13';\n\n const request = http.request({\n headers: headers,\n host: '127.0.0.1',\n port: wss.address().port\n });\n\n request.end();\n});\n```\n\n### Patches\n\nThe vulnerability was fixed in ws@8.17.1 (https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c) and backported to ws@7.5.10 (https://github.com/websockets/ws/commit/22c28763234aa75a7e1b76f5c01c181260d7917f), ws@6.2.3 (https://github.com/websockets/ws/commit/eeb76d313e2a00dd5247ca3597bba7877d064a63), and ws@5.2.4 (https://github.com/websockets/ws/commit/4abd8f6de4b0b65ef80b3ff081989479ed93377e)\n\n### Workarounds\n\nIn vulnerable versions of ws, the issue can be mitigated in the following ways:\n\n1. Reduce the maximum allowed length of the request headers using the [`--max-http-header-size=size`][] and/or the [`maxHeaderSize`][] options so that no more headers than the `server.maxHeadersCount` limit can be sent.\n2. Set `server.maxHeadersCount` to `0` so that no limit is applied.\n\n### Credits\n\nThe vulnerability was reported by [Ryan LaPointe](https://github.com/rrlapointe) in https://github.com/websockets/ws/issues/2230.\n\n### References\n\n- https://github.com/websockets/ws/issues/2230\n- https://github.com/websockets/ws/pull/2231\n\n[`--max-http-header-size=size`]: https://nodejs.org/api/cli.html#--max-http-header-sizesize\n[`maxHeaderSize`]: https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener\n[`server.maxHeadersCount`]: https://nodejs.org/api/http.html#servermaxheaderscount\n","url":"https://github.com/advisories/GHSA-3h5v-q93c-6h6q"}}} +{"type":"auditAdvisory","data":{"resolution":{"id":1097615,"path":"newrelic>@newrelic/security-agent>ws","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"8.16.0","paths":["jsdom>ws"]},{"version":"8.14.2","paths":["newrelic>@newrelic/security-agent>ws"]},{"version":"8.5.0","paths":["ws"]}],"metadata":null,"vulnerable_versions":">=8.0.0 <8.17.1","module_name":"ws","severity":"high","github_advisory_id":"GHSA-3h5v-q93c-6h6q","cves":["CVE-2024-37890"],"access":"public","patched_versions":">=8.17.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2024-06-17T19:09:12.000Z","recommendation":"Upgrade to version 8.17.1 or later","cwe":["CWE-476"],"found_by":null,"deleted":null,"id":1097615,"references":"- https://github.com/websockets/ws/security/advisories/GHSA-3h5v-q93c-6h6q\n- https://github.com/websockets/ws/issues/2230\n- https://github.com/websockets/ws/pull/2231\n- https://github.com/websockets/ws/commit/22c28763234aa75a7e1b76f5c01c181260d7917f\n- https://github.com/websockets/ws/commit/4abd8f6de4b0b65ef80b3ff081989479ed93377e\n- https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c\n- https://github.com/websockets/ws/commit/eeb76d313e2a00dd5247ca3597bba7877d064a63\n- https://github.com/advisories/GHSA-3h5v-q93c-6h6q","created":"2024-06-17T19:09:10.000Z","reported_by":null,"title":"ws affected by a DoS when handling a request with many HTTP headers","npm_advisory_id":null,"overview":"### Impact\n\nA request with a number of headers exceeding the[`server.maxHeadersCount`][] threshold could be used to crash a ws server.\n\n### Proof of concept\n\n```js\nconst http = require('http');\nconst WebSocket = require('ws');\n\nconst wss = new WebSocket.Server({ port: 0 }, function () {\n const chars = \"!#$%&'*+-.0123456789abcdefghijklmnopqrstuvwxyz^_`|~\".split('');\n const headers = {};\n let count = 0;\n\n for (let i = 0; i < chars.length; i++) {\n if (count === 2000) break;\n\n for (let j = 0; j < chars.length; j++) {\n const key = chars[i] + chars[j];\n headers[key] = 'x';\n\n if (++count === 2000) break;\n }\n }\n\n headers.Connection = 'Upgrade';\n headers.Upgrade = 'websocket';\n headers['Sec-WebSocket-Key'] = 'dGhlIHNhbXBsZSBub25jZQ==';\n headers['Sec-WebSocket-Version'] = '13';\n\n const request = http.request({\n headers: headers,\n host: '127.0.0.1',\n port: wss.address().port\n });\n\n request.end();\n});\n```\n\n### Patches\n\nThe vulnerability was fixed in ws@8.17.1 (https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c) and backported to ws@7.5.10 (https://github.com/websockets/ws/commit/22c28763234aa75a7e1b76f5c01c181260d7917f), ws@6.2.3 (https://github.com/websockets/ws/commit/eeb76d313e2a00dd5247ca3597bba7877d064a63), and ws@5.2.4 (https://github.com/websockets/ws/commit/4abd8f6de4b0b65ef80b3ff081989479ed93377e)\n\n### Workarounds\n\nIn vulnerable versions of ws, the issue can be mitigated in the following ways:\n\n1. Reduce the maximum allowed length of the request headers using the [`--max-http-header-size=size`][] and/or the [`maxHeaderSize`][] options so that no more headers than the `server.maxHeadersCount` limit can be sent.\n2. Set `server.maxHeadersCount` to `0` so that no limit is applied.\n\n### Credits\n\nThe vulnerability was reported by [Ryan LaPointe](https://github.com/rrlapointe) in https://github.com/websockets/ws/issues/2230.\n\n### References\n\n- https://github.com/websockets/ws/issues/2230\n- https://github.com/websockets/ws/pull/2231\n\n[`--max-http-header-size=size`]: https://nodejs.org/api/cli.html#--max-http-header-sizesize\n[`maxHeaderSize`]: https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener\n[`server.maxHeadersCount`]: https://nodejs.org/api/http.html#servermaxheaderscount\n","url":"https://github.com/advisories/GHSA-3h5v-q93c-6h6q"}}} +{"type":"auditAdvisory","data":{"resolution":{"id":1097615,"path":"ws","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"8.16.0","paths":["jsdom>ws"]},{"version":"8.14.2","paths":["newrelic>@newrelic/security-agent>ws"]},{"version":"8.5.0","paths":["ws"]}],"metadata":null,"vulnerable_versions":">=8.0.0 <8.17.1","module_name":"ws","severity":"high","github_advisory_id":"GHSA-3h5v-q93c-6h6q","cves":["CVE-2024-37890"],"access":"public","patched_versions":">=8.17.1","cvss":{"score":7.5,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"},"updated":"2024-06-17T19:09:12.000Z","recommendation":"Upgrade to version 8.17.1 or later","cwe":["CWE-476"],"found_by":null,"deleted":null,"id":1097615,"references":"- https://github.com/websockets/ws/security/advisories/GHSA-3h5v-q93c-6h6q\n- https://github.com/websockets/ws/issues/2230\n- https://github.com/websockets/ws/pull/2231\n- https://github.com/websockets/ws/commit/22c28763234aa75a7e1b76f5c01c181260d7917f\n- https://github.com/websockets/ws/commit/4abd8f6de4b0b65ef80b3ff081989479ed93377e\n- https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c\n- https://github.com/websockets/ws/commit/eeb76d313e2a00dd5247ca3597bba7877d064a63\n- https://github.com/advisories/GHSA-3h5v-q93c-6h6q","created":"2024-06-17T19:09:10.000Z","reported_by":null,"title":"ws affected by a DoS when handling a request with many HTTP headers","npm_advisory_id":null,"overview":"### Impact\n\nA request with a number of headers exceeding the[`server.maxHeadersCount`][] threshold could be used to crash a ws server.\n\n### Proof of concept\n\n```js\nconst http = require('http');\nconst WebSocket = require('ws');\n\nconst wss = new WebSocket.Server({ port: 0 }, function () {\n const chars = \"!#$%&'*+-.0123456789abcdefghijklmnopqrstuvwxyz^_`|~\".split('');\n const headers = {};\n let count = 0;\n\n for (let i = 0; i < chars.length; i++) {\n if (count === 2000) break;\n\n for (let j = 0; j < chars.length; j++) {\n const key = chars[i] + chars[j];\n headers[key] = 'x';\n\n if (++count === 2000) break;\n }\n }\n\n headers.Connection = 'Upgrade';\n headers.Upgrade = 'websocket';\n headers['Sec-WebSocket-Key'] = 'dGhlIHNhbXBsZSBub25jZQ==';\n headers['Sec-WebSocket-Version'] = '13';\n\n const request = http.request({\n headers: headers,\n host: '127.0.0.1',\n port: wss.address().port\n });\n\n request.end();\n});\n```\n\n### Patches\n\nThe vulnerability was fixed in ws@8.17.1 (https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c) and backported to ws@7.5.10 (https://github.com/websockets/ws/commit/22c28763234aa75a7e1b76f5c01c181260d7917f), ws@6.2.3 (https://github.com/websockets/ws/commit/eeb76d313e2a00dd5247ca3597bba7877d064a63), and ws@5.2.4 (https://github.com/websockets/ws/commit/4abd8f6de4b0b65ef80b3ff081989479ed93377e)\n\n### Workarounds\n\nIn vulnerable versions of ws, the issue can be mitigated in the following ways:\n\n1. Reduce the maximum allowed length of the request headers using the [`--max-http-header-size=size`][] and/or the [`maxHeaderSize`][] options so that no more headers than the `server.maxHeadersCount` limit can be sent.\n2. Set `server.maxHeadersCount` to `0` so that no limit is applied.\n\n### Credits\n\nThe vulnerability was reported by [Ryan LaPointe](https://github.com/rrlapointe) in https://github.com/websockets/ws/issues/2230.\n\n### References\n\n- https://github.com/websockets/ws/issues/2230\n- https://github.com/websockets/ws/pull/2231\n\n[`--max-http-header-size=size`]: https://nodejs.org/api/cli.html#--max-http-header-sizesize\n[`maxHeaderSize`]: https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener\n[`server.maxHeadersCount`]: https://nodejs.org/api/http.html#servermaxheaderscount\n","url":"https://github.com/advisories/GHSA-3h5v-q93c-6h6q"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1096410,"path":"xml2json>hoek","dev":false,"bundled":false,"optional":false},"advisory":{"findings":[{"version":"4.2.1","paths":["xml2json>hoek"]},{"version":"5.0.4","paths":["xml2json>joi>hoek"]},{"version":"6.1.3","paths":["xml2json>joi>topo>hoek"]}],"metadata":null,"vulnerable_versions":"<=6.1.3","module_name":"hoek","severity":"high","github_advisory_id":"GHSA-c429-5p7v-vgjp","cves":["CVE-2020-36604"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":8.1,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2024-02-07T18:59:37.000Z","recommendation":"None","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1096410,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-36604\n- https://github.com/hapijs/hoek/issues/352\n- https://github.com/hapijs/hoek/commit/4d0804bc6135ad72afdc5e1ec002b935b2f5216a\n- https://github.com/hapijs/hoek/commit/948baf98634a5c206875b67d11368f133034fa90\n- https://github.com/advisories/GHSA-c429-5p7v-vgjp","created":"2022-09-25T00:00:27.000Z","reported_by":null,"title":"hoek subject to prototype pollution via the clone function.","npm_advisory_id":null,"overview":"hoek versions prior to 8.5.1, and 9.x prior to 9.0.3 are vulnerable to prototype pollution in the clone function. If an object with the __proto__ key is passed to clone() the key is converted to a prototype. This issue has been patched in version 9.0.3, and backported to 8.5.1. ","url":"https://github.com/advisories/GHSA-c429-5p7v-vgjp"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1096410,"path":"xml2json>joi>hoek","dev":false,"bundled":false,"optional":false},"advisory":{"findings":[{"version":"4.2.1","paths":["xml2json>hoek"]},{"version":"5.0.4","paths":["xml2json>joi>hoek"]},{"version":"6.1.3","paths":["xml2json>joi>topo>hoek"]}],"metadata":null,"vulnerable_versions":"<=6.1.3","module_name":"hoek","severity":"high","github_advisory_id":"GHSA-c429-5p7v-vgjp","cves":["CVE-2020-36604"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":8.1,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2024-02-07T18:59:37.000Z","recommendation":"None","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1096410,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-36604\n- https://github.com/hapijs/hoek/issues/352\n- https://github.com/hapijs/hoek/commit/4d0804bc6135ad72afdc5e1ec002b935b2f5216a\n- https://github.com/hapijs/hoek/commit/948baf98634a5c206875b67d11368f133034fa90\n- https://github.com/advisories/GHSA-c429-5p7v-vgjp","created":"2022-09-25T00:00:27.000Z","reported_by":null,"title":"hoek subject to prototype pollution via the clone function.","npm_advisory_id":null,"overview":"hoek versions prior to 8.5.1, and 9.x prior to 9.0.3 are vulnerable to prototype pollution in the clone function. If an object with the __proto__ key is passed to clone() the key is converted to a prototype. This issue has been patched in version 9.0.3, and backported to 8.5.1. ","url":"https://github.com/advisories/GHSA-c429-5p7v-vgjp"}}} {"type":"auditAdvisory","data":{"resolution":{"id":1096410,"path":"xml2json>joi>topo>hoek","dev":false,"bundled":false,"optional":false},"advisory":{"findings":[{"version":"4.2.1","paths":["xml2json>hoek"]},{"version":"5.0.4","paths":["xml2json>joi>hoek"]},{"version":"6.1.3","paths":["xml2json>joi>topo>hoek"]}],"metadata":null,"vulnerable_versions":"<=6.1.3","module_name":"hoek","severity":"high","github_advisory_id":"GHSA-c429-5p7v-vgjp","cves":["CVE-2020-36604"],"access":"public","patched_versions":"<0.0.0","cvss":{"score":8.1,"vectorString":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"},"updated":"2024-02-07T18:59:37.000Z","recommendation":"None","cwe":["CWE-1321"],"found_by":null,"deleted":null,"id":1096410,"references":"- https://nvd.nist.gov/vuln/detail/CVE-2020-36604\n- https://github.com/hapijs/hoek/issues/352\n- https://github.com/hapijs/hoek/commit/4d0804bc6135ad72afdc5e1ec002b935b2f5216a\n- https://github.com/hapijs/hoek/commit/948baf98634a5c206875b67d11368f133034fa90\n- https://github.com/advisories/GHSA-c429-5p7v-vgjp","created":"2022-09-25T00:00:27.000Z","reported_by":null,"title":"hoek subject to prototype pollution via the clone function.","npm_advisory_id":null,"overview":"hoek versions prior to 8.5.1, and 9.x prior to 9.0.3 are vulnerable to prototype pollution in the clone function. If an object with the __proto__ key is passed to clone() the key is converted to a prototype. This issue has been patched in version 9.0.3, and backported to 8.5.1. ","url":"https://github.com/advisories/GHSA-c429-5p7v-vgjp"}}} From a654c12e3c1efe3cd20affaa5e4cf08548b3ff0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:38:41 +0000 Subject: [PATCH 13/16] Bump scikit-learn from 1.3.0 to 1.5.0 in /similarity_api/src Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.3.0 to 1.5.0. - [Release notes](https://github.com/scikit-learn/scikit-learn/releases) - [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.3.0...1.5.0) --- updated-dependencies: - dependency-name: scikit-learn dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- similarity_api/src/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/similarity_api/src/requirements.txt b/similarity_api/src/requirements.txt index 89a46b2912..174e45e3ef 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 From f525b822cd18da840bc5366ee72aeb71b311d6d4 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 18 Jun 2024 16:08:48 -0400 Subject: [PATCH 14/16] sort recipients by alpha --- src/goalServices/reduceGoals.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/goalServices/reduceGoals.ts b/src/goalServices/reduceGoals.ts index 6df048b190..b599774989 100644 --- a/src/goalServices/reduceGoals.ts +++ b/src/goalServices/reduceGoals.ts @@ -381,6 +381,8 @@ function reducePromptsForReview( } // 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; } @@ -401,6 +403,8 @@ function reducePromptsForReview( } 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; From e01012c272dadd1d4cb7b5a4136282d29c79dd50 Mon Sep 17 00:00:00 2001 From: nvms Date: Thu, 20 Jun 2024 10:23:07 -0400 Subject: [PATCH 15/16] update label and test --- frontend/src/components/filter/__tests__/goalFilters.js | 3 ++- frontend/src/components/filter/goalFilters.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/filter/__tests__/goalFilters.js b/frontend/src/components/filter/__tests__/goalFilters.js index 17bd778001..564a8afe91 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..c70abb7177 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 From 640e2e3ae8aa70eaee7dee91424ffe04d5a0c993 Mon Sep 17 00:00:00 2001 From: nvms Date: Thu, 20 Jun 2024 12:33:21 -0400 Subject: [PATCH 16/16] change ", " to " - " per design review --- frontend/src/components/filter/__tests__/goalFilters.js | 2 +- frontend/src/components/filter/goalFilters.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/filter/__tests__/goalFilters.js b/frontend/src/components/filter/__tests__/goalFilters.js index 564a8afe91..f95b17ebc9 100644 --- a/frontend/src/components/filter/__tests__/goalFilters.js +++ b/frontend/src/components/filter/__tests__/goalFilters.js @@ -140,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, Active']); + 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 c70abb7177..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}, ${g.status}`, + label: `${g.numberWithProgramTypes} - ${g.status}`, }))} selectedValues={query} mapByValue