@orchestrator-ui/orchestrator-ui-components 8.6.0 → 8.7.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 +16 -13
- package/CHANGELOG.md +11 -0
- package/dist/index.d.ts +106 -8
- package/dist/index.js +2540 -2078
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/components/WfoPydanticForm/fields/WfoCron.spec.ts +88 -0
- package/src/components/WfoPydanticForm/fields/WfoCron.tsx +233 -0
- package/src/components/WfoPydanticForm/fields/index.ts +1 -0
- package/src/components/WfoPydanticForm/fields/styles.ts +92 -0
- package/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResultItem.tsx +2 -1
- package/src/components/WfoSearchPage/utils.ts +37 -1
- package/src/components/WfoSubscription/WfoInUseByRelations.tsx +6 -2
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionExpandableMenuItem.tsx +19 -14
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +13 -1
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem.tsx +3 -0
- package/src/components/WfoSubscription/WfoSubscriptionProductBlock/WfoSubscriptionProductBlock.tsx +1 -1
- package/src/components/WfoSubscriptionsList/WfoSubscriptionsList.tsx +5 -0
- package/src/components/WfoSubscriptionsList/subscriptionResultMappers.ts +3 -1
- package/src/components/WfoTable/WfoAdvancedTable/WfoAdvancedTable.tsx +2 -12
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoExpandingSearchRow.tsx +21 -14
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +27 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +97 -91
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoOperatorSelector.tsx +32 -7
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRuleGroup.tsx +5 -4
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +32 -30
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +41 -11
- package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +22 -5
- package/src/components/WfoTable/WfoTable/WfoTable.tsx +1 -0
- package/src/components/WfoTable/WfoTable/WfoTableDataRows.tsx +16 -4
- package/src/components/WfoTable/WfoTable/styles.ts +8 -0
- package/src/components/WfoTable/WfoTable/utils.spec.ts +62 -1
- package/src/components/WfoTable/WfoTable/utils.ts +4 -1
- package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.spec.ts +45 -0
- package/src/components/WfoTable/WfoTableSettingsModal/index.ts +1 -0
- package/src/components/WfoTable/WfoTableSettingsModal/utils.ts +17 -0
- package/src/configuration/version.ts +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useGetPydanticFormsConfig.tsx +12 -0
- package/src/hooks/useLanguageCode.ts +11 -0
- package/src/messages/en-GB.json +19 -0
- package/src/messages/nl-NL.json +19 -0
- package/src/pages/WfoSearchPocPage.tsx +14 -5
- package/src/rtk/endpoints/subscriptionList.ts +1 -0
- package/src/theme/baseStyles/formFieldsBaseStyle.ts +0 -1
- package/src/types/search.ts +1 -1
- package/src/types/types.ts +1 -1
- package/src/utils/getDefaultTableConfig.ts +6 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/integer.spec.ts +20 -0
- package/src/utils/integer.ts +3 -0
package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem.tsx
CHANGED
|
@@ -19,6 +19,7 @@ interface MenuItemProps {
|
|
|
19
19
|
setPopover: (isOpen: boolean) => void;
|
|
20
20
|
onClick: () => void;
|
|
21
21
|
isLoading?: boolean;
|
|
22
|
+
subscriptionPath?: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export const WfoSubscriptionActionsMenuItem: FC<MenuItemProps> = ({
|
|
@@ -27,6 +28,7 @@ export const WfoSubscriptionActionsMenuItem: FC<MenuItemProps> = ({
|
|
|
27
28
|
target,
|
|
28
29
|
setPopover,
|
|
29
30
|
isLoading = false,
|
|
31
|
+
subscriptionPath,
|
|
30
32
|
}) => {
|
|
31
33
|
const { linkMenuItemStyle, tooltipMenuItemStyle, disabledIconStyle, iconStyle, secondaryIconStyle } =
|
|
32
34
|
useWithOrchestratorTheme(getSubscriptionActionStyles);
|
|
@@ -102,6 +104,7 @@ export const WfoSubscriptionActionsMenuItem: FC<MenuItemProps> = ({
|
|
|
102
104
|
<WfoSubscriptionActionExpandableMenuItem
|
|
103
105
|
subscriptionAction={subscriptionAction}
|
|
104
106
|
onClickLockedRelation={() => setPopover(false)}
|
|
107
|
+
subscriptionPath={subscriptionPath}
|
|
105
108
|
>
|
|
106
109
|
{actionItem}
|
|
107
110
|
</WfoSubscriptionActionExpandableMenuItem>
|
package/src/components/WfoSubscription/WfoSubscriptionProductBlock/WfoSubscriptionProductBlock.tsx
CHANGED
|
@@ -147,7 +147,7 @@ export const WfoSubscriptionProductBlock = ({
|
|
|
147
147
|
</td>
|
|
148
148
|
<td>
|
|
149
149
|
{(inUseByRelations.length === 0 && 'None') || (
|
|
150
|
-
<WfoInUseByRelations inUseByRelations={inUseByRelations} />
|
|
150
|
+
<WfoInUseByRelations inUseByRelations={inUseByRelations} subscriptionPath={subscriptionPath} />
|
|
151
151
|
)}
|
|
152
152
|
</td>
|
|
153
153
|
</tr>
|
|
@@ -122,6 +122,11 @@ export const WfoSubscriptionsList: FC<WfoSubscriptionsListProps> = ({
|
|
|
122
122
|
label: t('tag'),
|
|
123
123
|
width: '100px',
|
|
124
124
|
},
|
|
125
|
+
customerId: {
|
|
126
|
+
columnType: ColumnType.DATA,
|
|
127
|
+
label: t('customerId'),
|
|
128
|
+
width: '100px',
|
|
129
|
+
},
|
|
125
130
|
customerFullname: {
|
|
126
131
|
columnType: ColumnType.DATA,
|
|
127
132
|
label: t('customerFullname'),
|
|
@@ -12,6 +12,7 @@ export type SubscriptionListItem = Pick<
|
|
|
12
12
|
tag: string | null;
|
|
13
13
|
customerFullname: string;
|
|
14
14
|
customerShortcode: string;
|
|
15
|
+
customerId: string;
|
|
15
16
|
metadata: object | null;
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -27,7 +28,7 @@ export const mapGraphQlSubscriptionsResultToSubscriptionListItems = (
|
|
|
27
28
|
subscription;
|
|
28
29
|
|
|
29
30
|
const { name: productName, tag } = product;
|
|
30
|
-
const { fullname: customerFullname, shortcode: customerShortcode } = customer;
|
|
31
|
+
const { fullname: customerFullname, shortcode: customerShortcode, customerId } = customer;
|
|
31
32
|
|
|
32
33
|
return {
|
|
33
34
|
subscriptionId,
|
|
@@ -41,6 +42,7 @@ export const mapGraphQlSubscriptionsResultToSubscriptionListItems = (
|
|
|
41
42
|
tag,
|
|
42
43
|
customerFullname,
|
|
43
44
|
customerShortcode,
|
|
45
|
+
customerId,
|
|
44
46
|
metadata: Object.keys(metadata).length > 0 ? metadata : null,
|
|
45
47
|
};
|
|
46
48
|
});
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
DEFAULT_PAGE_SIZE,
|
|
9
9
|
DEFAULT_PAGE_SIZES,
|
|
10
10
|
TableColumnKeys,
|
|
11
|
-
TableSettingsColumnConfig,
|
|
12
11
|
TableSettingsConfig,
|
|
13
12
|
TableSettingsModal,
|
|
14
13
|
WfoErrorWithMessage,
|
|
@@ -18,6 +17,7 @@ import {
|
|
|
18
17
|
WfoSearchField,
|
|
19
18
|
WfoToolTip,
|
|
20
19
|
clearTableConfigFromLocalStorage,
|
|
20
|
+
getTableSettingsColumns,
|
|
21
21
|
setTableConfigToLocalStorage,
|
|
22
22
|
} from '@/components';
|
|
23
23
|
import { getRowDetailData } from '@/components/WfoTable/WfoAdvancedTable/getRowDetailData';
|
|
@@ -96,17 +96,7 @@ export const WfoAdvancedTable = <T extends object>({
|
|
|
96
96
|
...tableColumnConfig,
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
const tableSettingsColumns
|
|
100
|
-
([key, { label }]): TableSettingsColumnConfig<T> => {
|
|
101
|
-
const field = key as keyof T;
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
field,
|
|
105
|
-
name: label,
|
|
106
|
-
isVisible: hiddenColumns.indexOf(field) === -1,
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
);
|
|
99
|
+
const tableSettingsColumns = getTableSettingsColumns(tableColumnConfig, hiddenColumns);
|
|
110
100
|
|
|
111
101
|
const rowDetailData: WfoKeyValueTableDataType[] | undefined =
|
|
112
102
|
selectedDataForDetailModal && getRowDetailData(selectedDataForDetailModal, tableColumnConfig);
|
|
@@ -1,36 +1,43 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
+
import { WfoHighlightedText } from '@/components/WfoSearchPage/WfoSearchResults/WfoHighlightedText';
|
|
3
4
|
import { useWithOrchestratorTheme } from '@/hooks';
|
|
4
5
|
import type { MatchingField } from '@/types';
|
|
6
|
+
import { toPercentage } from '@/utils';
|
|
5
7
|
|
|
6
8
|
import { getWfoStructuredSearchTableStyles } from './styles';
|
|
7
9
|
|
|
8
10
|
interface WfoExpandingSearchRowProps {
|
|
9
11
|
score?: number;
|
|
10
12
|
perfectMatch?: number;
|
|
11
|
-
|
|
13
|
+
matchingFields?: MatchingField[] | null;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
export const WfoExpandingSearchRow = ({ score,
|
|
15
|
-
const { expandingSearchRowStyles, expandingRowBodyStyles } =
|
|
16
|
-
getWfoStructuredSearchTableStyles
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
if (!matchingField) return null;
|
|
16
|
+
export const WfoExpandingSearchRow = ({ score, matchingFields }: WfoExpandingSearchRowProps) => {
|
|
17
|
+
const { expandingSearchRowStyles, expandingRowBodyStyles, hideExpandedRowStyle, dotStyles, expandingRowFieldStyles } =
|
|
18
|
+
useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
if (!matchingFields || matchingFields.length === 0) return null;
|
|
22
21
|
|
|
22
|
+
// Note: The row is shown on hover by the row above is as configured in the table component expandableRow config
|
|
23
23
|
return (
|
|
24
|
-
<tr>
|
|
24
|
+
<tr css={hideExpandedRowStyle}>
|
|
25
25
|
<td colSpan={999} css={expandingSearchRowStyles}>
|
|
26
26
|
<div css={expandingRowBodyStyles}>
|
|
27
27
|
<div>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
{matchingFields.map((field) => {
|
|
29
|
+
return (
|
|
30
|
+
<div key={`${field.path}:${field.text}`} css={expandingRowFieldStyles}>
|
|
31
|
+
<div>
|
|
32
|
+
<span css={dotStyles}>•</span>
|
|
33
|
+
{field.path}: <WfoHighlightedText text={field.text} highlight_indices={field.highlight_indices} />
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
})}
|
|
33
38
|
</div>
|
|
39
|
+
|
|
40
|
+
{score && <div css={dotStyles}>confidence: {toPercentage(score)}</div>}
|
|
34
41
|
</div>
|
|
35
42
|
</td>
|
|
36
43
|
</tr>
|
|
@@ -9,6 +9,27 @@ import { EuiComboBox } from '@elastic/eui';
|
|
|
9
9
|
import { usePathAutocomplete } from '@/hooks';
|
|
10
10
|
import { EntityKind, FieldToOperatorMap, PathInfo } from '@/types';
|
|
11
11
|
|
|
12
|
+
// react-querybuilder applies the `.rule` class to the rule container and `.rule-value` to
|
|
13
|
+
// the value-editor cell (see `standardClassnames` in @react-querybuilder/core). We hop from
|
|
14
|
+
// the EuiComboBox search input up to the enclosing rule and focus the value editor's first
|
|
15
|
+
// focusable child on the next frame, once react-querybuilder has re-rendered the rule with
|
|
16
|
+
// the editor type for the new field.
|
|
17
|
+
const focusValueEditorAfterRender = (searchInput: HTMLInputElement | null) => {
|
|
18
|
+
if (!searchInput) return;
|
|
19
|
+
requestAnimationFrame(() => {
|
|
20
|
+
const valueCell = searchInput.closest('.rule')?.querySelector('.rule-value');
|
|
21
|
+
const focusable = valueCell?.querySelector<HTMLElement>(
|
|
22
|
+
'input:not([disabled]), textarea:not([disabled]), button:not([disabled])',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!focusable) return;
|
|
26
|
+
focusable.focus();
|
|
27
|
+
if (focusable instanceof HTMLInputElement && (focusable.type === 'text' || focusable.type === 'number')) {
|
|
28
|
+
focusable.select();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
12
33
|
export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: FieldSelectorProps) => {
|
|
13
34
|
const { field } = rule;
|
|
14
35
|
const prefilledFieldOptions: FieldToOperatorMap = context.prefilledFieldOptions;
|
|
@@ -74,6 +95,8 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
|
|
|
74
95
|
storeFieldOperators(selectedValue);
|
|
75
96
|
|
|
76
97
|
handleOnChange(selectedValue);
|
|
98
|
+
|
|
99
|
+
if (selectedValue) focusValueEditorAfterRender(searchInput);
|
|
77
100
|
};
|
|
78
101
|
|
|
79
102
|
// EuiComboBox only auto-selects on Enter/Tab when exactly one option matches the typed
|
|
@@ -95,7 +118,10 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
|
|
|
95
118
|
(option) => !option.disabled && option.label.toLowerCase() === typed.toLowerCase(),
|
|
96
119
|
);
|
|
97
120
|
if (!exactMatch) return;
|
|
98
|
-
|
|
121
|
+
// Prevent both Enter (form submit / EUI's no-op fallback) and Tab (native focus
|
|
122
|
+
// shift to the operator selector) — handleFieldSelection moves focus to the value
|
|
123
|
+
// editor itself, and a native Tab in the meantime causes a visible focus flash.
|
|
124
|
+
event.preventDefault();
|
|
99
125
|
handleFieldSelectionRef.current([exactMatch]);
|
|
100
126
|
|
|
101
127
|
// EuiComboBox keeps its own internal `searchValue` and `isListOpen` state. Clearing
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
2
|
import { FullOperator, QueryBuilder, type RuleGroupType, generateID } from 'react-querybuilder';
|
|
3
3
|
import 'react-querybuilder/dist/query-builder.css';
|
|
4
4
|
|
|
@@ -58,7 +58,6 @@ interface WfoFilterBuilderProps {
|
|
|
58
58
|
queryBuilderRuleGroup?: RuleGroupType;
|
|
59
59
|
onUpdateQueryBuilder: (ruleGroup: RuleGroupType | false) => void;
|
|
60
60
|
handleSearch: (searchParams?: SearchParams) => void;
|
|
61
|
-
isFilterBuilderVisible: boolean;
|
|
62
61
|
onToggleFilterBuilder: (isVisible: boolean) => void;
|
|
63
62
|
prefilledFieldOptions: FieldToOperatorMap;
|
|
64
63
|
}
|
|
@@ -82,7 +81,6 @@ export const WfoFilterBuilder = ({
|
|
|
82
81
|
onUpdateQueryBuilder,
|
|
83
82
|
handleSearch,
|
|
84
83
|
prefilledFieldOptions,
|
|
85
|
-
isFilterBuilderVisible,
|
|
86
84
|
onToggleFilterBuilder,
|
|
87
85
|
}: WfoFilterBuilderProps) => {
|
|
88
86
|
const mapOperatorsToRQBOperatorOptions = (operators?: string[]): FullOperator[] => {
|
|
@@ -94,11 +92,20 @@ export const WfoFilterBuilder = ({
|
|
|
94
92
|
};
|
|
95
93
|
|
|
96
94
|
const t = useTranslations('common');
|
|
97
|
-
const { queryBuilderContainerStyles
|
|
98
|
-
getWfoStructuredSearchTableStyles,
|
|
99
|
-
);
|
|
95
|
+
const { queryBuilderContainerStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
100
96
|
const [fieldToOperatorMap, setFieldToOperatorMap] = useState<FieldToOperatorMap>(prefilledFieldOptions);
|
|
101
97
|
|
|
98
|
+
// Enter in a value editor commits its value on blur, and that state update has not
|
|
99
|
+
// flushed yet when the search runs in the same keydown. onQueryChange fires
|
|
100
|
+
// synchronously during the blur, so this ref always holds the freshest rule group,
|
|
101
|
+
// which is passed to handleSearch explicitly (same pattern as the remove-filter link).
|
|
102
|
+
const latestRuleGroupRef = useRef<RuleGroupType | undefined>(queryBuilderRuleGroup);
|
|
103
|
+
latestRuleGroupRef.current = queryBuilderRuleGroup;
|
|
104
|
+
|
|
105
|
+
const handleValueEditorEnter = () => {
|
|
106
|
+
handleSearch({ ruleGroup: latestRuleGroupRef.current });
|
|
107
|
+
};
|
|
108
|
+
|
|
102
109
|
const handleFieldSelected = (field: string, operators: string[]) => {
|
|
103
110
|
setFieldToOperatorMap((previousMap) => {
|
|
104
111
|
return new Map(previousMap).set(field, operators);
|
|
@@ -106,93 +113,92 @@ export const WfoFilterBuilder = ({
|
|
|
106
113
|
};
|
|
107
114
|
|
|
108
115
|
return (
|
|
109
|
-
<EuiFlexGroup css={
|
|
110
|
-
{
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
116
|
+
<EuiFlexGroup css={queryBuilderContainerStyles}>
|
|
117
|
+
<EuiFlexGroup direction={'column'}>
|
|
118
|
+
<EuiFlexItem>
|
|
119
|
+
<QueryBuilder
|
|
120
|
+
query={queryBuilderRuleGroup}
|
|
121
|
+
enableMountQueryChange={false}
|
|
122
|
+
onQueryChange={(ruleGroup: RuleGroupType) => {
|
|
123
|
+
latestRuleGroupRef.current = ruleGroup;
|
|
124
|
+
onUpdateQueryBuilder(ruleGroup);
|
|
125
|
+
}}
|
|
126
|
+
context={{
|
|
127
|
+
onFieldSelected: handleFieldSelected,
|
|
128
|
+
prefilledFieldOptions,
|
|
129
|
+
onValueEditorEnter: handleValueEditorEnter,
|
|
130
|
+
}}
|
|
131
|
+
getOperators={(field) => {
|
|
132
|
+
const operators = fieldToOperatorMap.get(field);
|
|
133
|
+
return mapOperatorsToRQBOperatorOptions(operators);
|
|
134
|
+
}}
|
|
135
|
+
controlElements={{
|
|
136
|
+
fieldSelector: WfoFieldSelector,
|
|
137
|
+
operatorSelector: WfoOperatorSelector,
|
|
138
|
+
valueEditor: WfoValueEditor,
|
|
139
|
+
ruleGroup: WfoRuleGroup,
|
|
140
|
+
rule: WfoRule,
|
|
141
|
+
combinatorSelector: WfoCombinatorSelector,
|
|
142
|
+
inlineCombinator: WfoInlineCombinator,
|
|
143
|
+
addRuleAction: null,
|
|
144
|
+
addGroupAction: null,
|
|
145
|
+
removeGroupAction: null,
|
|
146
|
+
removeRuleAction: WfoRemoveRuleAction,
|
|
147
|
+
}}
|
|
148
|
+
addRuleToNewGroups
|
|
149
|
+
onAddGroup={onAddGroupHandler}
|
|
150
|
+
maxLevels={5}
|
|
151
|
+
showCombinatorsBetweenRules
|
|
152
|
+
resetOnFieldChange={false}
|
|
153
|
+
/>
|
|
154
|
+
</EuiFlexItem>
|
|
155
|
+
<EuiFlexItem>
|
|
156
|
+
<WfoAutoExpandableTextArea
|
|
157
|
+
id={'searchbox-textarea'}
|
|
158
|
+
value={filterString ?? ''}
|
|
159
|
+
onChange={(e) => {
|
|
160
|
+
const filterString = e.target.value;
|
|
161
|
+
onUpdateFilterString(filterString);
|
|
162
|
+
}}
|
|
163
|
+
onKeyDown={(event) => {
|
|
164
|
+
// Enter applies the filter like the Apply button (and like it, does nothing
|
|
165
|
+
// while the filter string is invalid); Shift+Enter inserts a newline.
|
|
166
|
+
if (event.key !== 'Enter' || event.shiftKey) return;
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
if (isValidFilterString) {
|
|
158
169
|
handleSearch();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
170
|
+
}
|
|
171
|
+
}}
|
|
172
|
+
isInvalid={!isValidFilterString}
|
|
173
|
+
/>
|
|
174
|
+
</EuiFlexItem>
|
|
175
|
+
|
|
176
|
+
<EuiFlexGroup direction={'rowReverse'} alignItems={'center'}>
|
|
177
|
+
<EuiButton
|
|
178
|
+
onClick={() => {
|
|
179
|
+
handleSearch();
|
|
180
|
+
}}
|
|
181
|
+
id={'button-apply-filter'}
|
|
182
|
+
data-test-id={'button-apply-filter'}
|
|
183
|
+
fill
|
|
184
|
+
type="submit"
|
|
185
|
+
aria-label={t('applyFilter')}
|
|
186
|
+
disabled={!isValidFilterString}
|
|
187
|
+
>
|
|
188
|
+
{t('applyFilter')}
|
|
189
|
+
</EuiButton>
|
|
190
|
+
<WfoTextAnchor
|
|
191
|
+
text={t('removeFilter')}
|
|
192
|
+
onClick={() => {
|
|
193
|
+
onUpdateQueryBuilder(false);
|
|
194
|
+
// we call with ruleGroup: false explictly to
|
|
195
|
+
// avoid state not having caught up yet when searching
|
|
196
|
+
handleSearch({ ruleGroup: false });
|
|
197
|
+
onToggleFilterBuilder(false);
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
180
200
|
</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
|
-
)}
|
|
201
|
+
</EuiFlexGroup>
|
|
196
202
|
</EuiFlexGroup>
|
|
197
203
|
);
|
|
198
204
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useEffect, useMemo } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import type { FullOperator, OperatorSelectorProps, OptionGroup } from 'react-querybuilder';
|
|
3
|
+
import { defaultOperators } from 'react-querybuilder';
|
|
3
4
|
|
|
4
5
|
import { EuiSelect } from '@elastic/eui';
|
|
5
6
|
|
|
@@ -7,6 +8,8 @@ const isOptionGroup = (operator: FullOperator | OptionGroup<FullOperator>): oper
|
|
|
7
8
|
'options' in operator;
|
|
8
9
|
|
|
9
10
|
export const WfoOperatorSelector = (props: OperatorSelectorProps) => {
|
|
11
|
+
const { value, handleOnChange } = props;
|
|
12
|
+
|
|
10
13
|
const flatOptions = (props.options as Array<FullOperator | OptionGroup<FullOperator>>).flatMap((option) =>
|
|
11
14
|
isOptionGroup(option) ? option.options : [option],
|
|
12
15
|
);
|
|
@@ -16,17 +19,39 @@ export const WfoOperatorSelector = (props: OperatorSelectorProps) => {
|
|
|
16
19
|
[flatOptions],
|
|
17
20
|
);
|
|
18
21
|
|
|
22
|
+
const optionsKey = selectOptions.map((option) => option.value).join('|');
|
|
23
|
+
const previousOptionsKeyRef = useRef(optionsKey);
|
|
24
|
+
|
|
19
25
|
useEffect(() => {
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
const optionsChanged = previousOptionsKeyRef.current !== optionsKey;
|
|
27
|
+
previousOptionsKeyRef.current = optionsKey;
|
|
28
|
+
|
|
29
|
+
// Reset to the first option only when the field's operator list changes — i.e. the
|
|
30
|
+
// user picked a (different) field, and resetOnFieldChange=false on QueryBuilder
|
|
31
|
+
// preserved an operator that is not valid for it. The `optionsChanged` guard keeps
|
|
32
|
+
// rules restored from CEL (URL or textarea) intact on mount: parseCEL can produce
|
|
33
|
+
// operators outside the prefilled operator lists (e.g. beginsWith), and resetting
|
|
34
|
+
// those here would silently rewrite the user's filter string.
|
|
35
|
+
if (!optionsChanged || selectOptions.length === 0) return;
|
|
36
|
+
const currentValueIsValid = selectOptions.some((option) => option.value === value);
|
|
37
|
+
if (!currentValueIsValid) {
|
|
38
|
+
handleOnChange(selectOptions[0].value);
|
|
22
39
|
}
|
|
23
|
-
}, [
|
|
40
|
+
}, [optionsKey, selectOptions, value, handleOnChange]);
|
|
41
|
+
|
|
42
|
+
// A restored operator that falls outside the field's list must still be visible in
|
|
43
|
+
// the dropdown; without it EuiSelect renders an empty selection for the rule.
|
|
44
|
+
const currentValueIsListed = !value || selectOptions.some((option) => option.value === value);
|
|
45
|
+
const displayOptions =
|
|
46
|
+
currentValueIsListed ? selectOptions : (
|
|
47
|
+
[...selectOptions, { value, text: defaultOperators.find((operator) => operator.name === value)?.label ?? value }]
|
|
48
|
+
);
|
|
24
49
|
|
|
25
50
|
return (
|
|
26
51
|
<EuiSelect
|
|
27
|
-
options={
|
|
28
|
-
value={
|
|
29
|
-
onChange={(e) =>
|
|
52
|
+
options={displayOptions}
|
|
53
|
+
value={value}
|
|
54
|
+
onChange={(e) => handleOnChange(e.target.value)}
|
|
30
55
|
disabled={props.disabled}
|
|
31
56
|
/>
|
|
32
57
|
);
|
|
@@ -13,7 +13,8 @@ import { WfoRemoveGroupAction } from './WfoRemoveGroupAction';
|
|
|
13
13
|
|
|
14
14
|
export const WfoRuleGroup = (props: RuleGroupProps) => {
|
|
15
15
|
const ruleGroupProps = useRuleGroup(props);
|
|
16
|
-
const
|
|
16
|
+
const rulePath = ruleGroupProps.path;
|
|
17
|
+
const isRootGroup: boolean = !rulePath || rulePath.length === 0;
|
|
17
18
|
|
|
18
19
|
const {
|
|
19
20
|
ruleGroupContainerBlueStyles,
|
|
@@ -24,9 +25,9 @@ export const WfoRuleGroup = (props: RuleGroupProps) => {
|
|
|
24
25
|
} = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
25
26
|
const getGroupContainerStyles = () => {
|
|
26
27
|
if (ruleGroupProps.path.length % 2) {
|
|
27
|
-
return
|
|
28
|
+
return !isRootGroup ? innerGroupContainerWhiteStyles : ruleGroupContainerWhiteStyles;
|
|
28
29
|
} else {
|
|
29
|
-
return
|
|
30
|
+
return !isRootGroup ? innerGroupContainerBlueStyles : ruleGroupContainerBlueStyles;
|
|
30
31
|
}
|
|
31
32
|
};
|
|
32
33
|
|
|
@@ -70,7 +71,7 @@ export const WfoRuleGroup = (props: RuleGroupProps) => {
|
|
|
70
71
|
</EuiFlexItem>
|
|
71
72
|
</EuiFlexGroup>
|
|
72
73
|
</EuiFlexItem>
|
|
73
|
-
{
|
|
74
|
+
{!isRootGroup && <WfoRemoveGroupAction onClick={ruleGroupProps.removeGroup} />}
|
|
74
75
|
</EuiFlexGroup>
|
|
75
76
|
);
|
|
76
77
|
};
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
DEFAULT_PAGE_SIZE,
|
|
10
10
|
DEFAULT_PAGE_SIZES,
|
|
11
11
|
TableColumnKeys,
|
|
12
|
-
TableSettingsColumnConfig,
|
|
13
12
|
TableSettingsConfig,
|
|
14
13
|
TableSettingsModal,
|
|
15
14
|
WfoDataSearch,
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
WfoKeyValueTable,
|
|
20
19
|
WfoKeyValueTableDataType,
|
|
21
20
|
clearTableConfigFromLocalStorage,
|
|
21
|
+
getTableSettingsColumns,
|
|
22
22
|
setTableConfigToLocalStorage,
|
|
23
23
|
} from '@/components';
|
|
24
24
|
import { getRowDetailData } from '@/components/WfoTable/WfoAdvancedTable/getRowDetailData';
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
WfoTableControlColumnConfigItem,
|
|
28
28
|
WfoTableDataColumnConfigItem,
|
|
29
29
|
} from '@/components/WfoTable/WfoTable';
|
|
30
|
-
import { useOrchestratorTheme } from '@/hooks';
|
|
30
|
+
import { useOrchestratorTheme, useWithOrchestratorTheme } from '@/hooks';
|
|
31
31
|
import { WfoArrowsExpand } from '@/icons';
|
|
32
32
|
import { WfoGraphqlError } from '@/rtk';
|
|
33
33
|
import { FieldToOperatorMap, RetrieverType } from '@/types';
|
|
@@ -36,6 +36,7 @@ import { getDefaultTableConfig } from '@/utils';
|
|
|
36
36
|
import { ColumnType, WfoTable, WfoTableProps } from '../WfoTable';
|
|
37
37
|
import { WfoFilterBuilder } from './WfoFilterBuilder';
|
|
38
38
|
import { WfoSearchFieldWithActions } from './WfoSearchFieldWithActions';
|
|
39
|
+
import { getWfoStructuredSearchTableStyles } from './styles';
|
|
39
40
|
import { buildColumnFilter } from './utils';
|
|
40
41
|
|
|
41
42
|
export type WfoStructuredSearchTableDataColumnConfigItem<
|
|
@@ -129,7 +130,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
129
130
|
...tableProps
|
|
130
131
|
}: WfoStructuredSearchTableProps<T>) => {
|
|
131
132
|
const { theme } = useOrchestratorTheme();
|
|
132
|
-
|
|
133
|
+
const { toggleButtonStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
133
134
|
const [hiddenColumns, setHiddenColumns] = useState<TableColumnKeys<T>>(defaultHiddenColumns);
|
|
134
135
|
const [isFilterBuilderVisible, setIsFilterBuilderVisible] = useState(false);
|
|
135
136
|
const [showTableSettingsModal, setShowTableSettingsModal] = useState(false);
|
|
@@ -166,17 +167,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
166
167
|
...tableColumnConfig,
|
|
167
168
|
};
|
|
168
169
|
|
|
169
|
-
const tableSettingsColumns
|
|
170
|
-
([key, { label }]): TableSettingsColumnConfig<T> => {
|
|
171
|
-
const field = key as keyof T;
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
field,
|
|
175
|
-
name: label,
|
|
176
|
-
isVisible: hiddenColumns.indexOf(field) === -1,
|
|
177
|
-
};
|
|
178
|
-
},
|
|
179
|
-
);
|
|
170
|
+
const tableSettingsColumns = getTableSettingsColumns(tableColumnConfig, hiddenColumns);
|
|
180
171
|
|
|
181
172
|
const rowDetailData: WfoKeyValueTableDataType[] | undefined =
|
|
182
173
|
rowDetailModalData && getRowDetailData(rowDetailModalData, tableColumnConfig);
|
|
@@ -211,24 +202,26 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
211
202
|
handleSearch({ ruleGroup: columnFilter.ruleGroup });
|
|
212
203
|
};
|
|
213
204
|
|
|
214
|
-
const filterBuilder = (
|
|
215
|
-
<WfoFilterBuilder
|
|
216
|
-
filterString={filterString}
|
|
217
|
-
onUpdateFilterString={onUpdateFilterString}
|
|
218
|
-
isValidFilterString={isValidFilterString}
|
|
219
|
-
queryBuilderRuleGroup={queryBuilderRuleGroup}
|
|
220
|
-
onUpdateQueryBuilder={onUpdateQueryBuilder}
|
|
221
|
-
handleSearch={handleSearch}
|
|
222
|
-
isFilterBuilderVisible={isFilterBuilderVisible}
|
|
223
|
-
onToggleFilterBuilder={setIsFilterBuilderVisible}
|
|
224
|
-
prefilledFieldOptions={prefilledFieldOptions}
|
|
225
|
-
/>
|
|
226
|
-
);
|
|
227
|
-
|
|
228
205
|
return (
|
|
229
206
|
<>
|
|
230
207
|
<EuiFlexGroup alignItems="center" gutterSize="s">
|
|
231
|
-
{!isFilterBuilderVisible &&
|
|
208
|
+
{!isFilterBuilderVisible && (
|
|
209
|
+
<EuiFlexItem grow={false}>
|
|
210
|
+
<EuiButton
|
|
211
|
+
css={toggleButtonStyles}
|
|
212
|
+
onClick={() => setIsFilterBuilderVisible(true)}
|
|
213
|
+
id={'button-toggle-filter-builder'}
|
|
214
|
+
data-test-id={'button-toggle-filter-builder'}
|
|
215
|
+
fill
|
|
216
|
+
type="submit"
|
|
217
|
+
iconType="filter"
|
|
218
|
+
iconSide="left"
|
|
219
|
+
aria-label={t('createFilter')}
|
|
220
|
+
>
|
|
221
|
+
{t('createFilter')}
|
|
222
|
+
</EuiButton>
|
|
223
|
+
</EuiFlexItem>
|
|
224
|
+
)}
|
|
232
225
|
<WfoSearchFieldWithActions
|
|
233
226
|
queryText={queryText}
|
|
234
227
|
onChangeQueryText={onChangeQueryText}
|
|
@@ -241,7 +234,16 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
241
234
|
{isFilterBuilderVisible && (
|
|
242
235
|
<>
|
|
243
236
|
<EuiSpacer size="s" />
|
|
244
|
-
|
|
237
|
+
<WfoFilterBuilder
|
|
238
|
+
filterString={filterString}
|
|
239
|
+
onUpdateFilterString={onUpdateFilterString}
|
|
240
|
+
isValidFilterString={isValidFilterString}
|
|
241
|
+
queryBuilderRuleGroup={queryBuilderRuleGroup}
|
|
242
|
+
onUpdateQueryBuilder={onUpdateQueryBuilder}
|
|
243
|
+
handleSearch={handleSearch}
|
|
244
|
+
onToggleFilterBuilder={setIsFilterBuilderVisible}
|
|
245
|
+
prefilledFieldOptions={prefilledFieldOptions}
|
|
246
|
+
/>
|
|
245
247
|
</>
|
|
246
248
|
)}
|
|
247
249
|
|