[Fix][Zeta] Refresh job logs when job id changes#11389
Conversation
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for working on this. I re-reviewed the latest head f7767c105fbd from scratch.
What this PR fixes
- User pain: on the Zeta job detail page, the
JobLogpanel can miss the real job logs because the component tries to load logs before the parent page has finished populatingjob.jobId. - Fix approach: replace the one-shot
getJobLogs(props.jobId)call with awatch(() => props.jobId, ..., { immediate: true }), so the log request runs again once the prop becomes available. - One-line summary: this is a small but accurate lifecycle fix for the job log panel, and I do not see a new source-side blocker in the current head.
Full call path I checked
Route /jobs/:jobId
-> views/jobs/detail.tsx
-> getJobInfo(jobId) [detail.tsx:61-63]
-> Object.assign(job, res)
-> <JobLog jobId={job.jobId}> [detail.tsx:574]
JobLog component
-> components/job-log/index.tsx
-> old code: getJobLogs(props.jobId) once during setup
-> new code: watch(() => props.jobId, ...) with immediate=true
-> getJobLogs(`/logs/${jobId}?format=json`)
-> render NCollapse items + iframe log links
I also checked the route lifecycle: the main layout renders <router-view key={route.fullPath}> in layouts/main/index.tsx:66, so a full path change remounts the page. That means the real bug here is the async "prop becomes valid after mount" path inside the same detail page, and this patch addresses exactly that path.
Findings
I did not find a new code-side blocker in the current version.
The only follow-up I would suggest is adding a small frontend regression test for the case where JobLog mounts with an empty/placeholder jobId and then receives the real value later, so this lifecycle bug stays covered in the future. I would treat that as a non-blocking improvement rather than a merge blocker for this patch.
Review conclusion
Conclusion: source blocker cleared, waiting for CI
- Blocking items
- None from my side in the current source diff.
- Non-blocking suggestion
- Add a focused regression test for the delayed-
jobIdupdate path inJobLog.
Overall, the fix looks correct to me on the actual parent/child lifecycle path. The remaining gate right now is CI: the required Build check is still queued at https://github.com/apache/seatunnel/runs/86071083921, so I am not turning this into an approval yet. Once the required checks are green, this looks good from the source side.
|
Rechecked the current unchanged head My earlier source-side blocker on this same revision still stands from Daniel's side. Separately, this branch is also behind the latest So at this point I would treat the next steps as two separate gates:
If the updated head is still red after the sync, please paste the new failing check link and I can help narrow it down against the refreshed revision. |
SEZ9
left a comment
There was a problem hiding this comment.
Thanks for working on this. I re-reviewed the latest head f7767c105fbd from scratch.
What this PR fixes
- User pain: The job log panel fetched logs only once on component setup, so switching to a different job left stale logs from the previously selected job on screen.
- Fix approach: Replace the one-shot
getJobLogs(props.jobId)call with awatchonprops.jobId(withimmediate: trueand a falsy-guard) so logs reload both on mount and on every subsequent jobId change; also drops the now-unusedNSpaceimport. - One-line summary: The fix is correct and idiomatic Vue, but the async fetch inside the watcher has no stale-response guard, so rapid jobId switches can race and render the wrong job's logs, and the change ships without any test or completed type-check.
Runtime chain I rechecked
JobLog component setup(props) seatunnel-engine-ui/src/components/job-log/index.tsx:30
watch(() => props.jobId, handler, { immediate: true }) index.tsx:32-39
handler fires immediately on mount and again on each jobId prop change index.tsx:34
guard: if (!jobId) return index.tsx:35
getJobLogs(jobId) index.tsx:36 -> seatunnel-engine-ui/src/service/job-log/index.ts (HTTP fetch of job logs)
.then((res) => (logList.value = res)) index.tsx:36 <-- assignment is unconditional; a slow response for an old jobId can overwrite logs for the currently selected job
render: logList.value mapped into NCollapse/NCollapseItem index.tsx:41-48
Findings
Issue 1: Race condition: out-of-order responses when jobId changes quickly can display the wrong job's logs
- Location:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:36 - Why it matters: - Root cause: the watcher fires
getJobLogs(jobId)on every jobId change but never cancels or guards the previous in-flight request. If the user switches from job A to job B while A's request is still pending, and A's response arrives after B's,logList.value = resat seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:36 overwrites the correct data with stale logs. - Impact: operators on the Zeta engine UI can see logs labeled for job B that actually belong to job A — a misleading result that is worse than an empty view, especially on slow REST endpoints or large log payloads.
- Fix: capture the current jobId in the closure and compare before assigning, e.g.
getJobLogs(jobId).then((res) => { if (jobId === props.jobId) logList.value = res }), or use an incrementing request token / AbortController to invalidate superseded requests. - Evidence:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:36 getJobLogs(jobId).then((res) => (logList.value = res)) - Suggested fix: There is a stale-response race here: if
jobIdchanges while a previousgetJobLogscall is still in flight, the older response can resolve last and overwritelogListwith the wrong job's logs. Please guard the assignment, e.g.getJobLogs(jobId).then((res) => { if (jobId === props.jobId) logList.value = res }), or track a request token so only the latest request wins. - Severity: Medium
Issue 2: No test or verified type-check for the reworked watcher logic; the fixed behavior is unguarded against regression
- Location:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:32 - Why it matters: - The PR description states
vue-tsccould not be run locally, so the only changed file has not even been type-checked, let alone covered by a component test. - The exact bug being fixed (logs not refreshing on jobId change) is behavioral and easy to regress if someone later refactors the setup function back to a one-shot fetch.
- Fix: add a Vitest component test (the seatunnel-engine-ui module already uses Vue 3 tooling) that mounts the component, updates the
jobIdprop, and assertsgetJobLogsis called again with the new id and that{ immediate: true }triggers the initial load; at minimum confirmnpm run type-checkandnpm run buildpass in CI before merge. - Suggested fix: Since the PR notes
vue-tsccould not be run locally, please make surenpm run type-check/buildpass in CI, and ideally add a small Vitest test that mounts this component, changes thejobIdprop, and assertsgetJobLogsis re-invoked — that would lock in the refresh behavior this PR fixes. - Severity: Low
Review conclusion
Conclusion: can merge; the notes below are non-blocking
2. Suggested (non-blocking) follow-ups
- Issue 1: Race condition: out-of-order responses when jobId changes quickly can display the wrong job's logs
- Issue 2: No test or verified type-check for the reworked watcher logic; the fixed behavior is unguarded against regression
Nice work — thanks again for the contribution. Happy to discuss any of the above.
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks @SEZ9 for the fresh review. I agree the stale-response race in the job-log watcher is the main code-side risk to address before this can move forward; it is aligned with the concern that the UI must not render logs from an older jobId after a fast switch.
No new commit has landed after that review yet, so I am not doing another full Daniel re-review in this round. Once the branch is updated, I will recheck the latest head.
SEZ9
left a comment
There was a problem hiding this comment.
Thanks for working on this. I re-reviewed the latest head 01945d6ef326 from scratch.
What this PR fixes
- User pain: In the Zeta engine UI, the job log panel fetched logs only once at component setup, so switching to a different job kept showing the previous job's stale logs.
- Fix approach: Replace the one-shot
getJobLogs(props.jobId)call insetup()with awatchon() => props.jobId(with{ immediate: true }and a falsy-id guard) that refetches and reassignslogList, plus removal of the now-unusedNSpaceimport. - One-line summary: The fix is small, correct for the reported symptom, and keeps the initial-load behavior via
immediate: true, but the watcher has no request cancellation/staleness guard, so rapid jobId switches can race and render the wrong job's logs, and neither a component test nor a successfulvue-tscrun backs the change.
Findings
Issue 1: Out-of-order responses can display the wrong job's logs when jobId changes rapidly
- Location:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:36 - Why it matters: - Root cause: the
watchcallback fires a newgetJobLogs(jobId)request on every jobId change, but the.thenunconditionally writes intologList.value. If the user switches from job A to job B and A's (slower) response resolves after B's, the UI will show job A's logs under job B's view. - Impact: operators inspecting Zeta job logs through the engine UI can be shown log content belonging to a different job than the one selected, which is misleading during incident triage and is a mild cross-job information-exposure in shared-console scenarios.
- Fix: guard the assignment with a staleness check, e.g. capture the requested id and only assign when it still matches the current prop:
getJobLogs(jobId).then((res) => { if (jobId === props.jobId) logList.value = res }), or use an incrementing request token / AbortController to cancel superseded requests. - Suggested fix: This
.thenassigns tologList.valueunconditionally, so if the user switches jobs quickly, a slower response for the previous job can overwrite the logs of the currently selected job. Please add a staleness guard, e.g.getJobLogs(jobId).then((res) => { if (jobId === props.jobId) logList.value = res }), or track a request token/AbortController so only the latest request wins. - Severity: Medium
Issue 2: No component test for the new watch behavior; type-check was not actually run
- Location:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:32 - Why it matters: - Root cause: the PR adds reactive re-fetch logic but ships no Vitest/
@vue/test-utilscoverage, and the description statesvue-tsccould not be executed locally becauseseatunnel-engine-ui/node_moduleswas not installed, so even static type verification of the newwatchsignature is unconfirmed. - Impact: regressions in this component (e.g. the race and stale-state issues flagged above, or a future refactor of
getJobLogs) would go undetected; the engine-ui module already has a test setup that this change bypasses. - Fix: add a component test that mocks
getJobLogs, mounts the component, updates thejobIdprop, and asserts a second fetch occurs andlogListis replaced; also runnpm run type-checkin CI-equivalent conditions before merge. - Evidence:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:33 () => props.jobId, - Suggested fix: Please add a small component test (mock
getJobLogs, mount, change thejobIdprop, assert a second fetch and list refresh) — this newwatchis exactly the kind of reactive logic that regresses silently. Also please runnpm run type-checkwith dependencies installed before merge, since it was skipped per the PR description. - Severity: Low
Issue 3: Consider watchEffect or explicit oldValue check to keep the watcher minimal
- Location:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:32 watch( - Why it matters: - Root cause: the watcher only depends on
props.jobIdand immediately uses it, which is exactly the patternwatchEffectcovers with less boilerplate; alternatively the currentwatchis fine but should stay a single-source watcher. - Impact: purely stylistic — no functional difference — but consolidating on one idiom keeps the seatunnel-engine-ui components consistent.
- Fix: either keep the current
watch(() => props.jobId, ..., { immediate: true })(acceptable) or switch towatchEffect(() => { const id = props.jobId; if (!id) return; ... }); whichever is chosen, pair it with the staleness guard from the other comment. - Evidence:
seatunnel-engine/seatunnel-engine-ui/src/components/job-log/index.tsx:38 { immediate: true } - Severity: Low
Review conclusion
Conclusion: can merge; the notes below are non-blocking
2. Suggested (non-blocking) follow-ups
- Issue 1: Out-of-order responses can display the wrong job's logs when jobId changes rapidly
- Issue 2: No component test for the new watch behavior; type-check was not actually run
- Issue 3: Consider watchEffect or explicit oldValue check to keep the watcher minimal
Nice work — thanks again for the contribution. Happy to discuss any of the above.
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for the update. I re-reviewed the latest head 01945d6ef326 against the actual Zeta UI job-detail lifecycle.
What this PR fixes
- User pain: the JobLog component previously loaded logs only once in
setup(), so when the parent detail page populated or changedjob.jobIdafter mount, the log panel could keep the old or empty result. - Fix approach: replace the one-shot
getJobLogs(props.jobId)call with awatch(() => props.jobId, ..., { immediate: true }), so initial load and laterjobIdupdates both trigger a log fetch. - One-line summary: this is the correct Vue lifecycle fix for the delayed-
jobIdpath.
Runtime chain I rechecked
Job detail route
-> layouts/main/index.tsx renders router-view with key={route.fullPath}
-> navigating to another job detail remounts the detail page
-> views/jobs/detail.tsx
-> getJobInfo(route job id)
-> Object.assign(job, res)
-> <JobLog jobId={job.jobId}>
JobLog component
-> setup(props)
-> watch(() => props.jobId, handler, { immediate: true })
-> handler ignores empty jobId
-> getJobLogs(jobId)
-> logList.value = response
-> render NCollapseItem / iframe log links
Findings
I do not see a remaining source-level blocker on the latest head.
+1 to @SEZ9's point that guarding stale async responses would make this component more robust. I rechecked the current parent/route lifecycle, though, and I would not keep that as a merge blocker for this PR: the normal cross-job navigation path remounts the page because router-view is keyed by route.fullPath, while this PR's real bug is the same component receiving the real jobId after initial mount. The new watcher handles that path, and the falsy guard avoids the initial empty request.
I would still keep the stale-response guard as a nice follow-up, for example:
getJobLogs(jobId).then((res) => {
if (jobId === props.jobId) logList.value = res
})I did not run local frontend tests in this round. This review is based on:
git diff --stat upstream/dev...01945d6ef326: 1 file changed, 10 insertions and 3 deletions.git diff upstream/dev...01945d6ef326forcomponents/job-log/index.tsx.- Parent lifecycle lookup in
layouts/main/index.tsxandviews/jobs/detail.tsx. - GitHub check metadata for the current Build run.
Current CI / merge gate
The aggregate Build check is red: https://github.com/apache/seatunnel/runs/86718101675
The linked fork workflow run is accessible and does not show a source failure from this UI diff: it has 10 successful jobs, 19 cancelled jobs, and 1 skipped job. I do not see a failing job that points back to components/job-log/index.tsx.
So from Daniel's side, the source blocker is cleared. The remaining gate is to rerun/retrigger the Build workflow so the cancelled aggregate result can be replaced with a completed green run.
Merge conclusion
Conclusion: source blocker cleared, waiting for CI
Blocking items:
- None from Daniel's source review on the latest head.
- Remaining gate: rerun Build; the current aggregate result is red because jobs were cancelled/skipped.
Suggested follow-ups, non-blocking:
- Add the stale-response guard above for extra robustness.
- Add a small component test for the delayed-
jobIdupdate path when the UI test setup is available.
Overall, this is a small and correct Zeta UI lifecycle fix. I am not approving yet only because the required Build gate is not green.
Purpose
Refresh job log data when the selected job changes.
Changes
jobIdand reload logs immediately and on later prop changes.NSpaceimport.Tests
git diff --checknpm.cmd run type-checkwas attempted locally, butvue-tscis unavailable because frontend dependencies are not installed inseatunnel-engine-ui/node_modules.