@cccsaurora/howler-ui 2.13.0-dev.142 → 2.13.0-dev.148
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/components/app/App.js +1 -2
- package/components/app/hooks/useMatchers.d.ts +1 -0
- package/components/app/hooks/useMatchers.js +12 -1
- package/components/app/providers/AnalyticProvider.d.ts +0 -4
- package/components/app/providers/AnalyticProvider.js +1 -44
- package/components/elements/display/ActionButton.d.ts +8 -0
- package/components/elements/display/ActionButton.js +18 -0
- package/components/elements/display/handlebars/helpers.js +12 -0
- package/components/elements/hit/HitActions.js +4 -6
- package/components/elements/hit/HitBanner.js +8 -4
- package/components/elements/hit/HitComments.js +5 -4
- package/components/routes/analytics/AnalyticSearch.js +14 -2
- package/components/routes/hits/search/HitContextMenu.js +6 -7
- package/components/routes/hits/search/InformationPane.js +3 -5
- package/components/routes/hits/search/grid/HitGrid.js +6 -5
- package/components/routes/hits/view/HitViewer.js +4 -6
- package/components/routes/overviews/OverviewViewer.js +6 -13
- package/models/WithMetadata.d.ts +3 -1
- package/package.json +1 -1
package/components/app/App.js
CHANGED
|
@@ -59,7 +59,6 @@ import { createBrowserRouter, Outlet, RouterProvider, useLocation, useNavigate }
|
|
|
59
59
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
60
60
|
import useMySearch from '../hooks/useMySearch';
|
|
61
61
|
import AppContainer from './AppContainer';
|
|
62
|
-
import AnalyticProvider from './providers/AnalyticProvider';
|
|
63
62
|
import ApiConfigProvider, { ApiConfigContext } from './providers/ApiConfigProvider';
|
|
64
63
|
import AvatarProvider from './providers/AvatarProvider';
|
|
65
64
|
import CustomPluginProvider from './providers/CustomPluginProvider';
|
|
@@ -138,7 +137,7 @@ const MyAppProvider = ({ children }) => {
|
|
|
138
137
|
const mySitemap = useMySitemap();
|
|
139
138
|
const myUser = useMyUser();
|
|
140
139
|
const mySearch = useMySearch();
|
|
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(
|
|
140
|
+
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(FavouriteProvider, { children: _jsx(UserListProvider, { children: children }) }) }) }) }) }) }) }) }) }) }) }) }) }) }));
|
|
142
141
|
};
|
|
143
142
|
const AppProviderWrapper = () => {
|
|
144
143
|
return (_jsx(I18nextProvider, { i18n: i18n, defaultNS: "translation", children: _jsx(ApiConfigProvider, { children: _jsx(PluginProvider, { pluginStore: howlerPluginStore.pluginStore, children: _jsxs(MyAppProvider, { children: [_jsx(MyApp, {}), _jsx(Modal, {})] }) }) }) }));
|
|
@@ -4,5 +4,6 @@ declare const useMatchers: () => {
|
|
|
4
4
|
getMatchingDossiers: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Dossier").Dossier[]>;
|
|
5
5
|
getMatchingOverview: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Overview").Overview>;
|
|
6
6
|
getMatchingTemplate: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Template").Template>;
|
|
7
|
+
getMatchingAnalytic: (hit: WithMetadata<Hit>) => Promise<import("../../../models/entities/generated/Analytic").Analytic>;
|
|
7
8
|
};
|
|
8
9
|
export default useMatchers;
|
|
@@ -37,10 +37,21 @@ const useMatchers = () => {
|
|
|
37
37
|
// should also exist
|
|
38
38
|
return (await getHit(hit.howler.id, true)).__dossiers;
|
|
39
39
|
}, [getHit]);
|
|
40
|
+
const getMatchingAnalytic = useCallback(async (hit) => {
|
|
41
|
+
if (!hit) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (has(hit, '__analytic')) {
|
|
45
|
+
return hit.__analytic;
|
|
46
|
+
}
|
|
47
|
+
// This is a fallback in case metadata is not included.
|
|
48
|
+
return (await getHit(hit.howler.id, true)).__analytic;
|
|
49
|
+
}, [getHit]);
|
|
40
50
|
return {
|
|
41
51
|
getMatchingDossiers,
|
|
42
52
|
getMatchingOverview,
|
|
43
|
-
getMatchingTemplate
|
|
53
|
+
getMatchingTemplate,
|
|
54
|
+
getMatchingAnalytic
|
|
44
55
|
};
|
|
45
56
|
};
|
|
46
57
|
export default useMatchers;
|
|
@@ -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;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '@mui/material';
|
|
3
|
+
import api from '@cccsaurora/howler-ui/api';
|
|
4
|
+
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
5
|
+
import useMyActionFunctions from '@cccsaurora/howler-ui/components/routes/action/useMyActionFunctions';
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
const ActionButton = ({ actionId, hitId, label, ...otherProps }) => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const { dispatchApi } = useMyApi();
|
|
11
|
+
const { executeAction } = useMyActionFunctions();
|
|
12
|
+
const [action, setAction] = useState(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
dispatchApi(api.search.action.post({ query: `action_id:${actionId}`, rows: 1 })).then(result => setAction(result.items[0]));
|
|
15
|
+
}, [actionId, dispatchApi]);
|
|
16
|
+
return (_jsx(Button, { variant: otherProps.variant ?? 'outlined', disabled: !action, onClick: () => executeAction(actionId, `howler.id:${hitId}`), color: otherProps.color ?? 'primary', children: label ?? action?.name ?? t('loading') }));
|
|
17
|
+
};
|
|
18
|
+
export default ActionButton;
|
|
@@ -10,6 +10,7 @@ import { capitalize, get, groupBy, isObject } from 'lodash-es';
|
|
|
10
10
|
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
11
11
|
import { useMemo } from 'react';
|
|
12
12
|
import { usePluginStore } from 'react-pluggable';
|
|
13
|
+
import ActionButton from '../ActionButton';
|
|
13
14
|
import JSONViewer from '../json/JSONViewer';
|
|
14
15
|
const FETCH_RESULTS = {};
|
|
15
16
|
export const useHelpers = () => {
|
|
@@ -147,6 +148,17 @@ export const useHelpers = () => {
|
|
|
147
148
|
}) })] }) }));
|
|
148
149
|
}
|
|
149
150
|
},
|
|
151
|
+
{
|
|
152
|
+
keyword: 'action',
|
|
153
|
+
documentation: 'Execute a howler action given a specific action ID (from the URL when viewing the action, i.e. yaIKVqiKhWpyCsWdqsE4D)',
|
|
154
|
+
componentCallback: (actionId, hitId, context) => {
|
|
155
|
+
if (!actionId || !hitId) {
|
|
156
|
+
console.warn('Missing parameters for the action button.');
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return _jsx(ActionButton, { actionId: actionId, hitId: hitId, ...(context.hash ?? {}) });
|
|
160
|
+
}
|
|
161
|
+
},
|
|
150
162
|
...howlerPluginStore.plugins.flatMap(plugin => pluginStore.executeFunction(`${plugin}.helpers`))
|
|
151
163
|
], [pluginStore]);
|
|
152
164
|
return allHelpers;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { MoreHoriz } from '@mui/icons-material';
|
|
3
3
|
import { Box, CircularProgress, Divider, FormControl, FormControlLabel, FormLabel, IconButton, Menu, Radio, RadioGroup, Stack, Switch, useMediaQuery } from '@mui/material';
|
|
4
|
-
import
|
|
4
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
5
5
|
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
6
6
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
7
7
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
@@ -27,7 +27,7 @@ const HitActions = ({ hit, orientation = 'horizontal' }) => {
|
|
|
27
27
|
const { config } = useContext(ApiConfigContext);
|
|
28
28
|
const { values, set } = useMyLocalStorageProvider();
|
|
29
29
|
const pluginStore = usePluginStore();
|
|
30
|
-
const {
|
|
30
|
+
const { getMatchingAnalytic } = useMatchers();
|
|
31
31
|
const getCurrentView = useContextSelector(ViewContext, ctx => ctx.getCurrentView);
|
|
32
32
|
const selected = useContextSelector(ParameterContext, ctx => ctx?.selected);
|
|
33
33
|
const setSelected = useContextSelector(ParameterContext, ctx => ctx?.setSelected);
|
|
@@ -112,11 +112,9 @@ const HitActions = ({ hit, orientation = 'horizontal' }) => {
|
|
|
112
112
|
}
|
|
113
113
|
}, [keyboardDownHandler]);
|
|
114
114
|
useEffect(() => {
|
|
115
|
-
(
|
|
116
|
-
setAnalytic(await getAnalyticFromName(hit.howler.analytic));
|
|
117
|
-
})();
|
|
115
|
+
getMatchingAnalytic(hit).then(setAnalytic);
|
|
118
116
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
-
}, [hit
|
|
117
|
+
}, [hit?.howler.analytic]);
|
|
120
118
|
const handleOpenSetting = useCallback((e) => setOpenSetting(e.currentTarget), []);
|
|
121
119
|
const handleCloseSetting = useCallback(() => setOpenSetting(null), []);
|
|
122
120
|
const onShortcutChange = useCallback((__, s) => set(StorageKey.HIT_SHORTCUTS, s), [set]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Chip, Divider, Grid, Stack, Tooltip, Typography, avatarClasses, iconButtonClasses, useTheme } from '@mui/material';
|
|
3
|
-
import
|
|
3
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
4
4
|
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
5
5
|
import { uniq } from 'lodash-es';
|
|
6
6
|
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
@@ -19,15 +19,19 @@ import { HitLayout } from './HitLayout';
|
|
|
19
19
|
const HitBanner = ({ hit, layout = HitLayout.NORMAL, showAssigned = true }) => {
|
|
20
20
|
const { t } = useTranslation();
|
|
21
21
|
const { config } = useContext(ApiConfigContext);
|
|
22
|
-
const { getIdFromName } = useContext(AnalyticContext);
|
|
23
22
|
const theme = useTheme();
|
|
24
23
|
const pluginStore = usePluginStore();
|
|
24
|
+
const { getMatchingAnalytic } = useMatchers();
|
|
25
25
|
const [analyticId, setAnalyticId] = useState();
|
|
26
26
|
const compressed = useMemo(() => layout === HitLayout.DENSE, [layout]);
|
|
27
27
|
const textVariant = useMemo(() => (layout === HitLayout.COMFY ? 'body1' : 'caption'), [layout]);
|
|
28
28
|
useEffect(() => {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
if (!hit?.howler.analytic) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
getMatchingAnalytic(hit).then(analytic => setAnalyticId(analytic.analytic_id));
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, [hit?.howler.analytic]);
|
|
31
35
|
const providerColor = useMemo(() => PROVIDER_COLORS[hit.event?.provider ?? 'unknown'] ?? stringToColor(hit.event.provider), [hit.event?.provider]);
|
|
32
36
|
const mitreId = useMemo(() => {
|
|
33
37
|
if (hit.threat?.framework?.toLowerCase().startsWith('mitre')) {
|
|
@@ -3,7 +3,7 @@ import { Clear, KeyboardArrowDown, Send } from '@mui/icons-material';
|
|
|
3
3
|
import { Accordion, AccordionDetails, AccordionSummary, AvatarGroup, Chip, IconButton, Skeleton, Stack, TextField, Typography } from '@mui/material';
|
|
4
4
|
import api from '@cccsaurora/howler-ui/api';
|
|
5
5
|
import { useAppUser } from '@cccsaurora/howler-ui/commons/components/app/hooks';
|
|
6
|
-
import
|
|
6
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
7
7
|
import { SocketContext } from '@cccsaurora/howler-ui/components/app/providers/SocketProvider';
|
|
8
8
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
9
9
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
@@ -20,8 +20,8 @@ const HitComments = ({ hit, users }) => {
|
|
|
20
20
|
const { t } = useTranslation();
|
|
21
21
|
const navigate = useNavigate();
|
|
22
22
|
const { dispatchApi } = useMyApi();
|
|
23
|
-
const { getAnalyticFromName } = useContext(AnalyticContext);
|
|
24
23
|
const { addListener, removeListener, emit } = useContext(SocketContext);
|
|
24
|
+
const { getMatchingAnalytic } = useMatchers();
|
|
25
25
|
const [typers, setTypers] = useState([]);
|
|
26
26
|
const [loading, setLoading] = useState(false);
|
|
27
27
|
const [showClear, setShowClear] = useState(false);
|
|
@@ -51,12 +51,13 @@ const HitComments = ({ hit, users }) => {
|
|
|
51
51
|
}, [handler]);
|
|
52
52
|
useEffect(() => {
|
|
53
53
|
if (hit?.howler?.analytic) {
|
|
54
|
-
|
|
54
|
+
getMatchingAnalytic(hit).then(analytic => {
|
|
55
55
|
setAnalyticId(analytic?.analytic_id);
|
|
56
56
|
setAnalyticComments(sortByTimestamp(analytic?.comment ?? []));
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, [getMatchingAnalytic, hit?.howler?.analytic]);
|
|
60
61
|
const onSubmit = useCallback(async () => {
|
|
61
62
|
if (!input.current?.value || !hit || input.current.value.length > MAX_LENGTH)
|
|
62
63
|
return;
|
|
@@ -4,7 +4,6 @@ import { AvatarGroup, Card, CardContent, CardHeader, Chip, Divider, Grid, IconBu
|
|
|
4
4
|
import api from '@cccsaurora/howler-ui/api';
|
|
5
5
|
import { useAppUser } from '@cccsaurora/howler-ui/commons/components/app/hooks';
|
|
6
6
|
import useLocalStorageItem from '@cccsaurora/howler-ui/commons/components/utils/hooks/useLocalStorageItem';
|
|
7
|
-
import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
|
|
8
7
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
9
8
|
import { TuiListProvider } from '@cccsaurora/howler-ui/components/elements/addons/lists';
|
|
10
9
|
import { TuiListMethodContext } from '@cccsaurora/howler-ui/components/elements/addons/lists/TuiListProvider';
|
|
@@ -26,13 +25,26 @@ const AnalyticSearchBase = () => {
|
|
|
26
25
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
27
26
|
const pageCount = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25)[0];
|
|
28
27
|
const appUser = useAppUser();
|
|
29
|
-
const { addFavourite, removeFavourite } = useContext(AnalyticContext);
|
|
30
28
|
const [onlyRules, setOnlyRules] = useLocalStorageItem(StorageKey.ONLY_RULES, 0);
|
|
31
29
|
const [searching, setSearching] = useState(false);
|
|
32
30
|
const [hasError, setHasError] = useState(false);
|
|
33
31
|
const [phrase, setPhrase] = useState(searchParams.get('phrase') || '');
|
|
34
32
|
const [offset, setOffset] = useState(parseInt(searchParams.get('offset')) || 0);
|
|
35
33
|
const [response, setResponse] = useState(null);
|
|
34
|
+
const addFavourite = useCallback(async (analytic) => {
|
|
35
|
+
await dispatchApi(api.analytic.favourite.post(analytic.analytic_id));
|
|
36
|
+
appUser.setUser({
|
|
37
|
+
...appUser.user,
|
|
38
|
+
favourite_analytics: [...appUser.user.favourite_analytics, analytic.analytic_id]
|
|
39
|
+
});
|
|
40
|
+
}, [appUser, dispatchApi]);
|
|
41
|
+
const removeFavourite = useCallback(async (analytic) => {
|
|
42
|
+
await dispatchApi(api.analytic.favourite.del(analytic.analytic_id));
|
|
43
|
+
appUser.setUser({
|
|
44
|
+
...appUser.user,
|
|
45
|
+
favourite_analytics: appUser.user.favourite_analytics.filter(v => v !== analytic.analytic_id)
|
|
46
|
+
});
|
|
47
|
+
}, [appUser, dispatchApi]);
|
|
36
48
|
// Search Handler.
|
|
37
49
|
const onSearch = useCallback(async () => {
|
|
38
50
|
setSearching(true);
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Assignment, Edit, HowToVote, KeyboardArrowRight, OpenInNew, QueryStats, SettingsSuggest, Terminal } from '@mui/icons-material';
|
|
3
3
|
import { Box, Divider, Fade, ListItemIcon, ListItemText, Menu, MenuItem, MenuList, Paper } from '@mui/material';
|
|
4
4
|
import api from '@cccsaurora/howler-ui/api';
|
|
5
|
-
import
|
|
5
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
6
6
|
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
7
7
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
8
8
|
import { TOP_ROW, VOTE_OPTIONS } from '@cccsaurora/howler-ui/components/elements/hit/actions/SharedComponents';
|
|
@@ -24,11 +24,11 @@ const ICON_MAP = {
|
|
|
24
24
|
};
|
|
25
25
|
const HitContextMenu = ({ children, getSelectedId, Component = Box }) => {
|
|
26
26
|
const { t } = useTranslation();
|
|
27
|
-
const analyticContext = useContext(AnalyticContext);
|
|
28
27
|
const { dispatchApi } = useMyApi();
|
|
29
28
|
const { executeAction } = useMyActionFunctions();
|
|
30
29
|
const { config } = useContext(ApiConfigContext);
|
|
31
30
|
const pluginStore = usePluginStore();
|
|
31
|
+
const { getMatchingAnalytic } = useMatchers();
|
|
32
32
|
const [id, setId] = useState(null);
|
|
33
33
|
const hit = useContextSelector(HitContext, ctx => ctx.hits[id]);
|
|
34
34
|
const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
|
|
@@ -90,13 +90,12 @@ const HitContextMenu = ({ children, getSelectedId, Component = Box }) => {
|
|
|
90
90
|
return Object.entries(groupBy(_actions, 'type')).sort(([a], [b]) => ORDER.indexOf(a) - ORDER.indexOf(b));
|
|
91
91
|
}, [analytic, assess, availableTransitions, canAssess, canVote, config.lookups, vote, pluginActions]);
|
|
92
92
|
useEffect(() => {
|
|
93
|
-
if (!hit) {
|
|
93
|
+
if (!hit?.howler.analytic) {
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}, [analyticContext, hit]);
|
|
96
|
+
getMatchingAnalytic(hit).then(setAnalytic);
|
|
97
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
98
|
+
}, [hit?.howler.analytic]);
|
|
100
99
|
useEffect(() => {
|
|
101
100
|
if (!anchorEl) {
|
|
102
101
|
setClickLocation([-1, -1]);
|
|
@@ -4,7 +4,6 @@ import { Badge, Box, Divider, Skeleton, Stack, Tab, Tabs, Tooltip, useTheme } fr
|
|
|
4
4
|
import TuiIconButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomIconButton';
|
|
5
5
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
|
6
6
|
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
7
|
-
import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
|
|
8
7
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
9
8
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
10
9
|
import { SocketContext } from '@cccsaurora/howler-ui/components/app/providers/SocketProvider';
|
|
@@ -46,8 +45,7 @@ const InformationPane = ({ onClose }) => {
|
|
|
46
45
|
const theme = useTheme();
|
|
47
46
|
const location = useLocation();
|
|
48
47
|
const { emit, isOpen } = useContext(SocketContext);
|
|
49
|
-
const {
|
|
50
|
-
const { getMatchingOverview, getMatchingDossiers } = useMatchers();
|
|
48
|
+
const { getMatchingOverview, getMatchingDossiers, getMatchingAnalytic } = useMatchers();
|
|
51
49
|
const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
|
|
52
50
|
const pluginStore = usePluginStore();
|
|
53
51
|
const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
|
|
@@ -75,10 +73,10 @@ const InformationPane = ({ onClose }) => {
|
|
|
75
73
|
return;
|
|
76
74
|
}
|
|
77
75
|
setUserIds(getUserList(hit));
|
|
78
|
-
|
|
76
|
+
getMatchingAnalytic(hit).then(setAnalytic);
|
|
79
77
|
getMatchingDossiers(hit).then(setDossiers);
|
|
80
78
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
|
-
}, [
|
|
79
|
+
}, [getHit, selected]);
|
|
82
80
|
useEffect(() => {
|
|
83
81
|
if (tab === 'hit_aggregate' && !hit?.howler.is_bundle) {
|
|
84
82
|
setTab('overview');
|
|
@@ -3,7 +3,7 @@ import { DndContext, KeyboardSensor, PointerSensor, pointerWithin, useSensor, us
|
|
|
3
3
|
import { arrayMove, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
4
4
|
import { Add, FormatIndentDecrease, FormatIndentIncrease, Info, List, Search, TableChart } from '@mui/icons-material';
|
|
5
5
|
import { IconButton, LinearProgress, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow, ToggleButton, ToggleButtonGroup, Typography, useTheme } from '@mui/material';
|
|
6
|
-
import
|
|
6
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
7
7
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
8
8
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
9
9
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
@@ -14,7 +14,7 @@ import DevelopmentBanner from '@cccsaurora/howler-ui/components/elements/display
|
|
|
14
14
|
import DevelopmentIcon from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentIcon';
|
|
15
15
|
import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
|
|
16
16
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
17
|
-
import { useCallback,
|
|
17
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
18
18
|
import { useTranslation } from 'react-i18next';
|
|
19
19
|
import { useContextSelector } from 'use-context-selector';
|
|
20
20
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
@@ -27,10 +27,10 @@ import ColumnHeader from './ColumnHeader';
|
|
|
27
27
|
import HitRow from './HitRow';
|
|
28
28
|
const HitGrid = () => {
|
|
29
29
|
const { t } = useTranslation();
|
|
30
|
-
const { getIdFromName } = useContext(AnalyticContext);
|
|
31
30
|
const theme = useTheme();
|
|
32
31
|
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
33
32
|
const { onClick } = useHitSelection();
|
|
33
|
+
const { getMatchingAnalytic } = useMatchers();
|
|
34
34
|
const search = useContextSelector(HitSearchContext, ctx => ctx.search);
|
|
35
35
|
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
36
36
|
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
@@ -65,10 +65,11 @@ const HitGrid = () => {
|
|
|
65
65
|
useEffect(() => {
|
|
66
66
|
response?.items.forEach(hit => {
|
|
67
67
|
if (!analyticIds[hit.howler.analytic]) {
|
|
68
|
-
|
|
68
|
+
getMatchingAnalytic(hit).then(_analytic => setAnalyticIds(_analyticIds => ({ ..._analyticIds, [hit.howler.analytic]: _analytic.analytic_id })));
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
|
-
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
}, [analyticIds, response]);
|
|
72
73
|
const onMouseMove = useCallback((event) => {
|
|
73
74
|
event.stopPropagation();
|
|
74
75
|
event.preventDefault();
|
|
@@ -4,7 +4,6 @@ import { Code, Comment, DataObject, History, LinkSharp, QueryStats, ViewAgenda }
|
|
|
4
4
|
import { Badge, Box, CardContent, Collapse, IconButton, Skeleton, Stack, Tab, Tabs, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
|
5
5
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
6
6
|
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
7
|
-
import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
|
|
8
7
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
9
8
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
10
9
|
import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
|
|
@@ -27,7 +26,7 @@ import RelatedLink from '@cccsaurora/howler-ui/components/elements/hit/related/R
|
|
|
27
26
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
28
27
|
import useMyUserList from '@cccsaurora/howler-ui/components/hooks/useMyUserList';
|
|
29
28
|
import uniqBy from 'lodash-es/uniqBy';
|
|
30
|
-
import { useCallback,
|
|
29
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
31
30
|
import { useTranslation } from 'react-i18next';
|
|
32
31
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
33
32
|
import { useContextSelector } from 'use-context-selector';
|
|
@@ -47,8 +46,7 @@ const HitViewer = () => {
|
|
|
47
46
|
const theme = useTheme();
|
|
48
47
|
const isUnderLg = useMediaQuery(theme.breakpoints.down('lg'));
|
|
49
48
|
const [orientation, setOrientation] = useMyLocalStorageItem(StorageKey.VIEWER_ORIENTATION, Orientation.VERTICAL);
|
|
50
|
-
const {
|
|
51
|
-
const { getMatchingOverview, getMatchingDossiers } = useMatchers();
|
|
49
|
+
const { getMatchingOverview, getMatchingDossiers, getMatchingAnalytic } = useMatchers();
|
|
52
50
|
const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
|
|
53
51
|
const hit = useContextSelector(HitContext, ctx => ctx.hits[params.id]);
|
|
54
52
|
const [userIds, setUserIds] = useState(new Set());
|
|
@@ -64,14 +62,14 @@ const HitViewer = () => {
|
|
|
64
62
|
return;
|
|
65
63
|
}
|
|
66
64
|
setUserIds(getUserList(hit));
|
|
67
|
-
setAnalytic(await
|
|
65
|
+
setAnalytic(await getMatchingAnalytic(hit));
|
|
68
66
|
}
|
|
69
67
|
catch (err) {
|
|
70
68
|
if (err.cause?.api_status_code === 404) {
|
|
71
69
|
navigate('/404');
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
|
-
}, [hit,
|
|
72
|
+
}, [hit, getMatchingAnalytic, getHit, params.id, navigate]);
|
|
75
73
|
useEffect(() => {
|
|
76
74
|
if (isUnderLg) {
|
|
77
75
|
setOrientation(Orientation.HORIZONTAL);
|
|
@@ -8,7 +8,6 @@ import { Check, DarkMode, Delete, SsidChart, WbSunny } from '@mui/icons-material
|
|
|
8
8
|
import { useApp } from '@cccsaurora/howler-ui/commons/components/app/hooks';
|
|
9
9
|
import AppInfoPanel from '@cccsaurora/howler-ui/commons/components/display/AppInfoPanel';
|
|
10
10
|
import useThemeBuilder from '@cccsaurora/howler-ui/commons/components/utils/hooks/useThemeBuilder';
|
|
11
|
-
import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
|
|
12
11
|
import { OverviewContext } from '@cccsaurora/howler-ui/components/app/providers/OverviewProvider';
|
|
13
12
|
import HitOverview from '@cccsaurora/howler-ui/components/elements/hit/HitOverview';
|
|
14
13
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
@@ -38,7 +37,6 @@ const OverviewViewer = () => {
|
|
|
38
37
|
const [overviewLoading, setOverviewLoading] = useState(false);
|
|
39
38
|
const [exampleHit, setExampleHit] = useState(null);
|
|
40
39
|
const [x, setX] = useState(0);
|
|
41
|
-
const analyticContext = useContext(AnalyticContext);
|
|
42
40
|
const wrapper = useRef();
|
|
43
41
|
const startingTemplate = useStartingTemplate();
|
|
44
42
|
useEffect(() => {
|
|
@@ -64,19 +62,14 @@ const OverviewViewer = () => {
|
|
|
64
62
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
63
|
}, [analytic, dispatchApi]);
|
|
66
64
|
useEffect(() => {
|
|
67
|
-
if (analytic) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.then(foundAnalytic => {
|
|
72
|
-
setDetections(foundAnalytic.detections);
|
|
73
|
-
})
|
|
74
|
-
.catch(() => {
|
|
65
|
+
if (analytic && analytics) {
|
|
66
|
+
const _detections = analytics.find(_analytic => _analytic.name.toLowerCase() === analytic.toLowerCase())?.detections ?? [];
|
|
67
|
+
setDetections(_detections);
|
|
68
|
+
if (detection && !_detections.map(_detection => _detection.toLowerCase()).includes(detection.toLowerCase())) {
|
|
75
69
|
setDetection('ANY');
|
|
76
|
-
}
|
|
77
|
-
.finally(() => setLoading(false));
|
|
70
|
+
}
|
|
78
71
|
}
|
|
79
|
-
}, [analytic,
|
|
72
|
+
}, [analytic, analytics, detection]);
|
|
80
73
|
useEffect(() => {
|
|
81
74
|
(async () => {
|
|
82
75
|
const result = await dispatchApi(api.search.hit.post({
|
package/models/WithMetadata.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { Analytic } from './entities/generated/Analytic';
|
|
1
2
|
import type { Dossier } from './entities/generated/Dossier';
|
|
2
3
|
import type { Overview } from './entities/generated/Overview';
|
|
3
4
|
import type { Template } from './entities/generated/Template';
|
|
4
5
|
export type WithMetadata<T> = T & {
|
|
5
|
-
|
|
6
|
+
__analytic?: Analytic;
|
|
6
7
|
__overview?: Overview;
|
|
8
|
+
__template?: Template;
|
|
7
9
|
__dossiers?: Dossier[];
|
|
8
10
|
};
|