fix: bind the node-update changelog to the advertised release version (#1492)

The changelog tab fetched release notes once and held them in component state
without tying them to a version, and the endpoint did not report which release
the notes belonged to. When a newer release surfaced while the sheet stayed
mounted, reopening the changelog could show the previous version's notes, and a
GitHub/Docker Hub fallback or independent cache timing could leave the notes out
of sync with the advertised latest version.

The release-notes endpoint now returns the release version (normalized
tag_name). The changelog keys its loaded notes to the advertised latest version,
refetching when that version changes, and labels the notes with the version they
belong to so the displayed content is always explicit.
This commit is contained in:
Anso
2026-06-27 22:38:55 -04:00
committed by GitHub
parent 3ad807be45
commit 4cf04056c9
4 changed files with 142 additions and 30 deletions
@@ -276,3 +276,35 @@ describe('forced-recheck throttle', () => {
expect(FleetUpdateTrackerService.getInstance().get(proxyNodeId)).toBeUndefined();
});
});
describe('GET /api/fleet/update-status/release-notes', () => {
// Each case uses ?recheck=true so getLatestRelease force-invalidates the cache
// and fetches fresh, keeping the assertion independent of prior cache state.
it('binds the returned notes to the release version (normalized tag_name)', async () => {
vi.spyOn(globalThis, 'fetch').mockImplementation(async () =>
new Response(JSON.stringify({
tag_name: 'v0.93.0',
body: '## Notes for 0.93.0',
html_url: 'https://github.com/studio-saelix/sencho/releases/tag/v0.93.0',
}), { status: 200, headers: { 'content-type': 'application/json' } }),
);
const res = await request(app)
.get('/api/fleet/update-status/release-notes?recheck=true')
.set('Authorization', adminAuth);
expect(res.status).toBe(200);
expect(res.body.version).toBe('0.93.0');
expect(res.body.releaseNotes).toBe('## Notes for 0.93.0');
expect(res.body.htmlUrl).toContain('v0.93.0');
});
it('returns null fields when the upstream release lookup fails', async () => {
vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('', { status: 500 }));
const res = await request(app)
.get('/api/fleet/update-status/release-notes?recheck=true')
.set('Authorization', adminAuth);
expect(res.status).toBe(200);
expect(res.body.version).toBeNull();
expect(res.body.releaseNotes).toBeNull();
expect(res.body.htmlUrl).toBeNull();
});
});
+3
View File
@@ -1070,6 +1070,9 @@ fleetRouter.get('/update-status/release-notes', authMiddleware, async (req: Requ
const forceRefresh = req.query.recheck === 'true';
const release = await getLatestRelease(forceRefresh);
res.json({
// Bind the notes to the release they belong to so the frontend can tell
// when the advertised latest version has moved past the loaded notes.
version: release ? release.tag_name.replace(/^v/, '') : null,
releaseNotes: release?.body ?? null,
htmlUrl: release?.html_url ?? null,
});