Align mission control with shared context scope

This commit is contained in:
master
2026-03-10 13:13:57 +02:00
parent fc7aaf4d37
commit 0e764da736
5 changed files with 172 additions and 135 deletions

View File

@@ -6,33 +6,39 @@ import { DashboardV3Component } from '../../features/dashboard-v3/dashboard-v3.c
describe('DashboardV3Component', () => {
it('builds canonical topology posture targets for environment cards', () => {
const contextStore = {
initialize: () => undefined,
selectedRegions: () => [],
timeWindow: () => '24h',
setRegions: () => undefined,
setTimeWindow: () => 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',
},
],
};
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',
},
],
},
useValue: contextStore,
},
],
});
@@ -55,33 +61,39 @@ describe('DashboardV3Component', () => {
});
it('builds canonical findings scope for downstream pages instead of synthetic dashboard ids', () => {
const contextStore = {
initialize: () => undefined,
selectedRegions: () => [],
timeWindow: () => '24h',
setRegions: () => undefined,
setTimeWindow: () => 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',
},
],
};
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',
},
],
},
useValue: contextStore,
},
],
});
@@ -101,4 +113,50 @@ describe('DashboardV3Component', () => {
jasmine.objectContaining({ env: 'prod-us-east' }),
);
});
it('filters mission board environments from the active context scope', () => {
const contextStore = {
initialize: () => undefined,
selectedRegions: () => ['us-east'],
timeWindow: () => '7d',
regions: () => [
{ regionId: 'eu-west', displayName: 'EU West' },
{ regionId: 'us-east', displayName: 'US East' },
],
environments: () => [
{
environmentId: 'stage',
regionId: 'eu-west',
environmentType: 'staging',
displayName: 'Staging EU West',
},
{
environmentId: 'stage',
regionId: 'us-east',
environmentType: 'staging',
displayName: 'Staging US East',
},
],
};
TestBed.configureTestingModule({
imports: [DashboardV3Component],
providers: [
provideRouter([]),
{ provide: PlatformContextStore, useValue: contextStore },
],
});
const fixture = TestBed.createComponent(DashboardV3Component);
const component = fixture.componentInstance;
expect(component.filteredEnvironments().map((environment) => environment.regionId)).toEqual(['us-east']);
expect(component.filteredEnvironments().map((environment) => environment.environmentId)).toEqual(['stage']);
expect(component.environmentScopeQuery(component.filteredEnvironments()[0])).toEqual({
region: 'us-east',
regions: 'us-east',
environment: 'stage',
environments: 'stage',
});
});
});

View File

@@ -23,6 +23,10 @@ describe('Mission scope-preserving links', () => {
provide: PlatformContextStore,
useValue: {
initialize: () => undefined,
selectedRegions: () => ['us-east'],
timeWindow: () => '7d',
setRegions: () => undefined,
setTimeWindow: () => undefined,
regions: () => [
{ regionId: 'eu-west', displayName: 'EU West' },
{ regionId: 'us-east', displayName: 'US East' },
@@ -50,7 +54,7 @@ describe('Mission scope-preserving links', () => {
it('marks every mission alerts link to merge the active query scope', () => {
const links = routerLinksFor(MissionAlertsPageComponent);
expect(links.length).toBe(3);
expect(links.length).toBeGreaterThanOrEqual(3);
expect(links.every((link) => link.queryParamsHandling === 'merge')).toBeTrue();
});

View File

@@ -63,37 +63,6 @@ interface MissionSummary {
<p class="board-subtitle">Mission board for release health across regions and environments</p>
</div>
<div class="header-controls">
<div class="control-group">
<label class="control-label" for="regionFilter">Region</label>
<select
id="regionFilter"
class="control-select"
[value]="selectedRegion()"
(change)="onRegionChange($event)"
>
<option value="all">All Regions</option>
@for (region of availableRegions(); track region.value) {
<option [value]="region.value">{{ region.label }}</option>
}
</select>
</div>
<div class="control-group">
<label class="control-label" for="timeWindow">Time Window</label>
<select
id="timeWindow"
class="control-select"
[value]="selectedTimeWindow()"
(change)="onTimeWindowChange($event)"
>
<option value="1h">Last 1h</option>
<option value="24h">Last 24h</option>
<option value="7d">Last 7d</option>
<option value="30d">Last 30d</option>
</select>
</div>
</div>
</header>
<!-- Mission Summary Strip -->
@@ -449,34 +418,6 @@ interface MissionSummary {
margin: 0.25rem 0 0;
}
.header-controls {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.control-label {
font-size: 0.75rem;
color: var(--color-text-secondary);
font-weight: var(--font-weight-medium);
}
.control-select {
padding: 0.4rem 0.75rem;
border: 1px solid var(--color-border-primary);
border-radius: var(--radius-sm);
background: var(--color-surface-primary);
color: var(--color-text-primary);
font-size: 0.9rem;
min-width: 140px;
}
/* Mission Summary Strip */
.mission-summary {
display: grid;
@@ -1020,14 +961,6 @@ interface MissionSummary {
})
export class DashboardV3Component {
private readonly context = inject(PlatformContextStore);
readonly selectedRegion = signal<string>('all');
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
readonly summary = signal<MissionSummary>({
@@ -1114,18 +1047,6 @@ export class DashboardV3Component {
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) {
@@ -1136,10 +1057,14 @@ export class DashboardV3Component {
});
readonly filteredEnvironments = computed(() => {
const region = this.selectedRegion();
const environments = this.allEnvironments();
if (region === 'all') return environments;
return environments.filter((environment) => environment.regionId === region);
const selectedRegions = this.context.selectedRegions();
if (selectedRegions.length === 0) {
return environments;
}
const allowed = new Set(selectedRegions);
return environments.filter((environment) => allowed.has(environment.regionId));
});
readonly riskEnvironments = computed(() =>
@@ -1198,16 +1123,6 @@ export class DashboardV3Component {
},
]);
onRegionChange(event: Event): void {
const select = event.target as HTMLSelectElement;
this.selectedRegion.set(select.value);
}
onTimeWindowChange(event: Event): void {
const select = event.target as HTMLSelectElement;
this.selectedTimeWindow.set(select.value);
}
environmentPostureRoute(env: EnvironmentCard): string[] {
return ['/setup/topology/environments', env.environmentId, 'posture'];
}

View File

@@ -19,6 +19,7 @@ import { RouterLink } from '@angular/router';
<a
routerLink="/setup/trust-signing/watchlist/alerts"
[queryParams]="{ alertId: 'alert-001', returnTo: '/mission-control/alerts', scope: 'tenant', tab: 'alerts' }"
queryParamsHandling="merge"
>
Identity watchlist alert requires signer review
</a>