Fix medium issues: user ID display, mirror toast, post-seal guidance
Fix #14 — User ID hash display: Add formatActor() to bundle-version-detail that truncates raw hex hashes to "User 209d1257..." instead of showing the full 32-char ID. Short IDs, emails, and names pass through unchanged. Fix #15 — Mirror generate-immediately silent failure: When domain creation succeeds but bundle generation fails, show a meaningful status message instead of silently swallowing the error: "Generation failed — the domain was created but the initial bundle could not be generated. You can retry from the mirror dashboard." Fix #19 — Wizard error handling: Already implemented — topology wizard sets wizard.error signal on all CRUD failures and displays error banner. No additional changes needed. Fix #21 — Post-seal guidance: After sealing a release (source=release-create query param), show a "What's next?" panel with links to: Promote to environment, Review approvals, Back to versions. Uses branded action buttons. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ import {
|
|||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<h2>Created by</h2>
|
<h2>Created by</h2>
|
||||||
<p>{{ versionDetailModel.createdBy }}</p>
|
<p>{{ formatActor(versionDetailModel.createdBy) }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<h2>Promotion readiness</h2>
|
<h2>Promotion readiness</h2>
|
||||||
@@ -49,6 +49,17 @@ import {
|
|||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@if (showPostSealGuide()) {
|
||||||
|
<section class="bvd__post-seal" aria-label="Next steps">
|
||||||
|
<h3>Release sealed successfully. What's next?</h3>
|
||||||
|
<div class="bvd__post-seal-actions">
|
||||||
|
<a [routerLink]="['/releases/promotions']" queryParamsHandling="merge" class="bvd__action-link">Promote to environment</a>
|
||||||
|
<a [routerLink]="['/releases/approvals']" queryParamsHandling="merge" class="bvd__action-link">Review approvals</a>
|
||||||
|
<a [routerLink]="['/releases/versions']" queryParamsHandling="merge" class="bvd__action-link">Back to versions</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="bvd__tabs" role="tablist">
|
<div class="bvd__tabs" role="tablist">
|
||||||
<button role="tab" [class.tab--active]="activeTab() === 'components'" (click)="setTab('components')">Components</button>
|
<button role="tab" [class.tab--active]="activeTab() === 'components'" (click)="setTab('components')">Components</button>
|
||||||
<button role="tab" [class.tab--active]="activeTab() === 'validation'" (click)="setTab('validation')">Validation</button>
|
<button role="tab" [class.tab--active]="activeTab() === 'validation'" (click)="setTab('validation')">Validation</button>
|
||||||
@@ -207,6 +218,36 @@ import {
|
|||||||
font-size: 0.84rem;
|
font-size: 0.84rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bvd__post-seal {
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border: 1px solid var(--color-brand-primary, #6366f1);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-surface-secondary, #f8fafc);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bvd__post-seal h3 {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bvd__post-seal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bvd__action-link {
|
||||||
|
padding: 0.45rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--color-brand-primary, #6366f1);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.bvd__tabs {
|
.bvd__tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-bottom: 2px solid var(--color-border, #e5e7eb);
|
border-bottom: 2px solid var(--color-border, #e5e7eb);
|
||||||
@@ -366,6 +407,7 @@ export class BundleVersionDetailComponent implements OnInit {
|
|||||||
readonly versionId = signal('');
|
readonly versionId = signal('');
|
||||||
readonly activeTab = signal<'components' | 'validation' | 'releases'>('components');
|
readonly activeTab = signal<'components' | 'validation' | 'releases'>('components');
|
||||||
readonly loading = signal(true);
|
readonly loading = signal(true);
|
||||||
|
readonly showPostSealGuide = signal(false);
|
||||||
readonly errorMessage = signal<string | null>(null);
|
readonly errorMessage = signal<string | null>(null);
|
||||||
readonly versionDetail = signal<ReleaseControlBundleVersionDetailDto | null>(null);
|
readonly versionDetail = signal<ReleaseControlBundleVersionDetailDto | null>(null);
|
||||||
readonly materializing = signal(false);
|
readonly materializing = signal(false);
|
||||||
@@ -379,6 +421,12 @@ export class BundleVersionDetailComponent implements OnInit {
|
|||||||
this.route.snapshot.params['versionId'] ?? this.route.snapshot.params['version'] ?? ''
|
this.route.snapshot.params['versionId'] ?? this.route.snapshot.params['version'] ?? ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Show post-seal guidance when arriving from the release creation wizard
|
||||||
|
const source = this.route.snapshot.queryParams['source'];
|
||||||
|
if (source === 'release-create') {
|
||||||
|
this.showPostSealGuide.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.bundleId() || !this.versionId()) {
|
if (!this.bundleId() || !this.versionId()) {
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
this.errorMessage.set('Bundle or version id is missing from route.');
|
this.errorMessage.set('Bundle or version id is missing from route.');
|
||||||
@@ -435,6 +483,16 @@ export class BundleVersionDetailComponent implements OnInit {
|
|||||||
return `${digest.slice(0, 20)}...`;
|
return `${digest.slice(0, 20)}...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatActor(actorId: string): string {
|
||||||
|
if (!actorId) return 'Unknown';
|
||||||
|
// Short readable IDs or well-known names pass through
|
||||||
|
if (actorId.length <= 20 || actorId.includes('@') || actorId.includes(' ')) {
|
||||||
|
return actorId;
|
||||||
|
}
|
||||||
|
// Raw hex hashes get truncated with a user indicator
|
||||||
|
return `User ${actorId.slice(0, 8)}...`;
|
||||||
|
}
|
||||||
|
|
||||||
promotionReadiness(status: string): string {
|
promotionReadiness(status: string): string {
|
||||||
return status.toLowerCase() === 'published'
|
return status.toLowerCase() === 'published'
|
||||||
? 'Ready after validation and materialization checks.'
|
? 'Ready after validation and materialization checks.'
|
||||||
|
|||||||
@@ -1446,7 +1446,13 @@ export class MirrorDomainBuilderComponent implements OnInit {
|
|||||||
this.creating.set(false);
|
this.creating.set(false);
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
// Domain was created but generate failed -- still show success
|
// Domain was created but generate failed
|
||||||
|
this.generateResult.set({
|
||||||
|
domainId: domain.domainId,
|
||||||
|
jobId: 'failed',
|
||||||
|
status: 'Generation failed — the domain was created but the initial bundle could not be generated. You can retry from the mirror dashboard.',
|
||||||
|
startedAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
this.creating.set(false);
|
this.creating.set(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user