@orchestrator-ui/orchestrator-ui-components 8.8.1 → 8.9.0
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/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +15 -14
- package/CHANGELOG.md +23 -0
- package/dist/index.d.ts +63 -24
- package/dist/index.js +2695 -2480
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/WfoBadges/WfoProductStatusBadge/WfoProductStatusBadge.tsx +2 -5
- package/src/components/WfoPydanticForm/fields/WfoMultiCheckboxField.tsx +2 -1
- package/src/components/WfoStartButton/WfoStartButtonComboBox.tsx +6 -11
- package/src/components/WfoStartButton/WfoStartWorkflowComboBox.tsx +1 -1
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +6 -16
- package/src/components/WfoSubscription/utils/utils.spec.ts +57 -0
- package/src/components/WfoSubscription/utils/utils.ts +15 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +12 -8
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +23 -10
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoSearchHelpModal.tsx +59 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +37 -20
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +4 -2
- package/src/components/WfoTable/WfoTable/WfoTable.tsx +77 -46
- package/src/components/WfoTable/WfoTable/WfoTableSkeletonRows.tsx +52 -0
- package/src/components/WfoTable/WfoTable/index.ts +1 -0
- package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.tsx +21 -18
- package/src/components/WfoTable/WfoTableSettingsModal/styles.ts +17 -0
- package/src/components/WfoTable/utils/tableConfigPersistence.ts +1 -0
- package/src/configuration/version.ts +1 -1
- package/src/hooks/useGetPydanticFormsConfig.tsx +5 -3
- package/src/hooks/useSearch.ts +2 -2
- package/src/hooks/useStoredTableConfig.ts +1 -0
- package/src/messages/en-GB.json +21 -0
- package/src/messages/nl-NL.json +22 -1
- package/src/pages/WfoSearchPocPage.tsx +28 -23
- package/src/pages/processes/WfoProductInformationWithLink.tsx +9 -4
- package/src/rtk/endpoints/metadata/productBlocks.ts +9 -1
- package/src/rtk/endpoints/metadata/products.ts +5 -2
- package/src/rtk/endpoints/startOptions.ts +4 -3
- package/src/types/search.ts +8 -0
- package/src/types/types.ts +3 -3
- package/src/utils/getProductLifecycleStatus.spec.ts +26 -0
- package/src/utils/getProductLifecycleStatus.ts +13 -0
- package/src/utils/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchestrator-ui/orchestrator-ui-components",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.9.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Library of UI Components used to display the workflow orchestrator frontend",
|
|
6
6
|
"author": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"next-query-params": "^5.0.0",
|
|
52
52
|
"object-hash": "^3.0.0",
|
|
53
53
|
"prism-themes": "^1.9.0",
|
|
54
|
-
"pydantic-forms": "^3.0
|
|
54
|
+
"pydantic-forms": "^3.1.0",
|
|
55
55
|
"react-diff-view": "^3.2.0",
|
|
56
56
|
"react-draggable": "^4.4.6",
|
|
57
57
|
"react-querybuilder": "^8.18.0",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"react-select": "^5.8.0",
|
|
60
60
|
"scroll-into-view": "^1.16.2",
|
|
61
61
|
"unidiff": "^1.0.4",
|
|
62
|
-
"use-query-params": "^2.2.2"
|
|
62
|
+
"use-query-params": "^2.2.2",
|
|
63
|
+
"zod": "^4.4.3"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@elastic/eui": "^113.0.0",
|
|
@@ -8,16 +8,13 @@ export type WfoProductStatusBadgeProps = {
|
|
|
8
8
|
status: ProductLifecycleStatus;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
const normalizedStatus = (status: ProductLifecycleStatus): string => status.toLowerCase().replace(/_/g, ' ');
|
|
12
|
-
|
|
13
11
|
export const WfoProductStatusBadge: FC<WfoProductStatusBadgeProps> = ({ status }) => {
|
|
14
12
|
const { theme, toSecondaryColor } = useOrchestratorTheme();
|
|
15
13
|
|
|
16
|
-
const productLifeCycleStatus = normalizedStatus(status);
|
|
17
14
|
const getBadgeColorFromStatus = () => {
|
|
18
15
|
const { primary, borderBaseSubdued, textPrimary, textParagraph, success, textSuccess } = theme.colors;
|
|
19
16
|
|
|
20
|
-
switch (
|
|
17
|
+
switch (status) {
|
|
21
18
|
case ProductLifecycleStatus.ACTIVE:
|
|
22
19
|
return {
|
|
23
20
|
badgeColor: toSecondaryColor(success),
|
|
@@ -41,7 +38,7 @@ export const WfoProductStatusBadge: FC<WfoProductStatusBadgeProps> = ({ status }
|
|
|
41
38
|
|
|
42
39
|
return (
|
|
43
40
|
<WfoBadge textColor={textColor} color={badgeColor}>
|
|
44
|
-
{
|
|
41
|
+
{status}
|
|
45
42
|
</WfoBadge>
|
|
46
43
|
);
|
|
47
44
|
};
|
|
@@ -24,7 +24,8 @@ export const WfoMultiCheckboxField: PydanticFormControlledElement = ({ pydanticF
|
|
|
24
24
|
|
|
25
25
|
const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState<Record<string, boolean>>({});
|
|
26
26
|
|
|
27
|
-
const {
|
|
27
|
+
const { arrayItem, id } = pydanticFormField;
|
|
28
|
+
const options = arrayItem?.options || [];
|
|
28
29
|
|
|
29
30
|
const checkboxes = options?.map((option, index) => ({
|
|
30
31
|
label: option.label,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import { capitalize } from 'lodash';
|
|
4
|
-
|
|
5
3
|
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiPopover, EuiSelectable, EuiSpacer } from '@elastic/eui';
|
|
6
4
|
|
|
7
5
|
import { useGetOrchestratorConfig, useOrchestratorTheme, useWithOrchestratorTheme } from '@/hooks';
|
|
8
6
|
import { WfoChevronDown, WfoPlusCircleFill } from '@/icons';
|
|
9
7
|
import { ProductLifecycleStatus, StartComboBoxOption } from '@/types';
|
|
8
|
+
import { getProductLifecycleStatus } from '@/utils';
|
|
10
9
|
|
|
11
10
|
import { getStyles } from './styles';
|
|
12
11
|
|
|
@@ -16,12 +15,8 @@ export type WfoStartButtonComboBoxProps = {
|
|
|
16
15
|
onOptionChange: (selectedOption: StartComboBoxOption) => void;
|
|
17
16
|
isProcess: boolean;
|
|
18
17
|
className?: string;
|
|
19
|
-
selectedProductStatus?: ProductLifecycleStatus
|
|
20
|
-
setSelectedProductStatus?: (status: ProductLifecycleStatus
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const formatProductStatusLabel = (productStatus: string) => {
|
|
24
|
-
return capitalize(productStatus.replace(/_/g, ' '));
|
|
18
|
+
selectedProductStatus?: ProductLifecycleStatus;
|
|
19
|
+
setSelectedProductStatus?: (status: ProductLifecycleStatus) => void;
|
|
25
20
|
};
|
|
26
21
|
|
|
27
22
|
export const WfoStartButtonComboBox = ({
|
|
@@ -67,7 +62,7 @@ export const WfoStartButtonComboBox = ({
|
|
|
67
62
|
iconType={() => <WfoChevronDown height={18} width={18} color="currentColor" />}
|
|
68
63
|
onClick={() => setFilterPopoverOpen((v) => !v)}
|
|
69
64
|
>
|
|
70
|
-
<b>{
|
|
65
|
+
<b>{selectedProductStatus ?? ProductLifecycleStatus.ACTIVE}</b>
|
|
71
66
|
</EuiButtonEmpty>
|
|
72
67
|
}
|
|
73
68
|
isOpen={isFilterPopoverOpen}
|
|
@@ -80,11 +75,11 @@ export const WfoStartButtonComboBox = ({
|
|
|
80
75
|
key={productStatus}
|
|
81
76
|
size="xs"
|
|
82
77
|
onClick={() => {
|
|
83
|
-
setSelectedProductStatus(productStatus
|
|
78
|
+
setSelectedProductStatus(getProductLifecycleStatus(productStatus));
|
|
84
79
|
setFilterPopoverOpen(false);
|
|
85
80
|
}}
|
|
86
81
|
>
|
|
87
|
-
{
|
|
82
|
+
{productStatus ?? ProductLifecycleStatus.ACTIVE}
|
|
88
83
|
</EuiButtonEmpty>
|
|
89
84
|
</div>
|
|
90
85
|
))}
|
|
@@ -14,7 +14,7 @@ export const WfoStartWorkflowButtonComboBox = () => {
|
|
|
14
14
|
const router = useRouter();
|
|
15
15
|
const t = useTranslations('common');
|
|
16
16
|
const { isEngineRunningNow } = useCheckEngineStatus();
|
|
17
|
-
const [selectedProductStatus, setSelectedProductStatus] = React.useState<ProductLifecycleStatus
|
|
17
|
+
const [selectedProductStatus, setSelectedProductStatus] = React.useState<ProductLifecycleStatus>(
|
|
18
18
|
ProductLifecycleStatus.ACTIVE,
|
|
19
19
|
);
|
|
20
20
|
|
|
@@ -12,13 +12,14 @@ import {
|
|
|
12
12
|
WfoInSyncField,
|
|
13
13
|
WfoPopover,
|
|
14
14
|
} from '@/components';
|
|
15
|
+
import { getActionItemsByTarget } from '@/components/WfoSubscription';
|
|
15
16
|
import { WfoSubscriptionActionsMenuItem } from '@/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem';
|
|
16
17
|
import { useActiveProcess } from '@/components/WfoSubscription/WfoSubscriptionActions/utils';
|
|
17
18
|
import { PolicyResource } from '@/configuration/policy-resources';
|
|
18
19
|
import { useOrchestratorTheme, usePolicy } from '@/hooks';
|
|
19
20
|
import { WfoDotsHorizontal } from '@/icons/WfoDotsHorizontal';
|
|
20
21
|
import { useGetSubscriptionActionsQuery, useGetSubscriptionDetailQuery, useStartProcessMutation } from '@/rtk';
|
|
21
|
-
import {
|
|
22
|
+
import { WorkflowTarget } from '@/types';
|
|
22
23
|
|
|
23
24
|
type MenuBlockProps = {
|
|
24
25
|
title: string;
|
|
@@ -129,21 +130,10 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
129
130
|
}
|
|
130
131
|
};
|
|
131
132
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const actionsByTarget = subscriptionActions as unknown as
|
|
137
|
-
| Record<string, SubscriptionAction[] | undefined>
|
|
138
|
-
| undefined;
|
|
139
|
-
|
|
140
|
-
return actionsByTarget?.[workflowTarget] ?? actionsByTarget?.[workflowTarget.toLowerCase()] ?? [];
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const validateActionItems = getActionItems(WorkflowTarget.VALIDATE);
|
|
144
|
-
const reconcileActionItems = getActionItems(WorkflowTarget.RECONCILE);
|
|
145
|
-
const modifyActionItems = getActionItems(WorkflowTarget.MODIFY);
|
|
146
|
-
const terminateActionItems = getActionItems(WorkflowTarget.TERMINATE);
|
|
133
|
+
const validateActionItems = getActionItemsByTarget(WorkflowTarget.VALIDATE, subscriptionActions);
|
|
134
|
+
const reconcileActionItems = getActionItemsByTarget(WorkflowTarget.RECONCILE, subscriptionActions);
|
|
135
|
+
const modifyActionItems = getActionItemsByTarget(WorkflowTarget.MODIFY, subscriptionActions);
|
|
136
|
+
const terminateActionItems = getActionItemsByTarget(WorkflowTarget.TERMINATE, subscriptionActions);
|
|
147
137
|
|
|
148
138
|
const compactItems = (
|
|
149
139
|
<>
|
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
ProcessStatus,
|
|
6
6
|
ProductBlockInstance,
|
|
7
7
|
SubscriptionAction,
|
|
8
|
+
SubscriptionActions,
|
|
8
9
|
SubscriptionDetailProcess,
|
|
9
10
|
WorkflowTarget,
|
|
10
11
|
} from '../../../types';
|
|
11
12
|
import {
|
|
12
13
|
flattenSubscriptionActionProps,
|
|
14
|
+
getActionItemsByTarget,
|
|
13
15
|
getFieldFromProductBlockInstanceValues,
|
|
14
16
|
getLastUncompletedProcess,
|
|
15
17
|
getLatestTaskDate,
|
|
@@ -470,6 +472,61 @@ describe('mapProductBlockInstancesToEuiSelectableOptions', () => {
|
|
|
470
472
|
});
|
|
471
473
|
});
|
|
472
474
|
|
|
475
|
+
describe('getActionItemsByTarget', () => {
|
|
476
|
+
const modifyAction: SubscriptionAction = {
|
|
477
|
+
name: 'modify_note',
|
|
478
|
+
description: 'Modify note',
|
|
479
|
+
};
|
|
480
|
+
const terminateAction: SubscriptionAction = {
|
|
481
|
+
name: 'terminate',
|
|
482
|
+
description: 'Terminate subscription',
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const subscriptionActions: SubscriptionActions = {
|
|
486
|
+
[WorkflowTarget.MODIFY]: [modifyAction],
|
|
487
|
+
[WorkflowTarget.TERMINATE]: [terminateAction],
|
|
488
|
+
[WorkflowTarget.SYSTEM]: [],
|
|
489
|
+
[WorkflowTarget.VALIDATE]: [],
|
|
490
|
+
[WorkflowTarget.RECONCILE]: [],
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
it('returns actions for the given target with uppercase keys', () => {
|
|
494
|
+
expect(getActionItemsByTarget(WorkflowTarget.MODIFY, subscriptionActions)).toEqual([modifyAction]);
|
|
495
|
+
expect(getActionItemsByTarget(WorkflowTarget.TERMINATE, subscriptionActions)).toEqual([terminateAction]);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('falls back to lowercase keys returned by core versions 5.2 and lower', () => {
|
|
499
|
+
const lowercaseActions = {
|
|
500
|
+
modify: [modifyAction],
|
|
501
|
+
terminate: [terminateAction],
|
|
502
|
+
} as unknown as SubscriptionActions;
|
|
503
|
+
|
|
504
|
+
expect(getActionItemsByTarget(WorkflowTarget.MODIFY, lowercaseActions)).toEqual([modifyAction]);
|
|
505
|
+
expect(getActionItemsByTarget(WorkflowTarget.TERMINATE, lowercaseActions)).toEqual([terminateAction]);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('prefers the uppercase key when both casings are present', () => {
|
|
509
|
+
const mixedActions = {
|
|
510
|
+
[WorkflowTarget.MODIFY]: [modifyAction],
|
|
511
|
+
modify: [terminateAction],
|
|
512
|
+
} as unknown as SubscriptionActions;
|
|
513
|
+
|
|
514
|
+
expect(getActionItemsByTarget(WorkflowTarget.MODIFY, mixedActions)).toEqual([modifyAction]);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('returns an empty array when the target is missing in either casing', () => {
|
|
518
|
+
const actionsWithoutValidate = {
|
|
519
|
+
[WorkflowTarget.MODIFY]: [modifyAction],
|
|
520
|
+
} as unknown as SubscriptionActions;
|
|
521
|
+
|
|
522
|
+
expect(getActionItemsByTarget(WorkflowTarget.VALIDATE, actionsWithoutValidate)).toEqual([]);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('returns an empty array when subscriptionActions is undefined', () => {
|
|
526
|
+
expect(getActionItemsByTarget(WorkflowTarget.MODIFY, undefined)).toEqual([]);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
473
530
|
describe('parseErrorDetail', () => {
|
|
474
531
|
it('parses a single UUID inside brackets', () => {
|
|
475
532
|
const input = "Subscription 123 has still failed processes with id's: ['11111111-1111-1111-1111-111111111111']";
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ProductBlockInstance,
|
|
10
10
|
SortOrder,
|
|
11
11
|
SubscriptionAction,
|
|
12
|
+
SubscriptionActions,
|
|
12
13
|
SubscriptionDetailProcess,
|
|
13
14
|
SubscriptionRelation,
|
|
14
15
|
WorkflowTarget,
|
|
@@ -189,3 +190,17 @@ export const mapProductBlockInstancesToEuiSelectableOptions = (
|
|
|
189
190
|
},
|
|
190
191
|
}));
|
|
191
192
|
};
|
|
193
|
+
|
|
194
|
+
export const getActionItemsByTarget = (
|
|
195
|
+
workflowTarget: WorkflowTarget,
|
|
196
|
+
subscriptionActions?: SubscriptionActions,
|
|
197
|
+
): SubscriptionAction[] => {
|
|
198
|
+
// https://github.com/workfloworchestrator/orchestrator-core/issues/1820 will align the WorkflowTarget enum and make it
|
|
199
|
+
// uppercase. We support both for now. The lowercase keys are deliberately not part of the
|
|
200
|
+
// SubscriptionActions type, so the fallback is a runtime-only check behind a cast.
|
|
201
|
+
const actionsByTarget = subscriptionActions as unknown as
|
|
202
|
+
| Record<string, SubscriptionAction[] | undefined>
|
|
203
|
+
| undefined;
|
|
204
|
+
|
|
205
|
+
return actionsByTarget?.[workflowTarget] ?? actionsByTarget?.[workflowTarget.toLowerCase()] ?? [];
|
|
206
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { FC, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { FieldSelectorProps } from 'react-querybuilder';
|
|
3
3
|
|
|
4
4
|
import { useTranslations } from 'next-intl';
|
|
@@ -7,7 +7,7 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui';
|
|
|
7
7
|
import { EuiComboBox } from '@elastic/eui';
|
|
8
8
|
|
|
9
9
|
import { usePathAutocomplete } from '@/hooks';
|
|
10
|
-
import { EntityKind,
|
|
10
|
+
import { EntityKind, PathInfo, WfoQueryBuilderContext } from '@/types';
|
|
11
11
|
|
|
12
12
|
// react-querybuilder applies the `.rule` class to the rule container and `.rule-value` to
|
|
13
13
|
// the value-editor cell (see `standardClassnames` in @react-querybuilder/core). We hop from
|
|
@@ -30,9 +30,13 @@ const focusValueEditorAfterRender = (searchInput: HTMLInputElement | null) => {
|
|
|
30
30
|
});
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
interface WfoFieldSelectorProps extends Omit<FieldSelectorProps, 'context'> {
|
|
34
|
+
context: WfoQueryBuilderContext;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const WfoFieldSelector: FC<WfoFieldSelectorProps> = ({ handleOnChange, disabled, rule, context }) => {
|
|
34
38
|
const { field } = rule;
|
|
35
|
-
const prefilledFieldOptions
|
|
39
|
+
const { useAdvancedNestedSearch, prefilledFieldOptions, onFieldSelected } = context;
|
|
36
40
|
const [selectedValue, setSelectedValue] = useState<string>(field);
|
|
37
41
|
const [searchInput, setSearchInput] = useState<HTMLInputElement | null>(null);
|
|
38
42
|
const optionsRef = useRef<EuiComboBoxOptionOption<string>[]>([]);
|
|
@@ -43,13 +47,13 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
|
|
|
43
47
|
label: path,
|
|
44
48
|
});
|
|
45
49
|
|
|
50
|
+
const isSelectablePath = (path: string) => useAdvancedNestedSearch || !path.includes('.');
|
|
51
|
+
|
|
46
52
|
const getOptionsFromPathInfo = (pathInfos: PathInfo[]): EuiComboBoxOptionOption<string>[] => {
|
|
47
53
|
const pathOptions: EuiComboBoxOptionOption<string>[] = [];
|
|
48
54
|
|
|
49
55
|
pathInfos.forEach((pathInfo) => {
|
|
50
|
-
|
|
51
|
-
// Adds more specific paths
|
|
52
|
-
pathInfo.availablePaths?.forEach((path) => {
|
|
56
|
+
[pathInfo.path, ...(pathInfo.availablePaths ?? [])].filter(isSelectablePath).forEach((path) => {
|
|
53
57
|
pathOptions.push(getOption(path));
|
|
54
58
|
});
|
|
55
59
|
});
|
|
@@ -85,7 +89,7 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
|
|
|
85
89
|
?? paths.find((path) => path.availablePaths?.includes(selectedValue));
|
|
86
90
|
const operators = matchingPath?.operators ?? prefilledFieldOptions.get(selectedValue) ?? [];
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
onFieldSelected(selectedValue, operators, matchingPath);
|
|
89
93
|
};
|
|
90
94
|
|
|
91
95
|
const handleFieldSelection = (selectedOptions: EuiComboBoxOptionOption<string>[]) => {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { type ComponentType, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type FieldSelectorProps,
|
|
4
|
+
FullOperator,
|
|
5
|
+
QueryBuilder,
|
|
6
|
+
type RuleGroupType,
|
|
7
|
+
generateID,
|
|
8
|
+
} from 'react-querybuilder';
|
|
3
9
|
import 'react-querybuilder/dist/query-builder.css';
|
|
4
10
|
|
|
5
11
|
import { useTranslations } from 'next-intl';
|
|
@@ -10,7 +16,7 @@ import { SearchParams, WfoAutoExpandableTextArea, WfoTextAnchor } from '@/compon
|
|
|
10
16
|
import { WfoCombinatorSelector } from '@/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector';
|
|
11
17
|
import { useFieldsPathInfo, useWithOrchestratorTheme } from '@/hooks';
|
|
12
18
|
import { EntityKind, OperatorDisplay } from '@/types';
|
|
13
|
-
import type { FieldToOperatorMap, PathInfo } from '@/types';
|
|
19
|
+
import type { FieldToOperatorMap, PathInfo, WfoQueryBuilderContext } from '@/types';
|
|
14
20
|
|
|
15
21
|
import { WfoFieldSelector } from './WfoFieldSelector';
|
|
16
22
|
import { WfoInlineCombinator } from './WfoInlineCombinator';
|
|
@@ -65,6 +71,7 @@ interface WfoFilterBuilderProps {
|
|
|
65
71
|
handleSearch: (searchParams?: SearchParams) => void;
|
|
66
72
|
onToggleFilterBuilder: (isVisible: boolean) => void;
|
|
67
73
|
prefilledFieldOptions: FieldToOperatorMap;
|
|
74
|
+
useAdvancedNestedSearch?: boolean;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
const initialRuleGroup: RuleGroupType = {
|
|
@@ -87,6 +94,7 @@ export const WfoFilterBuilder = ({
|
|
|
87
94
|
handleSearch,
|
|
88
95
|
prefilledFieldOptions,
|
|
89
96
|
onToggleFilterBuilder,
|
|
97
|
+
useAdvancedNestedSearch = true,
|
|
90
98
|
}: WfoFilterBuilderProps) => {
|
|
91
99
|
const mapOperatorsToRQBOperatorOptions = (operators?: string[]): FullOperator[] => {
|
|
92
100
|
return (operators ?? []).map((operator) => {
|
|
@@ -143,6 +151,14 @@ export const WfoFilterBuilder = ({
|
|
|
143
151
|
);
|
|
144
152
|
const resolvedFieldsPathInfo = useFieldsPathInfo(unresolvedFields, EntityKind.SUBSCRIPTION);
|
|
145
153
|
|
|
154
|
+
const queryBuilderContext: WfoQueryBuilderContext = {
|
|
155
|
+
onFieldSelected: handleFieldSelected,
|
|
156
|
+
prefilledFieldOptions,
|
|
157
|
+
fieldPathInfoMap,
|
|
158
|
+
onValueEditorEnter: handleValueEditorEnter,
|
|
159
|
+
useAdvancedNestedSearch,
|
|
160
|
+
};
|
|
161
|
+
|
|
146
162
|
useEffect(() => {
|
|
147
163
|
resolvedFieldsPathInfo.forEach((pathInfo, field) => {
|
|
148
164
|
if (pathInfo && !fieldPathInfoMap.has(field)) {
|
|
@@ -162,18 +178,15 @@ export const WfoFilterBuilder = ({
|
|
|
162
178
|
latestRuleGroupRef.current = ruleGroup;
|
|
163
179
|
onUpdateQueryBuilder(ruleGroup);
|
|
164
180
|
}}
|
|
165
|
-
context={
|
|
166
|
-
onFieldSelected: handleFieldSelected,
|
|
167
|
-
prefilledFieldOptions,
|
|
168
|
-
fieldPathInfoMap,
|
|
169
|
-
onValueEditorEnter: handleValueEditorEnter,
|
|
170
|
-
}}
|
|
181
|
+
context={queryBuilderContext}
|
|
171
182
|
getOperators={(field) => {
|
|
172
183
|
const operators = fieldToOperatorMap.get(field);
|
|
173
184
|
return mapOperatorsToRQBOperatorOptions(operators);
|
|
174
185
|
}}
|
|
175
186
|
controlElements={{
|
|
176
|
-
|
|
187
|
+
// WfoFieldSelector requires the context this QueryBuilder always provides,
|
|
188
|
+
// while react-querybuilder declares it optional — hence the cast.
|
|
189
|
+
fieldSelector: WfoFieldSelector as ComponentType<FieldSelectorProps>,
|
|
177
190
|
operatorSelector: WfoOperatorSelector,
|
|
178
191
|
valueEditor: WfoValueEditor,
|
|
179
192
|
ruleGroup: WfoRuleGroup,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTranslations } from 'next-intl';
|
|
4
|
+
|
|
5
|
+
import { EuiText } from '@elastic/eui';
|
|
6
|
+
|
|
7
|
+
import { WfoInformationModal } from '@/components';
|
|
8
|
+
|
|
9
|
+
export type WfoSearchHelpModalProps = {
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const WfoSearchHelpModal = ({ onClose }: WfoSearchHelpModalProps) => {
|
|
14
|
+
const t = useTranslations('search.page');
|
|
15
|
+
const tCommon = useTranslations('common');
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<WfoInformationModal title={t('help.title')} onClose={onClose}>
|
|
19
|
+
<EuiText size="s">
|
|
20
|
+
<p>
|
|
21
|
+
<strong>{t('help.searchFieldTitle')}</strong> - {t('help.searchFieldDescription')}
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<h4>{t('help.filterTitle')}</h4>
|
|
25
|
+
<p>{t('help.filterIntro')}</p>
|
|
26
|
+
<ul>
|
|
27
|
+
<li>
|
|
28
|
+
<strong>{t('addRule')}</strong> - {t('help.addRuleDescription')}
|
|
29
|
+
</li>
|
|
30
|
+
<li>
|
|
31
|
+
<strong>{t('addGroup')}</strong> - {t('help.addGroupDescription')}
|
|
32
|
+
</li>
|
|
33
|
+
</ul>
|
|
34
|
+
|
|
35
|
+
<h4>{t('help.quickFilterTitle')}</h4>
|
|
36
|
+
<p>{t('help.quickFilterDescription')}</p>
|
|
37
|
+
|
|
38
|
+
<h4>{t('help.matchingRowTitle')}</h4>
|
|
39
|
+
<p>{t('help.matchingRowDescription')}</p>
|
|
40
|
+
|
|
41
|
+
<h4>{t('help.tableSettingsTitle')}</h4>
|
|
42
|
+
<ul>
|
|
43
|
+
<li>
|
|
44
|
+
<strong>{tCommon('showMatchDetails')}</strong> - {t('help.showMatchDetailsDescription')}
|
|
45
|
+
</li>
|
|
46
|
+
<li>
|
|
47
|
+
<strong>{tCommon('retrieval')}</strong> - {t('help.retrievalDescription')}
|
|
48
|
+
</li>
|
|
49
|
+
<li>
|
|
50
|
+
<strong>{tCommon('export')}</strong> - {t('help.exportDescription')}
|
|
51
|
+
</li>
|
|
52
|
+
<li>
|
|
53
|
+
<strong>{t('help.advancedNestedSearchLabel')}</strong> - {t('help.advancedNestedSearchDescription')}
|
|
54
|
+
</li>
|
|
55
|
+
</ul>
|
|
56
|
+
</EuiText>
|
|
57
|
+
</WfoInformationModal>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -3,16 +3,7 @@ import type { RuleGroupType } from 'react-querybuilder';
|
|
|
3
3
|
|
|
4
4
|
import { useTranslations } from 'next-intl';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
EuiButton,
|
|
8
|
-
EuiFlexGroup,
|
|
9
|
-
EuiFlexItem,
|
|
10
|
-
EuiFormRow,
|
|
11
|
-
EuiSelect,
|
|
12
|
-
EuiSpacer,
|
|
13
|
-
EuiSwitch,
|
|
14
|
-
EuiText,
|
|
15
|
-
} from '@elastic/eui';
|
|
6
|
+
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSpacer, EuiSwitch } from '@elastic/eui';
|
|
16
7
|
|
|
17
8
|
import {
|
|
18
9
|
DEFAULT_PAGE_SIZE,
|
|
@@ -45,6 +36,7 @@ import { getDefaultTableConfig } from '@/utils';
|
|
|
45
36
|
import { ColumnType, WfoTable, WfoTableProps } from '../WfoTable';
|
|
46
37
|
import { WfoFilterBuilder } from './WfoFilterBuilder';
|
|
47
38
|
import { WfoSearchFieldWithActions } from './WfoSearchFieldWithActions';
|
|
39
|
+
import { WfoSearchHelpModal } from './WfoSearchHelpModal';
|
|
48
40
|
import { getWfoStructuredSearchTableStyles } from './styles';
|
|
49
41
|
import { buildColumnFilter } from './utils';
|
|
50
42
|
|
|
@@ -80,6 +72,7 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
|
|
|
80
72
|
rowExpandingConfiguration: WfoTableProps<T>['rowExpandingConfiguration'];
|
|
81
73
|
defaultHiddenColumns?: TableColumnKeys<T>;
|
|
82
74
|
defaultShowMatchDetails?: boolean;
|
|
75
|
+
defaultAdvancedNestedSearch?: boolean;
|
|
83
76
|
queryText?: string;
|
|
84
77
|
localStorageKey: string;
|
|
85
78
|
exportDataIsLoading?: boolean;
|
|
@@ -101,7 +94,7 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
|
|
|
101
94
|
onUpdateQueryBuilder: (ruleGroup: RuleGroupType | false) => void;
|
|
102
95
|
handleSearch: (searchParams?: SearchParams) => void;
|
|
103
96
|
pageSize: number;
|
|
104
|
-
setPageSize:
|
|
97
|
+
setPageSize: (updatedPageSize: number) => void;
|
|
105
98
|
totalItems: number | false;
|
|
106
99
|
hasNextPage: boolean;
|
|
107
100
|
prefilledFieldOptions: FieldToOperatorMap;
|
|
@@ -111,6 +104,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
111
104
|
tableColumnConfig,
|
|
112
105
|
defaultHiddenColumns = [],
|
|
113
106
|
defaultShowMatchDetails = false,
|
|
107
|
+
defaultAdvancedNestedSearch = true,
|
|
114
108
|
queryText,
|
|
115
109
|
localStorageKey,
|
|
116
110
|
exportDataIsLoading,
|
|
@@ -148,6 +142,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
148
142
|
const [rowDetailModalData, setRowDetailModalData] = useState<T | undefined>(undefined);
|
|
149
143
|
const [showInformationModal, setShowInformationModal] = useState(false);
|
|
150
144
|
const [showMatchDetails, setShowMatchDetails] = useState(defaultShowMatchDetails);
|
|
145
|
+
const [advancedNestedSearch, setAdvancedNestedSearch] = useState(defaultAdvancedNestedSearch);
|
|
151
146
|
const t = useTranslations('common');
|
|
152
147
|
|
|
153
148
|
useEffect(() => {
|
|
@@ -160,6 +155,10 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
160
155
|
setShowMatchDetails(defaultShowMatchDetails);
|
|
161
156
|
}, [defaultShowMatchDetails]);
|
|
162
157
|
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
setAdvancedNestedSearch(defaultAdvancedNestedSearch);
|
|
160
|
+
}, [defaultAdvancedNestedSearch]);
|
|
161
|
+
|
|
163
162
|
useEffect(() => {
|
|
164
163
|
if (filterString) {
|
|
165
164
|
setIsFilterBuilderVisible(true);
|
|
@@ -199,10 +198,11 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
199
198
|
hiddenColumns: updatedHiddenColumns,
|
|
200
199
|
selectedPageSize: updatedTableConfig.selectedPageSize,
|
|
201
200
|
showMatchDetails,
|
|
201
|
+
advancedNestedSearch,
|
|
202
202
|
});
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
-
// The
|
|
205
|
+
// The toggles apply live, so persist them immediately alongside the currently committed
|
|
206
206
|
// hidden columns and page size instead of waiting for the modal's "Update" action.
|
|
207
207
|
const handleToggleShowMatchDetails = (checked: boolean) => {
|
|
208
208
|
setShowMatchDetails(checked);
|
|
@@ -210,6 +210,17 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
210
210
|
hiddenColumns,
|
|
211
211
|
selectedPageSize: pageSize ?? DEFAULT_PAGE_SIZE,
|
|
212
212
|
showMatchDetails: checked,
|
|
213
|
+
advancedNestedSearch,
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const handleToggleAdvancedNestedSearch = (checked: boolean) => {
|
|
218
|
+
setAdvancedNestedSearch(checked);
|
|
219
|
+
setTableConfigToLocalStorage(localStorageKey, {
|
|
220
|
+
hiddenColumns,
|
|
221
|
+
selectedPageSize: pageSize ?? DEFAULT_PAGE_SIZE,
|
|
222
|
+
showMatchDetails,
|
|
223
|
+
advancedNestedSearch: checked,
|
|
213
224
|
});
|
|
214
225
|
};
|
|
215
226
|
|
|
@@ -218,6 +229,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
218
229
|
setHiddenColumns(defaultTableConfig.hiddenColumns);
|
|
219
230
|
setPageSize(defaultTableConfig.selectedPageSize);
|
|
220
231
|
setShowMatchDetails(defaultTableConfig.showMatchDetails ?? false);
|
|
232
|
+
setAdvancedNestedSearch(defaultTableConfig.advancedNestedSearch ?? false);
|
|
221
233
|
setShowTableSettingsModal(false);
|
|
222
234
|
clearTableConfigFromLocalStorage(localStorageKey);
|
|
223
235
|
};
|
|
@@ -272,6 +284,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
272
284
|
handleSearch={handleSearch}
|
|
273
285
|
onToggleFilterBuilder={setIsFilterBuilderVisible}
|
|
274
286
|
prefilledFieldOptions={prefilledFieldOptions}
|
|
287
|
+
useAdvancedNestedSearch={advancedNestedSearch}
|
|
275
288
|
/>
|
|
276
289
|
</>
|
|
277
290
|
)}
|
|
@@ -290,6 +303,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
290
303
|
dataSorting={dataSorting}
|
|
291
304
|
data={data}
|
|
292
305
|
isLoading={isLoading}
|
|
306
|
+
loadingSkeletonRowCount={pageSize}
|
|
293
307
|
{...tableProps}
|
|
294
308
|
/>
|
|
295
309
|
|
|
@@ -323,6 +337,15 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
323
337
|
compressed
|
|
324
338
|
/>
|
|
325
339
|
</EuiFormRow>
|
|
340
|
+
<EuiFormRow label={t('advancedNestedSearch')} display="columnCompressed">
|
|
341
|
+
<EuiSwitch
|
|
342
|
+
showLabel={false}
|
|
343
|
+
label={t('advancedNestedSearch')}
|
|
344
|
+
checked={advancedNestedSearch}
|
|
345
|
+
onChange={(event) => handleToggleAdvancedNestedSearch(event.target.checked)}
|
|
346
|
+
compressed
|
|
347
|
+
/>
|
|
348
|
+
</EuiFormRow>
|
|
326
349
|
<EuiFormRow label={t('retrieval')} display="columnCompressed">
|
|
327
350
|
<EuiSelect
|
|
328
351
|
options={[
|
|
@@ -340,7 +363,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
340
363
|
<>
|
|
341
364
|
<EuiSpacer size="m" />
|
|
342
365
|
<EuiButton isLoading={exportDataIsLoading} onClick={() => onExportData()} fullWidth>
|
|
343
|
-
{t('export')}
|
|
366
|
+
{totalItems ? t('exportRows', { numberOfRows: totalItems }) : t('export')}
|
|
344
367
|
</EuiButton>
|
|
345
368
|
</>
|
|
346
369
|
)}
|
|
@@ -349,13 +372,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
349
372
|
/>
|
|
350
373
|
)}
|
|
351
374
|
|
|
352
|
-
{showInformationModal && (
|
|
353
|
-
<WfoInformationModal title={t('searchModalTitle')} onClose={() => setShowInformationModal(false)}>
|
|
354
|
-
<EuiText>
|
|
355
|
-
<p>TODO: Info about searching</p>
|
|
356
|
-
</EuiText>
|
|
357
|
-
</WfoInformationModal>
|
|
358
|
-
)}
|
|
375
|
+
{showInformationModal && <WfoSearchHelpModal onClose={() => setShowInformationModal(false)} />}
|
|
359
376
|
|
|
360
377
|
{rowDetailData && (
|
|
361
378
|
<WfoInformationModal title={'TODO: Information modal title'} onClose={() => setRowDetailModalData(undefined)}>
|
|
@@ -10,6 +10,7 @@ import { EuiButtonGroup, EuiDatePicker, EuiFieldNumber, EuiFieldText } from '@el
|
|
|
10
10
|
import { WfoRangeEditor } from '@/components/WfoTable/WfoStructuredSearchTable/WfoRangeEditor';
|
|
11
11
|
import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
|
|
12
12
|
import { useWithOrchestratorTheme } from '@/hooks';
|
|
13
|
+
import type { WfoQueryBuilderContext } from '@/types';
|
|
13
14
|
|
|
14
15
|
export interface EditorInputFieldProps<T = string> {
|
|
15
16
|
handleOnChange: ValueEditorProps['handleOnChange'];
|
|
@@ -176,7 +177,8 @@ export const WfoValueEditor = ({
|
|
|
176
177
|
return TextEditor;
|
|
177
178
|
};
|
|
178
179
|
|
|
179
|
-
const
|
|
180
|
+
const queryBuilderContext: WfoQueryBuilderContext | undefined = context;
|
|
181
|
+
const fieldPathInfoMap = queryBuilderContext?.fieldPathInfoMap;
|
|
180
182
|
|
|
181
183
|
const fieldInfo = fieldPathInfoMap && fieldPathInfoMap.has(fieldName) ? fieldPathInfoMap.get(fieldName) : undefined;
|
|
182
184
|
const uiFieldType = fieldInfo?.ui_types?.[0] || UiFieldType.text;
|
|
@@ -198,7 +200,7 @@ export const WfoValueEditor = ({
|
|
|
198
200
|
// the boolean buttons means "select" — its click fires only after this keydown, so
|
|
199
201
|
// searching there would use the pre-toggle value.
|
|
200
202
|
if (!(event.target instanceof HTMLInputElement)) return;
|
|
201
|
-
|
|
203
|
+
queryBuilderContext?.onValueEditorEnter();
|
|
202
204
|
};
|
|
203
205
|
|
|
204
206
|
// react-querybuilder delivers the standard `rule-value` class via this prop; the wrapper
|