diff --git a/packages/api/src/jobs/export.ts b/packages/api/src/jobs/export.ts index 02b0fdc48..61b0feb5d 100644 --- a/packages/api/src/jobs/export.ts +++ b/packages/api/src/jobs/export.ts @@ -175,6 +175,12 @@ export const exportJob = async (jobData: ExportJobData) => { userId ) + await saveExport(userId, { + id: exportId, + state: TaskState.Running, + totalItems: itemCount, + }) + logger.info(`exporting ${itemCount} items...`, { userId, }) @@ -226,6 +232,11 @@ export const exportJob = async (jobData: ExportJobData) => { // fetch data from the database const batchSize = 20 for (cursor = 0; cursor < itemCount; cursor += batchSize) { + logger.info(`export extracting ${cursor} of ${itemCount}`, { + userId, + exportId, + }) + const items = await searchLibraryItems( { from: cursor, @@ -242,6 +253,10 @@ export const exportJob = async (jobData: ExportJobData) => { // write data to the csv file if (size > 0) { await uploadToBucket(userId, items, cursor, size, archive) + await saveExport(userId, { + id: exportId, + processedItems: cursor, + }) } else { break } @@ -264,7 +279,7 @@ export const exportJob = async (jobData: ExportJobData) => { // generate a temporary signed url for the zip file const [signedUrl] = await file.getSignedUrl({ action: 'read', - expires: Date.now() + 48 * 60 * 60 * 1000, // 48 hours + expires: Date.now() + 168 * 60 * 60 * 1000, // one week }) logger.info('signed url for export:', { @@ -275,6 +290,8 @@ export const exportJob = async (jobData: ExportJobData) => { await saveExport(userId, { id: exportId, state: TaskState.Succeeded, + signedUrl, + processedItems: itemCount, }) const job = await sendExportJobEmail(userId, 'completed', signedUrl) diff --git a/packages/web/lib/networking/useCreateExport.tsx b/packages/web/lib/networking/useCreateExport.tsx index 853cd1101..de2f5bd9b 100644 --- a/packages/web/lib/networking/useCreateExport.tsx +++ b/packages/web/lib/networking/useCreateExport.tsx @@ -1,11 +1,38 @@ +import { useQuery, useQueryClient } from '@tanstack/react-query' import { apiFetcher } from './networkHelpers' +import { TaskState } from './mutations/exportToIntegrationMutation' + +type Export = { + id: string + state: TaskState + createdAt: string + signedUrl: string +} + +type ExportsResponse = { + exports: Export[] +} export const createExport = async (): Promise => { try { const response = await apiFetcher(`/api/export/`) + console.log('RESPONSE: ', response) + if ('error' in (response as any)) { + return false + } return true } catch (error) { console.log('error scheduling export. ') return false } } + +export function useGetExports() { + return useQuery({ + queryKey: ['exports'], + queryFn: async () => { + const response = (await apiFetcher(`/api/export/list`)) as ExportsResponse + return response.exports + }, + }) +} diff --git a/packages/web/pages/settings/account.tsx b/packages/web/pages/settings/account.tsx index e49e50616..1271dd335 100644 --- a/packages/web/pages/settings/account.tsx +++ b/packages/web/pages/settings/account.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { Button } from '../../components/elements/Button' import { Box, + HStack, SpanBox, VStack, } from '../../components/elements/LayoutPrimitives' @@ -27,7 +28,13 @@ import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuer import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery' import { applyStoredTheme } from '../../lib/themeUpdater' import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' -import { createExport } from '../../lib/networking/useCreateExport' +import { + createExport, + useGetExports, +} from '../../lib/networking/useCreateExport' +import { TaskState } from '../../lib/networking/mutations/exportToIntegrationMutation' +import { timeAgo } from '../../lib/textFormatting' +import { Download, DownloadSimple } from '@phosphor-icons/react' const ACCOUNT_LIMIT = 50_000 @@ -475,6 +482,8 @@ export default function Account(): JSX.Element { } const ExportSection = (): JSX.Element => { + const { data: recentExports } = useGetExports() + console.log('recentExports: ', recentExports) const doExport = useCallback(async () => { const result = await createExport() if (result) { @@ -502,6 +511,12 @@ const ExportSection = (): JSX.Element => { you should receive an email with a link to your data within an hour. The download link will be available for 24 hours. + + If you do not receive your completed export within 24hrs please contact{' '} + + Contact us via email + + + + {recentExports && ( + + Recent exports + {recentExports.map((item) => { + return ( + + + {timeAgo(item.createdAt)} + + {item.state} + {item.signedUrl && ( + + + Download + + + )} + + ) + })} + + )} ) }