@cccsaurora/howler-ui 2.12.1 → 2.13.0-dev.75
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/search/facet/hit.d.ts +4 -2
- package/api/search/facet/hit.js +5 -5
- package/api/search/facet/index.d.ts +1 -0
- package/components/elements/hit/HitSummary.js +5 -5
- package/components/elements/hit/aggregate/HitGraph.js +6 -9
- package/components/routes/advanced/luceneCompletionProvider.js +4 -2
- package/components/routes/analytics/widgets/Assessment.js +3 -2
- package/components/routes/analytics/widgets/Escalation.js +4 -3
- package/components/routes/analytics/widgets/Stacked.js +4 -3
- package/components/routes/hits/search/shared/HitFilter.js +2 -2
- package/package.json +5 -1
- package/plugins/borealis/Provider.d.ts +3 -0
- package/plugins/borealis/Provider.js +14 -0
- package/plugins/borealis/components/BorealisChip.d.ts +3 -0
- package/plugins/borealis/components/BorealisChip.js +27 -0
- package/plugins/borealis/components/BorealisLeadForm.d.ts +4 -0
- package/plugins/borealis/components/BorealisLeadForm.js +23 -0
- package/plugins/borealis/components/BorealisPivot.d.ts +3 -0
- package/plugins/borealis/components/BorealisPivot.js +83 -0
- package/plugins/borealis/components/BorealisPivotForm.d.ts +4 -0
- package/plugins/borealis/components/BorealisPivotForm.js +44 -0
- package/plugins/borealis/components/BorealisTypography.d.ts +3 -0
- package/plugins/borealis/components/BorealisTypography.js +53 -0
- package/plugins/borealis/helpers.d.ts +6 -0
- package/plugins/borealis/helpers.js +137 -0
- package/plugins/borealis/index.d.ts +21 -0
- package/plugins/borealis/index.js +46 -0
- package/plugins/borealis/locales/borealis.en.json +7 -0
- package/plugins/borealis/locales/borealis.fr.json +7 -0
- package/plugins/borealis/setup.d.ts +2 -0
- package/plugins/borealis/setup.js +44 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { HowlerFacetSearchRequest, HowlerFacetSearchResponse } from '@cccsaurora/howler-ui/api/search/facet';
|
|
2
|
-
export declare const uri: (
|
|
3
|
-
export declare const post: (
|
|
2
|
+
export declare const uri: () => string;
|
|
3
|
+
export declare const post: (request?: HowlerFacetSearchRequest) => Promise<{
|
|
4
|
+
[index: string]: HowlerFacetSearchResponse;
|
|
5
|
+
}>;
|
package/api/search/facet/hit.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { hpost,
|
|
1
|
+
import { hpost, joinUri } from '@cccsaurora/howler-ui/api';
|
|
2
2
|
import { uri as parentUri } from '@cccsaurora/howler-ui/api/search/facet';
|
|
3
|
-
export const uri = (
|
|
4
|
-
return
|
|
3
|
+
export const uri = () => {
|
|
4
|
+
return joinUri(parentUri(), 'hit');
|
|
5
5
|
};
|
|
6
|
-
export const post = (
|
|
7
|
-
return hpost(uri(
|
|
6
|
+
export const post = (request) => {
|
|
7
|
+
return hpost(uri(), { ...(request || {}), query: request?.query || 'howler.id:*' });
|
|
8
8
|
};
|
|
@@ -84,10 +84,10 @@ const HitSummary = ({ query, response, onStart, onComplete }) => {
|
|
|
84
84
|
setAggregateResults({});
|
|
85
85
|
// Sort the fields based on the number of occurrences
|
|
86
86
|
const sortedKeys = Object.keys(_keyCounts).sort((a, b) => (_keyCounts[b]?.count ?? 0) - (_keyCounts[a]?.count ?? 0));
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
if (sortedKeys.length > 0) {
|
|
88
|
+
setLoading(true);
|
|
89
|
+
const result = await dispatchApi(api.search.facet.hit.post({
|
|
90
|
+
fields: sortedKeys,
|
|
91
91
|
query,
|
|
92
92
|
rows: pageCount,
|
|
93
93
|
filters
|
|
@@ -99,7 +99,7 @@ const HitSummary = ({ query, response, onStart, onComplete }) => {
|
|
|
99
99
|
if (result) {
|
|
100
100
|
setAggregateResults(_results => ({
|
|
101
101
|
..._results,
|
|
102
|
-
|
|
102
|
+
...result
|
|
103
103
|
}));
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -41,7 +41,6 @@ const HitGraph = ({ query }) => {
|
|
|
41
41
|
const addHitToSelection = useContextSelector(HitContext, ctx => ctx.addHitToSelection);
|
|
42
42
|
const removeHitFromSelection = useContextSelector(HitContext, ctx => ctx.removeHitFromSelection);
|
|
43
43
|
const viewId = useContextSelector(HitSearchContext, ctx => ctx.viewId);
|
|
44
|
-
const searching = useContextSelector(HitSearchContext, ctx => ctx.searching);
|
|
45
44
|
const error = useContextSelector(HitSearchContext, ctx => ctx.error);
|
|
46
45
|
const chartRef = useRef();
|
|
47
46
|
const [loading, setLoading] = useState(false);
|
|
@@ -63,6 +62,9 @@ const HitGraph = ({ query }) => {
|
|
|
63
62
|
else if (startDate && endDate) {
|
|
64
63
|
filters.push(`event.created:${convertCustomDateRangeToLucene(startDate, endDate)}`);
|
|
65
64
|
}
|
|
65
|
+
if (escalationFilter) {
|
|
66
|
+
filters.push(`howler.escalation:${escalationFilter}`);
|
|
67
|
+
}
|
|
66
68
|
const total = (await dispatchApi(api.search.count.hit.post({
|
|
67
69
|
query,
|
|
68
70
|
filters
|
|
@@ -75,13 +77,8 @@ const HitGraph = ({ query }) => {
|
|
|
75
77
|
else {
|
|
76
78
|
setDisabled(false);
|
|
77
79
|
}
|
|
78
|
-
const subQueries = [query || 'howler.id:*'];
|
|
79
|
-
if (escalationFilter) {
|
|
80
|
-
subQueries.push(`howler.escalation:${escalationFilter}`);
|
|
81
|
-
}
|
|
82
|
-
const graphQuery = subQueries.map(_query => `(${_query})`).join(' AND ');
|
|
83
80
|
const _data = await dispatchApi(api.search.grouped.hit.post(filterField, {
|
|
84
|
-
query:
|
|
81
|
+
query: query || 'howler.id:*',
|
|
85
82
|
fl: 'event.created,howler.assessment,howler.analytic,howler.detection,howler.outline.threat,howler.outline.target,howler.outline.summary,howler.id',
|
|
86
83
|
// We want a generally random sample across all date ranges, so we use hash.
|
|
87
84
|
// If we used event.created instead, when 1 million hits/hour are created, you'd only see hits from this past minute
|
|
@@ -116,12 +113,12 @@ const HitGraph = ({ query }) => {
|
|
|
116
113
|
}
|
|
117
114
|
}, [dispatchApi, endDate, escalationFilter, filterField, override, query, span, startDate]);
|
|
118
115
|
useEffect(() => {
|
|
119
|
-
if ((!query && !viewId) ||
|
|
116
|
+
if ((!query && !viewId) || error) {
|
|
120
117
|
return;
|
|
121
118
|
}
|
|
122
119
|
performQuery();
|
|
123
120
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
124
|
-
}, [query, viewId,
|
|
121
|
+
}, [query, viewId, error, span]);
|
|
125
122
|
const options = useMemo(() => {
|
|
126
123
|
const parentOptions = scatter('hit.summary.title', 'hit.summary.subtitle');
|
|
127
124
|
return {
|
|
@@ -41,10 +41,12 @@ const useLuceneCompletionProvider = () => {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
|
-
const options = await api.search.facet.hit
|
|
44
|
+
const options = await api.search.facet.hit
|
|
45
|
+
.post({ query: 'howler.id:*', rows: 250, fields: [key] })
|
|
46
|
+
.catch(() => ({}));
|
|
45
47
|
const _position = model.getWordUntilPosition(position);
|
|
46
48
|
return {
|
|
47
|
-
suggestions: Object.keys(options).map(_value => ({
|
|
49
|
+
suggestions: Object.keys(options[key] || {}).map(_value => ({
|
|
48
50
|
label: _value,
|
|
49
51
|
kind: monaco.languages.CompletionItemKind.Constant,
|
|
50
52
|
insertText: `"${_value}"`,
|
|
@@ -18,10 +18,11 @@ const Assessment = forwardRef(({ analytic }, ref) => {
|
|
|
18
18
|
}
|
|
19
19
|
setLoading(true);
|
|
20
20
|
api.search.facet.hit
|
|
21
|
-
.post(
|
|
21
|
+
.post({
|
|
22
|
+
fields: ['howler.assessment'],
|
|
22
23
|
query: `howler.analytic:("${analytic.name}")`
|
|
23
24
|
})
|
|
24
|
-
.then(data => setAssessmentData(data))
|
|
25
|
+
.then(data => setAssessmentData(data['howler.assessment']))
|
|
25
26
|
.finally(() => setLoading(false));
|
|
26
27
|
}, [analytic]);
|
|
27
28
|
if (!loading && assessmentData.length < 1) {
|
|
@@ -18,10 +18,11 @@ const Escalation = forwardRef(({ analytic, maxWidth = '45%' }, ref) => {
|
|
|
18
18
|
}
|
|
19
19
|
setLoading(true);
|
|
20
20
|
api.search.facet.hit
|
|
21
|
-
.post(
|
|
22
|
-
query: `howler.analytic:("${analytic.name}")
|
|
21
|
+
.post({
|
|
22
|
+
query: `howler.analytic:("${analytic.name}")`,
|
|
23
|
+
fields: ['howler.escalation']
|
|
23
24
|
})
|
|
24
|
-
.then(data => setEscalationData(data))
|
|
25
|
+
.then(data => setEscalationData(data['howler.escalation']))
|
|
25
26
|
.finally(() => setLoading(false));
|
|
26
27
|
}, [analytic]);
|
|
27
28
|
return analytic && !loading ? (_jsx("div", { style: { maxWidth }, children: _jsx(Doughnut, { ref: ref, options: doughnut('route.analytics.escalation.title', ''), data: {
|
|
@@ -13,10 +13,11 @@ const Stacked = forwardRef(({ analytic, field, color }, ref) => {
|
|
|
13
13
|
const fetchData = useCallback(async () => {
|
|
14
14
|
try {
|
|
15
15
|
setLoading(true);
|
|
16
|
-
const result = await api.search.facet.hit.post(
|
|
17
|
-
query: `howler.analytic:("${analytic.name}")
|
|
16
|
+
const result = await api.search.facet.hit.post({
|
|
17
|
+
query: `howler.analytic:("${analytic.name}")`,
|
|
18
|
+
fields: [field]
|
|
18
19
|
});
|
|
19
|
-
const values = Object.entries(result)
|
|
20
|
+
const values = Object.entries(result[field])
|
|
20
21
|
.sort(([__, valA], [___, valB]) => valB - valA)
|
|
21
22
|
.map(([key]) => key);
|
|
22
23
|
setDatasets(await Promise.all(values.map(async (_value) => {
|
|
@@ -42,8 +42,8 @@ const HitFilter = ({ size }) => {
|
|
|
42
42
|
setFilter('');
|
|
43
43
|
setSavedFilter(null);
|
|
44
44
|
if (!config.lookups[_category]) {
|
|
45
|
-
const facets = await api.search.facet.hit.post(
|
|
46
|
-
setCustomLookups(Object.keys(facets));
|
|
45
|
+
const facets = await api.search.facet.hit.post({ query: 'howler.id:*', fields: [_category] });
|
|
46
|
+
setCustomLookups(Object.keys(facets[_category]));
|
|
47
47
|
}
|
|
48
48
|
else {
|
|
49
49
|
setCustomLookups([]);
|
package/package.json
CHANGED
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"internal-slot": "1.0.7"
|
|
97
97
|
},
|
|
98
98
|
"type": "module",
|
|
99
|
-
"version": "2.
|
|
99
|
+
"version": "2.13.0-dev.75",
|
|
100
100
|
"exports": {
|
|
101
101
|
"./i18n": "./i18n.js",
|
|
102
102
|
"./index.css": "./index.css",
|
|
@@ -201,6 +201,10 @@
|
|
|
201
201
|
"./components/routes/analytics/widgets/*": "./components/routes/analytics/widgets/*.js",
|
|
202
202
|
"./components/logins/hooks/*": "./components/logins/hooks/*.js",
|
|
203
203
|
"./components/logins/auth/*": "./components/logins/auth/*.js",
|
|
204
|
+
"./plugins/borealis/*": "./plugins/borealis/*.js",
|
|
205
|
+
"./plugins/borealis": "./plugins/borealis/index.js",
|
|
206
|
+
"./plugins/borealis/components/*": "./plugins/borealis/components/*.js",
|
|
207
|
+
"./plugins/borealis/locales/*": "./plugins/borealis/locales/*.js",
|
|
204
208
|
"./api/search/*": "./api/search/*.js",
|
|
205
209
|
"./api/search": "./api/search/index.js",
|
|
206
210
|
"./api/analytic/*": "./api/analytic/*.js",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { BorealisProvider } from 'borealis-ui/dist/hooks/BorealisProvider';
|
|
3
|
+
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
4
|
+
import { useContext } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
7
|
+
import { getStored } from '@cccsaurora/howler-ui/utils/localStorage';
|
|
8
|
+
const Provider = ({ children }) => {
|
|
9
|
+
const apiConfig = useContext(ApiConfigContext);
|
|
10
|
+
return (_jsx(BorealisProvider, { baseURL: location.origin + '/api/v1/borealis', getToken: () => getStored(StorageKey.APP_TOKEN), enabled: apiConfig.config?.configuration?.features?.borealis, publicIconify: false, customIconify: location.origin.includes('localhost')
|
|
11
|
+
? 'https://icons.dev.analysis.cyber.gc.ca'
|
|
12
|
+
: location.origin.replace(/howler(-stg)?/, 'icons'), defaultTimeout: 5, i18next: useTranslation('borealis'), chunkSize: 50, children: children }));
|
|
13
|
+
};
|
|
14
|
+
export default Provider;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Chip } from '@mui/material';
|
|
3
|
+
import { EnrichedChip, useBorealisEnrichSelector } from 'borealis-ui';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
const BorealisChip = ({ children, value, context, ...props }) => {
|
|
6
|
+
const guessType = useBorealisEnrichSelector(ctx => ctx.guessType);
|
|
7
|
+
const type = guessType(value);
|
|
8
|
+
if (!type) {
|
|
9
|
+
return _jsx(Chip, { ...props, children: children });
|
|
10
|
+
}
|
|
11
|
+
let enrichedProps = {
|
|
12
|
+
...props,
|
|
13
|
+
value
|
|
14
|
+
};
|
|
15
|
+
delete enrichedProps.label;
|
|
16
|
+
if (context === 'summary') {
|
|
17
|
+
enrichedProps = {
|
|
18
|
+
...enrichedProps,
|
|
19
|
+
sx: [
|
|
20
|
+
...(Array.isArray(enrichedProps.sx) ? enrichedProps.sx : [enrichedProps.sx]),
|
|
21
|
+
[{ height: '24px', '& .iconify': { fontSize: '1em' } }]
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return _jsx(EnrichedChip, { ...enrichedProps, type: type });
|
|
26
|
+
};
|
|
27
|
+
export default memo(BorealisChip);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Autocomplete, Divider, ListItemText, TextField, Typography } from '@mui/material';
|
|
3
|
+
import { useBorealisEnrichSelector, useBorealisFetcherSelector } from 'borealis-ui';
|
|
4
|
+
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
5
|
+
import { useContext, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
const BorealisLeadForm = ({ lead, metadata, update, updateMetadata }) => {
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
const { config } = useContext(ApiConfigContext);
|
|
10
|
+
const fetchers = useBorealisFetcherSelector(ctx => ctx.fetchers);
|
|
11
|
+
const types = useBorealisEnrichSelector(ctx => ctx.typesDetection);
|
|
12
|
+
const [showCustom, setShowCustom] = useState(false);
|
|
13
|
+
return (_jsxs(_Fragment, { children: [_jsx(Divider, { orientation: "horizontal" }), _jsx(Autocomplete, { disabled: !lead, options: Object.keys(fetchers), renderInput: params => _jsx(TextField, { ...params, size: "small", label: t('route.dossiers.manager.borealis') }), value: Object.keys(fetchers).includes(lead?.content) ? lead.content : '', onChange: (_ev, content) => update({ content, metadata: '{}' }), renderOption: (props, option) => (_jsx(ListItemText, { ...props, sx: { flexDirection: 'column', alignItems: 'start !important' }, primary: _jsx("code", { children: option }), secondary: fetchers[option].description })) }), _jsx(Autocomplete, { options: Object.keys(types), renderInput: params => _jsx(TextField, { ...params, size: "small", label: t('route.dossiers.manager.borealis.type') }), value: metadata?.type ?? '', onChange: (_ev, type) => updateMetadata({ type }) }), _jsx(Autocomplete, { options: ['custom', ...Object.keys(config.indexes.hit)], disabled: !metadata?.type || !types[metadata.type], renderInput: params => (_jsx(TextField, { ...params, size: "small", label: t('route.dossiers.manager.borealis.value') })), getOptionLabel: opt => t(opt), value: metadata?.value ?? '', onChange: (_ev, value) => {
|
|
14
|
+
if (value === 'custom') {
|
|
15
|
+
setShowCustom(true);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
setShowCustom(false);
|
|
19
|
+
updateMetadata({ value });
|
|
20
|
+
}
|
|
21
|
+
} }), showCustom && (_jsxs(_Fragment, { children: [_jsx(TextField, { size: "small", label: t('route.dossiers.manager.borealis.value.custom'), value: metadata?.value ?? '', disabled: !metadata?.type || !types[metadata.type], fullWidth: true, onChange: ev => updateMetadata({ value: ev.target.value }) }), _jsx(Typography, { variant: "caption", color: "text.secondary", sx: { mt: '0 !important' }, children: t('route.dossiers.manager.borealis.value.description') })] }))] }));
|
|
22
|
+
};
|
|
23
|
+
export default BorealisLeadForm;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from '@iconify/react/dist/iconify.js';
|
|
3
|
+
import { Settings } from '@mui/icons-material';
|
|
4
|
+
import { Divider, IconButton, Stack, Typography } from '@mui/material';
|
|
5
|
+
import { useBorealisActionsSelector, useBorealisEnrichSelector } from 'borealis-ui';
|
|
6
|
+
import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
|
|
7
|
+
import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
|
|
8
|
+
import { get } from 'lodash-es';
|
|
9
|
+
import { memo, useCallback, useState } from 'react';
|
|
10
|
+
import { useTranslation } from 'react-i18next';
|
|
11
|
+
const BorealisPivot = ({ pivot, hit, compact }) => {
|
|
12
|
+
const guessType = useBorealisEnrichSelector(ctx => ctx?.guessType);
|
|
13
|
+
const { showErrorMessage } = useMySnackbar();
|
|
14
|
+
const { i18n, t } = useTranslation();
|
|
15
|
+
const actions = useBorealisActionsSelector(ctx => ctx?.availableActions ?? {});
|
|
16
|
+
const executeAction = useBorealisActionsSelector(ctx => ctx?.executeAction);
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
const onBorealisClick = useCallback(async (event, forceMenu = false) => {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
event.stopPropagation();
|
|
21
|
+
if (loading) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!actions[pivot.value]) {
|
|
25
|
+
showErrorMessage(t('pivot.borealis.missing'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
setLoading(true);
|
|
29
|
+
const data = Object.fromEntries(pivot.mappings.map(_mapping => {
|
|
30
|
+
const value = _mapping.field !== 'custom' ? get(hit, _mapping.field) : _mapping.custom_value;
|
|
31
|
+
if (['selector', 'selectors'].includes(_mapping.key)) {
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
return [
|
|
34
|
+
_mapping.key,
|
|
35
|
+
value.map(val => ({
|
|
36
|
+
// TODO: Use the mapped borealis values here eventually
|
|
37
|
+
type: guessType(val),
|
|
38
|
+
value: val
|
|
39
|
+
}))
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
return [
|
|
43
|
+
_mapping.key,
|
|
44
|
+
{
|
|
45
|
+
// TODO: Use the mapped borealis values here eventually
|
|
46
|
+
type: guessType(value),
|
|
47
|
+
value
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
return [_mapping.key, value];
|
|
52
|
+
}));
|
|
53
|
+
const selectors = (actions[pivot.value].accept_multiple ? [data.selectors] : [data.selector]).flat();
|
|
54
|
+
delete data.selector;
|
|
55
|
+
delete data.selectors;
|
|
56
|
+
try {
|
|
57
|
+
await executeAction(pivot.value, selectors, data, { forceMenu });
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
setLoading(false);
|
|
61
|
+
}
|
|
62
|
+
}, [actions, executeAction, guessType, hit, loading, pivot.mappings, pivot.value, showErrorMessage, t]);
|
|
63
|
+
if (!actions[pivot.value]) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
return (_jsx(HowlerCard, { variant: compact ? 'outlined' : 'elevation', onClick: e => onBorealisClick(e), sx: [
|
|
67
|
+
theme => ({
|
|
68
|
+
backgroundColor: 'transparent',
|
|
69
|
+
transition: theme.transitions.create(['border-color']),
|
|
70
|
+
'&:hover': { borderColor: 'primary.main' },
|
|
71
|
+
'& > div': {
|
|
72
|
+
height: '100%'
|
|
73
|
+
}
|
|
74
|
+
}),
|
|
75
|
+
loading
|
|
76
|
+
? { opacity: 0.5, pointerEvents: 'none' }
|
|
77
|
+
: {
|
|
78
|
+
cursor: 'pointer'
|
|
79
|
+
},
|
|
80
|
+
!compact && { border: 'thin solid', borderColor: 'transparent' }
|
|
81
|
+
], children: _jsxs(Stack, { direction: "row", p: compact ? 0.5 : 1, spacing: 1, alignItems: "center", children: [_jsx(Icon, { fontSize: "1.5rem", icon: pivot.icon }), _jsx(Typography, { children: pivot.label[i18n.language] }), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsx(IconButton, { size: "small", onClick: e => onBorealisClick(e, true), children: _jsx(Settings, { fontSize: "small" }) })] }) }));
|
|
82
|
+
};
|
|
83
|
+
export default memo(BorealisPivot);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Autocomplete, Divider, Stack, TextField, Typography, useTheme } from '@mui/material';
|
|
3
|
+
import { useBorealisActionsSelector } from 'borealis-ui';
|
|
4
|
+
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
5
|
+
import { Fragment, useContext } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
const BorealisPivotForm = ({ pivot, update }) => {
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const { config } = useContext(ApiConfigContext);
|
|
11
|
+
const actions = useBorealisActionsSelector(ctx => ctx?.availableActions);
|
|
12
|
+
return (_jsxs(_Fragment, { children: [_jsx(Autocomplete, { fullWidth: true, disabled: !pivot, options: Object.entries(actions)
|
|
13
|
+
.filter(([_key, definition]) => !!definition && definition.format == 'pivot')
|
|
14
|
+
.map(([key]) => key), renderOption: ({ key, ...optionProps }, actionId) => {
|
|
15
|
+
const definition = actions[actionId];
|
|
16
|
+
return (_jsxs(Stack, { component: "li", ...optionProps, spacing: 1, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignSelf: "start", alignItems: "center", children: [_jsx(Typography, { children: definition.name }), _jsx("pre", { style: {
|
|
17
|
+
fontSize: '0.85rem',
|
|
18
|
+
border: `thin solid ${theme.palette.divider}`,
|
|
19
|
+
padding: theme.spacing(0.5),
|
|
20
|
+
borderRadius: theme.shape.borderRadius
|
|
21
|
+
}, children: actionId })] }), _jsx(Typography, { variant: "body2", color: "text.secondary", alignSelf: "start", children: definition.summary })] }, key));
|
|
22
|
+
}, getOptionLabel: opt => actions[opt]?.name ?? '', renderInput: params => (_jsx(TextField, { ...params, size: "small", fullWidth: true, label: t('route.dossiers.manager.pivot.value') })), value: pivot?.value ?? '', onChange: (_ev, value) => update({
|
|
23
|
+
value,
|
|
24
|
+
mappings: [
|
|
25
|
+
{ key: actions[value].accept_multiple ? 'selectors' : 'selector', field: 'howler.id' },
|
|
26
|
+
...Object.entries(actions[value].params?.properties ?? [])
|
|
27
|
+
.filter(([property]) => !['selector', 'selectors'].includes(property))
|
|
28
|
+
.map(([prop, schema]) => ({
|
|
29
|
+
key: prop,
|
|
30
|
+
field: typeof schema === 'boolean' || !schema.default ? null : 'custom',
|
|
31
|
+
custom_value: typeof schema !== 'boolean' && !!schema.default ? schema.default.toString() : null
|
|
32
|
+
}))
|
|
33
|
+
]
|
|
34
|
+
}) }), _jsx(Divider, { flexItem: true }), _jsx(Typography, { children: t('route.dossiers.manager.pivot.mappings') }), pivot?.mappings?.map((_mapping, index) => (
|
|
35
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
36
|
+
_jsxs(Fragment, { children: [_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(TextField, { size: "small", label: t('route.dossiers.manager.pivot.mapping.key'), disabled: !pivot, value: _mapping?.key ?? '', onChange: ev => update({
|
|
37
|
+
mappings: pivot.mappings.map((_m, _index) => index === _index ? { ..._m, key: ev.target.value } : _m)
|
|
38
|
+
}) }), _jsx(Autocomplete, { fullWidth: true, disabled: !pivot, options: ['custom', 'unset', ...Object.keys(config.indexes.hit)], renderInput: params => (_jsx(TextField, { ...params, size: "small", fullWidth: true, label: t('route.dossiers.manager.pivot.mapping.field'), sx: { minWidth: '150px' } })), getOptionLabel: opt => t(opt), value: _mapping.field ?? '', onChange: (_ev, field) => update({
|
|
39
|
+
mappings: pivot.mappings.map((_m, _index) => (index === _index ? { ..._m, field } : _m))
|
|
40
|
+
}) })] }), _mapping.field === 'custom' && (_jsx(TextField, { size: "small", label: t('route.dossiers.manager.pivot.mapping.custom'), disabled: !pivot, value: _mapping?.custom_value ?? '', onChange: ev => update({
|
|
41
|
+
mappings: pivot.mappings.map((_m, _index) => index === _index ? { ..._m, custom_value: ev.target.value } : _m)
|
|
42
|
+
}) }))] }, index)))] }));
|
|
43
|
+
};
|
|
44
|
+
export default BorealisPivotForm;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Typography } from '@mui/material';
|
|
3
|
+
import { EnrichedTypography, useBorealisEnrichSelector } from 'borealis-ui';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
const BorealisTypography = ({ children, value, context, ...props }) => {
|
|
6
|
+
const guessType = useBorealisEnrichSelector(ctx => ctx.guessType);
|
|
7
|
+
const type = guessType(value);
|
|
8
|
+
if (!type) {
|
|
9
|
+
return _jsx(Typography, { ...props, children: children });
|
|
10
|
+
}
|
|
11
|
+
let enrichedProps = {
|
|
12
|
+
...props,
|
|
13
|
+
value
|
|
14
|
+
};
|
|
15
|
+
if (context === 'banner') {
|
|
16
|
+
enrichedProps = {
|
|
17
|
+
...enrichedProps,
|
|
18
|
+
slotProps: { stack: { component: 'span' } }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
else if (context === 'outline') {
|
|
22
|
+
enrichedProps = {
|
|
23
|
+
...enrichedProps,
|
|
24
|
+
hideLoading: true,
|
|
25
|
+
slotProps: {
|
|
26
|
+
stack: {
|
|
27
|
+
sx: { mr: 'auto' },
|
|
28
|
+
onClick: e => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
e.stopPropagation();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
popover: {
|
|
34
|
+
onClick: e => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
e.stopPropagation();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
else if (context === 'table') {
|
|
43
|
+
enrichedProps = {
|
|
44
|
+
...enrichedProps,
|
|
45
|
+
hideLoading: true,
|
|
46
|
+
slotProps: {
|
|
47
|
+
stack: { sx: { width: '100%', '& > p': { textOverflow: 'ellipsis', overflow: 'hidden' } } }
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return _jsx(EnrichedTypography, { ...enrichedProps, type: type });
|
|
52
|
+
};
|
|
53
|
+
export default memo(BorealisTypography);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
import { Checkbox, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
|
|
4
|
+
import { EnrichedTypography, Entry, Fetcher, Group, useBorealisEnrichSelector } from 'borealis-ui';
|
|
5
|
+
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
6
|
+
import i18nInstance from '@cccsaurora/howler-ui/i18n';
|
|
7
|
+
import { capitalize, groupBy, uniq } from 'lodash-es';
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
const BorealisTypography = props => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
try {
|
|
13
|
+
const guessType = useBorealisEnrichSelector(ctx => ctx.guessType);
|
|
14
|
+
let type = props.type;
|
|
15
|
+
if (!type || type?.toLowerCase() === 'guess') {
|
|
16
|
+
type = guessType(props.value);
|
|
17
|
+
}
|
|
18
|
+
if (!type) {
|
|
19
|
+
return _jsx("span", { children: props.value });
|
|
20
|
+
}
|
|
21
|
+
return _jsx(EnrichedTypography, { ...props, type: type });
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return (_jsxs(Stack, { children: [_jsx("strong", { style: { color: 'red' }, children: t('markdown.error') }), _jsx("strong", { children: err.toString() }), _jsx("code", { style: { fontSize: '0.8rem' }, children: _jsx("pre", { children: err.stack }) })] }));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const BorealisGroup = props => {
|
|
28
|
+
if (!props.enabled) {
|
|
29
|
+
return _jsx(_Fragment, { children: props.children });
|
|
30
|
+
}
|
|
31
|
+
if (!props.type) {
|
|
32
|
+
console.error('Missing required props for group helper');
|
|
33
|
+
return (_jsxs(Stack, { spacing: 1, children: [_jsx("strong", { style: { color: 'red' }, children: i18nInstance.t('markdown.error') }), _jsxs("code", { style: { fontSize: '0.8rem' }, children: [i18nInstance.t('markdown.props.missing'), ": type"] })] }));
|
|
34
|
+
}
|
|
35
|
+
return _jsx(Group, { type: props.type, children: props.children });
|
|
36
|
+
};
|
|
37
|
+
const BorealisEntry = ({ value }) => {
|
|
38
|
+
const [checked, setChecked] = useState(false);
|
|
39
|
+
return (_jsx(Entry, { entry: value, selected: checked, children: _jsx(Paper, { sx: { p: 1 }, children: _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Checkbox, { checked: checked, onChange: (_event, _checked) => setChecked(_checked) }), _jsx(BorealisTypography, { value: value }), _jsx(FlexOne, {})] }) }) }));
|
|
40
|
+
};
|
|
41
|
+
const BorealisCheckbox = ({ value }) => {
|
|
42
|
+
const [checked, setChecked] = useState(false);
|
|
43
|
+
return (_jsx(Entry, { entry: value, selected: checked, children: _jsx(Checkbox, { checked: checked, onChange: (_event, _checked) => setChecked(_checked) }) }));
|
|
44
|
+
};
|
|
45
|
+
const HELPERS = [
|
|
46
|
+
{
|
|
47
|
+
keyword: 'borealis',
|
|
48
|
+
documentation: 'Given a selector, this helper enriches the selector through borealis.',
|
|
49
|
+
componentCallback: (type, value) => {
|
|
50
|
+
if (typeof type !== 'string' || typeof value !== 'string') {
|
|
51
|
+
return (_jsxs(Stack, { spacing: 1, children: [_jsx("strong", { style: { color: 'red' }, children: i18nInstance.t('markdown.error') }), _jsx("code", { style: { fontSize: '0.8rem' }, children: "You must provide at least two arguments: type and value." })] }));
|
|
52
|
+
}
|
|
53
|
+
return (_jsx(BorealisTypography, { slotProps: { stack: { component: 'span', sx: { width: 'fit-content' } } }, component: "span", type: type, value: typeof value === 'string' ? value : null }));
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
keyword: 'fetcher',
|
|
58
|
+
documentation: 'Given a selector, this helper fetches data for the selector through borealis.',
|
|
59
|
+
componentCallback: (...args) => {
|
|
60
|
+
const options = args.pop();
|
|
61
|
+
const props = options?.hash ?? {};
|
|
62
|
+
if (!props.type || !props.value || !props.fetcherId) {
|
|
63
|
+
console.error('Missing required props for fetcher helper');
|
|
64
|
+
return (_jsxs(Stack, { spacing: 1, children: [_jsx("strong", { style: { color: 'red' }, children: i18nInstance.t('markdown.error') }), _jsxs("code", { style: { fontSize: '0.8rem' }, children: [i18nInstance.t('markdown.props.missing'), ":", ' ', ['type', 'value', 'fetcherId'].filter(key => !props[key]).join(', ')] })] }));
|
|
65
|
+
}
|
|
66
|
+
console.debug(`Rendering fetcher (${props.fetcherId}) for selector ${props.type}:${props.value}`);
|
|
67
|
+
return (_jsx(Fetcher, { slotProps: { stack: { component: 'span', sx: { width: 'fit-content' } } }, component: "span", ...props }));
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
keyword: 'borealis_group',
|
|
72
|
+
documentation: 'Initializes a borealis group',
|
|
73
|
+
componentCallback: (values, ...args) => {
|
|
74
|
+
const options = args.pop();
|
|
75
|
+
const props = options?.hash ?? {};
|
|
76
|
+
const missing = [];
|
|
77
|
+
if (!Array.isArray(values)) {
|
|
78
|
+
missing.push('values');
|
|
79
|
+
}
|
|
80
|
+
if (!props.type) {
|
|
81
|
+
missing.push('type');
|
|
82
|
+
}
|
|
83
|
+
if (missing.length > 0) {
|
|
84
|
+
return (_jsxs(Stack, { spacing: 1, children: [_jsx("strong", { style: { color: 'red' }, children: i18nInstance.t('markdown.error') }), _jsxs("code", { style: { fontSize: '0.8rem' }, children: [i18nInstance.t('markdown.props.missing'), ": ", missing.join(', ')] })] }));
|
|
85
|
+
}
|
|
86
|
+
return (_jsx(BorealisGroup, { type: props.type, enabled: true, children: _jsx(Stack, { spacing: 1, mt: 1, children: uniq(values).map(value => (_jsx(BorealisEntry, { value: value }, value))) }) }));
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
keyword: 'borealis_table',
|
|
91
|
+
documentation: `Render a table with optional Borealis enrichments and actions.
|
|
92
|
+
|
|
93
|
+
Borealis enrichments are performed for cells with a borealis_type.
|
|
94
|
+
|
|
95
|
+
Borealis actions are enabled by specifying a borealis action type using the optional borealis_action_type parameter. If enabled, cells with borealis_entity==true will be selectable for use with Borealis enrichments and actions, with a value of action_value if present, and otherwise value.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
\`\`\`markdown
|
|
99
|
+
{{curly 'borealis_table borealis_table_cells borealis_action_type="ip"'}}
|
|
100
|
+
\`\`\`
|
|
101
|
+
where borealis_table_cells is an array with properties:
|
|
102
|
+
|
|
103
|
+
\`\`\`
|
|
104
|
+
column: string;
|
|
105
|
+
row: string;
|
|
106
|
+
value: string;
|
|
107
|
+
borealis_type (optional): string;
|
|
108
|
+
borealis_entity (optional): boolean;
|
|
109
|
+
action_value (optional): string;
|
|
110
|
+
\`\`\`
|
|
111
|
+
`,
|
|
112
|
+
componentCallback: (cells, ...args) => {
|
|
113
|
+
const options = args.pop();
|
|
114
|
+
const props = options?.hash ?? {};
|
|
115
|
+
const columns = Object.keys(groupBy(cells, 'column'));
|
|
116
|
+
const rows = groupBy(cells, 'row');
|
|
117
|
+
const enableBorealisActions = !props.borealis_action_type ? false : true;
|
|
118
|
+
const borealisActionType = !props.borealis_action_type ? 'false' : props.borealis_action_type;
|
|
119
|
+
return (_jsx(Paper, { sx: { width: '95%', overflowX: 'auto', m: 1 }, children: _jsx(BorealisGroup, { type: borealisActionType, enabled: enableBorealisActions, children: _jsxs(Table, { children: [_jsx(TableHead, { children: _jsx(TableRow, { children: columns.map(col => (_jsx(TableCell, { sx: { maxWidth: '150px' }, children: col
|
|
120
|
+
.split(/[_-]/)
|
|
121
|
+
.map(word => capitalize(word))
|
|
122
|
+
.join(' ') }, col))) }) }), _jsx(TableBody, { sx: { '& td': { wordBreak: 'break-word' } }, children: Object.entries(rows).map(([rowId, _cells]) => {
|
|
123
|
+
return (_jsx(TableRow, { children: columns.map(col => {
|
|
124
|
+
const cell = _cells.find(row => row.column === col);
|
|
125
|
+
return (_jsxs(TableCell, { children: [enableBorealisActions && cell.borealis_entity === true ? (_jsx(BorealisCheckbox, { value: cell.action_value ?? cell.value })) : null, typeof cell.borealis_type === 'string' ? (_jsx(BorealisTypography, { slotProps: {
|
|
126
|
+
stack: {
|
|
127
|
+
component: 'span',
|
|
128
|
+
sx: { width: 'fit-content' },
|
|
129
|
+
display: 'inline-flex'
|
|
130
|
+
}
|
|
131
|
+
}, component: "span", type: cell?.borealis_type, value: cell?.value })) : ((cell?.value ?? 'N/A'))] }, col + cell?.value));
|
|
132
|
+
}) }, rowId));
|
|
133
|
+
}) })] }) }) }));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
export default HELPERS;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HowlerHelper } from '@cccsaurora/howler-ui/components/elements/display/handlebars/helpers';
|
|
2
|
+
import type { PluginChipProps } from '@cccsaurora/howler-ui/components/elements/PluginChip';
|
|
3
|
+
import type { PluginTypographyProps } from '@cccsaurora/howler-ui/components/elements/PluginTypography';
|
|
4
|
+
import { type i18n as I18N } from 'i18next';
|
|
5
|
+
import HowlerPlugin from '@cccsaurora/howler-ui/plugins/HowlerPlugin';
|
|
6
|
+
declare class BorealisPlugin extends HowlerPlugin {
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
author: string;
|
|
10
|
+
description: string;
|
|
11
|
+
activate(): void;
|
|
12
|
+
provider(): import("react").FC<{
|
|
13
|
+
children?: import("react").ReactNode | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
setup(): () => void;
|
|
16
|
+
localization(i18nInstance: I18N): void;
|
|
17
|
+
helpers(): HowlerHelper[];
|
|
18
|
+
typography(_props: PluginTypographyProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
chip(_props: PluginChipProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
}
|
|
21
|
+
export default BorealisPlugin;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from '@mui/material';
|
|
3
|
+
import { Fetcher } from 'borealis-ui';
|
|
4
|
+
import borealisEN from 'borealis-ui/dist/en/translation.json';
|
|
5
|
+
import borealisFR from 'borealis-ui/dist/fr/translation.json';
|
|
6
|
+
import {} from 'i18next';
|
|
7
|
+
import HowlerPlugin from '@cccsaurora/howler-ui/plugins/HowlerPlugin';
|
|
8
|
+
import BorealisChip from './components/BorealisChip';
|
|
9
|
+
import BorealisLeadForm from './components/BorealisLeadForm';
|
|
10
|
+
import BorealisPivot from './components/BorealisPivot';
|
|
11
|
+
import BorealisPivotForm from './components/BorealisPivotForm';
|
|
12
|
+
import BorealisTypography from './components/BorealisTypography';
|
|
13
|
+
import HELPERS from './helpers';
|
|
14
|
+
import Provider from './Provider';
|
|
15
|
+
import useSetup from './setup';
|
|
16
|
+
class BorealisPlugin extends HowlerPlugin {
|
|
17
|
+
name = 'BorealisPlugin';
|
|
18
|
+
version = '0.0.1';
|
|
19
|
+
author = 'Matthew Rafuse <matthew.rafuse@cyber.gc.ca>';
|
|
20
|
+
description = 'This plugin enables borealis enrichment in Howler.';
|
|
21
|
+
activate() {
|
|
22
|
+
super.activate();
|
|
23
|
+
super.addLead('borealis', props => _jsx(BorealisLeadForm, { ...props }), (content, metadata) => (_jsx(Box, { p: 1, flex: 1, display: "flex", alignItems: "stretch", children: _jsx(Fetcher, { fetcherId: content, ...JSON.parse(metadata) }) })));
|
|
24
|
+
super.addPivot('borealis', props => _jsx(BorealisPivotForm, { ...props }), props => _jsx(BorealisPivot, { ...props }));
|
|
25
|
+
}
|
|
26
|
+
provider() {
|
|
27
|
+
return Provider;
|
|
28
|
+
}
|
|
29
|
+
setup() {
|
|
30
|
+
return useSetup;
|
|
31
|
+
}
|
|
32
|
+
localization(i18nInstance) {
|
|
33
|
+
i18nInstance.addResourceBundle('en', 'borealis', borealisEN, true, true);
|
|
34
|
+
i18nInstance.addResourceBundle('fr', 'borealis', borealisFR, true, true);
|
|
35
|
+
}
|
|
36
|
+
helpers() {
|
|
37
|
+
return HELPERS;
|
|
38
|
+
}
|
|
39
|
+
typography(_props) {
|
|
40
|
+
return _jsx(BorealisTypography, { ..._props });
|
|
41
|
+
}
|
|
42
|
+
chip(_props) {
|
|
43
|
+
return _jsx(BorealisChip, { ..._props });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export default BorealisPlugin;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"route.dossiers.manager.borealis": "Borealis Fetcher ID",
|
|
3
|
+
"route.dossiers.manager.borealis.type": "Selector Type",
|
|
4
|
+
"route.dossiers.manager.borealis.value": "Selector Value",
|
|
5
|
+
"route.dossiers.manager.borealis.value.custom": "Custom Selector Value",
|
|
6
|
+
"route.dossiers.manager.borealis.value.description": "You can use handlebars notation to insert fields from the corresponding alert (i.e. {{howler.id}})."
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"route.dossiers.manager.borealis": "ID du sélecteur Borealis",
|
|
3
|
+
"route.dossiers.manager.borealis.type": "Type de sélecteur",
|
|
4
|
+
"route.dossiers.manager.borealis.value": "Valeur du sélecteur",
|
|
5
|
+
"route.dossiers.manager.borealis.value.custom": "Valeur du sélecteur personnalisé",
|
|
6
|
+
"route.dossiers.manager.borealis.value.description": "Vous pouvez utiliser la notation « handlebars » pour insérer des champs à partir de l'alerte correspondante (c'est-à-dire {{howler.id}})."
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SNACKBAR_EVENT_ID, useBorealis } from 'borealis-ui';
|
|
2
|
+
import { useAppUser } from '@cccsaurora/howler-ui/commons/components/app/hooks';
|
|
3
|
+
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
4
|
+
import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
|
|
5
|
+
import { useContext, useEffect } from 'react';
|
|
6
|
+
const useSetup = () => {
|
|
7
|
+
const borealis = useBorealis();
|
|
8
|
+
const appUser = useAppUser();
|
|
9
|
+
const apiConfig = useContext(ApiConfigContext);
|
|
10
|
+
const { showSuccessMessage, showErrorMessage, showInfoMessage, showWarningMessage } = useMySnackbar();
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.debug('Initializing borealis snackbar event handler');
|
|
14
|
+
const handleMessage = (event) => {
|
|
15
|
+
const { detail } = event;
|
|
16
|
+
if (detail.level === 'success') {
|
|
17
|
+
showSuccessMessage(detail.message, detail.timeout, detail.options);
|
|
18
|
+
}
|
|
19
|
+
else if (detail.level === 'error') {
|
|
20
|
+
showErrorMessage(detail.message, detail.timeout, detail.options);
|
|
21
|
+
}
|
|
22
|
+
else if (detail.level === 'info') {
|
|
23
|
+
showInfoMessage(detail.message, detail.timeout, detail.options);
|
|
24
|
+
}
|
|
25
|
+
else if (detail.level === 'warning') {
|
|
26
|
+
showWarningMessage(detail.message, detail.timeout, detail.options);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
window.addEventListener(SNACKBAR_EVENT_ID, handleMessage);
|
|
30
|
+
return () => {
|
|
31
|
+
window.removeEventListener(SNACKBAR_EVENT_ID, handleMessage);
|
|
32
|
+
};
|
|
33
|
+
}, [showErrorMessage, showInfoMessage, showSuccessMessage, showWarningMessage]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!appUser.isReady()) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (apiConfig.config.configuration?.features?.borealis) {
|
|
39
|
+
borealis.setReady(true);
|
|
40
|
+
}
|
|
41
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
+
}, [apiConfig.config.configuration?.features?.borealis, appUser.isReady()]);
|
|
43
|
+
};
|
|
44
|
+
export default useSetup;
|