From b87ffeb237b36512fe2e4a8ce88230599ce5334b Mon Sep 17 00:00:00 2001 From: master <> Date: Mon, 9 Mar 2026 00:09:01 +0200 Subject: [PATCH] Repair live releases deployment detail flows --- ...-detail-with-workflow-dag-visualization.md | 10 +- .../checked/web/deployment-monitoring-ui.md | 10 +- ...ses_deployments_route_and_action_repair.md | 69 ++++++ .../live-releases-deployments-check.mjs | 160 ++++++++++++++ .../deployment-detail-page.component.ts | 205 ++++++++++++++++-- .../deployments-list-page.component.ts | 5 +- .../src/app/routes/releases.routes.spec.ts | 8 + .../src/app/routes/releases.routes.ts | 4 +- .../deployment-detail-page.component.spec.ts | 36 +++ .../deployments-list-page.component.spec.ts | 31 +++ 10 files changed, 508 insertions(+), 30 deletions(-) create mode 100644 docs/implplan/SPRINT_20260308_026_FE_live_releases_deployments_route_and_action_repair.md create mode 100644 src/Web/StellaOps.Web/scripts/live-releases-deployments-check.mjs create mode 100644 src/Web/StellaOps.Web/src/tests/deployments/deployments-list-page.component.spec.ts diff --git a/docs/features/checked/web/deployment-detail-with-workflow-dag-visualization.md b/docs/features/checked/web/deployment-detail-with-workflow-dag-visualization.md index 3aa2ad268..ed2c32451 100644 --- a/docs/features/checked/web/deployment-detail-with-workflow-dag-visualization.md +++ b/docs/features/checked/web/deployment-detail-with-workflow-dag-visualization.md @@ -7,11 +7,11 @@ Web VERIFIED ## Description -Deployment detail page with workflow DAG visualization showing deployment step execution, artifact promotion flow, and gate evaluation results. +Read-only deployment detail page under the canonical `/releases/deployments/:deploymentId` host, with workflow DAG visualization, artifact/log inspection, and evidence/replay hand-offs that stay truthful to the current live backend contract. ## Implementation Details - **Feature directory**: `src/Web/StellaOps.Web/src/app/features/deployments/` -- **Routes**: `deployments.routes.ts` +- **Routes**: `src/app/routes/releases.routes.ts` mounts `features/deployments/deployments.routes.ts` under `/releases/deployments` - **Components**: - `deployment-detail-page` (`src/Web/StellaOps.Web/src/app/features/deployments/deployment-detail-page.component.ts`) - `deployments-list-page` (`src/Web/StellaOps.Web/src/app/features/deployments/deployments-list-page.component.ts`) @@ -49,3 +49,9 @@ Deployment detail page with workflow DAG visualization showing deployment step e - Status: PASSED (strict Tier 2 UI replay) - Tier 2 evidence: docs/qa/feature-checks/runs/web/deployment-detail-with-workflow-dag-visualization/run-004/tier2-ui-check.json - Notes: Verified via /release-jobengine/deployments/dep-001 workflow DAG node rendering and selection checks. + +## Recheck (run-005) +- Date (UTC): 2026-03-08T22:06:32Z +- Status: VERIFIED (strict live Playwright replay) +- Tier 2 evidence: `src/Web/StellaOps.Web/output/playwright/live-releases-deployments-check.json` +- Notes: Verified the canonical Releases detail route at `/releases/deployments/DEP-2026-050`, workflow/targets/artifacts/logs/evidence tabs, and the repaired detail actions. The page remains intentionally read-only until the live deployment operate API is available again. diff --git a/docs/features/checked/web/deployment-monitoring-ui.md b/docs/features/checked/web/deployment-monitoring-ui.md index 9cc4936c2..c2c852a1c 100644 --- a/docs/features/checked/web/deployment-monitoring-ui.md +++ b/docs/features/checked/web/deployment-monitoring-ui.md @@ -7,11 +7,11 @@ Web VERIFIED ## Description -Real-time deployment monitoring with per-target progress tracking, live log streaming, deployment actions (pause/resume/cancel), and rollback capabilities. +Read-only deployment monitoring in the canonical Releases shell with per-target progress tracking, workflow/log inspection, artifact export, and evidence/replay hand-offs. Live operate and rollback controls remain deferred until the deployment API is restored. ## Implementation Details - **Feature directory**: `src/Web/StellaOps.Web/src/app/features/deployments/` -- **Routes**: `deployments.routes.ts` +- **Routes**: `src/app/routes/releases.routes.ts` mounts `features/deployments/deployments.routes.ts` under `/releases/deployments` - **Components**: - `deployment-detail-page` (`src/Web/StellaOps.Web/src/app/features/deployments/deployment-detail-page.component.ts`) - `deployments-list-page` (`src/Web/StellaOps.Web/src/app/features/deployments/deployments-list-page.component.ts`) @@ -43,3 +43,9 @@ Real-time deployment monitoring with per-target progress tracking, live log stre - Status: VERIFIED (strict Tier 2 UI replay) - Tier 2 evidence: docs/qa/feature-checks/runs/web/deployment-monitoring-ui/run-003/tier2-ui-check.json. +## Recheck (run-004) +- Date (UTC): 2026-03-08T22:06:32Z +- Status: VERIFIED (strict live Playwright replay) +- Tier 2 evidence: `src/Web/StellaOps.Web/output/playwright/live-releases-deployments-check.json` +- Notes: Verified canonical `/releases/deployments/:deploymentId` render, removed dead release-version anchors from the list, confirmed header replay hand-off, local evidence-tab hand-off, artifact hash copy fallback, artifact view/download fallback, log download, and evidence/proof-chain route entry points. Rollback is intentionally absent from the mounted surface while the live deployment operate API remains unavailable. + diff --git a/docs/implplan/SPRINT_20260308_026_FE_live_releases_deployments_route_and_action_repair.md b/docs/implplan/SPRINT_20260308_026_FE_live_releases_deployments_route_and_action_repair.md new file mode 100644 index 000000000..d51e4a083 --- /dev/null +++ b/docs/implplan/SPRINT_20260308_026_FE_live_releases_deployments_route_and_action_repair.md @@ -0,0 +1,69 @@ +# Sprint 20260308-026 - FE Live Releases Deployments Route And Action Repair + +## Topic & Scope +- Repair the canonical `/releases/deployments` subtree so deployment detail routes render under the Releases shell instead of falling through to unrelated fallback content. +- Remove or replace dead actions inside the currently mounted deployment history/detail surfaces so visible UI affordances are either functional or explicitly not presented. +- Keep the repair inside the active Web shell and document the live contract boundary while the legacy deployment API remains unavailable. +- Working directory: `src/Web/StellaOps.Web`. +- Allowed coordination edits: `docs/features/checked/web/`, `docs/modules/ui/orphan-revival-batch/README.md`, `docs/modules/ui/TASKS.md`, `src/Web/StellaOps.Web/src/app/routes/releases.routes.ts`, `src/Web/StellaOps.Web/src/app/features/deployments/`, `src/Web/StellaOps.Web/scripts/`. +- Expected evidence: targeted Angular coverage, rebuilt web bundle synced to the compose frontdoor volume, and live Playwright verification against `https://stella-ops.local`. + +## Dependencies & Concurrency +- Depends on the current Releases shell remaining canonical for deployment history under `/releases`. +- Safe parallelism: avoid unrelated search, package, and setup areas; keep edits limited to the releases route tree and `features/deployments`. + +## Documentation Prerequisites +- `src/Web/StellaOps.Web/AGENTS.md` +- `docs/modules/platform/architecture-overview.md` +- `docs/technical/architecture/console-admin-rbac.md` +- `docs/technical/architecture/console-branding.md` +- `docs/modules/ui/orphan-revival-batch/README.md` +- `docs/features/checked/web/deployment-monitoring-ui.md` +- `docs/features/checked/web/deployment-detail-with-workflow-dag-visualization.md` + +## Delivery Tracker + +### FE-LIVE-DEP-001 - Reconnect canonical Releases deployment detail routing +Status: DONE +Dependency: none +Owners: Developer (FE), QA +Task description: +- Mount the full deployments route tree under `/releases/deployments` so detail URLs resolve inside the canonical Releases shell. +- Verify that the deployment list no longer links to unreachable routes or invalid release-version URLs. + +Completion criteria: +- [x] `/releases/deployments/:deploymentId` renders the deployment detail workspace instead of fallback content. +- [x] Deployment list actions do not point at non-existent `/releases/:version` routes. +- [x] Route-focused regression coverage exists for the canonical Releases mount. + +### FE-LIVE-DEP-002 - Make deployment detail actions functional or remove them +Status: DONE +Dependency: FE-LIVE-DEP-001 +Owners: Developer (FE), Product Manager +Task description: +- Replace console-log-only deployment detail actions with real operator flows where safe, and remove misleading actions where no live contract exists yet. +- Keep the detail workspace internally consistent in the canonical `/releases` host even while the legacy deployment API remains unavailable. + +Completion criteria: +- [x] Visible deployment detail actions either navigate/download/copy successfully or are no longer shown. +- [x] Legacy `/deployments` path assumptions are removed from the mounted detail workspace. +- [x] Checked-feature documentation records the repaired live contract and any intentionally deferred capability. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-08 | Sprint created after live Playwright found `/releases/deployments/:deploymentId` falling through to fallback content and the deployment list generating dead release links. | Developer (FE) | +| 2026-03-08 | Re-mounted `/releases/deployments` as the full lazy route tree, removed dead release-version anchors from the list surface, and added route/list regression coverage. | Developer (FE) | +| 2026-03-08 | Replaced console-log-only detail actions with real evidence-tab, replay, proof-chain, artifact, and log flows; removed rollback from the mounted UI because the live deployment operate API is still absent. | Developer (FE) | +| 2026-03-08 | Verified targeted Angular coverage (`15/15`), rebuilt the web bundle, synced `dist/stellaops-web/browser` into `compose_console-dist`, and passed the live Playwright regression script `scripts/live-releases-deployments-check.mjs` against `https://stella-ops.local`. | QA | + +## Decisions & Risks +- Decision: keep the current deployment detail workspace as a bounded, read-only `/releases/deployments/:deploymentId` surface because the Releases shell still owns deployment history, but avoid implying live operate/rollback contracts that the backend does not currently provide. +- Risk: the legacy deployment HTTP API (`/api/v1/release-orchestrator/deployments`) is currently unavailable in the live stack, so this sprint must avoid binding visible routes to dead backend contracts. +- Mitigation: repair route ownership first, then keep the detail page honest about which actions are available in the current canonical host. +- Decision: `Open Evidence` now focuses the local evidence tab, while evidence/proof-chain hand-offs route to canonical `/evidence/capsules` and `/evidence/proofs` entry points instead of fabricating a capsule-detail deep link that may not exist in live data. +- Decision: the evidence workspace and proof-chain links preserve `returnTo` so operators can jump back to the deployment detail route without losing the canonical Releases host. + +## Next Checkpoints +- 2026-03-08: route tree reconnected and live detail render verified. +- 2026-03-08: detail actions rechecked with Playwright after bundle sync. diff --git a/src/Web/StellaOps.Web/scripts/live-releases-deployments-check.mjs b/src/Web/StellaOps.Web/scripts/live-releases-deployments-check.mjs new file mode 100644 index 000000000..d353cb5c7 --- /dev/null +++ b/src/Web/StellaOps.Web/scripts/live-releases-deployments-check.mjs @@ -0,0 +1,160 @@ +#!/usr/bin/env node + +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { chromium } from 'playwright'; + +import { authenticateFrontdoor } from './live-frontdoor-auth.mjs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const webRoot = path.resolve(__dirname, '..'); +const outputDirectory = path.join(webRoot, 'output', 'playwright'); + +const BASE_URL = process.env.STELLAOPS_FRONTDOOR_BASE_URL?.trim() || 'https://stella-ops.local'; +const LIST_URL = `${BASE_URL}/releases/deployments?tenant=demo-prod®ions=us-east&environments=stage`; +const DETAIL_URL = `${BASE_URL}/releases/deployments/DEP-2026-050?tenant=demo-prod®ions=us-east&environments=stage`; +const STATE_PATH = path.join(outputDirectory, 'live-frontdoor-auth-state.json'); +const REPORT_PATH = path.join(outputDirectory, 'live-frontdoor-auth-report.json'); +const RESULT_PATH = path.join(outputDirectory, 'live-releases-deployments-check.json'); + +async function seedAuthenticatedPage(browser, authReport) { + const context = await browser.newContext({ + ignoreHTTPSErrors: true, + acceptDownloads: true, + storageState: STATE_PATH, + }); + const page = await context.newPage(); + + await page.goto(`${BASE_URL}/welcome`, { waitUntil: 'domcontentloaded', timeout: 30_000 }); + await page.evaluate((storage) => { + sessionStorage.clear(); + for (const [key, value] of storage.sessionStorageEntries ?? []) { + if (typeof key === 'string' && typeof value === 'string') { + sessionStorage.setItem(key, value); + } + } + }, authReport.storage); + + await page.goto(LIST_URL, { waitUntil: 'networkidle', timeout: 30_000 }); + return { context, page }; +} + +async function main() { + mkdirSync(outputDirectory, { recursive: true }); + + await authenticateFrontdoor({ + baseUrl: BASE_URL, + statePath: STATE_PATH, + reportPath: REPORT_PATH, + headless: true, + }); + + const authReport = JSON.parse(readFileSync(REPORT_PATH, 'utf8')); + const browser = await chromium.launch({ headless: true, args: ['--disable-dev-shm-usage'] }); + + try { + const { context, page } = await seedAuthenticatedPage(browser, authReport); + const result = { + checkedAtUtc: new Date().toISOString(), + listUrl: page.url(), + listHeading: await page.locator('h1').first().textContent(), + releaseVersionAnchors: await page.locator('tbody tr td:nth-child(2) a').count(), + firstDeploymentHref: '', + detailUrl: '', + detailHeading: '', + reloadedDetailUrl: '', + reloadedDetailHeading: '', + reloadedDetailButtons: [], + replayUrl: '', + evidenceUrl: '', + evidenceHeading: null, + evidenceWorkspaceHref: '', + evidenceWorkspaceUrl: '', + proofChainsHref: '', + proofChainsUrl: '', + copyHashStatus: '', + artifactViewUrl: '', + artifactViewStatus: '', + artifactDownloadSuggestedFilename: '', + logsDownloadSuggestedFilename: '', + detailActionStatus: '', + }; + const headerActions = page.locator('.deployment-detail .header-actions button'); + + result.firstDeploymentHref = (await page.locator('tbody a.deployment-link').first().getAttribute('href')) ?? ''; + + await page.goto(DETAIL_URL, { waitUntil: 'networkidle', timeout: 30_000 }); + result.detailUrl = page.url(); + result.detailHeading = (await page.locator('h1').first().textContent()) ?? ''; + process.stdout.write(`[live-releases-deployments-check] detail ${result.detailUrl} :: ${result.detailHeading}\n`); + + await headerActions.nth(1).click(); + await page.waitForTimeout(1_000); + result.replayUrl = page.url(); + + await page.goto(result.detailUrl, { waitUntil: 'networkidle', timeout: 30_000 }); + result.reloadedDetailUrl = page.url(); + result.reloadedDetailHeading = (await page.locator('h1').first().textContent().catch(() => '')) ?? ''; + result.reloadedDetailButtons = await page.locator('button').allTextContents(); + process.stdout.write( + `[live-releases-deployments-check] reloaded ${result.reloadedDetailUrl} :: ${result.reloadedDetailHeading} :: ${result.reloadedDetailButtons.join(' | ')}\n`, + ); + await headerActions.nth(0).click(); + await page.waitForTimeout(1_000); + result.evidenceUrl = page.url(); + result.evidenceHeading = await page.locator('.tab-content h3').first().textContent().catch(() => null); + result.evidenceWorkspaceHref = (await page.locator('.evidence-info a').getAttribute('href')) ?? ''; + await page.goto(new URL(result.evidenceWorkspaceHref, BASE_URL).toString(), { waitUntil: 'networkidle', timeout: 30_000 }); + result.evidenceWorkspaceUrl = page.url(); + + await page.goto(result.detailUrl, { waitUntil: 'networkidle', timeout: 30_000 }); + await headerActions.nth(0).click(); + await page.waitForTimeout(1_000); + result.proofChainsHref = (await page.locator('.rekor-link').getAttribute('href')) ?? ''; + await page.goto(new URL(result.proofChainsHref, BASE_URL).toString(), { waitUntil: 'networkidle', timeout: 30_000 }); + result.proofChainsUrl = page.url(); + + await page.goto(result.detailUrl, { waitUntil: 'networkidle', timeout: 30_000 }); + await page.getByRole('button', { name: 'Artifacts' }).click(); + await page.getByTitle('Copy full hash').first().click(); + await page.waitForTimeout(500); + result.copyHashStatus = + (await page.locator('.deployment-detail .action-status').first().textContent().catch(() => ''))?.trim() ?? ''; + + const popupPromise = page.waitForEvent('popup', { timeout: 5_000 }).catch(() => null); + await page.getByRole('button', { name: 'View' }).first().click(); + const popup = await popupPromise; + result.artifactViewUrl = popup?.url() ?? ''; + result.artifactViewStatus = + (await page.locator('.deployment-detail .action-status').first().textContent().catch(() => ''))?.trim() ?? ''; + await popup?.close(); + + const artifactDownloadPromise = page.waitForEvent('download'); + await page.getByRole('button', { name: 'Download' }).first().click(); + const artifactDownload = await artifactDownloadPromise; + result.artifactDownloadSuggestedFilename = artifactDownload.suggestedFilename(); + + await page.getByRole('button', { name: 'Logs' }).click(); + const logsDownloadPromise = page.waitForEvent('download'); + await page.getByRole('button', { name: 'Download' }).click(); + const logsDownload = await logsDownloadPromise; + result.logsDownloadSuggestedFilename = logsDownload.suggestedFilename(); + + result.detailActionStatus = + (await page.locator('.deployment-detail .action-status').first().textContent().catch(() => ''))?.trim() ?? ''; + + writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, 'utf8'); + await context.close(); + process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); + } finally { + await browser.close(); + } +} + +main().catch((error) => { + process.stderr.write(`[live-releases-deployments-check] ${error instanceof Error ? error.message : String(error)}\n`); + process.exit(1); +}); diff --git a/src/Web/StellaOps.Web/src/app/features/deployments/deployment-detail-page.component.ts b/src/Web/StellaOps.Web/src/app/features/deployments/deployment-detail-page.component.ts index e920daaf6..af4ac2312 100644 --- a/src/Web/StellaOps.Web/src/app/features/deployments/deployment-detail-page.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/deployments/deployment-detail-page.component.ts @@ -7,7 +7,7 @@ import { Component, ChangeDetectionStrategy, signal, computed, inject, OnInit, ElementRef, ViewChild, AfterViewChecked } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ActivatedRoute, RouterLink } from '@angular/router'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { FormsModule } from '@angular/forms'; interface WorkflowStep { @@ -36,7 +36,7 @@ interface DeploymentArtifact { template: `
{{ actionMessage() }}
+ } @@ -291,7 +289,12 @@ interface DeploymentArtifact {Evidence for this deployment is sealed and signed. - View full evidence packet + + Open evidence workspace +