Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MCR-3900 Can continue or save as draft with new API #2313

Merged
merged 7 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,19 @@ async function updateDraftContractRates(
)
}

let nextRateNumber = state.latestStateRateCertNumber
let nextRateNumber = state.latestStateRateCertNumber + 1

// create new rates with new revisions
const ratesToCreate = args.rateUpdates.create.map((ru) => {
const rateFormDataem = ru.formData
const rateFormData = ru.formData
const thisRateNumber = nextRateNumber
nextRateNumber++
return {
stateCode: contract.stateCode,
stateNumber: thisRateNumber,
revisions: {
create: prismaRateCreateFormDataFromDomain(
rateFormDataem
rateFormData
),
},
}
Expand Down
8 changes: 4 additions & 4 deletions services/app-web/src/formHelpers/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ const formatForApi = (attribute: string): string | null => {

// Convert api data for use in form. Form fields must be a string.
// Empty values as an empty string, dates in date picker as YYYY-MM-DD, boolean as "Yes" "No" values
const formatForForm = (
attribute: boolean | Date | string | null | undefined
): string => {
function formatForForm<T> (
attribute: T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect you could just use any here and it would work the same as the generic parameter

): string {
if (attribute === null || attribute === undefined) {
return ''
} else if (attribute instanceof Date) {
return dayjs(attribute).utc().format('YYYY-MM-DD')
} else if (typeof attribute === 'boolean') {
return attribute ? 'YES' : 'NO'
} else {
return attribute
return attribute.toString()
}
}

Expand Down
19 changes: 18 additions & 1 deletion services/app-web/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,24 @@ Amplify.configure({

const authMode = process.env.REACT_APP_AUTH_MODE
assertIsAuthMode(authMode)
const cache = new InMemoryCache()
const cache = new InMemoryCache({
typePolicies: {
ContractRevision: {
fields: {
formData: {
merge: true,
},
},
},
RateRevision: {
fields: {
formData: {
merge: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting GQL warnings (not blocking but they clutter the console) because form datas dont have id but are nested objects that are changing.

https://www.apollographql.com/docs/react/caching/cache-field-behavior/#merging-non-normalized-objects

Following docs, I believe this is the proper way to address but welcome other eyes.

Copy link
Contributor

@macrael macrael Mar 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, funny because these aren't really stand alone objects, we just made formData to wrap the data up nicely. This looks right to me based on those docs

},
},
},
},
})
const defaultOptions: DefaultOptions = {
watchQuery: {
fetchPolicy: 'network-only',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const LinkRates = (): React.ReactElement => {
onSubmit={(values) => console.info('submitted', values)}
>
<form>
<LinkYourRates fieldNamePrefix="rates.1" index={1} />
<LinkYourRates fieldNamePrefix="rateForms.1" index={1} />
</form>
</Formik>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('LinkYourRates', () => {
onSubmit={(values) => console.info('submitted', values)}
>
<form>
<LinkYourRates fieldNamePrefix="rates.1" index={1} />
<LinkYourRates fieldNamePrefix="rateForms.1" index={1} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good name change

</form>
</Formik>,
{
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('LinkYourRates', () => {
onSubmit={(values) => console.info('submitted', values)}
>
<form>
<LinkYourRates fieldNamePrefix="rates.1" index={1} />
<LinkYourRates fieldNamePrefix="rateForms.1" index={1} />
</form>
</Formik>,
{
Expand Down Expand Up @@ -83,7 +83,7 @@ describe('LinkYourRates', () => {
onSubmit={(values) => console.info('submitted', values)}
>
<form>
<LinkYourRates fieldNamePrefix="rates.1" index={1} />
<LinkYourRates fieldNamePrefix="rateForms.1" index={1} />
</form>
</Formik>,
{
Expand Down
107 changes: 4 additions & 103 deletions services/app-web/src/pages/RateEdit/RateEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import React from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import {
RateFormDataInput,
UpdateInformation,
useFetchRateQuery,
useSubmitRateMutation,
} from '../../gen/gqlClient'
import { GridContainer } from '@trussworks/react-uswds'
import { Loading } from '../../components'
import { GenericErrorPage } from '../Errors/GenericErrorPage'
import { RateFormDataInput } from '../../gen/gqlClient'

import { RateDetailsV2 } from '../StateSubmission/RateDetails/V2/RateDetailsV2'
import { RouteT, RoutesRecord } from '../../constants'
import { useAuth } from '../../contexts/AuthContext'
import { ErrorForbiddenPage } from '../Errors/ErrorForbiddenPage'
import { Error404 } from '../Errors/Error404Page'
import { PageBannerAlerts } from '../StateSubmission'
import { RouteT } from '../../constants'

export type SubmitRateHandler = (
rateID: string,
Expand All @@ -23,97 +11,10 @@ export type SubmitRateHandler = (
redirect: RouteT
) => void

type RouteParams = {
id: string
}

export const RateEdit = (): React.ReactElement => {
const navigate = useNavigate()
const { id } = useParams<keyof RouteParams>()
const { loggedInUser } = useAuth()
if (!id) {
throw new Error(
'PROGRAMMING ERROR: id param not set in state submission form.'
)
}

// API handling
const {
data: fetchData,
loading: fetchLoading,
error: fetchError,
} = useFetchRateQuery({
variables: {
input: {
rateID: id,
},
},
})
const rate = fetchData?.fetchRate.rate

const [submitRate, { error: submitError }] = useSubmitRateMutation()
const submitRateHandler: SubmitRateHandler = async (
rateID,
formInput,
setIsSubmitting,
redirect
) => {
setIsSubmitting(true)
try {
await submitRate({
variables: {
input: {
rateID: rateID,
formData: formInput,
},
},
})

navigate(RoutesRecord[redirect])
} catch (serverError) {
setIsSubmitting(false)
}
}

if (fetchLoading) {
return (
<GridContainer>
<Loading />
</GridContainer>
)
} else if (fetchError || !rate) {
//error handling for a state user that tries to access rates for a different state
if (fetchError?.graphQLErrors[0]?.extensions?.code === 'FORBIDDEN') {
return (
<ErrorForbiddenPage
errorMsg={fetchError.graphQLErrors[0].message}
/>
)
} else if (
fetchError?.graphQLErrors[0]?.extensions?.code === 'NOT_FOUND'
) {
return <Error404 />
} else {
return <GenericErrorPage />
}
}

if (rate.status !== 'UNLOCKED') {
navigate(`/rates/${id}`)
}

// An unlocked revision is defined by having unlockInfo on it, pull it out here if it exists
const unlockedInfo: UpdateInformation | undefined =
rate.revisions[0].unlockInfo || undefined

return (
<div data-testid="single-rate-edit">
<PageBannerAlerts
loggedInUser={loggedInUser}
unlockedInfo={unlockedInfo}
showPageErrorMessage={Boolean(fetchError || submitError)}
/>
<RateDetailsV2 type="SINGLE" submitRate={submitRateHandler} />
<RateDetailsV2 type="SINGLE" />
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ const SingleRateCertSchema = (_activeFeatureFlags: FeatureFlagSettings) =>
})

const RateDetailsFormSchema = (activeFeatureFlags?: FeatureFlagSettings) => {
return activeFeatureFlags?.['rate-edit-unlock']?
return activeFeatureFlags?.['rate-edit-unlock'] || activeFeatureFlags?.['link-rates'] ?
Yup.object().shape({
rates: Yup.array().of(
rateForms: Yup.array().of(
SingleRateCertSchema(activeFeatureFlags || {})
),
}):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import {
dragAndDrop,
renderWithProviders,
} from '../../../../testHelpers'
import {
fetchCurrentUserMock,
fetchRateMockSuccess,
} from '../../../../testHelpers/apolloMocks'
import { fetchCurrentUserMock } from '../../../../testHelpers/apolloMocks'
import { Route, Routes } from 'react-router-dom'
import { RoutesRecord } from '../../../../constants'
import userEvent from '@testing-library/user-event'
Expand All @@ -20,44 +17,35 @@ import {
fillOutFirstRate,
rateCertifications,
} from '../../../../testHelpers/jestRateHelpers'

describe('RateDetails', () => {
//eslint-disable-next-line
describe.skip('RateDetailsv2', () => {
describe('handles edit of a single rate', () => {
it('renders without errors', async () => {
const mockSubmit = jest.fn()
const rateID = 'test-abc-123'
renderWithProviders(
<Routes>
<Route
path={RoutesRecord.RATE_EDIT}
element={
<RateDetailsV2
type="SINGLE"
submitRate={mockSubmit}
/>
}
element={<RateDetailsV2 type="SINGLE" />}
/>
</Routes>,
{
apolloProvider: {
mocks: [
fetchCurrentUserMock({ statusCode: 200 }),
fetchRateMockSuccess({ id: rateID }),
fetchDraftRateMockSuccess({ id: rateID }),
],
},
routerProvider: {
route: `/rates/${rateID}/edit`,
},
}
)
await waitFor(() => {
expect(
screen.getByText('Rate certification type')
).toBeInTheDocument()
})
expect(
screen.getByText('Upload one rate certification document')
).toBeInTheDocument()

await screen.findByText('Rate Details')
await screen.findByText(/Rate certification/)
await screen.findByText('Upload one rate certification document')

expect(
screen.getByRole('button', { name: 'Submit' })
).not.toHaveAttribute('aria-disabled')
Expand All @@ -74,28 +62,22 @@ describe('RateDetails', () => {
<Routes>
<Route
path={RoutesRecord.RATE_EDIT}
element={
<RateDetailsV2
type="SINGLE"
submitRate={jest.fn()}
/>
}
element={<RateDetailsV2 type="SINGLE" />}
/>
</Routes>,
{
apolloProvider: {
mocks: [
fetchCurrentUserMock({ statusCode: 200 }),
fetchRateMockSuccess({ id: rateID }),
fetchDraftRateMockSuccess({ id: rateID }),
],
},
routerProvider: {
route: `/rates/${rateID}/edit`,
},
}
)

await screen.findByText('Rate certification type')
await screen.findByText('Rate certification')

const input = screen.getByLabelText(
'Upload one rate certification document'
Expand Down Expand Up @@ -124,12 +106,7 @@ describe('RateDetails', () => {
<Routes>
<Route
path={RoutesRecord.RATE_EDIT}
element={
<RateDetailsV2
type="SINGLE"
submitRate={jest.fn()}
/>
}
element={<RateDetailsV2 type="SINGLE" />}
/>
</Routes>,
{
Expand Down Expand Up @@ -170,7 +147,7 @@ describe('RateDetails', () => {
}
)

await screen.findByText('Rate certification type')
await screen.findByText('Rate certification')
const submitButton = screen.getByRole('button', {
name: 'Submit',
})
Expand Down Expand Up @@ -272,7 +249,7 @@ describe('RateDetails', () => {
},
}
)
await screen.findByText('Rate certification type')
await screen.findByText('Rate certification')
const input = screen.getByLabelText(
'Upload one rate certification document'
)
Expand Down
Loading
Loading