diff --git a/.github/branch-name-validation.sh b/.github/branch-name-validation.sh index e071a65ae..20cd856f5 100755 --- a/.github/branch-name-validation.sh +++ b/.github/branch-name-validation.sh @@ -3,6 +3,7 @@ set -e local_branch=${1} +[ -z "${1}" ] && local_branch=$(git rev-parse --abbrev-ref HEAD) valid_branch='^[a-z][a-z0-9-]*$' @@ -15,7 +16,7 @@ join_by() { local IFS='|'; echo "$*"; } #creates glob match to check for reserved words used in branch names which would trigger failures glob=$(join_by $(for i in ${reserved_words[@]}; do echo "^$i-|-$i$|-$i-|^$i$"; done;)) -if [[ ! $local_branch =~ $valid_branch ]] || [[ $local_branch =~ $glob ]] || [[ ${#local_branch} -gt 64 ]]; then +if [[ ! $local_branch =~ $valid_branch ]] || [[ $local_branch =~ $glob ]] || [[ ${#local_branch} -gt 20 ]]; then echo """ ------------------------------------------------------------------------------------------------------------------------------ ERROR: Please read below @@ -28,7 +29,7 @@ if [[ ! $local_branch =~ $valid_branch ]] || [[ $local_branch =~ $glob ]] || [[ Therefore, the branch name must be a valid service name. Branch name must be all lower case with no spaces and no underscores. From Serverless: - A service name should only contain alphanumeric (case sensitive) and hyphens. It should start with an alphabetic character and shouldnt exceed 128 characters. + A service name should only contain alphanumeric (case sensitive) and hyphens. It should start with an alphabetic character and shouldnt exceed 20 characters. For Github Actions support, please push your code to a new branch with a name that meets Serverless' service name requirements. So, make a new branch with a name that begins with a letter and is made up of only letters, numbers, and hyphens... then delete this branch. ------------------------------------------------------------------------------------------------------------------------------ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ff6d1bef1..f7ba106c1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -217,12 +217,12 @@ jobs: - name: Run Cypress Tests uses: cypress-io/github-action@v6 with: - working-directory: tests/cypress + working-directory: tests spec: | - e2e/*.cy.js - e2e/admin/*.cy.js - e2e/mcpar/*.cy.js - e2e/mlr/*.cy.js + cypress/e2e/*.cy.js + cypress/e2e/admin/*.cy.js + cypress/e2e/mcpar/*.cy.js + cypress/e2e/mlr/*.cy.js browser: chrome config: baseUrl=${{ needs.deploy.outputs.application_endpoint }} wait-on: ${{ needs.deploy.outputs.application_endpoint }} @@ -257,8 +257,8 @@ jobs: - name: Check Project A11y uses: cypress-io/github-action@v6 with: - working-directory: tests/cypress - spec: e2e/accessibility/*.cy.js + working-directory: tests + spec: cypress/e2e/accessibility/*.cy.js browser: chrome config: baseUrl=${{ needs.deploy.outputs.application_endpoint }} wait-on: ${{ needs.deploy.outputs.application_endpoint }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 851c0cc34..8493610c9 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -10,6 +10,9 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.10' + - name: Configure pre-commit to skip branch name validation + run: | + echo "SKIP=branch-name-validation" >> $GITHUB_ENV - uses: pre-commit/action@v3.0.1 - uses: actions/setup-node@v4 with: diff --git a/.gitignore b/.gitignore index fe774668b..2c3dbe615 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,6 @@ tests_output tests/cypress/videos tests/cypress/screenshots tests/cypress/downloads -tests/cypress/package-lock.json -package-lock.json .vscode/ *._S3rver_cors.xml services/database/local_buckets diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff0fa40ee..fb9dce0ac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,3 +36,10 @@ repos: rev: v8.12.0 hooks: - id: gitleaks + - repo: local + hooks: + - id: branch-name-validation + name: branch-name-validation + entry: .github/branch-name-validation.sh + language: script + pass_filenames: false diff --git a/package.json b/package.json index 59a302eca..ad4408cfc 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "test": "tests" }, "scripts": { - "test": "cd tests/cypress && npm test && cd -", - "test:ci": "cd tests/cypress && yarn run test:ci" + "test": "cd tests && npm test && cd -", + "test:ci": "cd tests && yarn run test:ci" }, "repository": { "type": "git", diff --git a/services/app-api/forms/naaar.json b/services/app-api/forms/naaar.json index 9f43d0602..65cf7913f 100644 --- a/services/app-api/forms/naaar.json +++ b/services/app-api/forms/naaar.json @@ -3,7 +3,9 @@ "name": "NAAAR Report Submission Form", "basePath": "/naaar", "version": "NAAAR_2024-08-06", - "entities": {}, + "entities": { + "plans": { "required": true } + }, "routes": [ { "name": "I. State and program information", @@ -37,11 +39,10 @@ "type": "p", "content": "According to the Paperwork Reduction Act of 1995, no persons are required to respond to a collection of information unless it displays a valid OMB control number. The valid OMB control number for this information collection is 0938-0920 (Expires: June 30, 2024). The time required to complete this information collection is estimated to average 6 hours per response, including the time to review instructions, search existing data resources, gather the data needed, and complete and review the information collection. If you have comments concerning the accuracy of the time estimate(s) or suggestions for improving this form, please write to: CMS, 7500 Security Boulevard, Attn: PRA Reports Clearance Officer, Mail Stop C4-26-05, Baltimore, Maryland 21244-1850" } - ] }, "form": { - "id": "isiarc", + "id": "isiars", "fields": [ { "id": "contactName", @@ -62,7 +63,7 @@ } }, { - "id": "stateOrTerritory", + "id": "stateName", "type": "text", "validation": "textOptional", "props": { @@ -83,12 +84,115 @@ }, { "id": "reportingScenario", - "type": "text", - "validation": "textOptional", + "type": "radio", + "validation": "radio", "props": { - "label": "I.A.5 Reporting scenario (TODO)", - "hint": "TODO: Convert to radio", - "disabled": true + "label": "1.A.5 Reporting scenario", + "hint": [ + { + "type": "p", + "content": "Enter the scenario under which the state is submitting this form to CMS. Under 42 C.F.R. § 438.207(c) - (d), the state must submit an assurance of compliance after reviewing documentation submitted by a plan under the following three scenarios:" + }, + { + "type": "ul", + "children": [ + { + "type": "li", + "content": "Scenario 1: At the time the plan enters into a contract with the state;" + }, + { + "type": "li", + "content": "Scenario 2: On an annual basis;" + }, + { + "type": "li", + "content": "Scenario 3: Any time there has been a significant change (as defined by the state) in the plan’s operations that would affect its adequacy of capacity and services, including (1) changes in the plan’s services, benefits, geographic service area, composition of or payments to its provider network, or (2) enrollment of a new population in the plan." + } + ] + }, + { + "type": "p", + "content": "States should complete one (1) form with information for applicable managed care plans and programs. For example, if the state submits this form under scenario 1 above, the state should submit this form only for the managed care plan (and the applicable managed care program) that entered into a new contract with the state. The state should not report on any other plans or programs under this scenario. As another example, if the state submits this form under scenario 2, the state should submit this form for all managed care plans and managed care programs." + } + ], + "choices": [ + { + "id": "g3B64XNZhZCZ017er2Y6hJ", + "label": "Scenario 1: New contract" + }, + { + "id": "mlvX4umhgnU199rPmMJybc", + "label": "Scenario 2: Annual report" + }, + { + "id": "l7XDjKKDrwryEe5YgDTTmP", + "label": "Scenario 3: Significant change - services" + }, + { + "id": "7ZtsZ25K0romDSTAIStzzY", + "label": "Scenario 3: Significant change - benefits" + }, + { + "id": "ObbAyoYB97trcbVN0OFQ2X", + "label": "Scenario 3: Significant change - geographic service area" + }, + { + "id": "FC9hagL8vz3i0bFoHsJYEw", + "label": "Scenario 3: Significant change - composition of provider network" + }, + { + "id": "ziLQDOD5TucgEHzmlzOEn6", + "label": "Scenario 3: Significant change - payments to provider network" + }, + { + "id": "TDkPWZJai0kwHIp4zm1ZVy", + "label": "Scenario 3: Significant change - enrollment of new population" + }, + { + "id": "IyPRlUA4k3bTkj5rF1E8zg", + "label": "Other, specify", + "children": [ + { + "id": "reportingScenario-otherText", + "type": "textarea", + "validation": { + "type": "text", + "nested": true, + "parentFieldName": "reportingScenario" + }, + "props": { + "label": "1.A.6 Reporting scenario - other ", + "hint": "If the state is submitting this form to CMS for any reason other than those specified in I.A.5, explain the reason." + } + } + ] + } + ] + } + } + ] + } + }, + { + "name": "Add Plans", + "path": "/naaar/state-and-program-information/add-plans", + "pageType": "standard", + "verbiage": { + "intro": { + "section": "I. State and program information", + "subsection": "Add plans", + "info": "Enter the name of each plan that participates in the program for which the state is reporting data." + } + }, + "form": { + "id": "iap", + "fields": [ + { + "id": "plans", + "type": "dynamic", + "validation": "dynamic", + "props": { + "label": "Plan name" } } ] diff --git a/services/app-api/handlers/reports/submit.ts b/services/app-api/handlers/reports/submit.ts index b2542814d..42649b224 100644 --- a/services/app-api/handlers/reports/submit.ts +++ b/services/app-api/handlers/reports/submit.ts @@ -29,6 +29,7 @@ import { MCPARReportMetadata, MLRReportMetadata, UserRoles, + NAAARReportMetadata, } from "../../utils/types"; export const submitReport = handler(async (event, _context) => { @@ -65,7 +66,8 @@ export const submitReport = handler(async (event, _context) => { const reportMetadata = response.Item as | MLRReportMetadata - | MCPARReportMetadata; + | MCPARReportMetadata + | NAAARReportMetadata; const { status, isComplete, fieldDataId, formTemplateId } = reportMetadata; if (status === "Submitted") { diff --git a/services/app-api/utils/types/reportContext.ts b/services/app-api/utils/types/reportContext.ts index 69dd52e41..e8e4af24e 100644 --- a/services/app-api/utils/types/reportContext.ts +++ b/services/app-api/utils/types/reportContext.ts @@ -30,6 +30,8 @@ export interface ReportMetadataShape extends ReportKeys { copyFieldDataSourceId?: string; programIsPCCM?: Choice[]; previousRevisions: string[]; + planTypeIncludedInProgram?: Choice[]; + "planTypeIncludedInProgram-otherText"?: string; novMcparRelease?: boolean; } diff --git a/services/app-api/utils/types/reports.ts b/services/app-api/utils/types/reports.ts index 090e74599..d41e813f6 100644 --- a/services/app-api/utils/types/reports.ts +++ b/services/app-api/utils/types/reports.ts @@ -184,6 +184,16 @@ export interface MCPARReportMetadata extends ReportMetadata { novMcparRelease: boolean; } +export interface NAAARReportMetadata extends ReportMetadata { + programName: string; + reportType: "NAAAR"; + reportingPeriodStartDate: number; + reportingPeriodEndDate: number; + dueDate: number; + planTypeIncludedInProgram: Choice[]; + "planTypeIncludedInProgram-otherText"?: string; +} + // HELPER FUNCTIONS /** diff --git a/services/app-api/utils/validation/schemas.ts b/services/app-api/utils/validation/schemas.ts index 296a9bc2d..09b037b96 100644 --- a/services/app-api/utils/validation/schemas.ts +++ b/services/app-api/utils/validation/schemas.ts @@ -1,5 +1,5 @@ import * as yup from "yup"; -import { radioOptional } from "./completionSchemas"; +import { radioOptional, textOptional } from "./completionSchemas"; export const metadataValidationSchema = yup.object().shape({ programName: yup.string(), @@ -19,4 +19,6 @@ export const metadataValidationSchema = yup.object().shape({ submissionName: yup.string(), completionStatus: yup.mixed(), copyFieldDataSourceId: yup.string(), + planTypeIncludedInProgram: radioOptional(), + "planTypeIncludedInProgram-otherText": textOptional(), }); diff --git a/services/ui-src/package.json b/services/ui-src/package.json index 97a1aaa21..51aa9c30e 100644 --- a/services/ui-src/package.json +++ b/services/ui-src/package.json @@ -20,7 +20,7 @@ "@emotion/styled": "^11.12.0", "@hookform/resolvers": "^2.9.11", "@vitejs/plugin-react": "^4.3.2", - "aws-amplify": "^6.6.4", + "aws-amplify": "^6.6.5", "date-fns": "^2.30.0", "date-fns-tz": "^1.3.8", "dompurify": "^2.5.7", @@ -30,7 +30,7 @@ "html-react-parser": "^3.0.16", "jest": "^28.0.0", "jest-environment-jsdom": "^28.0.0", - "launchdarkly-react-client-sdk": "^3.2.0", + "launchdarkly-react-client-sdk": "^3.4.0", "nth-check": "2.1.1", "object-path": "^0.11.8", "react": "^17.0.1", @@ -41,9 +41,9 @@ "react-icons": "^4.3.1", "react-router-dom": "6.27.0", "react-uuid": "^1.0.3", - "sass": "^1.79.4", + "sass": "^1.80.7", "vite": "^5.4.8", - "vite-tsconfig-paths": "^4.3.2", + "vite-tsconfig-paths": "^5.1.2", "yup": "^0.32.11", "zustand": "^4.5.3" }, diff --git a/services/ui-src/src/components/fields/DynamicField.tsx b/services/ui-src/src/components/fields/DynamicField.tsx index f7a5c5c7f..ec99d734e 100644 --- a/services/ui-src/src/components/fields/DynamicField.tsx +++ b/services/ui-src/src/components/fields/DynamicField.tsx @@ -332,5 +332,8 @@ const sx = { }, textField: { width: "100%", + label: { + marginTop: "0", + }, }, }; diff --git a/services/ui-src/src/components/modals/AddEditReportModal.test.tsx b/services/ui-src/src/components/modals/AddEditReportModal.test.tsx index 66f256889..0bb79b098 100644 --- a/services/ui-src/src/components/modals/AddEditReportModal.test.tsx +++ b/services/ui-src/src/components/modals/AddEditReportModal.test.tsx @@ -10,6 +10,7 @@ import { mockMlrReport, mockMlrReportContext, mockMlrReportStore, + mockNaaarReport, mockNaaarReportContext, mockNaaarReportStore, mockStateUserStore, @@ -81,6 +82,22 @@ const mockSelectedMcparReport = { }, }; +// a similar assignment is performed in DashboardPage and is needed here to make sure the modal form hydrates +const mockSelectedNaaarReport = { + ...mockNaaarReport, + fieldData: { + programName: mockNaaarReport.programName, + reportingPeriodEndDate: convertDateUtcToEt( + mockNaaarReport.reportingPeriodEndDate + ), + reportingPeriodStartDate: convertDateUtcToEt( + mockNaaarReport.reportingPeriodStartDate + ), + combinedData: mockNaaarReport.combinedData, + planTypeIncludedInProgram: mockNaaarReport.planTypeIncludedInProgram, + }, +}; + const modalComponentWithSelectedReport = ( @@ -145,6 +162,22 @@ const naaarModalComponent = ( ); +const naaarModalComponentWithSelectedReport = ( + + + + + +); + describe("Test AddEditProgramModal", () => { beforeEach(async () => { mockedUseStore.mockReturnValue({ @@ -324,8 +357,16 @@ describe("Test AddEditReportModal functionality for NAAAR", () => { }); const fillForm = async (form: any) => { - const contactNameField = form.querySelector("[name='contactName']")!; - await userEvent.type(contactNameField, "fake contact name"); + const programNameField = form.querySelector("[name='programName']")!; + await userEvent.type(programNameField, "fake program name"); + const startDateField = form.querySelector( + "[name='reportingPeriodStartDate']" + )!; + await userEvent.type(startDateField, "1/1/2022"); + const endDateField = form.querySelector("[name='reportingPeriodEndDate']")!; + await userEvent.type(endDateField, "12/31/2022"); + const planTypeField = screen.getByLabelText("MCO") as HTMLInputElement; + await userEvent.click(planTypeField); const submitButton = screen.getByRole("button", { name: "Save" }); await userEvent.click(submitButton); }; @@ -340,6 +381,50 @@ describe("Test AddEditReportModal functionality for NAAAR", () => { expect(mockCloseHandler).toHaveBeenCalledTimes(1); }); }); + + test("Edit modal hydrates with report info and disables fields", async () => { + const result = render(naaarModalComponentWithSelectedReport); + const form = result.getByTestId("add-edit-report-form"); + const copyFieldDataSourceId = form.querySelector( + "[name='copyFieldDataSourceId']" + )!; + + // yoy copy field is disabled + expect(copyFieldDataSourceId).toHaveProperty("disabled", true); + + // hydrated values are in the modal + const programNameField = form.querySelector("[name='programName']")!; + const startDateField = form.querySelector( + "[name='reportingPeriodStartDate']" + )!; + const endDateField = form.querySelector("[name='reportingPeriodEndDate']")!; + + expect(programNameField).toHaveProperty( + "value", + mockNaaarReport.programName + ); + expect(startDateField).toHaveProperty( + "value", + convertDateUtcToEt(mockNaaarReport.reportingPeriodStartDate) + ); + expect(endDateField).toHaveProperty( + "value", + convertDateUtcToEt(mockNaaarReport.reportingPeriodEndDate) + ); + + await userEvent.click(screen.getByText("Cancel")); + }); + + test("Editing an existing report", async () => { + const result = render(naaarModalComponentWithSelectedReport); + const form = result.getByTestId("add-edit-report-form"); + await fillForm(form); + await waitFor(() => { + expect(mockUpdateReport).toHaveBeenCalledTimes(1); + expect(mockFetchReportsByState).toHaveBeenCalledTimes(1); + expect(mockCloseHandler).toHaveBeenCalledTimes(1); + }); + }); }); describe("Test AddEditReportModal accessibility", () => { diff --git a/services/ui-src/src/components/modals/AddEditReportModal.tsx b/services/ui-src/src/components/modals/AddEditReportModal.tsx index fb8aa6f96..09f944843 100644 --- a/services/ui-src/src/components/modals/AddEditReportModal.tsx +++ b/services/ui-src/src/components/modals/AddEditReportModal.tsx @@ -138,19 +138,35 @@ export const AddEditReportModal = ({ // NAAAR report payload const prepareNaaarPayload = (formData: any) => { - const contactName = formData["contactName"]; + const programName = formData["programName"]; + const copyFieldDataSourceId = formData["copyFieldDataSourceId"]; + const dueDate = calculateDueDate(formData["reportingPeriodEndDate"]); + const reportingPeriodStartDate = convertDateEtToUtc( + formData["reportingPeriodStartDate"] + ); + const reportingPeriodEndDate = convertDateEtToUtc( + formData["reportingPeriodEndDate"] + ); + const planTypeIncludedInProgram = formData["planTypeIncludedInProgram"]; return { metadata: { - contactName, + programName, + reportingPeriodStartDate, + reportingPeriodEndDate, + dueDate, lastAlteredBy: full_name, + copyFieldDataSourceId: copyFieldDataSourceId?.value, + planTypeIncludedInProgram, + "planTypeIncludedInProgram-otherText": + formData["planTypeIncludedInProgram-otherText"], locked: false, submissionCount: 0, previousRevisions: [], naaarReport, }, fieldData: { - contactName, + programName, }, }; }; @@ -227,6 +243,7 @@ export const AddEditReportModal = ({ content={{ heading: selectedReport?.id ? form.heading?.edit : form.heading?.add, subheading: selectedReport?.id ? "" : form.heading?.subheading, + intro: selectedReport?.id ? "" : form.heading?.intro, actionButtonText: submitting ? : "Save", closeButtonText: "Cancel", }} diff --git a/services/ui-src/src/components/modals/Modal.tsx b/services/ui-src/src/components/modals/Modal.tsx index 65137032d..773ed0518 100644 --- a/services/ui-src/src/components/modals/Modal.tsx +++ b/services/ui-src/src/components/modals/Modal.tsx @@ -39,8 +39,9 @@ export const Modal = ({ {content.subheading && ( - {content.subheading} + {content.subheading} )} + {content.intro && {content.intro}}