fix(exports): surface pending status for slow video exports#60546
Merged
rafaeelaudibert merged 6 commits intoJun 1, 2026
Conversation
MP4 / WEBM / GIF session-replay exports can take tens of minutes, but the Exports page and side panel previously rendered the same generic "Export not ready yet" disabled-tooltip for every format. Users could not distinguish "still rendering" from "stuck", which led to repeated clicks on the disabled Download button and bursts of refresh-driven timeouts. This change: - Centralises the disabled-reason and pending-label logic in a new `exportStatus.ts` helper, with a video-specific message for the long-running formats (MP4, WEBM, GIF). - Renders the pending label next to the Download spinner on both the Exports page and the side panel, so users see a status pill while the asset is rendering. - Backs the polling cadence off from 10s to 30s when the only pending exports are long-running formats, cutting refresh-driven timeout noise without slowing down ordinary CSV/PDF/PNG exports. Generated-By: PostHog Code Task-Id: 78e78380-c2a0-4cc8-abf8-2fae610e3570
Contributor
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
frontend/src/lib/components/ExportButton/exportStatus.test.ts:38-92
The three functions that branch on long-running formats (`getExportPendingStatus`, `getExportPendingLabel`, `getExportDisabledReason`) each test only MP4 for the long-running path, leaving WEBM and GIF uncovered. Since all three formats are added to `LONG_RUNNING_FORMATS` together, the tests for these functions should be parameterized over all three — matching the existing `isLongRunningExportFormat` `it.each` block — so a future accidental removal of WEBM or GIF from the set would be caught.
```suggestion
describe('getExportPendingStatus', () => {
it('returns null when the asset has content', () => {
expect(getExportPendingStatus(baseAsset({ has_content: true }))).toBeNull()
})
it('returns null when the asset has an exception', () => {
expect(getExportPendingStatus(baseAsset({ exception: 'boom' }))).toBeNull()
})
it.each([ExporterFormat.MP4, ExporterFormat.WEBM, ExporterFormat.GIF])(
'returns rendering_video for pending long-running format %s',
(format) => {
expect(getExportPendingStatus(baseAsset({ export_format: format }))).toBe('rendering_video')
}
)
it('returns pending for other in-progress formats', () => {
expect(getExportPendingStatus(baseAsset({ export_format: ExporterFormat.CSV }))).toBe('pending')
})
})
describe('getExportPendingLabel', () => {
it.each([ExporterFormat.MP4, ExporterFormat.WEBM, ExporterFormat.GIF])(
'returns a video-specific message for %s',
(format) => {
expect(getExportPendingLabel(baseAsset({ export_format: format }))).toBe(
'Rendering video — usually takes several minutes'
)
}
)
it('returns a generic message for non-video pending exports', () => {
expect(getExportPendingLabel(baseAsset({ export_format: ExporterFormat.CSV }))).toBe('Preparing export…')
})
it('returns null once the asset is ready', () => {
expect(getExportPendingLabel(baseAsset({ has_content: true }))).toBeNull()
})
})
describe('getExportDisabledReason', () => {
it('surfaces the exception message when present', () => {
expect(getExportDisabledReason(baseAsset({ exception: 'rasterize failed' }))).toBe('rasterize failed')
})
it('returns undefined when the asset is downloadable', () => {
expect(getExportDisabledReason(baseAsset({ has_content: true }))).toBeUndefined()
})
it.each([ExporterFormat.MP4, ExporterFormat.WEBM, ExporterFormat.GIF])(
'returns a video-specific reason for pending %s exports',
(format) => {
expect(getExportDisabledReason(baseAsset({ export_format: format }))).toBe(
'Video export is still rendering — this usually takes several minutes'
)
}
)
it('falls back to the generic reason for other formats', () => {
expect(getExportDisabledReason(baseAsset({ export_format: ExporterFormat.PDF }))).toBe(
'Export not ready yet'
)
})
})
```
### Issue 2 of 2
frontend/src/lib/components/ExportButton/exportStatus.test.ts:28
The `it.each` title `'returns %s for %s'` substitutes `format` first and `expected` second, producing e.g. `"returns mp4 for true"` — the subject and result are swapped. Swapping the template placeholders makes the name read naturally.
```suggestion
])('returns %s for format %s', (format, expected) => {
```
Reviews (1): Last reviewed commit: "fix(exports): surface pending status for..." | Re-trigger Greptile |
Contributor
|
Size Change: 0 B Total Size: 80.9 MB ℹ️ View Unchanged
|
rafaeelaudibert
approved these changes
May 28, 2026
- Reorder ExportButton imports so `exporter` precedes `exportStatus` and collapse the multi-line `<span>` in `SidePanelExports.tsx` — matches what `oxfmt --check` and the schema-regen typecheck job produced on CI.
- Parameterise the `getExportPendingStatus`, `getExportPendingLabel`, and `getExportDisabledReason` long-running-format cases over MP4 / WEBM / GIF, and swap the `isLongRunningExportFormat` tuple order so the `it.each` title reads naturally ("returns true for format video/mp4"). Both come from Greptile review feedback on the PR.
Generated-By: PostHog Code
Task-Id: 78e78380-c2a0-4cc8-abf8-2fae610e3570
oxfmt case-insensitively sorts `exportsLogic` before `exportStatus`, so the previous commit's ordering still tripped the Frontend formatting / typechecking checks. Applies the diff oxfmt produced on CI. Generated-By: PostHog Code Task-Id: 78e78380-c2a0-4cc8-abf8-2fae610e3570
rafaeelaudibert
approved these changes
May 29, 2026
Contributor
Author
|
👋 Visual changes detected for this PR. Review and approve in PostHog Visual Review If these changes are unexpected, they may be caused by a flaky test or a broken snapshot on master. Don't approve — rerun the job or wait for a fix. |
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
MP4 / WEBM / GIF session-replay exports go through
RasterizeRecordingWorkflowand can take many minutes to complete. The Exports page (ExportsScene.tsx) and the side panel (SidePanelExports.tsx) derive the Download button purely fromasset.has_content/asset.exceptionand surface the same generic "Export not ready yet" disabled tooltip for every format — so users can't tell whether a long-running render is making progress or has stalled, and the page keeps polling for the not-yet-ready asset every 10s.Closes inbox report
019e188d-563e-7baa-a4fe-0b007261681b.Changes
frontend/src/lib/components/ExportButton/exportStatus.tswith format-aware helpers (isLongRunningExportFormat,getExportPendingStatus,getExportPendingLabel,getExportDisabledReason). MP4 / WEBM / GIF are treated as long-running.ExportsScene.tsxandSidePanelExports.tsxnow use those helpers and render an inline status label next to the Download spinner while the asset is still rendering — "Rendering video — usually takes several minutes" for MP4 / WEBM / GIF, "Preparing export…" otherwise — and the disabled Download tooltip now names the format and expected wait.exportsLogic.tsexports a smallpickPollDelayMshelper that backs the polling cadence off from 10s to 30s whenever every pending export is a long-running format, without slowing down ordinary CSV/PDF/PNG exports.How did you test this code?
I'm an agent (PostHog Code). I did not run the dev server or click through the UI. I added Jest coverage for the new helpers:
frontend/src/lib/components/ExportButton/exportStatus.test.ts— coversisLongRunningExportFormat,getExportPendingStatus,getExportPendingLabel, andgetExportDisabledReasonacross video/non-video formats, error states, and ready states.frontend/src/lib/components/ExportButton/exportsLogic.test.ts— coverspickPollDelayMsfor empty / mixed / all-long-running pending sets and confirms completed-or-failed assets are ignored.I was not able to execute these tests in the cloud workspace (
node_modulesnot installed andpnpm testtriggered a separatebuild:productsstep that can't run here), so they need to be run in CI alongside the typecheck and lint jobs.Automatic notifications
Docs update
No docs change required — this is a UX clarity fix on an existing page.
🤖 Agent context
Authored by PostHog Code (Claude Opus 4.7) acting on the inbox report linked above. The report identified two consumers of the same flawed pending state (
ExportActionsinExportsScene.tsx,ExportRowinSidePanelExports.tsx) and a too-aggressive polling loop inexportsLogic.ts.Considered, decided against in this PR:
statusfield (queued / rendering / uploading / failed) fromRasterizeRecordingWorkflowthrough theExportedAssetserializer. This is what the inbox report recommends as the "right" long-term shape, but it touches the Temporal workflow, the model, the serializer, and generated frontend types — a substantially larger and riskier change. The frontend-only change in this PR already removes the worst part of the experience (generic tooltip + rage-clickable Download with no progress feedback), and the new helper module gives a clean spot to surface a real backend status field once it exists.Agent has not exercised the UI manually — reviewer should sanity-check that the new status label renders sensibly next to the spinner on both the Exports page and the side panel.