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

MR-3573: Download all contracts button does not generate zip file on first click #1859

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
margin-left: .5rem
}

.buttonTextWithoutIcon {
vertical-align: middle;
}

.successButton {
background: $theme-color-success;
&:hover {
Expand Down
25 changes: 18 additions & 7 deletions services/app-web/src/components/ActionButton/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState, ComponentProps } from 'react'
import React, { useState, ComponentProps, useLayoutEffect } from 'react'
import { Button as UswdsButton } from '@trussworks/react-uswds'
import classnames from 'classnames'

Expand Down Expand Up @@ -26,8 +26,10 @@ export const ActionButton = ({
animationTimeout = 750,
onClick,
...inheritedProps
}: ActionButtonProps): React.ReactElement => {
const [showLoading, setShowLoading] = useState(false)
}: ActionButtonProps): React.ReactElement | null => {
const [showLoading, setShowLoading] = useState<boolean | undefined>(
undefined
)
const isDisabled = disabled || inheritedProps['aria-disabled']
const isLinkStyled = variant === 'linkStyle'
const isOutline = variant === 'outline'
Expand All @@ -39,14 +41,17 @@ export const ActionButton = ({
'CODING ERROR: Incompatible props on ActionButton are being used. Button should not be both loading and disabled at the same time.'
)

useEffect(() => {
if (loading) {
useLayoutEffect(() => {
// If there is no animationTimeout, do not use setTimeout else you get flickering UI.
if (animationTimeout > 0) {
const timeout = setTimeout(() => {
setShowLoading(true)
setShowLoading(loading)
}, animationTimeout)
return function cleanup() {
clearTimeout(timeout)
}
} else {
setShowLoading(loading)
}
}, [loading, animationTimeout])

Expand Down Expand Up @@ -87,7 +92,13 @@ export const ActionButton = ({
className={classes}
>
{showLoading && <Spinner size="small" />}
<span className={showLoading ? styles.buttonTextWithIcon : ''}>
<span
className={
showLoading
? styles.buttonTextWithIcon
: styles.buttonTextWithoutIcon
}
>
{showLoading ? 'Loading' : children}
</span>
</UswdsButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import styles from '../Banner.module.scss'
import { Alert } from '@trussworks/react-uswds'
import { useStringConstants } from '../../../hooks/useStringConstants'
import classnames from 'classnames'

export const DocumentWarningBanner = (): React.ReactElement => {
const stringConstants = useStringConstants()
const MAIL_TO_SUPPORT = stringConstants.MAIL_TO_SUPPORT
return (
<Alert
role="alert"
type="warning"
heading={`Document download unavailable`}
headingLevel="h4"
data-testid="warning-alert"
className={classnames(styles.bannerBodyText, 'usa-alert__text')}
>
<span>
Some documents aren’t available right now. Refresh the page to
try again. If you still see this message,&nbsp;
</span>
<a
href={`mailto: ${MAIL_TO_SUPPORT}, mc-review-team@truss.works`}
className="usa-link"
target="_blank"
rel="noreferrer"
>
email the help desk.
</a>
</Alert>
)
}
1 change: 1 addition & 0 deletions services/app-web/src/components/Banner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { SubmissionUpdatedBanner } from './SubmissionUpdatedBanner/SubmissionUpd
export { GenericApiErrorBanner } from './GenericApiErrorBanner/GenericApiErrorBanner'
export { QuestionResponseSubmitBanner } from './QuestionResponseSubmitBanner'
export { UserAccountWarningBanner } from './UserAccountWarningBanner/UserAccountWarningBanner'
export { DocumentWarningBanner } from './DocumentWarningBanner/DocumentWarningBanner'
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@import '../../../styles/uswdsImports.scss';
@import '../../../styles/custom.scss';

.missingInfo {
color: $theme-color-warning-darker;
font-weight: 700;
display: flex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Icon as UswdsIcon } from '@trussworks/react-uswds'
import styles from './InlineDocumentWarning.module.scss'

interface USWDSIconProps {
focusable?: boolean
role?: string
size?: 3 | 4 | 5 | 6 | 7 | 8 | 9
className?: string
}

type IconProps = USWDSIconProps & React.JSX.IntrinsicElements['svg']

type IconType = keyof typeof UswdsIcon
export const InlineDocumentWarning = ({
message,
iconType,
}: {
message?: string
iconType?: IconType
}): React.ReactElement | null => {
const requiredFieldMissingText =
message || 'Document download is unavailable'
const type = iconType || 'Warning'
const Icon = UswdsIcon[type] as React.ComponentType<IconProps>

return (
<span className={styles.missingInfo}>
<span>
<Icon aria-label={`An ${type} icon`} size={3} />
</span>
<span>{requiredFieldMissingText}</span>
</span>
)
}
1 change: 1 addition & 0 deletions services/app-web/src/components/DocumentWarning/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { InlineDocumentWarning } from './InlineDocumentWarning/InlineDocumentWarning'
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,20 @@ describe('DownloadButton', () => {
zippedFilesURL="https://example.com"
/>
)
expect(screen.getByRole('link', {name: 'Download all documents'})).toHaveClass(
'usa-button usa-button--small'
)
expect(
screen.getByRole('button', { name: 'Download all documents' })
).toHaveClass('usa-button')
expect(screen.getByText('Download all documents')).toBeInTheDocument()
})
it('renders loading button', () => {
render(
<DownloadButton
text="Download all documents"
zippedFilesURL={undefined}
/>
)
expect(screen.getByRole('button', { name: 'Loading' })).toHaveClass(
'usa-button'
)
})
})
27 changes: 15 additions & 12 deletions services/app-web/src/components/DownloadButton/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import React from 'react'
import { Link } from '@trussworks/react-uswds'
import { ActionButton } from '../ActionButton'

type DownloadButtonProps = {
text: string
zippedFilesURL: string
zippedFilesURL: string | undefined
}

export const DownloadButton = ({
text,
zippedFilesURL,
}: DownloadButtonProps): React.ReactElement => {
const handleClick = () => {
if (zippedFilesURL) {
window.open(zippedFilesURL)
Copy link
Contributor

Choose a reason for hiding this comment

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

why are we opening the link with JS? Shouldn't it be a simple Link in the end? Id worry about screen readers here going outside the normal semantics

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh! Good call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@macrael I updated this to use the Link component again. From this blog, disabling the link might not be the way to go, so I'm rendering just a div with the loading spinner until the link is ready. Could use you're eyes on this again.

}
}

return (
<div>
<Link
className="usa-button usa-button--small"
variant="unstyled"
href={zippedFilesURL}
target="_blank"
>
{text}
</Link>
</div>
<ActionButton
onClick={handleClick}
loading={zippedFilesURL === undefined}
type="button"
children={text}
animationTimeout={0}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
mockStateSubmission,
} from '../../../testHelpers/apolloMocks'
import { UnlockedHealthPlanFormDataType } from '../../../common-code/healthPlanFormDataType'
import { testS3Client } from '../../../testHelpers/s3Helpers'

describe('ContractDetailsSummarySection', () => {
it('can render draft submission without errors (review and submit behavior)', async () => {
Expand Down Expand Up @@ -64,7 +65,7 @@ describe('ContractDetailsSummarySection', () => {
).toBeNull()
})

it('can render state submission on summary page without errors (submission summary behavior)', () => {
it('can render state submission on summary page without errors (submission summary behavior)', async () => {
renderWithProviders(
<ContractDetailsSummarySection
documentDateLookupTable={{ previousSubmissionDate: '01/01/01' }}
Expand All @@ -83,11 +84,22 @@ describe('ContractDetailsSummarySection', () => {
})
).toBeInTheDocument()
expect(screen.queryByText('Edit')).not.toBeInTheDocument()

//expects loading button on component load
expect(
screen.getByRole('link', {
name: 'Download all contract documents',
screen.getByRole('button', {
name: 'Loading',
})
).toBeInTheDocument()

// expects download all button after loading has completed
await waitFor(() => {
expect(
screen.getByRole('button', {
name: 'Download all contract documents',
})
).toBeInTheDocument()
})
})

it('can render all contract details fields', () => {
Expand Down Expand Up @@ -336,6 +348,36 @@ describe('ContractDetailsSummarySection', () => {
await screen.queryByText('1937 Benchmark Authority')
).not.toBeInTheDocument()
})
it('renders inline error when bulk URL is unavailable', async () => {
const s3Provider = {
...testS3Client(),
getBulkDlURL: async (
keys: string[],
fileName: string
): Promise<string | Error> => {
return new Error('Error: getBulkDlURL encountered an error')
},
}
renderWithProviders(
<ContractDetailsSummarySection
documentDateLookupTable={{ previousSubmissionDate: '01/01/01' }}
submission={{
...mockStateSubmission(),
status: 'SUBMITTED',
}}
submissionName="MN-PMAP-0001"
/>,
{
s3Provider,
}
)

await waitFor(() => {
expect(
screen.getByText('Contract document download is unavailable')
).toBeInTheDocument()
})
})
describe('contract provisions', () => {
it('renders provisions and MLR references for a medicaid amendment', () => {
renderWithProviders(
Expand Down
Loading
Loading