@cccsaurora/howler-ui 2.13.0-dev.96 → 2.13.1-dev.184
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/hit/index.d.ts +1 -1
- package/api/hit/index.js +6 -2
- package/api/search/index.d.ts +1 -0
- package/api/view/index.d.ts +1 -1
- package/api/view/index.js +2 -2
- package/commons/components/notification/elements/item/NotificationItemDate.js +2 -2
- package/commons/components/utils/hooks/useEnv.d.ts +1 -1
- package/components/app/App.js +1 -3
- package/components/app/drawers/ApiKeyDrawer.js +4 -4
- package/components/app/hooks/useMatchers.d.ts +9 -0
- package/components/app/hooks/useMatchers.js +82 -0
- package/components/app/hooks/useMatchers.test.d.ts +1 -0
- package/components/app/hooks/useMatchers.test.js +237 -0
- package/components/app/hooks/useTitle.js +5 -4
- package/components/app/providers/AnalyticProvider.d.ts +0 -4
- package/components/app/providers/AnalyticProvider.js +1 -44
- package/components/app/providers/ApiConfigProvider.js +2 -1
- package/components/app/providers/HitProvider.d.ts +2 -1
- package/components/app/providers/HitProvider.js +8 -2
- package/components/app/providers/HitSearchProvider.d.ts +2 -1
- package/components/app/providers/HitSearchProvider.js +2 -1
- package/components/app/providers/SocketProvider.js +1 -1
- package/components/app/providers/ViewProvider.d.ts +1 -1
- package/components/app/providers/ViewProvider.js +3 -3
- package/components/app/providers/ViewProvider.test.d.ts +1 -0
- package/components/app/providers/ViewProvider.test.js +150 -0
- package/components/elements/display/ActionButton.d.ts +8 -0
- package/components/elements/display/ActionButton.js +18 -0
- package/components/elements/display/handlebars/helpers.js +17 -2
- package/components/elements/display/json/JSONViewer.d.ts +2 -0
- package/components/elements/display/json/JSONViewer.js +6 -13
- package/components/elements/hit/HitActions.js +4 -6
- package/components/elements/hit/HitBanner.js +13 -5
- package/components/elements/hit/HitCard.js +0 -5
- package/components/elements/hit/HitComments.js +5 -4
- package/components/elements/hit/HitOutline.d.ts +2 -2
- package/components/elements/hit/HitOutline.js +11 -21
- package/components/elements/hit/HitOverview.js +7 -4
- package/components/elements/hit/HitSummary.d.ts +2 -1
- package/components/elements/hit/HitSummary.js +8 -7
- package/components/elements/hit/aggregate/HitGraph.d.ts +1 -1
- package/components/elements/hit/aggregate/HitGraph.js +7 -7
- package/components/elements/hit/elements/HitTimestamp.js +8 -8
- package/components/elements/hit/related/PivotLink.js +11 -5
- package/components/elements/hit/related/RelatedIcon.d.ts +8 -0
- package/components/elements/hit/related/RelatedIcon.js +32 -0
- package/components/elements/hit/related/RelatedLink.js +4 -25
- package/components/hooks/useMyChart.d.ts +1 -1
- package/components/hooks/useMyChart.js +1 -1
- package/components/routes/advanced/QueryBuilder.js +47 -11
- package/components/routes/advanced/QueryEditor.js +8 -13
- package/components/routes/analytics/AnalyticOverview.d.ts +1 -1
- package/components/routes/analytics/AnalyticOverview.js +1 -1
- package/components/routes/analytics/AnalyticOverviews.d.ts +1 -1
- package/components/routes/analytics/AnalyticOverviews.js +1 -1
- package/components/routes/analytics/AnalyticSearch.js +14 -2
- package/components/routes/analytics/AnalyticTemplates.d.ts +1 -1
- package/components/routes/analytics/AnalyticTemplates.js +10 -7
- package/components/routes/analytics/RuleView.d.ts +1 -1
- package/components/routes/analytics/RuleView.js +1 -1
- package/components/routes/analytics/TriageSettings.d.ts +1 -1
- package/components/routes/analytics/TriageSettings.js +1 -1
- package/components/routes/analytics/widgets/Assessment.d.ts +1 -1
- package/components/routes/analytics/widgets/Assessment.js +1 -1
- package/components/routes/analytics/widgets/Created.d.ts +1 -1
- package/components/routes/analytics/widgets/Created.js +1 -1
- package/components/routes/analytics/widgets/Detection.d.ts +1 -1
- package/components/routes/analytics/widgets/Detection.js +1 -1
- package/components/routes/analytics/widgets/Escalation.d.ts +1 -1
- package/components/routes/analytics/widgets/Escalation.js +1 -1
- package/components/routes/analytics/widgets/Stacked.d.ts +1 -1
- package/components/routes/analytics/widgets/Stacked.js +1 -1
- package/components/routes/analytics/widgets/Status.d.ts +1 -1
- package/components/routes/analytics/widgets/Status.js +1 -1
- package/components/routes/help/SearchDocumentation.js +2 -1
- package/components/routes/help/TemplateDocumentation.js +5 -5
- package/components/routes/hits/search/HitBrowser.js +2 -2
- package/components/routes/hits/search/HitContextMenu.js +6 -7
- package/components/routes/hits/search/HitQuery.js +2 -1
- package/components/routes/hits/search/InformationPane.js +75 -78
- package/components/routes/hits/search/SearchPane.js +3 -9
- package/components/routes/hits/search/grid/AddColumnModal.js +10 -5
- package/components/routes/hits/search/grid/HitGrid.js +6 -5
- package/components/routes/hits/search/shared/CustomSpan.js +6 -6
- package/components/routes/hits/view/HitViewer.js +18 -26
- package/components/routes/home/index.js +4 -4
- package/components/routes/overviews/OverviewViewer.js +33 -31
- package/components/routes/settings/SecuritySection.js +2 -2
- package/components/routes/templates/TemplateViewer.js +27 -36
- package/components/routes/templates/Templates.js +4 -11
- package/components/routes/views/ViewComposer.js +8 -1
- package/components/routes/views/Views.js +25 -9
- package/index.js +7 -0
- package/locales/en/help/search.json +17 -0
- package/locales/en/translation.json +12 -3
- package/locales/fr/help/search.json +17 -0
- package/locales/fr/translation.json +12 -4
- package/models/WithMetadata.d.ts +10 -0
- package/models/WithMetadata.js +1 -0
- package/models/entities/generated/ApiType.d.ts +7 -0
- package/package.json +112 -111
- package/plugins/borealis/components/BorealisTypography.js +4 -2
- package/setupTests.d.ts +1 -0
- package/setupTests.js +12 -0
- package/tests/MockLocalStorage.d.ts +5 -0
- package/tests/MockLocalStorage.js +44 -0
- package/tests/server-handlers.d.ts +5 -0
- package/tests/server-handlers.js +97 -0
- package/tests/server.d.ts +3 -0
- package/tests/server.js +5 -0
- package/utils/constants.js +2 -2
- package/utils/stringUtils.d.ts +1 -0
- package/utils/stringUtils.js +9 -0
- package/utils/utils.js +3 -3
- package/components/app/providers/DossierProvider.d.ts +0 -16
- package/components/app/providers/DossierProvider.js +0 -82
- package/components/app/providers/TemplateProvider.d.ts +0 -14
- package/components/app/providers/TemplateProvider.js +0 -103
package/api/hit/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type HitActionResponse = {
|
|
|
20
20
|
success: boolean;
|
|
21
21
|
};
|
|
22
22
|
export declare const uri: (id?: string) => string;
|
|
23
|
-
export declare const get: (id: string) => Promise<
|
|
23
|
+
export declare const get: <T extends Hit>(id: string, metadata?: string[]) => Promise<T>;
|
|
24
24
|
interface PostResponse {
|
|
25
25
|
valid: Hit[];
|
|
26
26
|
invalid: {
|
package/api/hit/index.js
CHANGED
|
@@ -7,8 +7,12 @@ import * as transition from '@cccsaurora/howler-ui/api/hit/transition';
|
|
|
7
7
|
export const uri = (id) => {
|
|
8
8
|
return id ? joinAllUri(parentUri(), 'hit', id) : joinUri(parentUri(), 'hit');
|
|
9
9
|
};
|
|
10
|
-
export const get = (id) => {
|
|
11
|
-
|
|
10
|
+
export const get = (id, metadata) => {
|
|
11
|
+
const params = new URLSearchParams();
|
|
12
|
+
if (metadata) {
|
|
13
|
+
params.append('metadata', metadata.join(','));
|
|
14
|
+
}
|
|
15
|
+
return hget(uri(id), params);
|
|
12
16
|
};
|
|
13
17
|
export const post = (hits) => {
|
|
14
18
|
return hpost(uri(), hits);
|
package/api/search/index.d.ts
CHANGED
package/api/view/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ import type { View } from '@cccsaurora/howler-ui/models/entities/generated/View'
|
|
|
3
3
|
export declare const uri: (id?: string) => string;
|
|
4
4
|
export declare const get: () => Promise<View[]>;
|
|
5
5
|
export declare const post: (newData: Partial<View>) => Promise<View>;
|
|
6
|
-
export declare const put: (id: string,
|
|
6
|
+
export declare const put: (id: string, partialView: Partial<Omit<View, "view_id">>) => Promise<View>;
|
|
7
7
|
export declare const del: (id: string) => Promise<void>;
|
|
8
8
|
export { favourite };
|
package/api/view/index.js
CHANGED
|
@@ -9,8 +9,8 @@ export const get = () => {
|
|
|
9
9
|
export const post = (newData) => {
|
|
10
10
|
return hpost(uri(), newData);
|
|
11
11
|
};
|
|
12
|
-
export const put = (id,
|
|
13
|
-
return hput(uri(id),
|
|
12
|
+
export const put = (id, partialView) => {
|
|
13
|
+
return hput(uri(id), partialView);
|
|
14
14
|
};
|
|
15
15
|
export const del = (id) => {
|
|
16
16
|
return hdelete(uri(id));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Typography } from '@mui/material';
|
|
3
3
|
import {} from '@cccsaurora/howler-ui/commons/components/notification';
|
|
4
|
-
import
|
|
4
|
+
import dayjs from 'dayjs';
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
export const NotificationItemDate = memo(({ date_published = null }) => {
|
|
8
8
|
const { i18n } = useTranslation(['notification']);
|
|
9
|
-
return !date_published ? null : (_jsx(Typography, { lineHeight: "revert", display: "block", variant: "caption", color: "textSecondary", children: _jsx(_Fragment, { children:
|
|
9
|
+
return !date_published ? null : (_jsx(Typography, { lineHeight: "revert", display: "block", variant: "caption", color: "textSecondary", children: _jsx(_Fragment, { children: dayjs(date_published).locale(i18n.language).fromNow() }) }));
|
|
10
10
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default function useAppEnv(key?: string):
|
|
1
|
+
export default function useAppEnv(key?: string): string | NodeJS.ProcessEnv;
|
package/components/app/App.js
CHANGED
|
@@ -63,7 +63,6 @@ import AnalyticProvider from './providers/AnalyticProvider';
|
|
|
63
63
|
import ApiConfigProvider, { ApiConfigContext } from './providers/ApiConfigProvider';
|
|
64
64
|
import AvatarProvider from './providers/AvatarProvider';
|
|
65
65
|
import CustomPluginProvider from './providers/CustomPluginProvider';
|
|
66
|
-
import DossierProvider from './providers/DossierProvider';
|
|
67
66
|
import FavouriteProvider from './providers/FavouritesProvider';
|
|
68
67
|
import FieldProvider from './providers/FieldProvider';
|
|
69
68
|
import HitProvider from './providers/HitProvider';
|
|
@@ -72,7 +71,6 @@ import ModalProvider from './providers/ModalProvider';
|
|
|
72
71
|
import OverviewProvider from './providers/OverviewProvider';
|
|
73
72
|
import ParameterProvider from './providers/ParameterProvider';
|
|
74
73
|
import SocketProvider from './providers/SocketProvider';
|
|
75
|
-
import TemplateProvider from './providers/TemplateProvider';
|
|
76
74
|
import UserListProvider from './providers/UserListProvider';
|
|
77
75
|
import ViewProvider from './providers/ViewProvider';
|
|
78
76
|
loader.config({ monaco });
|
|
@@ -140,7 +138,7 @@ const MyAppProvider = ({ children }) => {
|
|
|
140
138
|
const mySitemap = useMySitemap();
|
|
141
139
|
const myUser = useMyUser();
|
|
142
140
|
const mySearch = useMySearch();
|
|
143
|
-
return (_jsx(ErrorBoundary, { children: _jsx(AppProvider, { preferences: myPreferences, theme: myTheme, sitemap: mySitemap, user: myUser, search: mySearch, children: _jsx(CustomPluginProvider, { children: _jsx(ErrorBoundary, { children: _jsx(ErrorBoundary, { children: _jsx(
|
|
141
|
+
return (_jsx(ErrorBoundary, { children: _jsx(AppProvider, { preferences: myPreferences, theme: myTheme, sitemap: mySitemap, user: myUser, search: mySearch, children: _jsx(CustomPluginProvider, { children: _jsx(ErrorBoundary, { children: _jsx(ErrorBoundary, { children: _jsx(ViewProvider, { children: _jsx(AvatarProvider, { children: _jsx(ModalProvider, { children: _jsx(FieldProvider, { children: _jsx(LocalStorageProvider, { children: _jsx(SocketProvider, { children: _jsx(HitProvider, { children: _jsx(OverviewProvider, { children: _jsx(AnalyticProvider, { children: _jsx(FavouriteProvider, { children: _jsx(UserListProvider, { children: children }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) }));
|
|
144
142
|
};
|
|
145
143
|
const AppProviderWrapper = () => {
|
|
146
144
|
return (_jsx(I18nextProvider, { i18n: i18n, defaultNS: "translation", children: _jsx(ApiConfigProvider, { children: _jsx(PluginProvider, { pluginStore: howlerPluginStore.pluginStore, children: _jsxs(MyAppProvider, { children: [_jsx(MyApp, {}), _jsx(Modal, {})] }) }) }) }));
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Alert, AlertTitle, Button, Checkbox, Divider, FormControl, FormControlLabel, FormGroup, FormLabel, Stack, TextField, Typography } from '@mui/material';
|
|
3
3
|
import { LocalizationProvider, StaticDateTimePicker } from '@mui/x-date-pickers';
|
|
4
|
-
import {
|
|
4
|
+
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
|
5
5
|
import api from '@cccsaurora/howler-ui/api';
|
|
6
6
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
7
7
|
import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
|
|
8
|
+
import dayjs from 'dayjs';
|
|
8
9
|
import {} from '@cccsaurora/howler-ui/models/entities/generated/ApiType';
|
|
9
|
-
import moment from 'moment';
|
|
10
10
|
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
11
11
|
import { Trans, useTranslation } from 'react-i18next';
|
|
12
12
|
import { ApiConfigContext } from '../providers/ApiConfigProvider';
|
|
@@ -30,7 +30,7 @@ const ApiKeyDrawer = ({ onCreated }) => {
|
|
|
30
30
|
if (!amount || !unit) {
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
-
return
|
|
33
|
+
return dayjs().add(amount, unit);
|
|
34
34
|
}, [amount, unit]);
|
|
35
35
|
const updatePrivs = useCallback((priv) => (ev) => {
|
|
36
36
|
if (ev.target.checked) {
|
|
@@ -62,6 +62,6 @@ const ApiKeyDrawer = ({ onCreated }) => {
|
|
|
62
62
|
useEffect(() => {
|
|
63
63
|
setExpiryDate(maxDate);
|
|
64
64
|
}, [maxDate]);
|
|
65
|
-
return (_jsx(LocalizationProvider, { dateAdapter:
|
|
65
|
+
return (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "column", spacing: 2, sx: { mt: 2 }, children: [_jsx(Typography, { sx: { maxWidth: '500px' }, children: _jsx(Trans, { i18nKey: "app.drawer.user.apikey.description" }) }), amount && unit && dayjs.duration(amount, unit).asSeconds() < dayjs.duration(7, 'days').asSeconds() && (_jsxs(Alert, { severity: "warning", variant: "outlined", sx: { maxWidth: '500px' }, children: [_jsx(AlertTitle, { children: t('app.drawer.user.apikey.limit.title') }), t('app.drawer.user.apikey.limit.description'), " (", maxDate?.format('MMM D HH:mm:ss'), ")"] })), _jsx(TextField, { label: t('app.drawer.user.apikey.field.name'), required: true, fullWidth: true, value: keyName, onChange: onChange }), _jsxs(FormControl, { required: true, children: [_jsx(FormLabel, { component: "legend", children: t('app.drawer.user.apikey.permissions') }), _jsxs(FormGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, children: [_jsx(FormControlLabel, { control: _jsx(Checkbox, { onChange: updatePrivs('R') }), label: t('@cccsaurora/howler-ui/apikey.read') }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { onChange: updatePrivs('W') }), label: t('@cccsaurora/howler-ui/apikey.write') }), _jsx(FormControlLabel, { disabled: privs.includes('E'), control: _jsx(Checkbox, { onChange: updatePrivs('I') }), label: t('@cccsaurora/howler-ui/apikey.impersonate') }), config.configuration.auth.allow_extended_apikeys && (_jsx(FormControlLabel, { disabled: privs.includes('I'), control: _jsx(Checkbox, { onChange: updatePrivs('E') }), label: t('@cccsaurora/howler-ui/apikey.extended') }))] })] }), _jsxs(FormControl, { required: !!maxDate, children: [_jsx(FormLabel, { children: t('app.drawer.user.apikey.expiry.date') }), _jsx(StaticDateTimePicker, { orientation: "landscape", ampm: false, value: expiryDate, onChange: newValue => setExpiryDate(newValue), disablePast: true, maxDate: maxDate, maxTime: maxDate, sx: { backgroundColor: 'transparent', '& > div:first-of-type': { maxWidth: '300px' } } })] }), _jsx(Button, { onClick: onSubmit, disabled: !keyName || (!privs.includes('R') && !privs.includes('W')) || (maxDate && !expiryDate), variant: "outlined", children: _jsx(Trans, { i18nKey: "button.create" }) }), createdKey && (_jsxs(_Fragment, { children: [_jsx(Divider, { orientation: "horizontal" }), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "stretch", children: [_jsx(TextField, { size: "small", value: createdKey, inputProps: { readOnly: true }, fullWidth: true }), _jsx(Button, { variant: "outlined", onClick: onCopy, disabled: !createdKey, children: _jsx(Trans, { i18nKey: "button.copy" }) })] })] }))] }) }));
|
|
66
66
|
};
|
|
67
67
|
export default ApiKeyDrawer;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
|
|
2
|
+
import type { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
|
|
3
|
+
declare const useMatchers: () => {
|
|
4
|
+
getMatchingDossiers: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Dossier").Dossier[]>;
|
|
5
|
+
getMatchingOverview: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Overview").Overview>;
|
|
6
|
+
getMatchingTemplate: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Template").Template>;
|
|
7
|
+
getMatchingAnalytic: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Analytic").Analytic>;
|
|
8
|
+
};
|
|
9
|
+
export default useMatchers;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { has } from 'lodash-es';
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useContextSelector } from 'use-context-selector';
|
|
5
|
+
import { HitContext } from '../providers/HitProvider';
|
|
6
|
+
const useMatchers = () => {
|
|
7
|
+
const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
|
|
8
|
+
const getMatchingTemplate = useCallback(async (hit) => {
|
|
9
|
+
if (!hit) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (has(hit, '__template')) {
|
|
13
|
+
return hit.__template;
|
|
14
|
+
}
|
|
15
|
+
// This is a fallback in case metadata is not included. In most cases templates are shown, the template metadata
|
|
16
|
+
// should also exist
|
|
17
|
+
try {
|
|
18
|
+
return (await getHit(hit.howler.id, true)).__template;
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
console.warn(e);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}, [getHit]);
|
|
25
|
+
const getMatchingOverview = useCallback(async (hit) => {
|
|
26
|
+
if (!hit) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (has(hit, '__overview')) {
|
|
30
|
+
return hit.__overview;
|
|
31
|
+
}
|
|
32
|
+
// This is a fallback in case metadata is not included. In most cases templates are shown, the template metadata
|
|
33
|
+
// should also exist
|
|
34
|
+
try {
|
|
35
|
+
return (await getHit(hit.howler.id, true)).__overview;
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
console.warn(e);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}, [getHit]);
|
|
42
|
+
const getMatchingDossiers = useCallback(async (hit) => {
|
|
43
|
+
if (!hit) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (has(hit, '__dossiers')) {
|
|
47
|
+
return hit.__dossiers;
|
|
48
|
+
}
|
|
49
|
+
// This is a fallback in case metadata is not included. In most cases templates are shown, the template metadata
|
|
50
|
+
// should also exist
|
|
51
|
+
try {
|
|
52
|
+
return (await getHit(hit.howler.id, true)).__dossiers ?? [];
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
console.warn(e);
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}, [getHit]);
|
|
59
|
+
const getMatchingAnalytic = useCallback(async (hit) => {
|
|
60
|
+
if (!hit) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (has(hit, '__analytic')) {
|
|
64
|
+
return hit.__analytic;
|
|
65
|
+
}
|
|
66
|
+
// This is a fallback in case metadata is not included.
|
|
67
|
+
try {
|
|
68
|
+
return (await getHit(hit.howler.id, true)).__analytic;
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.warn(e);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}, [getHit]);
|
|
75
|
+
return {
|
|
76
|
+
getMatchingDossiers,
|
|
77
|
+
getMatchingOverview,
|
|
78
|
+
getMatchingTemplate,
|
|
79
|
+
getMatchingAnalytic
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
export default useMatchers;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import { useContextSelector } from 'use-context-selector';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { HitContext } from '../providers/HitProvider';
|
|
5
|
+
import useMatchers from './useMatchers';
|
|
6
|
+
// Mock the useContextSelector hook
|
|
7
|
+
vi.mock('use-context-selector', () => ({
|
|
8
|
+
useContextSelector: vi.fn(),
|
|
9
|
+
createContext: vi.fn()
|
|
10
|
+
}));
|
|
11
|
+
// Mock lodash-es has function
|
|
12
|
+
vi.mock('lodash-es', () => ({
|
|
13
|
+
has: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
// Create mock data
|
|
16
|
+
const mockTemplate = {
|
|
17
|
+
name: 'test-template',
|
|
18
|
+
template: 'Test template content'
|
|
19
|
+
};
|
|
20
|
+
const mockOverview = {
|
|
21
|
+
name: 'test-overview',
|
|
22
|
+
overview: 'Test overview content'
|
|
23
|
+
};
|
|
24
|
+
const mockDossiers = [
|
|
25
|
+
{
|
|
26
|
+
name: 'test-dossier',
|
|
27
|
+
description: 'Test dossier content'
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
const mockHit = {
|
|
31
|
+
howler: {
|
|
32
|
+
id: 'test-hit-id',
|
|
33
|
+
analytic: 'test-analytic',
|
|
34
|
+
data: {}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const mockHitWithMetadata = {
|
|
38
|
+
...mockHit,
|
|
39
|
+
__template: mockTemplate,
|
|
40
|
+
__overview: mockOverview,
|
|
41
|
+
__dossiers: mockDossiers
|
|
42
|
+
};
|
|
43
|
+
const mockGetHit = vi.fn();
|
|
44
|
+
describe('useMatchers', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
// Mock useContextSelector to return our mock getHit function
|
|
48
|
+
useContextSelector.mockImplementation((context, selector) => {
|
|
49
|
+
if (context === HitContext) {
|
|
50
|
+
return selector({ getHit: mockGetHit });
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('getMatchingTemplate', () => {
|
|
56
|
+
it('should return null when hit is null', async () => {
|
|
57
|
+
const { result } = renderHook(() => useMatchers());
|
|
58
|
+
const template = await result.current.getMatchingTemplate(null);
|
|
59
|
+
expect(template).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
it('should return null when hit is undefined', async () => {
|
|
62
|
+
const { result } = renderHook(() => useMatchers());
|
|
63
|
+
const template = await result.current.getMatchingTemplate(undefined);
|
|
64
|
+
expect(template).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
it('should return template from metadata when it exists', async () => {
|
|
67
|
+
const { has } = await import('lodash-es');
|
|
68
|
+
has.mockReturnValue(true);
|
|
69
|
+
const { result } = renderHook(() => useMatchers());
|
|
70
|
+
const template = await result.current.getMatchingTemplate(mockHitWithMetadata);
|
|
71
|
+
expect(template).toBe(mockTemplate);
|
|
72
|
+
expect(mockGetHit).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
it('should fetch hit with metadata when template is not present', async () => {
|
|
75
|
+
const { has } = await import('lodash-es');
|
|
76
|
+
has.mockReturnValue(false);
|
|
77
|
+
const hitWithFetchedMetadata = { ...mockHit, __template: mockTemplate };
|
|
78
|
+
mockGetHit.mockResolvedValue(hitWithFetchedMetadata);
|
|
79
|
+
const { result } = renderHook(() => useMatchers());
|
|
80
|
+
const template = await result.current.getMatchingTemplate(mockHit);
|
|
81
|
+
expect(template).toBe(mockTemplate);
|
|
82
|
+
expect(mockGetHit).toHaveBeenCalledWith('test-hit-id', true);
|
|
83
|
+
});
|
|
84
|
+
it('should handle getHit rejection gracefully', async () => {
|
|
85
|
+
const { has } = await import('lodash-es');
|
|
86
|
+
has.mockReturnValue(false);
|
|
87
|
+
mockGetHit.mockRejectedValue(new Error('Failed to fetch hit'));
|
|
88
|
+
const { result } = renderHook(() => useMatchers());
|
|
89
|
+
await expect(result.current.getMatchingTemplate(mockHit)).resolves.toBeNull();
|
|
90
|
+
expect(mockGetHit).toHaveBeenCalledWith('test-hit-id', true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('getMatchingOverview', () => {
|
|
94
|
+
it('should return null when hit is null', async () => {
|
|
95
|
+
const { result } = renderHook(() => useMatchers());
|
|
96
|
+
const overview = await result.current.getMatchingOverview(null);
|
|
97
|
+
expect(overview).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
it('should return null when hit is undefined', async () => {
|
|
100
|
+
const { result } = renderHook(() => useMatchers());
|
|
101
|
+
const overview = await result.current.getMatchingOverview(undefined);
|
|
102
|
+
expect(overview).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
it('should return overview from metadata when it exists', async () => {
|
|
105
|
+
const { has } = await import('lodash-es');
|
|
106
|
+
has.mockReturnValue(true);
|
|
107
|
+
const { result } = renderHook(() => useMatchers());
|
|
108
|
+
const overview = await result.current.getMatchingOverview(mockHitWithMetadata);
|
|
109
|
+
expect(overview).toBe(mockOverview);
|
|
110
|
+
expect(mockGetHit).not.toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
it('should fetch hit with metadata when overview is not present', async () => {
|
|
113
|
+
const { has } = await import('lodash-es');
|
|
114
|
+
has.mockReturnValue(false);
|
|
115
|
+
const hitWithFetchedMetadata = { ...mockHit, __overview: mockOverview };
|
|
116
|
+
mockGetHit.mockResolvedValue(hitWithFetchedMetadata);
|
|
117
|
+
const { result } = renderHook(() => useMatchers());
|
|
118
|
+
const overview = await result.current.getMatchingOverview(mockHit);
|
|
119
|
+
expect(overview).toBe(mockOverview);
|
|
120
|
+
expect(mockGetHit).toHaveBeenCalledWith('test-hit-id', true);
|
|
121
|
+
});
|
|
122
|
+
it('should handle getHit rejection gracefully', async () => {
|
|
123
|
+
const { has } = await import('lodash-es');
|
|
124
|
+
has.mockReturnValue(false);
|
|
125
|
+
mockGetHit.mockRejectedValue(new Error('Failed to fetch hit'));
|
|
126
|
+
const { result } = renderHook(() => useMatchers());
|
|
127
|
+
await expect(result.current.getMatchingOverview(mockHit)).resolves.toBeNull();
|
|
128
|
+
expect(mockGetHit).toHaveBeenCalledWith('test-hit-id', true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('getMatchingDossiers', () => {
|
|
132
|
+
it('should return null when hit is null', async () => {
|
|
133
|
+
const { result } = renderHook(() => useMatchers());
|
|
134
|
+
const dossiers = await result.current.getMatchingDossiers(null);
|
|
135
|
+
expect(dossiers).toBeNull();
|
|
136
|
+
});
|
|
137
|
+
it('should return null when hit is undefined', async () => {
|
|
138
|
+
const { result } = renderHook(() => useMatchers());
|
|
139
|
+
const dossiers = await result.current.getMatchingDossiers(undefined);
|
|
140
|
+
expect(dossiers).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
it('should return dossiers from metadata when they exist', async () => {
|
|
143
|
+
const { has } = await import('lodash-es');
|
|
144
|
+
has.mockReturnValue(true);
|
|
145
|
+
const { result } = renderHook(() => useMatchers());
|
|
146
|
+
const dossiers = await result.current.getMatchingDossiers(mockHitWithMetadata);
|
|
147
|
+
expect(dossiers).toBe(mockDossiers);
|
|
148
|
+
expect(mockGetHit).not.toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
it('should fetch hit with metadata when dossiers are not present', async () => {
|
|
151
|
+
const { has } = await import('lodash-es');
|
|
152
|
+
has.mockReturnValue(false);
|
|
153
|
+
const hitWithFetchedMetadata = { ...mockHit, __dossiers: mockDossiers };
|
|
154
|
+
mockGetHit.mockResolvedValue(hitWithFetchedMetadata);
|
|
155
|
+
const { result } = renderHook(() => useMatchers());
|
|
156
|
+
const dossiers = await result.current.getMatchingDossiers(mockHit);
|
|
157
|
+
expect(dossiers).toBe(mockDossiers);
|
|
158
|
+
expect(mockGetHit).toHaveBeenCalledWith('test-hit-id', true);
|
|
159
|
+
});
|
|
160
|
+
it('should handle getHit rejection gracefully', async () => {
|
|
161
|
+
const { has } = await import('lodash-es');
|
|
162
|
+
has.mockReturnValue(false);
|
|
163
|
+
mockGetHit.mockRejectedValue(new Error('Failed to fetch hit'));
|
|
164
|
+
const { result } = renderHook(() => useMatchers());
|
|
165
|
+
await expect(result.current.getMatchingDossiers(mockHit)).resolves.toEqual([]);
|
|
166
|
+
expect(mockGetHit).toHaveBeenCalledWith('test-hit-id', true);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe('integration tests', () => {
|
|
170
|
+
it('should correctly handle has function calls for different metadata properties', async () => {
|
|
171
|
+
const { has } = await import('lodash-es');
|
|
172
|
+
// Mock has to return true for __template, false for others
|
|
173
|
+
has.mockImplementation((_obj, prop) => {
|
|
174
|
+
return prop === '__template';
|
|
175
|
+
});
|
|
176
|
+
const hitWithPartialMetadata = {
|
|
177
|
+
...mockHit,
|
|
178
|
+
__template: mockTemplate
|
|
179
|
+
};
|
|
180
|
+
mockGetHit.mockResolvedValue({
|
|
181
|
+
...mockHit,
|
|
182
|
+
__overview: mockOverview,
|
|
183
|
+
__dossiers: mockDossiers
|
|
184
|
+
});
|
|
185
|
+
const { result } = renderHook(() => useMatchers());
|
|
186
|
+
// Should return template from metadata
|
|
187
|
+
const template = await result.current.getMatchingTemplate(hitWithPartialMetadata);
|
|
188
|
+
expect(template).toBe(mockTemplate);
|
|
189
|
+
// Should fetch overview
|
|
190
|
+
const overview = await result.current.getMatchingOverview(hitWithPartialMetadata);
|
|
191
|
+
expect(overview).toBe(mockOverview);
|
|
192
|
+
// Should fetch dossiers
|
|
193
|
+
const dossiers = await result.current.getMatchingDossiers(hitWithPartialMetadata);
|
|
194
|
+
expect(dossiers).toBe(mockDossiers);
|
|
195
|
+
// Verify has was called with correct parameters
|
|
196
|
+
expect(has).toHaveBeenCalledWith(hitWithPartialMetadata, '__template');
|
|
197
|
+
expect(has).toHaveBeenCalledWith(hitWithPartialMetadata, '__overview');
|
|
198
|
+
expect(has).toHaveBeenCalledWith(hitWithPartialMetadata, '__dossiers');
|
|
199
|
+
});
|
|
200
|
+
it('should handle empty or undefined metadata gracefully', async () => {
|
|
201
|
+
const { has } = await import('lodash-es');
|
|
202
|
+
has.mockReturnValue(false);
|
|
203
|
+
mockGetHit.mockResolvedValue({
|
|
204
|
+
...mockHit,
|
|
205
|
+
__template: undefined,
|
|
206
|
+
__overview: null,
|
|
207
|
+
__dossiers: []
|
|
208
|
+
});
|
|
209
|
+
const { result } = renderHook(() => useMatchers());
|
|
210
|
+
const template = await result.current.getMatchingTemplate(mockHit);
|
|
211
|
+
const overview = await result.current.getMatchingOverview(mockHit);
|
|
212
|
+
const dossiers = await result.current.getMatchingDossiers(mockHit);
|
|
213
|
+
expect(template).toBeUndefined();
|
|
214
|
+
expect(overview).toBeNull();
|
|
215
|
+
expect(dossiers).toEqual([]);
|
|
216
|
+
expect(mockGetHit).toHaveBeenCalledTimes(3);
|
|
217
|
+
});
|
|
218
|
+
it('should maintain referential equality of returned functions', () => {
|
|
219
|
+
const { result, rerender } = renderHook(() => useMatchers());
|
|
220
|
+
const firstRender = {
|
|
221
|
+
getMatchingTemplate: result.current.getMatchingTemplate,
|
|
222
|
+
getMatchingOverview: result.current.getMatchingOverview,
|
|
223
|
+
getMatchingDossiers: result.current.getMatchingDossiers
|
|
224
|
+
};
|
|
225
|
+
rerender();
|
|
226
|
+
const secondRender = {
|
|
227
|
+
getMatchingTemplate: result.current.getMatchingTemplate,
|
|
228
|
+
getMatchingOverview: result.current.getMatchingOverview,
|
|
229
|
+
getMatchingDossiers: result.current.getMatchingDossiers
|
|
230
|
+
};
|
|
231
|
+
// Functions should be the same reference due to useCallback
|
|
232
|
+
expect(firstRender.getMatchingTemplate).toBe(secondRender.getMatchingTemplate);
|
|
233
|
+
expect(firstRender.getMatchingOverview).toBe(secondRender.getMatchingOverview);
|
|
234
|
+
expect(firstRender.getMatchingDossiers).toBe(secondRender.getMatchingDossiers);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import api from '@cccsaurora/howler-ui/api';
|
|
2
1
|
import useMySitemap from '@cccsaurora/howler-ui/components/hooks/useMySitemap';
|
|
3
2
|
import { capitalize } from 'lodash-es';
|
|
4
3
|
import { useCallback, useContext, useEffect } from 'react';
|
|
5
4
|
import { useTranslation } from 'react-i18next';
|
|
6
5
|
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
|
|
6
|
+
import { useContextSelector } from 'use-context-selector';
|
|
7
7
|
import { AnalyticContext } from '../providers/AnalyticProvider';
|
|
8
|
+
import { HitContext } from '../providers/HitProvider';
|
|
8
9
|
const useTitle = () => {
|
|
9
10
|
const { t } = useTranslation();
|
|
10
11
|
const location = useLocation();
|
|
@@ -12,6 +13,7 @@ const useTitle = () => {
|
|
|
12
13
|
const searchParams = useSearchParams()[0];
|
|
13
14
|
const sitemap = useMySitemap();
|
|
14
15
|
const { getAnalyticFromId } = useContext(AnalyticContext);
|
|
16
|
+
const hits = useContextSelector(HitContext, ctx => ctx.hits);
|
|
15
17
|
const setTitle = useCallback((title) => {
|
|
16
18
|
document.querySelector('title').innerHTML = title;
|
|
17
19
|
}, []);
|
|
@@ -32,8 +34,7 @@ const useTitle = () => {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
else if (searchType === 'hit' && params.id) {
|
|
35
|
-
const
|
|
36
|
-
const hit = result.items[0];
|
|
37
|
+
const hit = hits[params.id];
|
|
37
38
|
let newTitle = `${capitalize(hit.howler.escalation)} - ${hit.howler.analytic}`;
|
|
38
39
|
if (hit.howler.detection) {
|
|
39
40
|
newTitle += `: ${hit.howler.detection}`;
|
|
@@ -59,7 +60,7 @@ const useTitle = () => {
|
|
|
59
60
|
setTitle(`Howler - ${t(matchingRoute.title)}`);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
|
-
}, [getAnalyticFromId, location.pathname, params.id, searchParams, setTitle, sitemap.routes, t]);
|
|
63
|
+
}, [getAnalyticFromId, location.pathname, params.id, searchParams, hits, setTitle, sitemap.routes, t]);
|
|
63
64
|
useEffect(() => {
|
|
64
65
|
runChecks();
|
|
65
66
|
}, [runChecks]);
|
|
@@ -3,10 +3,6 @@ import { type FC, type PropsWithChildren } from 'react';
|
|
|
3
3
|
interface AnalyticContextType {
|
|
4
4
|
ready: boolean;
|
|
5
5
|
analytics: Analytic[];
|
|
6
|
-
addFavourite: (analytic: Analytic) => Promise<void>;
|
|
7
|
-
removeFavourite: (analytic: Analytic) => Promise<void>;
|
|
8
|
-
getIdFromName: (name: string) => Promise<string>;
|
|
9
|
-
getAnalyticFromName: (name: string) => Promise<Analytic>;
|
|
10
6
|
getAnalyticFromId: (id: string) => Promise<Analytic>;
|
|
11
7
|
}
|
|
12
8
|
export declare const AnalyticContext: import("react").Context<AnalyticContextType>;
|
|
@@ -2,7 +2,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import api from '@cccsaurora/howler-ui/api';
|
|
3
3
|
import { useAppUser } from '@cccsaurora/howler-ui/commons/components/app/hooks';
|
|
4
4
|
import { createContext, useCallback, useEffect, useState } from 'react';
|
|
5
|
-
import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
|
|
6
5
|
export const AnalyticContext = createContext(null);
|
|
7
6
|
/**
|
|
8
7
|
* A set of promises for each analytic search. This is to stop several identical
|
|
@@ -32,20 +31,6 @@ const AnalyticProvider = ({ children }) => {
|
|
|
32
31
|
fetchAnalytics();
|
|
33
32
|
}
|
|
34
33
|
}, [ready, appUser, fetchAnalytics]);
|
|
35
|
-
const addFavourite = useCallback(async (analytic) => {
|
|
36
|
-
await api.analytic.favourite.post(analytic.analytic_id);
|
|
37
|
-
appUser.setUser({
|
|
38
|
-
...appUser.user,
|
|
39
|
-
favourite_analytics: [...appUser.user.favourite_analytics, analytic.analytic_id]
|
|
40
|
-
});
|
|
41
|
-
}, [appUser]);
|
|
42
|
-
const removeFavourite = useCallback(async (analytic) => {
|
|
43
|
-
await api.analytic.favourite.del(analytic.analytic_id);
|
|
44
|
-
appUser.setUser({
|
|
45
|
-
...appUser.user,
|
|
46
|
-
favourite_analytics: appUser.user.favourite_analytics.filter(v => v !== analytic.analytic_id)
|
|
47
|
-
});
|
|
48
|
-
}, [appUser]);
|
|
49
34
|
const getAnalyticFromId = useCallback(async (id) => {
|
|
50
35
|
const candidate = analytics?.find(_analytic => _analytic.analytic_id === id);
|
|
51
36
|
if (candidate) {
|
|
@@ -71,34 +56,6 @@ const AnalyticProvider = ({ children }) => {
|
|
|
71
56
|
}
|
|
72
57
|
return null;
|
|
73
58
|
}, [analytics]);
|
|
74
|
-
|
|
75
|
-
const candidate = analytics.find(_analytic => _analytic.name === name);
|
|
76
|
-
if (candidate) {
|
|
77
|
-
return candidate;
|
|
78
|
-
}
|
|
79
|
-
// We check to see if there's already a request in progress
|
|
80
|
-
if (!PROMISES[name]) {
|
|
81
|
-
PROMISES[name] = api.search.analytic.post({
|
|
82
|
-
query: `name:(${sanitizeLuceneQuery(name)})`
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const result = await PROMISES[name];
|
|
87
|
-
const analytic = result.items?.[0];
|
|
88
|
-
if (analytic) {
|
|
89
|
-
setAnalytics([...analytics, analytic]);
|
|
90
|
-
return analytic;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch (e) {
|
|
94
|
-
// eslint-disable-next-line no-console
|
|
95
|
-
console.error(e);
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}, [analytics]);
|
|
99
|
-
const getIdFromName = useCallback(async (name) => {
|
|
100
|
-
return (await getAnalyticFromName(name))?.analytic_id;
|
|
101
|
-
}, [getAnalyticFromName]);
|
|
102
|
-
return (_jsx(AnalyticContext.Provider, { value: { analytics, ready, addFavourite, removeFavourite, getAnalyticFromName, getAnalyticFromId, getIdFromName }, children: children }));
|
|
59
|
+
return (_jsx(AnalyticContext.Provider, { value: { analytics, ready, getAnalyticFromId }, children: children }));
|
|
103
60
|
};
|
|
104
61
|
export default AnalyticProvider;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
|
|
2
|
+
import type { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
|
|
2
3
|
import type { FC, PropsWithChildren } from 'react';
|
|
3
4
|
interface HitProviderType {
|
|
4
5
|
hits: {
|
|
@@ -10,7 +11,7 @@ interface HitProviderType {
|
|
|
10
11
|
clearSelectedHits: (except?: string) => void;
|
|
11
12
|
loadHits: (hits: Hit[]) => void;
|
|
12
13
|
updateHit: (newHit: Hit) => void;
|
|
13
|
-
getHit: (id: string, force?: boolean) => Promise<Hit
|
|
14
|
+
getHit: (id: string, force?: boolean) => Promise<WithMetadata<Hit>>;
|
|
14
15
|
}
|
|
15
16
|
export declare const HitContext: import("use-context-selector").Context<HitProviderType>;
|
|
16
17
|
/**
|
|
@@ -32,7 +32,13 @@ const HitProvider = ({ children }) => {
|
|
|
32
32
|
// eslint-disable-next-line no-console
|
|
33
33
|
console.debug('Received websocket update for hit', data.hit.howler.id);
|
|
34
34
|
hitRequests.current[data.hit.howler.id] = Promise.resolve(data.hit);
|
|
35
|
-
setHits(_hits => ({
|
|
35
|
+
setHits(_hits => ({
|
|
36
|
+
..._hits,
|
|
37
|
+
[data.hit.howler.id]: {
|
|
38
|
+
..._hits[data.hit.howler.id],
|
|
39
|
+
...data.hit
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
36
42
|
}
|
|
37
43
|
}, []);
|
|
38
44
|
useEffect(() => {
|
|
@@ -45,7 +51,7 @@ const HitProvider = ({ children }) => {
|
|
|
45
51
|
*/
|
|
46
52
|
const getHit = useCallback(async (id, force = false) => {
|
|
47
53
|
if (!hitRequests.current[id] || force) {
|
|
48
|
-
hitRequests.current[id] = dispatchApi(api.hit.get(id));
|
|
54
|
+
hitRequests.current[id] = dispatchApi(api.hit.get(id, ['template', 'dossiers', 'analytic', 'overview']));
|
|
49
55
|
const newHit = await hitRequests.current[id];
|
|
50
56
|
setHits(_hits => ({ ..._hits, [id]: newHit }));
|
|
51
57
|
}
|