Skip to content

feat(frontend): detect new deployment and prompt to reload#5849

Open
Ma77Ball wants to merge 17 commits into
apache:mainfrom
Ma77Ball:feat/deployment-version-check
Open

feat(frontend): detect new deployment and prompt to reload#5849
Ma77Ball wants to merge 17 commits into
apache:mainfrom
Ma77Ball:feat/deployment-version-check

Conversation

@Ma77Ball

@Ma77Ball Ma77Ball commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

  • Add DeploymentVersionService (provided in root): checkForUpdate() fetches a cache-busted /assets/version.json and reports true only when its buildNumber is a non-empty string that differs from the compiled Version.buildNumber; start() polls every 5 minutes and, on the first detected change, shows one sticky dismissible notification (NotificationService.blank with nzDuration 0) asking the user to refresh.
  • Extend build-version.js to also emit src/assets/version.json ({buildNumber, version}) alongside version.prod.ts (gitignored, regenerated per build).
  • Wire the poll in AppComponent, skipped when Version.buildNumber is the "dev" placeholder so dev/test builds do not poll.
  • Malformed manifests (missing, empty, non-string buildNumber, null body) and transport failures (network error, 404, 500) are treated as "no update" so a bad manifest never triggers a false prompt.
image

Any related issues, documentation, discussions?

Closes: #5836

How was this PR tested?

  • Run yarn test --include='**/deployment-version.service.spec.ts' from frontend/, expect 15 passing cases: update detected (differing build), no update (same build), malformed manifests (missing/empty/non-string buildNumber, null body), transport failures (network error, 404, 500), the request carries a cache-busting query param, promptReload shows exactly one sticky notification, and the polling path prompts once on update, never prompts while unchanged, and stops after the first update.
  • Manual: build the frontend (yarn build) and confirm dist/assets/version.json exists with the build number; serve it, then replace version.json with a different buildNumber and within the poll interval expect a single "New version available" notification; reload clears it.

Was this PR authored or co-authored using generative AI tooling?

Co-authored with Claude Opus 4.8 in compliance with ASF

@github-actions github-actions Bot added feature frontend Changes related to the frontend GUI labels Jun 21, 2026
@aglinxinyuan

Copy link
Copy Markdown
Contributor

I feel this PR may be over-engineering a relatively small and infrequent issue.

@codecov-commenter

codecov-commenter commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.17073% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.92%. Comparing base (7a38b6c) to head (d94ac5a).

Files with missing lines Patch % Lines
frontend/build-version.js 45.45% 6 Missing ⚠️
...la/org/apache/texera/common/config/GuiConfig.scala 0.00% 2 Missing ⚠️
...ommon/service/notification/notification.service.ts 0.00% 2 Missing ⚠️
...e/deployment-version/deployment-version.service.ts 94.73% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5849      +/-   ##
============================================
+ Coverage     54.89%   54.92%   +0.02%     
+ Complexity     2962     2957       -5     
============================================
  Files          1117     1120       +3     
  Lines         43133    43190      +57     
  Branches       4648     4651       +3     
============================================
+ Hits          23680    23724      +44     
- Misses        18064    18069       +5     
- Partials       1389     1397       +8     
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø)
agent-service 34.36% <ø> (ø) Carriedforward from 3fba750
amber 57.01% <0.00%> (-0.05%) ⬇️
computing-unit-managing-service 0.00% <ø> (ø)
config-service 52.30% <100.00%> (+0.74%) ⬆️
file-service 59.02% <ø> (ø)
frontend 48.75% <76.31%> (+0.14%) ⬆️
notebook-migration-service 78.57% <ø> (ø)
pyamber 90.20% <ø> (ø) Carriedforward from 3fba750
python 90.76% <ø> (ø) Carriedforward from 3fba750
workflow-compiling-service 55.14% <ø> (ø)

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Ma77Ball

Ma77Ball commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

@aglinxinyuan Fair point, but the issue isn't cosmetic:

  • We ship JS bundles with hashed filenames, so after a deploy, open tabs reference old files that no longer exist.
  • Navigating then 404s and breaks the page (usually a blank screen). Since people keep tabs open for hours, this hits after most releases.
  • This PR just shows a one-click "refresh to update" message instead.

It's also lightweight: one small service that polls a static version.json every 5 min, does nothing on dev builds, and prompts at most once, with no backend or new infra.

Happy to scale it back if you'd prefer (e.g. check only on navigation, or behind a config flag).

@Ma77Ball Ma77Ball force-pushed the feat/deployment-version-check branch from 60be3aa to 0317dce Compare June 21, 2026 08:59
@Ma77Ball Ma77Ball marked this pull request as ready for review June 21, 2026 09:06
@Ma77Ball

Copy link
Copy Markdown
Contributor Author

/request-review @aglinxinyuan

@github-actions github-actions Bot requested a review from aglinxinyuan June 21, 2026 09:32
@Yicong-Huang

Copy link
Copy Markdown
Contributor

@Ma77Ball please include a UI screenshot/gif for this feature. Thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a frontend mechanism to detect when a newer deployment is available (via a generated assets/version.json) and prompts the user to reload, reducing the chance of running an outdated bundle after a deploy.

Changes:

  • Generate a runtime-readable deployment manifest (src/assets/version.json) alongside version.prod.ts during yarn build via build-version.js.
  • Add DeploymentVersionService to poll assets/version.json and show a sticky notification once a new build is detected.
  • Wire polling startup in AppComponent (guarded to skip "dev" builds) and add unit tests for the build artifact generator, the service, and the AppComponent wiring.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
frontend/src/build-version.spec.ts Adds tests for renderVersionArtifacts (TS module + manifest JSON generation and escaping).
frontend/src/app/common/service/deployment-version/deployment-version.service.ts New service that polls assets/version.json and prompts once on mismatch with compiled build number.
frontend/src/app/common/service/deployment-version/deployment-version.service.spec.ts Unit tests covering update detection, malformed/failed fetches, cache-busting, and polling behavior.
frontend/src/app/app.component.ts Starts deployment polling on app bootstrap when not in "dev" build.
frontend/src/app/app.component.spec.ts Adds tests for config-loaded detection, polling guard, and retry reload behavior.
frontend/build-version.js Extends build step to emit src/assets/version.json and exports a testable artifact renderer.
frontend/build-version.d.ts Provides TypeScript typings for build-version.js exports used in tests.
frontend/.gitignore Ignores generated src/assets/version.json.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +122 to +137
it("reloads the page", () => {
const reload = vi.fn();
// location.reload itself is non-configurable, so swap the whole
// location object for the duration of the test.
const original = window.location;
Object.defineProperty(window, "location", {
configurable: true,
value: { ...original, reload },
});
try {
create().componentInstance.retry();
expect(reload).toHaveBeenCalledTimes(1);
} finally {
Object.defineProperty(window, "location", { configurable: true, value: original });
}
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this jsdom build location.reload is non-writable/non-configurable and not on the prototype, so spying throws. window.location is configurable, so swapping the whole object is the only thing that works. Kept it with a comment.

@Yicong-Huang

Copy link
Copy Markdown
Contributor

To reply @aglinxinyuan, I somewhat think this might be a good feature to include, but would love to see the frontend experience to decide.

@Ma77Ball it may be a good idea to have a config to turn off by default. We could try it with one specific deployment later.

@Ma77Ball

Copy link
Copy Markdown
Contributor Author

@Yicong-Huang Added deploymentVersionCheckEnabled, off by default. Polling only runs when a deployment opts in. Screenshot/gif coming shortly.

@Ma77Ball

Copy link
Copy Markdown
Contributor Author

@aglinxinyuan It's now behind a flag that's off by default, so no change unless a deployment opts in.

@github-actions github-actions Bot added common platform Non-amber Scala service paths labels Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @zyratlo, @aglinxinyuan
    You can notify them by mentioning @zyratlo, @aglinxinyuan in a comment.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 2 better · 🔴 4 worse · ⚪ 9 noise (<±5%) · 0 without baseline

Compared against main 7a38b6c benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🔴 bs=10 sw=10 sl=64 401 0.245 24,309/34,490/34,490 us 🔴 +13.6% / 🔴 +121.4%
🔴 bs=100 sw=10 sl=64 795 0.485 128,091/147,297/147,297 us 🟢 -22.4% / 🔴 +34.4%
bs=1000 sw=10 sl=64 905 0.552 1,116,759/1,144,436/1,144,436 us ⚪ within ±5% / 🔴 +10.6%
Baseline details

Latest main 7a38b6c from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 401 tuples/sec 429 tuples/sec 758.88 tuples/sec -6.5% -47.2%
bs=10 sw=10 sl=64 MB/s 0.245 MB/s 0.262 MB/s 0.463 MB/s -6.5% -47.1%
bs=10 sw=10 sl=64 p50 24,309 us 21,391 us 12,965 us +13.6% +87.5%
bs=10 sw=10 sl=64 p95 34,490 us 34,271 us 15,578 us +0.6% +121.4%
bs=10 sw=10 sl=64 p99 34,490 us 34,271 us 18,378 us +0.6% +87.7%
bs=100 sw=10 sl=64 throughput 795 tuples/sec 801 tuples/sec 968.9 tuples/sec -0.7% -17.9%
bs=100 sw=10 sl=64 MB/s 0.485 MB/s 0.489 MB/s 0.591 MB/s -0.8% -18.0%
bs=100 sw=10 sl=64 p50 128,091 us 118,461 us 102,767 us +8.1% +24.6%
bs=100 sw=10 sl=64 p95 147,297 us 189,912 us 109,629 us -22.4% +34.4%
bs=100 sw=10 sl=64 p99 147,297 us 189,912 us 118,129 us -22.4% +24.7%
bs=1000 sw=10 sl=64 throughput 905 tuples/sec 927 tuples/sec 997.01 tuples/sec -2.4% -9.2%
bs=1000 sw=10 sl=64 MB/s 0.552 MB/s 0.566 MB/s 0.609 MB/s -2.5% -9.3%
bs=1000 sw=10 sl=64 p50 1,116,759 us 1,079,573 us 1,009,306 us +3.4% +10.6%
bs=1000 sw=10 sl=64 p95 1,144,436 us 1,115,875 us 1,051,088 us +2.6% +8.9%
bs=1000 sw=10 sl=64 p99 1,144,436 us 1,115,875 us 1,082,535 us +2.6% +5.7%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,498.44,200,128000,401,0.245,24308.58,34489.80,34489.80
1,100,10,64,20,2516.37,2000,1280000,795,0.485,128090.65,147296.71,147296.71
2,1000,10,64,20,22094.49,20000,12800000,905,0.552,1116759.42,1144436.14,1144436.14

@aglinxinyuan aglinxinyuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@Yicong-Huang Yicong-Huang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, left comments inline

The idea is not bad, many services (e.g., google doc) has this force refresh alert.

The only big downside is what if a user is in the middle of something where he/she can lose unsaved progress. For workflow edits/execution etc probably be fine, because we do fine grained millisecond level auto save. But some cases where we have not taken care of, could have issues. For instance, when I am uploading very large files, can this interrupt?

Or do we have the assumption that frontend deployment happens only with backend service restart? then all operations are disconnected, anyway.

Comment on lines +26 to +32
// Records blank() calls so the prompt is observable without a spy framework.
class FakeNotificationService {
public blankCalls: { title: string; content: string; options: NzNotificationDataOptions }[] = [];
blank(title: string, content: string, options: NzNotificationDataOptions = {}): void {
this.blankCalls.push({ title, content, options });
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's call it a mock, not "fake"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't we test against the real notification service?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Now injects the real NotificationService (stubbing only ng-zorro's NzNotificationService/NzMessageService) and spies on blank().

Comment on lines +69 to +75
promptReload(): void {
this.notification.blank(
"New version available",
"A new version of Texera is available. Refresh the page to update.",
{ nzDuration: 0 }
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if notification can add this:

have a button to trigger page refresh?

just for convenience?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. blank() returns the NzNotificationRef, so clicking the notification now reloads the page (guarded with take(1)). Added a test for it.

Comment thread frontend/src/app/app.component.spec.ts Outdated
}

// Records start() invocations without a spy framework.
class FakeDeploymentVersionService {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create a spy around the real service?

vitest should have framework to spy a function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Uses the real service with vi.spyOn(service, "startPollingForUpdates") instead of a fake.

Comment thread frontend/src/app/app.component.ts Outdated
Comment on lines +46 to +47
private config: GuiConfigService,
private deploymentVersion: DeploymentVersionService

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configService and deploymentVersionService

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Renamed to configService and deploymentVersionService.

Comment thread frontend/src/app/app.component.ts Outdated
// config actually loaded, and this isn't the "dev" placeholder build where
// no deployments occur.
if (this.configLoaded && this.config.env.deploymentVersionCheckEnabled && Version.buildNumber !== "dev") {
this.deploymentVersion.start();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you cannot start a version. let's make the naming better

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Renamed start() to startPollingForUpdates().

Comment thread frontend/build-version.js
Comment on lines +41 to +47
function main() {
const { version } = require("./package.json");
const { buildNumber, prodTs, manifestJson } = renderVersionArtifacts(version);
writeFileSync(resolve(__dirname, "src", "environments", "version.prod.ts"), prodTs);
writeFileSync(resolve(__dirname, "src", "assets", "version.json"), manifestJson);
console.log(`build-version: ${buildNumber}`);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it the only way to write a file? how about use an env variable?

either way we have to take care of the file life cycle: who is going to delete this file/env variable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An env var won't work: the browser fetches version.json at runtime to read the deployed version, and it can't read a server-side env var. Both generated files are gitignored and rewritten in place every build, so there's nothing to clean up.

Ma77Ball added 3 commits June 24, 2026 01:12
  refactor(frontend): address review feedback on
  deployment version check

  - Test the real
  NotificationService/DeploymentVersionService with
  vi.spyOn
    instead of hand-rolled fakes, stubbing only
  ng-zorro's lower-level services
  - Make the reload notification actionable: blank()
  returns the NzNotificationRef
    and promptReload() reloads the page on click
  (guarded with take(1))
  - Rename start() -> startPollingForUpdates() for
  clearer intent
  - Rename AppComponent constructor params to
  configService/deploymentVersionService
@Ma77Ball Ma77Ball force-pushed the feat/deployment-version-check branch from 49b77f0 to 7502d56 Compare June 26, 2026 19:04
@Ma77Ball Ma77Ball requested a review from Yicong-Huang June 26, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common feature frontend Changes related to the frontend GUI platform Non-amber Scala service paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect a new frontend deployment at runtime and prompt the user to reload

5 participants