@cccsaurora/howler-ui 3.1.0-dev.1454 → 3.1.0-dev.1463
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/elements/display/Modal.js +1 -1
- package/components/elements/display/modals/ConfirmDeleteModal.d.ts +2 -2
- package/components/elements/display/modals/ConfirmDeleteModal.js +2 -2
- package/components/routes/overviews/OverviewCard.d.ts +3 -2
- package/components/routes/overviews/OverviewCard.js +15 -4
- package/components/routes/overviews/OverviewCard.test.js +8 -4
- package/components/routes/overviews/Overviews.js +23 -22
- package/components/routes/templates/TemplateCard.js +1 -1
- package/locales/en/translation.json +4 -2
- package/locales/fr/translation.json +5 -3
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ const Modal = () => {
|
|
|
16
16
|
left: '50%',
|
|
17
17
|
maxWidth: options.maxWidth || '1200px',
|
|
18
18
|
maxHeight: options.maxHeight || '400px',
|
|
19
|
-
height: has(options, 'height') ? options.height : '
|
|
19
|
+
height: has(options, 'height') ? options.height : 'auto',
|
|
20
20
|
transform: 'translate(-50%, -50%)',
|
|
21
21
|
backgroundColor: 'background.paper',
|
|
22
22
|
borderRadius: theme.shape.borderRadius,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { FC } from 'react';
|
|
1
|
+
import type { FC, MouseEventHandler } from 'react';
|
|
2
2
|
declare const ConfirmDeleteModal: FC<{
|
|
3
|
-
onConfirm: () => void;
|
|
3
|
+
onConfirm: MouseEventHandler<HTMLButtonElement> | (() => void);
|
|
4
4
|
title?: string;
|
|
5
5
|
description?: string;
|
|
6
6
|
preferDelete?: boolean;
|
|
@@ -6,8 +6,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
const ConfirmDeleteModal = ({ onConfirm, title, description, preferDelete, preferCancel }) => {
|
|
7
7
|
const { t } = useTranslation();
|
|
8
8
|
const { close } = useContext(ModalContext);
|
|
9
|
-
const handleConfirm = useCallback(() => {
|
|
10
|
-
onConfirm();
|
|
9
|
+
const handleConfirm = useCallback((e) => {
|
|
10
|
+
onConfirm(e);
|
|
11
11
|
close();
|
|
12
12
|
}, [close, onConfirm]);
|
|
13
13
|
const modalTitle = title ?? t('modal.confirm.delete.title');
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Overview } from '@cccsaurora/howler-ui/models/entities/generated/Overview';
|
|
2
|
-
import type
|
|
2
|
+
import { type FC } from 'react';
|
|
3
3
|
declare const OverviewCard: FC<{
|
|
4
4
|
overview: Overview;
|
|
5
5
|
className?: string;
|
|
6
|
-
|
|
6
|
+
error?: boolean;
|
|
7
|
+
onRemove?: (id: string) => Promise<void>;
|
|
7
8
|
}>;
|
|
8
9
|
export default OverviewCard;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Delete } from '@mui/icons-material';
|
|
3
|
-
import { Card, IconButton, Stack, Tooltip, Typography } from '@mui/material';
|
|
2
|
+
import { Delete, ReportProblem } from '@mui/icons-material';
|
|
3
|
+
import { Button, Card, IconButton, Stack, Tooltip, Typography } from '@mui/material';
|
|
4
|
+
import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
|
|
4
5
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
5
6
|
import HowlerAvatar from '@cccsaurora/howler-ui/components/elements/display/HowlerAvatar';
|
|
7
|
+
import ConfirmDeleteModal from '@cccsaurora/howler-ui/components/elements/display/modals/ConfirmDeleteModal';
|
|
8
|
+
import { useCallback, useContext } from 'react';
|
|
6
9
|
import { useTranslation } from 'react-i18next';
|
|
7
|
-
const OverviewCard = ({ overview, className,
|
|
10
|
+
const OverviewCard = ({ overview, error, className, onRemove }) => {
|
|
8
11
|
const { t } = useTranslation();
|
|
12
|
+
const { showModal, withConfirmDeleteModal } = useContext(ModalContext);
|
|
13
|
+
const onDelete = useCallback((e, id) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
e.stopPropagation();
|
|
16
|
+
withConfirmDeleteModal(async () => {
|
|
17
|
+
await onRemove?.(id);
|
|
18
|
+
});
|
|
19
|
+
}, [onRemove, withConfirmDeleteModal]);
|
|
9
20
|
return (_jsx(Card, { variant: "outlined", sx: { p: 1, mb: 1 }, className: className, children: _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, children: [_jsxs(Stack, { children: [_jsxs(Typography, { variant: "body1", children: [t(overview.analytic), " - ", t(overview.detection ?? 'all')] }), _jsx(Typography, { variant: "caption", color: "text.secondary", children: _jsx("code", { children: _jsx("pre", { children: overview.content
|
|
10
21
|
.split('\n')
|
|
11
22
|
.filter(line => !!line)
|
|
12
23
|
.slice(0, 3)
|
|
13
24
|
.map(content => content.replace(/(.{,64}).+/, '$1'))
|
|
14
|
-
.join('\n') }) }) })] }), _jsx(FlexOne, {}), _jsx(HowlerAvatar, { sx: { height: '24px', width: '24px' }, userId: overview.owner }),
|
|
25
|
+
.join('\n') }) }) })] }), _jsx(FlexOne, {}), _jsx(HowlerAvatar, { sx: { height: '24px', width: '24px' }, userId: overview.owner }), onRemove && (_jsx(Tooltip, { title: t('route.overviews.manager.delete'), children: _jsx(IconButton, { onClick: e => onDelete?.(e, overview.overview_id), children: _jsx(Delete, {}) }) })), error && (_jsx(Stack, { direction: "row", justifyContent: "end", children: _jsx(Stack, { children: _jsx(Tooltip, { title: t('error.invalid_detection.action'), children: _jsx(Button, { startIcon: _jsx(ReportProblem, {}), color: "warning", onClick: () => showModal(_jsx(ConfirmDeleteModal, { onConfirm: () => onRemove?.(overview.overview_id), title: t('route.overviews.manager.error.modal.title'), description: t('route.overviews.manager.error.modal.description'), preferDelete: true })), children: t('error.invalid_detection.message') }) }) }) }))] }) }, overview.overview_id));
|
|
15
26
|
};
|
|
16
27
|
export default OverviewCard;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/// <reference types="vitest" />
|
|
3
3
|
import { render, screen } from '@testing-library/react';
|
|
4
4
|
import userEvent, {} from '@testing-library/user-event';
|
|
5
|
+
import ModalProvider from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
|
|
6
|
+
import Modal from '@cccsaurora/howler-ui/components/elements/display/Modal';
|
|
5
7
|
import i18n from '@cccsaurora/howler-ui/i18n';
|
|
6
8
|
import { I18nextProvider } from 'react-i18next';
|
|
7
9
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
@@ -12,7 +14,7 @@ vi.mock('components/elements/display/HowlerAvatar', () => ({
|
|
|
12
14
|
vi.mock('components/elements/addons/layout/FlexOne', () => ({
|
|
13
15
|
default: () => _jsx("div", { id: "flex-one" })
|
|
14
16
|
}));
|
|
15
|
-
const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: children }));
|
|
17
|
+
const Wrapper = ({ children }) => (_jsx(I18nextProvider, { i18n: i18n, children: _jsxs(ModalProvider, { children: [children, _jsx(Modal, {})] }) }));
|
|
16
18
|
describe('OverviewCard', () => {
|
|
17
19
|
let user;
|
|
18
20
|
beforeEach(() => {
|
|
@@ -87,10 +89,12 @@ describe('OverviewCard', () => {
|
|
|
87
89
|
content: 'content',
|
|
88
90
|
owner: 'testuser'
|
|
89
91
|
};
|
|
90
|
-
render(_jsx(OverviewCard, { overview: overview,
|
|
92
|
+
render(_jsx(OverviewCard, { overview: overview, onRemove: mockOnDelete }), { wrapper: Wrapper });
|
|
91
93
|
const deleteButton = document.querySelector('[data-testid="DeleteIcon"]').closest('button');
|
|
92
94
|
await user.click(deleteButton);
|
|
93
|
-
|
|
95
|
+
const confirmButton = screen.getByRole('button', { name: /delete/i });
|
|
96
|
+
await user.click(confirmButton);
|
|
97
|
+
expect(mockOnDelete).toHaveBeenCalledWith('ov-1');
|
|
94
98
|
});
|
|
95
99
|
it('should apply custom className', () => {
|
|
96
100
|
const overview = {
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Article } from '@mui/icons-material';
|
|
3
3
|
import { Typography } from '@mui/material';
|
|
4
4
|
import api from '@cccsaurora/howler-ui/api';
|
|
5
|
-
import {
|
|
5
|
+
import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
|
|
6
6
|
import SearchResponseProvider, { SearchResponseContext } from '@cccsaurora/howler-ui/components/app/providers/SearchResponseProvider';
|
|
7
7
|
import { TuiListProvider } from '@cccsaurora/howler-ui/components/elements/addons/lists';
|
|
8
8
|
import { TuiListMethodContext } from '@cccsaurora/howler-ui/components/elements/addons/lists/TuiListProvider';
|
|
@@ -20,7 +20,6 @@ const OverviewsBase = () => {
|
|
|
20
20
|
const navigate = useNavigate();
|
|
21
21
|
const { dispatchApi } = useMyApi();
|
|
22
22
|
const { showSuccessMessage } = useMySnackbar();
|
|
23
|
-
const { withConfirmDeleteModal } = useContext(ModalContext);
|
|
24
23
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
25
24
|
const { load } = useContext(TuiListMethodContext);
|
|
26
25
|
const pageCount = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25)[0];
|
|
@@ -28,6 +27,7 @@ const OverviewsBase = () => {
|
|
|
28
27
|
const [offset, setOffset] = useState(parseInt(searchParams.get('offset')) || 0);
|
|
29
28
|
const [hasError, setHasError] = useState(false);
|
|
30
29
|
const [loading, setLoading] = useState(false);
|
|
30
|
+
const { analytics } = useContext(AnalyticContext);
|
|
31
31
|
const { response, request, remove, getSearchRequestData } = useContext(SearchResponseContext);
|
|
32
32
|
const onSearch = useCallback(async () => {
|
|
33
33
|
try {
|
|
@@ -58,17 +58,22 @@ const OverviewsBase = () => {
|
|
|
58
58
|
}, [phrase, setSearchParams, searchParams, request, pageCount, offset]);
|
|
59
59
|
// Load the items into list when response changes.
|
|
60
60
|
// This hook should only trigger when the 'response' changes.
|
|
61
|
+
// or if the analytic list changes to refresh the disabled state
|
|
61
62
|
useEffect(() => {
|
|
62
63
|
if (response) {
|
|
63
64
|
load(response.items.map((item) => ({
|
|
64
65
|
id: item.overview_id,
|
|
65
66
|
item,
|
|
66
67
|
selected: false,
|
|
67
|
-
cursor: false
|
|
68
|
+
cursor: false,
|
|
69
|
+
disabled: item.detection &&
|
|
70
|
+
!analytics
|
|
71
|
+
.find(v => v.name === item.analytic)
|
|
72
|
+
?.detections?.map((s) => s.toLowerCase())
|
|
73
|
+
?.includes(item.detection?.toLowerCase())
|
|
68
74
|
})));
|
|
69
75
|
}
|
|
70
|
-
|
|
71
|
-
}, [response, load]);
|
|
76
|
+
}, [response, load, analytics]);
|
|
72
77
|
const onPageChange = useCallback((_offset) => {
|
|
73
78
|
if (_offset !== offset) {
|
|
74
79
|
const modifiedRequest = getSearchRequestData({ offset: _offset });
|
|
@@ -77,21 +82,17 @@ const OverviewsBase = () => {
|
|
|
77
82
|
setOffset(modifiedRequest.offset);
|
|
78
83
|
}
|
|
79
84
|
}, [offset, searchParams, setSearchParams, getSearchRequestData]);
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.warn(_err);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}, [dispatchApi, remove, withConfirmDeleteModal, showSuccessMessage, t]);
|
|
85
|
+
const removeOverview = useCallback(async (overviewId) => {
|
|
86
|
+
try {
|
|
87
|
+
await dispatchApi(api.overview.del(overviewId), { throwError: true, showError: true });
|
|
88
|
+
remove(overviewId);
|
|
89
|
+
showSuccessMessage(t('route.overviews.manager.delete.success'));
|
|
90
|
+
}
|
|
91
|
+
catch (_err) {
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.warn(_err);
|
|
94
|
+
}
|
|
95
|
+
}, [dispatchApi, remove, showSuccessMessage, t]);
|
|
95
96
|
useEffect(() => {
|
|
96
97
|
void onSearch();
|
|
97
98
|
if (!searchParams.has('offset')) {
|
|
@@ -113,8 +114,8 @@ const OverviewsBase = () => {
|
|
|
113
114
|
}
|
|
114
115
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
116
|
}, [offset]);
|
|
116
|
-
const renderer = useCallback((item, className) => _jsx(OverviewCard, { overview: item, className: className,
|
|
117
|
-
return (_jsx(ItemManager, { onSearch: onSearch, onPageChange: onPageChange, phrase: phrase, setPhrase: setPhrase, hasError: hasError, searching: loading, aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.overviews.search.prompt') }), renderer: ({ item }, classRenderer) => renderer(item.item, classRenderer()), response: response, onSelect: (item) => navigate(`/overviews/view?analytic=${item.item.analytic}${item.item.detection ? '&detection=' + item.item.detection : ''}`), onCreate: () => navigate('/overviews/view'), createPrompt: "route.overviews.create", searchPrompt: "route.overviews.manager.search", createIcon: _jsx(Article, { sx: { mr: 1 } }) }));
|
|
117
|
+
const renderer = useCallback((item, error, className) => (_jsx(OverviewCard, { overview: item, error: error, className: className, onRemove: removeOverview })), [removeOverview]);
|
|
118
|
+
return (_jsx(ItemManager, { onSearch: onSearch, onPageChange: onPageChange, phrase: phrase, setPhrase: setPhrase, hasError: hasError, searching: loading, aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.overviews.search.prompt') }), renderer: ({ item }, classRenderer) => renderer(item.item, !!item.disabled, classRenderer()), response: response, onSelect: (item) => navigate(`/overviews/view?analytic=${item.item.analytic}${item.item.detection ? '&detection=' + item.item.detection : ''}`), onCreate: () => navigate('/overviews/view'), createPrompt: "route.overviews.create", searchPrompt: "route.overviews.manager.search", createIcon: _jsx(Article, { sx: { mr: 1 } }) }));
|
|
118
119
|
};
|
|
119
120
|
const Overviews = () => {
|
|
120
121
|
return (_jsx(TuiListProvider, { children: _jsx(SearchResponseProvider, { idField: "overview_id", children: _jsx(OverviewsBase, {}) }) }));
|
|
@@ -12,6 +12,6 @@ const TemplateCard = ({ template, onRemove, error, className }) => {
|
|
|
12
12
|
readonly: _jsx(Lock, {}),
|
|
13
13
|
global: _jsx(Language, {}),
|
|
14
14
|
personal: _jsx(Person, {})
|
|
15
|
-
}[template.type] }), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsxs(Stack, { children: [_jsxs(Typography, { variant: "body1", children: [t(template.analytic), " - ", t(template.detection ?? 'all')] }), template.keys.map(key => (_jsx(Typography, { variant: "caption", sx: { ml: 1 }, children: _jsx("code", { children: key }) }, template.template_id + key)))] }), error && (_jsx(Stack, { direction: "row", justifyContent: "end", width: "100%", children: _jsx(Stack, { children: _jsx(Tooltip, { title: t('
|
|
15
|
+
}[template.type] }), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsxs(Stack, { children: [_jsxs(Typography, { variant: "body1", children: [t(template.analytic), " - ", t(template.detection ?? 'all')] }), template.keys.map(key => (_jsx(Typography, { variant: "caption", sx: { ml: 1 }, children: _jsx("code", { children: key }) }, template.template_id + key)))] }), error && (_jsx(Stack, { direction: "row", justifyContent: "end", width: "100%", children: _jsx(Stack, { children: _jsx(Tooltip, { title: t('error.invalid_detection.action'), children: _jsx(Button, { startIcon: _jsx(ReportProblem, {}), color: "warning", onClick: () => showModal(_jsx(ConfirmDeleteModal, { onConfirm: () => onRemove?.(template.template_id), title: t('route.templates.manager.error.modal.title'), description: t('route.templates.manager.error.modal.description'), preferDelete: true })), children: t('error.invalid_detection.message') }) }) }) }))] }) }, template.template_id));
|
|
16
16
|
};
|
|
17
17
|
export default TemplateCard;
|
|
@@ -112,6 +112,8 @@
|
|
|
112
112
|
"duplicates.omitted": "Some duplicate entries have been omitted.",
|
|
113
113
|
"edit": "Edit",
|
|
114
114
|
"enabled": "Enabled",
|
|
115
|
+
"error.invalid_detection.action": "Click to open quick fix options",
|
|
116
|
+
"error.invalid_detection.message": "Invalid Detection",
|
|
115
117
|
"event.module": "Event Module",
|
|
116
118
|
"event.open": "Open Event",
|
|
117
119
|
"event.type": "Event Type",
|
|
@@ -806,6 +808,8 @@
|
|
|
806
808
|
"route.overviews.detection": "Choose Detection",
|
|
807
809
|
"route.overviews.manager.delete": "Delete Overview",
|
|
808
810
|
"route.overviews.manager.delete.success": "Overview removed.",
|
|
811
|
+
"route.overviews.manager.error.modal.description": "The overview fields are read only and will not be used. Do you want to remove the template?",
|
|
812
|
+
"route.overviews.manager.error.modal.title": "Overview detection no longer exists",
|
|
809
813
|
"route.overviews.manager.search": "Search Overviews",
|
|
810
814
|
"route.overviews.prompt": "Activate autocomplete using [ctrl + space].",
|
|
811
815
|
"route.overviews.search.prompt": "Search by title, content, analytic or detection.",
|
|
@@ -824,8 +828,6 @@
|
|
|
824
828
|
"route.templates.detection": "Choose Detection",
|
|
825
829
|
"route.templates.global": "Global",
|
|
826
830
|
"route.templates.manager.delete.success": "Template removed.",
|
|
827
|
-
"route.templates.manager.error.action": "Click to open quick fix options",
|
|
828
|
-
"route.templates.manager.error.message": "Invalid Detection",
|
|
829
831
|
"route.templates.manager.error.modal.description": "The template fields are read only and will not be used. Do you want to remove the template?",
|
|
830
832
|
"route.templates.manager.error.modal.title": "Template detection no longer exists",
|
|
831
833
|
"route.templates.manager.global": "Global",
|
|
@@ -112,6 +112,8 @@
|
|
|
112
112
|
"duplicates.omitted": "Certains doublons ont été omis.",
|
|
113
113
|
"edit": "Modifier",
|
|
114
114
|
"enabled": "Activé",
|
|
115
|
+
"error.invalid_detection.action": "Cliquez ici pour afficher les options de correction rapide",
|
|
116
|
+
"error.invalid_detection.message": "Détection non valide",
|
|
115
117
|
"event.module": "Module d'événement",
|
|
116
118
|
"event.open": "Ouvrir événement",
|
|
117
119
|
"event.type": "Type d'événement",
|
|
@@ -806,6 +808,8 @@
|
|
|
806
808
|
"route.overviews.detection": "Choisir une détection",
|
|
807
809
|
"route.overviews.manager.delete": "Supprimer la vue d'ensemble",
|
|
808
810
|
"route.overviews.manager.delete.success": "Vue d'ensemble supprimée.",
|
|
811
|
+
"route.overviews.manager.error.modal.description": "Les clés de la vue d'ensemble sont en lecture seule et ne seront pas utilisées. Voulez-vous supprimer cette vue d'ensemble ?",
|
|
812
|
+
"route.overviews.manager.error.modal.title": "La détection de la vue d'ensemble n'existe plus",
|
|
809
813
|
"route.overviews.manager.search": "Rechercher les vues d'ensemble",
|
|
810
814
|
"route.overviews.prompt": "Activer l'autocomplétion en utilisant [ctrl + espace].",
|
|
811
815
|
"route.overviews.search.prompt": "Recherche par titre, contenu, clé d'analyse ou de détection.",
|
|
@@ -824,9 +828,7 @@
|
|
|
824
828
|
"route.templates.detection": "Choisir une détection",
|
|
825
829
|
"route.templates.global": "Global",
|
|
826
830
|
"route.templates.manager.delete.success": "Modèle supprimé.",
|
|
827
|
-
"route.templates.manager.error.
|
|
828
|
-
"route.templates.manager.error.message": "Détection non valide",
|
|
829
|
-
"route.templates.manager.error.modal.description": "Les clés du modèle sont en lecture seule et ne seront pas utilisées. Voulez-vous supprimer le modèle ?",
|
|
831
|
+
"route.templates.manager.error.modal.description": "Les clés du modèle sont en lecture seule et ne seront pas utilisées. Voulez-vous supprimer ce modèle ?",
|
|
830
832
|
"route.templates.manager.error.modal.title": "La détection du modèle n'existe plus",
|
|
831
833
|
"route.templates.manager.global": "Global",
|
|
832
834
|
"route.templates.manager.open": "Ouvrir la vue",
|