Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 49 additions & 35 deletions src/apps/draft-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -138,6 +134,7 @@ async function generatePublicLocalFormSubmissionDraftsFromStorage(

async function generateLocalFormSubmissionDraftsFromStorage(
localDraftsStorage: LocalDraftsStorage,
abortSignal: AbortSignal | undefined,
): Promise<LocalFormSubmissionDraft[]> {
const pendingSubmissionsDraftIds = await getPendingSubmissionsDraftIds()
const deletedDraftIds = new Set(
Expand Down Expand Up @@ -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,
})
}

Expand Down Expand Up @@ -438,7 +430,7 @@ async function upsertDraft({
}
}

await setAndBroadcastDrafts(localDraftsStorage)
await setAndBroadcastDrafts(localDraftsStorage, abortSignal)
} else {
let updated = false
const publicDraftsStorage = (await getPublicDraftsFromStorage()).map(
Expand Down Expand Up @@ -521,11 +513,17 @@ async function getPublicDraftsFromStorage(): Promise<DraftSubmission[]> {
* const drafts = await draftService.getDrafts()
* ```
*
* @param abortSignal - Signal to abort the requests
* @returns
*/
async function getDrafts(): Promise<LocalFormSubmissionDraft[]> {
async function getDrafts(
abortSignal?: AbortSignal,
): Promise<LocalFormSubmissionDraft[]> {
const localDraftsStorage = await getLocalDraftsFromStorage()
return await generateLocalFormSubmissionDraftsFromStorage(localDraftsStorage)
return await generateLocalFormSubmissionDraftsFromStorage(
localDraftsStorage,
abortSignal,
)
}

/**
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -678,7 +688,7 @@ async function deleteDraft(
)
}

await setAndBroadcastDrafts(localDraftsStorage)
await setAndBroadcastDrafts(localDraftsStorage, abortSignal)
} else {
let publicDraftsStorage = await getPublicDraftsFromStorage()
const draftSubmission = publicDraftsStorage.find(
Expand Down Expand Up @@ -723,6 +733,7 @@ async function setAndBroadcastPublicDrafts(publicDrafts: DraftSubmission[]) {

async function setAndBroadcastDrafts(
localDraftsStorage: LocalDraftsStorage,
abortSignal: AbortSignal | undefined,
): Promise<void> {
const username = getUsername()
if (!username) {
Expand All @@ -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)
}

Expand Down Expand Up @@ -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.')
})
Expand Down
11 changes: 8 additions & 3 deletions src/apps/job-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<SubmissionTypes.FormsAppJob[]> {
if (!isLoggedIn()) {
return []
Expand All @@ -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,
Expand Down
50 changes: 36 additions & 14 deletions src/apps/services/api/drafts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -172,28 +170,52 @@ async function getFormSubmissionDrafts(
}

async function downloadDraftData(
formSubmissionDraftVersionId: string,
formSubmissionDraft: SubmissionTypes.FormSubmissionDraft,
latestFormSubmissionDraftVersion: SubmissionTypes.FormSubmissionDraftVersion,
abortSignal?: AbortSignal,
) {
): Promise<DraftSubmission | undefined> {
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,
Expand Down
48 changes: 14 additions & 34 deletions src/apps/services/draft-data-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,57 +116,37 @@ export function getLatestFormSubmissionDraftVersion(

export async function getDraftSubmission(
formSubmissionDraft: SubmissionTypes.FormSubmissionDraft,
abortSignal?: AbortSignal,
abortSignal: AbortSignal | undefined,
): Promise<DraftSubmission | undefined> {
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<SubmissionTypes.S3SubmissionData, 'formsAppId'> & {
formsAppId: number
)
if (draftSubmission) {
await setLocalDraftSubmission(draftSubmission)
}
return await setLocalDraftSubmission({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

downloadDraftData is no longer setting in LocalForage, I think we should keep that functionality

Copy link
Copy Markdown
Contributor

@divporter divporter Feb 25, 2026

Choose a reason for hiding this comment

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

Well to be precise, the change to this function means the downloaded draft data is not being set in localForage

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, oversite.

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,
})
}