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

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 (88) 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/CaseAssets.d.ts +2 -2
  25. package/components/routes/cases/detail/CaseAssets.js +2 -2
  26. package/components/routes/cases/detail/CaseAssets.test.js +4 -4
  27. package/components/routes/cases/detail/CaseDashboard.js +2 -2
  28. package/components/routes/cases/detail/CaseRules.test.js +2 -2
  29. package/components/routes/cases/detail/CaseTimeline.js +5 -5
  30. package/components/routes/cases/detail/CaseTimeline.test.js +7 -7
  31. package/components/routes/cases/detail/CreateRuleDialog.js +4 -4
  32. package/components/routes/cases/detail/CreateRuleDialog.test.js +5 -5
  33. package/components/routes/cases/detail/ItemPage.js +1 -1
  34. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +2 -2
  35. package/components/routes/cases/detail/assets/Asset.d.ts +1 -1
  36. package/components/routes/cases/detail/assets/Asset.test.js +2 -2
  37. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +1 -1
  38. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +3 -3
  39. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +8 -8
  40. package/components/routes/cases/detail/sidebar/FolderEntry.js +1 -1
  41. package/components/routes/cases/modals/AddToCaseModal.d.ts +2 -2
  42. package/components/routes/cases/modals/AddToCaseModal.test.js +11 -11
  43. package/components/routes/cases/modals/CaseRecordRow.js +1 -1
  44. package/components/routes/cases/modals/CreateCaseModal.d.ts +2 -2
  45. package/components/routes/cases/modals/CreateCaseModal.test.js +14 -14
  46. package/components/routes/cases/modals/hooks.d.ts +3 -3
  47. package/components/routes/cases/modals/hooks.js +1 -1
  48. package/components/routes/cases/modals/types.d.ts +4 -1
  49. package/components/routes/events/EventViewer.d.ts +7 -0
  50. package/components/routes/events/EventViewer.js +27 -0
  51. package/components/routes/hits/search/SearchPane.js +3 -3
  52. package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -2
  53. package/components/routes/hits/search/grid/RecordRow.d.ts +3 -3
  54. package/components/routes/hits/search/grid/RecordRow.js +2 -2
  55. package/components/routes/hits/search/shared/IndexPicker.js +1 -1
  56. package/components/routes/home/ViewCard.js +4 -4
  57. package/components/routes/views/ViewComposer.js +2 -2
  58. package/locales/en/translation.json +4 -4
  59. package/locales/fr/translation.json +4 -4
  60. package/models/entities/generated/ECSEvent.d.ts +31 -0
  61. package/models/entities/generated/Event.d.ts +80 -26
  62. package/models/entities/generated/{ObservableCloud.d.ts → EventCloud.d.ts} +1 -1
  63. package/models/entities/generated/{ObservableDestination.d.ts → EventDestination.d.ts} +1 -1
  64. package/models/entities/generated/{ObservableEmail.d.ts → EventEmail.d.ts} +1 -1
  65. package/models/entities/generated/{ObservableFile.d.ts → EventFile.d.ts} +1 -1
  66. package/models/entities/generated/{ObservableHowler.d.ts → EventHowler.d.ts} +1 -1
  67. package/models/entities/generated/{ObservableHttp.d.ts → EventHttp.d.ts} +1 -1
  68. package/models/entities/generated/{ObservableObserver.d.ts → EventObserver.d.ts} +1 -1
  69. package/models/entities/generated/{ObservableOrganization.d.ts → EventOrganization.d.ts} +1 -1
  70. package/models/entities/generated/{ObservableProcess.d.ts → EventProcess.d.ts} +1 -1
  71. package/models/entities/generated/{ObservableSource.d.ts → EventSource.d.ts} +1 -1
  72. package/models/entities/generated/{ObservableThreat.d.ts → EventThreat.d.ts} +1 -1
  73. package/models/entities/generated/{ObservableTls.d.ts → EventTls.d.ts} +1 -1
  74. package/models/entities/generated/Hit.d.ts +2 -2
  75. package/models/entities/generated/Rule.d.ts +1 -1
  76. package/package.json +3 -3
  77. package/plugins/clue/utils.d.ts +2 -2
  78. package/tests/server-handlers.js +1 -1
  79. package/tests/utils.d.ts +2 -2
  80. package/tests/utils.js +3 -3
  81. package/utils/hitFunctions.d.ts +2 -2
  82. package/utils/typeUtils.d.ts +2 -2
  83. package/utils/typeUtils.js +2 -2
  84. package/components/elements/observable/ObservableCard.d.ts +0 -6
  85. package/components/elements/observable/ObservablePreview.js +0 -12
  86. package/components/routes/observables/ObservableViewer.d.ts +0 -7
  87. package/components/routes/observables/ObservableViewer.js +0 -27
  88. package/models/entities/generated/Observable.d.ts +0 -85
@@ -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<{
@@ -49,13 +49,13 @@ describe('Asset', () => {
49
49
  const _case = createMockCase({
50
50
  items: [
51
51
  { path: 'alerts/my-analytic (hit-001)', type: 'hit', value: 'hit-001' },
52
- { path: 'observables/obs-002', type: 'observable', value: 'obs-002' },
52
+ { path: 'events/obs-002', type: 'event', value: 'obs-002' },
53
53
  { path: 'alerts/other-analytic (hit-003)', type: 'hit', value: 'hit-003' }
54
54
  ]
55
55
  });
56
56
  render(_jsx(MemoryRouter, { children: _jsx(Asset, { asset: makeAsset({ seenIn: ['hit-001', 'obs-002', 'hit-003'] }), case: _case }) }));
57
57
  expect(screen.getByText('alerts/my-analytic (hit-001)')).toBeTruthy();
58
- expect(screen.getByText('observables/obs-002')).toBeTruthy();
58
+ expect(screen.getByText('events/obs-002')).toBeTruthy();
59
59
  expect(screen.getByText('alerts/other-analytic (hit-003)')).toBeTruthy();
60
60
  });
61
61
  it('links each chip to /cases/:case_id/:path', () => {
@@ -10,7 +10,7 @@ export declare const collectAllLeaves: (tree: Tree) => Item[];
10
10
  * Returns the URL to open for a given leaf item, or null if no URL applies.
11
11
  * - reference: the item's value (an external URL)
12
12
  * - hit: /hits/<id>
13
- * - observable: /observables/<id>
13
+ * - event: /events/<id>
14
14
  * - case: /cases/<id>
15
15
  * - table / lead: null (no dedicated detail page)
16
16
  */
@@ -21,7 +21,7 @@ export const collectAllLeaves = (tree) => {
21
21
  * Returns the URL to open for a given leaf item, or null if no URL applies.
22
22
  * - reference: the item's value (an external URL)
23
23
  * - hit: /hits/<id>
24
- * - observable: /observables/<id>
24
+ * - event: /events/<id>
25
25
  * - case: /cases/<id>
26
26
  * - table / lead: null (no dedicated detail page)
27
27
  */
@@ -33,8 +33,8 @@ export const getOpenUrl = (leaf) => {
33
33
  if (type === 'hit') {
34
34
  return leaf.value ? `/hits/${leaf.value}` : null;
35
35
  }
36
- if (type === 'observable') {
37
- return leaf.value ? `/observables/${leaf.value}` : null;
36
+ if (type === 'event') {
37
+ return leaf.value ? `/events/${leaf.value}` : null;
38
38
  }
39
39
  if (type === 'case') {
40
40
  return leaf.value ? `/cases/${leaf.value}` : null;
@@ -54,7 +54,7 @@ import CaseFolderContextMenu, { collectAllLeaves, getOpenUrl } from './CaseFolde
54
54
  const mockCase = { case_id: 'case-1', title: 'Test Case', items: [] };
55
55
  const hitLeaf = { type: 'hit', value: 'hit-123', path: 'folder/hit-item' };
56
56
  const referenceLeaf = { type: 'reference', value: 'https://example.com', path: 'folder/ref-item' };
57
- const observableLeaf = { type: 'observable', value: 'obs-456', path: 'folder/obs-item' };
57
+ const eventLeaf = { type: 'event', value: 'obs-456', path: 'folder/event-item' };
58
58
  const caseLeaf = { type: 'case', value: 'nested-case-id', path: 'folder/case-item' };
59
59
  const tableLeaf = { type: 'table', value: 'table-789', path: 'folder/table-item' };
60
60
  const leadLeaf = { type: 'lead', value: 'lead-999', path: 'folder/lead-item' };
@@ -119,8 +119,8 @@ describe('getOpenUrl', () => {
119
119
  it('returns /hits/<id> for a hit item', () => {
120
120
  expect(getOpenUrl(hitLeaf)).toBe('/hits/hit-123');
121
121
  });
122
- it('returns /observables/<id> for an observable item', () => {
123
- expect(getOpenUrl(observableLeaf)).toBe('/observables/obs-456');
122
+ it('returns /events/<id> for an event item', () => {
123
+ expect(getOpenUrl(eventLeaf)).toBe('/events/obs-456');
124
124
  });
125
125
  it('returns /cases/<id> for a case item', () => {
126
126
  expect(getOpenUrl(caseLeaf)).toBe('/cases/nested-case-id');
@@ -159,8 +159,8 @@ describe('CaseFolderContextMenu', () => {
159
159
  expect(screen.getByTestId('open-item')).toBeInTheDocument();
160
160
  expect(screen.getByTestId('remove-item')).toBeInTheDocument();
161
161
  });
162
- it('shows "Open item" and "Remove item" for an observable leaf', () => {
163
- renderMenu({ leaf: observableLeaf });
162
+ it('shows "Open item" and "Remove item" for an event leaf', () => {
163
+ renderMenu({ leaf: eventLeaf });
164
164
  expect(screen.getByTestId('open-item')).toBeInTheDocument();
165
165
  expect(screen.getByTestId('remove-item')).toBeInTheDocument();
166
166
  });
@@ -219,12 +219,12 @@ describe('CaseFolderContextMenu', () => {
219
219
  });
220
220
  expect(window.open).toHaveBeenCalledWith('https://example.com', '_blank', 'noopener noreferrer');
221
221
  });
222
- it('calls window.open with the observable URL', () => {
223
- renderMenu({ leaf: observableLeaf });
222
+ it('calls window.open with the event URL', () => {
223
+ renderMenu({ leaf: eventLeaf });
224
224
  act(() => {
225
225
  fireEvent.click(screen.getByTestId('open-item'));
226
226
  });
227
- expect(window.open).toHaveBeenCalledWith('/observables/obs-456', '_blank', 'noopener noreferrer');
227
+ expect(window.open).toHaveBeenCalledWith('/events/obs-456', '_blank', 'noopener noreferrer');
228
228
  });
229
229
  it('calls window.open with the case URL', () => {
230
230
  renderMenu({ leaf: caseLeaf });
@@ -9,7 +9,7 @@ import { Link, useLocation } from 'react-router-dom';
9
9
  const ICON_FOR_TYPE = {
10
10
  folder: Folder,
11
11
  case: BookRounded,
12
- observable: Visibility,
12
+ event: Visibility,
13
13
  hit: CheckCircle,
14
14
  table: TableChart,
15
15
  lead: Lightbulb,
@@ -1,7 +1,7 @@
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
  import { type FC } from 'react';
4
4
  declare const AddToCaseModal: FC<{
5
- records: (Hit | Observable)[];
5
+ records: (Hit | Event)[];
6
6
  }>;
7
7
  export default AddToCaseModal;
@@ -5,7 +5,7 @@ import userEvent, {} from '@testing-library/user-event';
5
5
  import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
6
6
  import i18n from '@cccsaurora/howler-ui/i18n';
7
7
  import { I18nextProvider } from 'react-i18next';
8
- import { createMockCase, createMockHit, createMockObservable } from '@cccsaurora/howler-ui/tests/utils';
8
+ import { createMockCase, createMockEvent, createMockHit } from '@cccsaurora/howler-ui/tests/utils';
9
9
  import { beforeEach, describe, expect, it, vi } from 'vitest';
10
10
  import AddToCaseModal from './AddToCaseModal';
11
11
  // ---------------------------------------------------------------------------
@@ -49,7 +49,7 @@ const MOCK_HIT_1 = createMockHit({
49
49
  const MOCK_HIT_2 = createMockHit({
50
50
  howler: { id: 'hit-002', analytic: 'AnalyticTwo', status: 'open' }
51
51
  });
52
- const MOCK_OBSERVABLE = createMockObservable({
52
+ const MOCK_EVENT = createMockEvent({
53
53
  howler: { id: 'obs-001' }
54
54
  });
55
55
  // ---------------------------------------------------------------------------
@@ -114,12 +114,12 @@ describe('AddToCaseModal', () => {
114
114
  const titleInput = screen.getByPlaceholderText(i18n.t('modal.cases.add_to_case.title'));
115
115
  expect(titleInput).toHaveValue(`${MOCK_HIT_1.howler.analytic} (${MOCK_HIT_1.howler.id})`);
116
116
  });
117
- it('pre-populates title for an observable with Observable and id', async () => {
118
- renderModal([MOCK_OBSERVABLE]);
117
+ it('pre-populates title for an event with Event and id', async () => {
118
+ renderModal([MOCK_EVENT]);
119
119
  await waitFor(() => expect(mockDispatchApi).toHaveBeenCalled());
120
120
  await selectCase(user, 'Case Alpha');
121
121
  const titleInput = screen.getByPlaceholderText(i18n.t('modal.cases.add_to_case.title'));
122
- expect(titleInput).toHaveValue(`Observable (${MOCK_OBSERVABLE.howler.id})`);
122
+ expect(titleInput).toHaveValue(`Event (${MOCK_EVENT.howler.id})`);
123
123
  });
124
124
  it('title input is editable', async () => {
125
125
  renderModal([MOCK_HIT_1]);
@@ -161,13 +161,13 @@ describe('AddToCaseModal', () => {
161
161
  expect(titleInputs[0]).toHaveValue('Edited');
162
162
  expect(titleInputs[1]).toHaveValue(`${MOCK_HIT_2.howler.analytic} (${MOCK_HIT_2.howler.id})`);
163
163
  });
164
- it('mixed hit and observable records each get correct default titles', async () => {
165
- renderModal([MOCK_HIT_1, MOCK_OBSERVABLE]);
164
+ it('mixed hit and event records each get correct default titles', async () => {
165
+ renderModal([MOCK_HIT_1, MOCK_EVENT]);
166
166
  await waitFor(() => expect(mockDispatchApi).toHaveBeenCalled());
167
167
  await selectCase(user, 'Case Alpha');
168
168
  const titleInputs = screen.getAllByPlaceholderText(i18n.t('modal.cases.add_to_case.title'));
169
169
  expect(titleInputs[0]).toHaveValue(`${MOCK_HIT_1.howler.analytic} (${MOCK_HIT_1.howler.id})`);
170
- expect(titleInputs[1]).toHaveValue(`Observable (${MOCK_OBSERVABLE.howler.id})`);
170
+ expect(titleInputs[1]).toHaveValue(`Event (${MOCK_EVENT.howler.id})`);
171
171
  });
172
172
  });
173
173
  // -------------------------------------------------------------------------
@@ -260,14 +260,14 @@ describe('AddToCaseModal', () => {
260
260
  path: `investigations/${MOCK_HIT_1.howler.analytic} (${MOCK_HIT_1.howler.id})`
261
261
  }));
262
262
  });
263
- it('uses observable __index for observable records', async () => {
263
+ it('uses event __index for event records', async () => {
264
264
  const api = (await import('api')).default;
265
- renderModal([MOCK_OBSERVABLE]);
265
+ renderModal([MOCK_EVENT]);
266
266
  await waitFor(() => expect(mockDispatchApi).toHaveBeenCalled());
267
267
  await selectCase(user, 'Case Alpha');
268
268
  await user.click(screen.getByRole('button', { name: i18n.t('confirm') }));
269
269
  await waitFor(() => expect(mockClose).toHaveBeenCalled());
270
- expect(api.v2.case.items.post).toHaveBeenCalledWith('case-a', expect.objectContaining({ type: 'observable' }));
270
+ expect(api.v2.case.items.post).toHaveBeenCalledWith('case-a', expect.objectContaining({ type: 'event' }));
271
271
  });
272
272
  });
273
273
  // -------------------------------------------------------------------------
@@ -10,6 +10,6 @@ const CaseRecordRow = ({ entry, folderOptions = [], onTitleChange, onPathChange
10
10
  const { record, path, title } = entry;
11
11
  const fullPath = path ? `${path}/${title}` : title;
12
12
  const pathError = path.startsWith('/') || path.endsWith('/');
13
- return (_jsxs(Accordion, { variant: "outlined", defaultExpanded: true, sx: { flexShrink: 0 }, children: [_jsx(AccordionSummary, { expandIcon: _jsx(KeyboardArrowDown, {}), sx: { px: 1, minHeight: '48px !important', '& > *': { margin: '0 !important' } }, children: _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, width: "100%", children: [_jsx(Typography, { variant: "body2", fontWeight: 500, sx: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: isHit(record) ? record.howler.analytic : 'Observable' }), isHit(record) && _jsx(EscalationChip, { hit: record, layout: HitLayout.DENSE }), isHit(record) && _jsx(Chip, { label: record.howler.status, size: "small", color: "primary", sx: { flexShrink: 0 } }), _jsx(Typography, { variant: "caption", color: "textSecondary", sx: { flexShrink: 0 }, children: record.howler.id })] }) }), _jsx(AccordionDetails, { children: _jsxs(Stack, { spacing: 1, children: [_jsx(Autocomplete, { freeSolo: true, disablePortal: true, options: folderOptions, value: path, onInputChange: (_ev, newVal) => onPathChange(newVal), renderInput: params => (_jsx(TextField, { ...params, size: "small", placeholder: t('modal.cases.add_to_case.select_path'), fullWidth: true, error: pathError, helperText: pathError ? t('modal.cases.add_to_case.path_invalid') : undefined })) }), _jsx(TextField, { size: "small", fullWidth: true, placeholder: t('modal.cases.add_to_case.title'), value: title, onChange: ev => onTitleChange(ev.target.value) }), title && (_jsx(Typography, { variant: "caption", color: "textSecondary", children: t('modal.cases.add_to_case.full_path', { path: fullPath }) }))] }) })] }));
13
+ return (_jsxs(Accordion, { variant: "outlined", defaultExpanded: true, sx: { flexShrink: 0 }, children: [_jsx(AccordionSummary, { expandIcon: _jsx(KeyboardArrowDown, {}), sx: { px: 1, minHeight: '48px !important', '& > *': { margin: '0 !important' } }, children: _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, width: "100%", children: [_jsx(Typography, { variant: "body2", fontWeight: 500, sx: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: isHit(record) ? record.howler.analytic : 'Event' }), isHit(record) && _jsx(EscalationChip, { hit: record, layout: HitLayout.DENSE }), isHit(record) && _jsx(Chip, { label: record.howler.status, size: "small", color: "primary", sx: { flexShrink: 0 } }), _jsx(Typography, { variant: "caption", color: "textSecondary", sx: { flexShrink: 0 }, children: record.howler.id })] }) }), _jsx(AccordionDetails, { children: _jsxs(Stack, { spacing: 1, children: [_jsx(Autocomplete, { freeSolo: true, disablePortal: true, options: folderOptions, value: path, onInputChange: (_ev, newVal) => onPathChange(newVal), renderInput: params => (_jsx(TextField, { ...params, size: "small", placeholder: t('modal.cases.add_to_case.select_path'), fullWidth: true, error: pathError, helperText: pathError ? t('modal.cases.add_to_case.path_invalid') : undefined })) }), _jsx(TextField, { size: "small", fullWidth: true, placeholder: t('modal.cases.add_to_case.title'), value: title, onChange: ev => onTitleChange(ev.target.value) }), title && (_jsx(Typography, { variant: "caption", color: "textSecondary", children: t('modal.cases.add_to_case.full_path', { path: fullPath }) }))] }) })] }));
14
14
  };
15
15
  export default CaseRecordRow;
@@ -1,7 +1,7 @@
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
  import { type FC } from 'react';
4
4
  declare const CreateCaseModal: FC<{
5
- records: (Hit | Observable)[];
5
+ records: (Hit | Event)[];
6
6
  }>;
7
7
  export default CreateCaseModal;
@@ -6,7 +6,7 @@ import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers
6
6
  import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
7
7
  import i18n from '@cccsaurora/howler-ui/i18n';
8
8
  import { I18nextProvider } from 'react-i18next';
9
- import { createMockHit, createMockObservable } from '@cccsaurora/howler-ui/tests/utils';
9
+ import { createMockEvent, createMockHit } from '@cccsaurora/howler-ui/tests/utils';
10
10
  import { beforeEach, describe, expect, it, vi } from 'vitest';
11
11
  import CreateCaseModal from './CreateCaseModal';
12
12
  // ---------------------------------------------------------------------------
@@ -48,7 +48,7 @@ const MOCK_HIT_1 = createMockHit({
48
48
  const MOCK_HIT_2 = createMockHit({
49
49
  howler: { id: 'hit-002', analytic: 'AnalyticTwo', status: 'open' }
50
50
  });
51
- const MOCK_OBSERVABLE = createMockObservable({
51
+ const MOCK_EVENT = createMockEvent({
52
52
  howler: { id: 'obs-001' }
53
53
  });
54
54
  const MOCK_CONFIG = {
@@ -115,19 +115,19 @@ describe('CreateCaseModal', () => {
115
115
  expect(screen.getByText(MOCK_HIT_1.howler.analytic)).toBeInTheDocument();
116
116
  expect(screen.getByText(MOCK_HIT_2.howler.analytic)).toBeInTheDocument();
117
117
  });
118
- it('renders a row for an observable record', () => {
119
- renderModal([MOCK_OBSERVABLE]);
120
- expect(screen.getByText('Observable')).toBeInTheDocument();
118
+ it('renders a row for an event record', () => {
119
+ renderModal([MOCK_EVENT]);
120
+ expect(screen.getByText('Event')).toBeInTheDocument();
121
121
  });
122
122
  it('pre-populates the title for a hit with analytic and id', () => {
123
123
  renderModal([MOCK_HIT_1]);
124
124
  const titleInputs = screen.getAllByPlaceholderText(i18n.t('modal.cases.add_to_case.title'));
125
125
  expect(titleInputs[0]).toHaveValue(`${MOCK_HIT_1.howler.analytic} (${MOCK_HIT_1.howler.id})`);
126
126
  });
127
- it('pre-populates the title for an observable with Observable and id', () => {
128
- renderModal([MOCK_OBSERVABLE]);
127
+ it('pre-populates the title for an event with Event and id', () => {
128
+ renderModal([MOCK_EVENT]);
129
129
  const titleInput = screen.getByPlaceholderText(i18n.t('modal.cases.add_to_case.title'));
130
- expect(titleInput).toHaveValue(`Observable (${MOCK_OBSERVABLE.howler.id})`);
130
+ expect(titleInput).toHaveValue(`Event (${MOCK_EVENT.howler.id})`);
131
131
  });
132
132
  it('shows the alert placement section label when there are records', () => {
133
133
  renderModal([MOCK_HIT_1]);
@@ -302,14 +302,14 @@ describe('CreateCaseModal', () => {
302
302
  path: `investigations/${MOCK_HIT_1.howler.analytic} (${MOCK_HIT_1.howler.id})`
303
303
  }));
304
304
  });
305
- it('uses observable __index for observable records', async () => {
305
+ it('uses event __index for event records', async () => {
306
306
  const api = (await import('api')).default;
307
307
  mockDispatchApi.mockResolvedValue({ case_id: 'new-case-id' });
308
- renderModal([MOCK_OBSERVABLE]);
308
+ renderModal([MOCK_EVENT]);
309
309
  await fillCaseMetadata(user);
310
310
  await user.click(screen.getByRole('button', { name: i18n.t('confirm') }));
311
311
  await waitFor(() => expect(mockClose).toHaveBeenCalled());
312
- expect(api.v2.case.items.post).toHaveBeenCalledWith('new-case-id', expect.objectContaining({ type: 'observable' }));
312
+ expect(api.v2.case.items.post).toHaveBeenCalledWith('new-case-id', expect.objectContaining({ type: 'event' }));
313
313
  });
314
314
  it('does not call items.post when there are no records', async () => {
315
315
  const api = (await import('api')).default;
@@ -348,11 +348,11 @@ describe('CreateCaseModal', () => {
348
348
  expect(titleInputs[0]).toHaveValue('Edited Title');
349
349
  expect(titleInputs[1]).toHaveValue(`${MOCK_HIT_2.howler.analytic} (${MOCK_HIT_2.howler.id})`);
350
350
  });
351
- it('mixed hit and observable records each get correct default titles', () => {
352
- renderModal([MOCK_HIT_1, MOCK_OBSERVABLE]);
351
+ it('mixed hit and event records each get correct default titles', () => {
352
+ renderModal([MOCK_HIT_1, MOCK_EVENT]);
353
353
  const titleInputs = screen.getAllByPlaceholderText(i18n.t('modal.cases.add_to_case.title'));
354
354
  expect(titleInputs[0]).toHaveValue(`${MOCK_HIT_1.howler.analytic} (${MOCK_HIT_1.howler.id})`);
355
- expect(titleInputs[1]).toHaveValue(`Observable (${MOCK_OBSERVABLE.howler.id})`);
355
+ expect(titleInputs[1]).toHaveValue(`Event (${MOCK_EVENT.howler.id})`);
356
356
  });
357
357
  });
358
358
  });
@@ -1,7 +1,7 @@
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 { RecordEntry } from './types';
5
- export declare const defaultTitle: (record: Hit | Observable) => string;
5
+ export declare const defaultTitle: (record: Hit | Event) => string;
6
6
  export declare const useFolderOptions: (selectedCase: Case | null) => string[];
7
- export declare const useRecordEntries: (records: (Hit | Observable)[]) => readonly [RecordEntry[], (index: number, field: "title" | "path", value: string) => void];
7
+ export declare const useRecordEntries: (records: (Hit | Event)[]) => readonly [RecordEntry[], (index: number, field: "title" | "path", value: string) => void];
@@ -3,7 +3,7 @@ export const defaultTitle = (record) => {
3
3
  if (record.__index === 'hit') {
4
4
  return `${record.howler.analytic} (${record.howler.id})`;
5
5
  }
6
- return `Observable (${record.howler.id})`;
6
+ return `Event (${record.howler.id})`;
7
7
  };
8
8
  export const useFolderOptions = (selectedCase) => {
9
9
  return useMemo(() => {
@@ -1,5 +1,8 @@
1
+ import type { Event } from 'models/entities/generated/Event';
2
+ import type { Hit } from 'models/entities/generated/Hit';
3
+
1
4
  export interface RecordEntry {
2
- record: Hit | Observable;
5
+ record: Hit | Event;
3
6
  path: string;
4
7
  title: string;
5
8
  }
@@ -0,0 +1,7 @@
1
+ import type { Event } from '@cccsaurora/howler-ui/models/entities/generated/Event';
2
+ import { type FC } from 'react';
3
+ declare const EventViewer: FC<{
4
+ event?: Event;
5
+ eventId?: string;
6
+ }>;
7
+ export default EventViewer;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Skeleton } from '@mui/material';
3
+ import api from '@cccsaurora/howler-ui/api';
4
+ import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
5
+ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
6
+ import { useEffect, useState } from 'react';
7
+ const EventViewer = ({ event: provided, eventId }) => {
8
+ const { dispatchApi } = useMyApi();
9
+ const [event, setEvent] = useState(null);
10
+ useEffect(() => {
11
+ if (provided) {
12
+ setEvent(provided);
13
+ }
14
+ }, [provided]);
15
+ useEffect(() => {
16
+ if (eventId) {
17
+ dispatchApi(api.v2.search.post('event', { query: `howler.id:${eventId}`, rows: 1 }), {
18
+ throwError: false
19
+ }).then(res => setEvent(res.items[0]));
20
+ }
21
+ }, [dispatchApi, eventId]);
22
+ if (!event) {
23
+ return;
24
+ }
25
+ return _jsx(Box, { p: 1, children: event ? _jsx(ObjectDetails, { obj: event }) : _jsx(Skeleton, { height: 120 }) });
26
+ };
27
+ export default EventViewer;
@@ -14,9 +14,9 @@ import VSBoxContent from '@cccsaurora/howler-ui/components/elements/addons/layou
14
14
  import VSBoxHeader from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBoxHeader';
15
15
  import SearchPagination from '@cccsaurora/howler-ui/components/elements/addons/search/SearchPagination';
16
16
  import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
17
+ import EventCard from '@cccsaurora/howler-ui/components/elements/event/EventCard';
17
18
  import HitCard from '@cccsaurora/howler-ui/components/elements/hit/HitCard';
18
19
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
19
- import ObservableCard from '@cccsaurora/howler-ui/components/elements/observable/ObservableCard';
20
20
  import RecordContextMenu from '@cccsaurora/howler-ui/components/elements/record/RecordContextMenu';
21
21
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
22
22
  import useRecordSelection from '@cccsaurora/howler-ui/components/hooks/useRecordSelection';
@@ -26,7 +26,7 @@ import { useTranslation } from 'react-i18next';
26
26
  import { Link } from 'react-router-dom';
27
27
  import { useContextSelector } from 'use-context-selector';
28
28
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
29
- import { isHit, isObservable } from '@cccsaurora/howler-ui/utils/typeUtils';
29
+ import { isEvent, isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
30
30
  import LayoutSettings from './LayoutSettings';
31
31
  import QuerySettings from './QuerySettings';
32
32
  import RecordQuery from './RecordQuery';
@@ -73,7 +73,7 @@ const Item = memo(({ record, onClick }) => {
73
73
  boxShadow: `0px 0px 5px 2px ${theme.palette.primary.main}`
74
74
  }
75
75
  }
76
- ], children: [isHit(record) && _jsx(HitCard, { id: record.howler.id, layout: layout }), isObservable(record) && _jsx(ObservableCard, { id: record.howler.id, observable: record })] }));
76
+ ], children: [isHit(record) && _jsx(HitCard, { id: record.howler.id, layout: layout }), isEvent(record) && _jsx(EventCard, { id: record.howler.id, event: record })] }));
77
77
  });
78
78
  const SearchPane = () => {
79
79
  const { t } = useTranslation();