@orchestrator-ui/orchestrator-ui-components 8.5.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 +10 -14
- package/.turbo/turbo-lint.log +2 -4
- package/.turbo/turbo-test.log +16 -15
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +188 -80
- package/dist/index.js +3509 -2731
- package/dist/index.js.map +1 -1
- package/package.json +3 -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/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/WfoAddGroupAction.tsx +1 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector.tsx +3 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoExpandingSearchRow.tsx +21 -14
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +84 -4
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +119 -104
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoInlineCombinator.tsx +17 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoOperatorSelector.tsx +32 -7
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRemoveRuleAction.tsx +3 -3
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRuleGroup.tsx +11 -7
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoSearchFieldWithActions.tsx +56 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +92 -87
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +41 -11
- package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +74 -21
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +45 -0
- 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/WfoTableSettingsModal.tsx +9 -0
- package/src/components/WfoTable/WfoTableSettingsModal/index.ts +1 -0
- package/src/components/WfoTable/WfoTableSettingsModal/utils.ts +17 -0
- package/src/components/index.ts +1 -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 +20 -0
- package/src/messages/nl-NL.json +20 -0
- package/src/pages/WfoSearchPocPage.tsx +165 -30
- package/src/rtk/endpoints/subscriptionList.ts +1 -0
- package/src/theme/baseStyles/formFieldsBaseStyle.ts +0 -1
- package/src/types/search.ts +4 -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
|
@@ -15,12 +15,14 @@ export type WfoSubscriptionActionExpandableMenuItemProps = {
|
|
|
15
15
|
subscriptionAction: SubscriptionAction;
|
|
16
16
|
onClickLockedRelation: (relation: SubscriptionRelation) => void;
|
|
17
17
|
children: React.ReactNode;
|
|
18
|
+
subscriptionPath?: string;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export const WfoSubscriptionActionExpandableMenuItem: FC<WfoSubscriptionActionExpandableMenuItemProps> = ({
|
|
21
22
|
subscriptionAction,
|
|
22
23
|
onClickLockedRelation,
|
|
23
24
|
children,
|
|
25
|
+
subscriptionPath = PATH_SUBSCRIPTIONS,
|
|
24
26
|
}) => {
|
|
25
27
|
const t = useTranslations('subscriptions.detail.actions');
|
|
26
28
|
|
|
@@ -29,18 +31,21 @@ export const WfoSubscriptionActionExpandableMenuItem: FC<WfoSubscriptionActionEx
|
|
|
29
31
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
30
32
|
|
|
31
33
|
// TODO: remove lockedRelationsUuids fallback when orchestrator-core 6.0.0 is released and only use the _detail variant
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const relationsDetail = [
|
|
35
|
+
...(subscriptionAction.locked_relations_detail ?? []),
|
|
36
|
+
...(subscriptionAction.unterminated_in_use_by_subscriptions_detail ?? []),
|
|
37
|
+
];
|
|
38
|
+
const relationsUuids = [
|
|
39
|
+
...(subscriptionAction.locked_relations ?? []),
|
|
40
|
+
...(subscriptionAction.unterminated_in_use_by_subscriptions ?? []),
|
|
41
|
+
];
|
|
42
|
+
const hasRelations = relationsDetail.length > 0 || relationsUuids.length > 0;
|
|
35
43
|
|
|
36
44
|
return (
|
|
37
45
|
<div>
|
|
38
|
-
<div
|
|
39
|
-
css={[expandableMenuItemStyle, hasLockedRelations && clickableStyle]}
|
|
40
|
-
onClick={() => setIsExpanded(!isExpanded)}
|
|
41
|
-
>
|
|
46
|
+
<div css={[expandableMenuItemStyle, hasRelations && clickableStyle]} onClick={() => setIsExpanded(!isExpanded)}>
|
|
42
47
|
<div>{children}</div>
|
|
43
|
-
{
|
|
48
|
+
{hasRelations && (
|
|
44
49
|
<EuiButtonIcon
|
|
45
50
|
css={expandButtonStyle}
|
|
46
51
|
iconType={isExpanded ? 'arrowDown' : 'arrowRight'}
|
|
@@ -49,17 +54,17 @@ export const WfoSubscriptionActionExpandableMenuItem: FC<WfoSubscriptionActionEx
|
|
|
49
54
|
/>
|
|
50
55
|
)}
|
|
51
56
|
</div>
|
|
52
|
-
{
|
|
57
|
+
{hasRelations && isExpanded && (
|
|
53
58
|
<div css={expandedContentStyle}>
|
|
54
59
|
<EuiText size="xs">{t('lockedBySubscriptions')}</EuiText>
|
|
55
60
|
<ul css={{ margin: 0, paddingLeft: 16, listStyleType: 'disc' }}>
|
|
56
|
-
{
|
|
57
|
-
|
|
61
|
+
{relationsDetail.length > 0 ?
|
|
62
|
+
relationsDetail.map((relation) => (
|
|
58
63
|
<li key={relation.subscription_id}>
|
|
59
64
|
<EuiText size="xs">
|
|
60
65
|
<Link
|
|
61
66
|
css={linkStyle}
|
|
62
|
-
href={`${
|
|
67
|
+
href={`${subscriptionPath}/${relation.subscription_id}`}
|
|
63
68
|
target="_blank"
|
|
64
69
|
onClick={() => onClickLockedRelation(relation)}
|
|
65
70
|
>
|
|
@@ -68,10 +73,10 @@ export const WfoSubscriptionActionExpandableMenuItem: FC<WfoSubscriptionActionEx
|
|
|
68
73
|
</EuiText>
|
|
69
74
|
</li>
|
|
70
75
|
))
|
|
71
|
-
:
|
|
76
|
+
: relationsUuids.map((id) => (
|
|
72
77
|
<li key={id}>
|
|
73
78
|
<EuiText size="xs">
|
|
74
|
-
<Link css={linkStyle} href={`${
|
|
79
|
+
<Link css={linkStyle} href={`${subscriptionPath}/${id}`} target="_blank">
|
|
75
80
|
{id}
|
|
76
81
|
</Link>
|
|
77
82
|
</EuiText>
|
|
@@ -5,7 +5,13 @@ import { useRouter } from 'next/router';
|
|
|
5
5
|
|
|
6
6
|
import { EuiButton, EuiButtonIcon, EuiLoadingSpinner, EuiTitle } from '@elastic/eui';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
PATH_START_NEW_TASK,
|
|
10
|
+
PATH_START_NEW_WORKFLOW,
|
|
11
|
+
PATH_SUBSCRIPTIONS,
|
|
12
|
+
WfoInSyncField,
|
|
13
|
+
WfoPopover,
|
|
14
|
+
} from '@/components';
|
|
9
15
|
import { WfoSubscriptionActionsMenuItem } from '@/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem';
|
|
10
16
|
import { useActiveProcess } from '@/components/WfoSubscription/WfoSubscriptionActions/utils';
|
|
11
17
|
import { PolicyResource } from '@/configuration/policy-resources';
|
|
@@ -27,12 +33,14 @@ export type WfoSubscriptionActionsProps = {
|
|
|
27
33
|
subscriptionId: string;
|
|
28
34
|
isLoading?: boolean;
|
|
29
35
|
compactMode?: boolean;
|
|
36
|
+
subscriptionPath?: string;
|
|
30
37
|
};
|
|
31
38
|
|
|
32
39
|
export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
33
40
|
subscriptionId,
|
|
34
41
|
isLoading,
|
|
35
42
|
compactMode = false,
|
|
43
|
+
subscriptionPath = PATH_SUBSCRIPTIONS,
|
|
36
44
|
}) => {
|
|
37
45
|
const t = useTranslations('subscriptions.detail.actions');
|
|
38
46
|
const { theme } = useOrchestratorTheme();
|
|
@@ -132,6 +140,7 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
132
140
|
subscriptionAction={subscriptionAction}
|
|
133
141
|
target={WorkflowTarget.VALIDATE}
|
|
134
142
|
setPopover={setPopover}
|
|
143
|
+
subscriptionPath={subscriptionPath}
|
|
135
144
|
onClick={() => handleActionClick(subscriptionAction.name, compactMode, true)}
|
|
136
145
|
isLoading={buttonIsLoading}
|
|
137
146
|
/>
|
|
@@ -152,6 +161,7 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
152
161
|
}
|
|
153
162
|
target={WorkflowTarget.RECONCILE}
|
|
154
163
|
setPopover={setPopover}
|
|
164
|
+
subscriptionPath={subscriptionPath}
|
|
155
165
|
onClick={() => handleActionClick(subscriptionAction.name, compactMode, false)}
|
|
156
166
|
isLoading={buttonIsLoading}
|
|
157
167
|
/>
|
|
@@ -182,6 +192,7 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
182
192
|
subscriptionAction={subscriptionAction}
|
|
183
193
|
target={WorkflowTarget.MODIFY}
|
|
184
194
|
setPopover={setPopover}
|
|
195
|
+
subscriptionPath={subscriptionPath}
|
|
185
196
|
onClick={() => {
|
|
186
197
|
redirectToUrl(subscriptionAction.name);
|
|
187
198
|
}}
|
|
@@ -199,6 +210,7 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
199
210
|
subscriptionAction={subscriptionAction}
|
|
200
211
|
target={WorkflowTarget.TERMINATE}
|
|
201
212
|
setPopover={setPopover}
|
|
213
|
+
subscriptionPath={subscriptionPath}
|
|
202
214
|
onClick={() => {
|
|
203
215
|
redirectToUrl(subscriptionAction.name);
|
|
204
216
|
}}
|
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);
|
|
@@ -12,7 +12,7 @@ export const WfoAddGroupAction = ({ disabled, handleOnClick }: ActionProps) => {
|
|
|
12
12
|
const t = useTranslations('search.page');
|
|
13
13
|
const { addGroupStyles, addRulePlusStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
14
14
|
return disabled ? null : (
|
|
15
|
-
<EuiFlexItem css={addGroupStyles} onClick={() => handleOnClick()}>
|
|
15
|
+
<EuiFlexItem grow={true} css={addGroupStyles} onClick={() => handleOnClick()}>
|
|
16
16
|
<span css={addRulePlusStyles}>+</span> {t('addGroup')}
|
|
17
17
|
</EuiFlexItem>
|
|
18
18
|
);
|
|
@@ -8,6 +8,9 @@ import { useWithOrchestratorTheme } from '@/hooks';
|
|
|
8
8
|
|
|
9
9
|
export const WfoCombinatorSelector = (props: CombinatorSelectorProps) => {
|
|
10
10
|
const { buttonGroupStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
11
|
+
|
|
12
|
+
if (props.rules.length < 2) return null;
|
|
13
|
+
|
|
11
14
|
const options = props.options.map((option) => ({
|
|
12
15
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
13
16
|
// @ts-ignore This seems to be a typing error in the react-querybuilde library. Option.name does exist
|
|
@@ -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>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { FieldSelectorProps } from 'react-querybuilder';
|
|
3
3
|
|
|
4
4
|
import { useTranslations } from 'next-intl';
|
|
@@ -7,16 +7,42 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui';
|
|
|
7
7
|
import { EuiComboBox } from '@elastic/eui';
|
|
8
8
|
|
|
9
9
|
import { usePathAutocomplete } from '@/hooks';
|
|
10
|
-
import { EntityKind, PathInfo } from '@/types';
|
|
10
|
+
import { EntityKind, FieldToOperatorMap, PathInfo } from '@/types';
|
|
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
|
+
};
|
|
11
32
|
|
|
12
33
|
export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: FieldSelectorProps) => {
|
|
13
34
|
const { field } = rule;
|
|
35
|
+
const prefilledFieldOptions: FieldToOperatorMap = context.prefilledFieldOptions;
|
|
14
36
|
const [selectedValue, setSelectedValue] = useState<string>(field);
|
|
37
|
+
const [searchInput, setSearchInput] = useState<HTMLInputElement | null>(null);
|
|
38
|
+
const optionsRef = useRef<EuiComboBoxOptionOption<string>[]>([]);
|
|
39
|
+
const handleFieldSelectionRef = useRef<(selected: EuiComboBoxOptionOption<string>[]) => void>(() => {});
|
|
15
40
|
const t = useTranslations('search.page');
|
|
16
41
|
const getOption = (path: string) => ({
|
|
17
42
|
value: path,
|
|
18
43
|
label: path,
|
|
19
44
|
});
|
|
45
|
+
|
|
20
46
|
const getOptionsFromPathInfo = (pathInfos: PathInfo[]): EuiComboBoxOptionOption<string>[] => {
|
|
21
47
|
const pathOptions: EuiComboBoxOptionOption<string>[] = [];
|
|
22
48
|
|
|
@@ -40,14 +66,26 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
|
|
|
40
66
|
error: errorMessage,
|
|
41
67
|
} = usePathAutocomplete(selectedValue, EntityKind.SUBSCRIPTION);
|
|
42
68
|
|
|
43
|
-
const
|
|
69
|
+
const prefilledOptions: EuiComboBoxOptionOption<string>[] = Array.from(prefilledFieldOptions.keys()).map(getOption);
|
|
70
|
+
const autocompleteOptions = getOptionsFromPathInfo(paths);
|
|
71
|
+
const placeholderOption: EuiComboBoxOptionOption<string> = {
|
|
72
|
+
label: '──────',
|
|
73
|
+
disabled: true,
|
|
74
|
+
};
|
|
75
|
+
const showPlaceholder = prefilledOptions.length > 0 && autocompleteOptions.length > 0;
|
|
76
|
+
const options: EuiComboBoxOptionOption<string>[] = [
|
|
77
|
+
...prefilledOptions,
|
|
78
|
+
...(showPlaceholder ? [placeholderOption] : []),
|
|
79
|
+
...autocompleteOptions,
|
|
80
|
+
];
|
|
44
81
|
|
|
45
82
|
const storeFieldOperators = (selectedValue: string) => {
|
|
46
83
|
const matchingPath =
|
|
47
84
|
paths.find((path) => path.path === selectedValue)
|
|
48
85
|
?? paths.find((path) => path.availablePaths?.includes(selectedValue));
|
|
86
|
+
const operators = matchingPath?.operators ?? prefilledFieldOptions.get(selectedValue) ?? [];
|
|
49
87
|
|
|
50
|
-
context?.onFieldSelected?.(selectedValue,
|
|
88
|
+
context?.onFieldSelected?.(selectedValue, operators);
|
|
51
89
|
};
|
|
52
90
|
|
|
53
91
|
const handleFieldSelection = (selectedOptions: EuiComboBoxOptionOption<string>[]) => {
|
|
@@ -57,8 +95,49 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
|
|
|
57
95
|
storeFieldOperators(selectedValue);
|
|
58
96
|
|
|
59
97
|
handleOnChange(selectedValue);
|
|
98
|
+
|
|
99
|
+
if (selectedValue) focusValueEditorAfterRender(searchInput);
|
|
60
100
|
};
|
|
61
101
|
|
|
102
|
+
// EuiComboBox only auto-selects on Enter/Tab when exactly one option matches the typed
|
|
103
|
+
// text. When several options share the typed prefix, the exact-match option is not picked
|
|
104
|
+
// unless the user arrow-navigates. Patch that with a native keydown listener on the input.
|
|
105
|
+
optionsRef.current = options;
|
|
106
|
+
handleFieldSelectionRef.current = handleFieldSelection;
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!searchInput) return;
|
|
110
|
+
|
|
111
|
+
const autoSelectExactMatchOption = (event: KeyboardEvent) => {
|
|
112
|
+
if (event.key !== 'Enter' && event.key !== 'Tab') return;
|
|
113
|
+
// Let EuiComboBox handle selection when an option is keyboard-highlighted.
|
|
114
|
+
if (searchInput.getAttribute('aria-activedescendant')) return;
|
|
115
|
+
const typed = searchInput.value.trim();
|
|
116
|
+
if (!typed) return;
|
|
117
|
+
const exactMatch = optionsRef.current.find(
|
|
118
|
+
(option) => !option.disabled && option.label.toLowerCase() === typed.toLowerCase(),
|
|
119
|
+
);
|
|
120
|
+
if (!exactMatch) return;
|
|
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();
|
|
125
|
+
handleFieldSelectionRef.current([exactMatch]);
|
|
126
|
+
|
|
127
|
+
// EuiComboBox keeps its own internal `searchValue` and `isListOpen` state. Clearing
|
|
128
|
+
// the value via the React-recognised native setter + input event suppresses EUI's
|
|
129
|
+
// "typed-but-not-selected" warning icon (the `markAsInvalid` branch in combo_box.js).
|
|
130
|
+
// The synthetic Escape closes the dropdown without disturbing focus.
|
|
131
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
|
|
132
|
+
nativeInputValueSetter?.call(searchInput, '');
|
|
133
|
+
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
134
|
+
searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
searchInput.addEventListener('keydown', autoSelectExactMatchOption);
|
|
138
|
+
return () => searchInput.removeEventListener('keydown', autoSelectExactMatchOption);
|
|
139
|
+
}, [searchInput]);
|
|
140
|
+
|
|
62
141
|
return (
|
|
63
142
|
<EuiComboBox
|
|
64
143
|
placeholder={t('searchFieldsPlaceholder')}
|
|
@@ -73,6 +152,7 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
|
|
|
73
152
|
setSelectedValue(inputValue);
|
|
74
153
|
}
|
|
75
154
|
}}
|
|
155
|
+
inputRef={setSearchInput}
|
|
76
156
|
singleSelection={{ asPlainText: true }}
|
|
77
157
|
isLoading={isLoading}
|
|
78
158
|
isClearable
|