Simplify Deployments tabs and fix duplicate Version button

Deployments page:
- Remove redundant context chips (US East, Development, 24h) — topbar already shows these
- Rename Timeline tab → Pipeline with appropriate icon
- Remove Table tab (raw data table, low value vs Pipeline view)
- Remove Correlations tab (deployment correlations belong in Pipeline view)
- Keep only Pipeline + Approvals tabs

Releases Versions tab:
- Fix duplicate "New Version" button — ReleaseListComponent now accepts
  [embedded]="true" to suppress its pageAction when rendered inside the
  unified Releases page (which already has "New Release" page action)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-27 17:44:34 +02:00
parent d704cb6c7f
commit ae9a9fab91
3 changed files with 11 additions and 69 deletions

View File

@@ -1,5 +1,5 @@
// Filter bar adoption: SPRINT_20260308_015_FE (FE-OFB-003)
import { Component, OnInit, OnDestroy, inject, signal, computed } from '@angular/core';
import { Component, Input, OnInit, OnDestroy, inject, signal, computed } from '@angular/core';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { PlatformContextStore } from '../../../../core/context/platform-context.store';
@@ -758,6 +758,8 @@ import { PageActionOutletComponent } from '../../../../shared/components/page-ac
`],
})
export class ReleaseListComponent implements OnInit, OnDestroy {
/** When true, suppresses page action (used when embedded as a tab inside another page). */
@Input() embedded = false;
private readonly dateFmt = inject(DateFormatService);
private readonly pageAction = inject(PageActionService);
@@ -831,7 +833,9 @@ export class ReleaseListComponent implements OnInit, OnDestroy {
});
ngOnInit(): void {
this.pageAction.set({ label: 'New Version', route: '/releases/versions/new' });
if (!this.embedded) {
this.pageAction.set({ label: 'New Version', route: '/releases/versions/new' });
}
this.context.initialize();
this.route.queryParamMap.subscribe((params) => {
this.applyingFromQuery = true;

View File

@@ -18,9 +18,7 @@ import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/c
import { ModalComponent } from '../../shared/components/modal/modal.component';
const VIEW_MODE_TABS: StellaPageTab[] = [
{ id: 'timeline', label: 'Timeline', icon: 'M12 12m-10 0a10 10 0 1 0 20 0 10 10 0 1 0-20 0|||M12 6v6l4 2' },
{ id: 'table', label: 'Table', icon: 'M8 6h13|||M8 12h13|||M8 18h13|||M3 6h.01|||M3 12h.01|||M3 18h.01' },
{ id: 'correlations', label: 'Correlations', icon: 'M22 12h-4l-3 9L9 3l-3 9H2' },
{ id: 'timeline', label: 'Pipeline', icon: 'M22 12h-4l-3 9L9 3l-3 9H2' },
{ id: 'approvals', label: 'Approvals', icon: 'M9 11l3 3L22 4|||M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11' },
];
import { StellaPageTabsComponent, StellaPageTab } from '../../shared/components/stella-page-tabs/stella-page-tabs.component';
@@ -72,12 +70,6 @@ function deriveOutcomeIcon(status: string): string {
<p>Deployment runs, approvals, and promotion activity.</p>
</header>
<div class="context">
<span>{{ context.regionSummary() }}</span>
<span>{{ context.environmentSummary() }}</span>
<span>{{ context.timeWindow() }}</span>
</div>
<!-- Pending approvals inline lane (dashboard-style action cards) -->
@if (pendingApprovals().length > 0 && viewMode() !== 'approvals') {
<div class="pending-lane">
@@ -304,60 +296,6 @@ function deriveOutcomeIcon(status: string): string {
</app-timeline-list>
</div>
}
@case ('correlations') {
<div class="clusters">
@for (cluster of correlationClusters(); track cluster.key) {
<article>
<h3>{{ cluster.key }}</h3>
<p>{{ cluster.count }} events · {{ cluster.releases }} release version(s)</p>
<p>{{ cluster.environments }}</p>
</article>
} @empty {
<div class="banner">No run correlations match the current filters.</div>
}
</div>
}
@default {
<table class="stella-table stella-table--striped stella-table--hoverable stella-table--bordered">
<thead>
<tr>
<th>Run</th>
<th>Release Version</th>
<th>Lane</th>
<th>Outcome</th>
<th>Environment</th>
<th>Needs Approval</th>
<th>Data Integrity</th>
<th>When</th>
</tr>
</thead>
<tbody>
@for (row of pagedRows(); track row.activityId) {
<tr>
<td><a [routerLink]="['/releases/runs', row.releaseId, 'summary']">{{ row.activityId }}</a></td>
<td>{{ row.releaseName }}</td>
<td>{{ deriveLane(row) }}</td>
<td>{{ deriveOutcome(row) }}</td>
<td>{{ row.targetRegion || '-' }}/{{ row.targetEnvironment || '-' }}</td>
<td>{{ deriveNeedsApproval(row) ? 'yes' : 'no' }}</td>
<td>{{ deriveDataIntegrity(row) }}</td>
<td>{{ formatDate(row.occurredAt) }}</td>
</tr>
} @empty {
<tr><td colspan="8">No runs match the active filters.</td></tr>
}
</tbody>
</table>
<div style="display: flex; justify-content: flex-end; padding-top: 0.75rem;">
<app-pagination
[total]="filteredRows().length"
[currentPage]="currentPage()"
[pageSize]="pageSize()"
[pageSizes]="[5, 10, 25, 50]"
(pageChange)="onPageChange($event)"
/>
</div>
}
}
}
}
@@ -547,7 +485,7 @@ export class ReleasesActivityComponent implements OnInit, OnDestroy {
readonly loading = signal(false);
readonly error = signal<string | null>(null);
readonly rows = signal<ReleaseActivityProjection[]>([]);
readonly viewMode = signal<'timeline' | 'table' | 'correlations' | 'approvals'>('timeline');
readonly viewMode = signal<'timeline' | 'approvals'>('timeline');
// ── Pending approvals card lane ──────────────────────────────────
@ViewChild('apcScroll') apcScrollRef?: ElementRef<HTMLDivElement>;
@@ -715,7 +653,7 @@ export class ReleasesActivityComponent implements OnInit, OnDestroy {
this.route.queryParamMap.subscribe((params) => {
const view = (params.get('view') ?? '').toLowerCase();
if (view && (view === 'timeline' || view === 'table' || view === 'correlations' || view === 'approvals')) {
if (view && (view === 'timeline' || view === 'approvals')) {
if (this.viewMode() !== view) {
this.viewMode.set(view);
if (view === 'approvals') this.loadApprovals();
@@ -792,7 +730,7 @@ export class ReleasesActivityComponent implements OnInit, OnDestroy {
}
onTabChange(tab: string): void {
this.viewMode.set(tab as 'timeline' | 'table' | 'correlations' | 'approvals');
this.viewMode.set(tab as 'timeline' | 'approvals');
if (tab === 'approvals') this.loadApprovals();
this.applyFilters();
}

View File

@@ -258,7 +258,7 @@ export interface PipelineRelease {
} <!-- end pipeline tab -->
@if (activeTab() === 'versions') {
<app-release-list />
<app-release-list [embedded]="true" />
}
</div>
`,