diff --git a/src/apps/draft-service.ts b/src/apps/draft-service.ts index f15b3460..2df99b28 100644 --- a/src/apps/draft-service.ts +++ b/src/apps/draft-service.ts @@ -5,11 +5,7 @@ import OneBlinkAppsError from './services/errors/oneBlinkAppsError' import { isOffline } from './offline-service' import { getUsername } from './services/cognito' import { getFormsKeyId, getCurrentFormsAppUser } from './auth-service' -import { - DRAFT_DATA_UNAVAILABLE_ERROR_TITLE, - getFormSubmissionDrafts, - uploadDraftData, -} from './services/api/drafts' +import { getFormSubmissionDrafts, uploadDraftData } from './services/api/drafts' import { getPendingQueueSubmissions, deletePendingQueueSubmission, @@ -138,6 +134,7 @@ async function generatePublicLocalFormSubmissionDraftsFromStorage( async function generateLocalFormSubmissionDraftsFromStorage( localDraftsStorage: LocalDraftsStorage, + abortSignal: AbortSignal | undefined, ): Promise { const pendingSubmissionsDraftIds = await getPendingSubmissionsDraftIds() const deletedDraftIds = new Set( @@ -200,38 +197,33 @@ async function generateLocalFormSubmissionDraftsFromStorage( } await broadcastUpdate() - const draftSubmission = await getDraftSubmission( - formSubmissionDraft, - ).catch((err) => { - console.warn( - `Could not fetch draft submission for draft: ${formSubmissionDraft.id}`, - err, + try { + const draftSubmission = await getDraftSubmission( + formSubmissionDraft, + abortSignal, ) - - if ( - err instanceof OneBlinkAppsError && - err.title === DRAFT_DATA_UNAVAILABLE_ERROR_TITLE - ) { + if (draftSubmission) { + localFormSubmissionDraftsMap.set(formSubmissionDraft.id, { + ...formSubmissionDraft, + draftSubmission: draftSubmission, + downloadStatus: 'SUCCESS', + }) + } else { localFormSubmissionDraftsMap.set(formSubmissionDraft.id, { ...formSubmissionDraft, downloadStatus: 'NOT_AVAILABLE', }) - return } + } catch (err) { + console.warn( + `Could not fetch draft submission for draft: ${formSubmissionDraft.id}`, + err, + ) localFormSubmissionDraftsMap.set(formSubmissionDraft.id, { ...formSubmissionDraft, downloadStatus: 'ERROR', - downloadError: err.message, - }) - - return undefined - }) - if (draftSubmission) { - localFormSubmissionDraftsMap.set(formSubmissionDraft.id, { - ...formSubmissionDraft, - draftSubmission: draftSubmission, - downloadStatus: 'SUCCESS', + downloadError: (err as Error).message, }) } @@ -438,7 +430,7 @@ async function upsertDraft({ } } - await setAndBroadcastDrafts(localDraftsStorage) + await setAndBroadcastDrafts(localDraftsStorage, abortSignal) } else { let updated = false const publicDraftsStorage = (await getPublicDraftsFromStorage()).map( @@ -521,11 +513,17 @@ async function getPublicDraftsFromStorage(): Promise { * const drafts = await draftService.getDrafts() * ``` * + * @param abortSignal - Signal to abort the requests * @returns */ -async function getDrafts(): Promise { +async function getDrafts( + abortSignal?: AbortSignal, +): Promise { const localDraftsStorage = await getLocalDraftsFromStorage() - return await generateLocalFormSubmissionDraftsFromStorage(localDraftsStorage) + return await generateLocalFormSubmissionDraftsFromStorage( + localDraftsStorage, + abortSignal, + ) } /** @@ -592,7 +590,7 @@ async function getDraftAndData( const localDraftsStorage = await getLocalDraftsFromStorage() if (formSubmissionDrafts) { localDraftsStorage.syncedFormSubmissionDrafts = formSubmissionDrafts - await setAndBroadcastDrafts(localDraftsStorage) + await setAndBroadcastDrafts(localDraftsStorage, abortSignal) } else { formSubmissionDrafts = localDraftsStorage.syncedFormSubmissionDrafts } @@ -604,7 +602,19 @@ async function getDraftAndData( return (await getLocalDraftSubmission(formSubmissionDraftId)) || undefined } - return await getDraftSubmission(formSubmissionDraft) + const s3SubmissionData = await getDraftSubmission( + formSubmissionDraft, + abortSignal, + ) + if (!s3SubmissionData) { + throw new OneBlinkAppsError( + "Data has been removed based on your administrator's draft data retention policy.", + { + title: 'Draft Data Unavailable', + }, + ) + } + return s3SubmissionData } else { if (formSubmissionDrafts) { return (await getLocalDraftSubmission(formSubmissionDraftId)) || undefined @@ -678,7 +688,7 @@ async function deleteDraft( ) } - await setAndBroadcastDrafts(localDraftsStorage) + await setAndBroadcastDrafts(localDraftsStorage, abortSignal) } else { let publicDraftsStorage = await getPublicDraftsFromStorage() const draftSubmission = publicDraftsStorage.find( @@ -723,6 +733,7 @@ async function setAndBroadcastPublicDrafts(publicDrafts: DraftSubmission[]) { async function setAndBroadcastDrafts( localDraftsStorage: LocalDraftsStorage, + abortSignal: AbortSignal | undefined, ): Promise { const username = getUsername() if (!username) { @@ -740,7 +751,10 @@ async function setAndBroadcastDrafts( console.log('Drafts have been updated', localDraftsStorage) const localFormSubmissionDrafts = - await generateLocalFormSubmissionDraftsFromStorage(localDraftsStorage) + await generateLocalFormSubmissionDraftsFromStorage( + localDraftsStorage, + abortSignal, + ) await executeDraftsListeners(localFormSubmissionDrafts) } @@ -870,7 +884,7 @@ async function syncDrafts({ } console.log('Downloading drafts in the background') - setAndBroadcastDrafts(localDraftsStorage) + setAndBroadcastDrafts(localDraftsStorage, abortSignal) .then(async () => { console.log('Finished syncing drafts.') }) diff --git a/src/apps/job-service.ts b/src/apps/job-service.ts index bab4848d..d5b1b636 100644 --- a/src/apps/job-service.ts +++ b/src/apps/job-service.ts @@ -23,8 +23,11 @@ async function removePendingSubmissions( }) } -async function tagDrafts(jobList: SubmissionTypes.FormsAppJob[]) { - return getDrafts().then((drafts) => +async function tagDrafts( + jobList: SubmissionTypes.FormsAppJob[], + abortSignal?: AbortSignal, +) { + return getDrafts(abortSignal).then((drafts) => jobList.map((job) => { const draft = drafts.find((draft) => draft.jobId === job.id) return { @@ -49,11 +52,13 @@ async function tagDrafts(jobList: SubmissionTypes.FormsAppJob[]) { * * @param formsAppId * @param jobsLabel + * @param abortSignal - Signal to abort the requests * @returns */ export async function getJobs( formsAppId: number, jobsLabel: string, + abortSignal?: AbortSignal, ): Promise { if (!isLoggedIn()) { return [] @@ -66,7 +71,7 @@ export async function getJobs( }, ) .then((data) => removePendingSubmissions(data.jobs)) - .then((jobs) => tagDrafts(jobs)) + .then((jobs) => tagDrafts(jobs, abortSignal)) .then((jobList) => _orderBy( jobList, diff --git a/src/apps/services/api/drafts.ts b/src/apps/services/api/drafts.ts index 40ca9e40..d5ff3c0f 100644 --- a/src/apps/services/api/drafts.ts +++ b/src/apps/services/api/drafts.ts @@ -12,8 +12,6 @@ import generateOneBlinkUploader from '../generateOneBlinkUploader' import { OneBlinkStorageError } from '@oneblink/storage' import generateOneBlinkDownloader from '../generateOneBlinkDownloader' -export const DRAFT_DATA_UNAVAILABLE_ERROR_TITLE = 'Draft Data Unavailable' - async function uploadDraftData( draftSubmission: DraftSubmission, onProgress?: ProgressListener, @@ -172,28 +170,52 @@ async function getFormSubmissionDrafts( } async function downloadDraftData( - formSubmissionDraftVersionId: string, + formSubmissionDraft: SubmissionTypes.FormSubmissionDraft, + latestFormSubmissionDraftVersion: SubmissionTypes.FormSubmissionDraftVersion, abortSignal?: AbortSignal, -) { +): Promise { try { console.log('Attempting to download draft form data:', { - formSubmissionDraftVersionId, + formSubmissionDraftVersionId: latestFormSubmissionDraftVersion.id, }) const oneblinkDownloader = generateOneBlinkDownloader() - const data = await oneblinkDownloader.downloadDraftSubmission({ - formSubmissionDraftVersionId, + const s3SubmissionData = await oneblinkDownloader.downloadDraftSubmission({ + formSubmissionDraftVersionId: latestFormSubmissionDraftVersion.id, abortSignal, }) - if (!data) { - throw new OneBlinkAppsError( - "Data has been removed based on your administrator's draft data retention policy.", - { - title: DRAFT_DATA_UNAVAILABLE_ERROR_TITLE, + if (!s3SubmissionData) { + return undefined + } + return { + definition: s3SubmissionData.definition, + submission: s3SubmissionData.submission, + lastElementUpdated: s3SubmissionData.lastElementUpdated, + // Drafts will always have a formsAppId, so we can safely cast to number + formsAppId: s3SubmissionData.formsAppId as number, + jobId: formSubmissionDraft.jobId, + externalId: formSubmissionDraft.externalId, + previousFormSubmissionApprovalId: + formSubmissionDraft.previousFormSubmissionApprovalId, + taskCompletion: s3SubmissionData.task && + s3SubmissionData.taskAction && { + task: s3SubmissionData.task, + taskAction: s3SubmissionData.taskAction, + taskGroup: s3SubmissionData.taskGroup, + taskGroupInstance: s3SubmissionData.taskGroupInstance, + redirectUrl: '', }, - ) + title: latestFormSubmissionDraftVersion.title, + createdAt: latestFormSubmissionDraftVersion.createdAt, + formSubmissionDraftId: formSubmissionDraft.id, + sectionState: s3SubmissionData.sectionState, + previousElapsedDurationSeconds: + s3SubmissionData.previousElapsedDurationSeconds, } - return data } catch (error) { + if (abortSignal?.aborted) { + return undefined + } + console.error( 'Error occurred while attempting to download draft data', error, diff --git a/src/apps/services/draft-data-store.ts b/src/apps/services/draft-data-store.ts index e8a92bda..1f6877e2 100644 --- a/src/apps/services/draft-data-store.ts +++ b/src/apps/services/draft-data-store.ts @@ -116,57 +116,37 @@ export function getLatestFormSubmissionDraftVersion( export async function getDraftSubmission( formSubmissionDraft: SubmissionTypes.FormSubmissionDraft, - abortSignal?: AbortSignal, + abortSignal: AbortSignal | undefined, ): Promise { const latestFormSubmissionDraftVersion = getLatestFormSubmissionDraftVersion( formSubmissionDraft.versions, ) - const draftSubmission = await getLocalDraftSubmission(formSubmissionDraft.id) + const localDraftSubmission = await getLocalDraftSubmission( + formSubmissionDraft.id, + ) // If there is local data and no server data, return local data. // Or if the latest server version of the draft is what // is currently saved locally, return local data. if ( - draftSubmission && + localDraftSubmission && (!latestFormSubmissionDraftVersion || - latestFormSubmissionDraftVersion.createdAt <= draftSubmission.createdAt) + latestFormSubmissionDraftVersion.createdAt <= + localDraftSubmission.createdAt) ) { - return draftSubmission + return localDraftSubmission } if (!latestFormSubmissionDraftVersion) { return undefined } - //drafts will always have a formsAppId - const s3SubmissionData = (await downloadDraftData( - latestFormSubmissionDraftVersion.id, + const draftSubmission = await downloadDraftData( + formSubmissionDraft, + latestFormSubmissionDraftVersion, abortSignal, - )) as Omit & { - formsAppId: number + ) + if (draftSubmission) { + await setLocalDraftSubmission(draftSubmission) } - return await setLocalDraftSubmission({ - definition: s3SubmissionData.definition, - submission: s3SubmissionData.submission, - lastElementUpdated: s3SubmissionData.lastElementUpdated, - formsAppId: s3SubmissionData.formsAppId, - jobId: formSubmissionDraft.jobId, - externalId: formSubmissionDraft.externalId, - previousFormSubmissionApprovalId: - formSubmissionDraft.previousFormSubmissionApprovalId, - taskCompletion: s3SubmissionData.task && - s3SubmissionData.taskAction && { - task: s3SubmissionData.task, - taskAction: s3SubmissionData.taskAction, - taskGroup: s3SubmissionData.taskGroup, - taskGroupInstance: s3SubmissionData.taskGroupInstance, - redirectUrl: '', - }, - title: latestFormSubmissionDraftVersion.title, - createdAt: latestFormSubmissionDraftVersion.createdAt, - formSubmissionDraftId: formSubmissionDraft.id, - sectionState: s3SubmissionData.sectionState, - previousElapsedDurationSeconds: - s3SubmissionData.previousElapsedDurationSeconds, - }) }