Skip to content

Commit 5a23a91

Browse files
committed
feat: [AB#17075] auto promotion for non essential questions associated with anytime actions to recommended for you category
1 parent bc6b5fc commit 5a23a91

File tree

5 files changed

+136
-39
lines changed

5 files changed

+136
-39
lines changed

shared/src/types/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ export interface AnytimeActionTask extends AnytimeAction {
165165
callToActionText?: string;
166166
issuingAgency?: string;
167167
moveToRecommendedForYouSection?: boolean;
168-
nonEssentialQuestionsMoveToRecommendedAnytimeActionIds?: string[];
169168
industryIds: string[];
170169
sectorIds: string[];
171170
applyToAllUsers: boolean;

web/decap-config/collections/05-roadmap.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,12 @@ collections:
319319
value_field: "{{fields.id}}"
320320
display_fields: [displayname]
321321
options_length: 9999
322-
- label: Anytime Actions Tasks
322+
- label: Anytime Actions Tasks (Added when Non Essential Question is Answered Yes)
323323
name: anytimeActions
324-
hint: Anytime Actions related to reinstatements are not included in this dropdown
324+
hint:
325+
(Anytime Actions chosen will automatically be displayed in the Recommended for You
326+
section for the user)(Anytime Actions related to reinstatements are not included
327+
in this dropdown)
325328
required: false
326329
default: []
327330
widget: relation

web/decap-config/collections/10-anytime-action.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,6 @@ collections:
9292
widget: "boolean",
9393
required: false,
9494
}
95-
- label:
96-
Non Essential Questions which will move this to recommended for you section if answered
97-
yes
98-
name: nonEssentialQuestionsMoveToRecommendedAnytimeActionIds
99-
widget: relation
100-
multiple: true
101-
required: false
102-
default: []
103-
collection: "nonEssentialQuestionsCollection"
104-
search_fields: ["nonEssentialQuestionsArray.*.id"]
105-
value_field: "nonEssentialQuestionsArray.*.id"
106-
display_fields: ["nonEssentialQuestionsArray.*.id"]
10795
- {
10896
label: "Url Slug - Internal Routing",
10997
name: "urlSlug",

web/src/components/dashboard/AnytimeActionSearch.test.tsx

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import { fireEvent, render, screen } from "@testing-library/react";
2020

2121
import { AnytimeActionSearch } from "@/components/dashboard/AnytimeActionSearch";
22-
import { taskIdLicenseNameMapping } from "@businessnjgovnavigator/shared/index";
22+
import { getMergedConfig, taskIdLicenseNameMapping } from "@businessnjgovnavigator/shared/index";
2323
import userEvent from "@testing-library/user-event";
2424

2525
function setupMockAnalytics(): typeof analytics {
@@ -50,6 +50,8 @@ describe("<AnytimeActionSearch />", () => {
5050
const taskName = "some-task-name";
5151
const licenseReinstatementName = "some-license-reinstatement-name";
5252

53+
const Config = getMergedConfig();
54+
5355
const anytimeActionTasksAlternate = [
5456
generateAnytimeActionTask({
5557
name: "test-title-1",
@@ -226,6 +228,7 @@ describe("<AnytimeActionSearch />", () => {
226228
filename: "some-filename-license",
227229
}),
228230
];
231+
anytimeActionTasksFromNonEssentialQuestions = [];
229232
});
230233

231234
const renderAnytimeActionSearch = (): void => {
@@ -398,6 +401,60 @@ describe("<AnytimeActionSearch />", () => {
398401
expect(screen.queryByText("license - hvac-reinstatement")).not.toBeInTheDocument();
399402
});
400403

404+
it("creates and moves anytime action to recommended for you section when flag is set", () => {
405+
anytimeActionTasks = [
406+
generateAnytimeActionTask({
407+
filename: "vacant-building-fire-permit",
408+
moveToRecommendedForYouSection: true,
409+
}),
410+
...anytimeActionTasks,
411+
];
412+
anytimeActionTasksFromNonEssentialQuestions = [];
413+
414+
useMockBusiness({
415+
profileData: generateProfileData({
416+
vacantPropertyOwner: true,
417+
}),
418+
});
419+
renderAnytimeActionSearch();
420+
fireEvent.click(screen.getByLabelText("Open"));
421+
const nonEssentialAnytimeAction = screen.getByTestId("vacant-building-fire-permit-option");
422+
const recommendedForYouCategory = screen.getByText(
423+
Config.dashboardAnytimeActionDefaults.recommendedForYouCategoryHeader,
424+
);
425+
const category1Title = screen.getByText("Some Category");
426+
expect(recommendedForYouCategory.compareDocumentPosition(nonEssentialAnytimeAction)).toBe(
427+
Node.DOCUMENT_POSITION_FOLLOWING,
428+
);
429+
expect(nonEssentialAnytimeAction.compareDocumentPosition(category1Title)).toBe(
430+
Node.DOCUMENT_POSITION_FOLLOWING,
431+
);
432+
});
433+
434+
it("does not creates recommended for you section when flag is set and nonEssentialQuestions is empty", () => {
435+
anytimeActionTasks = [
436+
generateAnytimeActionTask({
437+
filename: "vacant-building-fire-permit",
438+
moveToRecommendedForYouSection: false,
439+
}),
440+
...anytimeActionTasks,
441+
];
442+
443+
anytimeActionTasksFromNonEssentialQuestions = [];
444+
445+
useMockBusiness({
446+
profileData: generateProfileData({
447+
vacantPropertyOwner: true,
448+
}),
449+
});
450+
renderAnytimeActionSearch();
451+
fireEvent.click(screen.getByLabelText("Open"));
452+
expect(screen.getByTestId("vacant-building-fire-permit-option")).toBeInTheDocument();
453+
expect(
454+
screen.queryByText(Config.dashboardAnytimeActionDefaults.recommendedForYouCategoryHeader),
455+
).not.toBeInTheDocument();
456+
});
457+
401458
describe("non essential questions", () => {
402459
it("duplicate Anytime Actions added via non-essential questions are removed", () => {
403460
const duplicateAnytimeAction = generateAnytimeActionTask({
@@ -413,10 +470,35 @@ describe("<AnytimeActionSearch />", () => {
413470
expect(screen.getAllByText("same-anytime-action").length).toBe(1);
414471
});
415472

473+
it("promotes non essential questions to the recommended for you section", () => {
474+
anytimeActionTasksFromNonEssentialQuestions = [
475+
generateAnytimeActionTask({ filename: "vacant-building-fire-permit" }),
476+
...anytimeActionTasksFromNonEssentialQuestions,
477+
];
478+
479+
useMockBusiness({
480+
profileData: generateProfileData({
481+
vacantPropertyOwner: true,
482+
}),
483+
});
484+
renderAnytimeActionSearch();
485+
fireEvent.click(screen.getByLabelText("Open"));
486+
const nonEssentialAnytimeAction = screen.getByTestId("vacant-building-fire-permit-option");
487+
const recommendedForYouCategory = screen.getByText(
488+
Config.dashboardAnytimeActionDefaults.recommendedForYouCategoryHeader,
489+
);
490+
const category1Title = screen.getByText("Some Category");
491+
expect(recommendedForYouCategory.compareDocumentPosition(nonEssentialAnytimeAction)).toBe(
492+
Node.DOCUMENT_POSITION_FOLLOWING,
493+
);
494+
expect(nonEssentialAnytimeAction.compareDocumentPosition(category1Title)).toBe(
495+
Node.DOCUMENT_POSITION_FOLLOWING,
496+
);
497+
});
498+
416499
it("adds vacant property anytime action for vacant property owners", () => {
417500
anytimeActionTasks = [
418501
generateAnytimeActionTask({ filename: "vacant-building-fire-permit" }),
419-
...anytimeActionTasks,
420502
];
421503
useMockBusiness({
422504
profileData: generateProfileData({
@@ -440,7 +522,6 @@ describe("<AnytimeActionSearch />", () => {
440522
it("adds fire carnival modification for carnival ride owning businesses", () => {
441523
anytimeActionTasks = [
442524
generateAnytimeActionTask({ filename: "carnival-ride-supplemental-modification" }),
443-
...anytimeActionTasks,
444525
];
445526
useMockBusiness({
446527
profileData: generateProfileData({
@@ -468,7 +549,6 @@ describe("<AnytimeActionSearch />", () => {
468549
it("adds operating carnival fire permit for carnival owning businesses", () => {
469550
anytimeActionTasks = [
470551
generateAnytimeActionTask({ filename: "operating-carnival-fire-permit" }),
471-
...anytimeActionTasks,
472552
];
473553
useMockBusiness({
474554
profileData: generateProfileData({
@@ -496,7 +576,6 @@ describe("<AnytimeActionSearch />", () => {
496576
it("adds operating carnival fire permit for traveling circus or carnival owning businesses", () => {
497577
anytimeActionTasks = [
498578
generateAnytimeActionTask({ filename: "operating-carnival-fire-permit" }),
499-
...anytimeActionTasks,
500579
];
501580
useMockBusiness({
502581
profileData: generateProfileData({
@@ -524,7 +603,6 @@ describe("<AnytimeActionSearch />", () => {
524603
it("adds operating carnival fire permit for traveling circus or carnival owning businesses based on non-essential question answers", () => {
525604
anytimeActionTasks = [
526605
generateAnytimeActionTask({ filename: "operating-carnival-fire-permit" }),
527-
...anytimeActionTasks,
528606
];
529607
useMockBusiness({
530608
profileData: generateProfileData({
@@ -560,7 +638,6 @@ describe("<AnytimeActionSearch />", () => {
560638
it("adds carnival ride supplemental modification for traveling circus or carnival owning businesses based on non-essential question answers", () => {
561639
anytimeActionTasks = [
562640
generateAnytimeActionTask({ filename: "carnival-ride-supplemental-modification" }),
563-
...anytimeActionTasks,
564641
];
565642
useMockBusiness({
566643
profileData: generateProfileData({

web/src/components/dashboard/AnytimeActionSearch.tsx

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
AnytimeActionTask,
1313
} from "@businessnjgovnavigator/shared/types";
1414
import { Autocomplete, TextField, useMediaQuery } from "@mui/material";
15-
import { orderBy, unionBy } from "lodash";
15+
import { orderBy } from "lodash";
1616
import { useRouter } from "next/compat/router";
1717
import { type ReactElement, ReactNode, useState } from "react";
1818

@@ -73,21 +73,28 @@ export const AnytimeActionSearch = (props: Props): ReactElement => {
7373
}),
7474
];
7575

76-
const deduplicatedTasks = unionBy(
76+
const deduplicatedTasks = removeNonEssentialAnytimeActionTasksFromGenericAnytimeActionList(
7777
tasks,
7878
props.anytimeActionTasksFromNonEssentialQuestions,
79-
"name",
8079
);
8180

82-
const orderedTasks = orderBy(deduplicatedTasks, ["name"]);
83-
const orderedReinstatements = orderBy(reinstatements, ["name"]);
81+
const nonEssentialAnytimeActionsWithRecommendedCategory =
82+
moveNonEssentialAnyTimeActionsToRecommendeForYou(
83+
props.anytimeActionTasksFromNonEssentialQuestions,
84+
);
85+
const allTasks = [...nonEssentialAnytimeActionsWithRecommendedCategory, ...deduplicatedTasks];
8486

85-
const allActions = [...orderedTasks, ...orderedReinstatements];
87+
const allActions = [...allTasks, ...reinstatements];
88+
const allActionsOrdered = orderBy(allActions, ["name"]);
8689

87-
return allActions.sort((a, b) => {
90+
return allActionsOrdered.sort((a, b) => {
8891
const categoryA = a.category[0].categoryName.toLowerCase();
8992
const categoryB = b.category[0].categoryName.toLowerCase();
9093

94+
if (categoryA === categoryB) {
95+
return 0;
96+
}
97+
9198
if (categoryA === RECOMMENDED_FOR_YOU_DISPLAY_TEXT.toLowerCase()) {
9299
return -1;
93100
}
@@ -141,6 +148,39 @@ export const AnytimeActionSearch = (props: Props): ReactElement => {
141148
Config.dashboardAnytimeActionDefaults.recommendedForYouCategoryHeader;
142149
const RECOMMENDED_FOR_YOU_ID = "recommended-for-you";
143150

151+
const moveNonEssentialAnyTimeActionsToRecommendeForYou = (
152+
nonEssentialAnytimeActions: AnytimeActionTask[],
153+
): AnytimeActionTask[] => {
154+
const nonEssentialAnytimeActionsRecommendedForYouCategorysOverriden =
155+
nonEssentialAnytimeActions;
156+
for (const [index] of nonEssentialAnytimeActions.entries()) {
157+
nonEssentialAnytimeActionsRecommendedForYouCategorysOverriden[
158+
index
159+
].category[0].categoryName = RECOMMENDED_FOR_YOU_DISPLAY_TEXT;
160+
nonEssentialAnytimeActionsRecommendedForYouCategorysOverriden[index].category[0].categoryId =
161+
RECOMMENDED_FOR_YOU_ID;
162+
}
163+
return nonEssentialAnytimeActionsRecommendedForYouCategorysOverriden;
164+
};
165+
166+
const removeNonEssentialAnytimeActionTasksFromGenericAnytimeActionList = (
167+
anytimeActionsRoot: AnytimeActionTask[],
168+
anytimeActionsToRemoveFromRoot: AnytimeActionTask[],
169+
): AnytimeActionTask[] => {
170+
const anytimeActionsToRemoveFromRootUrlSlugs = new Set(
171+
anytimeActionsToRemoveFromRoot.map((anytimeAction) => anytimeAction.urlSlug),
172+
);
173+
174+
const anytimeActionsRootRemoved = anytimeActionsRoot.filter((anytimeAction) => {
175+
if (anytimeActionsToRemoveFromRootUrlSlugs.has(anytimeAction.urlSlug)) {
176+
return false;
177+
}
178+
return true;
179+
});
180+
181+
return anytimeActionsRootRemoved;
182+
};
183+
144184
const moveAnytimeActionsToRecommendedForYouSection = (
145185
anytimeActions: AnytimeActionTask[],
146186
): AnytimeActionTask[] => {
@@ -156,16 +196,6 @@ export const AnytimeActionSearch = (props: Props): ReactElement => {
156196
anytimeActionsWithRecommendedForYouCategorysOverriden[index].category[0].categoryId =
157197
RECOMMENDED_FOR_YOU_ID;
158198
}
159-
if (anytimeAction.nonEssentialQuestionsMoveToRecommendedAnytimeActionIds) {
160-
for (const nonEssentialQuestionId of anytimeAction.nonEssentialQuestionsMoveToRecommendedAnytimeActionIds) {
161-
if (business?.profileData.nonEssentialRadioAnswers[nonEssentialQuestionId]) {
162-
anytimeActionsWithRecommendedForYouCategorysOverriden[index].category[0].categoryName =
163-
RECOMMENDED_FOR_YOU_DISPLAY_TEXT;
164-
anytimeActionsWithRecommendedForYouCategorysOverriden[index].category[0].categoryId =
165-
RECOMMENDED_FOR_YOU_ID;
166-
}
167-
}
168-
}
169199
}
170200

171201
return anytimeActionsWithRecommendedForYouCategorysOverriden;

0 commit comments

Comments
 (0)