mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Centralize AI dashboard bundle in store
This commit is contained in:
@@ -80,6 +80,7 @@ Own canonical runtime payload shapes between backend and frontend.
|
||||
and the canonical policy-posture snapshot derived from unified resources, so sensitivity, routing, and redaction counts stay owned by the same AI summary contract instead of being reconstructed as a page-local governance rollup
|
||||
and the resource-intelligence payload carried by the drawer AI card, so the governed posture snapshot remains visible on the resource-detail surface without introducing a separate posture endpoint
|
||||
and the learned-correlation payload loaded into the shared AI intelligence store, so the Patrol intelligence page and the AI summary page consume the same governed correlation slice instead of each page fetching its own copy
|
||||
and the shared dashboard-load bundle inside `frontend-modern/src/stores/aiIntelligence.ts`, so the page orchestration stays on the store-owned bundle instead of enumerating the AI fetches inline
|
||||
8. Route frontend API-client parsed error propagation, API-error-status fallback handling, allowed-status handling, custom status-specific error handling, command-trigger success envelope handling, shared response parsing pipelines, missing-resource lookup handling, metadata CRUD routing, stream event consumption, response status, collection normalization, scalar payload coercion, and structured error normalization through canonical shared helpers under `frontend-modern/src/api/`
|
||||
9. Add or change API token scope, assignment, and revocation presentation through `frontend-modern/src/components/Settings/APITokenManager.tsx`
|
||||
10. Add or change infrastructure operations token generation, lookup, assignment, and reporting/install presentation through `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx`
|
||||
@@ -184,6 +185,9 @@ through the shared `frontend-modern/src/stores/aiIntelligence.ts` store for
|
||||
the Patrol intelligence page and the AI summary page, so the
|
||||
learned-correlation list is governed by the same API contract that backs the
|
||||
resource drawer's graph evidence instead of being fetched as page-local state.
|
||||
That store now also owns the dashboard load bundle used by the Patrol page,
|
||||
so the page refresh path stays aligned on one store-owned orchestration layer
|
||||
instead of re-encoding the AI bundle inline.
|
||||
The unified action, lifecycle, and export audit reads now also clamp oversized
|
||||
`limit` requests to the governed maximum of `1000`, so the control-plane audit
|
||||
surface stays bounded even when callers ask for arbitrarily large history
|
||||
|
||||
@@ -104,6 +104,10 @@ from the canonical AI correlations endpoint through the shared
|
||||
`frontend-modern/src/stores/aiIntelligence.ts` store, so the global summary
|
||||
and the resource drawer both reflect the same learned edge evidence instead
|
||||
of each page fetching its own copy.
|
||||
That same store now owns the Patrol dashboard load bundle as well, so the
|
||||
page refresh path stays aligned on a single orchestrated AI bundle instead of
|
||||
repeating the individual summary, findings, approval, and correlation fetches
|
||||
inline.
|
||||
The Patrol page and resource drawer now also share the canonical
|
||||
`frontend-modern/src/utils/resourceChangePresentation.ts` formatter so
|
||||
recent-change kind and headline wording stays aligned wherever the canonical
|
||||
|
||||
@@ -761,14 +761,7 @@ export function AIIntelligence() {
|
||||
async function loadAllData() {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await Promise.all([
|
||||
aiIntelligenceStore.loadIntelligenceSummary(),
|
||||
aiIntelligenceStore.loadFindings(),
|
||||
aiIntelligenceStore.loadCircuitBreakerStatus(),
|
||||
aiIntelligenceStore.loadPendingApprovals(),
|
||||
aiIntelligenceStore.loadCorrelations(),
|
||||
refetchPatrolStatus(),
|
||||
]);
|
||||
await Promise.all([aiIntelligenceStore.loadDashboardData(), refetchPatrolStatus()]);
|
||||
// Trigger refresh of patrol status bar
|
||||
setActivityRefreshTrigger((prev) => prev + 1);
|
||||
} finally {
|
||||
|
||||
@@ -144,8 +144,8 @@ vi.mock('@/stores/notifications', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/aiIntelligence', () => ({
|
||||
aiIntelligenceStore: {
|
||||
vi.mock('@/stores/aiIntelligence', () => {
|
||||
const store = {
|
||||
findings: [],
|
||||
loadFindings: vi.fn().mockResolvedValue(undefined),
|
||||
loadIntelligenceSummary: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -157,14 +157,27 @@ vi.mock('@/stores/aiIntelligence', () => ({
|
||||
setCorrelationsState(response);
|
||||
return response;
|
||||
}),
|
||||
loadDashboardData: vi.fn().mockImplementation(async () => {
|
||||
await Promise.all([
|
||||
store.loadFindings(),
|
||||
store.loadIntelligenceSummary(),
|
||||
store.loadCircuitBreakerStatus(),
|
||||
store.loadPendingApprovals(),
|
||||
store.loadCorrelations(),
|
||||
]);
|
||||
}),
|
||||
get intelligenceSummary() {
|
||||
return intelligenceState.summary;
|
||||
},
|
||||
get correlations() {
|
||||
return correlationsState();
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
aiIntelligenceStore: store,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@/components/AI/FindingsPanel', () => ({
|
||||
FindingsPanel: (props: Record<string, unknown>) => {
|
||||
|
||||
@@ -166,6 +166,48 @@ describe('aiIntelligenceStore', () => {
|
||||
expect(aiIntelligenceStore.correlations?.correlations).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('loads the canonical AI dashboard bundle', async () => {
|
||||
vi.mocked(AIAPI.getIntelligenceSummary).mockResolvedValueOnce({
|
||||
timestamp: '2026-03-01T00:00:00Z',
|
||||
overall_health: {
|
||||
score: 87,
|
||||
grade: 'B',
|
||||
trend: 'stable',
|
||||
factors: [],
|
||||
prediction: 'Stable',
|
||||
},
|
||||
findings_count: {
|
||||
critical: 0,
|
||||
warning: 0,
|
||||
watch: 0,
|
||||
info: 0,
|
||||
total: 0,
|
||||
},
|
||||
predictions_count: 0,
|
||||
recent_changes_count: 0,
|
||||
recent_changes: [],
|
||||
learning: {
|
||||
resources_with_knowledge: 0,
|
||||
total_notes: 0,
|
||||
resources_with_baselines: 0,
|
||||
patterns_detected: 0,
|
||||
correlations_learned: 0,
|
||||
incidents_tracked: 0,
|
||||
},
|
||||
});
|
||||
vi.mocked(AIAPI.getCorrelations).mockResolvedValueOnce({
|
||||
correlations: [],
|
||||
count: 0,
|
||||
});
|
||||
|
||||
await aiIntelligenceStore.loadDashboardData();
|
||||
|
||||
expect(AIAPI.getIntelligenceSummary).toHaveBeenCalledTimes(1);
|
||||
expect(AIAPI.getCorrelations).toHaveBeenCalledTimes(1);
|
||||
expect(aiIntelligenceStore.intelligenceSummary?.findings_count.total).toBe(0);
|
||||
expect(aiIntelligenceStore.correlations?.count).toBe(0);
|
||||
});
|
||||
|
||||
it('treats queued fixes without a live approval as findings needing attention', async () => {
|
||||
vi.mocked(AIAPI.getUnifiedFindings).mockResolvedValueOnce({
|
||||
findings: [
|
||||
|
||||
@@ -522,6 +522,16 @@ export const aiIntelligenceStore = {
|
||||
}
|
||||
},
|
||||
|
||||
async loadDashboardData() {
|
||||
await Promise.all([
|
||||
this.loadIntelligenceSummary(),
|
||||
this.loadFindings(),
|
||||
this.loadCircuitBreakerStatus(),
|
||||
this.loadPendingApprovals(),
|
||||
this.loadCorrelations(),
|
||||
]);
|
||||
},
|
||||
|
||||
async loadIntelligenceSummary() {
|
||||
try {
|
||||
const summary = await AIAPI.getIntelligenceSummary();
|
||||
@@ -546,12 +556,8 @@ export const aiIntelligenceStore = {
|
||||
// Initialize - load all data
|
||||
async initialize() {
|
||||
await Promise.all([
|
||||
this.loadIntelligenceSummary(),
|
||||
this.loadFindings(),
|
||||
this.loadDashboardData(),
|
||||
this.loadRemediationPlans(),
|
||||
this.loadCircuitBreakerStatus(),
|
||||
this.loadPendingApprovals(),
|
||||
this.loadCorrelations(),
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user