Replace Approve link with confirmation dialog on Releases page

The "Approve (1)" action on the Releases table was a link navigating to
/releases/approvals (a separate page). Users expected an inline confirmation
to approve the release directly.

Now shows an app-confirm-dialog with the release name, then calls the
approval decision API (POST /api/v1/approvals/{id}/decision) and refreshes
the release list on success.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-27 18:23:10 +02:00
parent 8a8e27d27d
commit a975a03159

View File

@@ -8,11 +8,15 @@
* Tab 2 "Approvals": embeds the existing ApprovalQueueComponent.
*/
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject, signal, computed } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, inject, signal, computed } from '@angular/core';
import { PageActionService } from '../../core/services/page-action.service';
import { PageActionOutletComponent } from '../../shared/components/page-action-outlet/page-action-outlet.component';
import { UpperCasePipe, SlicePipe } from '@angular/common';
import { RouterLink, ActivatedRoute } from '@angular/router';
import { take } from 'rxjs';
import { APPROVAL_API } from '../../core/api/approval.client';
import type { ApprovalApi } from '../../core/api/approval.client';
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
import { StellaFilterChipComponent, FilterChipOption } from '../../shared/components/stella-filter-chip/stella-filter-chip.component';
import { StellaPageTabsComponent, StellaPageTab } from '../../shared/components/stella-page-tabs/stella-page-tabs.component';
import { PaginationComponent, PageChangeEvent } from '../../shared/components/pagination/pagination.component';
@@ -63,6 +67,7 @@ export interface PipelineRelease {
PaginationComponent,
PageActionOutletComponent,
ReleaseListComponent,
ConfirmDialogComponent,
],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@@ -205,10 +210,10 @@ export interface PipelineRelease {
</button>
}
@if (r.gatePendingApprovals > 0) {
<a class="decision-capsule decision-capsule--approve" routerLink="/releases/approvals">
<button type="button" class="decision-capsule decision-capsule--approve" (click)="openApproveDialog(r)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 11l3 3L22 4"/></svg>
Approve ({{ r.gatePendingApprovals }})
</a>
</button>
}
@if (r.gateStatus === 'block') {
<a class="decision-capsule decision-capsule--review" [routerLink]="['/releases/detail', r.id, 'gates']">
@@ -260,6 +265,12 @@ export interface PipelineRelease {
@if (activeTab() === 'versions') {
<app-release-list [embedded]="true" />
}
<app-confirm-dialog #approveConfirm
title="Approve Release"
[message]="approveMessage()"
confirmLabel="Approve" cancelLabel="Cancel" variant="warning"
(confirmed)="executeApprove()" />
</div>
`,
styles: [`
@@ -380,10 +391,39 @@ export class ReleasesUnifiedPageComponent implements OnInit, OnDestroy {
private readonly store = inject(ReleaseManagementStore);
private readonly context = inject(PlatformContextStore);
private readonly route = inject(ActivatedRoute);
private readonly approvalApi = inject<ApprovalApi>(APPROVAL_API);
readonly releaseTabs = RELEASE_TABS;
readonly activeTab = signal<string>(this.route.snapshot.queryParamMap.get('tab') || 'releases');
// Approve dialog
readonly pendingApproveRelease = signal<PipelineRelease | null>(null);
@ViewChild('approveConfirm') approveConfirmRef!: ConfirmDialogComponent;
readonly approveMessage = computed(() => {
const r = this.pendingApproveRelease();
if (!r) return '';
return `Approve release "${r.name}" (${r.version || r.digest?.slice(0, 19) || 'unknown'}) for promotion?`;
});
openApproveDialog(r: PipelineRelease): void {
this.pendingApproveRelease.set(r);
this.approveConfirmRef.open();
}
executeApprove(): void {
const r = this.pendingApproveRelease();
if (!r) return;
// Call the approval decision endpoint for this release
this.approvalApi.approve(r.id, `Approved from releases page`).pipe(take(1)).subscribe({
next: () => {
this.pendingApproveRelease.set(null);
this.store.loadReleases({});
},
error: () => this.pendingApproveRelease.set(null),
});
}
ngOnInit(): void {
this.pageAction.set({ label: 'New Release', route: '/releases/new' });
this.context.initialize();