@cccsaurora/howler-ui 2.15.0-dev.318 → 2.15.0-dev.326

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 (33) hide show
  1. package/api/search/count/hit.js +2 -1
  2. package/api/search/explain/hit.js +2 -1
  3. package/api/search/facet/hit.js +2 -1
  4. package/api/search/grouped/hit.js +2 -1
  5. package/api/search/histogram/hit.js +2 -1
  6. package/api/search/hit.d.ts +1 -1
  7. package/api/search/hit.js +2 -1
  8. package/components/app/providers/HitSearchProvider.js +4 -4
  9. package/components/app/providers/ParameterProvider.js +2 -1
  10. package/components/app/providers/ViewProvider.test.js +4 -4
  11. package/components/elements/display/modals/RationaleModal.d.ts +2 -0
  12. package/components/elements/display/modals/RationaleModal.js +44 -7
  13. package/components/elements/hit/aggregate/HitGraph.js +2 -1
  14. package/components/hooks/useHitActions.js +1 -1
  15. package/components/routes/advanced/luceneCompletionProvider.js +2 -1
  16. package/components/routes/dossiers/DossierEditor.test.js +3 -2
  17. package/components/routes/help/ActionIntroductionDocumentation.js +3 -3
  18. package/components/routes/hits/search/HitBrowser.js +2 -2
  19. package/components/routes/hits/search/HitContextMenu.d.ts +15 -0
  20. package/components/routes/hits/search/HitContextMenu.js +100 -12
  21. package/components/routes/hits/search/HitContextMenu.test.d.ts +1 -0
  22. package/components/routes/hits/search/HitContextMenu.test.js +774 -0
  23. package/components/routes/hits/search/HitQuery.js +4 -3
  24. package/components/routes/views/ViewComposer.js +2 -2
  25. package/locales/en/translation.json +3 -0
  26. package/locales/fr/translation.json +3 -0
  27. package/package.json +1 -1
  28. package/setupTests.js +1 -0
  29. package/tests/server-handlers.js +7 -1
  30. package/tests/utils.d.ts +12 -0
  31. package/tests/utils.js +41 -0
  32. package/utils/constants.d.ts +1 -0
  33. package/utils/constants.js +1 -0
@@ -10,6 +10,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import { useLocation } from 'react-router-dom';
12
12
  import { useContextSelector } from 'use-context-selector';
13
+ import { DEFAULT_QUERY } from '@cccsaurora/howler-ui/utils/constants';
13
14
  import { sanitizeMultilineLucene } from '@cccsaurora/howler-ui/utils/stringUtils';
14
15
  const DEFAULT_MULTILINE_HEIGHT = 250;
15
16
  const PROMPT_CONTEXT = 'isHitQuery && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible';
@@ -18,9 +19,9 @@ const HitQuery = ({ searching = false, disabled = false, compact = false, trigge
18
19
  const location = useLocation();
19
20
  const theme = useTheme();
20
21
  const monaco = useMonaco();
21
- const savedQuery = useContextSelector(ParameterContext, ctx => ctx.query || 'howler.id:*');
22
+ const savedQuery = useContextSelector(ParameterContext, ctx => ctx.query || DEFAULT_QUERY);
22
23
  const prevQuery = useRef(null);
23
- const [query, setQuery] = useState(new URLSearchParams(window.location.search).get('query') || 'howler.id:*');
24
+ const [query, setQuery] = useState(new URLSearchParams(window.location.search).get('query') || DEFAULT_QUERY);
24
25
  const fzfSearch = useContextSelector(HitSearchContext, ctx => ctx?.fzfSearch ?? false);
25
26
  const [loaded, setLoaded] = useState(false);
26
27
  const [multiline, setMultiline] = useState(false);
@@ -128,7 +129,7 @@ const HitQuery = ({ searching = false, disabled = false, compact = false, trigge
128
129
  p: 0.5,
129
130
  height: multiline ? `${DEFAULT_MULTILINE_HEIGHT + y}px` : theme.spacing(5)
130
131
  }
131
- ], onKeyDown: e => e.stopPropagation(), children: [_jsx(TuiIconButton, { disabled: query.includes('\n#') || disabled, sx: { mr: 1, alignSelf: 'start' }, onClick: () => setMultiline(!multiline), color: multiline ? 'primary' : theme.palette.text.primary, transparent: !multiline, size: compact ? 'small' : 'medium', children: _jsx(Height, { sx: { fontSize: '20px' } }) }), _jsx(QueryEditor, { id: "hit-query", query: preppedQuery, setQuery: setQuery, language: "lucene", height: multiline ? `${DEFAULT_MULTILINE_HEIGHT - 30}px` : '20px', onMount: onMount, editorOptions: options }), fzfSearch && (_jsx(Tooltip, { title: t('route.history'), children: _jsx(History, {}) })), _jsx(TuiIconButton, { disabled: searching || disabled, onClick: () => setQuery('howler.id:*'), sx: { ml: 1, alignSelf: 'start', flexShrink: 0 }, size: compact ? 'small' : 'medium', children: _jsx(Tooltip, { title: t('route.clear'), children: _jsx(Clear, { sx: { fontSize: '20px' } }) }) }), _jsx(TuiIconButton, { disabled: searching || disabled, onClick: search, sx: { ml: 1, alignSelf: 'start', flexShrink: 0 }, size: compact ? 'small' : 'medium', children: _jsx(Tooltip, { title: t('route.search'), children: _jsx(Badge, { invisible: !isDirty, color: "warning", variant: "dot", children: _jsx(Search, { sx: { fontSize: '20px' } }) }) }) }), !loaded && (_jsx(Skeleton, { variant: "rectangular", sx: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }, height: "100%" })), multiline && (_jsx(Box, { sx: {
132
+ ], onKeyDown: e => e.stopPropagation(), children: [_jsx(TuiIconButton, { disabled: query.includes('\n#') || disabled, sx: { mr: 1, alignSelf: 'start' }, onClick: () => setMultiline(!multiline), color: multiline ? 'primary' : theme.palette.text.primary, transparent: !multiline, size: compact ? 'small' : 'medium', children: _jsx(Height, { sx: { fontSize: '20px' } }) }), _jsx(QueryEditor, { id: "hit-query", query: preppedQuery, setQuery: setQuery, language: "lucene", height: multiline ? `${DEFAULT_MULTILINE_HEIGHT - 30}px` : '20px', onMount: onMount, editorOptions: options }), fzfSearch && (_jsx(Tooltip, { title: t('route.history'), children: _jsx(History, {}) })), _jsx(TuiIconButton, { disabled: searching || disabled, onClick: () => setQuery(DEFAULT_QUERY), sx: { ml: 1, alignSelf: 'start', flexShrink: 0 }, size: compact ? 'small' : 'medium', children: _jsx(Tooltip, { title: t('route.clear'), children: _jsx(Clear, { sx: { fontSize: '20px' } }) }) }), _jsx(TuiIconButton, { disabled: searching || disabled, onClick: search, sx: { ml: 1, alignSelf: 'start', flexShrink: 0 }, size: compact ? 'small' : 'medium', children: _jsx(Tooltip, { title: t('route.search'), children: _jsx(Badge, { invisible: !isDirty, color: "warning", variant: "dot", children: _jsx(Search, { sx: { fontSize: '20px' } }) }) }) }), !loaded && (_jsx(Skeleton, { variant: "rectangular", sx: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }, height: "100%" })), multiline && (_jsx(Box, { sx: {
132
133
  position: 'absolute',
133
134
  left: 0,
134
135
  right: 0,
@@ -22,7 +22,7 @@ import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/us
22
22
  import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
23
23
  import { useNavigate, useParams } from 'react-router-dom';
24
24
  import { useContextSelector } from 'use-context-selector';
25
- import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
25
+ import { DEFAULT_QUERY, StorageKey } from '@cccsaurora/howler-ui/utils/constants';
26
26
  import { convertDateToLucene } from '@cccsaurora/howler-ui/utils/utils';
27
27
  import ErrorBoundary from '../ErrorBoundary';
28
28
  import HitQuery from '../hits/search/HitQuery';
@@ -127,7 +127,7 @@ const ViewComposer = () => {
127
127
  }
128
128
  }, [dispatchApi, loadHits, pageCount, setQuery, sort, span]);
129
129
  useEffect(() => {
130
- search(query || 'howler.id:*');
130
+ search(query || DEFAULT_QUERY);
131
131
  // eslint-disable-next-line react-hooks/exhaustive-deps
132
132
  }, []);
133
133
  // We only run this when ancillary properties (i.e. filters, sorting) change
@@ -179,6 +179,7 @@
179
179
  "hit.panel.open": "Open Hit Viewer",
180
180
  "hit.panel.close": "Close",
181
181
  "hit.panel.hit.noselection": "No hit has been selected",
182
+ "hit.panel.exclude": "Exclude By",
182
183
  "hit.panel.details.cluster": "Cluster Size",
183
184
  "hit.panel.details.cluster.description": "Size of the cluster",
184
185
  "hit.panel.details.no.subjects": "No subjects found",
@@ -305,6 +306,8 @@
305
306
  "modal.rationale.title": "Add Rationale",
306
307
  "modal.rationale.description": "Provide a rationale that succinctly explains to other analysts why you are confident in this assessment.",
307
308
  "modal.rationale.label": "Rationale",
309
+ "modal.rationale.type.analytic": "This rationale was used when assessing alerts with the same analytic name.",
310
+ "modal.rationale.type.assignment": "This is a rationale you have recently used when assessing an alert.",
308
311
  "none": "None",
309
312
  "no.data": "No Data",
310
313
  "on": "on",
@@ -180,6 +180,7 @@
180
180
  "hit.panel.open": "Ouvrir hit",
181
181
  "hit.panel.close": "Fermer",
182
182
  "hit.panel.hit.noselection": "Aucun résultat n'a été sélectionné",
183
+ "hit.panel.exclude": "Exclure par",
183
184
  "hit.panel.details.cluster": "Taille de la grappe",
184
185
  "hit.panel.details.cluster.description": "Taille de la grappe",
185
186
  "hit.panel.details.no.subjects": "Aucun sujet trouvé",
@@ -307,6 +308,8 @@
307
308
  "modal.rationale.title": "Ajouter une justification",
308
309
  "modal.rationale.description": "Fournissez une justification qui explique succinctement aux autres analystes les raisons pour lesquelles vous êtes confiant dans cette évaluation.",
309
310
  "modal.rationale.label": "Justification",
311
+ "modal.rationale.type.analytic": "Cette justification a été utilisée lors de l'évaluation des alertes avec le même nom d'analyse.",
312
+ "modal.rationale.type.assignment": "Il s'agit d'une justification que vous avez récemment utilisée lors de l'évaluation d'une alerte.",
310
313
  "none": "Rien",
311
314
  "no.data": "Aucune donnée",
312
315
  "on": "sur",
package/package.json CHANGED
@@ -96,7 +96,7 @@
96
96
  "internal-slot": "1.0.7"
97
97
  },
98
98
  "type": "module",
99
- "version": "2.15.0-dev.318",
99
+ "version": "2.15.0-dev.326",
100
100
  "exports": {
101
101
  "./i18n": "./i18n.js",
102
102
  "./index.css": "./index.css",
package/setupTests.js CHANGED
@@ -3,6 +3,7 @@ import * as matchers from '@testing-library/jest-dom/matchers';
3
3
  import '@testing-library/jest-dom/vitest';
4
4
  import { configure } from '@testing-library/react';
5
5
  import { server } from '@cccsaurora/howler-ui/tests/server';
6
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
6
7
  // Extend vitest with the dom matchers from jest-dom.
7
8
  expect.extend(matchers);
8
9
  // tell React Testing Library to look for id as the testId.
@@ -1,4 +1,5 @@
1
1
  import { http, HttpResponse } from 'msw';
2
+ import { createMockAction } from './utils';
2
3
  export const MOCK_RESPONSES = {
3
4
  '/api/v1/view/example_view_id': {
4
5
  owner: 'user',
@@ -42,7 +43,12 @@ export const MOCK_RESPONSES = {
42
43
  type: 'personal',
43
44
  span: 'date.range.1.month'
44
45
  },
45
- '/api/v1/view/:view_id/favourite': { success: true }
46
+ '/api/v1/view/:view_id/favourite': { success: true },
47
+ '/api/v1/search/action': {
48
+ items: [createMockAction()],
49
+ total: 1,
50
+ rows: 1
51
+ }
46
52
  };
47
53
  const handlers = [
48
54
  ...Object.entries(MOCK_RESPONSES).map(([path, data]) => http.all(path, async () => HttpResponse.json({ api_response: data }))),
@@ -0,0 +1,12 @@
1
+ import type { Action } from '@cccsaurora/howler-ui/models/entities/generated/Action';
2
+ import type { Analytic } from '@cccsaurora/howler-ui/models/entities/generated/Analytic';
3
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
4
+ import type { Template } from '@cccsaurora/howler-ui/models/entities/generated/Template';
5
+ type RecursivePartial<T> = {
6
+ [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P];
7
+ };
8
+ export declare const createMockHit: (overrides?: RecursivePartial<Hit>) => Hit;
9
+ export declare const createMockAnalytic: (overrides?: Partial<Analytic>) => Analytic;
10
+ export declare const createMockTemplate: (overrides?: Partial<Template>) => Template;
11
+ export declare const createMockAction: (overrides?: Partial<Action>) => Action;
12
+ export {};
package/tests/utils.js ADDED
@@ -0,0 +1,41 @@
1
+ // Mock data factories
2
+ export const createMockHit = (overrides) => ({
3
+ howler: {
4
+ id: 'test-hit-1',
5
+ analytic: 'test-analytic',
6
+ detection: 'Test Detection',
7
+ status: 'open',
8
+ assessment: null,
9
+ outline: { indicators: ['a', 'b', 'c'] },
10
+ ...overrides?.howler
11
+ },
12
+ event: {
13
+ id: 'event-123',
14
+ ...overrides?.event
15
+ }
16
+ });
17
+ export const createMockAnalytic = (overrides) => ({
18
+ analytic_id: 'test-analytic-id',
19
+ name: 'test-analytic',
20
+ triage_settings: {
21
+ valid_assessments: ['legitimate', 'false_positive'],
22
+ skip_rationale: false,
23
+ ...overrides?.triage_settings
24
+ },
25
+ ...overrides
26
+ });
27
+ export const createMockTemplate = (overrides) => ({
28
+ keys: ['howler.detection', 'event.id'],
29
+ ...overrides
30
+ });
31
+ export const createMockAction = (overrides) => ({
32
+ action_id: 'action-1',
33
+ name: 'Test Action',
34
+ operations: [
35
+ {
36
+ data_json: '{}',
37
+ operation_id: 'transition'
38
+ }
39
+ ],
40
+ ...overrides
41
+ });
@@ -76,4 +76,5 @@ interface LabelData {
76
76
  color?: string;
77
77
  }
78
78
  export declare const LABEL_TYPES: Record<string, LabelData>;
79
+ export declare const DEFAULT_QUERY = "howler.id:*";
79
80
  export {};
@@ -112,3 +112,4 @@ export const LABEL_TYPES = {
112
112
  assignments: {},
113
113
  tuning: {}
114
114
  };
115
+ export const DEFAULT_QUERY = 'howler.id:*';