@uppy/transloadit: add AssemblyStatus to plugin state (#6267)

- 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
9474e8d8e7/_console/js/features/templates/components/TestingArea/uppy.tsx (L131-L147)
- Partially addresses #6264
This commit is contained in:
Prakash 2026-05-13 02:52:53 +05:30 committed by GitHub
parent 0e09bd2096
commit 57f8daf55d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 62 additions and 2 deletions

View file

@ -0,0 +1,5 @@
---
"@uppy/transloadit": minor
---
Add assemblyStatus and lastAssemblyStatus to transloadit's plugin state

View file

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

View file

@ -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()
})

View file

@ -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.