@cccsaurora/howler-ui 2.18.0-dev.686 → 2.18.0-dev.695

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.
Files changed (39) hide show
  1. package/api/v2/case/index.d.ts +2 -0
  2. package/api/v2/case/index.js +2 -0
  3. package/api/v2/case/items.d.ts +5 -0
  4. package/api/v2/case/items.js +15 -0
  5. package/components/elements/case/CaseCard.d.ts +4 -0
  6. package/components/elements/case/CaseCard.js +5 -2
  7. package/components/elements/record/RecordContextMenu.js +14 -2
  8. package/components/elements/record/RecordContextMenu.test.js +56 -1
  9. package/components/routes/cases/CaseViewer.js +2 -2
  10. package/components/routes/cases/detail/AlertPanel.js +2 -2
  11. package/components/routes/cases/detail/CaseAssets.js +5 -2
  12. package/components/routes/cases/detail/CaseAssets.test.js +22 -18
  13. package/components/routes/cases/detail/CaseDashboard.js +5 -2
  14. package/components/routes/cases/detail/CaseDetails.js +1 -1
  15. package/components/routes/cases/detail/CaseSidebar.d.ts +4 -2
  16. package/components/routes/cases/detail/CaseSidebar.js +2 -2
  17. package/components/routes/cases/detail/ItemPage.js +5 -5
  18. package/components/routes/cases/detail/RelatedCasePanel.js +2 -2
  19. package/components/routes/cases/detail/aggregates/SourceAggregate.js +4 -1
  20. package/components/routes/cases/detail/assets/Asset.js +1 -1
  21. package/components/routes/cases/detail/assets/Asset.test.js +5 -5
  22. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +1 -0
  23. package/components/routes/cases/detail/sidebar/CaseFolder.js +45 -43
  24. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.d.ts +34 -0
  25. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.js +95 -0
  26. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.d.ts +1 -0
  27. package/components/routes/cases/detail/sidebar/CaseFolderContextMenu.test.js +296 -0
  28. package/components/routes/cases/hooks/useCase.d.ts +1 -1
  29. package/components/routes/cases/hooks/useCase.js +16 -3
  30. package/components/routes/cases/modals/AddToCaseModal.d.ts +7 -0
  31. package/components/routes/cases/modals/AddToCaseModal.js +62 -0
  32. package/components/routes/cases/modals/ResolveModal.js +5 -2
  33. package/locales/en/translation.json +8 -0
  34. package/locales/fr/translation.json +8 -0
  35. package/models/entities/generated/Item.d.ts +1 -1
  36. package/package.json +1 -1
  37. package/tests/server-handlers.js +6 -1
  38. package/tests/utils.d.ts +2 -0
  39. package/tests/utils.js +12 -0
@@ -0,0 +1,62 @@
1
+ import { createElement as _createElement } from "react";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Autocomplete, Button, Stack, TextField, Typography } from '@mui/material';
4
+ import api from '@cccsaurora/howler-ui/api';
5
+ import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
6
+ import CaseCard from '@cccsaurora/howler-ui/components/elements/case/CaseCard';
7
+ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
8
+ import { useContext, useEffect, useMemo, useState } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ const AddToCaseModal = ({ records }) => {
11
+ const { t } = useTranslation();
12
+ const { dispatchApi } = useMyApi();
13
+ const { close } = useContext(ModalContext);
14
+ const [cases, setCases] = useState([]);
15
+ const [selectedCase, setSelectedCase] = useState(null);
16
+ const [path, setPath] = useState('');
17
+ const [title, setTitle] = useState('');
18
+ useEffect(() => {
19
+ dispatchApi(api.search.case.post({ query: 'case_id:*', rows: 100 }), { throwError: false }).then(result => {
20
+ if (result) {
21
+ setCases(result.items);
22
+ }
23
+ });
24
+ }, [dispatchApi]);
25
+ const folderOptions = useMemo(() => {
26
+ if (!selectedCase?.items) {
27
+ return [];
28
+ }
29
+ const paths = new Set();
30
+ for (const item of selectedCase.items) {
31
+ if (!item.path) {
32
+ continue;
33
+ }
34
+ const parts = item.path.split('/');
35
+ parts.pop();
36
+ for (let i = 1; i <= parts.length; i++) {
37
+ paths.add(parts.slice(0, i).join('/'));
38
+ }
39
+ }
40
+ return Array.from(paths).sort();
41
+ }, [selectedCase]);
42
+ const fullPath = path ? `${path}/${title}` : title;
43
+ const isValid = !!selectedCase && !!title;
44
+ const onSubmit = async () => {
45
+ if (!selectedCase || records?.length < 1) {
46
+ return;
47
+ }
48
+ await dispatchApi(api.v2.case.items.post(selectedCase.case_id, {
49
+ path: fullPath,
50
+ value: records[0].howler.id,
51
+ type: records[0].__index
52
+ }));
53
+ close();
54
+ };
55
+ // TODO: No support currently for multiple records
56
+ return (_jsxs(Stack, { spacing: 2, p: 2, sx: { minWidth: 'min(800px, 60vw)', height: '100%' }, children: [_jsx(Typography, { variant: "h4", children: t('modal.cases.add_to_case') }), _jsx(Autocomplete, { options: cases, getOptionLabel: option => option.title ?? option.case_id ?? '', isOptionEqualToValue: (option, value) => option.case_id === value.case_id, value: selectedCase, disablePortal: true, onChange: (_ev, newVal) => {
57
+ setSelectedCase(newVal);
58
+ setPath('');
59
+ }, renderOption: (props, option) => (_createElement("li", { ...props, key: option.case_id, style: { ...props.style, display: 'flex', justifyContent: 'stretch', alignItems: 'stretch' } },
60
+ _jsx(CaseCard, { case: option, slotProps: { card: { sx: { width: '100%' } } } }))), renderInput: params => (_jsx(TextField, { ...params, size: "small", placeholder: t('modal.cases.add_to_case.select_case'), fullWidth: true })) }), selectedCase && (_jsxs(_Fragment, { children: [_jsx(Autocomplete, { freeSolo: true, disablePortal: true, options: folderOptions, value: path, onInputChange: (_ev, newVal) => setPath(newVal), renderInput: params => (_jsx(TextField, { ...params, size: "small", placeholder: t('modal.cases.add_to_case.select_path'), fullWidth: true })) }), _jsx(TextField, { size: "small", placeholder: t('modal.cases.add_to_case.title'), value: title, onChange: ev => setTitle(ev.target.value), fullWidth: true }), title && (_jsx(Typography, { variant: "caption", color: "textSecondary", children: t('modal.cases.add_to_case.full_path', { path: fullPath }) }))] })), _jsx("div", { style: { flex: 1 } }), _jsxs(Stack, { direction: "row", spacing: 1, alignSelf: "end", children: [_jsx(Button, { variant: "outlined", color: "error", onClick: close, children: t('cancel') }), _jsx(Button, { variant: "outlined", color: "success", disabled: !isValid, onClick: onSubmit, children: t('confirm') })] })] }));
61
+ };
62
+ export default AddToCaseModal;
@@ -19,12 +19,15 @@ const ResolveModal = ({ case: _case, onConfirm }) => {
19
19
  const { dispatchApi } = useMyApi();
20
20
  const { close } = useContext(ModalContext);
21
21
  const { config } = useContext(ApiConfigContext);
22
- const { updateCase } = useCase({ case: _case });
22
+ const { update: updateCase } = useCase({ case: _case });
23
23
  const [loading, setLoading] = useState(true);
24
24
  const [rationale, setRationale] = useState('');
25
25
  const [assessment, setAssessment] = useState(null);
26
26
  const [hits, setHits] = useState([]);
27
- const hitIds = useMemo(() => uniq((_case?.items ?? []).filter(item => item.type === 'hit').map(item => item.id)), [_case?.items]);
27
+ const hitIds = useMemo(() => uniq((_case?.items ?? [])
28
+ .filter(item => item.type === 'hit')
29
+ .map(item => item.value)
30
+ .filter(Boolean)), [_case?.items]);
28
31
  const { assess } = useHitActions(hits);
29
32
  useEffect(() => {
30
33
  (async () => {
@@ -310,6 +310,11 @@
310
310
  "modal.action.empty": "Action Name cannot be empty.",
311
311
  "modal.action.label": "Action Name",
312
312
  "modal.action.title": "Save Action",
313
+ "modal.cases.add_to_case": "Add to Case",
314
+ "modal.cases.add_to_case.full_path": "Full path: {{path}}",
315
+ "modal.cases.add_to_case.select_case": "Search Cases",
316
+ "modal.cases.add_to_case.select_path": "Select Folder Path",
317
+ "modal.cases.add_to_case.title": "Item Title",
313
318
  "modal.cases.resolve": "Resolve Case",
314
319
  "modal.cases.resolve.description": "When resolving a case, you must either assess all open alerts, or add an assessment to the alerts.",
315
320
  "modal.confirm.delete.description": "Are you sure you want to delete this item?",
@@ -379,6 +384,9 @@
379
384
  "page.cases.detail.properties": "Properties",
380
385
  "page.cases.detail.status": "Status",
381
386
  "page.cases.escalation": "Escalation",
387
+ "page.cases.sidebar.folder.remove": "Remove folder",
388
+ "page.cases.sidebar.item.open": "Open item",
389
+ "page.cases.sidebar.item.remove": "Remove item",
382
390
  "page.cases.sources": "Sources",
383
391
  "page.cases.updated": "Updated",
384
392
  "page.dashboard.settings.edit": "Edit Dashboard",
@@ -310,6 +310,11 @@
310
310
  "modal.action.empty": "Le nom de l'action ne peut pas être vide.",
311
311
  "modal.action.label": "Nom de l'action",
312
312
  "modal.action.title": "Enregistrer l'action",
313
+ "modal.cases.add_to_case": "Ajouter au cas",
314
+ "modal.cases.add_to_case.full_path": "Chemin complet : {{path}}",
315
+ "modal.cases.add_to_case.select_case": "Rechercher des cas",
316
+ "modal.cases.add_to_case.select_path": "Sélectionner le chemin du dossier",
317
+ "modal.cases.add_to_case.title": "Titre de l'élément",
313
318
  "modal.cases.resolve": "Résoudre le cas",
314
319
  "modal.cases.resolve.description": "Lors de la résolution d'un cas, vous devez soit évaluer toutes les alertes ouvertes, soit ajouter une évaluation aux alertes.",
315
320
  "modal.confirm.delete.description": "Êtes-vous sûr de vouloir supprimer cet élément ?",
@@ -379,6 +384,9 @@
379
384
  "page.cases.detail.properties": "Propriétés",
380
385
  "page.cases.detail.status": "Statut",
381
386
  "page.cases.escalation": "Escalade",
387
+ "page.cases.sidebar.folder.remove": "Supprimer le dossier",
388
+ "page.cases.sidebar.item.open": "Ouvrir l'élément",
389
+ "page.cases.sidebar.item.remove": "Supprimer l'élément",
382
390
  "page.cases.sources": "Sources",
383
391
  "page.cases.updated": "Mis à jour",
384
392
  "page.dashboard.settings.edit": "Modifier le tableau de bord",
@@ -2,8 +2,8 @@
2
2
  * NOTE: This is an auto-generated file. Don't edit this manually.
3
3
  */
4
4
  export interface Item {
5
- id?: string;
6
5
  path?: string;
7
6
  type?: string;
8
7
  value?: string;
8
+ visible?: boolean;
9
9
  }
package/package.json CHANGED
@@ -101,7 +101,7 @@
101
101
  "internal-slot": "1.0.7"
102
102
  },
103
103
  "type": "module",
104
- "version": "2.18.0-dev.686",
104
+ "version": "2.18.0-dev.695",
105
105
  "exports": {
106
106
  "./i18n": "./i18n.js",
107
107
  "./index.css": "./index.css",
@@ -30,7 +30,12 @@ export const MOCK_RESPONSES = {
30
30
  total: 1,
31
31
  rows: 1
32
32
  },
33
- '/api/v1/analytic': [createMockAnalytic()]
33
+ '/api/v1/analytic': [createMockAnalytic()],
34
+ '/api/v2/search/hit,observable': {
35
+ items: [],
36
+ total: 0,
37
+ rows: 0
38
+ }
34
39
  };
35
40
  const handlers = [
36
41
  ...Object.entries(MOCK_RESPONSES).map(([path, data]) => http.all(path, async () => HttpResponse.json({ api_response: data }))),
package/tests/utils.d.ts CHANGED
@@ -3,12 +3,14 @@ import type { Analytic } from '@cccsaurora/howler-ui/models/entities/generated/A
3
3
  import type { Case } from '@cccsaurora/howler-ui/models/entities/generated/Case';
4
4
  import type { Dossier } from '@cccsaurora/howler-ui/models/entities/generated/Dossier';
5
5
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
6
+ import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
6
7
  import type { Template } from '@cccsaurora/howler-ui/models/entities/generated/Template';
7
8
  import type { View } from '@cccsaurora/howler-ui/models/entities/generated/View';
8
9
  type RecursivePartial<T> = {
9
10
  [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P];
10
11
  };
11
12
  export declare const createMockHit: (overrides?: RecursivePartial<Hit>) => Hit;
13
+ export declare const createMockObservable: (overrides?: RecursivePartial<Observable>) => Observable;
12
14
  export declare const createMockAnalytic: (overrides?: Partial<Analytic>) => Analytic;
13
15
  export declare const createMockTemplate: (overrides?: Partial<Template>) => Template;
14
16
  export declare const createMockAction: (overrides?: Partial<Action>) => Action;
package/tests/utils.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Mock data factories
2
2
  export const createMockHit = (overrides) => ({
3
+ ...overrides,
3
4
  __index: 'hit',
4
5
  howler: {
5
6
  id: 'test-hit-1',
@@ -15,6 +16,17 @@ export const createMockHit = (overrides) => ({
15
16
  ...overrides?.event
16
17
  }
17
18
  });
19
+ export const createMockObservable = (overrides) => ({
20
+ ...overrides,
21
+ __index: 'observable',
22
+ howler: {
23
+ id: 'test-observable-1',
24
+ analytic: 'test-analytic',
25
+ detection: 'Test Detection',
26
+ hash: '',
27
+ ...overrides?.howler
28
+ }
29
+ });
18
30
  export const createMockAnalytic = (overrides) => ({
19
31
  analytic_id: 'test-analytic-id',
20
32
  name: 'test-analytic',