@cccsaurora/howler-ui 2.19.0-cases.1065 → 2.19.0-cases.1097

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.
Files changed (92) hide show
  1. package/api/v2/search/index.d.ts +2 -1
  2. package/api/v2/search/index.js +2 -2
  3. package/components/app/providers/ParameterProvider.d.ts +1 -1
  4. package/components/app/providers/ParameterProvider.test.js +25 -25
  5. package/components/app/providers/RecordProvider.d.ts +6 -6
  6. package/components/app/providers/RecordProvider.js +5 -1
  7. package/components/app/providers/RecordSearchProvider.d.ts +2 -2
  8. package/components/elements/ObjectDetails.d.ts +2 -2
  9. package/components/elements/PluginTypography.d.ts +2 -2
  10. package/components/elements/event/EventCard.d.ts +6 -0
  11. package/components/elements/{observable/ObservableCard.js → event/EventCard.js} +7 -7
  12. package/components/elements/{observable/ObservablePreview.d.ts → event/EventPreview.d.ts} +2 -2
  13. package/components/elements/event/EventPreview.js +12 -0
  14. package/components/elements/hit/HitCard.d.ts +2 -0
  15. package/components/elements/hit/HitCard.js +2 -2
  16. package/components/elements/hit/HitSummary.d.ts +2 -2
  17. package/components/elements/hit/related/RelatedRecords.js +4 -4
  18. package/components/elements/record/RecordComments.d.ts +2 -2
  19. package/components/elements/record/RecordRelated.d.ts +2 -2
  20. package/components/elements/record/RecordRelated.js +4 -4
  21. package/components/elements/record/RecordWorklog.d.ts +2 -2
  22. package/components/hooks/useRelatedRecords.d.ts +3 -3
  23. package/components/hooks/useRelatedRecords.js +2 -2
  24. package/components/routes/cases/detail/AlertPanel.test.d.ts +1 -0
  25. package/components/routes/cases/detail/AlertPanel.test.js +110 -0
  26. package/components/routes/cases/detail/CaseAssets.d.ts +2 -2
  27. package/components/routes/cases/detail/CaseAssets.js +2 -2
  28. package/components/routes/cases/detail/CaseAssets.test.js +4 -4
  29. package/components/routes/cases/detail/CaseDashboard.js +2 -2
  30. package/components/routes/cases/detail/CaseDashboard.test.d.ts +1 -0
  31. package/components/routes/cases/detail/CaseDashboard.test.js +218 -0
  32. package/components/routes/cases/detail/CaseRules.test.js +2 -2
  33. package/components/routes/cases/detail/CaseTimeline.js +5 -5
  34. package/components/routes/cases/detail/CaseTimeline.test.js +7 -7
  35. package/components/routes/cases/detail/CreateRuleDialog.js +4 -4
  36. package/components/routes/cases/detail/CreateRuleDialog.test.js +5 -5
  37. package/components/routes/cases/detail/ItemPage.js +1 -1
  38. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +2 -2
  39. package/components/routes/cases/detail/assets/Asset.d.ts +1 -1
  40. package/components/routes/cases/detail/assets/Asset.test.js +2 -2
  41. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +1 -1
  42. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +3 -3
  43. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +8 -8
  44. package/components/routes/cases/detail/sidebar/FolderEntry.js +1 -1
  45. package/components/routes/cases/modals/AddToCaseModal.d.ts +2 -2
  46. package/components/routes/cases/modals/AddToCaseModal.test.js +11 -11
  47. package/components/routes/cases/modals/CaseRecordRow.js +1 -1
  48. package/components/routes/cases/modals/CreateCaseModal.d.ts +2 -2
  49. package/components/routes/cases/modals/CreateCaseModal.test.js +14 -14
  50. package/components/routes/cases/modals/hooks.d.ts +3 -3
  51. package/components/routes/cases/modals/hooks.js +1 -1
  52. package/components/routes/cases/modals/types.d.ts +4 -1
  53. package/components/routes/events/EventViewer.d.ts +7 -0
  54. package/components/routes/events/EventViewer.js +27 -0
  55. package/components/routes/hits/search/SearchPane.js +3 -3
  56. package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -2
  57. package/components/routes/hits/search/grid/RecordRow.d.ts +3 -3
  58. package/components/routes/hits/search/grid/RecordRow.js +2 -2
  59. package/components/routes/hits/search/shared/IndexPicker.js +1 -1
  60. package/components/routes/home/ViewCard.js +4 -4
  61. package/components/routes/views/ViewComposer.js +2 -2
  62. package/locales/en/translation.json +4 -4
  63. package/locales/fr/translation.json +4 -4
  64. package/models/entities/generated/ECSEvent.d.ts +31 -0
  65. package/models/entities/generated/Event.d.ts +80 -26
  66. package/models/entities/generated/{ObservableCloud.d.ts → EventCloud.d.ts} +1 -1
  67. package/models/entities/generated/{ObservableDestination.d.ts → EventDestination.d.ts} +1 -1
  68. package/models/entities/generated/{ObservableEmail.d.ts → EventEmail.d.ts} +1 -1
  69. package/models/entities/generated/{ObservableFile.d.ts → EventFile.d.ts} +1 -1
  70. package/models/entities/generated/{ObservableHowler.d.ts → EventHowler.d.ts} +1 -1
  71. package/models/entities/generated/{ObservableHttp.d.ts → EventHttp.d.ts} +1 -1
  72. package/models/entities/generated/{ObservableObserver.d.ts → EventObserver.d.ts} +1 -1
  73. package/models/entities/generated/{ObservableOrganization.d.ts → EventOrganization.d.ts} +1 -1
  74. package/models/entities/generated/{ObservableProcess.d.ts → EventProcess.d.ts} +1 -1
  75. package/models/entities/generated/{ObservableSource.d.ts → EventSource.d.ts} +1 -1
  76. package/models/entities/generated/{ObservableThreat.d.ts → EventThreat.d.ts} +1 -1
  77. package/models/entities/generated/{ObservableTls.d.ts → EventTls.d.ts} +1 -1
  78. package/models/entities/generated/Hit.d.ts +2 -2
  79. package/models/entities/generated/Rule.d.ts +1 -1
  80. package/package.json +3 -3
  81. package/plugins/clue/utils.d.ts +2 -2
  82. package/tests/server-handlers.js +1 -1
  83. package/tests/utils.d.ts +2 -2
  84. package/tests/utils.js +3 -3
  85. package/utils/hitFunctions.d.ts +2 -2
  86. package/utils/typeUtils.d.ts +2 -2
  87. package/utils/typeUtils.js +2 -2
  88. package/components/elements/observable/ObservableCard.d.ts +0 -6
  89. package/components/elements/observable/ObservablePreview.js +0 -12
  90. package/components/routes/observables/ObservableViewer.d.ts +0 -7
  91. package/components/routes/observables/ObservableViewer.js +0 -27
  92. package/models/entities/generated/Observable.d.ts +0 -85
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import { createMockCase } from '@cccsaurora/howler-ui/tests/utils';
5
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
6
+ // ---------------------------------------------------------------------------
7
+ // Hoisted mocks
8
+ // ---------------------------------------------------------------------------
9
+ const mockHitCardProps = vi.hoisted(() => ({ current: [] }));
10
+ // ---------------------------------------------------------------------------
11
+ // Module mocks
12
+ // ---------------------------------------------------------------------------
13
+ vi.mock('components/elements/hit/HitCard', () => ({
14
+ default: (props) => {
15
+ mockHitCardProps.current.push(props);
16
+ return _jsx("div", { id: `hit-card-${String(props.id)}` });
17
+ }
18
+ }));
19
+ vi.mock('react-i18next', () => ({
20
+ useTranslation: () => ({
21
+ t: (key) => key
22
+ })
23
+ }));
24
+ vi.mock('react-router-dom', async () => {
25
+ const actual = await vi.importActual('react-router-dom');
26
+ return {
27
+ ...actual,
28
+ Link: ({ to, children, ...props }) => (_jsx("a", { href: to, ...props, children: children }))
29
+ };
30
+ });
31
+ // ---------------------------------------------------------------------------
32
+ // Imports after mocks
33
+ // ---------------------------------------------------------------------------
34
+ import AlertPanel from './AlertPanel';
35
+ // ---------------------------------------------------------------------------
36
+ // Helpers
37
+ // ---------------------------------------------------------------------------
38
+ const renderPanel = (caseValue) => {
39
+ return render(_jsx(MemoryRouter, { children: _jsx(AlertPanel, { case: caseValue }) }));
40
+ };
41
+ const makeHitItem = (id, path = `/cases/test/${id}`) => ({
42
+ type: 'hit',
43
+ value: id,
44
+ path
45
+ });
46
+ // ---------------------------------------------------------------------------
47
+ // Setup
48
+ // ---------------------------------------------------------------------------
49
+ beforeEach(() => {
50
+ mockHitCardProps.current = [];
51
+ });
52
+ // ---------------------------------------------------------------------------
53
+ // Tests
54
+ // ---------------------------------------------------------------------------
55
+ describe('AlertPanel', () => {
56
+ it('renders a skeleton when the case is null', () => {
57
+ const { container } = renderPanel(null);
58
+ expect(container.querySelector('.MuiSkeleton-root')).toBeTruthy();
59
+ });
60
+ it('renders the translated heading key', () => {
61
+ const _case = createMockCase({ case_id: 'case-1', items: [] });
62
+ renderPanel(_case);
63
+ expect(screen.getByText('page.cases.dashboard.alerts')).toBeInTheDocument();
64
+ });
65
+ it('renders HitCard only for unique hit items on the current page', () => {
66
+ const duplicate = makeHitItem('hit-1', '/cases/test/path-a');
67
+ const _case = createMockCase({
68
+ case_id: 'case-2',
69
+ items: [
70
+ duplicate,
71
+ duplicate,
72
+ makeHitItem('hit-2', '/cases/test/path-b'),
73
+ { type: 'event', value: 'event-1', path: '/cases/test/event-1' }
74
+ ]
75
+ });
76
+ renderPanel(_case);
77
+ expect(screen.getByTestId('hit-card-hit-1')).toBeInTheDocument();
78
+ expect(screen.getByTestId('hit-card-hit-2')).toBeInTheDocument();
79
+ expect(screen.queryByTestId('hit-card-event-1')).not.toBeInTheDocument();
80
+ expect(mockHitCardProps.current).toHaveLength(2);
81
+ expect(mockHitCardProps.current[0]).toEqual(expect.objectContaining({
82
+ id: 'hit-1',
83
+ lazy: true,
84
+ layout: 'dense'
85
+ }));
86
+ });
87
+ it('renders overlay links that target each hit path', () => {
88
+ const _case = createMockCase({
89
+ case_id: 'case-3',
90
+ items: [makeHitItem('hit-1', '/cases/case-3/path-one'), makeHitItem('hit-2', '/cases/case-3/path-two')]
91
+ });
92
+ const { container } = renderPanel(_case);
93
+ const links = Array.from(container.querySelectorAll('a'));
94
+ expect(links).toHaveLength(2);
95
+ expect(links[0]).toHaveAttribute('href', '/cases/case-3/path-one');
96
+ expect(links[1]).toHaveAttribute('href', '/cases/case-3/path-two');
97
+ });
98
+ it('shows pagination with multiple pages and switches to page 2 items', () => {
99
+ const items = Array.from({ length: 6 }, (_, idx) => makeHitItem(`hit-${idx + 1}`, `/cases/test/hit-${idx + 1}`));
100
+ const _case = createMockCase({ case_id: 'case-4', items });
101
+ renderPanel(_case);
102
+ expect(screen.getByTestId('hit-card-hit-1')).toBeInTheDocument();
103
+ expect(screen.getByTestId('hit-card-hit-5')).toBeInTheDocument();
104
+ expect(screen.queryByTestId('hit-card-hit-6')).not.toBeInTheDocument();
105
+ const pageTwoButton = screen.getByRole('button', { name: 'Go to page 2' });
106
+ fireEvent.click(pageTwoButton);
107
+ expect(screen.getByTestId('hit-card-hit-6')).toBeInTheDocument();
108
+ expect(screen.queryByTestId('hit-card-hit-1')).not.toBeInTheDocument();
109
+ });
110
+ });
@@ -1,9 +1,9 @@
1
1
  import type { Case } from '@cccsaurora/howler-ui/models/entities/generated/Case';
2
+ import type { Event } from '@cccsaurora/howler-ui/models/entities/generated/Event';
2
3
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
4
4
  import { type AssetEntry } from './assets/Asset';
5
5
  /** Deduplicate and merge seenIn lists into a map keyed by `type:value` */
6
- export declare const buildAssetEntries: (records: Partial<Hit | Observable>[]) => AssetEntry[];
6
+ export declare const buildAssetEntries: (records: Partial<Hit | Event>[]) => AssetEntry[];
7
7
  declare const _default: import("react").NamedExoticComponent<{
8
8
  case?: Case;
9
9
  caseId?: string;
@@ -60,7 +60,7 @@ const CaseAssets = ({ case: providedCase, caseId }) => {
60
60
  const [records, setRecords] = useState(null);
61
61
  const [activeFilters, setActiveFilters] = useState(new Set());
62
62
  const ids = useMemo(() => (_case?.items ?? [])
63
- .filter(item => ['hit', 'observable'].includes(item.type))
63
+ .filter(item => ['hit', 'event'].includes(item.type))
64
64
  .map(item => item.value)
65
65
  .filter(val => !!val), [_case?.items]);
66
66
  useEffect(() => {
@@ -68,7 +68,7 @@ const CaseAssets = ({ case: providedCase, caseId }) => {
68
68
  setRecords([]);
69
69
  return;
70
70
  }
71
- dispatchApi(api.v2.search.post(['hit', 'observable'], {
71
+ dispatchApi(api.v2.search.post(['hit', 'event'], {
72
72
  query: `howler.id:(${ids.join(' OR ')})`,
73
73
  fl: `howler.id,${RELATED_FIELDS}`
74
74
  })).then(response => setRecords(response.items));
@@ -4,7 +4,7 @@ import { render, screen } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
5
  import { createElement } from 'react';
6
6
  import { MemoryRouter } from 'react-router-dom';
7
- import { createMockHit, createMockObservable } from '@cccsaurora/howler-ui/tests/utils';
7
+ import { createMockEvent, createMockHit } from '@cccsaurora/howler-ui/tests/utils';
8
8
  import { describe, expect, it, vi } from 'vitest';
9
9
  import { buildAssetEntries } from './CaseAssets';
10
10
  // ---------------------------------------------------------------------------
@@ -29,7 +29,7 @@ describe('buildAssetEntries', () => {
29
29
  it('deduplicates the same asset value across multiple records', () => {
30
30
  const result = buildAssetEntries([
31
31
  createMockHit({ howler: { id: 'h1' }, related: { ip: ['1.2.3.4'] } }),
32
- createMockObservable({ howler: { id: 'obs1' }, related: { ip: ['1.2.3.4'] } })
32
+ createMockEvent({ howler: { id: 'obs1' }, related: { ip: ['1.2.3.4'] } })
33
33
  ]);
34
34
  expect(result).toHaveLength(1);
35
35
  expect(result[0].seenIn).toEqual(['h1', 'obs1']);
@@ -88,7 +88,7 @@ const mockCase = {
88
88
  case_id: 'case-001',
89
89
  items: [
90
90
  { type: 'hit', value: 'hit-1' },
91
- { type: 'observable', value: 'obs-1' }
91
+ { type: 'event', value: 'obs-1' }
92
92
  ]
93
93
  };
94
94
  const Wrapper = ({ children }) => createElement(MemoryRouter, { initialEntries: ['/cases/case-001/assets'] }, children);
@@ -155,7 +155,7 @@ describe('CaseAssets component', () => {
155
155
  expect(screen.getByText('1.2.3.4')).toBeTruthy();
156
156
  expect(screen.getByText('alice')).toBeTruthy();
157
157
  });
158
- it('renders nothing when the case has no hit/observable items', async () => {
158
+ it('renders nothing when the case has no hit/event items', async () => {
159
159
  const emptyCase = { case_id: 'case-002', items: [] };
160
160
  render(_jsx(CaseAssets, { case: emptyCase }), { wrapper: Wrapper });
161
161
  await screen.findByText('page.cases.assets.empty');
@@ -36,7 +36,7 @@ const CaseDashboard = ({ case: providedCase, caseId }) => {
36
36
  const { case: _case, update: updateCase } = useCase({ case: providedCase ?? routeCase, caseId });
37
37
  const [invalidIds, setInvalidIds] = useState([]);
38
38
  const ids = useMemo(() => (_case?.items ?? [])
39
- .filter(item => ['hit', 'observable'].includes(item.type))
39
+ .filter(item => ['hit', 'event'].includes(item.type))
40
40
  .map(item => item.value)
41
41
  .filter(val => !!val), [_case?.items]);
42
42
  const records = useContextSelector(RecordContext, ctx => ctx.records);
@@ -50,7 +50,7 @@ const CaseDashboard = ({ case: providedCase, caseId }) => {
50
50
  if (missingIds.length < 1) {
51
51
  return;
52
52
  }
53
- dispatchApi(api.v2.search.post(['hit', 'observable'], {
53
+ dispatchApi(api.v2.search.post(['hit', 'event'], {
54
54
  query: `howler.id:(${missingIds.join(' OR ')})`,
55
55
  metadata: ['template', 'analytic']
56
56
  })).then(response => {
@@ -0,0 +1,218 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, waitFor } from '@testing-library/react';
3
+ import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
4
+ import { useState } from 'react';
5
+ import { createMockCase, createMockEvent, createMockHit } from '@cccsaurora/howler-ui/tests/utils';
6
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
7
+ // ---------------------------------------------------------------------------
8
+ // Hoisted mocks
9
+ // ---------------------------------------------------------------------------
10
+ const mockDispatchApi = vi.hoisted(() => vi.fn());
11
+ const mockSearchPost = vi.hoisted(() => vi.fn());
12
+ const mockUpdateCase = vi.hoisted(() => vi.fn());
13
+ const mockUseCaseState = vi.hoisted(() => ({ case: null }));
14
+ const mockAggregateProps = vi.hoisted(() => ({
15
+ current: []
16
+ }));
17
+ const mockCaseOverviewProps = vi.hoisted(() => ({ current: [] }));
18
+ const mockTaskPanelProps = vi.hoisted(() => ({ current: [] }));
19
+ const mockAlertPanelProps = vi.hoisted(() => ({ current: [] }));
20
+ const mockRelatedCasePanelProps = vi.hoisted(() => ({ current: [] }));
21
+ // ---------------------------------------------------------------------------
22
+ // Module mocks
23
+ // ---------------------------------------------------------------------------
24
+ vi.mock('use-context-selector', async () => {
25
+ const react = (await vi.importActual('react'));
26
+ return {
27
+ createContext: react.createContext,
28
+ useContextSelector: (context, selector) => {
29
+ return selector(react.useContext(context));
30
+ }
31
+ };
32
+ });
33
+ vi.mock('components/hooks/useMyApi', () => ({
34
+ default: () => ({ dispatchApi: mockDispatchApi })
35
+ }));
36
+ vi.mock('api', () => ({
37
+ default: {
38
+ v2: {
39
+ search: {
40
+ post: (...args) => mockSearchPost(...args)
41
+ }
42
+ }
43
+ }
44
+ }));
45
+ vi.mock('../hooks/useCase', () => ({
46
+ default: () => ({
47
+ case: mockUseCaseState.case,
48
+ update: mockUpdateCase
49
+ })
50
+ }));
51
+ vi.mock('react-i18next', () => ({
52
+ useTranslation: () => ({
53
+ t: (key) => key
54
+ })
55
+ }));
56
+ vi.mock('react-router-dom', async () => {
57
+ const actual = await vi.importActual('react-router-dom');
58
+ return {
59
+ ...actual,
60
+ useOutletContext: () => createMockCase({ case_id: 'route-case-id', items: [] })
61
+ };
62
+ });
63
+ vi.mock('./aggregates/CaseAggregate', () => ({
64
+ default: (props) => {
65
+ mockAggregateProps.current.push(props);
66
+ return _jsx("div", { id: `aggregate-${String(mockAggregateProps.current.length)}` });
67
+ }
68
+ }));
69
+ vi.mock('./CaseOverview', () => ({
70
+ default: (props) => {
71
+ mockCaseOverviewProps.current.push(props);
72
+ return _jsx("div", { id: "case-overview" });
73
+ }
74
+ }));
75
+ vi.mock('./TaskPanel', () => ({
76
+ default: (props) => {
77
+ mockTaskPanelProps.current.push(props);
78
+ return _jsx("div", { id: "task-panel" });
79
+ }
80
+ }));
81
+ vi.mock('./AlertPanel', () => ({
82
+ default: (props) => {
83
+ mockAlertPanelProps.current.push(props);
84
+ return _jsx("div", { id: "alert-panel" });
85
+ }
86
+ }));
87
+ vi.mock('./RelatedCasePanel', () => ({
88
+ default: (props) => {
89
+ mockRelatedCasePanelProps.current.push(props);
90
+ return _jsx("div", { id: "related-case-panel" });
91
+ }
92
+ }));
93
+ // ---------------------------------------------------------------------------
94
+ // Imports after mocks
95
+ // ---------------------------------------------------------------------------
96
+ import CaseDashboard from './CaseDashboard';
97
+ // ---------------------------------------------------------------------------
98
+ // Helpers
99
+ // ---------------------------------------------------------------------------
100
+ const makeRecordContextValue = (records, loadRecords) => ({
101
+ records,
102
+ selectedRecords: [],
103
+ addRecordToSelection: vi.fn(),
104
+ removeRecordFromSelection: vi.fn(),
105
+ clearSelectedRecords: vi.fn(),
106
+ loadRecords,
107
+ updateRecord: vi.fn(),
108
+ getRecord: vi.fn()
109
+ });
110
+ const renderDashboard = ({ dashboardCase, initialRecords = {}, onLoadRecords }) => {
111
+ const Wrapper = ({ children }) => {
112
+ const [records, setRecords] = useState(initialRecords);
113
+ const loadRecords = items => {
114
+ onLoadRecords?.(items);
115
+ const mapped = Object.fromEntries(items.map(item => [item.howler.id, item]));
116
+ setRecords(prev => ({ ...prev, ...mapped }));
117
+ };
118
+ return (_jsx(RecordContext.Provider, { value: makeRecordContextValue(records, loadRecords), children: children }));
119
+ };
120
+ return render(_jsx(CaseDashboard, { case: dashboardCase }), { wrapper: Wrapper });
121
+ };
122
+ // ---------------------------------------------------------------------------
123
+ // Setup
124
+ // ---------------------------------------------------------------------------
125
+ beforeEach(() => {
126
+ mockDispatchApi.mockReset();
127
+ mockSearchPost.mockReset();
128
+ mockUpdateCase.mockReset();
129
+ mockAggregateProps.current = [];
130
+ mockCaseOverviewProps.current = [];
131
+ mockTaskPanelProps.current = [];
132
+ mockAlertPanelProps.current = [];
133
+ mockRelatedCasePanelProps.current = [];
134
+ mockUseCaseState.case = null;
135
+ });
136
+ // ---------------------------------------------------------------------------
137
+ // Tests
138
+ // ---------------------------------------------------------------------------
139
+ describe('CaseDashboard', () => {
140
+ it('renders nothing when useCase does not return a case', () => {
141
+ const baseCase = createMockCase({ case_id: 'case-1', items: [] });
142
+ mockUseCaseState.case = null;
143
+ const { container } = renderDashboard({ dashboardCase: baseCase });
144
+ expect(container).toBeEmptyDOMElement();
145
+ });
146
+ it('renders overview, panels and aggregate cards with translated subtitles', () => {
147
+ const dashboardCase = createMockCase({
148
+ case_id: 'case-2',
149
+ items: [
150
+ { type: 'hit', value: 'hit-1', path: 'root/hit-1' },
151
+ { type: 'event', value: 'event-1', path: 'root/event-1' }
152
+ ]
153
+ });
154
+ mockUseCaseState.case = dashboardCase;
155
+ renderDashboard({
156
+ dashboardCase,
157
+ initialRecords: {
158
+ 'hit-1': createMockHit({ howler: { id: 'hit-1', outline: { threat: 'threat-a', target: 'target-a' } } }),
159
+ 'event-1': createMockEvent({ howler: { id: 'event-1', outline: { indicators: ['ioc-a'] } } })
160
+ }
161
+ });
162
+ expect(mockCaseOverviewProps.current).toHaveLength(1);
163
+ expect(mockTaskPanelProps.current).toHaveLength(1);
164
+ expect(mockAlertPanelProps.current).toHaveLength(1);
165
+ expect(mockRelatedCasePanelProps.current).toHaveLength(1);
166
+ expect(mockAggregateProps.current).toHaveLength(4);
167
+ expect(mockAggregateProps.current[0]).toEqual(expect.objectContaining({
168
+ field: 'howler.outline.threat',
169
+ subtitle: 'page.cases.dashboard.threat'
170
+ }));
171
+ expect(mockAggregateProps.current[1]).toEqual(expect.objectContaining({
172
+ field: 'howler.outline.target',
173
+ subtitle: 'page.cases.dashboard.target'
174
+ }));
175
+ expect(mockAggregateProps.current[2]).toEqual(expect.objectContaining({
176
+ field: 'howler.outline.indicators',
177
+ subtitle: 'page.cases.dashboard.indicators'
178
+ }));
179
+ expect(mockAggregateProps.current[3]).toEqual(expect.objectContaining({
180
+ subtitle: 'page.cases.dashboard.duration',
181
+ title: '--'
182
+ }));
183
+ });
184
+ it('loads missing hit and event records and sends expected search query', async () => {
185
+ const dashboardCase = createMockCase({
186
+ case_id: 'case-3',
187
+ items: [
188
+ { type: 'hit', value: 'hit-1', path: 'root/hit-1' },
189
+ { type: 'event', value: 'event-1', path: 'root/event-1' },
190
+ { type: 'hit', value: 'hit-2', path: 'root/hit-2' },
191
+ { type: 'case', value: 'case-child', path: 'root/case-child' }
192
+ ]
193
+ });
194
+ mockUseCaseState.case = dashboardCase;
195
+ const loadedEvent = createMockEvent({ howler: { id: 'event-1' } });
196
+ const loadedHit = createMockHit({ howler: { id: 'hit-2' } });
197
+ mockSearchPost.mockReturnValue({ endpoint: 'search' });
198
+ mockDispatchApi.mockResolvedValue({
199
+ items: [loadedEvent, loadedHit]
200
+ });
201
+ const onLoadRecords = vi.fn();
202
+ renderDashboard({
203
+ dashboardCase,
204
+ initialRecords: {
205
+ 'hit-1': createMockHit({ howler: { id: 'hit-1' } })
206
+ },
207
+ onLoadRecords
208
+ });
209
+ await waitFor(() => {
210
+ expect(mockSearchPost).toHaveBeenCalledWith(['hit', 'event'], {
211
+ query: 'howler.id:(event-1 OR hit-2)',
212
+ metadata: ['template', 'analytic']
213
+ });
214
+ expect(mockDispatchApi).toHaveBeenCalledTimes(1);
215
+ expect(onLoadRecords).toHaveBeenCalledWith([loadedEvent, loadedHit]);
216
+ });
217
+ });
218
+ });
@@ -116,11 +116,11 @@ describe('CaseRules', () => {
116
116
  it('renders index chips for rule indexes', () => {
117
117
  mockCase.current = createMockCase({
118
118
  case_id: 'case-001',
119
- rules: [makeRule({ indexes: ['hit', 'observable'] })]
119
+ rules: [makeRule({ indexes: ['hit', 'event'] })]
120
120
  });
121
121
  render(_jsx(CaseRules, {}));
122
122
  expect(screen.getByText('hit')).toBeInTheDocument();
123
- expect(screen.getByText('observable')).toBeInTheDocument();
123
+ expect(screen.getByText('event')).toBeInTheDocument();
124
124
  });
125
125
  it('defaults to hit chip when indexes is undefined', () => {
126
126
  mockCase.current = createMockCase({
@@ -5,9 +5,9 @@ import { Autocomplete, Box, Chip, Divider, Skeleton, Stack, TextField, Tooltip,
5
5
  import api from '@cccsaurora/howler-ui/api';
6
6
  import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
7
7
  import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
8
+ import EventCard from '@cccsaurora/howler-ui/components/elements/event/EventCard';
8
9
  import HitCard from '@cccsaurora/howler-ui/components/elements/hit/HitCard';
9
10
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
10
- import ObservableCard from '@cccsaurora/howler-ui/components/elements/observable/ObservableCard';
11
11
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
12
12
  import dayjs from 'dayjs';
13
13
  import { capitalize } from 'lodash-es';
@@ -48,7 +48,7 @@ const CaseTimeline = ({ case: providedCase, caseId }) => {
48
48
  const [selectedMitres, setSelectedMitres] = useState([]);
49
49
  const [selectedEscalations, setSelectedEscalations] = useState(['evidence']);
50
50
  const ids = useMemo(() => (_case?.items ?? [])
51
- .filter(item => ['hit', 'observable'].includes(item.type))
51
+ .filter(item => ['hit', 'event'].includes(item.type))
52
52
  .map(item => item.value)
53
53
  .filter(Boolean), [_case]);
54
54
  const getPath = (value) => _case.items.find(item => item.value === value)?.path;
@@ -56,7 +56,7 @@ const CaseTimeline = ({ case: providedCase, caseId }) => {
56
56
  if (ids.length < 1) {
57
57
  return;
58
58
  }
59
- dispatchApi(api.v2.search.facet.post(['hit', 'observable'], {
59
+ dispatchApi(api.v2.search.facet.post(['hit', 'event'], {
60
60
  fields: ['threat.tactic.id', 'threat.technique.id', 'howler.escalation'],
61
61
  filters: [`howler.id:(${ids.join(' OR ')})`]
62
62
  }), { throwError: false }).then(result => {
@@ -84,7 +84,7 @@ const CaseTimeline = ({ case: providedCase, caseId }) => {
84
84
  }
85
85
  setLoading(true);
86
86
  const filters = buildFilters(selectedMitres, selectedEscalations);
87
- dispatchApi(api.v2.search.post(['hit', 'observable'], {
87
+ dispatchApi(api.v2.search.post(['hit', 'event'], {
88
88
  query: `howler.id:(${ids.join(' OR ')})`,
89
89
  sort: 'event.created asc',
90
90
  rows: ids.length,
@@ -101,6 +101,6 @@ const CaseTimeline = ({ case: providedCase, caseId }) => {
101
101
  if (!_case) {
102
102
  return null;
103
103
  }
104
- return (_jsxs(Stack, { spacing: 0, sx: { height: '100%' }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", sx: { p: 1, gap: 1 }, children: [_jsx(Tooltip, { title: t('page.cases.timeline.filter.label'), children: _jsx(FilterList, { fontSize: "small", color: "action" }) }), _jsx(Autocomplete, { multiple: true, size: "small", options: mitreOptions, value: selectedMitres, onChange: (_e, values) => setSelectedMitres(values), getOptionLabel: opt => `${opt.id} - ${opt.name}`, isOptionEqualToValue: (opt, val) => opt.id === val.id, groupBy: opt => capitalize(opt.kind), renderTags: (value, getTagProps) => value.map((opt, index) => (_createElement(Chip, { ...getTagProps({ index }), key: opt.id, size: "small", label: opt.id, color: "primary" }))), renderInput: params => (_jsx(TextField, { ...params, label: t('page.cases.timeline.filter.mitre'), sx: { minWidth: 260 } })), noOptionsText: t('page.cases.timeline.filter.mitre.empty') }), _jsx(Autocomplete, { multiple: true, size: "small", options: escalationOptions, value: selectedEscalations, onChange: (_e, value) => setSelectedEscalations(value), getOptionLabel: opt => t(`howler.escalation.${opt}`, opt), renderTags: (value, getTagProps) => value.map((opt, index) => (_createElement(Chip, { ...getTagProps({ index }), key: opt, size: "small", label: opt, color: ESCALATION_COLORS[opt] }))), renderInput: params => (_jsx(TextField, { ...params, label: t('page.cases.timeline.filter.escalation'), sx: { minWidth: 220 } })), noOptionsText: t('page.cases.timeline.filter.escalation.empty') })] }), _jsx(Divider, {}), loading ? (_jsx(Stack, { spacing: 2, sx: { px: 2, py: 1 }, children: [0, 1, 2].map(i => (_jsxs(Stack, { direction: "row", width: "100%", spacing: 1, children: [_jsx(Skeleton, { variant: "text", width: 120, height: 24 }), _jsx(Skeleton, { variant: "rounded", height: 120, sx: { flex: 1 } })] }, i))) })) : displayedEntries.length === 0 ? (_jsx(Box, { sx: { pt: 4, textAlign: 'center' }, children: _jsx(Typography, { color: "textSecondary", children: t('page.cases.timeline.empty') }) })) : (_jsx(Stack, { component: "ol", spacing: 0, sx: { px: 2, py: 1, listStyle: 'none', m: 0, overflow: 'auto' }, children: displayedEntries.map(entry => (_jsxs(Stack, { component: "li", spacing: 1, sx: { pb: 1 }, children: [_jsxs(Stack, { direction: "row", spacing: 2, alignItems: "flex-start", children: [_jsxs(Stack, { spacing: 0.5, alignItems: "end", children: [_jsx(Typography, { variant: "caption", color: "text.secondary", sx: { whiteSpace: 'nowrap' }, children: dayjs(entry.event?.created ?? entry.timestamp).format('YYYY-MM-DD HH:mm:ss') }), entry.threat?.technique?.id && (_jsx(Tooltip, { title: `${entry.threat.technique.id}: ${config.lookups?.techniques?.[entry.threat.technique.id].name}`, children: _jsx(Typography, { component: config.lookups?.techniques?.[entry.threat.technique.id]?.url ? 'a' : undefined, href: config.lookups?.techniques?.[entry.threat.technique.id]?.url, variant: "caption", color: "text.secondary", sx: { whiteSpace: 'nowrap' }, children: entry.threat.technique.id }) })), entry.threat?.tactic?.id && (_jsx(Tooltip, { title: `${entry.threat.tactic.id}: ${config.lookups?.tactics?.[entry.threat.tactic.id]?.name ?? t('unknown')}`, children: _jsx(Typography, { component: config.lookups?.tactics?.[entry.threat.tactic.id]?.url ? 'a' : undefined, href: config.lookups?.tactics?.[entry.threat.tactic.id]?.url, variant: "caption", color: "text.secondary", sx: { whiteSpace: 'nowrap' }, children: entry.threat.tactic.id }) }))] }), _jsx(Box, { component: Link, to: `/cases/${_case.case_id}/${getPath(entry.howler.id)}`, sx: { flex: 1, minWidth: 0, textDecoration: 'none' }, children: isHit(entry) ? (_jsx(HitCard, { id: entry.howler.id, layout: HitLayout.DENSE, readOnly: true })) : (_jsx(ObservableCard, { id: entry.howler.id })) })] }), _jsx(Divider, { flexItem: true })] }, entry.howler.id))) }))] }));
104
+ return (_jsxs(Stack, { spacing: 0, sx: { height: '100%' }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", sx: { p: 1, gap: 1 }, children: [_jsx(Tooltip, { title: t('page.cases.timeline.filter.label'), children: _jsx(FilterList, { fontSize: "small", color: "action" }) }), _jsx(Autocomplete, { multiple: true, size: "small", options: mitreOptions, value: selectedMitres, onChange: (_e, values) => setSelectedMitres(values), getOptionLabel: opt => `${opt.id} - ${opt.name}`, isOptionEqualToValue: (opt, val) => opt.id === val.id, groupBy: opt => capitalize(opt.kind), renderTags: (value, getTagProps) => value.map((opt, index) => (_createElement(Chip, { ...getTagProps({ index }), key: opt.id, size: "small", label: opt.id, color: "primary" }))), renderInput: params => (_jsx(TextField, { ...params, label: t('page.cases.timeline.filter.mitre'), sx: { minWidth: 260 } })), noOptionsText: t('page.cases.timeline.filter.mitre.empty') }), _jsx(Autocomplete, { multiple: true, size: "small", options: escalationOptions, value: selectedEscalations, onChange: (_e, value) => setSelectedEscalations(value), getOptionLabel: opt => t(`howler.escalation.${opt}`, opt), renderTags: (value, getTagProps) => value.map((opt, index) => (_createElement(Chip, { ...getTagProps({ index }), key: opt, size: "small", label: opt, color: ESCALATION_COLORS[opt] }))), renderInput: params => (_jsx(TextField, { ...params, label: t('page.cases.timeline.filter.escalation'), sx: { minWidth: 220 } })), noOptionsText: t('page.cases.timeline.filter.escalation.empty') })] }), _jsx(Divider, {}), loading ? (_jsx(Stack, { spacing: 2, sx: { px: 2, py: 1 }, children: [0, 1, 2].map(i => (_jsxs(Stack, { direction: "row", width: "100%", spacing: 1, children: [_jsx(Skeleton, { variant: "text", width: 120, height: 24 }), _jsx(Skeleton, { variant: "rounded", height: 120, sx: { flex: 1 } })] }, i))) })) : displayedEntries.length === 0 ? (_jsx(Box, { sx: { pt: 4, textAlign: 'center' }, children: _jsx(Typography, { color: "textSecondary", children: t('page.cases.timeline.empty') }) })) : (_jsx(Stack, { component: "ol", spacing: 0, sx: { px: 2, py: 1, listStyle: 'none', m: 0, overflow: 'auto' }, children: displayedEntries.map(entry => (_jsxs(Stack, { component: "li", spacing: 1, sx: { pb: 1 }, children: [_jsxs(Stack, { direction: "row", spacing: 2, alignItems: "flex-start", children: [_jsxs(Stack, { spacing: 0.5, alignItems: "end", children: [_jsx(Typography, { variant: "caption", color: "text.secondary", sx: { whiteSpace: 'nowrap' }, children: dayjs(entry.event?.created ?? entry.timestamp).format('YYYY-MM-DD HH:mm:ss') }), entry.threat?.technique?.id && (_jsx(Tooltip, { title: `${entry.threat.technique.id}: ${config.lookups?.techniques?.[entry.threat.technique.id].name}`, children: _jsx(Typography, { component: config.lookups?.techniques?.[entry.threat.technique.id]?.url ? 'a' : undefined, href: config.lookups?.techniques?.[entry.threat.technique.id]?.url, variant: "caption", color: "text.secondary", sx: { whiteSpace: 'nowrap' }, children: entry.threat.technique.id }) })), entry.threat?.tactic?.id && (_jsx(Tooltip, { title: `${entry.threat.tactic.id}: ${config.lookups?.tactics?.[entry.threat.tactic.id]?.name ?? t('unknown')}`, children: _jsx(Typography, { component: config.lookups?.tactics?.[entry.threat.tactic.id]?.url ? 'a' : undefined, href: config.lookups?.tactics?.[entry.threat.tactic.id]?.url, variant: "caption", color: "text.secondary", sx: { whiteSpace: 'nowrap' }, children: entry.threat.tactic.id }) }))] }), _jsx(Box, { component: Link, to: `/cases/${_case.case_id}/${getPath(entry.howler.id)}`, sx: { flex: 1, minWidth: 0, textDecoration: 'none' }, children: isHit(entry) ? (_jsx(HitCard, { id: entry.howler.id, hit: entry, layout: HitLayout.DENSE, readOnly: true })) : (_jsx(EventCard, { id: entry.howler.id, event: entry })) })] }), _jsx(Divider, { flexItem: true })] }, entry.howler.id))) }))] }));
105
105
  };
106
106
  export default memo(CaseTimeline);
@@ -7,7 +7,7 @@ import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers
7
7
  import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/RecordProvider';
8
8
  import { createElement } from 'react';
9
9
  import { MemoryRouter } from 'react-router-dom';
10
- import { createMockHit, createMockObservable } from '@cccsaurora/howler-ui/tests/utils';
10
+ import { createMockEvent, createMockHit } from '@cccsaurora/howler-ui/tests/utils';
11
11
  import { beforeEach, describe, expect, it, vi } from 'vitest';
12
12
  import { buildFilters } from './CaseTimeline';
13
13
  // ---------------------------------------------------------------------------
@@ -70,8 +70,8 @@ vi.mock('react-router-dom', async () => {
70
70
  vi.mock('components/elements/hit/HitCard', () => ({
71
71
  default: ({ id }) => _jsx("div", { children: `HitCard:${id}` })
72
72
  }));
73
- vi.mock('components/elements/observable/ObservableCard', () => ({
74
- default: ({ id }) => _jsx("div", { children: `ObservableCard:${id}` })
73
+ vi.mock('components/elements/event/EventCard', () => ({
74
+ default: ({ id }) => _jsx("div", { children: `EventCard:${id}` })
75
75
  }));
76
76
  // Return the request object rather than a Promise so dispatchApi's first
77
77
  // argument is JSON-serialisable (JSON.stringify(Promise) === '{}').
@@ -98,7 +98,7 @@ const mockCase = {
98
98
  case_id: 'case-001',
99
99
  items: [
100
100
  { type: 'hit', value: 'hit-1', path: 'hits/hit-1' },
101
- { type: 'observable', value: 'obs-1', path: 'observables/obs-1' }
101
+ { type: 'event', value: 'obs-1', path: 'events/obs-1' }
102
102
  ]
103
103
  };
104
104
  const Wrapper = ({ children }) => (_jsx(ApiConfigContext.Provider, { value: { config: mockConfig, setConfig: vi.fn() }, children: _jsx(RecordContext.Provider, { value: { records: {}, loadRecords: mockLoadRecords }, children: _jsx(MemoryRouter, { initialEntries: ['/cases/case-001/timeline'], children: children }) }) }));
@@ -120,7 +120,7 @@ const mockFacetResponse = {
120
120
  };
121
121
  const mockSearchResponse = (items = [
122
122
  createMockHit({ howler: { id: 'hit-1' }, event: { created: '2024-01-01T00:00:00Z' } }),
123
- createMockObservable({ howler: { id: 'obs-1' } })
123
+ createMockEvent({ howler: { id: 'obs-1' } })
124
124
  ]) => ({ items, total: items.length, rows: items.length, offset: 0 });
125
125
  describe('CaseTimeline component', () => {
126
126
  beforeEach(() => {
@@ -137,11 +137,11 @@ describe('CaseTimeline component', () => {
137
137
  render(_jsx(CaseTimeline, { case: mockCase }), { wrapper: Wrapper });
138
138
  await screen.findByText('page.cases.timeline.empty');
139
139
  });
140
- it('renders HitCard for hit entries and ObservableCard for observable entries', async () => {
140
+ it('renders HitCard for hit entries and EventCard for event entries', async () => {
141
141
  mockDispatchApi.mockResolvedValueOnce(mockFacetResponse).mockResolvedValueOnce(mockSearchResponse());
142
142
  render(_jsx(CaseTimeline, { case: mockCase }), { wrapper: Wrapper });
143
143
  expect(await screen.findByText('HitCard:hit-1')).toBeTruthy();
144
- expect(screen.getByText('ObservableCard:obs-1')).toBeTruthy();
144
+ expect(screen.getByText('EventCard:obs-1')).toBeTruthy();
145
145
  });
146
146
  it('renders the formatted event.created timestamp for hits', async () => {
147
147
  mockDispatchApi
@@ -141,14 +141,14 @@ const CreateRuleDialog = ({ open, onClose, onSubmit }) => {
141
141
  }
142
142
  return prev.filter(i => i !== 'hit');
143
143
  });
144
- } }), label: t('hit.search.index.hit') }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { id: "rule-index-observable", checked: indexes.includes('observable'), onChange: (_e, checked) => {
144
+ } }), label: t('hit.search.index.hit') }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { id: "rule-index-event", checked: indexes.includes('event'), onChange: (_e, checked) => {
145
145
  setIndexes(prev => {
146
146
  if (checked) {
147
- return prev.includes('observable') ? prev : [...prev, 'observable'];
147
+ return prev.includes('event') ? prev : [...prev, 'event'];
148
148
  }
149
- return prev.filter(i => i !== 'observable');
149
+ return prev.filter(i => i !== 'event');
150
150
  });
151
- } }), label: t('hit.search.index.observable') })] })] }), _jsxs(Stack, { direction: "row", spacing: 2, alignItems: "center", children: [_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsx(DateTimePicker, { slotProps: {
151
+ } }), label: t('hit.search.index.event') })] })] }), _jsxs(Stack, { direction: "row", spacing: 2, alignItems: "center", children: [_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsx(DateTimePicker, { slotProps: {
152
152
  textField: {
153
153
  id: 'rule-timeframe-input',
154
154
  fullWidth: true,
@@ -61,7 +61,7 @@ describe('CreateRuleDialog', () => {
61
61
  expect(screen.getByTestId('rule-timeframe-input')).toBeInTheDocument();
62
62
  expect(screen.getByTestId('rule-no-expiry-checkbox')).toBeInTheDocument();
63
63
  expect(screen.getByTestId('rule-index-hit')).toBeInTheDocument();
64
- expect(screen.getByTestId('rule-index-observable')).toBeInTheDocument();
64
+ expect(screen.getByTestId('rule-index-event')).toBeInTheDocument();
65
65
  });
66
66
  it('disables submit when query is empty', () => {
67
67
  render(_jsx(CreateRuleDialog, { ...defaultProps }));
@@ -205,9 +205,9 @@ describe('CreateRuleDialog', () => {
205
205
  it('defaults to hit index checked', () => {
206
206
  render(_jsx(CreateRuleDialog, { ...defaultProps }));
207
207
  const hitCheckbox = screen.getByTestId('rule-index-hit');
208
- const observableCheckbox = screen.getByTestId('rule-index-observable');
208
+ const eventCheckbox = screen.getByTestId('rule-index-event');
209
209
  expect(hitCheckbox).toBeChecked();
210
- expect(observableCheckbox).not.toBeChecked();
210
+ expect(eventCheckbox).not.toBeChecked();
211
211
  });
212
212
  it('includes both indexes when both are selected', async () => {
213
213
  const user = userEvent.setup();
@@ -216,7 +216,7 @@ describe('CreateRuleDialog', () => {
216
216
  api.search.hit.post.mockReturnValue('search-request');
217
217
  render(_jsx(CreateRuleDialog, { ...defaultProps, onSubmit: onSubmit }));
218
218
  await act(async () => {
219
- await user.click(screen.getByTestId('rule-index-observable'));
219
+ await user.click(screen.getByTestId('rule-index-event'));
220
220
  });
221
221
  await act(async () => {
222
222
  await user.type(screen.getByTestId('rule-query-editor'), 'event.kind:alert');
@@ -232,7 +232,7 @@ describe('CreateRuleDialog', () => {
232
232
  });
233
233
  await waitFor(() => {
234
234
  expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({
235
- indexes: expect.arrayContaining(['hit', 'observable'])
235
+ indexes: expect.arrayContaining(['hit', 'event'])
236
236
  }));
237
237
  });
238
238
  });
@@ -84,7 +84,7 @@ const ItemPage = ({ case: providedCase }) => {
84
84
  if (!item) {
85
85
  return _jsx(NotFoundPage, {});
86
86
  }
87
- if (item.type === 'hit' || item.type === 'observable') {
87
+ if (item.type === 'hit' || item.type === 'event') {
88
88
  return _jsx(InformationPane, { selected: item.value });
89
89
  }
90
90
  if (item.type === 'case') {
@@ -1,10 +1,10 @@
1
+ import type { Event } from '@cccsaurora/howler-ui/models/entities/generated/Event';
1
2
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
2
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
3
3
  declare const _default: import("react").NamedExoticComponent<{
4
4
  icon?: string;
5
5
  iconColor?: string;
6
6
  field?: string;
7
- records?: Partial<Hit | Observable>[];
7
+ records?: Partial<Hit | Event>[];
8
8
  title?: string;
9
9
  subtitle?: string;
10
10
  }>;
@@ -4,7 +4,7 @@ export type AssetType = 'hash' | 'hosts' | 'ip' | 'user' | 'ids' | 'id' | 'uri'
4
4
  export interface AssetEntry {
5
5
  type: AssetType;
6
6
  value: string;
7
- /** IDs of the hits/observables this asset was seen in */
7
+ /** IDs of the hits/events this asset was seen in */
8
8
  seenIn: string[];
9
9
  }
10
10
  declare const Asset: FC<{