From 57f8daf55de6d61a430f3b591f33b4be34c1ae3e Mon Sep 17 00:00:00 2001 From: Prakash Date: Wed, 13 May 2026 02:52:53 +0530 Subject: [PATCH] @uppy/transloadit: add AssemblyStatus to plugin state (#6267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fixes content repo testing area bug https://github.com/transloadit/content/issues/4943 (tested locally in content repo) - Simplifies the testing area code — no need to manually construct the assembly object and listen for multiple events; just subscribe to state using `useUppyState` and that's it (tested locally in content repo) - Addresses https://transloadit.slack.com/archives/C0AFZGN6W2F/p1775580430051829?thread_ts=1775555821.837409&cid=C0AFZGN6W2F (one of our customers wanted to create a custom UI around assembly execution); this would become much easier now (need to verify) - Addresses https://github.com/transloadit/content/blob/9474e8d8e72c73d27288507c874f550c2df6cee4/_console/js/features/templates/components/TestingArea/uppy.tsx#L131-L147 - Partially addresses #6264 --- .changeset/legal-drinks-marry.md | 5 +++ packages/@uppy/transloadit/src/Assembly.ts | 7 +++++ packages/@uppy/transloadit/src/index.test.js | 19 +++++++++++ packages/@uppy/transloadit/src/index.ts | 33 ++++++++++++++++++-- 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 .changeset/legal-drinks-marry.md 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.