Skip to content

Commit

Permalink
Merge pull request #2191 from HHS/main
Browse files Browse the repository at this point in the history
[PROD-06/05]  [TTAHUB-2942] Fix RTR goal selection
  • Loading branch information
Jones-QuarteyDana authored Jun 5, 2024
2 parents 358ad1d + b144936 commit c1d4117
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
57 changes: 34 additions & 23 deletions frontend/src/components/GoalCards/GoalCards.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ function GoalCards({
canMergeGoals,
shouldDisplayMergeSuccess,
dismissMergeSuccess,
goalBuckets,
}) {
const history = useHistory();
const [rttapaValidation, setRttapaValidation] = useState(false);

// 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([]);
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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}`;
}

Expand Down Expand Up @@ -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}
Expand All @@ -241,6 +246,8 @@ function GoalCards({
canMergeGoals={canMergeGoals}
shouldDisplayMergeSuccess={shouldDisplayMergeSuccess}
dismissMergeSuccess={dismissMergeSuccess}
goalBuckets={goalBuckets}
allSelectedGoalIds={selectedGoalCheckBoxes}
/>
<div className="padding-x-3 padding-y-2">
{goals.map((goal, index) => (
Expand Down Expand Up @@ -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 = {
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/GoalCards/GoalDataController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -259,6 +261,7 @@ function GoalDataController({
canMergeGoals={canMergeGoals}
shouldDisplayMergeSuccess={shouldDisplayMergeSuccess}
dismissMergeSuccess={dismissMergeSuccess}
goalBuckets={data.goalBuckets}
/>
</FilterContext.Provider>
</div>
Expand Down
44 changes: 36 additions & 8 deletions frontend/src/components/GoalCards/GoalsCardsHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ export default function GoalCardsHeader({
allGoalsChecked,
selectAllGoalCheckboxSelect,
selectAllGoals,
selectedGoalIds,
pageSelectedGoalIds,
perPageChange,
pageGoalIds,
showRttapaValidation,
draftSelectedRttapa,
canMergeGoals,
shouldDisplayMergeSuccess,
dismissMergeSuccess,
allSelectedGoalIds,
goalBuckets,
}) {
const [goalMergeGroups, setGoalMergeGroups] = useState([]);
const history = useHistory();
Expand Down Expand Up @@ -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,
});
};

Expand All @@ -89,6 +105,9 @@ export default function GoalCardsHeader({
return null;
})();

const hasGoalsSelected = pageSelectedGoalIds ? pageSelectedGoalIds.length > 0 : false;
const showClearAllAlert = numberOfSelectedGoals === count;

return (
<div className="padding-x-3 position-relative">
<div className="desktop:display-flex flex-1 desktop:padding-top-0 padding-top-2 bg-white">
Expand Down Expand Up @@ -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' : ''}`}
</Button>
</div>
<div>
Expand All @@ -222,16 +241,20 @@ export default function GoalCardsHeader({
</Alert>
)}
{
!showRttapaValidation && allGoalsChecked && (numberOfSelectedGoals !== count)
!showRttapaValidation && allGoalsChecked
? (
<Alert className="margin-top-3" type="info" slim>
{`All ${numberOfSelectedGoals} goals on this page are selected.`}
{showClearAllAlert
? `All ${count} goals are selected.`
: `All ${pageSelectedGoalIds.length} goals on this page are selected.`}
<button
type="button"
className="usa-button usa-button--unstyled margin-left-1"
onClick={selectAllGoals}
onClick={() => selectAllGoals(showClearAllAlert)}
>
{`Select all ${count} goals`}
{showClearAllAlert
? 'Clear selection'
: `Select all ${count} goals`}
</button>
</Alert>
)
Expand Down Expand Up @@ -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],
Expand All @@ -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 = {
Expand Down
54 changes: 53 additions & 1 deletion frontend/src/components/GoalCards/__tests__/GoalCards.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down Expand Up @@ -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(
<Router history={history}>
<AriaLiveContext.Provider value={{ announce: mockAnnounce }}>
Expand All @@ -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}
/>
</UserContext.Provider>
</AriaLiveContext.Provider>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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', () => {
Expand Down
Loading

0 comments on commit c1d4117

Please sign in to comment.