@orchestrator-ui/orchestrator-ui-components 8.4.5 → 8.6.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 +10 -14
- package/.turbo/turbo-lint.log +2 -4
- package/.turbo/turbo-test.log +7 -9
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +1968 -630
- package/dist/index.js +4389 -2362
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/WfoAutoExpandableTextArea/WfoAutoExpandableTextArea.tsx +54 -0
- package/src/components/WfoAutoExpandableTextArea/index.ts +1 -0
- package/src/components/WfoAutoExpandableTextArea/styles.ts +18 -0
- package/src/components/WfoPageTemplate/paths.ts +1 -0
- package/src/components/WfoPydanticForm/WfoPydanticForm.tsx +23 -1
- package/src/components/WfoSearchBar/WfoSearchField.tsx +0 -1
- package/src/components/WfoSearchPage/WfoFilterGroup/WfoFilterGroup.tsx +5 -5
- package/src/components/WfoSearchPage/WfoSearch/WfoSearch.tsx +12 -5
- package/src/components/WfoSearchPage/constants.ts +0 -4
- package/src/components/WfoSearchPage/utils.ts +75 -20
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionExpandableMenuItem.tsx +36 -18
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem.tsx +38 -2
- package/src/components/WfoSubscription/utils/index.ts +3 -0
- package/src/components/WfoSubscription/utils/utils.spec.ts +39 -4
- package/src/components/WfoSubscription/utils/utils.ts +10 -4
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoAddGroupAction.tsx +19 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoAddRuleAction.tsx +23 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector.tsx +33 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoExpandingSearchRow.tsx +38 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +138 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +198 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoInlineCombinator.tsx +17 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoOperatorSelector.tsx +33 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRangeEditor.tsx +64 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRemoveGroupAction.tsx +21 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRemoveRuleAction.tsx +20 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRule.tsx +16 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRuleGroup.tsx +76 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoSearchFieldWithActions.tsx +56 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +326 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +159 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/index.ts +2 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +182 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +45 -0
- package/src/components/WfoTable/WfoTable/WfoDragHandler.tsx +4 -2
- package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.tsx +9 -0
- package/src/components/WfoTable/index.ts +2 -6
- package/src/components/WfoTitleWithWebsocketBadge/WfoTitleWithWebsocketBadge.tsx +14 -6
- package/src/components/WfoWorkflowSteps/WfoStep/WfoStep.tsx +1 -0
- package/src/components/WfoWorkflowSteps/WfoStepList/WfoStepList.tsx +3 -0
- package/src/components/WfoWorkflowSteps/WfoWorkflowStepList/WfoWorkflowStepList.tsx +32 -12
- package/src/components/WfoWorkflowUserGuide/WfoPageWithUserGuide.tsx +38 -0
- package/src/components/WfoWorkflowUserGuide/WfoWorkflowGuideExpandablePanel.tsx +151 -0
- package/src/components/WfoWorkflowUserGuide/index.ts +2 -0
- package/src/components/WfoWorkflowUserGuide/styles.ts +87 -0
- package/src/components/index.ts +2 -0
- package/src/configuration/version.ts +1 -1
- package/src/contexts/OrchestratorConfigContext.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/usePathAutoComplete.ts +3 -4
- package/src/hooks/useSearch.ts +20 -15
- package/src/hooks/useSearchPagination.ts +11 -8
- package/src/hooks/useUrlParams.ts +3 -5
- package/src/icons/WfoTrashFilled.tsx +13 -0
- package/src/icons/WfoXMarkSmall.tsx +2 -2
- package/src/icons/index.ts +1 -0
- package/src/messages/en-GB.json +95 -8
- package/src/messages/nl-NL.json +24 -5
- package/src/pages/WfoSearchPocPage.tsx +528 -0
- package/src/pages/index.ts +1 -0
- package/src/pages/processes/WfoProcessDetail.tsx +10 -10
- package/src/pages/processes/WfoProcessDetailPage.tsx +1 -0
- package/src/pages/processes/WfoStartProcessPage.tsx +15 -19
- package/src/rtk/api.ts +1 -0
- package/src/rtk/endpoints/index.ts +1 -0
- package/src/rtk/endpoints/search.ts +45 -10
- package/src/rtk/endpoints/workflowGuides.ts +25 -0
- package/src/types/search.ts +24 -5
- package/src/types/types.ts +10 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
SortOrder,
|
|
11
11
|
SubscriptionAction,
|
|
12
12
|
SubscriptionDetailProcess,
|
|
13
|
+
SubscriptionRelation,
|
|
13
14
|
WorkflowTarget,
|
|
14
15
|
} from '@/types';
|
|
15
16
|
|
|
@@ -40,16 +41,21 @@ export const getProductBlockTitle = (instanceValues: FieldValue[]): string | num
|
|
|
40
41
|
return title;
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
const toLabel = (item: string | SubscriptionRelation): string =>
|
|
45
|
+
typeof item === 'object' ? (item.subscription_description ?? item.subscription_id) : item;
|
|
46
|
+
|
|
47
|
+
export const flattenSubscriptionActionProps = (action?: SubscriptionAction | null): TranslationValues => {
|
|
48
|
+
if (!action) return {};
|
|
49
|
+
|
|
44
50
|
const flatObject: TranslationValues = {};
|
|
45
51
|
for (const [key, value] of Object.entries(action)) {
|
|
46
52
|
if (Array.isArray(value)) {
|
|
47
|
-
flatObject[key] = value.join(', ');
|
|
48
|
-
} else {
|
|
53
|
+
flatObject[key] = value.map(toLabel).join(', ');
|
|
54
|
+
} else if (typeof value !== 'object' || value === null) {
|
|
49
55
|
flatObject[key] = value;
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
|
-
return
|
|
58
|
+
return flatObject;
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
export const getWorkflowTargetColor = (workflowTarget: WorkflowTarget, theme: EuiThemeComputed) => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ActionProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
|
|
6
|
+
import { EuiFlexItem } from '@elastic/eui';
|
|
7
|
+
|
|
8
|
+
import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
|
|
9
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
10
|
+
|
|
11
|
+
export const WfoAddGroupAction = ({ disabled, handleOnClick }: ActionProps) => {
|
|
12
|
+
const t = useTranslations('search.page');
|
|
13
|
+
const { addGroupStyles, addRulePlusStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
14
|
+
return disabled ? null : (
|
|
15
|
+
<EuiFlexItem grow={true} css={addGroupStyles} onClick={() => handleOnClick()}>
|
|
16
|
+
<span css={addRulePlusStyles}>+</span> {t('addGroup')}
|
|
17
|
+
</EuiFlexItem>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ActionProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
|
|
6
|
+
import { EuiFlexItem } from '@elastic/eui';
|
|
7
|
+
|
|
8
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
9
|
+
|
|
10
|
+
import { getWfoStructuredSearchTableStyles } from './styles';
|
|
11
|
+
|
|
12
|
+
export const WfoAddRuleAction = (props: ActionProps) => {
|
|
13
|
+
const { addRulePlusStyles, addRuleContainerStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
14
|
+
|
|
15
|
+
const t = useTranslations('search.page');
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<EuiFlexItem grow={false} onClick={() => props.handleOnClick()} css={addRuleContainerStyles}>
|
|
19
|
+
<span css={addRulePlusStyles}>+</span>
|
|
20
|
+
{t('addRule')}
|
|
21
|
+
</EuiFlexItem>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { CombinatorSelectorProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { EuiButtonGroup } from '@elastic/eui';
|
|
5
|
+
|
|
6
|
+
import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
|
|
7
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
8
|
+
|
|
9
|
+
export const WfoCombinatorSelector = (props: CombinatorSelectorProps) => {
|
|
10
|
+
const { buttonGroupStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
11
|
+
|
|
12
|
+
if (props.rules.length < 2) return null;
|
|
13
|
+
|
|
14
|
+
const options = props.options.map((option) => ({
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
16
|
+
// @ts-ignore This seems to be a typing error in the react-querybuilde library. Option.name does exist
|
|
17
|
+
id: option.name,
|
|
18
|
+
label: option.label,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<EuiButtonGroup
|
|
23
|
+
css={buttonGroupStyles}
|
|
24
|
+
legend="Combinator"
|
|
25
|
+
type="single"
|
|
26
|
+
options={options}
|
|
27
|
+
idSelected={props.value || options[0].id}
|
|
28
|
+
onChange={(id) => props.handleOnChange(id)}
|
|
29
|
+
buttonSize="m"
|
|
30
|
+
color="primary"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
4
|
+
import type { MatchingField } from '@/types';
|
|
5
|
+
|
|
6
|
+
import { getWfoStructuredSearchTableStyles } from './styles';
|
|
7
|
+
|
|
8
|
+
interface WfoExpandingSearchRowProps {
|
|
9
|
+
score?: number;
|
|
10
|
+
perfectMatch?: number;
|
|
11
|
+
matchingField?: MatchingField | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const WfoExpandingSearchRow = ({ score, matchingField }: WfoExpandingSearchRowProps) => {
|
|
15
|
+
const { expandingSearchRowStyles, expandingRowBodyStyles } = useWithOrchestratorTheme(
|
|
16
|
+
getWfoStructuredSearchTableStyles,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (!matchingField) return null;
|
|
20
|
+
|
|
21
|
+
const { path, text } = matchingField;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<tr>
|
|
25
|
+
<td colSpan={999} css={expandingSearchRowStyles}>
|
|
26
|
+
<div css={expandingRowBodyStyles}>
|
|
27
|
+
<div>
|
|
28
|
+
<span>{path}</span> {text}
|
|
29
|
+
</div>
|
|
30
|
+
<span>•</span>
|
|
31
|
+
<div>
|
|
32
|
+
<span>score</span> {score !== undefined && `${(score * 100).toFixed(1)}%`}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { FieldSelectorProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
|
|
6
|
+
import type { EuiComboBoxOptionOption } from '@elastic/eui';
|
|
7
|
+
import { EuiComboBox } from '@elastic/eui';
|
|
8
|
+
|
|
9
|
+
import { usePathAutocomplete } from '@/hooks';
|
|
10
|
+
import { EntityKind, FieldToOperatorMap, PathInfo } from '@/types';
|
|
11
|
+
|
|
12
|
+
export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: FieldSelectorProps) => {
|
|
13
|
+
const { field } = rule;
|
|
14
|
+
const prefilledFieldOptions: FieldToOperatorMap = context.prefilledFieldOptions;
|
|
15
|
+
const [selectedValue, setSelectedValue] = useState<string>(field);
|
|
16
|
+
const [searchInput, setSearchInput] = useState<HTMLInputElement | null>(null);
|
|
17
|
+
const optionsRef = useRef<EuiComboBoxOptionOption<string>[]>([]);
|
|
18
|
+
const handleFieldSelectionRef = useRef<(selected: EuiComboBoxOptionOption<string>[]) => void>(() => {});
|
|
19
|
+
const t = useTranslations('search.page');
|
|
20
|
+
const getOption = (path: string) => ({
|
|
21
|
+
value: path,
|
|
22
|
+
label: path,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const getOptionsFromPathInfo = (pathInfos: PathInfo[]): EuiComboBoxOptionOption<string>[] => {
|
|
26
|
+
const pathOptions: EuiComboBoxOptionOption<string>[] = [];
|
|
27
|
+
|
|
28
|
+
pathInfos.forEach((pathInfo) => {
|
|
29
|
+
pathOptions.push(getOption(pathInfo.path));
|
|
30
|
+
// Adds more specific paths
|
|
31
|
+
pathInfo.availablePaths?.forEach((path) => {
|
|
32
|
+
pathOptions.push(getOption(path));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
return (
|
|
36
|
+
pathOptions.length > 0 ? pathOptions
|
|
37
|
+
: selectedValue ? [getOption(selectedValue)]
|
|
38
|
+
: []
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
paths,
|
|
44
|
+
loading: isLoading,
|
|
45
|
+
error: errorMessage,
|
|
46
|
+
} = usePathAutocomplete(selectedValue, EntityKind.SUBSCRIPTION);
|
|
47
|
+
|
|
48
|
+
const prefilledOptions: EuiComboBoxOptionOption<string>[] = Array.from(prefilledFieldOptions.keys()).map(getOption);
|
|
49
|
+
const autocompleteOptions = getOptionsFromPathInfo(paths);
|
|
50
|
+
const placeholderOption: EuiComboBoxOptionOption<string> = {
|
|
51
|
+
label: '──────',
|
|
52
|
+
disabled: true,
|
|
53
|
+
};
|
|
54
|
+
const showPlaceholder = prefilledOptions.length > 0 && autocompleteOptions.length > 0;
|
|
55
|
+
const options: EuiComboBoxOptionOption<string>[] = [
|
|
56
|
+
...prefilledOptions,
|
|
57
|
+
...(showPlaceholder ? [placeholderOption] : []),
|
|
58
|
+
...autocompleteOptions,
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const storeFieldOperators = (selectedValue: string) => {
|
|
62
|
+
const matchingPath =
|
|
63
|
+
paths.find((path) => path.path === selectedValue)
|
|
64
|
+
?? paths.find((path) => path.availablePaths?.includes(selectedValue));
|
|
65
|
+
const operators = matchingPath?.operators ?? prefilledFieldOptions.get(selectedValue) ?? [];
|
|
66
|
+
|
|
67
|
+
context?.onFieldSelected?.(selectedValue, operators);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleFieldSelection = (selectedOptions: EuiComboBoxOptionOption<string>[]) => {
|
|
71
|
+
const selectedOption = selectedOptions[0];
|
|
72
|
+
const selectedValue = selectedOption?.value || '';
|
|
73
|
+
setSelectedValue(selectedValue);
|
|
74
|
+
storeFieldOperators(selectedValue);
|
|
75
|
+
|
|
76
|
+
handleOnChange(selectedValue);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// EuiComboBox only auto-selects on Enter/Tab when exactly one option matches the typed
|
|
80
|
+
// text. When several options share the typed prefix, the exact-match option is not picked
|
|
81
|
+
// unless the user arrow-navigates. Patch that with a native keydown listener on the input.
|
|
82
|
+
optionsRef.current = options;
|
|
83
|
+
handleFieldSelectionRef.current = handleFieldSelection;
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!searchInput) return;
|
|
87
|
+
|
|
88
|
+
const autoSelectExactMatchOption = (event: KeyboardEvent) => {
|
|
89
|
+
if (event.key !== 'Enter' && event.key !== 'Tab') return;
|
|
90
|
+
// Let EuiComboBox handle selection when an option is keyboard-highlighted.
|
|
91
|
+
if (searchInput.getAttribute('aria-activedescendant')) return;
|
|
92
|
+
const typed = searchInput.value.trim();
|
|
93
|
+
if (!typed) return;
|
|
94
|
+
const exactMatch = optionsRef.current.find(
|
|
95
|
+
(option) => !option.disabled && option.label.toLowerCase() === typed.toLowerCase(),
|
|
96
|
+
);
|
|
97
|
+
if (!exactMatch) return;
|
|
98
|
+
if (event.key === 'Enter') event.preventDefault();
|
|
99
|
+
handleFieldSelectionRef.current([exactMatch]);
|
|
100
|
+
|
|
101
|
+
// EuiComboBox keeps its own internal `searchValue` and `isListOpen` state. Clearing
|
|
102
|
+
// the value via the React-recognised native setter + input event suppresses EUI's
|
|
103
|
+
// "typed-but-not-selected" warning icon (the `markAsInvalid` branch in combo_box.js).
|
|
104
|
+
// The synthetic Escape closes the dropdown without disturbing focus.
|
|
105
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
|
|
106
|
+
nativeInputValueSetter?.call(searchInput, '');
|
|
107
|
+
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
108
|
+
searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
searchInput.addEventListener('keydown', autoSelectExactMatchOption);
|
|
112
|
+
return () => searchInput.removeEventListener('keydown', autoSelectExactMatchOption);
|
|
113
|
+
}, [searchInput]);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<EuiComboBox
|
|
117
|
+
placeholder={t('searchFieldsPlaceholder')}
|
|
118
|
+
options={options}
|
|
119
|
+
fullWidth={true}
|
|
120
|
+
selectedOptions={options.filter((option) => option.value === selectedValue)}
|
|
121
|
+
onChange={(selectedOptions) => {
|
|
122
|
+
handleFieldSelection(selectedOptions);
|
|
123
|
+
}}
|
|
124
|
+
onSearchChange={(inputValue) => {
|
|
125
|
+
if (inputValue.length > 0) {
|
|
126
|
+
setSelectedValue(inputValue);
|
|
127
|
+
}
|
|
128
|
+
}}
|
|
129
|
+
inputRef={setSearchInput}
|
|
130
|
+
singleSelection={{ asPlainText: true }}
|
|
131
|
+
isLoading={isLoading}
|
|
132
|
+
isClearable
|
|
133
|
+
isInvalid={!!errorMessage}
|
|
134
|
+
isDisabled={disabled}
|
|
135
|
+
rowHeight={30}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { FullOperator, QueryBuilder, type RuleGroupType, generateID } from 'react-querybuilder';
|
|
3
|
+
import 'react-querybuilder/dist/query-builder.css';
|
|
4
|
+
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
6
|
+
|
|
7
|
+
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|
8
|
+
|
|
9
|
+
import { SearchParams, WfoAutoExpandableTextArea, WfoTextAnchor } from '@/components';
|
|
10
|
+
import { WfoCombinatorSelector } from '@/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector';
|
|
11
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
12
|
+
import { OperatorDisplay } from '@/types';
|
|
13
|
+
import type { FieldToOperatorMap } from '@/types';
|
|
14
|
+
|
|
15
|
+
import { WfoFieldSelector } from './WfoFieldSelector';
|
|
16
|
+
import { WfoInlineCombinator } from './WfoInlineCombinator';
|
|
17
|
+
import { WfoOperatorSelector } from './WfoOperatorSelector';
|
|
18
|
+
import { WfoRemoveRuleAction } from './WfoRemoveRuleAction';
|
|
19
|
+
import { WfoRule } from './WfoRule';
|
|
20
|
+
import { WfoRuleGroup } from './WfoRuleGroup';
|
|
21
|
+
import { WfoValueEditor } from './WfoValueEditor';
|
|
22
|
+
import { getWfoStructuredSearchTableStyles } from './styles';
|
|
23
|
+
|
|
24
|
+
// Maps PathInfo operator names to react-querybuilder's native operator names,
|
|
25
|
+
// which is what parseCEL produces and formatQuery(cel) expects.
|
|
26
|
+
const SEARCH_OPERATOR_TO_RQB_OPERATOR_MAP: Record<string, string> = {
|
|
27
|
+
eq: '=',
|
|
28
|
+
neq: '!=',
|
|
29
|
+
lt: '<',
|
|
30
|
+
lte: '<=',
|
|
31
|
+
gt: '>',
|
|
32
|
+
gte: '>=',
|
|
33
|
+
between: 'between',
|
|
34
|
+
like: 'contains',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const OPERATOR_MAP: Record<string, OperatorDisplay> = {
|
|
38
|
+
eq: { symbol: '=', description: 'equals' },
|
|
39
|
+
neq: { symbol: '≠', description: 'not equals' },
|
|
40
|
+
lt: { symbol: '<', description: 'less than' },
|
|
41
|
+
lte: { symbol: '≤', description: 'less than or equal to' },
|
|
42
|
+
gt: { symbol: '>', description: 'greater than' },
|
|
43
|
+
gte: { symbol: '≥', description: 'greater than or equal to' },
|
|
44
|
+
between: { symbol: '⟷', description: 'between (range)' },
|
|
45
|
+
has_component: { symbol: '✓', description: 'has component' },
|
|
46
|
+
not_has_component: { symbol: '✗', description: 'does not have component' },
|
|
47
|
+
like: { symbol: '∋', description: 'contains' },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/* TODO: Add the missing operators
|
|
51
|
+
['has_component', 'not_has_component'];
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
interface WfoFilterBuilderProps {
|
|
55
|
+
filterString?: string;
|
|
56
|
+
onUpdateFilterString: (filterString: string) => void;
|
|
57
|
+
isValidFilterString?: boolean;
|
|
58
|
+
queryBuilderRuleGroup?: RuleGroupType;
|
|
59
|
+
onUpdateQueryBuilder: (ruleGroup: RuleGroupType | false) => void;
|
|
60
|
+
handleSearch: (searchParams?: SearchParams) => void;
|
|
61
|
+
isFilterBuilderVisible: boolean;
|
|
62
|
+
onToggleFilterBuilder: (isVisible: boolean) => void;
|
|
63
|
+
prefilledFieldOptions: FieldToOperatorMap;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const initialRuleGroup: RuleGroupType = {
|
|
67
|
+
id: 'root',
|
|
68
|
+
rules: [{ id: 'rule-0', field: '~', operator: '=', value: '' }],
|
|
69
|
+
combinator: 'and',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const onAddGroupHandler = (ruleGroup: RuleGroupType): RuleGroupType => {
|
|
73
|
+
const [firstRule] = ruleGroup.rules;
|
|
74
|
+
return firstRule ? { ...ruleGroup, rules: [...ruleGroup.rules, { ...firstRule, id: generateID() }] } : ruleGroup;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const WfoFilterBuilder = ({
|
|
78
|
+
filterString,
|
|
79
|
+
onUpdateFilterString,
|
|
80
|
+
isValidFilterString = true,
|
|
81
|
+
queryBuilderRuleGroup = initialRuleGroup,
|
|
82
|
+
onUpdateQueryBuilder,
|
|
83
|
+
handleSearch,
|
|
84
|
+
prefilledFieldOptions,
|
|
85
|
+
isFilterBuilderVisible,
|
|
86
|
+
onToggleFilterBuilder,
|
|
87
|
+
}: WfoFilterBuilderProps) => {
|
|
88
|
+
const mapOperatorsToRQBOperatorOptions = (operators?: string[]): FullOperator[] => {
|
|
89
|
+
return (operators ?? []).map((operator) => {
|
|
90
|
+
const { symbol, description } = OPERATOR_MAP[operator] || { symbol: operator, description: operator };
|
|
91
|
+
const rqbOperator = SEARCH_OPERATOR_TO_RQB_OPERATOR_MAP[operator] ?? operator;
|
|
92
|
+
return { name: rqbOperator, label: `${symbol} ${description}`, value: rqbOperator };
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const t = useTranslations('common');
|
|
97
|
+
const { queryBuilderContainerStyles, toggleButtonStyles } = useWithOrchestratorTheme(
|
|
98
|
+
getWfoStructuredSearchTableStyles,
|
|
99
|
+
);
|
|
100
|
+
const [fieldToOperatorMap, setFieldToOperatorMap] = useState<FieldToOperatorMap>(prefilledFieldOptions);
|
|
101
|
+
|
|
102
|
+
const handleFieldSelected = (field: string, operators: string[]) => {
|
|
103
|
+
setFieldToOperatorMap((previousMap) => {
|
|
104
|
+
return new Map(previousMap).set(field, operators);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<EuiFlexGroup css={isFilterBuilderVisible ? queryBuilderContainerStyles : undefined}>
|
|
110
|
+
{(isFilterBuilderVisible && (
|
|
111
|
+
<EuiFlexGroup direction={'column'}>
|
|
112
|
+
<EuiFlexItem>
|
|
113
|
+
<QueryBuilder
|
|
114
|
+
query={queryBuilderRuleGroup}
|
|
115
|
+
enableMountQueryChange={false}
|
|
116
|
+
onQueryChange={(ruleGroup: RuleGroupType) => {
|
|
117
|
+
onUpdateQueryBuilder(ruleGroup);
|
|
118
|
+
}}
|
|
119
|
+
context={{ onFieldSelected: handleFieldSelected, prefilledFieldOptions }}
|
|
120
|
+
getOperators={(field) => {
|
|
121
|
+
const operators = fieldToOperatorMap.get(field);
|
|
122
|
+
return mapOperatorsToRQBOperatorOptions(operators);
|
|
123
|
+
}}
|
|
124
|
+
controlElements={{
|
|
125
|
+
fieldSelector: WfoFieldSelector,
|
|
126
|
+
operatorSelector: WfoOperatorSelector,
|
|
127
|
+
valueEditor: WfoValueEditor,
|
|
128
|
+
ruleGroup: WfoRuleGroup,
|
|
129
|
+
rule: WfoRule,
|
|
130
|
+
combinatorSelector: WfoCombinatorSelector,
|
|
131
|
+
inlineCombinator: WfoInlineCombinator,
|
|
132
|
+
addRuleAction: null,
|
|
133
|
+
addGroupAction: null,
|
|
134
|
+
removeGroupAction: null,
|
|
135
|
+
removeRuleAction: WfoRemoveRuleAction,
|
|
136
|
+
}}
|
|
137
|
+
addRuleToNewGroups
|
|
138
|
+
onAddGroup={onAddGroupHandler}
|
|
139
|
+
maxLevels={5}
|
|
140
|
+
showCombinatorsBetweenRules
|
|
141
|
+
/>
|
|
142
|
+
</EuiFlexItem>
|
|
143
|
+
<EuiFlexItem>
|
|
144
|
+
<WfoAutoExpandableTextArea
|
|
145
|
+
id={'searchbox-textarea'}
|
|
146
|
+
value={filterString ?? ''}
|
|
147
|
+
onChange={(e) => {
|
|
148
|
+
const filterString = e.target.value;
|
|
149
|
+
onUpdateFilterString(filterString);
|
|
150
|
+
}}
|
|
151
|
+
isInvalid={!isValidFilterString}
|
|
152
|
+
/>
|
|
153
|
+
</EuiFlexItem>
|
|
154
|
+
|
|
155
|
+
<EuiFlexGroup direction={'rowReverse'} alignItems={'center'}>
|
|
156
|
+
<EuiButton
|
|
157
|
+
onClick={() => {
|
|
158
|
+
handleSearch();
|
|
159
|
+
}}
|
|
160
|
+
id={'button-apply-filter'}
|
|
161
|
+
data-test-id={'button-apply-filter'}
|
|
162
|
+
fill
|
|
163
|
+
type="submit"
|
|
164
|
+
aria-label={t('applyFilter')}
|
|
165
|
+
disabled={!isValidFilterString}
|
|
166
|
+
>
|
|
167
|
+
{t('applyFilter')}
|
|
168
|
+
</EuiButton>
|
|
169
|
+
<WfoTextAnchor
|
|
170
|
+
text={t('removeFilter')}
|
|
171
|
+
onClick={() => {
|
|
172
|
+
onUpdateQueryBuilder(false);
|
|
173
|
+
// we call with ruleGroup: false explictly to
|
|
174
|
+
// avoid state not having caught up yet when searching
|
|
175
|
+
handleSearch({ ruleGroup: false });
|
|
176
|
+
onToggleFilterBuilder(false);
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
179
|
+
</EuiFlexGroup>
|
|
180
|
+
</EuiFlexGroup>
|
|
181
|
+
)) || (
|
|
182
|
+
<EuiButton
|
|
183
|
+
css={toggleButtonStyles}
|
|
184
|
+
onClick={() => onToggleFilterBuilder(true)}
|
|
185
|
+
id={'button-toggle-filter-builder'}
|
|
186
|
+
data-test-id={'button-toggle-filter-builder'}
|
|
187
|
+
fill
|
|
188
|
+
type="submit"
|
|
189
|
+
iconType="filter"
|
|
190
|
+
iconSide="left"
|
|
191
|
+
aria-label={t('createFilter')}
|
|
192
|
+
>
|
|
193
|
+
{t('createFilter')}
|
|
194
|
+
</EuiButton>
|
|
195
|
+
)}
|
|
196
|
+
</EuiFlexGroup>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { CombinatorSelectorProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
|
|
5
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
6
|
+
|
|
7
|
+
import { WfoCombinatorSelector } from './WfoCombinatorSelector';
|
|
8
|
+
|
|
9
|
+
export const WfoInlineCombinator = (props: CombinatorSelectorProps) => {
|
|
10
|
+
const { inlineCombinatorStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div css={inlineCombinatorStyles}>
|
|
14
|
+
<WfoCombinatorSelector {...props} />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { useEffect, useMemo } from 'react';
|
|
2
|
+
import type { FullOperator, OperatorSelectorProps, OptionGroup } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { EuiSelect } from '@elastic/eui';
|
|
5
|
+
|
|
6
|
+
const isOptionGroup = (operator: FullOperator | OptionGroup<FullOperator>): operator is OptionGroup<FullOperator> =>
|
|
7
|
+
'options' in operator;
|
|
8
|
+
|
|
9
|
+
export const WfoOperatorSelector = (props: OperatorSelectorProps) => {
|
|
10
|
+
const flatOptions = (props.options as Array<FullOperator | OptionGroup<FullOperator>>).flatMap((option) =>
|
|
11
|
+
isOptionGroup(option) ? option.options : [option],
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const selectOptions = useMemo(
|
|
15
|
+
() => flatOptions.map((option) => ({ value: option.name, text: option.label })),
|
|
16
|
+
[flatOptions],
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (props.options && !props.value && selectOptions.length > 0) {
|
|
21
|
+
props.handleOnChange(selectOptions[0].value);
|
|
22
|
+
}
|
|
23
|
+
}, [props, props.options, props.value, selectOptions]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<EuiSelect
|
|
27
|
+
options={selectOptions}
|
|
28
|
+
value={props.value}
|
|
29
|
+
onChange={(e) => props.handleOnChange(e.target.value)}
|
|
30
|
+
disabled={props.disabled}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { EuiFlexGroup } from '@elastic/eui';
|
|
4
|
+
|
|
5
|
+
export interface WfoRangeElementProps {
|
|
6
|
+
handleOnChange: (value: string | number | undefined, rangeIndex?: number) => void;
|
|
7
|
+
value: string;
|
|
8
|
+
operator?: string;
|
|
9
|
+
rangeIndex?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface WfoRangeEditorProps {
|
|
13
|
+
handleOnChange: (value: string | number | undefined, rangeIndex?: number) => void;
|
|
14
|
+
operator: string;
|
|
15
|
+
value: string;
|
|
16
|
+
Element: React.ComponentType<WfoRangeElementProps>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const WfoRangeEditor = ({ handleOnChange, operator, Element, value: currentValue }: WfoRangeEditorProps) => {
|
|
20
|
+
const [currentOperator, setCurrentOperator] = useState(operator);
|
|
21
|
+
const startValue = currentValue ? currentValue?.toString().split(',') : [];
|
|
22
|
+
const [value, setValue] = useState<string[]>(startValue);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (operator !== currentOperator && (operator === 'between' || currentOperator === 'between')) {
|
|
26
|
+
setValue([]);
|
|
27
|
+
handleOnChange('');
|
|
28
|
+
setCurrentOperator(operator);
|
|
29
|
+
}
|
|
30
|
+
}, [currentOperator, handleOnChange, operator]);
|
|
31
|
+
|
|
32
|
+
const handleChange = (value: string | number | undefined, rangeIndex: number = 0) => {
|
|
33
|
+
if (operator === 'between') {
|
|
34
|
+
setValue((currentDates) => {
|
|
35
|
+
// remove value if set to undefined
|
|
36
|
+
if (value === null) {
|
|
37
|
+
return currentDates.filter((_, index) => index !== rangeIndex);
|
|
38
|
+
}
|
|
39
|
+
// add value at supplied index
|
|
40
|
+
currentDates[rangeIndex] = value as string;
|
|
41
|
+
|
|
42
|
+
// call the parent if 2 values are present
|
|
43
|
+
if (currentDates.length === 2) {
|
|
44
|
+
handleOnChange(`${currentDates[0]},${currentDates[1]}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return currentDates;
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
handleOnChange(value);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (operator === 'between') {
|
|
55
|
+
return (
|
|
56
|
+
<EuiFlexGroup direction="row" gutterSize="s">
|
|
57
|
+
<Element handleOnChange={handleChange} rangeIndex={0} value={value[0]} />
|
|
58
|
+
<Element handleOnChange={handleChange} rangeIndex={1} value={value[1]} />
|
|
59
|
+
</EuiFlexGroup>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return <Element handleOnChange={handleOnChange} value={value[0]} />;
|
|
64
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useTranslations } from 'next-intl';
|
|
4
|
+
|
|
5
|
+
import { EuiFlexItem } from '@elastic/eui';
|
|
6
|
+
|
|
7
|
+
import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
|
|
8
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
9
|
+
import { useOrchestratorTheme } from '@/hooks';
|
|
10
|
+
import { WfoTrashFilled } from '@/icons';
|
|
11
|
+
|
|
12
|
+
export const WfoRemoveGroupAction = ({ onClick }: { onClick: () => void }) => {
|
|
13
|
+
const { removeGroupActionStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
14
|
+
const { theme } = useOrchestratorTheme();
|
|
15
|
+
const t = useTranslations('search.page');
|
|
16
|
+
return (
|
|
17
|
+
<EuiFlexItem grow={0} css={removeGroupActionStyles} onClick={() => onClick()}>
|
|
18
|
+
<WfoTrashFilled color={theme.colors.textPrimary} aria-label={t('removeGroup')} />
|
|
19
|
+
</EuiFlexItem>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ActionProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
|
|
6
|
+
import { EuiFlexItem } from '@elastic/eui';
|
|
7
|
+
|
|
8
|
+
import { useOrchestratorTheme } from '@/hooks';
|
|
9
|
+
import { WfoTrashFilled } from '@/icons';
|
|
10
|
+
|
|
11
|
+
export const WfoRemoveRuleAction = (props: ActionProps) => {
|
|
12
|
+
const { theme } = useOrchestratorTheme();
|
|
13
|
+
|
|
14
|
+
const t = useTranslations('search.page');
|
|
15
|
+
return (
|
|
16
|
+
<EuiFlexItem onClick={props.handleOnClick} css={{ cursor: 'pointer', paddingRight: theme.size.s }}>
|
|
17
|
+
<WfoTrashFilled color={theme.colors.textPrimary} aria-label={t('removeRule')} />
|
|
18
|
+
</EuiFlexItem>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { RuleProps } from 'react-querybuilder';
|
|
3
|
+
import { Rule } from 'react-querybuilder';
|
|
4
|
+
|
|
5
|
+
import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
|
|
6
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
7
|
+
|
|
8
|
+
export const WfoRule = (props: RuleProps) => {
|
|
9
|
+
const { ruleContainerStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div css={ruleContainerStyles}>
|
|
13
|
+
<Rule {...props} />
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
};
|