diff --git a/.circleci/config.yml b/.circleci/config.yml index 1507b79e58..9efd062c3a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -423,7 +423,7 @@ parameters: default: "mb/TTAHUB-2943/remove-models" type: string sandbox_git_branch: # change to feature branch to test deployment - default: "mb/TTAHUB-2530/update-RTR-objective-form" + default: "al-ttahub-2942-fix-number-of-goals" type: string prod_new_relic_app_id: default: "877570491" diff --git a/frontend/src/components/GoalCards/GoalCards.js b/frontend/src/components/GoalCards/GoalCards.js index 05dcc948b6..b3e0876b89 100644 --- a/frontend/src/components/GoalCards/GoalCards.js +++ b/frontend/src/components/GoalCards/GoalCards.js @@ -31,6 +31,7 @@ function GoalCards({ canMergeGoals, shouldDisplayMergeSuccess, dismissMergeSuccess, + goalBuckets, }) { const history = useHistory(); const [rttapaValidation, setRttapaValidation] = useState(false); @@ -38,7 +39,6 @@ function GoalCards({ // Goal select check boxes. const [selectedGoalCheckBoxes, setSelectedGoalCheckBoxes] = useState({}); const [allGoalsChecked, setAllGoalsChecked] = useState(false); - const [printAllGoals, setPrintAllGoals] = useState(false); // Close/Suspend Reason Modal. const [closeSuspendGoalIds, setCloseSuspendGoalIds] = useState([]); @@ -116,29 +116,35 @@ function GoalCards({ goalsArr.reduce((obj, g) => ({ ...obj, [g.id]: checked }), {}) ); - useEffect(() => { - const checkValues = Object.values(selectedGoalCheckBoxes); - if (checkValues.length - && (checkValues.length === goals.length || checkValues.length === goalsCount) - && checkValues.every((v) => v === true)) { - setAllGoalsChecked(true); - } else if (printAllGoals === true) { - setPrintAllGoals(false); - } - }, [selectedGoalCheckBoxes, allGoalsChecked, printAllGoals, goalsCount, goals.length]); - const selectAllGoalCheckboxSelect = (event) => { const { target: { checked = null } = {} } = event; + // Preserve checked goals on other pages. + const thisPagesGoalIds = goals.map((g) => g.id); + const preservedCheckboxes = Object.keys(selectedGoalCheckBoxes).reduce((obj, key) => { + if (!thisPagesGoalIds.includes(parseInt(key, DECIMAL_BASE))) { + return { ...obj, [key]: selectedGoalCheckBoxes[key] }; + } + return { ...obj }; + }, {}); + if (checked === true) { - setSelectedGoalCheckBoxes(makeGoalCheckboxes(goals, true)); + setSelectedGoalCheckBoxes({ ...makeGoalCheckboxes(goals, true), ...preservedCheckboxes }); + } else { + setSelectedGoalCheckBoxes({ ...makeGoalCheckboxes(goals, false), ...preservedCheckboxes }); + } + }; + + // Check if all goals on the page are checked. + useEffect(() => { + const goalIds = goals.map((g) => g.id); + const countOfCheckedOnThisPage = goalIds.filter((id) => selectedGoalCheckBoxes[id]).length; + if (goals.length === countOfCheckedOnThisPage) { setAllGoalsChecked(true); } else { - setSelectedGoalCheckBoxes(makeGoalCheckboxes(goals, false)); setAllGoalsChecked(false); - setPrintAllGoals(false); } - }; + }, [goals, selectedGoalCheckBoxes]); const handleGoalCheckboxSelect = (event) => { const { target: { checked = null, value = null } = {} } = event; @@ -149,10 +155,9 @@ function GoalCards({ } }; - const checkAllGoals = () => { - const allIdCheckBoxes = allGoalIds.reduce((obj, g) => ({ ...obj, [g]: true }), {}); + const checkAllGoals = (isClear) => { + const allIdCheckBoxes = allGoalIds.reduce((obj, g) => ({ ...obj, [g]: !isClear }), {}); setSelectedGoalCheckBoxes(allIdCheckBoxes); - setPrintAllGoals(true); }; const numberOfSelectedGoals = Object.values(selectedGoalCheckBoxes).filter((g) => g).length; @@ -164,14 +169,14 @@ function GoalCards({ const selectedGoalIdsButNumerical = selectedCheckBoxes.map((id) => parseInt(id, DECIMAL_BASE)); const draftSelectedRttapa = goals.filter((g) => selectedGoalIdsButNumerical.includes(g.id) && g.goalStatus === 'Draft').map((g) => g.id); - const allSelectedGoalIds = (() => { + const allSelectedPageGoalIds = (() => { const selection = goals.filter((g) => selectedGoalCheckBoxes[g.id]); - return selection.map((g) => g.ids).flat(); + return selection.map((g) => g.id); })(); const rttapaLink = (() => { if (selectedCheckBoxes && selectedCheckBoxes.length) { - const selectedGoalIdsQuery = allSelectedGoalIds.map((id) => `goalId[]=${encodeURIComponent(id)}`).join('&'); + const selectedGoalIdsQuery = allSelectedPageGoalIds.map((id) => `goalId[]=${encodeURIComponent(id)}`).join('&'); return `/recipient-tta-records/${recipientId}/region/${regionId}/rttapa/new?${selectedGoalIdsQuery}`; } @@ -232,7 +237,7 @@ function GoalCards({ allGoalsChecked={allGoalsChecked} selectAllGoalCheckboxSelect={selectAllGoalCheckboxSelect} selectAllGoals={checkAllGoals} - selectedGoalIds={allSelectedGoalIds} + pageSelectedGoalIds={allSelectedPageGoalIds} perPageChange={perPageChange} pageGoalIds={goals.map((g) => g.id)} showRttapaValidation={showRttapaValidation} @@ -241,6 +246,8 @@ function GoalCards({ canMergeGoals={canMergeGoals} shouldDisplayMergeSuccess={shouldDisplayMergeSuccess} dismissMergeSuccess={dismissMergeSuccess} + goalBuckets={goalBuckets} + allSelectedGoalIds={selectedGoalCheckBoxes} />
{goals.map((goal, index) => ( @@ -291,6 +298,10 @@ GoalCards.propTypes = { canMergeGoals: PropTypes.bool.isRequired, shouldDisplayMergeSuccess: PropTypes.bool, dismissMergeSuccess: PropTypes.func.isRequired, + goalBuckets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number, + goalIds: PropTypes.arrayOf(PropTypes.number), + })).isRequired, }; GoalCards.defaultProps = { diff --git a/frontend/src/components/GoalCards/GoalDataController.js b/frontend/src/components/GoalCards/GoalDataController.js index aaa8c74bab..d50dc898ed 100644 --- a/frontend/src/components/GoalCards/GoalDataController.js +++ b/frontend/src/components/GoalCards/GoalDataController.js @@ -132,7 +132,9 @@ function GoalDataController({ query, mergedGoals || [], ); - setData(response); + const rolledUpGoalIds = response.allGoalIds.map((goal) => goal.id); + const goalBuckets = response.allGoalIds; + setData({ ...response, allGoalIds: rolledUpGoalIds, goalBuckets }); setError(''); // display success message if we have merged goals setShouldDisplayMergedSuccess((mergedGoals && mergedGoals.length > 0)); @@ -259,6 +261,7 @@ function GoalDataController({ canMergeGoals={canMergeGoals} shouldDisplayMergeSuccess={shouldDisplayMergeSuccess} dismissMergeSuccess={dismissMergeSuccess} + goalBuckets={data.goalBuckets} />
diff --git a/frontend/src/components/GoalCards/GoalsCardsHeader.js b/frontend/src/components/GoalCards/GoalsCardsHeader.js index ad0c33b153..1086b88a46 100644 --- a/frontend/src/components/GoalCards/GoalsCardsHeader.js +++ b/frontend/src/components/GoalCards/GoalsCardsHeader.js @@ -30,7 +30,7 @@ export default function GoalCardsHeader({ allGoalsChecked, selectAllGoalCheckboxSelect, selectAllGoals, - selectedGoalIds, + pageSelectedGoalIds, perPageChange, pageGoalIds, showRttapaValidation, @@ -38,6 +38,8 @@ export default function GoalCardsHeader({ canMergeGoals, shouldDisplayMergeSuccess, dismissMergeSuccess, + allSelectedGoalIds, + goalBuckets, }) { const [goalMergeGroups, setGoalMergeGroups] = useState([]); const history = useHistory(); @@ -71,8 +73,22 @@ export default function GoalCardsHeader({ const showAddNewButton = hasActiveGrants && hasButtonPermissions; const onPrint = () => { + // See if we have goals selected. + let goalsToPrint = Object.keys(allSelectedGoalIds).filter( + (key) => allSelectedGoalIds[key], + ).map((key) => parseInt(key, DECIMAL_BASE)); + + // If we don't just print the page. + if (!goalsToPrint.length) { + goalsToPrint = pageGoalIds; + } + // Get all the goals and associated goals from the buckets. + goalsToPrint = goalBuckets.filter( + (bucket) => goalsToPrint.includes(bucket.id), + ).map((bucket) => bucket.goalIds).flat(); + history.push(`/recipient-tta-records/${recipientId}/region/${regionId}/rttapa/print${window.location.search}`, { - sortConfig, selectedGoalIds: !selectedGoalIds.length ? pageGoalIds : selectedGoalIds, + sortConfig, selectedGoalIds: goalsToPrint, }); }; @@ -89,6 +105,9 @@ export default function GoalCardsHeader({ return null; })(); + const hasGoalsSelected = pageSelectedGoalIds ? pageSelectedGoalIds.length > 0 : false; + const showClearAllAlert = numberOfSelectedGoals === count; + return (
@@ -199,7 +218,7 @@ export default function GoalCardsHeader({ className="display-flex flex-align-center margin-left-3 margin-y-0" onClick={onPrint} > - {`Preview and print ${selectedGoalIds.length > 0 ? 'selected' : ''}`} + {`Preview and print ${hasGoalsSelected ? 'selected' : ''}`}
@@ -222,16 +241,20 @@ export default function GoalCardsHeader({ )} { - !showRttapaValidation && allGoalsChecked && (numberOfSelectedGoals !== count) + !showRttapaValidation && allGoalsChecked ? ( - {`All ${numberOfSelectedGoals} goals on this page are selected.`} + {showClearAllAlert + ? `All ${count} goals are selected.` + : `All ${pageSelectedGoalIds.length} goals on this page are selected.`} ) @@ -287,7 +310,7 @@ GoalCardsHeader.propTypes = { allGoalsChecked: PropTypes.bool, numberOfSelectedGoals: PropTypes.number, selectAllGoals: PropTypes.func, - selectedGoalIds: PropTypes.arrayOf(PropTypes.string).isRequired, + pageSelectedGoalIds: PropTypes.arrayOf(PropTypes.number).isRequired, perPageChange: PropTypes.func.isRequired, pageGoalIds: PropTypes.oneOfType( [PropTypes.arrayOf(PropTypes.number), PropTypes.number], @@ -297,6 +320,11 @@ GoalCardsHeader.propTypes = { canMergeGoals: PropTypes.bool.isRequired, shouldDisplayMergeSuccess: PropTypes.bool, dismissMergeSuccess: PropTypes.func.isRequired, + allSelectedGoalIds: PropTypes.shape({ id: PropTypes.bool }).isRequired, + goalBuckets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number, + goals: PropTypes.arrayOf(PropTypes.number), + })).isRequired, }; GoalCardsHeader.defaultProps = { diff --git a/frontend/src/components/GoalCards/__tests__/GoalCards.js b/frontend/src/components/GoalCards/__tests__/GoalCards.js index 7edad74bdc..31fb57d644 100644 --- a/frontend/src/components/GoalCards/__tests__/GoalCards.js +++ b/frontend/src/components/GoalCards/__tests__/GoalCards.js @@ -36,7 +36,7 @@ const defaultUser = { const baseGoals = [{ id: 4598, - ids: [4598], + ids: [4598, 4599], goalStatus: 'In Progress', createdOn: '2021-06-15', goalText: 'This is goal text 1.', @@ -220,6 +220,7 @@ const setGoals = jest.fn(); const history = createMemoryHistory(); const renderTable = ({ goals, goalsCount, allGoalIds = null }, user, hasActiveGrants = true) => { + const goalBuckets = !goals ? [] : goals.map((g) => ({ id: g.id, goalIds: g.ids })); render( @@ -246,6 +247,7 @@ const renderTable = ({ goals, goalsCount, allGoalIds = null }, user, hasActiveGr allGoalIds={allGoalIds || goals.map((g) => g.id)} shouldDisplayMergeSuccess={false} dismissMergeSuccess={jest.fn()} + goalBuckets={goalBuckets} /> @@ -489,6 +491,27 @@ describe('Goals Table', () => { expect(screen.queryByText(/7 selected/i)).toBeNull(); }); + it('Shows the clear selection button and clears when clicked', async () => { + const selectAll = await screen.findByRole('checkbox', { name: /deselect all goals/i }); + fireEvent.click(selectAll); + expect(await screen.findByText(/6 selected/i)).toBeVisible(); + + const selectAllPages = await screen.findByRole('button', { name: /select all 7 goals/i }); + fireEvent.click(selectAllPages); + + expect(screen.queryByText(/7 selected/i)).toBeVisible(); + + const clearSelection = await screen.findByRole('button', { name: /clear selection/i }); + fireEvent.click(clearSelection); + + expect(screen.queryByText(/7 selected/i)).toBeNull(); + // verify all check boxes are unchecked. + const checkBoxes = screen.queryAllByTestId('selectGoalTestId'); + checkBoxes.forEach((checkBox) => { + expect(checkBox.checked).toBe(false); + }); + }); + it('Deselect via pill', async () => { const selectAll = await screen.findByRole('checkbox', { name: /deselect all goals/i }); fireEvent.click(selectAll); @@ -604,6 +627,35 @@ describe('Goals Table', () => { expect(history.push).toHaveBeenCalled(); }); + + it('calls print passing all goal ids on the page', async () => { + // print goals + const printButton = await screen.findByRole('button', { name: /Preview and print/i }); + userEvent.click(printButton); + expect(history.push).toHaveBeenCalledWith('/recipient-tta-records/1000/region/1/rttapa/print', { + selectedGoalIds: [4598, 4599, 65479], + sortConfig: { + activePage: 1, direction: 'asc', offset: 0, sortBy: 'goalStatus', + }, + }); + }); + + it('calls print passing all selected goal ids', async () => { + // print goals + const printButton = await screen.findByRole('button', { name: /Preview and print/i }); + + // select the checkbox with the value of 4598. + const checkBox = screen.queryAllByTestId('selectGoalTestId')[0]; + fireEvent.click(checkBox); + + userEvent.click(printButton); + expect(history.push).toHaveBeenCalledWith('/recipient-tta-records/1000/region/1/rttapa/print', { + selectedGoalIds: [4598, 4599], + sortConfig: { + activePage: 1, direction: 'asc', offset: 0, sortBy: 'goalStatus', + }, + }); + }); }); describe('Context Menu with Different User Permissions', () => { diff --git a/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js b/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js index 60009aaac8..92352568d0 100644 --- a/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js +++ b/frontend/src/pages/RecipientRecord/pages/__tests__/GoalsObjectives.js @@ -145,17 +145,37 @@ describe('Goals and Objectives', () => { fetchMock.reset(); // Default. const goalsUrl = `/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10&createDate.win=${yearToDate}`; - fetchMock.get(goalsUrl, { count: 1, goalRows: goals, statuses: defaultStatuses }); + fetchMock.get(goalsUrl, { + count: 1, + goalRows: goals, + statuses: defaultStatuses, + allGoalIds: [ + { id: goals[0].id, goalIds: goals[0].ids }, + ], + }); // Filters Status. const filterStatusUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10&status.in[]=Not%20started'; fetchMock.get(filterStatusUrl, { - count: 1, goalRows: filterStatusGoals, statuses: defaultStatuses, + count: 1, + goalRows: filterStatusGoals, + statuses: defaultStatuses, + allGoalIds: [ + { id: filterStatusGoals[0].id, goalIds: filterStatusGoals[0].ids }, + ], }); // No Filters. const noFilterUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10'; - fetchMock.get(noFilterUrl, { count: 2, goalRows: noFilterGoals, statuses: defaultStatuses }); + fetchMock.get(noFilterUrl, { + count: 2, + goalRows: noFilterGoals, + statuses: defaultStatuses, + allGoalIds: [ + { id: noFilterGoals[0].id, goalIds: noFilterGoals[0].ids }, + { id: noFilterGoals[1].id, goalIds: noFilterGoals[1].ids }, + ], + }); fetchMock.get( '/api/communication-logs/region/1/recipient/401?sortBy=communicationDate&direction=desc&offset=0&limit=5&format=json&purpose.in[]=RTTAPA%20updates&purpose.in[]=RTTAPA%20Initial%20Plan%20%2F%20New%20Recipient', @@ -243,7 +263,7 @@ describe('Goals and Objectives', () => { fetchMock.get( '/api/communication-logs/region/1/recipient/401?sortBy=communicationDate&direction=desc&offset=0&limit=5&format=json&purpose.in[]=RTTAPA%20updates&purpose.in[]=RTTAPA%20Initial%20Plan%20%2F%20New%20Recipient', - { rows: [], count: 0 }, + { rows: [], count: 0, allGoalIds: [] }, ); const response = [{ @@ -262,7 +282,12 @@ describe('Goals and Objectives', () => { ]; const noFilterUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10'; - fetchMock.get(noFilterUrl, { count: 2, goalRows: response, statuses: defaultStatuses }); + fetchMock.get(noFilterUrl, { + count: 2, + goalRows: response, + statuses: defaultStatuses, + allGoalIds: [{ id: 4598, goalIds: [4598] }], + }); act(() => renderGoalsAndObjectives()); @@ -305,6 +330,19 @@ describe('Goals and Objectives', () => { { ...goals[0], id: 3 }, ], statuses: defaultStatuses, + allGoalIds: [{ + id: 1, + goalIds: [1], + }, + { + id: 2, + goalIds: [2], + }, + { + id: 3, + goalIds: [3], + }, + ], }); act(() => renderGoalsAndObjectives([1])); // If api request contains 3 we know it included the desired sort. @@ -331,7 +369,7 @@ describe('Goals and Objectives', () => { fetchMock.get( '/api/communication-logs/region/1/recipient/401?sortBy=communicationDate&direction=desc&offset=0&limit=5&format=json&purpose.in[]=RTTAPA%20updates&purpose.in[]=RTTAPA%20Initial%20Plan%20%2F%20New%20Recipient', - { rows: [], count: 0 }, + { rows: [], count: 0, allGoalIds: [] }, ); const goalToUse = { id: 1, @@ -348,10 +386,12 @@ describe('Goals and Objectives', () => { }; const goalCount = 60; const goalsToDisplay = []; + const allGoalIds = []; // eslint-disable-next-line no-plusplus for (let i = 1; i <= goalCount; i++) { const goalText = `This is goal text ${i}.`; goalsToDisplay.push({ ...goalToUse, id: i, goalText }); + allGoalIds.push({ id: i, goalIds: [i] }); } const noFilterUrl = '/api/recipient/401/region/1/goals?sortBy=goalStatus&sortDir=asc&offset=0&limit=10'; fetchMock.get(noFilterUrl, @@ -359,6 +399,7 @@ describe('Goals and Objectives', () => { count: goalCount, goalRows: goalsToDisplay.slice(0, 10), statuses: defaultStatuses, + allGoalIds, }); // Render. @@ -376,6 +417,7 @@ describe('Goals and Objectives', () => { count: goalCount, goalRows: goalsToDisplay.slice(0, 25), statuses: defaultStatuses, + allGoalIds, }); const perPageDropDown = await screen.findByRole('combobox', { name: /select goals per page/i }); userEvent.selectOptions(perPageDropDown, '25'); @@ -385,4 +427,146 @@ describe('Goals and Objectives', () => { goalsPerPage = screen.queryAllByTestId('goalCard'); expect(goalsPerPage.length).toBe(25); }); + + it('respects select all on a per page basis', async () => { + const goalUrl = '/api/recipient/401/region/1/goals?sortBy=createdOn&sortDir=desc&offset=0&limit=10'; + fetchMock.get(goalUrl, { + count: 12, + goalRows: [ + { ...goals[0], id: 1 }, + { ...goals[0], id: 2 }, + { ...goals[0], id: 3 }, + { ...goals[0], id: 4 }, + { ...goals[0], id: 5 }, + { ...goals[0], id: 6 }, + { ...goals[0], id: 7 }, + { ...goals[0], id: 8 }, + { ...goals[0], id: 9 }, + { ...goals[0], id: 10 }, + ], + statuses: defaultStatuses, + allGoalIds: [{ + id: 1, + goalIds: [1], + }, + { + id: 2, + goalIds: [2], + }, + { + id: 3, + goalIds: [3], + }, + { + id: 4, + goalIds: [4], + }, + { + id: 5, + goalIds: [5], + }, + { + id: 6, + goalIds: [6], + }, + { + id: 7, + goalIds: [7], + }, + { + id: 8, + goalIds: [8], + }, + { + id: 9, + goalIds: [9], + }, + { + id: 10, + goalIds: [10], + }, + ], + }); + act(() => renderGoalsAndObjectives([1])); + expect(await screen.findByText(/1-10 of 12/i)).toBeVisible(); + + // Select All. + const selectAll = await screen.findByRole('checkbox', { name: /select all goals/i }); + userEvent.click(selectAll); + + // Assert all are selected. + const checkboxes = screen.queryAllByRole('checkbox', { name: /select goal/i }); + expect(checkboxes.length).toBe(10); + checkboxes.forEach((checkbox) => { + expect(checkbox).toBeChecked(); + }); + + // Shows 10 selected. + expect(await screen.findByText(/10 selected/i)).toBeVisible(); + + // Change per page. + const goalUrlMore = '/api/recipient/401/region/1/goals?sortBy=createdOn&sortDir=desc&offset=10&limit=10'; + fetchMock.get(goalUrlMore, { + count: 12, + goalRows: [ + { ...goals[0], id: 11 }, + { ...goals[0], id: 12 }, + ], + statuses: defaultStatuses, + allGoalIds: [{ + id: 11, + goalIds: [11], + }, + { + id: 12, + goalIds: [12], + }, + ], + }); + + // Click page 2. + const pageTwo = await screen.findByRole('link', { name: /go to page number 2/i }); + userEvent.click(pageTwo); + + expect(await screen.findByText(/11-12 of 12/i)).toBeVisible(); + + // Shows 10 selected. + expect(await screen.findByText(/10 selected/i)).toBeVisible(); + + // Assert all selected is NOT checked. + const selectAllNext = await screen.findByRole('checkbox', { name: /select all goals/i }); + expect(selectAllNext).not.toBeChecked(); + + // Get all the checkboxes. + const checkboxesNext = screen.queryAllByRole('checkbox', { name: /select goal/i }); + expect(checkboxesNext.length).toBe(2); + + // Check the second one. + userEvent.click(checkboxesNext[1]); + + // Shows 11 selected. + expect(await screen.findByText(/11 selected/i)).toBeVisible(); + + // Select All. + userEvent.click(selectAllNext); + + // Assert all are selected. + const checkboxesNextAll = screen.queryAllByRole('checkbox', { name: /select goal/i }); + expect(checkboxesNextAll.length).toBe(2); + checkboxesNextAll.forEach((checkbox) => { + expect(checkbox).toBeChecked(); + }); + + // Shows 12 selected. + expect(await screen.findByText(/12 selected/i)).toBeVisible(); + + // Uncheck the second checkbox. + userEvent.click(checkboxesNext[1]); + + // Assert the select all check box is not checked. + expect(selectAllNext).not.toBeChecked(); + + // Shows 11 selected. + expect(await screen.findByText(/11 selected/i)).toBeVisible(); + }); }); diff --git a/src/services/recipient.js b/src/services/recipient.js index e82efd901a..dea3aa3633 100644 --- a/src/services/recipient.js +++ b/src/services/recipient.js @@ -894,12 +894,21 @@ export async function getGoalsByActivityRecipient( }, }); + // For checkbox selection we only need the primary goal id. + const rolledUpGoalIds = r.goalRows.map((goal) => { + const bucket = { + id: goal.id, + goalIds: goal.ids, + }; + return bucket; + }); + if (limitNum) { return { count: r.goalRows.length, goalRows: r.goalRows.slice(offSetNum, offSetNum + limitNum), statuses, - allGoalIds, + allGoalIds: rolledUpGoalIds, }; } @@ -907,7 +916,7 @@ export async function getGoalsByActivityRecipient( count: r.goalRows.length, goalRows: r.goalRows.slice(offSetNum), statuses, - allGoalIds, + allGoalIds: rolledUpGoalIds, }; } diff --git a/src/services/recipient.test.js b/src/services/recipient.test.js index 0c0888b8ee..ddc9c7ccc9 100644 --- a/src/services/recipient.test.js +++ b/src/services/recipient.test.js @@ -874,8 +874,13 @@ describe('Recipient DB service', () => { }); it('properly de-duplicates based on responses', async () => { - const { goalRows } = await getGoalsByActivityRecipient(recipient.id, region, {}); + const { goalRows, allGoalIds } = await getGoalsByActivityRecipient(recipient.id, region, {}); expect(goalRows.length).toBe(3); + expect(allGoalIds.length).toBe(3); + + const goalWithMultipleIds = allGoalIds.find((g) => g.id === goals[2].id); + expect(goalWithMultipleIds).not.toBeNull(); + expect(goalWithMultipleIds.goalIds).toStrictEqual([goals[2].id, goals[1].id]); const doubler = goalRows.find((r) => r.responsesForComparison === 'not sure,dont have to'); expect(doubler).toBeTruthy(); @@ -896,8 +901,9 @@ describe('Recipient DB service', () => { goals[0].destroy(); goals[3].destroy(); - const { goalRows } = await getGoalsByActivityRecipient(recipient.id, region, {}); + const { goalRows, allGoalIds } = await getGoalsByActivityRecipient(recipient.id, region, {}); expect(goalRows.length).toBe(1); + expect(allGoalIds.length).toBe(1); // Verify goal 2 and 3 have empty creators/collaborators expect(goalRows[0].collaborators[0].goalCreator).toBe(undefined); // Verify goal 2 and 3 are rolled up @@ -1063,7 +1069,7 @@ describe('Recipient DB service', () => { expect(goalsForRecord.count).toBe(1); expect(goalsForRecord.goalRows.length).toBe(1); - expect(goalsForRecord.allGoalIds.length).toBe(2); + expect(goalsForRecord.allGoalIds.length).toBe(1); expect(goalsForRecord.goalRows.flatMap((g) => g.goalTopics)).toHaveLength(4); const goal = goalsForRecord.goalRows[0];