@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
@@ -1,5 +1,6 @@
1
1
  import type { HowlerSearchRequest, HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
2
2
  import * as facet from './facet';
3
+ export type SearchIndex = 'hit' | 'event' | 'case';
3
4
  export declare const uri: (indexes: string[]) => string;
4
- export declare const post: <T = any>(indexes: string | string[], request?: HowlerSearchRequest) => Promise<HowlerSearchResponse<T>>;
5
+ export declare const post: <T = any>(indexes: SearchIndex | SearchIndex[], request?: HowlerSearchRequest) => Promise<HowlerSearchResponse<T>>;
5
6
  export { facet };
@@ -13,8 +13,8 @@ export const post = (indexes, request) => {
13
13
  if (typeof indexes === 'string') {
14
14
  indexes = indexes.split(',').filter(identity);
15
15
  }
16
- if (indexes.some(index => !['hit', 'observable', 'case'].includes(index))) {
17
- throw new Error('Only hit, case, and observable indexes should be used currently.');
16
+ if (indexes.some(index => !['hit', 'event', 'case'].includes(index))) {
17
+ throw new Error('Only hit, case, and event indexes should be used currently.');
18
18
  }
19
19
  if (indexes.length < 1) {
20
20
  throw new Error('indexes must have length of at least 1.');
@@ -1,5 +1,5 @@
1
+ import type { SearchIndex } from '@cccsaurora/howler-ui/api/v2/search';
1
2
  import type { FC, PropsWithChildren } from 'react';
2
- export type SearchIndex = 'hit' | 'observable' | 'case';
3
3
  export interface ParameterContextType {
4
4
  selected?: string;
5
5
  query?: string;
@@ -690,15 +690,15 @@ describe('ParameterContext', () => {
690
690
  expect(hook.result.current).toEqual(['hit']);
691
691
  });
692
692
  it('should initialize with single index from URL', async () => {
693
- mockSearchParams.set('index', 'observable');
693
+ mockSearchParams.set('index', 'event');
694
694
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
695
- expect(hook.result.current).toEqual(['observable']);
695
+ expect(hook.result.current).toEqual(['event']);
696
696
  });
697
697
  it('should initialize with multiple indexes from URL', async () => {
698
698
  mockSearchParams.append('index', 'hit');
699
- mockSearchParams.append('index', 'observable');
699
+ mockSearchParams.append('index', 'event');
700
700
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
701
- expect(hook.result.current).toEqual(['hit', 'observable']);
701
+ expect(hook.result.current).toEqual(['hit', 'event']);
702
702
  });
703
703
  it('should deduplicate repeated index values from URL', async () => {
704
704
  mockSearchParams.append('index', 'hit');
@@ -713,10 +713,10 @@ describe('ParameterContext', () => {
713
713
  addIndex: ctx.addIndex
714
714
  })), { wrapper: Wrapper });
715
715
  await act(async () => {
716
- hook.result.current.addIndex('observable');
716
+ hook.result.current.addIndex('event');
717
717
  });
718
718
  await waitFor(() => {
719
- expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
719
+ expect(hook.result.current.indexes).toEqual(['hit', 'event']);
720
720
  });
721
721
  });
722
722
  it('should not add a duplicate index', async () => {
@@ -735,7 +735,7 @@ describe('ParameterContext', () => {
735
735
  describe('removeIndex', () => {
736
736
  it('should remove an index from the list', async () => {
737
737
  mockSearchParams.append('index', 'hit');
738
- mockSearchParams.append('index', 'observable');
738
+ mockSearchParams.append('index', 'event');
739
739
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
740
740
  indexes: ctx.indexes,
741
741
  removeIndex: ctx.removeIndex
@@ -744,7 +744,7 @@ describe('ParameterContext', () => {
744
744
  hook.result.current.removeIndex('hit');
745
745
  });
746
746
  await waitFor(() => {
747
- expect(hook.result.current.indexes).toEqual(['observable']);
747
+ expect(hook.result.current.indexes).toEqual(['event']);
748
748
  });
749
749
  });
750
750
  it('should do nothing when removing a nonexistent index', async () => {
@@ -753,7 +753,7 @@ describe('ParameterContext', () => {
753
753
  removeIndex: ctx.removeIndex
754
754
  })), { wrapper: Wrapper });
755
755
  await act(async () => {
756
- hook.result.current.removeIndex('observable');
756
+ hook.result.current.removeIndex('event');
757
757
  });
758
758
  await waitFor(() => {
759
759
  expect(hook.result.current.indexes).toEqual(['hit']);
@@ -776,16 +776,16 @@ describe('ParameterContext', () => {
776
776
  describe('setIndex', () => {
777
777
  it('should update the index at the specified position', async () => {
778
778
  mockSearchParams.append('index', 'hit');
779
- mockSearchParams.append('index', 'observable');
779
+ mockSearchParams.append('index', 'event');
780
780
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
781
781
  indexes: ctx.indexes,
782
782
  setIndex: ctx.setIndex
783
783
  })), { wrapper: Wrapper });
784
784
  await act(async () => {
785
- hook.result.current.setIndex(0, 'observable');
785
+ hook.result.current.setIndex(0, 'event');
786
786
  });
787
787
  await waitFor(() => {
788
- expect(hook.result.current.indexes).toEqual(['observable', 'observable']);
788
+ expect(hook.result.current.indexes).toEqual(['event', 'event']);
789
789
  });
790
790
  });
791
791
  it('should do nothing when index is out of bounds', async () => {
@@ -794,7 +794,7 @@ describe('ParameterContext', () => {
794
794
  setIndex: ctx.setIndex
795
795
  })), { wrapper: Wrapper });
796
796
  await act(async () => {
797
- hook.result.current.setIndex(5, 'observable');
797
+ hook.result.current.setIndex(5, 'event');
798
798
  });
799
799
  await waitFor(() => {
800
800
  expect(hook.result.current.indexes).toEqual(['hit']);
@@ -806,7 +806,7 @@ describe('ParameterContext', () => {
806
806
  setIndex: ctx.setIndex
807
807
  })), { wrapper: Wrapper });
808
808
  await act(async () => {
809
- hook.result.current.setIndex(-1, 'observable');
809
+ hook.result.current.setIndex(-1, 'event');
810
810
  });
811
811
  await waitFor(() => {
812
812
  expect(hook.result.current.indexes).toEqual(['hit']);
@@ -820,10 +820,10 @@ describe('ParameterContext', () => {
820
820
  setIndexes: ctx.setIndexes
821
821
  })), { wrapper: Wrapper });
822
822
  await act(async () => {
823
- hook.result.current.setIndexes(['observable']);
823
+ hook.result.current.setIndexes(['event']);
824
824
  });
825
825
  await waitFor(() => {
826
- expect(hook.result.current.indexes).toEqual(['observable']);
826
+ expect(hook.result.current.indexes).toEqual(['event']);
827
827
  });
828
828
  });
829
829
  it('should deduplicate values when setting all indexes', async () => {
@@ -832,10 +832,10 @@ describe('ParameterContext', () => {
832
832
  setIndexes: ctx.setIndexes
833
833
  })), { wrapper: Wrapper });
834
834
  await act(async () => {
835
- hook.result.current.setIndexes(['hit', 'hit', 'observable']);
835
+ hook.result.current.setIndexes(['hit', 'hit', 'event']);
836
836
  });
837
837
  await waitFor(() => {
838
- expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
838
+ expect(hook.result.current.indexes).toEqual(['hit', 'event']);
839
839
  });
840
840
  });
841
841
  it('should set to empty array', async () => {
@@ -854,7 +854,7 @@ describe('ParameterContext', () => {
854
854
  describe('resetIndexes', () => {
855
855
  it('should reset indexes to default ["hit"]', async () => {
856
856
  mockSearchParams.append('index', 'hit');
857
- mockSearchParams.append('index', 'observable');
857
+ mockSearchParams.append('index', 'event');
858
858
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
859
859
  indexes: ctx.indexes,
860
860
  resetIndexes: ctx.resetIndexes
@@ -913,13 +913,13 @@ describe('ParameterContext', () => {
913
913
  })), { wrapper: Wrapper });
914
914
  await act(async () => {
915
915
  hook.result.current.resetIndexes();
916
- hook.result.current.addIndex('observable');
916
+ hook.result.current.addIndex('event');
917
917
  });
918
918
  await waitFor(() => {
919
919
  expect(mockSetParams).toHaveBeenCalled();
920
920
  const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
921
921
  const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
922
- expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
922
+ expect(urlParams.getAll('index')).toEqual(['hit', 'event']);
923
923
  });
924
924
  });
925
925
  it('should sync multiple indexes to URL as multiple index params', async () => {
@@ -927,18 +927,18 @@ describe('ParameterContext', () => {
927
927
  addIndex: ctx.addIndex
928
928
  })), { wrapper: Wrapper });
929
929
  await act(async () => {
930
- hook.result.current.addIndex('observable');
930
+ hook.result.current.addIndex('event');
931
931
  });
932
932
  await waitFor(() => {
933
933
  expect(mockSetParams).toHaveBeenCalled();
934
934
  const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
935
935
  const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
936
- expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
936
+ expect(urlParams.getAll('index')).toEqual(['hit', 'event']);
937
937
  });
938
938
  });
939
939
  it('should remove all index params from URL when state resets to default', async () => {
940
940
  mockSearchParams.append('index', 'hit');
941
- mockSearchParams.append('index', 'observable');
941
+ mockSearchParams.append('index', 'event');
942
942
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
943
943
  resetIndexes: ctx.resetIndexes
944
944
  })), { wrapper: Wrapper });
@@ -953,7 +953,7 @@ describe('ParameterContext', () => {
953
953
  });
954
954
  });
955
955
  it('should remove index param from URL when state returns to default', async () => {
956
- mockSearchParams.set('index', 'observable');
956
+ mockSearchParams.set('index', 'event');
957
957
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
958
958
  setIndexes: ctx.setIndexes
959
959
  })), { wrapper: Wrapper });
@@ -1,18 +1,18 @@
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 { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
4
4
  import type { FC, PropsWithChildren } from 'react';
5
5
  export interface RecordContextType {
6
6
  records: {
7
- [index: string]: Hit | Observable;
7
+ [index: string]: Hit | Event;
8
8
  };
9
- selectedRecords: (Hit | Observable)[];
9
+ selectedRecords: (Hit | Event)[];
10
10
  addRecordToSelection: (id: string) => void;
11
11
  removeRecordFromSelection: (id: string) => void;
12
12
  clearSelectedRecords: (except?: string) => void;
13
- loadRecords: (hits: (Hit | Observable)[]) => void;
14
- updateRecord: (newHit: Hit | Observable) => void;
15
- getRecord: (id: string, force?: boolean) => Promise<WithMetadata<Hit | Observable>>;
13
+ loadRecords: (hits: (Hit | Event)[]) => void;
14
+ updateRecord: (newHit: Hit | Event) => void;
15
+ getRecord: (id: string, force?: boolean) => Promise<WithMetadata<Hit | Event>>;
16
16
  }
17
17
  export declare const RecordContext: import("use-context-selector").Context<RecordContextType>;
18
18
  /**
@@ -51,7 +51,11 @@ const RecordProvider = ({ children }) => {
51
51
  */
52
52
  const getRecord = useCallback(async (id, force = false) => {
53
53
  if (!recordRequests.current[id] || force) {
54
- recordRequests.current[id] = dispatchApi(api.hit.get(id, ['template', 'dossiers', 'analytic', 'overview']));
54
+ recordRequests.current[id] = dispatchApi(api.v2.search.post(['hit', 'event'], {
55
+ query: `howler.id:${id}`,
56
+ rows: 1,
57
+ metadata: ['template', 'dossiers', 'analytic', 'overview']
58
+ })).then(result => result.items[0]);
55
59
  const newRecord = await recordRequests.current[id];
56
60
  setRecords(_records => ({ ..._records, [id]: newRecord }));
57
61
  }
@@ -1,7 +1,7 @@
1
1
  import type { HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
2
2
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
3
+ import type { Event } from '@cccsaurora/howler-ui/models/entities/generated/Event';
3
4
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
4
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
5
5
  import type { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
6
6
  import { type Dispatch, type FC, type PropsWithChildren, type SetStateAction } from 'react';
7
7
  export interface QueryEntry {
@@ -11,7 +11,7 @@ export interface RecordSearchContextType {
11
11
  displayType: 'list' | 'grid';
12
12
  searching: boolean;
13
13
  error: string | null;
14
- response: HowlerSearchResponse<WithMetadata<Hit | Observable>> | null;
14
+ response: HowlerSearchResponse<WithMetadata<Hit | Event>> | null;
15
15
  fzfSearch: boolean;
16
16
  setDisplayType: (type: 'list' | 'grid') => void;
17
17
  setFzfSearch: Dispatch<SetStateAction<boolean>>;
@@ -1,6 +1,6 @@
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
- obj: Hit | Observable;
4
+ obj: Hit | Event;
5
5
  }>;
6
6
  export default _default;
@@ -1,11 +1,11 @@
1
1
  import { type TypographyProps } from '@mui/material';
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
  export type PluginTypographyProps = TypographyProps & {
5
5
  value: string;
6
6
  context: string;
7
7
  field?: string;
8
- obj?: Hit | Observable;
8
+ obj?: Hit | Event;
9
9
  };
10
10
  declare const _default: import("react").NamedExoticComponent<PluginTypographyProps>;
11
11
  export default _default;
@@ -0,0 +1,6 @@
1
+ import type { Event } from '@cccsaurora/howler-ui/models/entities/generated/Event';
2
+ declare const _default: import("react").NamedExoticComponent<{
3
+ id?: string;
4
+ event?: Event;
5
+ }>;
6
+ export default _default;
@@ -4,19 +4,19 @@ import { RecordContext } from '@cccsaurora/howler-ui/components/app/providers/Re
4
4
  import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
5
5
  import { memo, useEffect } from 'react';
6
6
  import { useContextSelector } from 'use-context-selector';
7
- import ObservablePreview from './ObservablePreview';
8
- const ObservableCard = ({ id, observable: _observable }) => {
7
+ import EventPreview from './EventPreview';
8
+ const EventCard = ({ id, event: _event }) => {
9
9
  const getRecord = useContextSelector(RecordContext, ctx => ctx.getRecord);
10
- const observable = useContextSelector(RecordContext, ctx => _observable ?? ctx.records[id]);
10
+ const event = useContextSelector(RecordContext, ctx => _event ?? ctx.records[id]);
11
11
  useEffect(() => {
12
- if (!observable) {
12
+ if (!event) {
13
13
  getRecord(id);
14
14
  }
15
15
  // eslint-disable-next-line react-hooks/exhaustive-deps
16
16
  }, [id]);
17
- if (!observable) {
17
+ if (!event) {
18
18
  return _jsx(Skeleton, { variant: "rounded", height: "200px" });
19
19
  }
20
- return (_jsx(HowlerCard, { sx: { position: 'relative' }, children: _jsx(CardContent, { children: _jsx(ObservablePreview, { observable: observable }) }) }));
20
+ return (_jsx(HowlerCard, { sx: { position: 'relative' }, children: _jsx(CardContent, { children: _jsx(EventPreview, { event: event }) }) }));
21
21
  };
22
- export default memo(ObservableCard);
22
+ export default memo(EventCard);
@@ -1,6 +1,6 @@
1
- import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
1
+ import type { Event } from '@cccsaurora/howler-ui/models/entities/generated/Event';
2
2
  type PreviewProps = {
3
- observable: Observable;
3
+ event: Event;
4
4
  };
5
5
  declare const _default: import("react").NamedExoticComponent<PreviewProps>;
6
6
  export default _default;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { OpenInNew } from '@mui/icons-material';
3
+ import { Chip, IconButton, Stack, Typography, useTheme } from '@mui/material';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Link } from 'react-router-dom';
7
+ const EventPreview = ({ event }) => {
8
+ const { t } = useTranslation();
9
+ const theme = useTheme();
10
+ return (_jsx(Stack, { flex: 1, spacing: 1, sx: { overflow: 'hidden', borderBottom: `thin solid ${theme.palette.divider}`, pb: 1, mb: 0 }, children: _jsxs(Stack, { children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "body1", fontWeight: "bold", children: event.event.provider }), _jsx("div", { style: { flex: 1 } }), _jsx(Chip, { label: event.event.kind }), event.event.reference && (_jsx(IconButton, { size: "small", component: Link, to: event.event.reference, sx: { opacity: 1 }, target: "_blank", rel: "noopener noreferrer", children: _jsx(OpenInNew, { fontSize: "small" }) }))] }), event.event.type && (_jsxs(Typography, { variant: "caption", children: [t('event.type'), " - ", event.event.type.join(', ')] })), event.event.module && (_jsxs(Typography, { variant: "caption", children: [t('event.module'), " - ", event.event.module] }))] }) }));
11
+ };
12
+ export default memo(EventPreview);
@@ -1,6 +1,8 @@
1
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
1
2
  import type { HitLayout } from './HitLayout';
2
3
  declare const _default: import("react").NamedExoticComponent<{
3
4
  id?: string;
5
+ hit?: Hit;
4
6
  lazy?: boolean;
5
7
  layout: HitLayout;
6
8
  readOnly?: boolean;
@@ -7,9 +7,9 @@ import HowlerCard from '../display/HowlerCard';
7
7
  import HitBanner from './HitBanner';
8
8
  import HitLabels from './HitLabels';
9
9
  import HitOutline from './HitOutline';
10
- const HitCard = ({ id, layout, readOnly = true, lazy = false, elevation }) => {
10
+ const HitCard = ({ id, layout, readOnly = true, lazy = false, elevation, hit: _hit }) => {
11
11
  const getRecord = useContextSelector(RecordContext, ctx => ctx.getRecord);
12
- const hit = useContextSelector(RecordContext, ctx => ctx.records[id]);
12
+ const hit = useContextSelector(RecordContext, ctx => _hit ?? ctx.records[id]);
13
13
  useEffect(() => {
14
14
  if (!hit && !lazy) {
15
15
  getRecord(id);
@@ -1,9 +1,9 @@
1
1
  import type { HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
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 { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
5
5
  declare const _default: import("react").NamedExoticComponent<{
6
- response?: HowlerSearchResponse<WithMetadata<Hit | Observable>>;
6
+ response?: HowlerSearchResponse<WithMetadata<Hit | Event>>;
7
7
  execute?: boolean;
8
8
  onStart?: () => void;
9
9
  onComplete?: () => void;
@@ -3,13 +3,13 @@ import { Link as LinkIcon } from '@mui/icons-material';
3
3
  import { alpha, Box, Chip, Divider, Stack, useTheme } from '@mui/material';
4
4
  import CasePreview from '@cccsaurora/howler-ui/components/elements/case/CasePreview';
5
5
  import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
6
- import ObservablePreview from '@cccsaurora/howler-ui/components/elements/observable/ObservablePreview';
6
+ import EventPreview from '@cccsaurora/howler-ui/components/elements/event/EventPreview';
7
7
  import useRelatedRecords from '@cccsaurora/howler-ui/components/hooks/useRelatedRecords';
8
8
  import { identity, uniq } from 'lodash-es';
9
9
  import { memo, useMemo, useState } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import { Link } from 'react-router-dom';
12
- import { isCase, isHit, isObservable } from '@cccsaurora/howler-ui/utils/typeUtils';
12
+ import { isCase, isEvent, isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
13
13
  import HitPreview from '../HitPreview';
14
14
  const RecordLink = ({ to, ariaLabel, children }) => {
15
15
  const theme = useTheme();
@@ -54,9 +54,9 @@ const RelatedRecords = ({ hit }) => {
54
54
  const key = entry.case_id;
55
55
  return (_jsx(RecordLink, { to: `/cases/${key}`, ariaLabel: t('hit.header.view.case', { id: key }), children: _jsx(CasePreview, { case: entry }) }, key));
56
56
  }
57
- else if (isObservable(entry)) {
57
+ else if (isEvent(entry)) {
58
58
  const key = entry.howler.id;
59
- return (_jsx(RecordLink, { to: `/observables/${key}`, ariaLabel: t('hit.header.view.observable', { id: key }), children: _jsx(ObservablePreview, { observable: entry }) }, key));
59
+ return (_jsx(RecordLink, { to: `/events/${key}`, ariaLabel: t('hit.header.view.event', { id: key }), children: _jsx(EventPreview, { event: entry }) }, key));
60
60
  }
61
61
  })] })] }));
62
62
  };
@@ -1,9 +1,9 @@
1
1
  import type { HowlerUser } from '@cccsaurora/howler-ui/models/entities/HowlerUser';
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 FC } from 'react';
5
5
  interface RecordCommentsProps {
6
- record: Hit | Observable;
6
+ record: Hit | Event;
7
7
  users: {
8
8
  [id: string]: HowlerUser;
9
9
  };
@@ -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 RecordRelated: FC<{
5
- record: Hit | Observable;
5
+ record: Hit | Event;
6
6
  }>;
7
7
  export default RecordRelated;
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Stack, Tab, Tabs, useTheme } from '@mui/material';
3
- import ObservableCard from '@cccsaurora/howler-ui/components/elements/observable/ObservableCard';
3
+ import EventCard from '@cccsaurora/howler-ui/components/elements/event/EventCard';
4
4
  import useRelatedRecords from '@cccsaurora/howler-ui/components/hooks/useRelatedRecords';
5
5
  import { groupBy } from 'lodash-es';
6
6
  import { useMemo, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Link } from 'react-router-dom';
9
- import { isCase, isHit, isObservable } from '@cccsaurora/howler-ui/utils/typeUtils';
9
+ import { isCase, isEvent, isHit } from '@cccsaurora/howler-ui/utils/typeUtils';
10
10
  import CaseCard from '../case/CaseCard';
11
11
  import HitCard from '../hit/HitCard';
12
12
  import { HitLayout } from '../hit/HitLayout';
@@ -22,13 +22,13 @@ const RecordRelated = ({ record }) => {
22
22
  hasLinks && 'links',
23
23
  groups.hit?.length > 0 && 'hit',
24
24
  groups.case?.length > 0 && 'case',
25
- groups.observable?.length > 0 && 'observable'
25
+ groups.event?.length > 0 && 'event'
26
26
  ].filter(Boolean);
27
27
  const [activeTab, setActiveTab] = useState(false);
28
28
  const currentTab = activeTab !== false && tabs.includes(activeTab) ? activeTab : (tabs[0] ?? false);
29
29
  if (!record) {
30
30
  return null;
31
31
  }
32
- return (_jsxs(Box, { sx: { borderTop: `thin solid ${theme.palette.divider}`, height: '100%', flex: 1, mr: 2, pb: 2 }, children: [_jsxs(Tabs, { value: currentTab, onChange: (_, v) => setActiveTab(v), variant: "scrollable", scrollButtons: "auto", children: [hasLinks && _jsx(Tab, { value: "links", label: t('hit.related.tab.links') }), groups.hit?.length > 0 && _jsx(Tab, { value: "hit", label: t('hit.related.tab.hit') }), groups.case?.length > 0 && _jsx(Tab, { value: "case", label: t('hit.related.tab.case') }), groups.observable?.length > 0 && _jsx(Tab, { value: "observable", label: t('hit.related.tab.observable') })] }), currentTab === 'links' && (_jsx(Box, { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: 1, pt: 1, children: record.howler.links.map(l => (_jsx(RelatedLink, { ...l }, l.title + l.href))) })), currentTab === 'hit' && (_jsx(Stack, { spacing: 1, pt: 1, children: records.filter(isHit).map(h => (_jsx(Link, { to: `/hits/${h.howler.id}`, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: 'none' }, children: _jsx(HitCard, { id: h.howler.id, layout: HitLayout.NORMAL }) }, h.howler.id))) })), currentTab === 'case' && (_jsx(Stack, { spacing: 1, pt: 1, children: records.filter(isCase).map(c => (_jsx(Link, { to: `/cases/${c.case_id}`, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: 'none' }, children: _jsx(CaseCard, { case: c }) }, c.case_id))) })), currentTab === 'observable' && (_jsx(Stack, { spacing: 1, pt: 1, children: records.filter(isObservable).map(o => (_jsx(Link, { to: `/observables/${o.howler.id}`, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: 'none' }, children: _jsx(ObservableCard, { observable: o }) }, o.howler.id))) }))] }));
32
+ return (_jsxs(Box, { sx: { borderTop: `thin solid ${theme.palette.divider}`, height: '100%', flex: 1, mr: 2, pb: 2 }, children: [_jsxs(Tabs, { value: currentTab, onChange: (_, v) => setActiveTab(v), variant: "scrollable", scrollButtons: "auto", children: [hasLinks && _jsx(Tab, { value: "links", label: t('hit.related.tab.links') }), groups.hit?.length > 0 && _jsx(Tab, { value: "hit", label: t('hit.related.tab.hit') }), groups.case?.length > 0 && _jsx(Tab, { value: "case", label: t('hit.related.tab.case') }), groups.event?.length > 0 && _jsx(Tab, { value: "event", label: t('hit.related.tab.event') })] }), currentTab === 'links' && (_jsx(Box, { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: 1, pt: 1, children: record.howler.links.map(l => (_jsx(RelatedLink, { ...l }, l.title + l.href))) })), currentTab === 'hit' && (_jsx(Stack, { spacing: 1, pt: 1, children: records.filter(isHit).map(h => (_jsx(Link, { to: `/hits/${h.howler.id}`, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: 'none' }, children: _jsx(HitCard, { id: h.howler.id, layout: HitLayout.NORMAL }) }, h.howler.id))) })), currentTab === 'case' && (_jsx(Stack, { spacing: 1, pt: 1, children: records.filter(isCase).map(c => (_jsx(Link, { to: `/cases/${c.case_id}`, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: 'none' }, children: _jsx(CaseCard, { case: c }) }, c.case_id))) })), currentTab === 'event' && (_jsx(Stack, { spacing: 1, pt: 1, children: records.filter(isEvent).map(o => (_jsx(Link, { to: `/events/${o.howler.id}`, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: 'none' }, children: _jsx(EventCard, { event: o }) }, o.howler.id))) }))] }));
33
33
  };
34
34
  export default RecordRelated;
@@ -1,9 +1,9 @@
1
1
  import type { HowlerUser } from '@cccsaurora/howler-ui/models/entities/HowlerUser';
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 { FC } from 'react';
5
5
  declare const RecordWorklog: FC<{
6
- record: Hit | Observable;
6
+ record: Hit | Event;
7
7
  users: {
8
8
  [id: string]: HowlerUser;
9
9
  };
@@ -1,10 +1,10 @@
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 { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
5
- type MixedRecords = Hit | Observable | Case;
5
+ type MixedRecords = Hit | Event | Case;
6
6
  /**
7
- * Fetches records matching the provided IDs from the hit, observable, and case indexes.
7
+ * Fetches records matching the provided IDs from the hit, event, and case indexes.
8
8
  *
9
9
  * @param ids - List of howler.id / case_id values to look up.
10
10
  * @param enabled - When false the fetch is skipped (e.g. while a panel is closed).
@@ -2,7 +2,7 @@ import api from '@cccsaurora/howler-ui/api';
2
2
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
3
3
  import { useEffect, useState } from 'react';
4
4
  /**
5
- * Fetches records matching the provided IDs from the hit, observable, and case indexes.
5
+ * Fetches records matching the provided IDs from the hit, event, and case indexes.
6
6
  *
7
7
  * @param ids - List of howler.id / case_id values to look up.
8
8
  * @param enabled - When false the fetch is skipped (e.g. while a panel is closed).
@@ -19,7 +19,7 @@ const useRelatedRecords = (ids, enabled = true) => {
19
19
  }
20
20
  (async () => {
21
21
  const joined = ids.join(' OR ');
22
- const result = await dispatchApi(api.v2.search.post('hit,observable,case', {
22
+ const result = await dispatchApi(api.v2.search.post(['hit', 'event', 'case'], {
23
23
  query: `howler.id:(${joined}) OR case_id:(${joined})`
24
24
  }), { throwError: false, showError: true });
25
25
  if (result) {
@@ -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 => {