mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-31 04:38:11 +00:00
fix(auto-update): label same-tag rebuilds as 'Rebuild available' instead of '10.11 -> 10.11' (#766)
When a registry pushes a new build of an image at the same tag (digest changes, tag does not), the preview service set next_tag to the same string as current_tag and the readiness view rendered '10.11 -> 10.11', which reads as a UI bug. Add an update_kind field to UpdatePreviewSummary that distinguishes: - 'tag' - at least one image has a strictly newer tag - 'digest' - the only updates available are same-tag rebuilds - 'none' - nothing to apply The frontend now branches on update_kind and renders 'Rebuild available' next to the current tag for the digest case, leaving the version-arrow diff for genuine tag bumps. Three new buildSummary cases lock in the kind classification.
This commit is contained in:
@@ -224,4 +224,23 @@ describe('buildSummary', () => {
|
|||||||
expect(preview.summary.semver_bump).toBe('major');
|
expect(preview.summary.semver_bump).toBe('major');
|
||||||
expect(preview.summary.blocked).toBe(true);
|
expect(preview.summary.blocked).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reports update_kind="tag" when at least one image has a strictly newer tag', () => {
|
||||||
|
const images = [
|
||||||
|
baseImage({ service: 'web', has_update: true, semver_bump: 'patch', next_tag: '1.0.1', current_tag: '1.0.0' }),
|
||||||
|
];
|
||||||
|
expect(buildSummary('stacky', images).summary.update_kind).toBe('tag');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports update_kind="digest" when only same-tag rebuilds are available', () => {
|
||||||
|
const images = [
|
||||||
|
baseImage({ service: 'web', has_update: true, semver_bump: 'patch', next_tag: '10.11', current_tag: '10.11', image: 'redis:10.11' }),
|
||||||
|
];
|
||||||
|
expect(buildSummary('stacky', images).summary.update_kind).toBe('digest');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports update_kind="none" when nothing has an update', () => {
|
||||||
|
const images = [baseImage({ service: 'clean', has_update: false })];
|
||||||
|
expect(buildSummary('stacky', images).summary.update_kind).toBe('none');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,12 +24,21 @@ export interface UpdatePreviewImage {
|
|||||||
semver_bump: SemverBump;
|
semver_bump: SemverBump;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UpdateKind = 'tag' | 'digest' | 'none';
|
||||||
|
|
||||||
export interface UpdatePreviewSummary {
|
export interface UpdatePreviewSummary {
|
||||||
has_update: boolean;
|
has_update: boolean;
|
||||||
primary_image: string | null;
|
primary_image: string | null;
|
||||||
current_tag: string | null;
|
current_tag: string | null;
|
||||||
next_tag: string | null;
|
next_tag: string | null;
|
||||||
semver_bump: SemverBump;
|
semver_bump: SemverBump;
|
||||||
|
/**
|
||||||
|
* Distinguishes a "new tag is available" update from a "same tag, new
|
||||||
|
* digest" rebuild. The UI renders the rebuild case differently because
|
||||||
|
* showing "10.11 -> 10.11" reads as a bug even though it is technically
|
||||||
|
* accurate (the tag did not change, only the immutable digest behind it).
|
||||||
|
*/
|
||||||
|
update_kind: UpdateKind;
|
||||||
blocked: boolean;
|
blocked: boolean;
|
||||||
blocked_reason: string | null;
|
blocked_reason: string | null;
|
||||||
}
|
}
|
||||||
@@ -208,6 +217,14 @@ export function buildSummary(stackName: string, images: UpdatePreviewImage[]): U
|
|||||||
'none',
|
'none',
|
||||||
);
|
);
|
||||||
const blocked = overallBump === 'major';
|
const blocked = overallBump === 'major';
|
||||||
|
// 'tag' means at least one image has a strictly newer tag; 'digest' means
|
||||||
|
// the only updates available are same-tag rebuilds (digest changed); 'none'
|
||||||
|
// means there is nothing to apply.
|
||||||
|
const updateKind: UpdateKind = !hasUpdate
|
||||||
|
? 'none'
|
||||||
|
: updated.some(i => i.next_tag !== null && i.next_tag !== i.current_tag)
|
||||||
|
? 'tag'
|
||||||
|
: 'digest';
|
||||||
return {
|
return {
|
||||||
stack_name: stackName,
|
stack_name: stackName,
|
||||||
images,
|
images,
|
||||||
@@ -217,6 +234,7 @@ export function buildSummary(stackName: string, images: UpdatePreviewImage[]): U
|
|||||||
current_tag: primary ? primary.current_tag : null,
|
current_tag: primary ? primary.current_tag : null,
|
||||||
next_tag: primary ? primary.next_tag : null,
|
next_tag: primary ? primary.next_tag : null,
|
||||||
semver_bump: overallBump,
|
semver_bump: overallBump,
|
||||||
|
update_kind: updateKind,
|
||||||
blocked,
|
blocked,
|
||||||
blocked_reason: blocked ? 'Major version jumps require human review before applying.' : null,
|
blocked_reason: blocked ? 'Major version jumps require human review before applying.' : null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ interface UpdatePreviewImage {
|
|||||||
semver_bump: SemverBump;
|
semver_bump: SemverBump;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateKind = 'tag' | 'digest' | 'none';
|
||||||
|
|
||||||
interface UpdatePreview {
|
interface UpdatePreview {
|
||||||
stack_name: string;
|
stack_name: string;
|
||||||
images: UpdatePreviewImage[];
|
images: UpdatePreviewImage[];
|
||||||
@@ -28,6 +30,7 @@ interface UpdatePreview {
|
|||||||
current_tag: string | null;
|
current_tag: string | null;
|
||||||
next_tag: string | null;
|
next_tag: string | null;
|
||||||
semver_bump: SemverBump;
|
semver_bump: SemverBump;
|
||||||
|
update_kind: UpdateKind;
|
||||||
blocked: boolean;
|
blocked: boolean;
|
||||||
blocked_reason: string | null;
|
blocked_reason: string | null;
|
||||||
};
|
};
|
||||||
@@ -160,10 +163,19 @@ function StackReadinessCard({
|
|||||||
const blockedReason = p.summary.blocked_reason;
|
const blockedReason = p.summary.blocked_reason;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<VersionDiff
|
{p.summary.update_kind === 'digest' ? (
|
||||||
current={p.summary.current_tag}
|
<div className="flex items-baseline gap-2 font-mono text-sm">
|
||||||
next={p.summary.next_tag}
|
<span className="text-stat-subtitle">{p.summary.current_tag}</span>
|
||||||
/>
|
<span className="text-brand text-[11px] uppercase tracking-[0.16em]">
|
||||||
|
Rebuild available
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<VersionDiff
|
||||||
|
current={p.summary.current_tag}
|
||||||
|
next={p.summary.next_tag}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5 font-mono text-[11px] text-stat-subtitle/80">
|
<div className="flex items-center gap-1.5 font-mono text-[11px] text-stat-subtitle/80">
|
||||||
<span>{p.summary.primary_image ?? '-'}</span>
|
<span>{p.summary.primary_image ?? '-'}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user