feat(ui): reconnect release investigation routes [SPRINT-022]
Mount deploy-diff, change-trace, and timeline under /releases/investigation as bounded secondary routes. Timeline uses correlation-based model to avoid collision with shipped run-workspace tab. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,20 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// change-trace.routes.ts
|
||||
// Sprint: SPRINT_20260112_200_007_FE_ui_components
|
||||
// Updated: Sprint 022 - Mounted under /releases/investigation/change-trace
|
||||
// Description: Routes for change-trace feature.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Change Trace Routes
|
||||
*
|
||||
* Mounted under /releases/investigation/change-trace by releases.routes.ts (Sprint 022).
|
||||
* Canonical URLs:
|
||||
* /releases/investigation/change-trace - Trace viewer
|
||||
* /releases/investigation/change-trace/:traceId - Specific trace detail
|
||||
*/
|
||||
export const changeTraceRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
@@ -13,6 +22,8 @@ export const changeTraceRoutes: Routes = [
|
||||
import('./change-trace-viewer.component').then(
|
||||
(m) => m.ChangeTraceViewerComponent
|
||||
),
|
||||
title: 'Change Trace',
|
||||
data: { breadcrumb: 'Change Trace' },
|
||||
},
|
||||
{
|
||||
path: ':traceId',
|
||||
@@ -20,5 +31,7 @@ export const changeTraceRoutes: Routes = [
|
||||
import('./change-trace-viewer.component').then(
|
||||
(m) => m.ChangeTraceViewerComponent
|
||||
),
|
||||
title: 'Change Trace Detail',
|
||||
data: { breadcrumb: 'Trace Detail' },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @file deploy-diff.routes.ts
|
||||
* @sprint SPRINT_20260125_006_FE_ab_deploy_diff_panel (DD-009)
|
||||
* @updated Sprint 022 - Mounted under /releases/investigation/deploy-diff
|
||||
* @description Routes for deploy diff feature module.
|
||||
*/
|
||||
|
||||
@@ -9,7 +10,8 @@ import { Routes } from '@angular/router';
|
||||
/**
|
||||
* Deploy diff feature routes.
|
||||
*
|
||||
* Route: /deploy/diff?from={digest}&to={digest}
|
||||
* Mounted under /releases/investigation/deploy-diff by releases.routes.ts (Sprint 022).
|
||||
* Canonical URL: /releases/investigation/deploy-diff?from={digest}&to={digest}
|
||||
*/
|
||||
export const DEPLOY_DIFF_ROUTES: Routes = [
|
||||
{
|
||||
@@ -18,7 +20,7 @@ export const DEPLOY_DIFF_ROUTES: Routes = [
|
||||
import('./pages/deploy-diff.page').then(m => m.DeployDiffPage),
|
||||
title: 'Deployment Diff',
|
||||
data: {
|
||||
breadcrumb: 'Compare Versions',
|
||||
breadcrumb: 'Deploy Diff',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -5,6 +5,17 @@
|
||||
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Investigation Timeline Routes
|
||||
*
|
||||
* Mounted under /releases/investigation/timeline by releases.routes.ts (Sprint 022).
|
||||
* This is a correlation-based investigation timeline, distinct from the run
|
||||
* workspace tab at /releases/runs/:runId/timeline which shows run execution flow.
|
||||
*
|
||||
* Canonical URLs:
|
||||
* /releases/investigation/timeline - Timeline overview
|
||||
* /releases/investigation/timeline/:correlationId - Correlated event drill-in
|
||||
*/
|
||||
export const TIMELINE_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
@@ -12,6 +23,8 @@ export const TIMELINE_ROUTES: Routes = [
|
||||
import('./pages/timeline-page/timeline-page.component').then(
|
||||
(m) => m.TimelinePageComponent
|
||||
),
|
||||
title: 'Investigation Timeline',
|
||||
data: { breadcrumb: 'Timeline' },
|
||||
},
|
||||
{
|
||||
path: ':correlationId',
|
||||
@@ -19,6 +32,8 @@ export const TIMELINE_ROUTES: Routes = [
|
||||
import('./pages/timeline-page/timeline-page.component').then(
|
||||
(m) => m.TimelinePageComponent
|
||||
),
|
||||
title: 'Correlated Events',
|
||||
data: { breadcrumb: 'Correlation' },
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
84
src/Web/StellaOps.Web/src/app/routes/releases.routes.spec.ts
Normal file
84
src/Web/StellaOps.Web/src/app/routes/releases.routes.spec.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Releases Routes - Route structure verification
|
||||
* Sprint 022: FE-URI-004
|
||||
*
|
||||
* Validates that release investigation routes (timeline, deploy-diff,
|
||||
* change-trace) are mounted under /releases/investigation/ without
|
||||
* colliding with the shipped run workspace tabs.
|
||||
*/
|
||||
import { RELEASES_ROUTES } from './releases.routes';
|
||||
|
||||
describe('RELEASES_ROUTES', () => {
|
||||
it('should export a non-empty route array', () => {
|
||||
expect(RELEASES_ROUTES).toBeDefined();
|
||||
expect(Array.isArray(RELEASES_ROUTES)).toBe(true);
|
||||
expect(RELEASES_ROUTES.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should mount investigation timeline at "investigation/timeline"', () => {
|
||||
const timelineRoute = RELEASES_ROUTES.find((r) => r.path === 'investigation/timeline');
|
||||
expect(timelineRoute).toBeDefined();
|
||||
expect(timelineRoute!.loadChildren).toBeDefined();
|
||||
expect(timelineRoute!.title).toBe('Investigation Timeline');
|
||||
expect(timelineRoute!.data?.['breadcrumb']).toBe('Investigation Timeline');
|
||||
});
|
||||
|
||||
it('should mount deploy-diff at "investigation/deploy-diff"', () => {
|
||||
const diffRoute = RELEASES_ROUTES.find((r) => r.path === 'investigation/deploy-diff');
|
||||
expect(diffRoute).toBeDefined();
|
||||
expect(diffRoute!.loadChildren).toBeDefined();
|
||||
expect(diffRoute!.title).toBe('Deployment Diff');
|
||||
expect(diffRoute!.data?.['breadcrumb']).toBe('Deploy Diff');
|
||||
});
|
||||
|
||||
it('should mount change-trace at "investigation/change-trace"', () => {
|
||||
const traceRoute = RELEASES_ROUTES.find((r) => r.path === 'investigation/change-trace');
|
||||
expect(traceRoute).toBeDefined();
|
||||
expect(traceRoute!.loadChildren).toBeDefined();
|
||||
expect(traceRoute!.title).toBe('Change Trace');
|
||||
expect(traceRoute!.data?.['breadcrumb']).toBe('Change Trace');
|
||||
});
|
||||
|
||||
it('should not collide with the shipped run workspace timeline tab', () => {
|
||||
// The run workspace tabs include 'timeline' at runs/:runId/timeline
|
||||
// Investigation timeline must NOT be mounted at runs/:runId/timeline
|
||||
const investigationTimeline = RELEASES_ROUTES.find(
|
||||
(r) => r.path === 'investigation/timeline'
|
||||
);
|
||||
expect(investigationTimeline).toBeDefined();
|
||||
|
||||
// Verify the run workspace tab still exists (generated from RUN_WORKSPACE_TABS)
|
||||
const runTimelineTab = RELEASES_ROUTES.find(
|
||||
(r) => r.path === 'runs/:runId/timeline'
|
||||
);
|
||||
expect(runTimelineTab).toBeDefined();
|
||||
|
||||
// They are different routes
|
||||
expect(investigationTimeline).not.toBe(runTimelineTab);
|
||||
});
|
||||
|
||||
it('should preserve existing canonical release routes', () => {
|
||||
const paths = RELEASES_ROUTES.map((r) => r.path);
|
||||
expect(paths).toContain('overview');
|
||||
expect(paths).toContain('versions');
|
||||
expect(paths).toContain('runs');
|
||||
expect(paths).toContain('deployments');
|
||||
expect(paths).toContain('bundles');
|
||||
expect(paths).toContain('approvals');
|
||||
expect(paths).toContain('promotions');
|
||||
});
|
||||
|
||||
it('should use loadChildren for lazy-loaded investigation routes', () => {
|
||||
const investigationPaths = [
|
||||
'investigation/timeline',
|
||||
'investigation/deploy-diff',
|
||||
'investigation/change-trace',
|
||||
];
|
||||
for (const path of investigationPaths) {
|
||||
const route = RELEASES_ROUTES.find((r) => r.path === path);
|
||||
expect(route).toBeDefined();
|
||||
expect(typeof route!.loadChildren).toBe('function');
|
||||
expect(route!.loadComponent).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -205,6 +205,32 @@ export const RELEASES_ROUTES: Routes = [
|
||||
loadComponent: () =>
|
||||
import('../features/deployments/deployments-list-page.component').then((m) => m.DeploymentsListPageComponent),
|
||||
},
|
||||
// --- Release investigation routes (Sprint 022) ---
|
||||
// The investigation timeline is mounted as a bounded secondary route under
|
||||
// /releases/investigation/timeline to avoid colliding with the shipped
|
||||
// run workspace tab at /releases/runs/:runId/timeline.
|
||||
// Decision: bounded-secondary-route (not absorb-into-run-workspace).
|
||||
{
|
||||
path: 'investigation/timeline',
|
||||
title: 'Investigation Timeline',
|
||||
data: { breadcrumb: 'Investigation Timeline' },
|
||||
loadChildren: () =>
|
||||
import('../features/timeline/timeline.routes').then((m) => m.TIMELINE_ROUTES),
|
||||
},
|
||||
{
|
||||
path: 'investigation/deploy-diff',
|
||||
title: 'Deployment Diff',
|
||||
data: { breadcrumb: 'Deploy Diff' },
|
||||
loadChildren: () =>
|
||||
import('../features/deploy-diff/deploy-diff.routes').then((m) => m.DEPLOY_DIFF_ROUTES),
|
||||
},
|
||||
{
|
||||
path: 'investigation/change-trace',
|
||||
title: 'Change Trace',
|
||||
data: { breadcrumb: 'Change Trace' },
|
||||
loadChildren: () =>
|
||||
import('../features/change-trace/change-trace.routes').then((m) => m.changeTraceRoutes),
|
||||
},
|
||||
{
|
||||
path: 'bundles',
|
||||
title: 'Bundles',
|
||||
|
||||
Reference in New Issue
Block a user