diff --git a/.changeset/legal-drinks-marry.md b/.changeset/legal-drinks-marry.md new file mode 100644 index 000000000..e859660be --- /dev/null +++ b/.changeset/legal-drinks-marry.md @@ -0,0 +1,5 @@ +--- +"@uppy/transloadit": minor +--- + +Add assemblyStatus and lastAssemblyStatus to transloadit's plugin state diff --git a/packages/@uppy/transloadit/src/Assembly.ts b/packages/@uppy/transloadit/src/Assembly.ts index ea2f676cf..d98900ac1 100644 --- a/packages/@uppy/transloadit/src/Assembly.ts +++ b/packages/@uppy/transloadit/src/Assembly.ts @@ -140,6 +140,13 @@ class TransloaditAssembly extends Emitter { this.#sse.addEventListener('assembly_execution_progress', (e) => { const details = JSON.parse(e.data) + // setting combined execution progress of the assembly + if (typeof details.progress_combined === 'number') { + this.status = { + ...this.status, + progress_combined: details.progress_combined, + } + } this.emit('execution-progress', details) }) diff --git a/packages/@uppy/transloadit/src/index.test.js b/packages/@uppy/transloadit/src/index.test.js index 8f3cf6e7d..0bd6f1854 100644 --- a/packages/@uppy/transloadit/src/index.test.js +++ b/packages/@uppy/transloadit/src/index.test.js @@ -189,6 +189,15 @@ describe('Transloadit', () => { }, }) + // Plugin state should start empty; track every distinct `ok` that lands in + // it so we can verify the assembly lifecycle is reflected. + expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeUndefined() + const okHistory = [] + const unsubscribe = uppy.store.subscribe((_prev, next) => { + const ok = next.plugins.Transloadit.assemblyStatus?.ok + if (ok && ok !== okHistory.at(-1)) okHistory.push(ok) + }) + uppy.addFile({ source: 'test', name: 'cat.jpg', @@ -228,12 +237,22 @@ describe('Transloadit', () => { uppy.resumeAll() await uploadPromise + unsubscribe() expect(successSpy).toHaveBeenCalled() // Should be reset to true after upload completes expect(uppy.getState().allowNewUpload).toBe(true) + // The createAssembly mock returned ASSEMBLY_EXECUTING and the assembly + // setter forwarded that status into plugin state during the upload. + expect(okHistory).toContain('ASSEMBLY_EXECUTING') + // `assemblyStatus` is the live slot — it clears when `this.assembly` + // becomes undefined at the end of `#afterUpload`. `lastAssembly` is + // intentionally not populated here because no terminal event fires in + // this mocked flow (the server keeps returning ASSEMBLY_EXECUTING). + expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeUndefined() + server.close() }) diff --git a/packages/@uppy/transloadit/src/index.ts b/packages/@uppy/transloadit/src/index.ts index 60ce29fa1..5d226595f 100644 --- a/packages/@uppy/transloadit/src/index.ts +++ b/packages/@uppy/transloadit/src/index.ts @@ -26,7 +26,9 @@ import AssemblyWatcher from './AssemblyWatcher.js' import Client, { type AssemblyError } from './Client.js' import locale from './locale.js' -export type AssemblyResponse = AssemblyStatus +export type AssemblyResponse = AssemblyStatus & { + progress_combined?: number +} export type AssemblyFile = AssemblyStatusUpload export type AssemblyResult = AssemblyStatusResult & { localId: string | null } export type AssemblyParameters = AssemblyInstructionsInput @@ -79,6 +81,19 @@ type TransloaditState = { string, { assembly: string; id: string; uploadedFile: AssemblyFile } > + /** + * Live status of the currently-active assembly. Tracks every status + * transition (UPLOADING → EXECUTING → ...). Cleared automatically when + * `this.assembly = undefined` (no live assembly). + */ + assemblyStatus: AssemblyResponse | undefined + /** + * Snapshot of the most recent non-null status seen. Persists across the + * gap between uploads so the UI can keep showing "your last upload's + * result" after `assemblyStatus` clears. Overwritten by the next + * status update routed through `#handleAssemblyStatusUpdate`. + */ + lastAssemblyStatus: AssemblyResponse | undefined results: Array<{ result: AssemblyResult stepName: string @@ -499,11 +514,20 @@ export default class Transloadit< } /** - * Allows Golden Retriever plugin to serialize the Assembly status so we can restore it later + * Mirrors the live Assembly status into plugin state and lets Golden + * Retriever serialize it for restore. `assemblyStatus` is written + * unconditionally — when `this.assembly = undefined`, `assemblyResponse` + * is undefined and `assemblyStatus` clears too. `lastAssemblyStatus` + * captures the most recent non-null status so the UI can keep displaying + * the previous run's result after `assemblyStatus` clears. */ #handleAssemblyStatusUpdate = ( assemblyResponse: AssemblyResponse | undefined, ) => { + if (assemblyResponse != null) { + this.setPluginState({ lastAssemblyStatus: assemblyResponse }) + } + this.setPluginState({ assemblyStatus: assemblyResponse }) this.uppy.emit('restore:plugin-data-changed', { [this.id]: assemblyResponse ? { assemblyResponse } : undefined, }) @@ -650,6 +674,9 @@ export default class Transloadit< this.uppy.log(err) } } + // `assemblyStatus` is cleared automatically when `this.assembly = undefined` + // (via `#cancelAssembly` above, or by `#afterUpload`'s finally block). + // Reset allowNewUpload when upload is cancelled this.uppy.setState({ allowNewUpload: true }) } @@ -1006,6 +1033,8 @@ export default class Transloadit< this.uppy.on('restored', this.#onRestored) this.setPluginState({ + assemblyStatus: undefined, + lastAssemblyStatus: undefined, // Contains file data from Transloadit, indexed by their Transloadit-assigned ID. files: {}, // Contains result data from Transloadit.