@cccsaurora/howler-ui 2.16.0-dev.378 → 2.16.0-dev.381
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 +2 -0
- package/components/app/hooks/useMatchers.js +0 -4
- package/components/app/providers/FavouritesProvider.js +2 -1
- package/components/app/providers/FieldProvider.d.ts +2 -2
- package/components/app/providers/HitProvider.d.ts +3 -3
- package/components/app/providers/HitSearchProvider.d.ts +7 -8
- package/components/app/providers/HitSearchProvider.js +64 -39
- package/components/app/providers/HitSearchProvider.test.d.ts +1 -0
- package/components/app/providers/HitSearchProvider.test.js +505 -0
- package/components/app/providers/ParameterProvider.d.ts +13 -5
- package/components/app/providers/ParameterProvider.js +240 -84
- package/components/app/providers/ParameterProvider.test.d.ts +1 -0
- package/components/app/providers/ParameterProvider.test.js +1041 -0
- package/components/app/providers/ViewProvider.d.ts +3 -2
- package/components/app/providers/ViewProvider.js +21 -14
- package/components/app/providers/ViewProvider.test.js +19 -29
- package/components/elements/display/ChipPopper.d.ts +21 -0
- package/components/elements/display/ChipPopper.js +36 -0
- package/components/elements/display/ChipPopper.test.d.ts +1 -0
- package/components/elements/display/ChipPopper.test.js +309 -0
- package/components/elements/hit/HitActions.js +3 -3
- package/components/elements/hit/HitSummary.d.ts +0 -1
- package/components/elements/hit/HitSummary.js +11 -21
- package/components/elements/hit/aggregate/HitGraph.d.ts +1 -3
- package/components/elements/hit/aggregate/HitGraph.js +9 -15
- package/components/routes/dossiers/DossierCard.test.js +0 -2
- package/components/routes/dossiers/DossierEditor.test.js +27 -33
- package/components/routes/hits/search/HitBrowser.js +7 -48
- package/components/routes/hits/search/HitContextMenu.test.js +11 -29
- package/components/routes/hits/search/InformationPane.js +1 -1
- package/components/routes/hits/search/QuerySettings.js +30 -0
- package/components/routes/hits/search/QuerySettings.test.d.ts +1 -0
- package/components/routes/hits/search/QuerySettings.test.js +553 -0
- package/components/routes/hits/search/SearchPane.js +8 -10
- package/components/routes/hits/search/ViewLink.d.ts +4 -1
- package/components/routes/hits/search/ViewLink.js +37 -19
- package/components/routes/hits/search/ViewLink.test.js +349 -303
- package/components/routes/hits/search/grid/HitGrid.js +2 -6
- package/components/routes/hits/search/shared/HitFilter.d.ts +2 -0
- package/components/routes/hits/search/shared/HitFilter.js +31 -23
- package/components/routes/hits/search/shared/HitSort.js +16 -8
- package/components/routes/hits/search/shared/SearchSpan.js +19 -10
- package/components/routes/views/ViewComposer.js +7 -6
- package/components/routes/views/Views.js +2 -1
- package/locales/en/translation.json +6 -0
- package/locales/fr/translation.json +6 -0
- package/package.json +2 -2
- package/setupTests.js +4 -1
- package/tests/mocks.d.ts +18 -0
- package/tests/mocks.js +65 -0
- package/tests/server-handlers.js +10 -28
- package/utils/viewUtils.d.ts +2 -0
- package/utils/viewUtils.js +11 -0
- package/components/routes/hits/search/shared/QuerySettings.js +0 -22
- /package/components/routes/hits/search/{shared/QuerySettings.d.ts → QuerySettings.d.ts} +0 -0
- /package/components/routes/hits/search/{CustomSort.d.ts → shared/CustomSort.d.ts} +0 -0
- /package/components/routes/hits/search/{CustomSort.js → shared/CustomSort.js} +0 -0
|
@@ -7,7 +7,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';
|
|
10
|
-
import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
|
|
11
10
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
12
11
|
import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
|
|
13
12
|
import DevelopmentBanner from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentBanner';
|
|
@@ -20,8 +19,7 @@ import { useContextSelector } from 'use-context-selector';
|
|
|
20
19
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
21
20
|
import HitContextMenu from '../HitContextMenu';
|
|
22
21
|
import HitQuery from '../HitQuery';
|
|
23
|
-
import QuerySettings from '../
|
|
24
|
-
import ViewLink from '../ViewLink';
|
|
22
|
+
import QuerySettings from '../QuerySettings';
|
|
25
23
|
import AddColumnModal from './AddColumnModal';
|
|
26
24
|
import ColumnHeader from './ColumnHeader';
|
|
27
25
|
import HitRow from './HitRow';
|
|
@@ -36,11 +34,9 @@ const HitGrid = () => {
|
|
|
36
34
|
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
37
35
|
const response = useContextSelector(HitSearchContext, ctx => ctx.response);
|
|
38
36
|
const searching = useContextSelector(HitSearchContext, ctx => ctx.searching);
|
|
39
|
-
const viewId = useContextSelector(HitSearchContext, ctx => ctx.viewId);
|
|
40
37
|
const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
|
|
41
38
|
const query = useContextSelector(ParameterContext, ctx => ctx.query);
|
|
42
39
|
const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
|
|
43
|
-
const selectedView = useContextSelector(ViewContext, ctx => ctx.views[viewId]);
|
|
44
40
|
const [collapseMainColumn, setCollapseMainColumn] = useMyLocalStorageItem(StorageKey.GRID_COLLAPSE_COLUMN, false);
|
|
45
41
|
const [analyticIds, setAnalyticIds] = useState({});
|
|
46
42
|
const columnModalRef = useRef();
|
|
@@ -124,7 +120,7 @@ const HitGrid = () => {
|
|
|
124
120
|
}
|
|
125
121
|
return selectedElement.id;
|
|
126
122
|
}, []);
|
|
127
|
-
return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}),
|
|
123
|
+
return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5, textAlign: 'left' }, variant: "body2", children: t('hit.search.prompt') }), _jsx(DevelopmentIcon, {})] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Stack, { position: "relative", flex: 1, children: [_jsx(HitQuery, { searching: searching, triggerSearch: search, compact: true }), searching && (_jsx(LinearProgress, { sx: {
|
|
128
124
|
position: 'absolute',
|
|
129
125
|
left: 0,
|
|
130
126
|
right: 0,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { FilterList } from '@mui/icons-material';
|
|
3
|
+
import { Autocomplete, Stack, TextField, Typography } from '@mui/material';
|
|
3
4
|
import api from '@cccsaurora/howler-ui/api';
|
|
4
5
|
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
5
6
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
7
|
+
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
8
|
+
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
6
9
|
import { memo, useCallback, useContext, useEffect, useState } from 'react';
|
|
7
10
|
import { useTranslation } from 'react-i18next';
|
|
8
11
|
import { useContextSelector } from 'use-context-selector';
|
|
@@ -15,50 +18,55 @@ const ACCEPTED_LOOKUPS = [
|
|
|
15
18
|
'event.provider',
|
|
16
19
|
'organization.name'
|
|
17
20
|
];
|
|
18
|
-
const HitFilter = ({ size }) => {
|
|
21
|
+
const HitFilter = ({ size, id, value }) => {
|
|
19
22
|
const { t } = useTranslation();
|
|
20
23
|
const { config } = useContext(ApiConfigContext);
|
|
21
|
-
const
|
|
24
|
+
const { dispatchApi } = useMyApi();
|
|
22
25
|
const setSavedFilter = useContextSelector(ParameterContext, ctx => ctx.setFilter);
|
|
23
|
-
const
|
|
24
|
-
const [
|
|
26
|
+
const removeSavedFilter = useContextSelector(ParameterContext, ctx => ctx.removeFilter);
|
|
27
|
+
const [category, setCategory] = useState(value?.split(':')[0] ?? ACCEPTED_LOOKUPS[0]);
|
|
28
|
+
const [filter, setFilter] = useState(value?.split(':')[1] ?? null);
|
|
29
|
+
const [loading, setLoading] = useState(false);
|
|
25
30
|
const [customLookups, setCustomLookups] = useState([]);
|
|
26
31
|
useEffect(() => {
|
|
27
|
-
if (
|
|
28
|
-
const [_category, _filter] = (
|
|
32
|
+
if (value) {
|
|
33
|
+
const [_category, _filter] = (value || ':').split(':');
|
|
29
34
|
if (_category) {
|
|
30
35
|
setCategory(_category);
|
|
31
36
|
}
|
|
32
|
-
if (_filter) {
|
|
37
|
+
if (_filter && _filter !== '*') {
|
|
33
38
|
setFilter(_filter);
|
|
34
39
|
}
|
|
35
40
|
if (_category && _filter) {
|
|
36
|
-
setSavedFilter(`${_category}:${_filter}`);
|
|
41
|
+
setSavedFilter(id, `${_category}:${_filter}`);
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
|
-
}, [setSavedFilter,
|
|
44
|
+
}, [id, setSavedFilter, value]);
|
|
40
45
|
const onCategoryChange = useCallback(async (_, _category) => {
|
|
41
46
|
setCategory(_category);
|
|
42
|
-
setFilter(
|
|
43
|
-
setSavedFilter(null);
|
|
47
|
+
setFilter(null);
|
|
44
48
|
if (!config.lookups[_category]) {
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
setLoading(true);
|
|
50
|
+
const facets = await dispatchApi(api.search.facet.hit.post({ query: 'howler.id:*', fields: [_category], rows: 100 }), {
|
|
51
|
+
throwError: false
|
|
52
|
+
});
|
|
53
|
+
setCustomLookups(Object.keys((facets ?? {})[_category]));
|
|
54
|
+
setLoading(false);
|
|
47
55
|
}
|
|
48
56
|
else {
|
|
49
57
|
setCustomLookups([]);
|
|
50
58
|
}
|
|
51
|
-
}, [config.lookups,
|
|
52
|
-
const onValueChange = useCallback((_,
|
|
53
|
-
setFilter(
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
setSavedFilter(newFilter);
|
|
59
|
+
}, [config.lookups, dispatchApi]);
|
|
60
|
+
const onValueChange = useCallback((_, newValue) => {
|
|
61
|
+
setFilter(newValue);
|
|
62
|
+
if (newValue && newValue !== '*') {
|
|
63
|
+
setSavedFilter(id, `${category}:"${sanitizeLuceneQuery(newValue)}"`);
|
|
57
64
|
}
|
|
58
65
|
else {
|
|
59
|
-
setSavedFilter(
|
|
66
|
+
setSavedFilter(id, `${category}:*`);
|
|
60
67
|
}
|
|
61
|
-
}, [category, setSavedFilter]);
|
|
62
|
-
|
|
68
|
+
}, [category, id, setSavedFilter]);
|
|
69
|
+
const filterValue = filter?.replaceAll('"', '').replaceAll('\\-', '-') || '';
|
|
70
|
+
return (_jsx(ChipPopper, { icon: _jsx(FilterList, { fontSize: "small" }), label: category && _jsx(Typography, { variant: "body2", children: `${category}:${filterValue || '*'}` }), minWidth: "250px", onDelete: () => removeSavedFilter(value), slotProps: { chip: { size: 'small', color: value?.endsWith('*') ? 'warning' : 'default' } }, children: _jsxs(Stack, { spacing: 1, sx: { minWidth: '225px' }, children: [_jsx(Autocomplete, { fullWidth: true, size: size ?? 'small', value: category, options: ACCEPTED_LOOKUPS, renderInput: _params => _jsx(TextField, { ..._params, label: t('hit.search.filter.fields') }), onChange: onCategoryChange }), _jsx(Autocomplete, { fullWidth: true, freeSolo: true, disabled: !category, loading: loading, size: size ?? 'small', value: filter?.replaceAll('"', '').replaceAll('\\-', '-') || '', options: [...(config.lookups[category] ? config.lookups[category] : customLookups), '*'], renderInput: _params => _jsx(TextField, { ..._params, label: t('hit.search.filter.values') }), getOptionLabel: option => t(option), onChange: onValueChange })] }) }));
|
|
63
71
|
};
|
|
64
72
|
export default memo(HitFilter);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Sort } from '@mui/icons-material';
|
|
3
|
+
import { Autocomplete, Stack, TextField } from '@mui/material';
|
|
3
4
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
4
5
|
import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
|
|
6
|
+
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
5
7
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
8
|
import { useTranslation } from 'react-i18next';
|
|
7
9
|
import { useLocation } from 'react-router-dom';
|
|
8
10
|
import { useContextSelector } from 'use-context-selector';
|
|
9
|
-
import CustomSort from '
|
|
11
|
+
import CustomSort from './CustomSort';
|
|
10
12
|
const CUSTOM = '__custom__';
|
|
11
13
|
const ACCEPTED_SORTS = [
|
|
12
14
|
'event.created',
|
|
@@ -21,7 +23,8 @@ const ACCEPTED_SORTS = [
|
|
|
21
23
|
const HitSort = ({ size = 'small' }) => {
|
|
22
24
|
const { t } = useTranslation();
|
|
23
25
|
const location = useLocation();
|
|
24
|
-
const
|
|
26
|
+
const getCurrentViews = useContextSelector(ViewContext, ctx => ctx.getCurrentViews);
|
|
27
|
+
const views = useContextSelector(ParameterContext, ctx => ctx.views);
|
|
25
28
|
const savedSort = useContextSelector(ParameterContext, ctx => ctx.sort);
|
|
26
29
|
const setSavedSort = useContextSelector(ParameterContext, ctx => ctx.setSort);
|
|
27
30
|
const sortEntries = useMemo(() => savedSort.split(',').filter(part => !!part), [savedSort]);
|
|
@@ -53,13 +56,18 @@ const HitSort = ({ size = 'small' }) => {
|
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
55
58
|
(async () => {
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
setSavedSort(
|
|
59
|
+
const selectedViewSort = (await getCurrentViews({ lazy: true })).find(view => view?.sort)?.sort;
|
|
60
|
+
if (selectedViewSort && !location.search.includes('sort')) {
|
|
61
|
+
setSavedSort(selectedViewSort);
|
|
59
62
|
}
|
|
60
63
|
})();
|
|
61
64
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
|
-
}, [
|
|
63
|
-
return
|
|
65
|
+
}, [getCurrentViews, views]);
|
|
66
|
+
return (_jsx(ChipPopper, { icon: _jsx(Sort, { fontSize: "small" }), label: savedSort, slotProps: { chip: { size: 'small' } }, children: !showCustomSort ? (_jsxs(Stack, { spacing: 1, onClick: e => {
|
|
67
|
+
e.stopPropagation();
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
}, children: [_jsx(Autocomplete, { fullWidth: true, sx: { minWidth: '275px' }, size: size, value: field, options: ACCEPTED_SORTS, getOptionLabel: option => (option === CUSTOM ? t('hit.search.custom') : option), isOptionEqualToValue: (option, value) => option === value || (!value && option === ACCEPTED_SORTS[0]), renderInput: _params => _jsx(TextField, { ..._params, label: t('hit.search.sort.fields') }), onChange: (_, value) => handleChange(value) }), _jsx(Autocomplete, { fullWidth: true, size: size, sx: { minWidth: '150px' }, value: sort, onChange: (_e, value) => {
|
|
70
|
+
setSavedSort(`${field} ${value}`);
|
|
71
|
+
}, options: ['asc', 'desc'], getOptionLabel: option => t(option), renderInput: _params => _jsx(TextField, { ..._params }) })] })) : (_jsx(CustomSort, {})) }));
|
|
64
72
|
};
|
|
65
73
|
export default memo(HitSort);
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AvTimer } from '@mui/icons-material';
|
|
3
|
+
import { Autocomplete, Stack, TextField, Typography } from '@mui/material';
|
|
3
4
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
4
5
|
import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
|
|
6
|
+
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
7
|
+
import dayjs from 'dayjs';
|
|
5
8
|
import { memo, useEffect } from 'react';
|
|
6
9
|
import { useTranslation } from 'react-i18next';
|
|
7
10
|
import { useLocation } from 'react-router-dom';
|
|
8
11
|
import { useContextSelector } from 'use-context-selector';
|
|
9
12
|
import { convertLuceneToDate } from '@cccsaurora/howler-ui/utils/utils';
|
|
13
|
+
import CustomSpan from './CustomSpan';
|
|
10
14
|
const DATE_RANGES = [
|
|
11
15
|
'date.range.1.day',
|
|
12
16
|
'date.range.3.day',
|
|
@@ -18,27 +22,32 @@ const DATE_RANGES = [
|
|
|
18
22
|
const SearchSpan = ({ omitCustom = false, size }) => {
|
|
19
23
|
const { t } = useTranslation();
|
|
20
24
|
const location = useLocation();
|
|
25
|
+
const views = useContextSelector(ParameterContext, ctx => ctx.views);
|
|
21
26
|
const span = useContextSelector(ParameterContext, ctx => ctx.span);
|
|
22
27
|
const setSpan = useContextSelector(ParameterContext, ctx => ctx.setSpan);
|
|
23
|
-
const
|
|
28
|
+
const startDate = useContextSelector(ParameterContext, ctx => (ctx.startDate ? dayjs(ctx.startDate) : null));
|
|
29
|
+
const endDate = useContextSelector(ParameterContext, ctx => (ctx.endDate ? dayjs(ctx.endDate) : null));
|
|
30
|
+
const getCurrentViews = useContextSelector(ViewContext, ctx => ctx.getCurrentViews);
|
|
24
31
|
useEffect(() => {
|
|
25
32
|
if (location.search.includes('span')) {
|
|
26
33
|
return;
|
|
27
34
|
}
|
|
28
35
|
(async () => {
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
36
|
+
const selectedViewSpan = (await getCurrentViews({ lazy: true })).find(view => view?.span)?.span;
|
|
37
|
+
if (!selectedViewSpan) {
|
|
31
38
|
return;
|
|
32
39
|
}
|
|
33
|
-
if (
|
|
34
|
-
setSpan(convertLuceneToDate(
|
|
40
|
+
if (selectedViewSpan.includes(':')) {
|
|
41
|
+
setSpan(convertLuceneToDate(selectedViewSpan));
|
|
35
42
|
}
|
|
36
43
|
else {
|
|
37
|
-
setSpan(
|
|
44
|
+
setSpan(selectedViewSpan);
|
|
38
45
|
}
|
|
39
46
|
})();
|
|
40
47
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
-
}, [
|
|
42
|
-
return (_jsx(
|
|
48
|
+
}, [getCurrentViews, views]);
|
|
49
|
+
return (_jsx(ChipPopper, { icon: _jsx(AvTimer, { fontSize: "small" }), label: _jsx(Typography, { variant: "body2", children: span !== 'date.range.custom'
|
|
50
|
+
? t(span)
|
|
51
|
+
: `${startDate.format('YYYY-MM-DD HH:mm')} ${t('to')} ${endDate.format('YYYY-MM-DD HH:mm')}` }), minWidth: "225px", slotProps: { chip: { size: 'small' } }, children: _jsxs(Stack, { spacing: 1, children: [_jsx(Autocomplete, { fullWidth: true, sx: { minWidth: '200px', flex: 1 }, size: size ?? 'small', value: span, options: omitCustom ? DATE_RANGES.slice(0, DATE_RANGES.length - 1) : DATE_RANGES, renderInput: _params => _jsx(TextField, { ..._params, label: t('hit.search.span') }), getOptionLabel: option => t(option), onChange: (_, value) => setSpan(value), disableClearable: true }), _jsx(CustomSpan, {})] }) }));
|
|
43
52
|
};
|
|
44
53
|
export default memo(SearchSpan);
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { HelpOutline, Save } from '@mui/icons-material';
|
|
5
|
-
import { Alert, Checkbox, CircularProgress,
|
|
5
|
+
import { Alert, Checkbox, CircularProgress, LinearProgress, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from '@mui/material';
|
|
6
6
|
import api from '@cccsaurora/howler-ui/api';
|
|
7
7
|
import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
|
|
8
8
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
@@ -24,6 +24,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|
|
24
24
|
import { useContextSelector } from 'use-context-selector';
|
|
25
25
|
import { DEFAULT_QUERY, StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
26
26
|
import { convertDateToLucene } from '@cccsaurora/howler-ui/utils/utils';
|
|
27
|
+
import { buildViewUrl } from '@cccsaurora/howler-ui/utils/viewUtils';
|
|
27
28
|
import ErrorBoundary from '../ErrorBoundary';
|
|
28
29
|
import HitQuery from '../hits/search/HitQuery';
|
|
29
30
|
import HitSort from '../hits/search/shared/HitSort';
|
|
@@ -36,7 +37,7 @@ const ViewComposer = () => {
|
|
|
36
37
|
const navigate = useNavigate();
|
|
37
38
|
const addView = useContextSelector(ViewContext, ctx => ctx.addView);
|
|
38
39
|
const editView = useContextSelector(ViewContext, ctx => ctx.editView);
|
|
39
|
-
const
|
|
40
|
+
const getCurrentViews = useContextSelector(ViewContext, ctx => ctx.getCurrentViews);
|
|
40
41
|
const pageCount = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25)[0];
|
|
41
42
|
const loadHits = useContextSelector(HitContext, ctx => ctx.loadHits);
|
|
42
43
|
// view state
|
|
@@ -69,7 +70,7 @@ const ViewComposer = () => {
|
|
|
69
70
|
advance_on_triage: advanceOnTriage
|
|
70
71
|
}
|
|
71
72
|
});
|
|
72
|
-
navigate(
|
|
73
|
+
navigate(buildViewUrl(newView));
|
|
73
74
|
}
|
|
74
75
|
else {
|
|
75
76
|
await editView(routeParams.id, {
|
|
@@ -142,7 +143,7 @@ const ViewComposer = () => {
|
|
|
142
143
|
return;
|
|
143
144
|
}
|
|
144
145
|
(async () => {
|
|
145
|
-
const viewToEdit = await
|
|
146
|
+
const viewToEdit = (await getCurrentViews({ viewId: routeParams.id }))[0];
|
|
146
147
|
if (!viewToEdit) {
|
|
147
148
|
setError('route.views.missing');
|
|
148
149
|
return;
|
|
@@ -161,7 +162,7 @@ const ViewComposer = () => {
|
|
|
161
162
|
}
|
|
162
163
|
})();
|
|
163
164
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
|
-
}, [routeParams.id,
|
|
165
|
+
}, [routeParams.id, getCurrentViews]);
|
|
165
166
|
return (_jsx(FlexPort, { children: _jsx(ErrorBoundary, { children: _jsx(PageCenter, { maxWidth: "1500px", textAlign: "left", height: "100%", children: _jsxs(VSBox, { top: 0, children: [_jsx(VSBoxHeader, { pb: 1, children: _jsxs(Stack, { spacing: 1, children: [error && (_jsx(Alert, { variant: "outlined", severity: "error", children: t(error) })), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(TextField, { label: t('route.views.name'), size: "small", value: title, onChange: e => setTitle(e.target.value), fullWidth: true }), _jsxs(ToggleButtonGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, size: "small", exclusive: true, value: type, onChange: (__, _type) => {
|
|
166
167
|
if (_type) {
|
|
167
168
|
setType(_type);
|
|
@@ -171,6 +172,6 @@ const ViewComposer = () => {
|
|
|
171
172
|
fontSize: '0.9em',
|
|
172
173
|
fontStyle: 'italic',
|
|
173
174
|
mb: 0.5
|
|
174
|
-
}), variant: "body2", children: t('hit.search.prompt') }), _jsx(HitQuery, { triggerSearch: search, searching: searching, onChange: (_query, isDirty) => setIsSearchDirty(isDirty) }), _jsxs(Stack, { direction: "row", spacing: 1,
|
|
175
|
+
}), variant: "body2", children: t('hit.search.prompt') }), _jsx(HitQuery, { triggerSearch: search, searching: searching, onChange: (_query, isDirty) => setIsSearchDirty(isDirty) }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(HitSort, {}), _jsx(SearchSpan, { omitCustom: true }), _jsx("div", { style: { flex: 1 } }), _jsxs(Stack, { spacing: 1, direction: "row", alignItems: "center", sx: { flex: '0 !important', minWidth: '300px' }, children: [_jsx(Typography, { component: "span", children: t('view.settings.advance_on_triage') }), _jsx(Tooltip, { title: t('view.settings.advance_on_triage.description'), children: _jsx(HelpOutline, { sx: { fontSize: '16px' } }) }), _jsx(Checkbox, { size: "small", checked: advanceOnTriage, onChange: (_event, checked) => setAdvanceOnTriage(checked) })] })] }), response?.total ? (_jsx(SearchTotal, { total: response.total, pageLength: response.items.length, offset: response.offset, sx: theme => ({ color: theme.palette.text.secondary, fontSize: '0.9em', fontStyle: 'italic' }) })) : null, _jsx(LinearProgress, { sx: [!searching && { opacity: 0 }] })] }) }), _jsx(VSBoxContent, { children: _jsxs(Stack, { spacing: 1, children: [!response?.total && _jsx(AppListEmpty, {}), response?.items.map(hit => (_jsx(HitCard, { id: hit.howler.id, layout: HitLayout.DENSE }, hit.howler.id)))] }) })] }) }) }) }));
|
|
175
176
|
};
|
|
176
177
|
export default ViewComposer;
|
|
@@ -20,6 +20,7 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
|
|
20
20
|
import { useContextSelector } from 'use-context-selector';
|
|
21
21
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
22
22
|
import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
|
|
23
|
+
import { buildViewUrl } from '@cccsaurora/howler-ui/utils/viewUtils';
|
|
23
24
|
const FIELDS_TO_SEARCH = ['title', 'query', 'sort', 'type', 'owner'];
|
|
24
25
|
const ViewsBase = () => {
|
|
25
26
|
const { t } = useTranslation();
|
|
@@ -173,7 +174,7 @@ const ViewsBase = () => {
|
|
|
173
174
|
}, [offset, favouritesOnly, type]);
|
|
174
175
|
return (_jsx(ItemManager, { onSearch: onSearch, onPageChange: onPageChange, phrase: phrase, setPhrase: setPhrase, hasError: hasError, searching: searching, searchFilters: _jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", children: _jsxs(ToggleButtonGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, size: "small", value: type, exclusive: true, onChange: (__, _type) => onTypeChange(_type), children: [_jsx(ToggleButton, { value: "personal", "aria-label": "personal", children: t('route.views.manager.personal') }), _jsx(ToggleButton, { value: "global", "aria-label": "global", children: t('route.views.manager.global') })] }) }), aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.views.search.prompt') }), afterSearch: size(views) > 0 ? (_jsx(Autocomplete, { open: defaultViewOpen, loading: defaultViewLoading, onOpen: onDefaultViewOpen, onClose: () => setDefaultViewOpen(false), options: Object.values(omitBy(views, isNull)), renderOption: ({ key, ...props }, o) => (_createElement("li", { ...props, key: key },
|
|
175
176
|
_jsxs(Stack, { children: [_jsx(Typography, { variant: "body1", children: t(o.title) }), _jsx(Typography, { variant: "caption", children: _jsx("code", { children: o.query }) })] }))), renderInput: params => (_jsx(TextField, { ...params, label: t('route.views.manager.default'), sx: { minWidth: '300px' } })), filterOptions: (_views, { inputValue }) => _views.filter(v => t(v.title).toLowerCase().includes(inputValue.toLowerCase()) ||
|
|
176
|
-
v.query.toLowerCase().includes(inputValue.toLowerCase())), getOptionLabel: (v) => t(v.title), isOptionEqualToValue: (view, value) => view.view_id === value.view_id, value: views[defaultView] ?? null, onChange: (_, option) => setDefaultView(option?.view_id) })) : (_jsx(Skeleton, { variant: "rounded", width: "300px", height: "initial" })), belowSearch: _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Checkbox, { size: "small", disabled: user.favourite_views?.length < 1, checked: favouritesOnly, onChange: (_, checked) => setFavouritesOnly(checked) }), _jsx(Typography, { variant: "body1", sx: theme => ({ color: theme.palette.text.disabled }), children: t('route.views.manager.favourites') })] }), renderer: ({ item }, classRenderer) => (_jsx(Card, { variant: "outlined", sx: { p: 1, mb: 1, transitionProperty: 'border-color', '&:hover': { borderColor: 'primary.main' } }, className: classRenderer(), children: _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, sx: { color: 'inherit', textDecoration: 'none' }, component: Link, to:
|
|
177
|
+
v.query.toLowerCase().includes(inputValue.toLowerCase())), getOptionLabel: (v) => t(v.title), isOptionEqualToValue: (view, value) => view.view_id === value.view_id, value: views[defaultView] ?? null, onChange: (_, option) => setDefaultView(option?.view_id) })) : (_jsx(Skeleton, { variant: "rounded", width: "300px", height: "initial" })), belowSearch: _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Checkbox, { size: "small", disabled: user.favourite_views?.length < 1, checked: favouritesOnly, onChange: (_, checked) => setFavouritesOnly(checked) }), _jsx(Typography, { variant: "body1", sx: theme => ({ color: theme.palette.text.disabled }), children: t('route.views.manager.favourites') })] }), renderer: ({ item }, classRenderer) => (_jsx(Card, { variant: "outlined", sx: { p: 1, mb: 1, transitionProperty: 'border-color', '&:hover': { borderColor: 'primary.main' } }, className: classRenderer(), children: _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, sx: { color: 'inherit', textDecoration: 'none' }, component: Link, to: buildViewUrl(item.item), children: [_jsx(ViewTitle, { ...item.item }), _jsx(FlexOne, {}), ((item.item.owner === user.username && item.item.type !== 'readonly') ||
|
|
177
178
|
(item.item.type === 'global' && user.is_admin)) && (_jsx(Tooltip, { title: t('button.edit'), children: _jsx(IconButton, { component: Link, to: `/views/${item.item.view_id}/edit?query=${item.item.query}`, children: _jsx(Edit, {}) }) })), item.item.owner === user.username && item.item.type !== 'readonly' && (_jsx(Tooltip, { title: t('button.delete'), children: _jsx(IconButton, { onClick: event => onDelete(event, item.item.view_id), children: _jsx(Clear, {}) }) })), item.item.type === 'global' && item.item.owner !== user.username && (_jsx(Tooltip, { title: item.item.owner, children: _jsx("div", { children: _jsx(HowlerAvatar, { sx: { width: 24, height: 24, marginRight: '8px !important', marginLeft: '8px !important' }, userId: item.item.owner }) }) })), _jsx(Tooltip, { title: t('button.pin'), children: _jsx(IconButton, { onClick: e => onFavourite(e, item.item.view_id), children: user.favourite_views?.includes(item.item.view_id) ? _jsx(Star, {}) : _jsx(StarBorder, {}) }) })] }) }, item.item.view_id)), response: response, searchPrompt: "route.views.manager.search", onCreate: () => navigate('/views/create'), createPrompt: "route.views.create", createIcon: _jsx(SavedSearch, { sx: { mr: 1 } }) }));
|
|
178
179
|
};
|
|
179
180
|
const Views = () => {
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
"adminmenu.users": "Users",
|
|
5
5
|
"add": "Add",
|
|
6
6
|
"all": "All",
|
|
7
|
+
"*": "All values",
|
|
7
8
|
"any": "Any",
|
|
8
9
|
"asc": "Ascending",
|
|
9
10
|
"close": "Close",
|
|
10
11
|
"desc": "Descending",
|
|
12
|
+
"to": "to",
|
|
11
13
|
"actions.running": "Action \"{{action}}\" is executing.",
|
|
12
14
|
"actions.succeeded": "Action \"{{action}}\" completed successfully.",
|
|
13
15
|
"api.user.apikey.updated": "New API key added successfully.",
|
|
@@ -246,6 +248,7 @@
|
|
|
246
248
|
"hit.search.filter.values": "Values",
|
|
247
249
|
"hit.search.filter.button": "Use lookup filters with predefined values",
|
|
248
250
|
"hit.search.filter.label": "Lookup Filters",
|
|
251
|
+
"hit.search.filter.add": "Add filter",
|
|
249
252
|
"hit.search.save.button": "Save query as view",
|
|
250
253
|
"hit.search.save.view": "Save View",
|
|
251
254
|
"hit.search.sort.button": "Sort Results",
|
|
@@ -254,8 +257,11 @@
|
|
|
254
257
|
"hit.search.select.all": "Select all hits on page",
|
|
255
258
|
"hit.search.select.clear": "Clear Selection",
|
|
256
259
|
"hit.search.selected": "{{size}} selected",
|
|
260
|
+
"hit.search.view.add": "Add view",
|
|
257
261
|
"hit.search.view.current": "Current View",
|
|
258
262
|
"hit.search.view.default": "(Default)",
|
|
263
|
+
"hit.search.view.select": "Select View",
|
|
264
|
+
"hit.search.view.remove": "Remove View",
|
|
259
265
|
"hit.viewer.aggregate": "Summary",
|
|
260
266
|
"hit.viewer.comments": "Comments",
|
|
261
267
|
"hit.viewer.data": "Raw Data",
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"apikey.write": "Écrire",
|
|
15
15
|
"add": "Ajouter",
|
|
16
16
|
"all": "Tous",
|
|
17
|
+
"*": "Toutes les valeurs",
|
|
17
18
|
"any": "Tous",
|
|
18
19
|
"asc": "Ascendant",
|
|
19
20
|
"close": "Fermer",
|
|
20
21
|
"desc": "Descendant",
|
|
22
|
+
"to": "à",
|
|
21
23
|
"actions.running": "Action \"{{action}}\" s'exécute.",
|
|
22
24
|
"actions.succeeded": "Action \"{{action}}\" achevé avec succès.",
|
|
23
25
|
"app.drawer.hit.assignment.autocomplete.label": "Choisissez le destinataire",
|
|
@@ -246,6 +248,7 @@
|
|
|
246
248
|
"hit.search.filter.values": "Valeurs",
|
|
247
249
|
"hit.search.filter.button": "Utiliser des filtres de recherche avec des valeurs prédéfinies",
|
|
248
250
|
"hit.search.filter.label": "Filtres de recherche",
|
|
251
|
+
"hit.search.filter.add": "Ajouter un filtre",
|
|
249
252
|
"hit.search.save.button": "Sauvegarder la requête en tant que vue",
|
|
250
253
|
"hit.search.save.view": "Sauvegarder la vue",
|
|
251
254
|
"hit.search.custom": "Triage personnalisé",
|
|
@@ -255,8 +258,11 @@
|
|
|
255
258
|
"hit.search.select.all": "Sélectionner tous les résultats de la page",
|
|
256
259
|
"hit.search.select.clear": "Effacer la sélection",
|
|
257
260
|
"hit.search.selected": "{{size}} sélectionné(s)",
|
|
261
|
+
"hit.search.view.add": "Ajouter une vue",
|
|
258
262
|
"hit.search.view.current": "Vue courante",
|
|
259
263
|
"hit.search.view.default": "(Défaut)",
|
|
264
|
+
"hit.search.view.select": "Sélectionner une vue",
|
|
265
|
+
"hit.search.view.remove": "Supprimer la vue",
|
|
260
266
|
"hit.viewer.aggregate": "Sommaire",
|
|
261
267
|
"hit.viewer.comments": "Commentaires",
|
|
262
268
|
"hit.viewer.data": "Données brutes",
|
package/package.json
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"react-markdown": "^10.1.0",
|
|
52
52
|
"react-pluggable": "^0.4.3",
|
|
53
53
|
"react-resize-detector": "^9.1.1",
|
|
54
|
-
"react-router": "^6.30.
|
|
54
|
+
"react-router": "^6.30.3",
|
|
55
55
|
"react-router-dom": "^6.30.1",
|
|
56
56
|
"react-syntax-highlighter": "^15.6.1",
|
|
57
57
|
"rehype-raw": "^7.0.0",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"internal-slot": "1.0.7"
|
|
97
97
|
},
|
|
98
98
|
"type": "module",
|
|
99
|
-
"version": "2.16.0-dev.
|
|
99
|
+
"version": "2.16.0-dev.381",
|
|
100
100
|
"exports": {
|
|
101
101
|
"./i18n": "./i18n.js",
|
|
102
102
|
"./index.css": "./index.css",
|
package/setupTests.js
CHANGED
|
@@ -9,5 +9,8 @@ expect.extend(matchers);
|
|
|
9
9
|
// tell React Testing Library to look for id as the testId.
|
|
10
10
|
configure({ testIdAttribute: 'id' });
|
|
11
11
|
beforeAll(() => server.listen());
|
|
12
|
-
afterEach(() =>
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
server.resetHandlers();
|
|
14
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
15
|
+
});
|
|
13
16
|
afterAll(() => server.close());
|
package/tests/mocks.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets up a mock for use-context-selector that uses React's native context
|
|
3
|
+
* This allows tests to use context providers without the full use-context-selector implementation
|
|
4
|
+
*/
|
|
5
|
+
export declare const setupContextSelectorMock: () => void;
|
|
6
|
+
/**
|
|
7
|
+
* Sets up a mock for react-router-dom with common defaults
|
|
8
|
+
* @param options - Override specific router behavior
|
|
9
|
+
*/
|
|
10
|
+
export declare const setupReactRouterMock: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* Sets up a mock localStorage instance
|
|
13
|
+
*/
|
|
14
|
+
export declare const setupLocalStorageMock: () => Storage;
|
|
15
|
+
/**
|
|
16
|
+
* Sets up a mock sessionStorage instance
|
|
17
|
+
*/
|
|
18
|
+
export declare const setupSessionStorageMock: () => Storage;
|
package/tests/mocks.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, forwardRef, useContext } from 'react';
|
|
3
|
+
import { vi } from 'vitest';
|
|
4
|
+
import MockLocalStorage from './MockLocalStorage';
|
|
5
|
+
/**
|
|
6
|
+
* Sets up a mock for use-context-selector that uses React's native context
|
|
7
|
+
* This allows tests to use context providers without the full use-context-selector implementation
|
|
8
|
+
*/
|
|
9
|
+
export const setupContextSelectorMock = () => {
|
|
10
|
+
beforeAll(() => {
|
|
11
|
+
vi.mock('use-context-selector', async () => {
|
|
12
|
+
const actual = await vi.importActual('use-context-selector');
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
createContext,
|
|
16
|
+
useContextSelector: (_context, selector) => {
|
|
17
|
+
return selector(useContext(_context));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
afterAll(() => vi.resetModules());
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Sets up a mock for react-router-dom with common defaults
|
|
26
|
+
* @param options - Override specific router behavior
|
|
27
|
+
*/
|
|
28
|
+
export const setupReactRouterMock = () => {
|
|
29
|
+
const mockLocation = vi.hoisted(() => ({ pathname: '/hits', search: '' }));
|
|
30
|
+
const mockParams = vi.hoisted(() => ({ id: undefined }));
|
|
31
|
+
const mockSearchParams = vi.hoisted(() => new URLSearchParams());
|
|
32
|
+
const mockSetParams = vi.hoisted(() => vi.fn());
|
|
33
|
+
beforeAll(() => {
|
|
34
|
+
vi.mock('react-router-dom', () => ({
|
|
35
|
+
Link: forwardRef(({ to, children, ...props }, ref) => (_jsx("a", { ref: ref, href: to, ...props, children: children }))),
|
|
36
|
+
useLocation: vi.fn(() => mockLocation),
|
|
37
|
+
useParams: vi.fn(() => mockParams),
|
|
38
|
+
useSearchParams: vi.fn(() => [mockSearchParams, mockSetParams]),
|
|
39
|
+
useNavigate: () => vi.fn()
|
|
40
|
+
}));
|
|
41
|
+
});
|
|
42
|
+
afterAll(() => vi.resetModules());
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Sets up a mock localStorage instance
|
|
46
|
+
*/
|
|
47
|
+
export const setupLocalStorageMock = () => {
|
|
48
|
+
const mockLocalStorage = new MockLocalStorage();
|
|
49
|
+
Object.defineProperty(window, 'localStorage', {
|
|
50
|
+
value: mockLocalStorage,
|
|
51
|
+
writable: true
|
|
52
|
+
});
|
|
53
|
+
return mockLocalStorage;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Sets up a mock sessionStorage instance
|
|
57
|
+
*/
|
|
58
|
+
export const setupSessionStorageMock = () => {
|
|
59
|
+
const mockSessionStorage = new MockLocalStorage();
|
|
60
|
+
Object.defineProperty(window, 'sessionStorage', {
|
|
61
|
+
value: mockSessionStorage,
|
|
62
|
+
writable: true
|
|
63
|
+
});
|
|
64
|
+
return mockSessionStorage;
|
|
65
|
+
};
|
package/tests/server-handlers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { http, HttpResponse } from 'msw';
|
|
2
|
-
import { createMockAction } from './utils';
|
|
2
|
+
import { createMockAction, createMockAnalytic, createMockHit, createMockView } from './utils';
|
|
3
3
|
export const MOCK_RESPONSES = {
|
|
4
4
|
'/api/v1/view/example_view_id': {
|
|
5
5
|
owner: 'user',
|
|
@@ -13,42 +13,24 @@ export const MOCK_RESPONSES = {
|
|
|
13
13
|
type: 'personal',
|
|
14
14
|
span: 'date.range.1.month'
|
|
15
15
|
},
|
|
16
|
-
'/api/v1/search/
|
|
17
|
-
items: [
|
|
18
|
-
{
|
|
19
|
-
owner: 'user',
|
|
20
|
-
settings: {
|
|
21
|
-
advance_on_triage: false
|
|
22
|
-
},
|
|
23
|
-
view_id: 'searched_view_id',
|
|
24
|
-
query: 'howler.id:searched',
|
|
25
|
-
sort: 'event.created desc',
|
|
26
|
-
title: 'Searched View',
|
|
27
|
-
type: 'personal',
|
|
28
|
-
span: 'date.range.1.month'
|
|
29
|
-
}
|
|
30
|
-
],
|
|
16
|
+
'/api/v1/search/hit': {
|
|
17
|
+
items: [createMockHit({ howler: { id: 'howler.id' } })],
|
|
31
18
|
total: 1,
|
|
32
19
|
rows: 1
|
|
33
20
|
},
|
|
34
|
-
'/api/v1/view
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
view_id: 'new_view_id',
|
|
40
|
-
query: 'howler.id:new',
|
|
41
|
-
sort: 'event.created desc',
|
|
42
|
-
title: 'New View',
|
|
43
|
-
type: 'personal',
|
|
44
|
-
span: 'date.range.1.month'
|
|
21
|
+
'/api/v1/search/view': {
|
|
22
|
+
items: [createMockView({ view_id: 'searched_view_id', title: 'Searched View' })],
|
|
23
|
+
total: 1,
|
|
24
|
+
rows: 1
|
|
45
25
|
},
|
|
26
|
+
'/api/v1/view/new_view_id': createMockView({ view_id: 'new_view_id' }),
|
|
46
27
|
'/api/v1/view/:view_id/favourite': { success: true },
|
|
47
28
|
'/api/v1/search/action': {
|
|
48
29
|
items: [createMockAction()],
|
|
49
30
|
total: 1,
|
|
50
31
|
rows: 1
|
|
51
|
-
}
|
|
32
|
+
},
|
|
33
|
+
'/api/v1/analytic': [createMockAnalytic()]
|
|
52
34
|
};
|
|
53
35
|
const handlers = [
|
|
54
36
|
...Object.entries(MOCK_RESPONSES).map(([path, data]) => http.all(path, async () => HttpResponse.json({ api_response: data }))),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const buildViewUrl = (view) => {
|
|
2
|
+
const params = new URLSearchParams();
|
|
3
|
+
params.set('view', view.view_id);
|
|
4
|
+
if (view.span) {
|
|
5
|
+
params.set('span', view.span);
|
|
6
|
+
}
|
|
7
|
+
if (view.sort) {
|
|
8
|
+
params.set('sort', view.sort);
|
|
9
|
+
}
|
|
10
|
+
return `/search?${params.toString()}`;
|
|
11
|
+
};
|