@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.
- package/api/v2/search/index.d.ts +2 -1
- package/api/v2/search/index.js +2 -2
- package/components/app/providers/ParameterProvider.d.ts +1 -1
- package/components/app/providers/ParameterProvider.test.js +25 -25
- package/components/app/providers/RecordProvider.d.ts +6 -6
- package/components/app/providers/RecordProvider.js +5 -1
- package/components/app/providers/RecordSearchProvider.d.ts +2 -2
- package/components/elements/ObjectDetails.d.ts +2 -2
- package/components/elements/PluginTypography.d.ts +2 -2
- package/components/elements/event/EventCard.d.ts +6 -0
- package/components/elements/{observable/ObservableCard.js → event/EventCard.js} +7 -7
- package/components/elements/{observable/ObservablePreview.d.ts → event/EventPreview.d.ts} +2 -2
- package/components/elements/event/EventPreview.js +12 -0
- package/components/elements/hit/HitCard.d.ts +2 -0
- package/components/elements/hit/HitCard.js +2 -2
- package/components/elements/hit/HitSummary.d.ts +2 -2
- package/components/elements/hit/related/RelatedRecords.js +4 -4
- package/components/elements/record/RecordComments.d.ts +2 -2
- package/components/elements/record/RecordRelated.d.ts +2 -2
- package/components/elements/record/RecordRelated.js +4 -4
- package/components/elements/record/RecordWorklog.d.ts +2 -2
- package/components/hooks/useRelatedRecords.d.ts +3 -3
- package/components/hooks/useRelatedRecords.js +2 -2
- package/components/routes/cases/detail/CaseAssets.d.ts +2 -2
- package/components/routes/cases/detail/CaseAssets.js +2 -2
- package/components/routes/cases/detail/CaseAssets.test.js +4 -4
- package/components/routes/cases/detail/CaseDashboard.js +2 -2
- package/components/routes/cases/detail/CaseRules.test.js +2 -2
- package/components/routes/cases/detail/CaseTimeline.js +5 -5
- package/components/routes/cases/detail/CaseTimeline.test.js +7 -7
- package/components/routes/cases/detail/CreateRuleDialog.js +4 -4
- package/components/routes/cases/detail/CreateRuleDialog.test.js +5 -5
- package/components/routes/cases/detail/ItemPage.js +1 -1
- package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +2 -2
- package/components/routes/cases/detail/assets/Asset.d.ts +1 -1
- package/components/routes/cases/detail/assets/Asset.test.js +2 -2
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +1 -1
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +3 -3
- package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +8 -8
- package/components/routes/cases/detail/sidebar/FolderEntry.js +1 -1
- package/components/routes/cases/modals/AddToCaseModal.d.ts +2 -2
- package/components/routes/cases/modals/AddToCaseModal.test.js +11 -11
- package/components/routes/cases/modals/CaseRecordRow.js +1 -1
- package/components/routes/cases/modals/CreateCaseModal.d.ts +2 -2
- package/components/routes/cases/modals/CreateCaseModal.test.js +14 -14
- package/components/routes/cases/modals/hooks.d.ts +3 -3
- package/components/routes/cases/modals/hooks.js +1 -1
- package/components/routes/cases/modals/types.d.ts +4 -1
- package/components/routes/events/EventViewer.d.ts +7 -0
- package/components/routes/events/EventViewer.js +27 -0
- package/components/routes/hits/search/SearchPane.js +3 -3
- package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -2
- package/components/routes/hits/search/grid/RecordRow.d.ts +3 -3
- package/components/routes/hits/search/grid/RecordRow.js +2 -2
- package/components/routes/hits/search/shared/IndexPicker.js +1 -1
- package/components/routes/home/ViewCard.js +4 -4
- package/components/routes/views/ViewComposer.js +2 -2
- package/locales/en/translation.json +4 -4
- package/locales/fr/translation.json +4 -4
- package/models/entities/generated/ECSEvent.d.ts +31 -0
- package/models/entities/generated/Event.d.ts +80 -26
- package/models/entities/generated/{ObservableCloud.d.ts → EventCloud.d.ts} +1 -1
- package/models/entities/generated/{ObservableDestination.d.ts → EventDestination.d.ts} +1 -1
- package/models/entities/generated/{ObservableEmail.d.ts → EventEmail.d.ts} +1 -1
- package/models/entities/generated/{ObservableFile.d.ts → EventFile.d.ts} +1 -1
- package/models/entities/generated/{ObservableHowler.d.ts → EventHowler.d.ts} +1 -1
- package/models/entities/generated/{ObservableHttp.d.ts → EventHttp.d.ts} +1 -1
- package/models/entities/generated/{ObservableObserver.d.ts → EventObserver.d.ts} +1 -1
- package/models/entities/generated/{ObservableOrganization.d.ts → EventOrganization.d.ts} +1 -1
- package/models/entities/generated/{ObservableProcess.d.ts → EventProcess.d.ts} +1 -1
- package/models/entities/generated/{ObservableSource.d.ts → EventSource.d.ts} +1 -1
- package/models/entities/generated/{ObservableThreat.d.ts → EventThreat.d.ts} +1 -1
- package/models/entities/generated/{ObservableTls.d.ts → EventTls.d.ts} +1 -1
- package/models/entities/generated/Hit.d.ts +2 -2
- package/models/entities/generated/Rule.d.ts +1 -1
- package/package.json +3 -3
- package/plugins/clue/utils.d.ts +2 -2
- package/tests/server-handlers.js +1 -1
- package/tests/utils.d.ts +2 -2
- package/tests/utils.js +3 -3
- package/utils/hitFunctions.d.ts +2 -2
- package/utils/typeUtils.d.ts +2 -2
- package/utils/typeUtils.js +2 -2
- package/components/elements/observable/ObservableCard.d.ts +0 -6
- package/components/elements/observable/ObservablePreview.js +0 -12
- package/components/routes/observables/ObservableViewer.d.ts +0 -7
- package/components/routes/observables/ObservableViewer.js +0 -27
- package/models/entities/generated/Observable.d.ts +0 -85
package/api/v2/search/index.d.ts
CHANGED
|
@@ -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:
|
|
5
|
+
export declare const post: <T = any>(indexes: SearchIndex | SearchIndex[], request?: HowlerSearchRequest) => Promise<HowlerSearchResponse<T>>;
|
|
5
6
|
export { facet };
|
package/api/v2/search/index.js
CHANGED
|
@@ -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', '
|
|
17
|
-
throw new Error('Only hit, case, and
|
|
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.');
|
|
@@ -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', '
|
|
693
|
+
mockSearchParams.set('index', 'event');
|
|
694
694
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
|
|
695
|
-
expect(hook.result.current).toEqual(['
|
|
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', '
|
|
699
|
+
mockSearchParams.append('index', 'event');
|
|
700
700
|
const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
|
|
701
|
-
expect(hook.result.current).toEqual(['hit', '
|
|
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('
|
|
716
|
+
hook.result.current.addIndex('event');
|
|
717
717
|
});
|
|
718
718
|
await waitFor(() => {
|
|
719
|
-
expect(hook.result.current.indexes).toEqual(['hit', '
|
|
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', '
|
|
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(['
|
|
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('
|
|
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', '
|
|
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, '
|
|
785
|
+
hook.result.current.setIndex(0, 'event');
|
|
786
786
|
});
|
|
787
787
|
await waitFor(() => {
|
|
788
|
-
expect(hook.result.current.indexes).toEqual(['
|
|
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, '
|
|
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, '
|
|
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(['
|
|
823
|
+
hook.result.current.setIndexes(['event']);
|
|
824
824
|
});
|
|
825
825
|
await waitFor(() => {
|
|
826
|
-
expect(hook.result.current.indexes).toEqual(['
|
|
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', '
|
|
835
|
+
hook.result.current.setIndexes(['hit', 'hit', 'event']);
|
|
836
836
|
});
|
|
837
837
|
await waitFor(() => {
|
|
838
|
-
expect(hook.result.current.indexes).toEqual(['hit', '
|
|
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', '
|
|
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('
|
|
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', '
|
|
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('
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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 |
|
|
7
|
+
[index: string]: Hit | Event;
|
|
8
8
|
};
|
|
9
|
-
selectedRecords: (Hit |
|
|
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 |
|
|
14
|
-
updateRecord: (newHit: Hit |
|
|
15
|
-
getRecord: (id: string, force?: boolean) => Promise<WithMetadata<Hit |
|
|
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.
|
|
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 |
|
|
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 |
|
|
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 |
|
|
8
|
+
obj?: Hit | Event;
|
|
9
9
|
};
|
|
10
10
|
declare const _default: import("react").NamedExoticComponent<PluginTypographyProps>;
|
|
11
11
|
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
|
|
8
|
-
const
|
|
7
|
+
import EventPreview from './EventPreview';
|
|
8
|
+
const EventCard = ({ id, event: _event }) => {
|
|
9
9
|
const getRecord = useContextSelector(RecordContext, ctx => ctx.getRecord);
|
|
10
|
-
const
|
|
10
|
+
const event = useContextSelector(RecordContext, ctx => _event ?? ctx.records[id]);
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
if (!
|
|
12
|
+
if (!event) {
|
|
13
13
|
getRecord(id);
|
|
14
14
|
}
|
|
15
15
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
16
16
|
}, [id]);
|
|
17
|
-
if (!
|
|
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(
|
|
20
|
+
return (_jsx(HowlerCard, { sx: { position: 'relative' }, children: _jsx(CardContent, { children: _jsx(EventPreview, { event: event }) }) }));
|
|
21
21
|
};
|
|
22
|
-
export default memo(
|
|
22
|
+
export default memo(EventCard);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Event } from '@cccsaurora/howler-ui/models/entities/generated/Event';
|
|
2
2
|
type PreviewProps = {
|
|
3
|
-
|
|
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 |
|
|
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
|
|
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,
|
|
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 (
|
|
57
|
+
else if (isEvent(entry)) {
|
|
58
58
|
const key = entry.howler.id;
|
|
59
|
-
return (_jsx(RecordLink, { to: `/
|
|
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 |
|
|
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 |
|
|
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
|
|
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,
|
|
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.
|
|
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.
|
|
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 |
|
|
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 |
|
|
5
|
+
type MixedRecords = Hit | Event | Case;
|
|
6
6
|
/**
|
|
7
|
-
* Fetches records matching the provided IDs from the hit,
|
|
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,
|
|
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,
|
|
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 |
|
|
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', '
|
|
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', '
|
|
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 {
|
|
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
|
-
|
|
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: '
|
|
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/
|
|
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', '
|
|
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', '
|
|
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 => {
|