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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user