From 4fe194e4f12d1972d524e703331c08b1f6b3c064 Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Tue, 19 Sep 2023 10:19:06 -0400 Subject: [PATCH 01/12] initial refactor --- services/app-api/handlers/formTemplates/post.ts | 1 - services/database/data/seed/seed-section-base-2023.json | 1 - services/ui-src/src/store/formData.js | 7 ------- services/ui-src/src/util/shouldDisplay.js | 2 ++ 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/services/app-api/handlers/formTemplates/post.ts b/services/app-api/handlers/formTemplates/post.ts index 8666a1ff2..b1014ae55 100644 --- a/services/app-api/handlers/formTemplates/post.ts +++ b/services/app-api/handlers/formTemplates/post.ts @@ -60,7 +60,6 @@ export const post = handler(async (event, _context) => { stateStatuses.push({ stateId: state.code, year: yearNumber, - programType: state.programType, status: "not_started", lastChanged: creationTime, }); diff --git a/services/database/data/seed/seed-section-base-2023.json b/services/database/data/seed/seed-section-base-2023.json index ded86d7ce..04690ec4f 100644 --- a/services/database/data/seed/seed-section-base-2023.json +++ b/services/database/data/seed/seed-section-base-2023.json @@ -52,7 +52,6 @@ "value": "separate_chip" } ], - "readonly": true, "prepopulated": true } }, diff --git a/services/ui-src/src/store/formData.js b/services/ui-src/src/store/formData.js index a8b6c4609..524959649 100644 --- a/services/ui-src/src/store/formData.js +++ b/services/ui-src/src/store/formData.js @@ -34,13 +34,6 @@ export default (state = initialState, action) => { updatedData[0].contents.section.subsections[0].parts[0].questions[0] = lastYearData[0].contents.section.subsections[0].parts[0].questions[0]; // Name } - if ( - !updatedData[0].contents.section.subsections[0].parts[0].questions[1] - .answer.entry - ) { - updatedData[0].contents.section.subsections[0].parts[0].questions[1] = - lastYearData[0].contents.section.subsections[0].parts[0].questions[1]; // Type - } // Cohort Questions - These should be revolving around a 2 year cycle, but today just pull forward if (twoYearCycle) { updatedData[3].contents.section.subsections[2].parts[5].questions[1].answer = diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index bfb8b5a74..4ade3ae5f 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -1,3 +1,4 @@ +import { selectFragmentById } from "../store/formData"; import { AppRoles } from "../types"; import jsonpath from "./jsonpath"; import { @@ -177,6 +178,7 @@ const hideIfTableValue = (state, hideIfTableValueInfo) => { const shouldDisplay = (state, context, programType = null) => { let program, reportStatusCode; if (state.stateUser.currentUser.role === AppRoles.CMS_ADMIN) return true; + programType = selectFragmentById(state, "2023-00-a-01-02").answer.entry; if (!programType) { // If program type is not provided as an argument (the user is a bus_user, co_user), use the value for program type present in state if (state.stateUser.currentUser.role === AppRoles.STATE_USER) { From 3905c552f1666d558ac97311cfbe8bd3618a9b06 Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Tue, 19 Sep 2023 11:45:07 -0400 Subject: [PATCH 02/12] fixup logic and add mocking for tests --- services/ui-src/src/util/shouldDisplay.js | 16 +++------------- services/ui-src/src/util/shouldDisplay.test.js | 7 +++++++ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index 4ade3ae5f..358b04616 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -176,20 +176,10 @@ const hideIfTableValue = (state, hideIfTableValueInfo) => { * @returns {boolean} - determines if an element should be filtered out, returning true means a question will display */ const shouldDisplay = (state, context, programType = null) => { - let program, reportStatusCode; + let program; if (state.stateUser.currentUser.role === AppRoles.CMS_ADMIN) return true; - programType = selectFragmentById(state, "2023-00-a-01-02").answer.entry; - if (!programType) { - // If program type is not provided as an argument (the user is a bus_user, co_user), use the value for program type present in state - if (state.stateUser.currentUser.role === AppRoles.STATE_USER) { - reportStatusCode = state.stateUser.abbr + state.global.formYear; - } else { - reportStatusCode = state.formData[0].stateId + state.formData[0].year; - } - program = state.reportStatus[reportStatusCode].programType; - } else { - program = programType; - } + program = + programType ?? selectFragmentById(state, "2023-00-a-01-02").answer.entry; if ( !context || diff --git a/services/ui-src/src/util/shouldDisplay.test.js b/services/ui-src/src/util/shouldDisplay.test.js index 196b2283b..0c7e04050 100644 --- a/services/ui-src/src/util/shouldDisplay.test.js +++ b/services/ui-src/src/util/shouldDisplay.test.js @@ -1,6 +1,13 @@ import { shouldDisplay } from "./shouldDisplay"; import { AppRoles } from "../types"; +jest.mock("../store/formData", () => ({ + ...jest.requireActual("../store/formData"), + selectFragmentById: () => ({ + answer: { entry: "test program" }, + }), +})); + describe("shouldDisplay", () => { it("should display everything for CMS Admins", () => { const state = { From 994cd30f83c6c3d2678c2dfd6368396d736b6e7e Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Tue, 19 Sep 2023 13:36:51 -0400 Subject: [PATCH 03/12] dynamic year and readd programType to initial status --- services/app-api/handlers/formTemplates/post.ts | 1 + services/ui-src/src/util/shouldDisplay.js | 10 ++++++++-- services/ui-src/src/util/shouldDisplay.test.js | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/services/app-api/handlers/formTemplates/post.ts b/services/app-api/handlers/formTemplates/post.ts index b1014ae55..8666a1ff2 100644 --- a/services/app-api/handlers/formTemplates/post.ts +++ b/services/app-api/handlers/formTemplates/post.ts @@ -60,6 +60,7 @@ export const post = handler(async (event, _context) => { stateStatuses.push({ stateId: state.code, year: yearNumber, + programType: state.programType, status: "not_started", lastChanged: creationTime, }); diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index 358b04616..6b60625ab 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -178,8 +178,14 @@ const hideIfTableValue = (state, hideIfTableValueInfo) => { const shouldDisplay = (state, context, programType = null) => { let program; if (state.stateUser.currentUser.role === AppRoles.CMS_ADMIN) return true; - program = - programType ?? selectFragmentById(state, "2023-00-a-01-02").answer.entry; + if (!programType) { + const formYear = state.global.formYear; + const programFromForm = selectFragmentById(state, `${formYear}-00-a-01-02`) + ?.answer.entry; + program = programFromForm; + } else { + program = programType; + } if ( !context || diff --git a/services/ui-src/src/util/shouldDisplay.test.js b/services/ui-src/src/util/shouldDisplay.test.js index 0c7e04050..084f21eff 100644 --- a/services/ui-src/src/util/shouldDisplay.test.js +++ b/services/ui-src/src/util/shouldDisplay.test.js @@ -113,6 +113,9 @@ describe("shouldDisplay", () => { it("should infer programType for non-state users", () => { const state = { + global: { + formYear: "2023", + }, stateUser: { currentUser: { role: "test role", From c09a1abfda9b7f6099f2ae9bce7ef261d49be88e Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Wed, 20 Sep 2023 09:18:31 -0400 Subject: [PATCH 04/12] search current and previous year for program type --- services/ui-src/src/constants.js | 1 + services/ui-src/src/util/shouldDisplay.js | 26 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 services/ui-src/src/constants.js diff --git a/services/ui-src/src/constants.js b/services/ui-src/src/constants.js new file mode 100644 index 000000000..8d61551bf --- /dev/null +++ b/services/ui-src/src/constants.js @@ -0,0 +1 @@ +export const PROGRAM_TYPE_QUESTION_ID = "-00-a-01-02"; diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index 6b60625ab..b7e59c76e 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -1,3 +1,4 @@ +import { PROGRAM_TYPE_QUESTION_ID } from "../constants"; import { selectFragmentById } from "../store/formData"; import { AppRoles } from "../types"; import jsonpath from "./jsonpath"; @@ -167,6 +168,26 @@ const hideIfTableValue = (state, hideIfTableValueInfo) => { return result; }; +const getProgramTypeFromForm = (state) => { + // attempt to find programType from same year as form + const formYear = state.global.formYear; + const currentYearProgramType = selectFragmentById( + state, + `${formYear}${PROGRAM_TYPE_QUESTION_ID}` + )?.answer.entry; + if (currentYearProgramType) { + return currentYearProgramType; + } + + // attempt to find programType from the previous year's form + const previousYear = parseInt(formYear) - 1; + const previousYearProgramType = selectFragmentById( + state, + `${previousYear}${PROGRAM_TYPE_QUESTION_ID}` + )?.answer.entry; + return previousYearProgramType || ""; +}; + /** * This function checks to see if a question should display based on an answer from a different question * @function shouldDisplay @@ -179,10 +200,7 @@ const shouldDisplay = (state, context, programType = null) => { let program; if (state.stateUser.currentUser.role === AppRoles.CMS_ADMIN) return true; if (!programType) { - const formYear = state.global.formYear; - const programFromForm = selectFragmentById(state, `${formYear}-00-a-01-02`) - ?.answer.entry; - program = programFromForm; + program = getProgramTypeFromForm(state); } else { program = programType; } From 2c67997ebdc3b5acf61ca69185c93e7b9976d4c0 Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Wed, 20 Sep 2023 10:33:34 -0400 Subject: [PATCH 05/12] tests --- services/ui-src/src/util/shouldDisplay.js | 4 +- .../ui-src/src/util/shouldDisplay.test.js | 52 +++++++++++++++++-- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index b7e59c76e..075b8daf5 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -174,7 +174,7 @@ const getProgramTypeFromForm = (state) => { const currentYearProgramType = selectFragmentById( state, `${formYear}${PROGRAM_TYPE_QUESTION_ID}` - )?.answer.entry; + )?.answer?.entry; if (currentYearProgramType) { return currentYearProgramType; } @@ -184,7 +184,7 @@ const getProgramTypeFromForm = (state) => { const previousYearProgramType = selectFragmentById( state, `${previousYear}${PROGRAM_TYPE_QUESTION_ID}` - )?.answer.entry; + )?.answer?.entry; return previousYearProgramType || ""; }; diff --git a/services/ui-src/src/util/shouldDisplay.test.js b/services/ui-src/src/util/shouldDisplay.test.js index 084f21eff..35b90efab 100644 --- a/services/ui-src/src/util/shouldDisplay.test.js +++ b/services/ui-src/src/util/shouldDisplay.test.js @@ -1,14 +1,18 @@ import { shouldDisplay } from "./shouldDisplay"; import { AppRoles } from "../types"; +import { selectFragmentById } from "../store/formData"; +const mockFragmentResult = { + answer: { entry: "test program" }, +}; jest.mock("../store/formData", () => ({ ...jest.requireActual("../store/formData"), - selectFragmentById: () => ({ - answer: { entry: "test program" }, - }), + selectFragmentById: jest.fn(), })); describe("shouldDisplay", () => { + beforeEach(() => jest.clearAllMocks()); + it("should display everything for CMS Admins", () => { const state = { stateUser: { @@ -85,7 +89,8 @@ describe("shouldDisplay", () => { expect(result).toBe(true); }); - it("should infer programType for state users", () => { + it("should find programType for state users", () => { + selectFragmentById.mockReturnValueOnce(mockFragmentResult); const state = { global: { formYear: "2023", @@ -109,9 +114,45 @@ describe("shouldDisplay", () => { const programType = null; const result = shouldDisplay(state, context, programType); expect(result).toBe(true); + expect(selectFragmentById).toHaveBeenCalledTimes(1); + }); + + it("should find programType for non-state users", () => { + selectFragmentById.mockReturnValueOnce(mockFragmentResult); + const state = { + global: { + formYear: "2023", + }, + stateUser: { + currentUser: { + role: "test role", + }, + }, + formData: [ + { + stateId: "CO", + year: "2023", + }, + ], + reportStatus: { + CO2023: { + programType: "test program", + }, + }, + }; + const context = { + conditional_display: null, + show_if_state_program_type_in: ["test program"], + }; + const programType = null; + const result = shouldDisplay(state, context, programType); + expect(result).toBe(true); + expect(selectFragmentById).toHaveBeenCalledTimes(1); }); - it("should infer programType for non-state users", () => { + it("should find programType from previous year if not in current year", () => { + selectFragmentById.mockReturnValueOnce({}); + selectFragmentById.mockReturnValueOnce(mockFragmentResult); const state = { global: { formYear: "2023", @@ -140,6 +181,7 @@ describe("shouldDisplay", () => { const programType = null; const result = shouldDisplay(state, context, programType); expect(result).toBe(true); + expect(selectFragmentById).toHaveBeenCalledTimes(2); }); it("should not display if hide_if logic is specified, and the state satisfies it", () => { From 9d53f144888ec7a6c66bbc06961277bb7fc3dfdb Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Thu, 21 Sep 2023 10:27:00 -0400 Subject: [PATCH 06/12] refactor programType --- services/app-api/handlers/section/update.ts | 28 +++++++++++ services/ui-src/src/components/layout/Part.js | 49 +------------------ services/ui-src/src/constants.js | 1 - services/ui-src/src/store/stateUser.js | 2 - services/ui-src/src/util/shouldDisplay.js | 34 ++----------- 5 files changed, 33 insertions(+), 81 deletions(-) delete mode 100644 services/ui-src/src/constants.js diff --git a/services/app-api/handlers/section/update.ts b/services/app-api/handlers/section/update.ts index 481a9e4fe..106adee30 100644 --- a/services/app-api/handlers/section/update.ts +++ b/services/app-api/handlers/section/update.ts @@ -1,3 +1,4 @@ +import { JSONPath } from "jsonpath-plus"; import handler from "../../libs/handler-lib"; import dynamoDb from "../../libs/dynamodb-lib"; import { getUserCredentialsFromJwt } from "../../libs/authorization"; @@ -89,6 +90,7 @@ export const updateSections = handler(async (event, _context) => { { status: moveToInProgress ? "in_progress" : stateStatus.status, lastChanged: lastChanged, + programType: getProgramType(year, reportData, stateStatus?.programType), }, "post" ), @@ -96,3 +98,29 @@ export const updateSections = handler(async (event, _context) => { await dynamoDb.update(statusParams); }); + +const PROGRAM_TYPE_QUESTION_ID = "-00-a-01-02"; + +const getProgramType = ( + year: string, + reportData: any, + programType: string | undefined +) => { + // attempt to find programType from same year as form + const currentProgramType = programType; + let idExpression = `${year}${PROGRAM_TYPE_QUESTION_ID}`; + let jpexpr = `$..*[?(@ && @.id=='${idExpression}')]`; + let fragment = JSONPath({ path: jpexpr, json: reportData[0].contents }); + let currentYearProgramType = fragment?.[0]?.answer?.entry; + + if (currentYearProgramType) { + return currentYearProgramType; + } + // attempt to find programType from the previous year's form + const previousYear = parseInt(year) - 1; + idExpression = `${previousYear}${PROGRAM_TYPE_QUESTION_ID}`; + jpexpr = `$..*[?(@ && @.id=='${idExpression}')]`; + fragment = JSONPath({ path: jpexpr, json: reportData[0].contents }); + const previousYearProgramType = fragment?.[0]?.answer?.entry; + return previousYearProgramType ?? currentProgramType; +}; diff --git a/services/ui-src/src/components/layout/Part.js b/services/ui-src/src/components/layout/Part.js index 74cdefbd5..0351a8b1e 100644 --- a/services/ui-src/src/components/layout/Part.js +++ b/services/ui-src/src/components/layout/Part.js @@ -8,19 +8,6 @@ import Question from "../fields/Question"; import { selectQuestionsForPart } from "../../store/selectors"; import { shouldDisplay } from "../../util/shouldDisplay"; import Text from "./Text"; -import { AppRoles } from "../../types"; - -const showPart = (contextData, programType, state) => { - if ( - contextData && - programType && - contextData.show_if_state_program_type_in && - !contextData.show_if_state_program_type_in.includes(programType) - ) { - return false; - } - return shouldDisplay(state, contextData, programType); -}; const Part = ({ context_data: contextData, @@ -100,16 +87,11 @@ const mapStateToProps = (state, { partId }) => { const part = selectFragment(state, partId); const questions = selectQuestionsForPart(state, partId); const contextData = part.context_data; - const location = window.location.pathname.split("/"); - const userState = location[3]; // Current state, ie: "AL" or "CT" - const programData = state.allStatesData.find( - (element) => element.code === userState - ); return { context_data: part.context_data, questions, - show: showPartBasedOnUserType(contextData, programData, state), + show: shouldDisplay(state, contextData), text: part ? part.text : null, title: part ? part.title : null, isFetching: state.global.isFetching, @@ -117,32 +99,3 @@ const mapStateToProps = (state, { partId }) => { }; export default connect(mapStateToProps)(Part); - -/** - * This function considers what arguments to invoke showPart() with based on user type. - * Business and CO users do not have program types saved to their user objects in local state - * @function showPartBasedOnUserType - * @param {object} contextData - The context data for a Part from JSON - * @param {object} programData - An object with the state's program type - * @param {string} state - application state from redux - * @returns {boolean} - determines if an element should show by invoking showPart() - */ -const showPartBasedOnUserType = (contextData, programData, state) => { - const role = state.stateUser.currentUser.role; - - if ( - programData && - (role === AppRoles.CMS_ADMIN || - role === AppRoles.INTERNAL_USER || - role === AppRoles.HELP_DESK || - role === AppRoles.CMS_APPROVER || - role === AppRoles.CMS_USER || - role === AppRoles.STATE_USER) - ) { - // program type from programData object, for bus_user and co_user - return showPart(contextData, programData.program_type, state); - } else { - // program type from stateUser object, for state_user's - return showPart(contextData, state.stateUser.programType, state); - } -}; diff --git a/services/ui-src/src/constants.js b/services/ui-src/src/constants.js deleted file mode 100644 index 8d61551bf..000000000 --- a/services/ui-src/src/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const PROGRAM_TYPE_QUESTION_ID = "-00-a-01-02"; diff --git a/services/ui-src/src/store/stateUser.js b/services/ui-src/src/store/stateUser.js index 424e3ded3..acd7eb476 100644 --- a/services/ui-src/src/store/stateUser.js +++ b/services/ui-src/src/store/stateUser.js @@ -16,7 +16,6 @@ export const getUserData = (userObject) => { export const getProgramData = (programObject) => ({ type: PROGRAM_INFO, - programType: programObject.programType, programName: programObject.programName, formName: programObject.formName, }); @@ -84,7 +83,6 @@ export default (state = initialState, action) => { case PROGRAM_INFO: return { ...state, - programType: action.programType, programName: action.programName, formName: action.formName, }; diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index 075b8daf5..21989930f 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -1,5 +1,3 @@ -import { PROGRAM_TYPE_QUESTION_ID } from "../constants"; -import { selectFragmentById } from "../store/formData"; import { AppRoles } from "../types"; import jsonpath from "./jsonpath"; import { @@ -168,42 +166,18 @@ const hideIfTableValue = (state, hideIfTableValueInfo) => { return result; }; -const getProgramTypeFromForm = (state) => { - // attempt to find programType from same year as form - const formYear = state.global.formYear; - const currentYearProgramType = selectFragmentById( - state, - `${formYear}${PROGRAM_TYPE_QUESTION_ID}` - )?.answer?.entry; - if (currentYearProgramType) { - return currentYearProgramType; - } - - // attempt to find programType from the previous year's form - const previousYear = parseInt(formYear) - 1; - const previousYearProgramType = selectFragmentById( - state, - `${previousYear}${PROGRAM_TYPE_QUESTION_ID}` - )?.answer?.entry; - return previousYearProgramType || ""; -}; - /** * This function checks to see if a question should display based on an answer from a different question * @function shouldDisplay * @param {object} state - The application state from redux, the object required for jsonpath to query * @param {object} context - the context_data from a question - * @param {string} programType - program type of the user's state * @returns {boolean} - determines if an element should be filtered out, returning true means a question will display */ -const shouldDisplay = (state, context, programType = null) => { - let program; +const shouldDisplay = (state, context) => { if (state.stateUser.currentUser.role === AppRoles.CMS_ADMIN) return true; - if (!programType) { - program = getProgramTypeFromForm(state); - } else { - program = programType; - } + + const reportStatusCode = state.formData[0].stateId + state.formData[0].year; + const program = state.reportStatus[reportStatusCode].programType; if ( !context || From 2e563f6dc41cf74811deb0eb232eee6aab13f97a Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Thu, 21 Sep 2023 11:32:29 -0400 Subject: [PATCH 07/12] update shouldDisplay and tests --- services/ui-src/src/util/shouldDisplay.js | 5 +- .../ui-src/src/util/shouldDisplay.test.js | 114 +++++++----------- 2 files changed, 45 insertions(+), 74 deletions(-) diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index 21989930f..806266331 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -176,9 +176,6 @@ const hideIfTableValue = (state, hideIfTableValueInfo) => { const shouldDisplay = (state, context) => { if (state.stateUser.currentUser.role === AppRoles.CMS_ADMIN) return true; - const reportStatusCode = state.formData[0].stateId + state.formData[0].year; - const program = state.reportStatus[reportStatusCode].programType; - if ( !context || (!context.conditional_display && !context.show_if_state_program_type_in) @@ -191,6 +188,8 @@ const shouldDisplay = (state, context) => { * displaying relies on that answer being included in the show_if_state_program_type_in array */ if (context.show_if_state_program_type_in) { + const reportStatusCode = state.formData[0].stateId + state.formData[0].year; + const program = state.reportStatus[reportStatusCode].programType; return context.show_if_state_program_type_in.includes(program); } diff --git a/services/ui-src/src/util/shouldDisplay.test.js b/services/ui-src/src/util/shouldDisplay.test.js index 35b90efab..aab840e7d 100644 --- a/services/ui-src/src/util/shouldDisplay.test.js +++ b/services/ui-src/src/util/shouldDisplay.test.js @@ -1,14 +1,5 @@ import { shouldDisplay } from "./shouldDisplay"; import { AppRoles } from "../types"; -import { selectFragmentById } from "../store/formData"; - -const mockFragmentResult = { - answer: { entry: "test program" }, -}; -jest.mock("../store/formData", () => ({ - ...jest.requireActual("../store/formData"), - selectFragmentById: jest.fn(), -})); describe("shouldDisplay", () => { beforeEach(() => jest.clearAllMocks()); @@ -21,7 +12,7 @@ describe("shouldDisplay", () => { }, }, }; - const result = shouldDisplay(state, null, null); + const result = shouldDisplay(state, null); expect(result).toBe(true); }); @@ -33,8 +24,7 @@ describe("shouldDisplay", () => { }, }, }; - const programType = "test program"; - const result = shouldDisplay(state, null, programType); + const result = shouldDisplay(state, null); expect(result).toBe(true); }); @@ -45,13 +35,23 @@ describe("shouldDisplay", () => { role: "test role", }, }, + formData: [ + { + stateId: "CO", + year: "2023", + }, + ], + reportStatus: { + CO2023: { + programType: "test program", + }, + }, }; const context = { conditional_display: null, show_if_state_program_type_in: null, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); }); @@ -62,13 +62,23 @@ describe("shouldDisplay", () => { role: "test role", }, }, + formData: [ + { + stateId: "CO", + year: "2023", + }, + ], + reportStatus: { + CO2023: { + programType: "test program", + }, + }, }; const context = { conditional_display: null, show_if_state_program_type_in: ["a different program"], }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(false); }); @@ -79,28 +89,12 @@ describe("shouldDisplay", () => { role: "test role", }, }, - }; - const context = { - conditional_display: null, - show_if_state_program_type_in: ["test program"], - }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); - expect(result).toBe(true); - }); - - it("should find programType for state users", () => { - selectFragmentById.mockReturnValueOnce(mockFragmentResult); - const state = { - global: { - formYear: "2023", - }, - stateUser: { - abbr: "CO", - currentUser: { - role: AppRoles.STATE_USER, + formData: [ + { + stateId: "CO", + year: "2023", }, - }, + ], reportStatus: { CO2023: { programType: "test program", @@ -111,18 +105,12 @@ describe("shouldDisplay", () => { conditional_display: null, show_if_state_program_type_in: ["test program"], }; - const programType = null; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); - expect(selectFragmentById).toHaveBeenCalledTimes(1); }); - it("should find programType for non-state users", () => { - selectFragmentById.mockReturnValueOnce(mockFragmentResult); + it("should find programType for state users", () => { const state = { - global: { - formYear: "2023", - }, stateUser: { currentUser: { role: "test role", @@ -144,19 +132,12 @@ describe("shouldDisplay", () => { conditional_display: null, show_if_state_program_type_in: ["test program"], }; - const programType = null; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); - expect(selectFragmentById).toHaveBeenCalledTimes(1); }); - it("should find programType from previous year if not in current year", () => { - selectFragmentById.mockReturnValueOnce({}); - selectFragmentById.mockReturnValueOnce(mockFragmentResult); + it("should find programType for non-state users", () => { const state = { - global: { - formYear: "2023", - }, stateUser: { currentUser: { role: "test role", @@ -178,10 +159,8 @@ describe("shouldDisplay", () => { conditional_display: null, show_if_state_program_type_in: ["test program"], }; - const programType = null; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); - expect(selectFragmentById).toHaveBeenCalledTimes(2); }); it("should not display if hide_if logic is specified, and the state satisfies it", () => { @@ -205,8 +184,7 @@ describe("shouldDisplay", () => { }, }, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(false); }); @@ -231,8 +209,7 @@ describe("shouldDisplay", () => { }, }, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); }); @@ -260,8 +237,7 @@ describe("shouldDisplay", () => { }, }, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(false); }); @@ -289,8 +265,7 @@ describe("shouldDisplay", () => { }, }, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); }); @@ -316,8 +291,7 @@ describe("shouldDisplay", () => { }, }, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); }); @@ -343,8 +317,7 @@ describe("shouldDisplay", () => { }, }, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(false); }); @@ -359,8 +332,7 @@ describe("shouldDisplay", () => { const context = { conditional_display: {}, }; - const programType = "test program"; - const result = shouldDisplay(state, context, programType); + const result = shouldDisplay(state, context); expect(result).toBe(true); }); }); From 80b4619875d7731eac660d4b74247e513ffd11ef Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Thu, 21 Sep 2023 11:35:21 -0400 Subject: [PATCH 08/12] add jsonpath-plus to package (already in yarn.lock) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e116bca6e..e6a0ba758 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "eslint-plugin-jsx-a11y": "^6.x", "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^2.x", + "jsonpath-plus": "^5.1.0", "prettier": "^2.4.1", "serverless": "^3.19.0", "serverless-api-client-certificate": "^1.0.2", From cbb3fe542ae571f2aab10aa7919c871195df556b Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Thu, 21 Sep 2023 12:32:34 -0400 Subject: [PATCH 09/12] move jsonpath-plus to app-api --- package.json | 1 - services/app-api/package.json | 1 + services/app-api/yarn.lock | 5 +++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e6a0ba758..e116bca6e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "eslint-plugin-jsx-a11y": "^6.x", "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^2.x", - "jsonpath-plus": "^5.1.0", "prettier": "^2.4.1", "serverless": "^3.19.0", "serverless-api-client-certificate": "^1.0.2", diff --git a/services/app-api/package.json b/services/app-api/package.json index d401ea9ca..71834f975 100644 --- a/services/app-api/package.json +++ b/services/app-api/package.json @@ -20,6 +20,7 @@ "@types/jest": "^27.4.0", "aws-lambda": "^1.0.7", "jest": "^27.4.7", + "jsonpath-plus": "^5.1.0", "serverless-associate-waf": "^1.2.1", "serverless-plugin-typescript": "^2.1.0", "ts-jest": "^27.1.3", diff --git a/services/app-api/yarn.lock b/services/app-api/yarn.lock index cc05e89af..81df25342 100644 --- a/services/app-api/yarn.lock +++ b/services/app-api/yarn.lock @@ -2026,6 +2026,11 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonpath-plus@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-5.1.0.tgz#2fc4b2e461950626c98525425a3a3518b85af6c3" + integrity sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ== + jsonschema@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" From 557a15eb1be8f5b36c1d68a66fbed43b316e9423 Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Fri, 22 Sep 2023 13:31:06 -0400 Subject: [PATCH 10/12] update getProgramType based on feedback --- services/app-api/handlers/section/update.ts | 39 +++++++++------------ 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/services/app-api/handlers/section/update.ts b/services/app-api/handlers/section/update.ts index 106adee30..6345898ed 100644 --- a/services/app-api/handlers/section/update.ts +++ b/services/app-api/handlers/section/update.ts @@ -80,6 +80,16 @@ export const updateSections = handler(async (event, _context) => { const moveToInProgress = queryValue.Items && stateStatus.status === "not_started"; + /* + * attempt to find programType from the reportData + * check current and previous year id + * otherwise use existing programType from state status + */ + const programType = + getProgramType(year, reportData) ?? + getProgramType(parseInt(year) - 1, reportData) ?? + stateStatus?.programType; + const statusParams = { TableName: process.env.stateStatusTableName!, Key: { @@ -90,7 +100,7 @@ export const updateSections = handler(async (event, _context) => { { status: moveToInProgress ? "in_progress" : stateStatus.status, lastChanged: lastChanged, - programType: getProgramType(year, reportData, stateStatus?.programType), + programType: programType, }, "post" ), @@ -101,26 +111,9 @@ export const updateSections = handler(async (event, _context) => { const PROGRAM_TYPE_QUESTION_ID = "-00-a-01-02"; -const getProgramType = ( - year: string, - reportData: any, - programType: string | undefined -) => { - // attempt to find programType from same year as form - const currentProgramType = programType; - let idExpression = `${year}${PROGRAM_TYPE_QUESTION_ID}`; - let jpexpr = `$..*[?(@ && @.id=='${idExpression}')]`; - let fragment = JSONPath({ path: jpexpr, json: reportData[0].contents }); - let currentYearProgramType = fragment?.[0]?.answer?.entry; - - if (currentYearProgramType) { - return currentYearProgramType; - } - // attempt to find programType from the previous year's form - const previousYear = parseInt(year) - 1; - idExpression = `${previousYear}${PROGRAM_TYPE_QUESTION_ID}`; - jpexpr = `$..*[?(@ && @.id=='${idExpression}')]`; - fragment = JSONPath({ path: jpexpr, json: reportData[0].contents }); - const previousYearProgramType = fragment?.[0]?.answer?.entry; - return previousYearProgramType ?? currentProgramType; +const getProgramType = (year: string | number, reportData: any) => { + const idExpression = `${year}${PROGRAM_TYPE_QUESTION_ID}`; + const jpexpr = `$..*[?(@ && @.id=='${idExpression}')]`; + const fragment = JSONPath({ path: jpexpr, json: reportData[0].contents }); + return fragment?.[0]?.answer?.entry; }; From c3318445ad64a51b33e3f01ae28c0238c0fa2906 Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Mon, 25 Sep 2023 12:07:53 -0400 Subject: [PATCH 11/12] readd fetch from state for ux --- services/ui-src/src/util/shouldDisplay.js | 28 +++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index 806266331..3593aca56 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -1,3 +1,4 @@ +import { selectFragmentById } from "../store/formData"; import { AppRoles } from "../types"; import jsonpath from "./jsonpath"; import { @@ -166,6 +167,30 @@ const hideIfTableValue = (state, hideIfTableValueInfo) => { return result; }; +const PROGRAM_TYPE_QUESTION_ID = "-00-a-01-02"; + +const getProgramTypeFromForm = (state) => { + // attempt to find programType from same year as form + const formYear = state.formData[0].year; + const currentYearProgramType = selectFragmentById( + state, + `${formYear}${PROGRAM_TYPE_QUESTION_ID}` + )?.answer.entry; + if (currentYearProgramType) { + return currentYearProgramType; + } + + // attempt to find programType from the previous year's form, otherwise retrieve from status + const previousYear = parseInt(formYear) - 1; + const previousYearProgramType = selectFragmentById( + state, + `${previousYear}${PROGRAM_TYPE_QUESTION_ID}` + )?.answer.entry; + const reportStatusCode = state.formData[0].stateId + state.formData[0].year; + const programFromStatus = state.reportStatus[reportStatusCode].programType; + return previousYearProgramType || programFromStatus; +}; + /** * This function checks to see if a question should display based on an answer from a different question * @function shouldDisplay @@ -188,8 +213,7 @@ const shouldDisplay = (state, context) => { * displaying relies on that answer being included in the show_if_state_program_type_in array */ if (context.show_if_state_program_type_in) { - const reportStatusCode = state.formData[0].stateId + state.formData[0].year; - const program = state.reportStatus[reportStatusCode].programType; + const program = getProgramTypeFromForm(state); return context.show_if_state_program_type_in.includes(program); } From 9ca805ed1cc4143f01e073f046d52ed7d03e8071 Mon Sep 17 00:00:00 2001 From: Garrett Rabian Date: Mon, 25 Sep 2023 12:25:41 -0400 Subject: [PATCH 12/12] readd tests --- services/ui-src/src/util/shouldDisplay.js | 4 +- .../ui-src/src/util/shouldDisplay.test.js | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/services/ui-src/src/util/shouldDisplay.js b/services/ui-src/src/util/shouldDisplay.js index 3593aca56..fe80e3ab7 100644 --- a/services/ui-src/src/util/shouldDisplay.js +++ b/services/ui-src/src/util/shouldDisplay.js @@ -175,7 +175,7 @@ const getProgramTypeFromForm = (state) => { const currentYearProgramType = selectFragmentById( state, `${formYear}${PROGRAM_TYPE_QUESTION_ID}` - )?.answer.entry; + )?.answer?.entry; if (currentYearProgramType) { return currentYearProgramType; } @@ -185,7 +185,7 @@ const getProgramTypeFromForm = (state) => { const previousYearProgramType = selectFragmentById( state, `${previousYear}${PROGRAM_TYPE_QUESTION_ID}` - )?.answer.entry; + )?.answer?.entry; const reportStatusCode = state.formData[0].stateId + state.formData[0].year; const programFromStatus = state.reportStatus[reportStatusCode].programType; return previousYearProgramType || programFromStatus; diff --git a/services/ui-src/src/util/shouldDisplay.test.js b/services/ui-src/src/util/shouldDisplay.test.js index aab840e7d..b3ccfd317 100644 --- a/services/ui-src/src/util/shouldDisplay.test.js +++ b/services/ui-src/src/util/shouldDisplay.test.js @@ -1,5 +1,15 @@ import { shouldDisplay } from "./shouldDisplay"; import { AppRoles } from "../types"; +import { selectFragmentById } from "../store/formData"; + +const mockFragmentResult = { + answer: { entry: "test program" }, +}; + +jest.mock("../store/formData", () => ({ + ...jest.requireActual("../store/formData"), + selectFragmentById: jest.fn(), +})); describe("shouldDisplay", () => { beforeEach(() => jest.clearAllMocks()); @@ -110,6 +120,7 @@ describe("shouldDisplay", () => { }); it("should find programType for state users", () => { + selectFragmentById.mockReturnValueOnce(mockFragmentResult); const state = { stateUser: { currentUser: { @@ -134,9 +145,41 @@ describe("shouldDisplay", () => { }; const result = shouldDisplay(state, context); expect(result).toBe(true); + expect(selectFragmentById).toHaveBeenCalledTimes(1); }); it("should find programType for non-state users", () => { + selectFragmentById.mockReturnValueOnce(mockFragmentResult); + const state = { + stateUser: { + currentUser: { + role: "test role", + }, + }, + formData: [ + { + stateId: "CO", + year: "2023", + }, + ], + reportStatus: { + CO2023: { + programType: "test program", + }, + }, + }; + const context = { + conditional_display: null, + show_if_state_program_type_in: ["test program"], + }; + const result = shouldDisplay(state, context); + expect(result).toBe(true); + expect(selectFragmentById).toHaveBeenCalledTimes(1); + }); + + it("should find programType from previous year if not in current year", () => { + selectFragmentById.mockReturnValueOnce({}); + selectFragmentById.mockReturnValueOnce(mockFragmentResult); const state = { stateUser: { currentUser: { @@ -161,6 +204,7 @@ describe("shouldDisplay", () => { }; const result = shouldDisplay(state, context); expect(result).toBe(true); + expect(selectFragmentById).toHaveBeenCalledTimes(2); }); it("should not display if hide_if logic is specified, and the state satisfies it", () => {