Fix mission board environment action scope
This commit is contained in:
@@ -0,0 +1,86 @@
|
|||||||
|
# Sprint 20260307-015 - FE Mission Board Environment Action Scope
|
||||||
|
|
||||||
|
## Topic & Scope
|
||||||
|
- Repair mission-board environment actions so they pass canonical environment and region scope into downstream topology and findings pages.
|
||||||
|
- Replace synthetic dashboard-only environment IDs in action links with route/query shapes the real pages actually understand.
|
||||||
|
- Add focused Angular coverage for the board action targets, then replay the affected actions with live Playwright from the authenticated mission board.
|
||||||
|
- Working directory: `src/Web/StellaOps.Web`.
|
||||||
|
- Expected evidence: focused Angular tests, live Playwright action checks on `https://stella-ops.local/mission-control/board`, and sprint execution log updates.
|
||||||
|
|
||||||
|
## Dependencies & Concurrency
|
||||||
|
- Depends on the earlier mission-board route fixes plus the release-health shared environment work (`SPRINT_20260307_012` and `SPRINT_20260307_013`) because the target topology/findings surfaces already expect canonical environment scope.
|
||||||
|
- Safe parallelism: stay inside `src/Web/StellaOps.Web` plus sprint updates; do not touch unrelated navigation/settings/sidebar work already in progress from other agents.
|
||||||
|
- Scope is limited to mission-board environment action routing, not a full dashboard data-model rewrite.
|
||||||
|
|
||||||
|
## Documentation Prerequisites
|
||||||
|
- `src/Web/StellaOps.Web/AGENTS.md`
|
||||||
|
- `src/Web/StellaOps.Web/src/app/features/dashboard-v3/dashboard-v3.component.ts`
|
||||||
|
- `src/Web/StellaOps.Web/src/app/features/topology/environment-posture-page.component.ts`
|
||||||
|
- `src/Web/StellaOps.Web/src/app/core/context/platform-context.store.ts`
|
||||||
|
|
||||||
|
## Delivery Tracker
|
||||||
|
|
||||||
|
### FE-DASH-001 - Reproduce mission-board environment action scope loss
|
||||||
|
Status: DONE
|
||||||
|
Dependency: none
|
||||||
|
Owners: QA
|
||||||
|
Task description:
|
||||||
|
- Replay mission-board actions from the live authenticated shell with Playwright.
|
||||||
|
- Confirm whether environment-specific actions preserve usable scope in downstream pages.
|
||||||
|
|
||||||
|
Completion criteria:
|
||||||
|
- [x] Live Playwright captures concrete mission-board action targets.
|
||||||
|
- [x] The broken action is reduced to a specific route/query mismatch.
|
||||||
|
|
||||||
|
### FE-DASH-002 - Replace synthetic environment action IDs with canonical scope
|
||||||
|
Status: DONE
|
||||||
|
Dependency: FE-DASH-001
|
||||||
|
Owners: Developer
|
||||||
|
Task description:
|
||||||
|
- Update the mission-board environment card/table model so action links use canonical environment IDs plus region scope instead of composite placeholder IDs.
|
||||||
|
- Preserve current board rendering while making downstream routes receive valid scope values.
|
||||||
|
|
||||||
|
Completion criteria:
|
||||||
|
- [x] Environment card `Detail` routes target the canonical topology posture path for the selected environment.
|
||||||
|
- [x] Environment card `Findings` routes preserve valid region/environment scope instead of `env=<synthetic-id>`.
|
||||||
|
- [x] Risk-table environment actions preserve the same canonical scope.
|
||||||
|
|
||||||
|
### FE-DASH-003 - Add focused Angular coverage for board action targets
|
||||||
|
Status: DONE
|
||||||
|
Dependency: FE-DASH-002
|
||||||
|
Owners: Test Automation
|
||||||
|
Task description:
|
||||||
|
- Add focused tests around the mission-board action helpers or rendered links.
|
||||||
|
- Verify the tests assert concrete route/query outputs for at least one environment card and one risk-table row.
|
||||||
|
|
||||||
|
Completion criteria:
|
||||||
|
- [x] Focused Angular tests prove the board emits canonical topology/findings targets.
|
||||||
|
- [x] Tests fail before the fix and pass after it.
|
||||||
|
|
||||||
|
### FE-DASH-004 - Replay the affected mission-board actions live
|
||||||
|
Status: DONE
|
||||||
|
Dependency: FE-DASH-003
|
||||||
|
Owners: QA
|
||||||
|
Task description:
|
||||||
|
- Re-run the mission-board environment actions with live Playwright after the Web bundle is rebuilt.
|
||||||
|
- Confirm the downstream pages load with the intended environment scope instead of dropping to all-environment views.
|
||||||
|
|
||||||
|
Completion criteria:
|
||||||
|
- [x] Live Playwright confirms the selected environment `Detail` action lands on the correct topology posture route.
|
||||||
|
- [x] Live Playwright confirms the selected environment `Findings` action preserves environment/region scope on the target page.
|
||||||
|
|
||||||
|
## Execution Log
|
||||||
|
| Date (UTC) | Update | Owner |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 2026-03-07 | Sprint created after live Playwright on `/mission-control/board` showed mission-board environment actions using synthetic IDs such as `env=dev-eu-west`, causing the downstream findings page to fall back to `All regions / All environments`. | QA |
|
||||||
|
| 2026-03-07 | Rebound `DashboardV3Component` environment actions to canonical region/environment scope from the live Platform context store and added focused coverage in `src/app/core/testing/dashboard-v3.component.spec.ts`; `npx ng test --watch=false --include src/app/core/testing/dashboard-v3.component.spec.ts` passed. | Developer |
|
||||||
|
| 2026-03-07 | Replayed the first mission-board environment card `Detail` and `Findings` actions plus the first risk-table `Open` action with live Playwright. The targets now preserve `regions=<region>&environments=<environment>` and the downstream pages render the expected scoped context (for example `US East / Development` and `US East / Staging`). | QA |
|
||||||
|
|
||||||
|
## Decisions & Risks
|
||||||
|
- Decision: treat the defect as a board action-contract problem rather than patching the findings page to understand synthetic dashboard IDs, because the board is the component inventing the invalid token.
|
||||||
|
- Decision: source mission-board action identities from `PlatformContextStore.environments()` so downstream routes follow the same canonical environment catalog the rest of the shell uses.
|
||||||
|
- Risk: `DashboardV3Component` still uses placeholder data, so the fix must stay scoped to action semantics and avoid broadening into a data-source rewrite during this iteration.
|
||||||
|
|
||||||
|
## Next Checkpoints
|
||||||
|
- 2026-03-07: land the mission-board action-scope fix and focused tests.
|
||||||
|
- 2026-03-07: replay the affected board actions with live Playwright and continue the authenticated page/action sweep.
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
|
import { PlatformContextStore } from '../context/platform-context.store';
|
||||||
|
import { DashboardV3Component } from '../../features/dashboard-v3/dashboard-v3.component';
|
||||||
|
|
||||||
|
describe('DashboardV3Component', () => {
|
||||||
|
it('builds canonical topology posture targets for environment cards', () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [DashboardV3Component],
|
||||||
|
providers: [
|
||||||
|
provideRouter([]),
|
||||||
|
{
|
||||||
|
provide: PlatformContextStore,
|
||||||
|
useValue: {
|
||||||
|
initialize: () => undefined,
|
||||||
|
regions: () => [
|
||||||
|
{ regionId: 'eu-west', displayName: 'EU West' },
|
||||||
|
{ regionId: 'us-east', displayName: 'US East' },
|
||||||
|
],
|
||||||
|
environments: () => [
|
||||||
|
{
|
||||||
|
environmentId: 'dev',
|
||||||
|
regionId: 'eu-west',
|
||||||
|
environmentType: 'development',
|
||||||
|
displayName: 'Development EU West',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentId: 'prod-us-east',
|
||||||
|
regionId: 'us-east',
|
||||||
|
environmentType: 'production',
|
||||||
|
displayName: 'Production US East',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(DashboardV3Component);
|
||||||
|
const component = fixture.componentInstance;
|
||||||
|
const environment = component.filteredEnvironments()[0];
|
||||||
|
|
||||||
|
expect(component.environmentPostureRoute(environment)).toEqual([
|
||||||
|
'/setup/topology/environments',
|
||||||
|
'dev',
|
||||||
|
'posture',
|
||||||
|
]);
|
||||||
|
expect(component.environmentScopeQuery(environment)).toEqual({
|
||||||
|
region: 'eu-west',
|
||||||
|
regions: 'eu-west',
|
||||||
|
environment: 'dev',
|
||||||
|
environments: 'dev',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds canonical findings scope for downstream pages instead of synthetic dashboard ids', () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [DashboardV3Component],
|
||||||
|
providers: [
|
||||||
|
provideRouter([]),
|
||||||
|
{
|
||||||
|
provide: PlatformContextStore,
|
||||||
|
useValue: {
|
||||||
|
initialize: () => undefined,
|
||||||
|
regions: () => [
|
||||||
|
{ regionId: 'eu-west', displayName: 'EU West' },
|
||||||
|
{ regionId: 'us-east', displayName: 'US East' },
|
||||||
|
],
|
||||||
|
environments: () => [
|
||||||
|
{
|
||||||
|
environmentId: 'dev',
|
||||||
|
regionId: 'eu-west',
|
||||||
|
environmentType: 'development',
|
||||||
|
displayName: 'Development EU West',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
environmentId: 'prod',
|
||||||
|
regionId: 'us-east',
|
||||||
|
environmentType: 'production',
|
||||||
|
displayName: 'Production US East',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(DashboardV3Component);
|
||||||
|
const component = fixture.componentInstance;
|
||||||
|
const environment = component.filteredEnvironments().find((candidate) => candidate.regionId === 'us-east');
|
||||||
|
|
||||||
|
expect(environment).toBeDefined();
|
||||||
|
expect(component.environmentScopeQuery(environment!)).toEqual({
|
||||||
|
region: 'us-east',
|
||||||
|
regions: 'us-east',
|
||||||
|
environment: 'prod',
|
||||||
|
environments: 'prod',
|
||||||
|
});
|
||||||
|
expect(component.environmentScopeQuery(environment!)).not.toEqual(
|
||||||
|
jasmine.objectContaining({ env: 'prod-us-east' }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,14 +9,21 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
signal,
|
|
||||||
computed,
|
computed,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { TitleCasePipe, UpperCasePipe } from '@angular/common';
|
import { TitleCasePipe, UpperCasePipe } from '@angular/common';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
import {
|
||||||
|
PlatformContextEnvironment,
|
||||||
|
PlatformContextStore,
|
||||||
|
} from '../../core/context/platform-context.store';
|
||||||
|
|
||||||
interface EnvironmentCard {
|
interface EnvironmentCard {
|
||||||
id: string;
|
id: string;
|
||||||
|
environmentId: string;
|
||||||
|
regionId: string;
|
||||||
name: string;
|
name: string;
|
||||||
region: string;
|
region: string;
|
||||||
deployStatus: 'healthy' | 'degraded' | 'blocked' | 'unknown';
|
deployStatus: 'healthy' | 'degraded' | 'blocked' | 'unknown';
|
||||||
@@ -66,9 +73,9 @@ interface MissionSummary {
|
|||||||
(change)="onRegionChange($event)"
|
(change)="onRegionChange($event)"
|
||||||
>
|
>
|
||||||
<option value="all">All Regions</option>
|
<option value="all">All Regions</option>
|
||||||
<option value="eu-west">EU West</option>
|
@for (region of availableRegions(); track region.value) {
|
||||||
<option value="us-east">US East</option>
|
<option [value]="region.value">{{ region.label }}</option>
|
||||||
<option value="ap-south">AP South</option>
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -174,10 +181,20 @@ interface MissionSummary {
|
|||||||
<div class="env-card-footer">
|
<div class="env-card-footer">
|
||||||
<span class="last-deployed">Deployed {{ env.lastDeployedAt }}</span>
|
<span class="last-deployed">Deployed {{ env.lastDeployedAt }}</span>
|
||||||
<div class="env-links">
|
<div class="env-links">
|
||||||
<a [routerLink]="['/setup/topology/environments', env.id, 'posture']" class="env-link">
|
<a
|
||||||
|
[routerLink]="environmentPostureRoute(env)"
|
||||||
|
[queryParams]="environmentScopeQuery(env)"
|
||||||
|
queryParamsHandling="merge"
|
||||||
|
class="env-link"
|
||||||
|
>
|
||||||
Detail
|
Detail
|
||||||
</a>
|
</a>
|
||||||
<a [routerLink]="['/security/findings']" [queryParams]="{ env: env.id }" class="env-link">
|
<a
|
||||||
|
[routerLink]="['/security/findings']"
|
||||||
|
[queryParams]="environmentScopeQuery(env)"
|
||||||
|
queryParamsHandling="merge"
|
||||||
|
class="env-link"
|
||||||
|
>
|
||||||
Findings
|
Findings
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,7 +244,13 @@ interface MissionSummary {
|
|||||||
<td>{{ env.birCoverage }}</td>
|
<td>{{ env.birCoverage }}</td>
|
||||||
<td>{{ env.lastDeployedAt }}</td>
|
<td>{{ env.lastDeployedAt }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a [routerLink]="['/setup/topology/environments', env.id, 'posture']">Open</a>
|
<a
|
||||||
|
[routerLink]="environmentPostureRoute(env)"
|
||||||
|
[queryParams]="environmentScopeQuery(env)"
|
||||||
|
queryParamsHandling="merge"
|
||||||
|
>
|
||||||
|
Open
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -996,9 +1019,16 @@ interface MissionSummary {
|
|||||||
`],
|
`],
|
||||||
})
|
})
|
||||||
export class DashboardV3Component {
|
export class DashboardV3Component {
|
||||||
|
private readonly context = inject(PlatformContextStore);
|
||||||
readonly selectedRegion = signal<string>('all');
|
readonly selectedRegion = signal<string>('all');
|
||||||
readonly selectedTimeWindow = signal<string>('24h');
|
readonly selectedTimeWindow = signal<string>('24h');
|
||||||
|
|
||||||
|
private readonly fallbackRegionOptions = [
|
||||||
|
{ value: 'eu-west', label: 'EU West' },
|
||||||
|
{ value: 'us-east', label: 'US East' },
|
||||||
|
{ value: 'apac', label: 'APAC' },
|
||||||
|
];
|
||||||
|
|
||||||
// Placeholder mission summary data
|
// Placeholder mission summary data
|
||||||
readonly summary = signal<MissionSummary>({
|
readonly summary = signal<MissionSummary>({
|
||||||
activePromotions: 3,
|
activePromotions: 3,
|
||||||
@@ -1007,10 +1037,11 @@ export class DashboardV3Component {
|
|||||||
dataIntegrityStatus: 'healthy',
|
dataIntegrityStatus: 'healthy',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Placeholder environments
|
private readonly fallbackEnvironments: EnvironmentCard[] = [
|
||||||
private readonly allEnvironments = signal<EnvironmentCard[]>([
|
|
||||||
{
|
{
|
||||||
id: 'dev-eu-west',
|
id: 'dev-eu-west',
|
||||||
|
environmentId: 'dev',
|
||||||
|
regionId: 'eu-west',
|
||||||
name: 'dev',
|
name: 'dev',
|
||||||
region: 'EU West',
|
region: 'EU West',
|
||||||
deployStatus: 'healthy',
|
deployStatus: 'healthy',
|
||||||
@@ -1023,6 +1054,8 @@ export class DashboardV3Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'stage-eu-west',
|
id: 'stage-eu-west',
|
||||||
|
environmentId: 'stage',
|
||||||
|
regionId: 'eu-west',
|
||||||
name: 'stage',
|
name: 'stage',
|
||||||
region: 'EU West',
|
region: 'EU West',
|
||||||
deployStatus: 'degraded',
|
deployStatus: 'degraded',
|
||||||
@@ -1035,6 +1068,8 @@ export class DashboardV3Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'prod-eu-west',
|
id: 'prod-eu-west',
|
||||||
|
environmentId: 'prod',
|
||||||
|
regionId: 'eu-west',
|
||||||
name: 'prod',
|
name: 'prod',
|
||||||
region: 'EU West',
|
region: 'EU West',
|
||||||
deployStatus: 'healthy',
|
deployStatus: 'healthy',
|
||||||
@@ -1047,6 +1082,8 @@ export class DashboardV3Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dev-us-east',
|
id: 'dev-us-east',
|
||||||
|
environmentId: 'dev',
|
||||||
|
regionId: 'us-east',
|
||||||
name: 'dev',
|
name: 'dev',
|
||||||
region: 'US East',
|
region: 'US East',
|
||||||
deployStatus: 'healthy',
|
deployStatus: 'healthy',
|
||||||
@@ -1059,6 +1096,8 @@ export class DashboardV3Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'prod-us-east',
|
id: 'prod-us-east',
|
||||||
|
environmentId: 'prod',
|
||||||
|
regionId: 'us-east',
|
||||||
name: 'prod',
|
name: 'prod',
|
||||||
region: 'US East',
|
region: 'US East',
|
||||||
deployStatus: 'blocked',
|
deployStatus: 'blocked',
|
||||||
@@ -1069,14 +1108,38 @@ export class DashboardV3Component {
|
|||||||
pendingApprovals: 3,
|
pendingApprovals: 3,
|
||||||
lastDeployedAt: '3d ago',
|
lastDeployedAt: '3d ago',
|
||||||
},
|
},
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.context.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly availableRegions = computed(() => {
|
||||||
|
const regions = this.context.regions();
|
||||||
|
if (regions.length === 0) {
|
||||||
|
return this.fallbackRegionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions.map((region) => ({
|
||||||
|
value: region.regionId,
|
||||||
|
label: region.displayName,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly allEnvironments = computed(() => {
|
||||||
|
const environments = this.context.environments();
|
||||||
|
if (environments.length === 0) {
|
||||||
|
return this.fallbackEnvironments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return environments.map((environment, index) => this.toEnvironmentCard(environment, index));
|
||||||
|
});
|
||||||
|
|
||||||
readonly filteredEnvironments = computed(() => {
|
readonly filteredEnvironments = computed(() => {
|
||||||
const region = this.selectedRegion();
|
const region = this.selectedRegion();
|
||||||
if (region === 'all') return this.allEnvironments();
|
const environments = this.allEnvironments();
|
||||||
return this.allEnvironments().filter(
|
if (region === 'all') return environments;
|
||||||
(e) => e.region.toLowerCase().replace(' ', '-') === region
|
return environments.filter((environment) => environment.regionId === region);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
readonly riskEnvironments = computed(() =>
|
readonly riskEnvironments = computed(() =>
|
||||||
@@ -1144,5 +1207,92 @@ export class DashboardV3Component {
|
|||||||
const select = event.target as HTMLSelectElement;
|
const select = event.target as HTMLSelectElement;
|
||||||
this.selectedTimeWindow.set(select.value);
|
this.selectedTimeWindow.set(select.value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
environmentPostureRoute(env: EnvironmentCard): string[] {
|
||||||
|
return ['/setup/topology/environments', env.environmentId, 'posture'];
|
||||||
|
}
|
||||||
|
|
||||||
|
environmentScopeQuery(env: EnvironmentCard): Record<string, string> {
|
||||||
|
return {
|
||||||
|
region: env.regionId,
|
||||||
|
regions: env.regionId,
|
||||||
|
environment: env.environmentId,
|
||||||
|
environments: env.environmentId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toEnvironmentCard(
|
||||||
|
environment: PlatformContextEnvironment,
|
||||||
|
index: number,
|
||||||
|
): EnvironmentCard {
|
||||||
|
const environmentType = environment.environmentType.toLowerCase();
|
||||||
|
const regionLabel = this.formatRegionLabel(environment.regionId);
|
||||||
|
const statusSeed = this.resolveStatusSeed(environment, index);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `${environment.regionId}:${environment.environmentId}`,
|
||||||
|
environmentId: environment.environmentId,
|
||||||
|
regionId: environment.regionId,
|
||||||
|
name: environment.displayName,
|
||||||
|
region: regionLabel,
|
||||||
|
deployStatus: statusSeed.deployStatus,
|
||||||
|
sbomFreshness: statusSeed.sbomFreshness,
|
||||||
|
critRCount: statusSeed.critRCount,
|
||||||
|
highRCount: statusSeed.highRCount,
|
||||||
|
birCoverage: statusSeed.birCoverage,
|
||||||
|
pendingApprovals: statusSeed.pendingApprovals,
|
||||||
|
lastDeployedAt: statusSeed.lastDeployedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveStatusSeed(
|
||||||
|
environment: PlatformContextEnvironment,
|
||||||
|
index: number,
|
||||||
|
): Omit<EnvironmentCard, 'id' | 'environmentId' | 'regionId' | 'name' | 'region'> {
|
||||||
|
const environmentType = environment.environmentType.toLowerCase();
|
||||||
|
|
||||||
|
if (environmentType === 'development') {
|
||||||
|
return {
|
||||||
|
deployStatus: 'healthy',
|
||||||
|
sbomFreshness: 'fresh',
|
||||||
|
critRCount: 0,
|
||||||
|
highRCount: 1,
|
||||||
|
birCoverage: '3/3',
|
||||||
|
pendingApprovals: 0,
|
||||||
|
lastDeployedAt: '3h ago',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environmentType === 'staging') {
|
||||||
|
return {
|
||||||
|
deployStatus: 'degraded',
|
||||||
|
sbomFreshness: 'stale',
|
||||||
|
critRCount: 1,
|
||||||
|
highRCount: 4,
|
||||||
|
birCoverage: '2/3',
|
||||||
|
pendingApprovals: 2,
|
||||||
|
lastDeployedAt: '6h ago',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockedProduction = environment.regionId === 'us-east' && index % 2 === 0;
|
||||||
|
return {
|
||||||
|
deployStatus: blockedProduction ? 'blocked' : 'healthy',
|
||||||
|
sbomFreshness: blockedProduction ? 'missing' : 'fresh',
|
||||||
|
critRCount: blockedProduction ? 5 : 2,
|
||||||
|
highRCount: blockedProduction ? 12 : 6,
|
||||||
|
birCoverage: blockedProduction ? '1/3' : '3/3',
|
||||||
|
pendingApprovals: blockedProduction ? 3 : 1,
|
||||||
|
lastDeployedAt: blockedProduction ? '3d ago' : '1d ago',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatRegionLabel(regionId: string): string {
|
||||||
|
return regionId
|
||||||
|
.split('-')
|
||||||
|
.map((segment) => segment.toUpperCase() === 'APAC'
|
||||||
|
? 'APAC'
|
||||||
|
: `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user