Skip to content

Commit

Permalink
admin/frontend: Show warning when PDF is too large and disable export
Browse files Browse the repository at this point in the history
  • Loading branch information
jonahkagan committed Aug 29, 2024
1 parent 0e040b1 commit 56f307a
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,12 @@ export function BallotCountReportViewer({
{ enabled: !disabled && autoGenerateReport }
);

/**
* No fetch has been attempted yet. The viewer must not be in autogenerate
* mode, and the user hasn't pressed "Generate Report" yet.
*/
const previewQueryNotAttempted =
!previewQuery.isFetching && !previewQuery.isSuccess;

const reportIsEmpty =
previewQuery.isSuccess &&
previewQuery.data.warning.type === 'no-reports-match-filter';

const disableActionButtons =
disabled || !previewQuery.isSuccess || reportIsEmpty;
disabled ||
!previewQuery.isSuccess ||
previewQuery.data.warning?.type === 'no-reports-match-filter';
const disablePdfExport =
previewQuery.data?.warning?.type === 'content-too-large';

return (
<React.Fragment>
Expand All @@ -74,7 +67,7 @@ export function BallotCountReportViewer({
)}
<ExportActions>
<PrintButton
disabled={disableActionButtons}
disabled={disableActionButtons || disablePdfExport}
print={() =>
printReportMutation.mutateAsync({
filter,
Expand Down Expand Up @@ -103,7 +96,7 @@ export function BallotCountReportViewer({
}
fileType="ballot count report"
fileTypeTitle="Ballot Count Report"
disabled={disableActionButtons}
disabled={disableActionButtons || disablePdfExport}
/>
<ExportFileButton
buttonText="Export Report CSV"
Expand All @@ -125,17 +118,17 @@ export function BallotCountReportViewer({
disabled={disableActionButtons}
/>
</ExportActions>
{previewQuery.isSuccess && (
<ReportWarning
text={getBallotCountReportWarningText({
{previewQuery.data?.warning && (
<ReportWarning>
{getBallotCountReportWarningText({
ballotCountReportWarning: previewQuery.data.warning,
})}
/>
</ReportWarning>
)}
</div>
<PdfViewer
pdfData={previewQuery.isSuccess ? previewQuery.data.pdf : undefined}
disabled={disabled || previewQueryNotAttempted}
pdfData={previewQuery.data?.pdf}
loading={previewQuery.isFetching}
/>
</React.Fragment>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BallotCountReportWarning } from '@votingworks/admin-backend';
import { throwIllegalValue } from '@votingworks/basics';

interface BallotCountReportWarningProps {
ballotCountReportWarning: BallotCountReportWarning;
Expand All @@ -7,9 +8,12 @@ interface BallotCountReportWarningProps {
export function getBallotCountReportWarningText({
ballotCountReportWarning,
}: BallotCountReportWarningProps): string {
if (ballotCountReportWarning.type === 'no-reports-match-filter') {
return `The current report parameters do not match any ballots.`;
switch (ballotCountReportWarning.type) {
case 'no-reports-match-filter':
return `The current report parameters do not match any ballots.`;
case 'content-too-large':
return `This report is too large to be exported as a PDF. You may export the report as a CSV instead.`;
default:
throwIllegalValue(ballotCountReportWarning);
}

return '';
}
12 changes: 6 additions & 6 deletions apps/admin/frontend/src/components/reporting/pdf_viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ const DEFAULT_ZOOM = 1.8;

export function PdfViewer({
pdfData,
disabled,
loading,
}: {
pdfData?: Buffer;
disabled?: boolean;
loading?: boolean;
}): JSX.Element {
const [numPages, setNumPages] = useState<number>();
const [currentPage, setCurrentPage] = useState(1);
Expand All @@ -82,7 +82,7 @@ export function PdfViewer({
const scrollProgress = (scrollTop + pageHeight / 6) / scrollHeight;
setCurrentPage(Math.floor(scrollProgress * numPages) + 1);
}
const loading = (
const loadingSpinner = (
<Row
style={{
justifyContent: 'center',
Expand All @@ -104,7 +104,7 @@ export function PdfViewer({
</PdfControls>
{file ? (
<PdfDocumentScroller onScroll={onScroll} data-testid="pdf-scroller">
{!numPages && loading}
{!numPages && loadingSpinner}
<Document
file={file}
onSourceSuccess={() => setNumPages(undefined)}
Expand Down Expand Up @@ -135,8 +135,8 @@ export function PdfViewer({
))}
</Document>
</PdfDocumentScroller>
) : !disabled ? (
loading
) : loading ? (
loadingSpinner
) : null}
</PdfContainer>
);
Expand Down
11 changes: 6 additions & 5 deletions apps/admin/frontend/src/components/reporting/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ export const WarningContainer = styled.div`
margin-top: 1rem;
`;

export function ReportWarning({ text }: { text: string }): JSX.Element | null {
if (!text) {
return null;
}
export function ReportWarning({
children,
}: {
children: React.ReactNode;
}): JSX.Element | null {
return (
<WarningContainer>
<Icons.Warning color="warning" /> {text}
<Icons.Warning color="warning" /> {children}
</WarningContainer>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,12 @@ export function TallyReportViewer({
{ enabled: !disabled && autoGenerateReport }
);

/**
* No fetch has been attempted yet. The viewer must not be in autogenerate
* mode, and the user hasn't pressed "Generate Report" yet.
*/
const previewQueryNotAttempted =
!previewQuery.isFetching && !previewQuery.isSuccess;

const reportIsEmpty =
previewQuery.isSuccess &&
previewQuery.data.warning.type === 'no-reports-match-filter';

const disableActionButtons =
disabled || !previewQuery.isSuccess || reportIsEmpty;
disabled ||
!previewQuery.isSuccess ||
previewQuery.data.warning?.type === 'no-reports-match-filter';
const disablePdfExport =
previewQuery.data?.warning?.type === 'content-too-large';

return (
<React.Fragment>
Expand All @@ -93,7 +86,7 @@ export function TallyReportViewer({
)}
<ExportActions>
<PrintButton
disabled={disableActionButtons}
disabled={disableActionButtons || disablePdfExport}
print={() =>
printReportMutation.mutateAsync({
filter,
Expand Down Expand Up @@ -122,7 +115,7 @@ export function TallyReportViewer({
}
fileType="tally report"
fileTypeTitle="Tally Report"
disabled={disableActionButtons}
disabled={disableActionButtons || disablePdfExport}
/>
<ExportFileButton
buttonText="Export Report CSV"
Expand Down Expand Up @@ -158,18 +151,18 @@ export function TallyReportViewer({
/>
)}
</ExportActions>
{previewQuery.isSuccess && (
<ReportWarning
text={getTallyReportWarningText({
{previewQuery.data?.warning && (
<ReportWarning>
{getTallyReportWarningText({
tallyReportWarning: previewQuery.data.warning,
election,
})}
/>
</ReportWarning>
)}
</div>
<PdfViewer
pdfData={previewQuery.isSuccess ? previewQuery.data.pdf : undefined}
disabled={disabled || previewQueryNotAttempted}
pdfData={previewQuery.data?.pdf}
loading={previewQuery.isFetching}
/>
</React.Fragment>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,44 @@ export function getTallyReportWarningText({
tallyReportWarning,
election,
}: TallyReportWarningProps): string {
if (tallyReportWarning.type === 'none') {
return '';
}
switch (tallyReportWarning.type) {
case 'no-reports-match-filter':
return `The current report parameters do not match any ballots.`;

if (tallyReportWarning.type === 'no-reports-match-filter') {
return `The current report parameters do not match any ballots.`;
}
case 'content-too-large':
return `This report is too large to be exported as a PDF. You may export the report as a CSV instead.`;

const targetReportLabel = tallyReportWarning.isOnlyOneReport
? 'This tally report'
: `A section of this tally report`;

switch (tallyReportWarning.subType) {
case 'low-ballot-count':
return `${targetReportLabel} contains fewer than ${Tabulation.TALLY_REPORT_PRIVACY_THRESHOLD} ballots, which may pose a voter privacy risk.`;
case 'contest-same-vote': {
const contests = getContestsFromIds(
election,
tallyReportWarning.contestIds
);
const baseWarningText = `${targetReportLabel} has ${
contests.length > 1 ? 'contests' : 'a contest'
} where all votes are for the same option, which may pose a voter privacy risk.`;
if (contests.length <= 3) {
return `${baseWarningText} Check ${oxfordCommaJoin(
contests.map((c) => c.title)
)}.`;
}
case 'privacy': {
const targetReportLabel = tallyReportWarning.isOnlyOneReport
? 'This tally report'
: `A section of this tally report`;

return baseWarningText;
switch (tallyReportWarning.subType) {
case 'low-ballot-count':
return `${targetReportLabel} contains fewer than ${Tabulation.TALLY_REPORT_PRIVACY_THRESHOLD} ballots, which may pose a voter privacy risk.`;
case 'contest-same-vote': {
const contests = getContestsFromIds(
election,
tallyReportWarning.contestIds
);
const baseWarningText = `${targetReportLabel} has ${
contests.length > 1 ? 'contests' : 'a contest'
} where all votes are for the same option, which may pose a voter privacy risk.`;
if (contests.length <= 3) {
return `${baseWarningText} Check ${oxfordCommaJoin(
contests.map((c) => c.title)
)}.`;
}

return baseWarningText;
}
// istanbul ignore next
default:
return throwIllegalValue(tallyReportWarning, 'subType');
}
}
// istanbul ignore next
default:
throwIllegalValue(tallyReportWarning, 'subType');
throwIllegalValue(tallyReportWarning, 'type');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ExportActions,
reportParentRoutes,
ReportScreenContainer,
ReportWarning,
} from '../../components/reporting/shared';
import { PrintButton } from '../../components/print_button';
import { PdfViewer } from '../../components/reporting/pdf_viewer';
Expand All @@ -22,14 +23,16 @@ export function TallyWriteInReportScreen(): JSX.Element {
const pdfExportMutation = exportWriteInAdjudicationReportPdf.useMutation();

const isPreviewLoading = !previewQuery.isSuccess;
const disablePdfExport =
previewQuery.data?.warning?.type === 'content-too-large';

return (
<NavigationScreen title={TITLE} parentRoutes={reportParentRoutes} noPadding>
<ReportScreenContainer>
<div style={{ padding: '1rem' }}>
<ExportActions>
<PrintButton
disabled={isPreviewLoading}
disabled={isPreviewLoading || disablePdfExport}
print={() => printMutation.mutateAsync()}
variant="primary"
>
Expand All @@ -50,11 +53,17 @@ export function TallyWriteInReportScreen(): JSX.Element {
}
fileType="write-in adjudication report"
fileTypeTitle="Write-In Adjudication Report"
disabled={isPreviewLoading}
disabled={isPreviewLoading || disablePdfExport}
/>
</ExportActions>
{previewQuery.data?.warning && (
<ReportWarning>This report is too large to export.</ReportWarning>
)}
</div>
<PdfViewer pdfData={previewQuery.data} />
<PdfViewer
loading={previewQuery.isFetching}
pdfData={previewQuery.data?.pdf}
/>
</ReportScreenContainer>
</NavigationScreen>
);
Expand Down

0 comments on commit 56f307a

Please sign in to comment.