diff --git a/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts b/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts index 5bc171b70..f09cb37a1 100644 --- a/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts @@ -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 { } @if (r.gatePendingApprovals > 0) { - + } @if (r.gateStatus === 'block') { @@ -260,6 +265,12 @@ export interface PipelineRelease { @if (activeTab() === 'versions') { } + + `, 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(APPROVAL_API); readonly releaseTabs = RELEASE_TABS; readonly activeTab = signal(this.route.snapshot.queryParamMap.get('tab') || 'releases'); + // Approve dialog + readonly pendingApproveRelease = signal(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();