From 8458874b3d0ec8d7b03884108580e3dc4b8788d5 Mon Sep 17 00:00:00 2001 From: Hana Worku Date: Wed, 17 Apr 2024 09:58:48 -0500 Subject: [PATCH 1/7] wip --- .../EmailAnalystsTable.tsx | 153 ++++++++++++++++++ .../EmailSettingsTables.tsx | 54 ++----- .../src/pages/Settings/Settings.module.scss | 4 + 3 files changed, 166 insertions(+), 45 deletions(-) create mode 100644 services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx diff --git a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx new file mode 100644 index 0000000000..e8cc85922e --- /dev/null +++ b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx @@ -0,0 +1,153 @@ +import { Column, createColumnHelper, getCoreRowModel, getFacetedUniqueValues, getFilteredRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table" +import { StateAnalystsConfiguration } from "../../../gen/gqlClient" +import { useMemo, useRef } from "react" +import { FilterSelect, FilterSelectedOptionsType } from "../../../components/FilterAccordion" +import { DoubleColumnGrid } from "../../../components" +import { formatEmails } from "./EmailSettingsTables" +import { Table } from "@trussworks/react-uswds" + +import styles from '../Settings.module.scss' +import { pluralize } from "../../../common-code/formatters" + +const columnHelper = createColumnHelper< StateAnalystsConfiguration>() + +const EmailAnalystsTable = ({ + analysts, +}: { + analysts: StateAnalystsConfiguration[] +}) => { + const lastClickedElement = useRef(null) + const tableColumns = useMemo( + () => [ + columnHelper.accessor('stateCode', { + id: 'stateCode', + header: 'State', + cell: (info) => {info.getValue()}, + filterFn: `arrIncludesSome`, + }), + columnHelper.accessor('emails', { + id: 'emails', + header: 'Emails', + cell: (info) => {info.getValue()}, + filterFn: `arrIncludesSome`, + }), + ], + [] + ) + + const reactTable = useReactTable({ + data: analysts.sort((a, b) => + a['stateCode'] > b['stateCode'] ? -1 : 1 + ), + filterFns: { + dateRangeFilter: () => true, + }, + getCoreRowModel: getCoreRowModel(), + columns: tableColumns, + getFacetedUniqueValues: getFacetedUniqueValues(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + }) + + const filteredRows = reactTable.getRowModel().rows + + const stateColumn = reactTable.getColumn( + 'stateCode' + ) as Column + const emailsColumn = reactTable.getColumn( + 'emails' + ) as Column + const rowCount = `Displaying ${filteredRows.length} of ${analysts.length} ${pluralize( + 'analyst', + analysts.length + )}` + const updateFilters = ( + column: Column, + selectedOptions: FilterSelectedOptionsType, + filterName: string + ) => { + lastClickedElement.current = filterName + column.setFilterValue( + selectedOptions.map((selection) => selection.value) + ) + } + + return ( + <> +

State Analyst emails

+

+ State analysts email settings. Currently a standalone + configuration based on the state programs spreadsheet. +

+ + + ({ + value: state, + label: state, + }))} + onChange={(selectedOptions) => + updateFilters( + stateColumn, + selectedOptions, + 'state' + ) + } + /> + ({ + value: state, + label: state, + }))} + onChange={(selectedOptions) => + updateFilters( + emailsColumn, + selectedOptions, + 'emails' + ) + } + /> + +
+ {rowCount} +
+
+ + + + + + + + + + + {filteredRows.map((row) => { + return ( + + + + + + ) + })} + +
Analyst emails
StateInbox
{row.getValue('stateCode')} + {formatEmails(row.getValue('emails') || [])} +
+ + ) +} +export {EmailAnalystsTable} \ No newline at end of file diff --git a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx index 10b5743864..3b5f4c727f 100644 --- a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx +++ b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx @@ -3,15 +3,15 @@ import React from 'react' import { Loading } from '../../../components' import { EmailConfiguration, - StateAnalystsConfiguration, useFetchEmailSettingsQuery, } from '../../../gen/gqlClient' import { SettingsErrorAlert } from '../SettingsErrorAlert' import styles from '../Settings.module.scss' +import { EmailAnalystsTable } from './EmailAnalystsTable' const formatEmails = (arr?: string[]) => (arr ? arr.join(',') : 'NOT DEFINED') -export const EmailSettingsTable = ({ +const EmailSettingsTable = ({ type, }: { type: 'GENERAL' | 'ANALYSTS' | 'SUPPORT' @@ -26,20 +26,20 @@ export const EmailSettingsTable = ({ {loading && } {data && config && type === 'GENERAL' && ( - + )} {data && analysts && type === 'ANALYSTS' && ( - + )} {data && config && type === 'SUPPORT' && ( - + )} ) } -const EmailsGeneralTable = ({ config }: { config: EmailConfiguration }) => { +const EmailGeneralTable = ({ config }: { config: EmailConfiguration }) => { console.info('ALL CONFIG', JSON.stringify(config)) return ( <> @@ -98,45 +98,7 @@ const EmailsGeneralTable = ({ config }: { config: EmailConfiguration }) => { ) } -const EmailsAnalystsTable = ({ - analysts, -}: { - analysts: StateAnalystsConfiguration[] -}) => { - return ( - <> -

State Analyst emails

-

- State analysts email settings. Currently a standalone - configuration based on the state programs spreadsheet. -

- - - - - - - - - - {analysts && - analysts.map((analyst, index) => { - return ( - - - - - ) - })} - -
Analyst emails
InboxState
- {formatEmails(analyst.emails || [])} - {analyst.stateCode}
- - ) -} - -const EmailsSupportTable = ({ config }: { config: EmailConfiguration }) => { +const EmailSupportTable = ({ config }: { config: EmailConfiguration }) => { return ( <>

Support emails

@@ -176,3 +138,5 @@ const EmailsSupportTable = ({ config }: { config: EmailConfiguration }) => { ) } + +export {EmailSettingsTable, formatEmails} \ No newline at end of file diff --git a/services/app-web/src/pages/Settings/Settings.module.scss b/services/app-web/src/pages/Settings/Settings.module.scss index bcd35cad68..cc8422fcda 100644 --- a/services/app-web/src/pages/Settings/Settings.module.scss +++ b/services/app-web/src/pages/Settings/Settings.module.scss @@ -12,6 +12,10 @@ border-left: 0; border-right: 0; } + td { + word-wrap: break-word; + overflow-wrap: break-word; + } } table { From 07d292d79f3e5ab8dc00f12a10afe9ad6052d056 Mon Sep 17 00:00:00 2001 From: Hana Worku Date: Thu, 18 Apr 2024 15:07:06 -0500 Subject: [PATCH 2/7] Fixup tests --- .../ContractDetailsSummarySection.tsx | 4 +- .../RateDetailsSummarySection.tsx | 4 +- .../UploadedDocumentsTable.test.tsx | 2 + .../UploadedDocumentsTable.tsx | 2 +- .../ContractDetailsSummarySectionV2.test.tsx | 16 +- .../V2/ReviewSubmit/ReviewSubmitV2.test.tsx | 57 ++- .../apolloMocks/contractPackageDataMock.ts | 377 +++++++++++++++++- .../src/testHelpers/apolloMocks/index.ts | 3 +- 8 files changed, 439 insertions(+), 26 deletions(-) diff --git a/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx b/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx index 50222683f3..9f25cdffa0 100644 --- a/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx @@ -309,7 +309,7 @@ export const ContractDetailsSummarySection = ({ ? new Date( documentDateLookupTable.previousSubmissionDate ) - : undefined + : null } caption="Contract" documentCategory="Contract" @@ -325,7 +325,7 @@ export const ContractDetailsSummarySection = ({ ? new Date( documentDateLookupTable.previousSubmissionDate ) - : undefined + : null } caption="Contract supporting documents" documentCategory="Contract-supporting" diff --git a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.tsx b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.tsx index 90c3fe7d5b..27b89b9143 100644 --- a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.tsx @@ -364,7 +364,7 @@ export const RateDetailsSummarySection = ({ ? new Date( documentDateLookupTable.previousSubmissionDate ) - : undefined + : null } multipleDocumentsAllowed={false} caption="Rate certification" @@ -388,7 +388,7 @@ export const RateDetailsSummarySection = ({ ? new Date( documentDateLookupTable.previousSubmissionDate ) - : undefined + : null } caption="Rate supporting documents" isSupportingDocuments diff --git a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.test.tsx b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.test.tsx index 19510796d4..a071847f1d 100644 --- a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.test.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.test.tsx @@ -67,6 +67,7 @@ describe('UploadedDocumentsTable', () => { renderWithProviders( { renderWithProviders( { const defaultApolloMocks = { @@ -314,7 +315,20 @@ describe('ContractDetailsSummarySection', () => { it('does not render supporting contract documents table when no documents exist', () => { renderWithProviders( , { diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx index a67d3cfecf..ddc8aad20a 100644 --- a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx +++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ReviewSubmitV2.test.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor } from '@testing-library/react' +import { screen, waitFor, within } from '@testing-library/react' import { renderWithProviders, userClickByRole, @@ -7,6 +7,7 @@ import { ReviewSubmitV2 } from './ReviewSubmitV2' import { fetchCurrentUserMock, fetchContractMockSuccess, + mockContractPackageUnlocked, } from '../../../../../testHelpers/apolloMocks' import { Route, Routes } from 'react-router-dom' import { RoutesRecord } from '../../../../../constants' @@ -177,6 +178,60 @@ describe('ReviewSubmit', () => { }) }) + it('extracts the correct dates from unlocked submission and displays them in tables', async () => { + const contractMock = fetchContractMockSuccess({ + contract: mockContractPackageUnlocked(), + }) + + renderWithProviders( + + } + /> + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ statusCode: 200 }), + contractMock, + ], + }, + routerProvider: { + route: '/submissions/test-abc-123/edit/review-and-submit', + }, + featureFlags: { + 'link-rates': true, + }, + } + ) + + await waitFor(() => { + const contractDocRow = screen.getByRole('row', { + name: /contract document/, + }) + expect( + within(contractDocRow).getByText('1/1/24') + ).toBeInTheDocument() + const contractSupporting1Row = screen.getByRole('row', { + name: /contractSupporting1/, + }) + expect( + within(contractSupporting1Row).getByText('1/15/24') + ).toBeInTheDocument() + const rateDocRow = screen.getByRole('row', { + name: /rate certification/, + }) + expect(within(rateDocRow).getByText('1/13/24')).toBeInTheDocument() + const rateSupporting1Row = screen.getByRole('row', { + name: /rateSupporting1/, + }) + expect( + within(rateSupporting1Row).getByText('1/15/24') + ).toBeInTheDocument() + }) + }) + it('displays back, save as draft, and submit buttons', async () => { renderWithProviders( diff --git a/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts index ee8ee65ae0..4cbbfa9a24 100644 --- a/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts +++ b/services/app-web/src/testHelpers/apolloMocks/contractPackageDataMock.ts @@ -1,9 +1,106 @@ import { mockMNState } from '../../common-code/healthPlanFormDataMocks/healthPlanFormData' -import { Contract } from '../../gen/gqlClient' +import { Contract, ContractFormData } from '../../gen/gqlClient' // Assemble versions of Contract data (with or without rates) for jest testing. Intended for use with related GQL Moc file. function mockContractPackageDraft( partial?: Partial +): Contract { + return { + __typename: 'Contract', + initiallySubmittedAt: undefined, + status: 'DRAFT', + createdAt: new Date('01/01/24'), + updatedAt: new Date(), + id: 'test-abc-123', + stateCode: 'MN', + state: mockMNState(), + stateNumber: 5, + draftRevision: { + __typename: 'ContractRevision', + submitInfo: undefined, + unlockInfo: undefined, + id: '123', + createdAt: new Date(), + updatedAt: new Date(), + contractName: 'MCR-0005-alvhalfhdsalf', + formData: mockContractFormData(partial?.draftRevision?.formData) + }, + + draftRates: [ + { + id: '123', + createdAt: new Date(), + updatedAt: new Date(), + status: 'DRAFT', + stateCode: 'MN', + revisions: [], + state: mockMNState(), + stateNumber: 5, + draftRevision: { + id: '123', + contractRevisions: [], + createdAt: new Date(), + updatedAt: new Date(), + formData: { + rateType: 'AMENDMENT', + rateCapitationType: 'RATE_CELL', + rateDocuments: [ + { + s3URL: 's3://bucketname/key/rate', + sha256: 'fakesha', + name: 'rate certification', + dateAdded: new Date('01/13/2024') + }, + ], + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/ratesupporting1', + sha256: 'fakesha', + name: 'rateSupporting1', + dateAdded: new Date('01/15/2024') + }, + { + s3URL: 's3://bucketname/key/rateSupporting2', + sha256: 'fakesha', + name: 'rateSupporting2', + dateAdded: new Date('01/13/2024') + }, + ], + rateDateStart: new Date(), + rateDateEnd: new Date(), + rateDateCertified: new Date(), + amendmentEffectiveDateStart: new Date(), + amendmentEffectiveDateEnd: new Date(), + rateProgramIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'], + certifyingActuaryContacts: [ + { + actuarialFirm: 'DELOITTE', + name: 'Actuary Contact 1', + titleRole: 'Test Actuary Contact 1', + email: 'actuarycontact1@test.com', + }, + ], + addtlActuaryContacts: [ + { + actuarialFirm: 'DELOITTE', + name: 'Actuary Contact 1', + titleRole: 'Test Actuary Contact 1', + email: 'additionalactuarycontact1@test.com', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY', + packagesWithSharedRateCerts: [], + } + } + + }, + ], + packageSubmissions: [], + ...partial, + } +} +function mockContractWithLinkedRateDraft( + partial?: Partial ): Contract { return { __typename: 'Contract', @@ -78,7 +175,7 @@ function mockContractPackageDraft( id: '123', createdAt: new Date(), updatedAt: new Date(), - status: 'DRAFT', + status: 'SUBMITTED', stateCode: 'MN', revisions: [], state: mockMNState(), @@ -133,14 +230,14 @@ function mockContractPackageDraft( ...partial, } } -function mockContractWithLinkedRateDraft( +function mockContractPackageUnlocked( partial?: Partial ): Contract { return { __typename: 'Contract', initiallySubmittedAt: undefined, - status: 'DRAFT', - createdAt: new Date(), + status: 'UNLOCKED', + createdAt: new Date('01/01/24'), updatedAt: new Date(), id: 'test-abc-123', stateCode: 'MN', @@ -160,7 +257,20 @@ function mockContractWithLinkedRateDraft( submissionType: 'CONTRACT_AND_RATES', riskBasedContract: true, submissionDescription: 'A real submission', - supportingDocuments: [], + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/contractsupporting1', + sha256: 'fakesha', + name: 'contractSupporting1', + dateAdded: new Date('01/15/2024') + }, + { + s3URL: 's3://bucketname/key/contractSupporting2', + sha256: 'fakesha', + name: 'contractSupporting2', + dateAdded: new Date('01/13/2024') + }, + ], stateContacts: [ { name: 'State Contact 1', @@ -174,8 +284,8 @@ function mockContractWithLinkedRateDraft( { s3URL: 's3://bucketname/one-two/one-two.png', sha256: 'fakesha', - name: 'one two', - dateAdded: new Date() + name: 'contract document', + dateAdded: new Date('01/01/2024') }, ], contractDateStart: new Date('01/01/2023'), @@ -209,7 +319,7 @@ function mockContractWithLinkedRateDraft( id: '123', createdAt: new Date(), updatedAt: new Date(), - status: 'SUBMITTED', + status: 'DRAFT', stateCode: 'MN', revisions: [], state: mockMNState(), @@ -226,11 +336,24 @@ function mockContractWithLinkedRateDraft( { s3URL: 's3://bucketname/key/rate', sha256: 'fakesha', - name: 'rate', - dateAdded: new Date() + name: 'rate certification', + dateAdded: new Date('01/13/2024') + }, + ], + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/ratesupporting1', + sha256: 'fakesha', + name: 'rateSupporting1', + dateAdded: new Date('01/15/2024') + }, + { + s3URL: 's3://bucketname/key/rateSupporting2', + sha256: 'fakesha', + name: 'rateSupporting2', + dateAdded: new Date('01/13/2024') }, ], - supportingDocuments: [], rateDateStart: new Date(), rateDateEnd: new Date(), rateDateCertified: new Date(), @@ -260,7 +383,134 @@ function mockContractWithLinkedRateDraft( }, ], - packageSubmissions: [], + packageSubmissions: [{ + cause: 'CONTRACT_SUBMISSION', + submitInfo: { + updatedAt: new Date('12/31/2023'), + updatedBy: 'example@state.com', + updatedReason: 'contract submit' + }, + submittedRevisions: [], + contractRevision: { + contractName: 'MCR-MN-0005-SNBC', + createdAt: new Date('01/01/2023'), + updatedAt: new Date('12/31/2023'), + id: '123', + formData: { + programIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'], + populationCovered: 'MEDICAID', + submissionType: 'CONTRACT_AND_RATES', + riskBasedContract: true, + submissionDescription: 'A real submission', + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/contractsupporting1', + sha256: 'fakesha', + name: 'contractSupporting1', + dateAdded: new Date('01/15/2023') + }, + { + s3URL: 's3://bucketname/key/contractSupporting2', + sha256: 'fakesha', + name: 'contractSupporting2', + dateAdded: new Date('01/13/2023') + }, + ], + stateContacts: [], + contractType: 'AMENDMENT', + contractExecutionStatus: 'EXECUTED', + contractDocuments: [ + { + s3URL: 's3://bucketname/key/contract', + sha256: 'fakesha', + name: 'contract', + dateAdded: new Date('01/01/2023') + }, + ], + contractDateStart: new Date(), + contractDateEnd: new Date(), + managedCareEntities: ['MCO'], + federalAuthorities: ['STATE_PLAN'], + inLieuServicesAndSettings: true, + modifiedBenefitsProvided: true, + modifiedGeoAreaServed: false, + modifiedMedicaidBeneficiaries: true, + modifiedRiskSharingStrategy: true, + modifiedIncentiveArrangements: false, + modifiedWitholdAgreements: false, + modifiedStateDirectedPayments: true, + modifiedPassThroughPayments: true, + modifiedPaymentsForMentalDiseaseInstitutions: false, + modifiedMedicalLossRatioStandards: true, + modifiedOtherFinancialPaymentIncentive: false, + modifiedEnrollmentProcess: true, + modifiedGrevienceAndAppeal: false, + modifiedNetworkAdequacyStandards: true, + modifiedLengthOfContract: false, + modifiedNonRiskPaymentArrangements: true, + statutoryRegulatoryAttestation: true, + statutoryRegulatoryAttestationDescription: "everything meets regulatory attestation" + } + }, + rateRevisions: [ + { + id: '1234', + createdAt: new Date('01/01/2023'), + updatedAt: new Date('01/01/2023'), + contractRevisions: [], + formData: { + rateType: 'AMENDMENT', + rateCapitationType: 'RATE_CELL', + rateDocuments: [ + { + s3URL: 's3://bucketname/key/rate', + sha256: 'fakesha', + name: 'rate', + dateAdded: new Date('01/01/2023') + }, + ], + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/rateSupporting1', + sha256: 'fakesha', + name: 'rate supporting 1', + dateAdded: new Date('01/15/2023') + }, + { + s3URL: 's3://bucketname/key/rateSupporting1', + sha256: 'fakesha', + name: 'rate supporting 2', + dateAdded: new Date('01/15/2023') + }, + ], + rateDateStart: new Date(), + rateDateEnd: new Date(), + rateDateCertified: new Date(), + amendmentEffectiveDateStart: new Date(), + amendmentEffectiveDateEnd: new Date(), + rateProgramIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'], + certifyingActuaryContacts: [ + { + actuarialFirm: 'DELOITTE', + name: 'Actuary Contact 1', + titleRole: 'Test Actuary Contact 1', + email: 'actuarycontact1@test.com', + }, + ], + addtlActuaryContacts: [ + { + actuarialFirm: 'DELOITTE', + name: 'Actuary Contact 1', + titleRole: 'Test Actuary Contact 1', + email: 'actuarycontact1@test.com', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY', + packagesWithSharedRateCerts: [] + } + }, + ], + }], ...partial, } } @@ -294,7 +544,20 @@ function mockContractPackageSubmitted( submissionType: 'CONTRACT_AND_RATES', riskBasedContract: true, submissionDescription: 'A real submission', - supportingDocuments: [], + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/contractsupporting1', + sha256: 'fakesha', + name: 'contractSupporting1', + dateAdded: new Date('01/15/2024') + }, + { + s3URL: 's3://bucketname/key/contractSupporting2', + sha256: 'fakesha', + name: 'contractSupporting2', + dateAdded: new Date('01/13/2024') + }, + ], stateContacts: [], contractType: 'AMENDMENT', contractExecutionStatus: 'EXECUTED', @@ -303,7 +566,7 @@ function mockContractPackageSubmitted( s3URL: 's3://bucketname/key/contract', sha256: 'fakesha', name: 'contract', - dateAdded: new Date() + dateAdded: new Date('01/01/2024') }, ], contractDateStart: new Date(), @@ -345,10 +608,23 @@ function mockContractPackageSubmitted( s3URL: 's3://bucketname/key/rate', sha256: 'fakesha', name: 'rate', - dateAdded: new Date() + dateAdded: new Date('01/01/2023') + }, + ], + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/rateSupporting1', + sha256: 'fakesha', + name: 'rate supporting 1', + dateAdded: new Date('01/15/2023') + }, + { + s3URL: 's3://bucketname/key/rateSupporting1', + sha256: 'fakesha', + name: 'rate supporting 2', + dateAdded: new Date('01/15/2023') }, ], - supportingDocuments: [], rateDateStart: new Date(), rateDateEnd: new Date(), rateDateCertified: new Date(), @@ -381,4 +657,69 @@ function mockContractPackageSubmitted( } } -export { mockContractPackageDraft, mockContractPackageSubmitted, mockContractWithLinkedRateDraft } +function mockContractFormData( partial?: Partial): ContractFormData { + return { + programIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'], + populationCovered: 'MEDICAID', + submissionType: 'CONTRACT_AND_RATES', + riskBasedContract: true, + submissionDescription: 'A real submission', + supportingDocuments: [ + { + s3URL: 's3://bucketname/key/contractsupporting1', + sha256: 'fakesha', + name: 'contractSupporting1', + dateAdded: new Date('01/15/2024') + }, + { + s3URL: 's3://bucketname/key/contractSupporting2', + sha256: 'fakesha', + name: 'contractSupporting2', + dateAdded: new Date('01/13/2024') + }, + ], + stateContacts: [ + { + name: 'State Contact 1', + titleRole: 'Test State Contact 1', + email: 'actuarycontact1@test.com', + }, + ], + contractType: 'AMENDMENT', + contractExecutionStatus: 'EXECUTED', + contractDocuments: [ + { + s3URL: 's3://bucketname/one-two/one-two.png', + sha256: 'fakesha', + name: 'contract document', + dateAdded: new Date('01/01/2024') + }, + ], + contractDateStart: new Date('01/01/2023'), + contractDateEnd: new Date('12/31/2023'), + managedCareEntities: ['MCO'], + federalAuthorities: ['STATE_PLAN'], + inLieuServicesAndSettings: true, + modifiedBenefitsProvided: true, + modifiedGeoAreaServed: false, + modifiedMedicaidBeneficiaries: true, + modifiedRiskSharingStrategy: true, + modifiedIncentiveArrangements: false, + modifiedWitholdAgreements: false, + modifiedStateDirectedPayments: true, + modifiedPassThroughPayments: true, + modifiedPaymentsForMentalDiseaseInstitutions: false, + modifiedMedicalLossRatioStandards: true, + modifiedOtherFinancialPaymentIncentive: false, + modifiedEnrollmentProcess: true, + modifiedGrevienceAndAppeal: false, + modifiedNetworkAdequacyStandards: true, + modifiedLengthOfContract: false, + modifiedNonRiskPaymentArrangements: true, + statutoryRegulatoryAttestation: true, + statutoryRegulatoryAttestationDescription: "everything meets regulatory attestation", + ...partial + } +} + +export { mockContractPackageDraft, mockContractPackageSubmitted, mockContractWithLinkedRateDraft, mockContractPackageUnlocked, mockContractFormData} diff --git a/services/app-web/src/testHelpers/apolloMocks/index.ts b/services/app-web/src/testHelpers/apolloMocks/index.ts index 656375ff07..403fd4a90f 100644 --- a/services/app-web/src/testHelpers/apolloMocks/index.ts +++ b/services/app-web/src/testHelpers/apolloMocks/index.ts @@ -65,7 +65,8 @@ export { export { mockContractPackageDraft, mockContractPackageSubmitted, - mockContractWithLinkedRateDraft + mockContractWithLinkedRateDraft, + mockContractPackageUnlocked } from './contractPackageDataMock' export { rateDataMock } from './rateDataMock' export { fetchContractMockSuccess, updateDraftContractRatesMockSuccess } from './contractGQLMock' From 7aa87f5e22dd0f6a79cee0530d95dcba749dd1c6 Mon Sep 17 00:00:00 2001 From: Hana Worku Date: Thu, 18 Apr 2024 15:07:32 -0500 Subject: [PATCH 3/7] Actual bugfix for LINK_RATES flag off --- .../src/documentHelpers/makeDocumentDateLookupTable.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts b/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts index 738bc7c084..0df8e6bcfc 100644 --- a/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts +++ b/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts @@ -30,12 +30,10 @@ function makeDocumentDateTable( Object.keys(revisionsLookup).forEach( (revisionId: string, index: number) => { const revision = revisionsLookup[revisionId] - if (index === 1) { - // second most recent revision + const previousSubmission = getDateAdded(revision) // used in UploadedDocumentsTable to determine if we should show NEW tag - if (previousSubmission) + if (previousSubmission && !lookupTable['previousSubmissionDate']) lookupTable['previousSubmissionDate'] = previousSubmission - } const allDocuments = getAllDocuments(revision.formData) allDocuments.forEach((doc) => { From adeab371d692ea7ea6f5c52283c8323cd0e2a39c Mon Sep 17 00:00:00 2001 From: Hana Worku Date: Thu, 18 Apr 2024 15:25:46 -0500 Subject: [PATCH 4/7] Revert "wip" This reverts commit 8458874b3d0ec8d7b03884108580e3dc4b8788d5. --- .../EmailAnalystsTable.tsx | 153 ------------------ .../EmailSettingsTables.tsx | 54 +++++-- .../src/pages/Settings/Settings.module.scss | 4 - 3 files changed, 45 insertions(+), 166 deletions(-) delete mode 100644 services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx diff --git a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx deleted file mode 100644 index e8cc85922e..0000000000 --- a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailAnalystsTable.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { Column, createColumnHelper, getCoreRowModel, getFacetedUniqueValues, getFilteredRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table" -import { StateAnalystsConfiguration } from "../../../gen/gqlClient" -import { useMemo, useRef } from "react" -import { FilterSelect, FilterSelectedOptionsType } from "../../../components/FilterAccordion" -import { DoubleColumnGrid } from "../../../components" -import { formatEmails } from "./EmailSettingsTables" -import { Table } from "@trussworks/react-uswds" - -import styles from '../Settings.module.scss' -import { pluralize } from "../../../common-code/formatters" - -const columnHelper = createColumnHelper< StateAnalystsConfiguration>() - -const EmailAnalystsTable = ({ - analysts, -}: { - analysts: StateAnalystsConfiguration[] -}) => { - const lastClickedElement = useRef(null) - const tableColumns = useMemo( - () => [ - columnHelper.accessor('stateCode', { - id: 'stateCode', - header: 'State', - cell: (info) => {info.getValue()}, - filterFn: `arrIncludesSome`, - }), - columnHelper.accessor('emails', { - id: 'emails', - header: 'Emails', - cell: (info) => {info.getValue()}, - filterFn: `arrIncludesSome`, - }), - ], - [] - ) - - const reactTable = useReactTable({ - data: analysts.sort((a, b) => - a['stateCode'] > b['stateCode'] ? -1 : 1 - ), - filterFns: { - dateRangeFilter: () => true, - }, - getCoreRowModel: getCoreRowModel(), - columns: tableColumns, - getFacetedUniqueValues: getFacetedUniqueValues(), - getFilteredRowModel: getFilteredRowModel(), - getSortedRowModel: getSortedRowModel(), - }) - - const filteredRows = reactTable.getRowModel().rows - - const stateColumn = reactTable.getColumn( - 'stateCode' - ) as Column - const emailsColumn = reactTable.getColumn( - 'emails' - ) as Column - const rowCount = `Displaying ${filteredRows.length} of ${analysts.length} ${pluralize( - 'analyst', - analysts.length - )}` - const updateFilters = ( - column: Column, - selectedOptions: FilterSelectedOptionsType, - filterName: string - ) => { - lastClickedElement.current = filterName - column.setFilterValue( - selectedOptions.map((selection) => selection.value) - ) - } - - return ( - <> -

State Analyst emails

-

- State analysts email settings. Currently a standalone - configuration based on the state programs spreadsheet. -

- - - ({ - value: state, - label: state, - }))} - onChange={(selectedOptions) => - updateFilters( - stateColumn, - selectedOptions, - 'state' - ) - } - /> - ({ - value: state, - label: state, - }))} - onChange={(selectedOptions) => - updateFilters( - emailsColumn, - selectedOptions, - 'emails' - ) - } - /> - -
- {rowCount} -
-
- - - - - - - - - - - {filteredRows.map((row) => { - return ( - - - - - - ) - })} - -
Analyst emails
StateInbox
{row.getValue('stateCode')} - {formatEmails(row.getValue('emails') || [])} -
- - ) -} -export {EmailAnalystsTable} \ No newline at end of file diff --git a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx index 3b5f4c727f..10b5743864 100644 --- a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx +++ b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx @@ -3,15 +3,15 @@ import React from 'react' import { Loading } from '../../../components' import { EmailConfiguration, + StateAnalystsConfiguration, useFetchEmailSettingsQuery, } from '../../../gen/gqlClient' import { SettingsErrorAlert } from '../SettingsErrorAlert' import styles from '../Settings.module.scss' -import { EmailAnalystsTable } from './EmailAnalystsTable' const formatEmails = (arr?: string[]) => (arr ? arr.join(',') : 'NOT DEFINED') -const EmailSettingsTable = ({ +export const EmailSettingsTable = ({ type, }: { type: 'GENERAL' | 'ANALYSTS' | 'SUPPORT' @@ -26,20 +26,20 @@ const EmailSettingsTable = ({ {loading && } {data && config && type === 'GENERAL' && ( - + )} {data && analysts && type === 'ANALYSTS' && ( - + )} {data && config && type === 'SUPPORT' && ( - + )} ) } -const EmailGeneralTable = ({ config }: { config: EmailConfiguration }) => { +const EmailsGeneralTable = ({ config }: { config: EmailConfiguration }) => { console.info('ALL CONFIG', JSON.stringify(config)) return ( <> @@ -98,7 +98,45 @@ const EmailGeneralTable = ({ config }: { config: EmailConfiguration }) => { ) } -const EmailSupportTable = ({ config }: { config: EmailConfiguration }) => { +const EmailsAnalystsTable = ({ + analysts, +}: { + analysts: StateAnalystsConfiguration[] +}) => { + return ( + <> +

State Analyst emails

+

+ State analysts email settings. Currently a standalone + configuration based on the state programs spreadsheet. +

+ + + + + + + + + + {analysts && + analysts.map((analyst, index) => { + return ( + + + + + ) + })} + +
Analyst emails
InboxState
+ {formatEmails(analyst.emails || [])} + {analyst.stateCode}
+ + ) +} + +const EmailsSupportTable = ({ config }: { config: EmailConfiguration }) => { return ( <>

Support emails

@@ -138,5 +176,3 @@ const EmailSupportTable = ({ config }: { config: EmailConfiguration }) => { ) } - -export {EmailSettingsTable, formatEmails} \ No newline at end of file diff --git a/services/app-web/src/pages/Settings/Settings.module.scss b/services/app-web/src/pages/Settings/Settings.module.scss index cc8422fcda..bcd35cad68 100644 --- a/services/app-web/src/pages/Settings/Settings.module.scss +++ b/services/app-web/src/pages/Settings/Settings.module.scss @@ -12,10 +12,6 @@ border-left: 0; border-right: 0; } - td { - word-wrap: break-word; - overflow-wrap: break-word; - } } table { From a36bf5a2190715f1079aadf0be9056368407237c Mon Sep 17 00:00:00 2001 From: Hana Worku Date: Thu, 18 Apr 2024 15:39:22 -0500 Subject: [PATCH 5/7] Add to cypress checks that N/A does not display. --- services/cypress/integration/cmsWorkflow/rateReview.spec.ts | 3 +++ .../cypress/integration/cmsWorkflow/unlockResubmit.spec.ts | 3 +++ .../cypress/integration/cmsWorkflow/viewSubmission.spec.ts | 4 ++++ .../stateWorkflow/questionResponse/questionResponse.spec.ts | 4 ++++ 4 files changed, 14 insertions(+) diff --git a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts index b7d476c7b0..b4e03ffed7 100644 --- a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts +++ b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts @@ -74,6 +74,9 @@ describe('CMS user can view rate reviews', () => { }) cy.findByText('rate1SupportingDocument1.pdf').should('exist') + // No document dates or other fields are undefined + cy.findByText('N/A').should('not.exist') + // Go back to dashboard and check both rates in the table // check the dashboard has the columns we expect cy.findByText('Back to dashboard').should('exist').click() diff --git a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts index e12dd3526f..bb02eae69c 100644 --- a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts +++ b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts @@ -196,6 +196,9 @@ describe('CMS user', () => { cy.navigateFormByDirectLink(submissionURL) cy.findByTestId('updatedSubmissionBanner').should('exist') + // No document dates or other fields are undefined + cy.findByText('N/A').should('not.exist') + // Should have change history records cy.findAllByTestId('change-history-record').should('have.length', 5) diff --git a/services/cypress/integration/cmsWorkflow/viewSubmission.spec.ts b/services/cypress/integration/cmsWorkflow/viewSubmission.spec.ts index b29cc9f7b2..f3d4af6885 100644 --- a/services/cypress/integration/cmsWorkflow/viewSubmission.spec.ts +++ b/services/cypress/integration/cmsWorkflow/viewSubmission.spec.ts @@ -95,6 +95,10 @@ describe('CMS user can view submission', () => { cy.findByText(submissionName).should('exist').click() cy.url({ timeout: 10_000 }).should('contain', submissionId) cy.findByTestId('submission-summary').should('exist') + + + // No document dates or other fields are undefined + cy.findByText('N/A').should('not.exist') }) }) }) diff --git a/services/cypress/integration/stateWorkflow/questionResponse/questionResponse.spec.ts b/services/cypress/integration/stateWorkflow/questionResponse/questionResponse.spec.ts index 5f8bc3e0b1..2f99690150 100644 --- a/services/cypress/integration/stateWorkflow/questionResponse/questionResponse.spec.ts +++ b/services/cypress/integration/stateWorkflow/questionResponse/questionResponse.spec.ts @@ -93,6 +93,10 @@ describe('Q&A', () => { { timeout: 5_000 } ).should('exist') }) + + + // No document dates or other fields are undefined + cy.findByText('N/A').should('not.exist') } ) }) From 8b8044b12551a2bd44f931855cfdac08e2b4ce64 Mon Sep 17 00:00:00 2001 From: Hana Worku Date: Thu, 18 Apr 2024 16:32:45 -0500 Subject: [PATCH 6/7] Actually fix the existing prod bug with makeDocumentDateLookupTable --- .../documentHelpers/makeDocumentDateLookupTable.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts b/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts index 0df8e6bcfc..89100623ce 100644 --- a/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts +++ b/services/app-web/src/documentHelpers/makeDocumentDateLookupTable.ts @@ -25,15 +25,17 @@ function makeDocumentDateTable( revisionsLookup: RevisionsLookupType ): DocumentDateLookupTableType { const lookupTable: DocumentDateLookupTableType = { - previousSubmissionDate: null, + previousSubmissionDate: null, // the last time there was a submission on this package } - Object.keys(revisionsLookup).forEach( - (revisionId: string, index: number) => { + const listOfRevisionLookups = Object.keys(revisionsLookup) + listOfRevisionLookups.forEach( + (revisionId: string, index) => { const revision = revisionsLookup[revisionId] - const previousSubmission = getDateAdded(revision) // used in UploadedDocumentsTable to determine if we should show NEW tag - if (previousSubmission && !lookupTable['previousSubmissionDate']) - lookupTable['previousSubmissionDate'] = previousSubmission + const submitDate = revision.submitInfo?.updatedAt + if (submitDate && (listOfRevisionLookups.length === 1 || index === 1)) { // if we have a package with only one submitted revision, use that - otherwise use whatever in is the 1 index because thats the last submitted + lookupTable['previousSubmissionDate'] = submitDate + } const allDocuments = getAllDocuments(revision.formData) allDocuments.forEach((doc) => { From 80470fad0f7295ea1d7ec9b1d1373c77c68bb4d4 Mon Sep 17 00:00:00 2001 From: Hana Worku Date: Fri, 19 Apr 2024 16:17:27 -0500 Subject: [PATCH 7/7] Fix build error --- .../RateDetailsSummarySection/SingleRateSummarySection.tsx | 2 +- .../pages/StateSubmission/RateDetails/V2/LinkedRateSummary.tsx | 1 + .../V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx | 2 +- .../V2/ReviewSubmit/RateDetailsSummarySectionV2.tsx | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.tsx b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.tsx index c3247b7b1f..f3b43803ec 100644 --- a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.tsx @@ -99,7 +99,7 @@ export const SingleRateSummarySection = ({ const navigate = useNavigate() const rateRevision = rate.revisions[0] const formData: RateFormData = rateRevision?.formData - const lastSubmittedDate = rate.revisions[0]?.submitInfo?.updatedAt + const lastSubmittedDate = rate.revisions[0]?.submitInfo?.updatedAt ?? null const isRateAmendment = formData.rateType === 'AMENDMENT' const isUnlocked = rate.status === 'UNLOCKED' const explainMissingData = diff --git a/services/app-web/src/pages/StateSubmission/RateDetails/V2/LinkedRateSummary.tsx b/services/app-web/src/pages/StateSubmission/RateDetails/V2/LinkedRateSummary.tsx index 0cf5083f21..e5c8977836 100644 --- a/services/app-web/src/pages/StateSubmission/RateDetails/V2/LinkedRateSummary.tsx +++ b/services/app-web/src/pages/StateSubmission/RateDetails/V2/LinkedRateSummary.tsx @@ -64,6 +64,7 @@ export const LinkedRateSummary = ({ caption="Rate certification" documentCategory="Rate certification" isEditing={false} + previousSubmissionDate={null} /> ) diff --git a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx index 81a5f58f2e..ddae446d85 100644 --- a/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx +++ b/services/app-web/src/pages/StateSubmission/ReviewSubmit/V2/ReviewSubmit/ContractDetailsSummarySectionV2.tsx @@ -157,7 +157,7 @@ export const ContractDetailsSummarySectionV2 = ({ isPreviousSubmission, ]) const lastSubmittedDate = - getLastContractSubmission(contract)?.submitInfo.updatedAt + getLastContractSubmission(contract)?.submitInfo.updatedAt ?? null return (