flock-core 0.5.0b65__py3-none-any.whl → 0.5.0b71__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/cli.py +74 -2
- flock/engines/dspy_engine.py +41 -5
- flock/examples.py +4 -1
- flock/frontend/README.md +15 -1
- flock/frontend/package-lock.json +2 -2
- flock/frontend/package.json +1 -1
- flock/frontend/src/App.tsx +74 -6
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +4 -5
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +7 -3
- flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
- flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
- flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
- flock/frontend/src/components/filters/FilterPills.module.css +186 -45
- flock/frontend/src/components/filters/FilterPills.test.tsx +115 -99
- flock/frontend/src/components/filters/FilterPills.tsx +120 -44
- flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
- flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
- flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
- flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
- flock/frontend/src/components/filters/TagFilter.tsx +21 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +24 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +6 -1
- flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +24 -0
- flock/frontend/src/components/layout/DashboardLayout.css +13 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +8 -24
- flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +460 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +7 -1
- flock/frontend/src/components/modules/registerModules.ts +9 -10
- flock/frontend/src/hooks/useModules.ts +11 -1
- flock/frontend/src/services/api.ts +140 -0
- flock/frontend/src/services/indexeddb.ts +56 -2
- flock/frontend/src/services/websocket.ts +129 -0
- flock/frontend/src/store/filterStore.test.ts +105 -185
- flock/frontend/src/store/filterStore.ts +173 -26
- flock/frontend/src/store/graphStore.test.ts +19 -0
- flock/frontend/src/store/graphStore.ts +166 -27
- flock/frontend/src/types/filters.ts +34 -1
- flock/frontend/src/types/graph.ts +7 -0
- flock/frontend/src/utils/artifacts.ts +24 -0
- flock/orchestrator.py +23 -1
- flock/service.py +146 -9
- flock/store.py +971 -24
- {flock_core-0.5.0b65.dist-info → flock_core-0.5.0b71.dist-info}/METADATA +26 -1
- {flock_core-0.5.0b65.dist-info → flock_core-0.5.0b71.dist-info}/RECORD +50 -43
- flock/frontend/src/components/filters/FilterBar.module.css +0 -29
- flock/frontend/src/components/filters/FilterBar.test.tsx +0 -133
- flock/frontend/src/components/filters/FilterBar.tsx +0 -33
- flock/frontend/src/components/modules/EventLogModule.test.tsx +0 -401
- flock/frontend/src/components/modules/EventLogModule.tsx +0 -396
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +0 -17
- {flock_core-0.5.0b65.dist-info → flock_core-0.5.0b71.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b65.dist-info → flock_core-0.5.0b71.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b65.dist-info → flock_core-0.5.0b71.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,242 +1,162 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { useFilterStore } from './filterStore';
|
|
3
|
+
import type { FilterFacets, FilterSnapshot } from '../types/filters';
|
|
3
4
|
|
|
4
5
|
describe('filterStore', () => {
|
|
5
6
|
beforeEach(() => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
useFilterStore.setState({
|
|
8
|
+
correlationId: null,
|
|
9
|
+
timeRange: { preset: 'last10min' },
|
|
10
|
+
selectedArtifactTypes: [],
|
|
11
|
+
selectedProducers: [],
|
|
12
|
+
selectedTags: [],
|
|
13
|
+
selectedVisibility: [],
|
|
14
|
+
availableCorrelationIds: [],
|
|
15
|
+
availableArtifactTypes: [],
|
|
16
|
+
availableProducers: [],
|
|
17
|
+
availableTags: [],
|
|
18
|
+
availableVisibility: [],
|
|
19
|
+
summary: null,
|
|
20
|
+
savedFilters: [],
|
|
21
|
+
});
|
|
9
22
|
});
|
|
10
23
|
|
|
11
|
-
describe('
|
|
12
|
-
it('should have
|
|
24
|
+
describe('initial state', () => {
|
|
25
|
+
it('should have defaults set', () => {
|
|
13
26
|
const state = useFilterStore.getState();
|
|
14
27
|
expect(state.correlationId).toBeNull();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should have default time range of last 10 minutes', () => {
|
|
18
|
-
const state = useFilterStore.getState();
|
|
19
28
|
expect(state.timeRange).toEqual({ preset: 'last10min' });
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
it('should have empty available correlation IDs', () => {
|
|
23
|
-
const state = useFilterStore.getState();
|
|
29
|
+
expect(state.selectedArtifactTypes).toEqual([]);
|
|
24
30
|
expect(state.availableCorrelationIds).toEqual([]);
|
|
25
31
|
});
|
|
26
32
|
});
|
|
27
33
|
|
|
28
|
-
describe('
|
|
29
|
-
it('should
|
|
30
|
-
const store = useFilterStore.getState();
|
|
31
|
-
store.setCorrelationId('test-correlation-id');
|
|
32
|
-
|
|
33
|
-
expect(useFilterStore.getState().correlationId).toBe('test-correlation-id');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should clear correlation ID when set to null', () => {
|
|
37
|
-
const store = useFilterStore.getState();
|
|
38
|
-
store.setCorrelationId('test-id');
|
|
39
|
-
store.setCorrelationId(null);
|
|
40
|
-
|
|
41
|
-
expect(useFilterStore.getState().correlationId).toBeNull();
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe('setTimeRange', () => {
|
|
46
|
-
it('should set time range preset', () => {
|
|
34
|
+
describe('mutators', () => {
|
|
35
|
+
it('should update correlation id and time range', () => {
|
|
47
36
|
const store = useFilterStore.getState();
|
|
37
|
+
store.setCorrelationId('abc');
|
|
48
38
|
store.setTimeRange({ preset: 'last5min' });
|
|
49
|
-
|
|
39
|
+
expect(useFilterStore.getState().correlationId).toBe('abc');
|
|
50
40
|
expect(useFilterStore.getState().timeRange).toEqual({ preset: 'last5min' });
|
|
51
41
|
});
|
|
52
42
|
|
|
53
|
-
it('should
|
|
43
|
+
it('should update multi-select filters', () => {
|
|
54
44
|
const store = useFilterStore.getState();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
60
|
-
store.setTimeRange(customRange);
|
|
45
|
+
store.setArtifactTypes(['TypeB', 'TypeA']);
|
|
46
|
+
store.setProducers(['b', 'a']);
|
|
47
|
+
store.setTags(['beta']);
|
|
48
|
+
store.setVisibility(['Private', 'Public']);
|
|
61
49
|
|
|
62
|
-
|
|
50
|
+
const state = useFilterStore.getState();
|
|
51
|
+
expect(state.selectedArtifactTypes).toEqual(['TypeA', 'TypeB']);
|
|
52
|
+
expect(state.selectedProducers).toEqual(['a', 'b']);
|
|
53
|
+
expect(state.selectedTags).toEqual(['beta']);
|
|
54
|
+
expect(state.selectedVisibility).toEqual(['Private', 'Public']);
|
|
63
55
|
});
|
|
64
|
-
});
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
it('should reset all filters to defaults', () => {
|
|
57
|
+
it('should clear filters to defaults', () => {
|
|
68
58
|
const store = useFilterStore.getState();
|
|
69
|
-
store.setCorrelationId('
|
|
70
|
-
store.
|
|
71
|
-
|
|
59
|
+
store.setCorrelationId('abc');
|
|
60
|
+
store.setArtifactTypes(['TypeA']);
|
|
61
|
+
store.setTags(['x']);
|
|
72
62
|
store.clearFilters();
|
|
73
63
|
|
|
74
64
|
const state = useFilterStore.getState();
|
|
75
65
|
expect(state.correlationId).toBeNull();
|
|
66
|
+
expect(state.selectedArtifactTypes).toEqual([]);
|
|
67
|
+
expect(state.selectedTags).toEqual([]);
|
|
76
68
|
expect(state.timeRange).toEqual({ preset: 'last10min' });
|
|
77
69
|
});
|
|
78
|
-
});
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
it('should update available correlation IDs with metadata', () => {
|
|
82
|
-
const store = useFilterStore.getState();
|
|
71
|
+
it('should update available correlation ids', () => {
|
|
83
72
|
const now = Date.now();
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
first_seen: now -
|
|
88
|
-
artifact_count:
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
correlation_id: 'def456',
|
|
93
|
-
first_seen: now - 60000,
|
|
94
|
-
artifact_count: 3,
|
|
95
|
-
run_count: 1,
|
|
96
|
-
},
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
store.updateAvailableCorrelationIds(metadata);
|
|
100
|
-
|
|
101
|
-
const state = useFilterStore.getState();
|
|
102
|
-
// Should be sorted by most recent first
|
|
103
|
-
expect(state.availableCorrelationIds).toHaveLength(2);
|
|
104
|
-
expect(state.availableCorrelationIds[0]?.correlation_id).toBe('def456');
|
|
105
|
-
expect(state.availableCorrelationIds[1]?.correlation_id).toBe('abc123');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should limit to 50 correlation IDs', () => {
|
|
109
|
-
const store = useFilterStore.getState();
|
|
110
|
-
const metadata = Array.from({ length: 100 }, (_, i) => ({
|
|
111
|
-
correlation_id: `id-${i}`,
|
|
112
|
-
first_seen: Date.now() - i * 1000,
|
|
113
|
-
artifact_count: i,
|
|
114
|
-
run_count: 1,
|
|
115
|
-
}));
|
|
116
|
-
|
|
117
|
-
store.updateAvailableCorrelationIds(metadata);
|
|
118
|
-
|
|
73
|
+
useFilterStore
|
|
74
|
+
.getState()
|
|
75
|
+
.updateAvailableCorrelationIds([
|
|
76
|
+
{ correlation_id: 'old', first_seen: now - 10_000, artifact_count: 1, run_count: 1 },
|
|
77
|
+
{ correlation_id: 'new', first_seen: now - 1_000, artifact_count: 2, run_count: 2 },
|
|
78
|
+
]);
|
|
119
79
|
const state = useFilterStore.getState();
|
|
120
|
-
expect(state.availableCorrelationIds).
|
|
80
|
+
expect(state.availableCorrelationIds.map((m) => m.correlation_id)).toEqual(['new', 'old']);
|
|
121
81
|
});
|
|
122
82
|
|
|
123
|
-
it('should
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
run_count: 1,
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
correlation_id: 'newest',
|
|
135
|
-
first_seen: now - 10000,
|
|
136
|
-
artifact_count: 2,
|
|
137
|
-
run_count: 1,
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
correlation_id: 'middle',
|
|
141
|
-
first_seen: now - 120000,
|
|
142
|
-
artifact_count: 3,
|
|
143
|
-
run_count: 1,
|
|
144
|
-
},
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
store.updateAvailableCorrelationIds(metadata);
|
|
148
|
-
|
|
83
|
+
it('should update available facets', () => {
|
|
84
|
+
const facets: FilterFacets = {
|
|
85
|
+
artifactTypes: ['TypeB', 'TypeA'],
|
|
86
|
+
producers: ['b', 'a'],
|
|
87
|
+
tags: ['beta', 'alpha'],
|
|
88
|
+
visibilities: ['Private', 'Public'],
|
|
89
|
+
};
|
|
90
|
+
useFilterStore.getState().updateAvailableFacets(facets);
|
|
149
91
|
const state = useFilterStore.getState();
|
|
150
|
-
expect(state.
|
|
151
|
-
expect(state.
|
|
152
|
-
expect(state.
|
|
92
|
+
expect(state.availableArtifactTypes).toEqual(['TypeA', 'TypeB']);
|
|
93
|
+
expect(state.availableProducers).toEqual(['a', 'b']);
|
|
94
|
+
expect(state.availableTags).toEqual(['alpha', 'beta']);
|
|
95
|
+
expect(state.availableVisibility).toEqual(['Private', 'Public']);
|
|
153
96
|
});
|
|
154
97
|
});
|
|
155
98
|
|
|
156
|
-
describe('
|
|
157
|
-
it('should return
|
|
158
|
-
const store = useFilterStore.getState();
|
|
159
|
-
const activeFilters = store.getActiveFilters();
|
|
160
|
-
expect(activeFilters).toEqual([]);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('should return correlation ID filter when set', () => {
|
|
164
|
-
const store = useFilterStore.getState();
|
|
165
|
-
store.setCorrelationId('test-123');
|
|
166
|
-
|
|
167
|
-
const activeFilters = store.getActiveFilters();
|
|
168
|
-
expect(activeFilters).toHaveLength(1);
|
|
169
|
-
expect(activeFilters[0]).toEqual({
|
|
170
|
-
type: 'correlationId',
|
|
171
|
-
value: 'test-123',
|
|
172
|
-
label: 'Correlation ID: test-123',
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should return time range filter when not default', () => {
|
|
99
|
+
describe('active filters', () => {
|
|
100
|
+
it('should return active filters across selections', () => {
|
|
177
101
|
const store = useFilterStore.getState();
|
|
102
|
+
store.setCorrelationId('abc');
|
|
178
103
|
store.setTimeRange({ preset: 'last5min' });
|
|
104
|
+
store.setArtifactTypes(['TypeA']);
|
|
105
|
+
store.setProducers(['agent1']);
|
|
106
|
+
store.setTags(['urgent']);
|
|
107
|
+
store.setVisibility(['Public']);
|
|
179
108
|
|
|
180
|
-
const
|
|
181
|
-
expect(
|
|
182
|
-
expect(
|
|
183
|
-
type: 'timeRange',
|
|
184
|
-
value: { preset: 'last5min' },
|
|
185
|
-
label: 'Time: Last 5 min',
|
|
186
|
-
});
|
|
109
|
+
const active = store.getActiveFilters();
|
|
110
|
+
expect(active).toHaveLength(6);
|
|
111
|
+
expect(active.map((f) => f.type)).toContain('visibility');
|
|
187
112
|
});
|
|
188
113
|
|
|
189
|
-
it('should
|
|
114
|
+
it('should remove individual filters', () => {
|
|
190
115
|
const store = useFilterStore.getState();
|
|
191
|
-
|
|
192
|
-
const end = Date.now();
|
|
193
|
-
store.setTimeRange({ preset: 'custom', start, end });
|
|
194
|
-
|
|
195
|
-
const activeFilters = store.getActiveFilters();
|
|
196
|
-
expect(activeFilters).toHaveLength(1);
|
|
197
|
-
expect(activeFilters[0]?.type).toBe('timeRange');
|
|
198
|
-
expect(activeFilters[0]?.label).toMatch(/^Time: /);
|
|
199
|
-
});
|
|
116
|
+
store.setArtifactTypes(['TypeA', 'TypeB']);
|
|
200
117
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
store.setCorrelationId('test-123');
|
|
204
|
-
store.setTimeRange({ preset: 'last1hour' });
|
|
118
|
+
const [firstFilter] = store.getActiveFilters().filter((f) => f.type === 'artifactTypes');
|
|
119
|
+
expect(firstFilter?.value).toBe('TypeA');
|
|
205
120
|
|
|
206
|
-
|
|
207
|
-
expect(
|
|
121
|
+
useFilterStore.getState().removeFilter(firstFilter!);
|
|
122
|
+
expect(useFilterStore.getState().selectedArtifactTypes).toEqual(['TypeB']);
|
|
208
123
|
});
|
|
209
124
|
});
|
|
210
125
|
|
|
211
|
-
describe('
|
|
212
|
-
it('should
|
|
126
|
+
describe('snapshots', () => {
|
|
127
|
+
it('should export and reapply filter snapshots', () => {
|
|
213
128
|
const store = useFilterStore.getState();
|
|
214
|
-
store.setCorrelationId('
|
|
129
|
+
store.setCorrelationId('snapshot');
|
|
130
|
+
store.setArtifactTypes(['TypeA']);
|
|
215
131
|
|
|
216
|
-
store.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('should reset time range filter to default', () => {
|
|
222
|
-
const store = useFilterStore.getState();
|
|
223
|
-
store.setTimeRange({ preset: 'last1hour' });
|
|
224
|
-
|
|
225
|
-
store.removeFilter('timeRange');
|
|
226
|
-
|
|
227
|
-
expect(useFilterStore.getState().timeRange).toEqual({ preset: 'last10min' });
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should not affect other filters', () => {
|
|
231
|
-
const store = useFilterStore.getState();
|
|
232
|
-
store.setCorrelationId('test-123');
|
|
233
|
-
store.setTimeRange({ preset: 'last1hour' });
|
|
234
|
-
|
|
235
|
-
store.removeFilter('correlationId');
|
|
132
|
+
const snapshot = store.getFilterSnapshot();
|
|
133
|
+
useFilterStore.getState().clearFilters();
|
|
134
|
+
useFilterStore.getState().applyFilterSnapshot(snapshot);
|
|
236
135
|
|
|
237
136
|
const state = useFilterStore.getState();
|
|
238
|
-
expect(state.correlationId).
|
|
239
|
-
expect(state.
|
|
137
|
+
expect(state.correlationId).toBe('snapshot');
|
|
138
|
+
expect(state.selectedArtifactTypes).toEqual(['TypeA']);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should manage saved filters list', () => {
|
|
142
|
+
const snapshot: FilterSnapshot = {
|
|
143
|
+
correlationId: null,
|
|
144
|
+
timeRange: { preset: 'last10min' },
|
|
145
|
+
artifactTypes: [],
|
|
146
|
+
producers: [],
|
|
147
|
+
tags: [],
|
|
148
|
+
visibility: [],
|
|
149
|
+
};
|
|
150
|
+
useFilterStore.getState().addSavedFilter({
|
|
151
|
+
filter_id: '1',
|
|
152
|
+
name: 'Default',
|
|
153
|
+
created_at: Date.now(),
|
|
154
|
+
filters: snapshot,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(useFilterStore.getState().savedFilters).toHaveLength(1);
|
|
158
|
+
useFilterStore.getState().removeSavedFilter('1');
|
|
159
|
+
expect(useFilterStore.getState().savedFilters).toHaveLength(0);
|
|
240
160
|
});
|
|
241
161
|
});
|
|
242
162
|
});
|
|
@@ -1,72 +1,169 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import { devtools } from 'zustand/middleware';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
TimeRange,
|
|
5
|
+
CorrelationIdMetadata,
|
|
6
|
+
FilterFacets,
|
|
7
|
+
ArtifactSummary,
|
|
8
|
+
SavedFilterMeta,
|
|
9
|
+
FilterSnapshot,
|
|
10
|
+
} from '../types/filters';
|
|
11
|
+
|
|
12
|
+
export type ActiveFilterType =
|
|
13
|
+
| 'correlationId'
|
|
14
|
+
| 'timeRange'
|
|
15
|
+
| 'artifactTypes'
|
|
16
|
+
| 'producers'
|
|
17
|
+
| 'tags'
|
|
18
|
+
| 'visibility';
|
|
4
19
|
|
|
5
20
|
export interface ActiveFilter {
|
|
6
|
-
type:
|
|
21
|
+
type: ActiveFilterType;
|
|
7
22
|
value: string | TimeRange;
|
|
8
23
|
label: string;
|
|
9
24
|
}
|
|
10
25
|
|
|
11
26
|
interface FilterState {
|
|
12
|
-
// Active filters
|
|
13
27
|
correlationId: string | null;
|
|
14
28
|
timeRange: TimeRange;
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
selectedArtifactTypes: string[];
|
|
30
|
+
selectedProducers: string[];
|
|
31
|
+
selectedTags: string[];
|
|
32
|
+
selectedVisibility: string[];
|
|
17
33
|
availableCorrelationIds: CorrelationIdMetadata[];
|
|
34
|
+
availableArtifactTypes: string[];
|
|
35
|
+
availableProducers: string[];
|
|
36
|
+
availableTags: string[];
|
|
37
|
+
availableVisibility: string[];
|
|
38
|
+
summary: ArtifactSummary | null;
|
|
39
|
+
savedFilters: SavedFilterMeta[];
|
|
18
40
|
|
|
19
|
-
// Actions
|
|
20
41
|
setCorrelationId: (id: string | null) => void;
|
|
21
42
|
setTimeRange: (range: TimeRange) => void;
|
|
43
|
+
setArtifactTypes: (types: string[]) => void;
|
|
44
|
+
setProducers: (producers: string[]) => void;
|
|
45
|
+
setTags: (tags: string[]) => void;
|
|
46
|
+
setVisibility: (visibility: string[]) => void;
|
|
22
47
|
clearFilters: () => void;
|
|
23
48
|
|
|
24
|
-
// Update available IDs
|
|
25
49
|
updateAvailableCorrelationIds: (metadata: CorrelationIdMetadata[]) => void;
|
|
50
|
+
updateAvailableFacets: (facets: FilterFacets) => void;
|
|
51
|
+
setSummary: (summary: ArtifactSummary | null) => void;
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
setSavedFilters: (filters: SavedFilterMeta[]) => void;
|
|
54
|
+
addSavedFilter: (filter: SavedFilterMeta) => void;
|
|
55
|
+
removeSavedFilter: (filterId: string) => void;
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
removeFilter: (
|
|
57
|
+
getActiveFilters: () => ActiveFilter[];
|
|
58
|
+
removeFilter: (filter: ActiveFilter) => void;
|
|
59
|
+
getFilterSnapshot: () => FilterSnapshot;
|
|
60
|
+
applyFilterSnapshot: (snapshot: FilterSnapshot) => void;
|
|
32
61
|
}
|
|
33
62
|
|
|
34
|
-
const
|
|
63
|
+
const defaultTimeRange: TimeRange = { preset: 'last10min' };
|
|
64
|
+
|
|
65
|
+
export const formatTimeRange = (range: TimeRange): string => {
|
|
35
66
|
if (range.preset === 'last5min') return 'Last 5 min';
|
|
36
67
|
if (range.preset === 'last10min') return 'Last 10 min';
|
|
37
68
|
if (range.preset === 'last1hour') return 'Last hour';
|
|
69
|
+
if (range.preset === 'all') return 'All time';
|
|
38
70
|
if (range.preset === 'custom' && range.start && range.end) {
|
|
39
71
|
const startDate = new Date(range.start).toLocaleString();
|
|
40
72
|
const endDate = new Date(range.end).toLocaleString();
|
|
41
73
|
return `${startDate} - ${endDate}`;
|
|
42
74
|
}
|
|
43
|
-
|
|
75
|
+
if (range.preset === 'custom') {
|
|
76
|
+
return 'Custom range';
|
|
77
|
+
}
|
|
78
|
+
return 'Last 10 min';
|
|
44
79
|
};
|
|
45
80
|
|
|
81
|
+
const uniqueSorted = (items: string[]) => Array.from(new Set(items)).sort((a, b) => a.localeCompare(b));
|
|
82
|
+
|
|
46
83
|
export const useFilterStore = create<FilterState>()(
|
|
47
84
|
devtools(
|
|
48
85
|
(set, get) => ({
|
|
49
86
|
correlationId: null,
|
|
50
|
-
timeRange:
|
|
87
|
+
timeRange: defaultTimeRange,
|
|
88
|
+
selectedArtifactTypes: [],
|
|
89
|
+
selectedProducers: [],
|
|
90
|
+
selectedTags: [],
|
|
91
|
+
selectedVisibility: [],
|
|
51
92
|
availableCorrelationIds: [],
|
|
93
|
+
availableArtifactTypes: [],
|
|
94
|
+
availableProducers: [],
|
|
95
|
+
availableTags: [],
|
|
96
|
+
availableVisibility: [],
|
|
97
|
+
summary: null,
|
|
98
|
+
savedFilters: [],
|
|
52
99
|
|
|
53
100
|
setCorrelationId: (id) => set({ correlationId: id }),
|
|
54
101
|
setTimeRange: (range) => set({ timeRange: range }),
|
|
102
|
+
setArtifactTypes: (types) => set({ selectedArtifactTypes: uniqueSorted(types) }),
|
|
103
|
+
setProducers: (producers) => set({ selectedProducers: uniqueSorted(producers) }),
|
|
104
|
+
setTags: (tags) => set({ selectedTags: uniqueSorted(tags) }),
|
|
105
|
+
setVisibility: (visibility) => set({ selectedVisibility: uniqueSorted(visibility) }),
|
|
55
106
|
clearFilters: () =>
|
|
56
107
|
set({
|
|
57
108
|
correlationId: null,
|
|
58
|
-
timeRange:
|
|
109
|
+
timeRange: defaultTimeRange,
|
|
110
|
+
selectedArtifactTypes: [],
|
|
111
|
+
selectedProducers: [],
|
|
112
|
+
selectedTags: [],
|
|
113
|
+
selectedVisibility: [],
|
|
59
114
|
}),
|
|
60
115
|
|
|
61
116
|
updateAvailableCorrelationIds: (metadata) => {
|
|
62
|
-
// Sort by most recent first
|
|
63
117
|
const sorted = [...metadata].sort((a, b) => b.first_seen - a.first_seen);
|
|
64
118
|
set({
|
|
65
119
|
availableCorrelationIds: sorted.slice(0, 50),
|
|
66
120
|
});
|
|
67
121
|
},
|
|
68
122
|
|
|
123
|
+
updateAvailableFacets: (facets) =>
|
|
124
|
+
set({
|
|
125
|
+
availableArtifactTypes: uniqueSorted(facets.artifactTypes),
|
|
126
|
+
availableProducers: uniqueSorted(facets.producers),
|
|
127
|
+
availableTags: uniqueSorted(facets.tags),
|
|
128
|
+
availableVisibility: uniqueSorted(facets.visibilities),
|
|
129
|
+
}),
|
|
130
|
+
|
|
131
|
+
setSummary: (summary) => set({ summary }),
|
|
132
|
+
|
|
133
|
+
setSavedFilters: (filters) => set({ savedFilters: filters }),
|
|
134
|
+
addSavedFilter: (filter) =>
|
|
135
|
+
set((state) => ({
|
|
136
|
+
savedFilters: [...state.savedFilters.filter((f) => f.filter_id !== filter.filter_id), filter],
|
|
137
|
+
})),
|
|
138
|
+
removeSavedFilter: (filterId) =>
|
|
139
|
+
set((state) => ({
|
|
140
|
+
savedFilters: state.savedFilters.filter((f) => f.filter_id !== filterId),
|
|
141
|
+
})),
|
|
142
|
+
|
|
143
|
+
getFilterSnapshot: () => {
|
|
144
|
+
const state = get();
|
|
145
|
+
return {
|
|
146
|
+
correlationId: state.correlationId,
|
|
147
|
+
timeRange: state.timeRange,
|
|
148
|
+
artifactTypes: [...state.selectedArtifactTypes],
|
|
149
|
+
producers: [...state.selectedProducers],
|
|
150
|
+
tags: [...state.selectedTags],
|
|
151
|
+
visibility: [...state.selectedVisibility],
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
applyFilterSnapshot: (snapshot) =>
|
|
156
|
+
set({
|
|
157
|
+
correlationId: snapshot.correlationId,
|
|
158
|
+
timeRange: snapshot.timeRange,
|
|
159
|
+
selectedArtifactTypes: uniqueSorted(snapshot.artifactTypes),
|
|
160
|
+
selectedProducers: uniqueSorted(snapshot.producers),
|
|
161
|
+
selectedTags: uniqueSorted(snapshot.tags),
|
|
162
|
+
selectedVisibility: uniqueSorted(snapshot.visibility),
|
|
163
|
+
}),
|
|
164
|
+
|
|
69
165
|
getActiveFilters: () => {
|
|
166
|
+
console.debug('[filterStore] computing active filters', { correlationId: get().correlationId, timeRange: get().timeRange });
|
|
70
167
|
const state = get();
|
|
71
168
|
const filters: ActiveFilter[] = [];
|
|
72
169
|
|
|
@@ -78,24 +175,74 @@ export const useFilterStore = create<FilterState>()(
|
|
|
78
175
|
});
|
|
79
176
|
}
|
|
80
177
|
|
|
81
|
-
|
|
82
|
-
|
|
178
|
+
filters.push({
|
|
179
|
+
type: 'timeRange',
|
|
180
|
+
value: state.timeRange,
|
|
181
|
+
label: `Time: ${formatTimeRange(state.timeRange)}`,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
state.selectedArtifactTypes.forEach((type) => {
|
|
83
185
|
filters.push({
|
|
84
|
-
type: '
|
|
85
|
-
value:
|
|
86
|
-
label: `
|
|
186
|
+
type: 'artifactTypes',
|
|
187
|
+
value: type,
|
|
188
|
+
label: `Type: ${type}`,
|
|
87
189
|
});
|
|
88
|
-
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
state.selectedProducers.forEach((producer) => {
|
|
193
|
+
filters.push({
|
|
194
|
+
type: 'producers',
|
|
195
|
+
value: producer,
|
|
196
|
+
label: `Producer: ${producer}`,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
state.selectedTags.forEach((tag) => {
|
|
201
|
+
filters.push({
|
|
202
|
+
type: 'tags',
|
|
203
|
+
value: tag,
|
|
204
|
+
label: `Tag: ${tag}`,
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
state.selectedVisibility.forEach((kind) => {
|
|
209
|
+
filters.push({
|
|
210
|
+
type: 'visibility',
|
|
211
|
+
value: kind,
|
|
212
|
+
label: `Visibility: ${kind}`,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
89
215
|
|
|
90
216
|
return filters;
|
|
91
217
|
},
|
|
92
218
|
|
|
93
|
-
removeFilter: (
|
|
94
|
-
if (type === 'correlationId') {
|
|
219
|
+
removeFilter: (filter) => {
|
|
220
|
+
if (filter.type === 'correlationId') {
|
|
95
221
|
set({ correlationId: null });
|
|
96
|
-
|
|
97
|
-
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (filter.type === 'timeRange') {
|
|
225
|
+
set({ timeRange: defaultTimeRange });
|
|
226
|
+
return;
|
|
98
227
|
}
|
|
228
|
+
if (typeof filter.value !== 'string') {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
set((state) => {
|
|
232
|
+
if (filter.type === 'artifactTypes') {
|
|
233
|
+
return { selectedArtifactTypes: state.selectedArtifactTypes.filter((item) => item !== filter.value) };
|
|
234
|
+
}
|
|
235
|
+
if (filter.type === 'producers') {
|
|
236
|
+
return { selectedProducers: state.selectedProducers.filter((item) => item !== filter.value) };
|
|
237
|
+
}
|
|
238
|
+
if (filter.type === 'tags') {
|
|
239
|
+
return { selectedTags: state.selectedTags.filter((item) => item !== filter.value) };
|
|
240
|
+
}
|
|
241
|
+
if (filter.type === 'visibility') {
|
|
242
|
+
return { selectedVisibility: state.selectedVisibility.filter((item) => item !== filter.value) };
|
|
243
|
+
}
|
|
244
|
+
return {};
|
|
245
|
+
});
|
|
99
246
|
},
|
|
100
247
|
}),
|
|
101
248
|
{ name: 'filterStore' }
|
|
@@ -9,6 +9,8 @@ describe('graphStore', () => {
|
|
|
9
9
|
agents: new Map(),
|
|
10
10
|
messages: new Map(),
|
|
11
11
|
events: [],
|
|
12
|
+
runs: new Map(),
|
|
13
|
+
consumptions: new Map(),
|
|
12
14
|
nodes: [],
|
|
13
15
|
edges: [],
|
|
14
16
|
});
|
|
@@ -183,4 +185,21 @@ describe('graphStore', () => {
|
|
|
183
185
|
expect(nodes[0]?.type).toBe('agent');
|
|
184
186
|
expect(edges.length).toBeGreaterThan(0);
|
|
185
187
|
});
|
|
188
|
+
|
|
189
|
+
it('should hydrate consumptions from message payload', () => {
|
|
190
|
+
const message: Message = {
|
|
191
|
+
id: 'msg-embed',
|
|
192
|
+
type: 'Recipe',
|
|
193
|
+
payload: {},
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
correlationId: 'corr-embed',
|
|
196
|
+
producedBy: 'chef',
|
|
197
|
+
consumedBy: ['critic'],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
useGraphStore.getState().batchUpdate({ messages: [message] });
|
|
201
|
+
|
|
202
|
+
const state = useGraphStore.getState();
|
|
203
|
+
expect(state.consumptions.get('msg-embed')).toEqual(['critic']);
|
|
204
|
+
});
|
|
186
205
|
});
|