From 69d93b9fd33e7650c939de252b9436bde599d202 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Fri, 3 Jun 2022 15:49:54 +0800 Subject: [PATCH] feat: add download button to download responses --- .../common/ResponsesTabWrapper.tsx | 3 +- .../storage/StorageResponsesContext.tsx | 4 +- .../storage/StorageResponsesProvider.tsx | 26 ++++--- .../storage/StorageResponsesTab.tsx | 26 +------ .../UnlockedResponses/DownloadButton.tsx | 72 +++++++++++++++++++ .../UnlockedResponses/UnlockedResponses.tsx | 42 +++++++++++ .../storage/UnlockedResponses/index.ts | 1 + .../storage/useDecryptionWorkers.ts | 11 +-- 8 files changed, 145 insertions(+), 40 deletions(-) create mode 100644 frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/DownloadButton.tsx create mode 100644 frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/UnlockedResponses.tsx create mode 100644 frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/index.ts diff --git a/frontend/src/features/admin-form/responses/ResponsesPage/common/ResponsesTabWrapper.tsx b/frontend/src/features/admin-form/responses/ResponsesPage/common/ResponsesTabWrapper.tsx index f4d0fea5be..6a6ea25ca5 100644 --- a/frontend/src/features/admin-form/responses/ResponsesPage/common/ResponsesTabWrapper.tsx +++ b/frontend/src/features/admin-form/responses/ResponsesPage/common/ResponsesTabWrapper.tsx @@ -8,7 +8,8 @@ export const ResponsesTabWrapper = ({ return ( void - handleExportCsv: () => void + downloadParams: Omit | null responsesCount?: number formPublicKey: string | null isLoading: boolean diff --git a/frontend/src/features/admin-form/responses/ResponsesPage/storage/StorageResponsesProvider.tsx b/frontend/src/features/admin-form/responses/ResponsesPage/storage/StorageResponsesProvider.tsx index 1c9dbb0bb0..54dd9e851e 100644 --- a/frontend/src/features/admin-form/responses/ResponsesPage/storage/StorageResponsesProvider.tsx +++ b/frontend/src/features/admin-form/responses/ResponsesPage/storage/StorageResponsesProvider.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react' +import { useMemo } from 'react' import { useParams } from 'react-router-dom' import { FormResponseMode } from '~shared/types' @@ -6,9 +6,9 @@ import { FormResponseMode } from '~shared/types' import { useAdminForm } from '~features/admin-form/common/queries' import { useFormResponsesCount } from '../../queries' -import useDecryptionWorkers from '../../useDecryptionWorkers' import { StorageResponsesContext } from './StorageResponsesContext' +import { useSecretKey } from './useSecretKey' export const StorageResponsesProvider = ({ children, @@ -16,30 +16,36 @@ export const StorageResponsesProvider = ({ children: React.ReactNode }): JSX.Element => { const { formId } = useParams() - const { downloadEncryptedResponses } = useDecryptionWorkers() + if (!formId) throw new Error('No formId provided') const { data: form, isLoading: isAdminFormLoading } = useAdminForm() const { data: responsesCount, isLoading: isFormResponsesLoading } = useFormResponsesCount() - const [secretKey, setSecretKey] = useState() - - const handleExportCsv = useCallback(() => { - if (!formId || !form?.title || !secretKey) return - return downloadEncryptedResponses(formId, form.title, secretKey) - }, [downloadEncryptedResponses, formId, secretKey, form?.title]) + const [secretKey, setSecretKey] = useSecretKey(formId) const formPublicKey = useMemo(() => { if (!form || form.responseMode !== FormResponseMode.Encrypt) return null return form.publicKey }, [form]) + const downloadParams = useMemo(() => { + if (!secretKey) return null + + return { + secretKey, + // TODO: Add selector for start and end dates. + endDate: undefined, + startDate: undefined, + } + }, [secretKey]) + return ( { return ( @@ -19,9 +15,7 @@ export const StorageResponsesTab = () => { } const ProvidedStorageResponsesTab = (): JSX.Element => { - const { responsesCount, secretKey, handleExportCsv } = - useStorageResponsesContext() - const { data } = useFormResponses() + const { responsesCount, secretKey } = useStorageResponsesContext() if (responsesCount === 0) { return @@ -29,21 +23,7 @@ const ProvidedStorageResponsesTab = (): JSX.Element => { return ( - {secretKey ? ( - <> - - - {data?.metadata.map((submission: StorageModeSubmissionMetadata) => { - return ( -
- Submission Ref No: {submission.refNo} -
- ) - })} - - ) : ( - - )} + {secretKey ? : }
) } diff --git a/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/DownloadButton.tsx b/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/DownloadButton.tsx new file mode 100644 index 0000000000..e15edfdfa9 --- /dev/null +++ b/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/DownloadButton.tsx @@ -0,0 +1,72 @@ +import { useCallback } from 'react' +import { useMutation } from 'react-query' +import { Box, MenuButton } from '@chakra-ui/react' + +import { BxsChevronDown } from '~assets/icons/BxsChevronDown' +import { BxsChevronUp } from '~assets/icons/BxsChevronUp' +import Badge from '~components/Badge' +import Button from '~components/Button' +import Menu from '~components/Menu' + +import { useStorageResponsesContext } from '../StorageResponsesContext' +import useDecryptionWorkers, { + DownloadEncryptedParams, +} from '../useDecryptionWorkers' + +export const DownloadButton = (): JSX.Element => { + const { downloadEncryptedResponses } = useDecryptionWorkers() + const { downloadParams } = useStorageResponsesContext() + + const handleExportCsvMutation = useMutation( + (params: DownloadEncryptedParams) => downloadEncryptedResponses(params), + // TODO: add error and success handling + ) + + const handleExportCsvNoAttachments = useCallback(() => { + if (!downloadParams) return + return handleExportCsvMutation.mutate({ + ...downloadParams, + downloadAttachments: false, + }) + }, [downloadParams, handleExportCsvMutation]) + + const handleExportCsvWithAttachments = useCallback(() => { + if (!downloadParams) return + return handleExportCsvMutation.mutate({ + ...downloadParams, + downloadAttachments: true, + }) + }, [downloadParams, handleExportCsvMutation]) + + return ( + + + {({ isOpen }) => ( + <> + : } + > + Download + + + + CSV only + + + CSV with attachments + + beta + + + + + )} + + + ) +} diff --git a/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/UnlockedResponses.tsx b/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/UnlockedResponses.tsx new file mode 100644 index 0000000000..213a644a05 --- /dev/null +++ b/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/UnlockedResponses.tsx @@ -0,0 +1,42 @@ +import { useMemo } from 'react' +import { Box, Flex, Grid, Text } from '@chakra-ui/react' +import simplur from 'simplur' + +import { useFormResponses } from '../../../queries' + +import { DownloadButton } from './DownloadButton' + +export const UnlockedResponses = (): JSX.Element => { + const { data: { count } = {} } = useFormResponses() + + const prettifiedResponsesCount = useMemo(() => { + if (!count) return + return simplur` ${[count]}response[|s] to date` + }, [count]) + + return ( + + + + + + {count?.toLocaleString()} + + {prettifiedResponsesCount} + + + + + + ) +} diff --git a/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/index.ts b/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/index.ts new file mode 100644 index 0000000000..eac3b19c86 --- /dev/null +++ b/frontend/src/features/admin-form/responses/ResponsesPage/storage/UnlockedResponses/index.ts @@ -0,0 +1 @@ +export { UnlockedResponses } from './UnlockedResponses' diff --git a/frontend/src/features/admin-form/responses/ResponsesPage/storage/useDecryptionWorkers.ts b/frontend/src/features/admin-form/responses/ResponsesPage/storage/useDecryptionWorkers.ts index 4ee4052012..d58c9402c9 100644 --- a/frontend/src/features/admin-form/responses/ResponsesPage/storage/useDecryptionWorkers.ts +++ b/frontend/src/features/admin-form/responses/ResponsesPage/storage/useDecryptionWorkers.ts @@ -27,18 +27,21 @@ export type DownloadEncryptedParams = EncryptedResponsesStreamParams & { const useDecryptionWorkers = () => { const [workers, setWorkers] = useState([]) const abortControllerRef = useRef(new AbortController()) + const { data: adminForm } = useAdminForm() const { refetch } = useFormResponsesCount() useEffect(() => { - const abortController = abortControllerRef.current + return () => killWorkers(workers) + }, [workers]) + useEffect(() => { + const abortController = abortControllerRef.current return () => { - killWorkers(workers) abortController.abort() } - }, [workers]) + }, []) const downloadEncryptedResponses = useCallback( async ({ @@ -53,8 +56,6 @@ const useDecryptionWorkers = () => { throwOnError: true, }) if (!responsesCount) return - // Abort any existing downloads if this function is re-invoked. - abortControllerRef.current.abort() if (workers.length) killWorkers(workers) // Create a pool of decryption workers