@orchestrator-ui/orchestrator-ui-components 8.7.3 → 8.8.2
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 +8 -8
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +17 -15
- package/CHANGELOG.md +34 -0
- package/dist/index.d.ts +42 -20
- package/dist/index.js +1612 -1378
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/WfoBadges/WfoWorkflowTargetBadge/WfoWorkflowTargetBadge.tsx +2 -2
- package/src/components/WfoContentHeader/WfoContentHeader.tsx +1 -1
- package/src/components/WfoInlineNoteEdit/WfoProcessListNoteEdit.spec.tsx +35 -0
- package/src/components/WfoInlineNoteEdit/WfoProcessListNoteEdit.tsx +7 -9
- package/src/components/WfoProcessList/WfoProcessListDeltaPopover.tsx +1 -1
- package/src/components/WfoProcessList/WfoProcessesList.tsx +7 -5
- package/src/components/WfoPydanticForm/fields/WfoMultiCheckboxField.tsx +2 -1
- package/src/components/WfoSubscription/WfoProcessesTimeline.tsx +21 -7
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +25 -9
- package/src/components/WfoSubscription/WfoSubscriptionGeneralSections/WfoSubscriptionDetailSection.tsx +1 -1
- package/src/components/WfoSubscription/styles.ts +6 -0
- package/src/components/WfoSubscription/utils/utils.ts +2 -4
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoExpandingSearchRow.tsx +0 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +12 -8
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +23 -10
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +73 -3
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +4 -2
- package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +8 -9
- package/src/components/WfoTable/WfoTable/WfoTable.tsx +80 -45
- package/src/components/WfoTable/WfoTable/WfoTableDataRows.tsx +11 -2
- package/src/components/WfoTable/WfoTable/WfoTableExpandedRowReveal.spec.tsx +120 -0
- package/src/components/WfoTable/WfoTable/WfoTableSkeletonRows.tsx +52 -0
- package/src/components/WfoTable/WfoTable/index.ts +1 -0
- package/src/components/WfoTable/WfoTable/styles.ts +6 -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 +2 -0
- package/src/components/WfoTimeline/styles.ts +1 -1
- package/src/components/WfoWorkflowUserGuide/WfoPageWithUserGuide.tsx +8 -2
- package/src/components/WfoWorkflowUserGuide/WfoWorkflowGuideExpandablePanel.tsx +39 -30
- package/src/components/WfoWorkflowUserGuide/styles.ts +55 -7
- package/src/configuration/constants.ts +1 -1
- 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 +2 -0
- package/src/messages/en-GB.json +5 -1
- package/src/messages/nl-NL.json +6 -2
- package/src/pages/WfoSearchPocPage.tsx +29 -24
- package/src/pages/processes/WfoProductInformationWithLink.tsx +9 -4
- package/src/pages/tasks/WfoTasksListPage.tsx +1 -0
- package/src/types/search.ts +8 -0
- package/src/types/types.ts +11 -11
- package/src/utils/getDefaultTableConfig.ts +2 -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.8.2",
|
|
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",
|
|
@@ -13,11 +13,11 @@ export const WfoWorkflowTargetBadge: FC<WfoWorkflowTargetBadgeProps> = ({ target
|
|
|
13
13
|
const { theme, toSecondaryColor } = useOrchestratorTheme();
|
|
14
14
|
if (!target) return null;
|
|
15
15
|
|
|
16
|
-
const getBadgeColorFromTarget = (_target:
|
|
16
|
+
const getBadgeColorFromTarget = (_target: WorkflowTarget) => {
|
|
17
17
|
const { primary, danger, textDanger, textPrimary, success, textSuccess, warning, textWarning, accent, textAccent } =
|
|
18
18
|
theme.colors;
|
|
19
19
|
|
|
20
|
-
switch (_target
|
|
20
|
+
switch (_target) {
|
|
21
21
|
case WorkflowTarget.CREATE:
|
|
22
22
|
return {
|
|
23
23
|
badgeColor: toSecondaryColor(success),
|
|
@@ -19,7 +19,7 @@ export const WfoContentHeader: FC<WfoContentHeaderProps> = ({ title, subtitle, c
|
|
|
19
19
|
</WfoRenderElementOrString>
|
|
20
20
|
{subtitle && (
|
|
21
21
|
<div>
|
|
22
|
-
<EuiSpacer size="
|
|
22
|
+
<EuiSpacer size="s" />
|
|
23
23
|
<WfoRenderElementOrString>{subtitle}</WfoRenderElementOrString>
|
|
24
24
|
</div>
|
|
25
25
|
)}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for the note column going stale while paginating: the table reuses row
|
|
3
|
+
* components across pages, and EuiInlineEditText only follows its value prop while the value
|
|
4
|
+
* is truthy. A row whose note is empty must therefore still pass a truthy value (the
|
|
5
|
+
* invisible character), otherwise the cell keeps showing the previous page's note.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
import '@testing-library/jest-dom';
|
|
10
|
+
import { render, screen } from '@testing-library/react';
|
|
11
|
+
|
|
12
|
+
import { WfoProcessListNoteEdit } from './WfoProcessListNoteEdit';
|
|
13
|
+
|
|
14
|
+
jest.mock('@/rtk', () => ({
|
|
15
|
+
...jest.requireActual('@/rtk'),
|
|
16
|
+
usePatchProcessMutation: () => [jest.fn()],
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe('WfoProcessListNoteEdit', () => {
|
|
20
|
+
it('stops showing the previous note when the reused row has no note', () => {
|
|
21
|
+
const { rerender } = render(<WfoProcessListNoteEdit processId="process-1" note="Note from page one" />);
|
|
22
|
+
expect(screen.getByText('Note from page one')).toBeInTheDocument();
|
|
23
|
+
|
|
24
|
+
rerender(<WfoProcessListNoteEdit processId="process-2" note={null} />);
|
|
25
|
+
expect(screen.queryByText('Note from page one')).not.toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('shows the new note when the reused row has a different note', () => {
|
|
29
|
+
const { rerender } = render(<WfoProcessListNoteEdit processId="process-1" note="Note from page one" />);
|
|
30
|
+
|
|
31
|
+
rerender(<WfoProcessListNoteEdit processId="process-2" note="Note from page two" />);
|
|
32
|
+
expect(screen.getByText('Note from page two')).toBeInTheDocument();
|
|
33
|
+
expect(screen.queryByText('Note from page one')).not.toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { FC } from 'react';
|
|
2
|
-
import React
|
|
2
|
+
import React from 'react';
|
|
3
3
|
|
|
4
4
|
import { usePatchProcessMutation } from '@/rtk';
|
|
5
5
|
import { ProcessDetail } from '@/types';
|
|
6
|
+
import { INVISIBLE_CHARACTER } from '@/utils';
|
|
6
7
|
|
|
7
8
|
import { WfoInlineEdit } from '../WfoInlineEdit';
|
|
8
9
|
|
|
@@ -13,13 +14,6 @@ interface WfoProcessDetailNoteEditProps {
|
|
|
13
14
|
|
|
14
15
|
export const WfoProcessListNoteEdit: FC<WfoProcessDetailNoteEditProps> = ({ processId, note }) => {
|
|
15
16
|
const [patchProcess] = usePatchProcessMutation();
|
|
16
|
-
const [noteValue, setNoteValue] = useState(note);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (note !== noteValue) {
|
|
20
|
-
setNoteValue(note);
|
|
21
|
-
}
|
|
22
|
-
}, [note, noteValue]);
|
|
23
17
|
|
|
24
18
|
const onSaveNote = async (note: string) => {
|
|
25
19
|
const noteModifyPayload = { id: processId, note: note };
|
|
@@ -27,5 +21,9 @@ export const WfoProcessListNoteEdit: FC<WfoProcessDetailNoteEditProps> = ({ proc
|
|
|
27
21
|
return note;
|
|
28
22
|
};
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
// An empty note must still be passed as a non-empty string (the invisible character):
|
|
25
|
+
// EuiInlineEditText only follows its value prop while the value is truthy, so an empty
|
|
26
|
+
// string would freeze the cell on the note of whichever row rendered here before it,
|
|
27
|
+
// e.g. when the table rows are reused while paginating.
|
|
28
|
+
return <WfoInlineEdit value={note?.trim() ? note : INVISIBLE_CHARACTER} onSave={onSaveNote} />;
|
|
31
29
|
};
|
|
@@ -28,7 +28,7 @@ export const WfoProcessListDeltaPopover: FC<WfoProcessListDeltaPopoverProps> = (
|
|
|
28
28
|
|
|
29
29
|
const { data, isFetching } = useGetRawProcessDetailQuery({ processId }, { skip: !isPopoverOpen });
|
|
30
30
|
|
|
31
|
-
if (workflowTarget
|
|
31
|
+
if (workflowTarget !== WorkflowTarget.MODIFY) {
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -241,15 +241,17 @@ export const WfoProcessesList = ({
|
|
|
241
241
|
const getProcessListForExport = () =>
|
|
242
242
|
getProcessListTrigger(getQueryVariablesForExport(processListQueryVars)).unwrap();
|
|
243
243
|
|
|
244
|
+
const tableConfig = mapSortableAndFilterableValuesToTableColumnConfig(
|
|
245
|
+
tableColumns,
|
|
246
|
+
pageInfo?.sortFields,
|
|
247
|
+
pageInfo?.filterFields,
|
|
248
|
+
);
|
|
249
|
+
|
|
244
250
|
return (
|
|
245
251
|
<WfoAdvancedTable<ProcessListItem>
|
|
246
252
|
queryString={queryString}
|
|
247
253
|
data={mapGraphQlProcessListResultToProcessListItems(processes || [])}
|
|
248
|
-
tableColumnConfig={
|
|
249
|
-
tableColumns,
|
|
250
|
-
pageInfo?.sortFields,
|
|
251
|
-
pageInfo?.filterFields,
|
|
252
|
-
)}
|
|
254
|
+
tableColumnConfig={tableConfig}
|
|
253
255
|
dataSorting={[dataSorting]}
|
|
254
256
|
pagination={pagination}
|
|
255
257
|
isLoading={isFetching}
|
|
@@ -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,9 +1,9 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useTranslations } from 'next-intl';
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
|
|
6
|
-
import { EuiComment, EuiCommentList, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
|
|
6
|
+
import { EuiComment, EuiCommentList, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch, EuiText } from '@elastic/eui';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
PATH_TASKS,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
sortProcessesByDate,
|
|
16
16
|
} from '@/components';
|
|
17
17
|
import { useWithOrchestratorTheme } from '@/hooks';
|
|
18
|
-
import { SortOrder, SubscriptionDetailProcess } from '@/types';
|
|
18
|
+
import { SortOrder, SubscriptionDetailProcess, WorkflowTarget } from '@/types';
|
|
19
19
|
import { parseDate, parseDateToLocaleDateTimeString, upperCaseFirstChar } from '@/utils';
|
|
20
20
|
|
|
21
21
|
import { WfoProcessStatusBadge } from '../WfoBadges';
|
|
@@ -122,8 +122,9 @@ export const WfoProcessesTimeline = ({ subscriptionDetailProcesses }: WfoProcess
|
|
|
122
122
|
value: SortOrder.DESC,
|
|
123
123
|
},
|
|
124
124
|
];
|
|
125
|
-
|
|
126
|
-
const [selectedOption, setSelectedOption] =
|
|
125
|
+
const { processToggleStyles } = useWithOrchestratorTheme(getSubscriptionDetailStyles);
|
|
126
|
+
const [selectedOption, setSelectedOption] = useState(options[0]);
|
|
127
|
+
const [hideValidateTasks, setHideValidateTasks] = useState<boolean>(true);
|
|
127
128
|
|
|
128
129
|
const handleOnSelectOption = (option: WfoRadioDropdownOption<SortOrder>) => {
|
|
129
130
|
setSelectedOption(option);
|
|
@@ -135,11 +136,24 @@ export const WfoProcessesTimeline = ({ subscriptionDetailProcesses }: WfoProcess
|
|
|
135
136
|
<>
|
|
136
137
|
<EuiSpacer size={'m'} />
|
|
137
138
|
{!subscriptionDetailProcesses && <WfoLoading />}
|
|
138
|
-
<
|
|
139
|
+
<EuiFlexGroup alignItems={'center'} css={processToggleStyles}>
|
|
140
|
+
<EuiSwitch
|
|
141
|
+
showLabel={true}
|
|
142
|
+
label={t('hideValidateTasks')}
|
|
143
|
+
type="button"
|
|
144
|
+
checked={hideValidateTasks}
|
|
145
|
+
onChange={() => setHideValidateTasks((value) => !value)}
|
|
146
|
+
/>
|
|
147
|
+
<WfoRadioDropdown options={options} onUpdateOption={handleOnSelectOption} selectedOption={selectedOption} />
|
|
148
|
+
</EuiFlexGroup>
|
|
149
|
+
|
|
139
150
|
{sortedProcesses && (
|
|
140
151
|
<EuiCommentList aria-label="Processes">
|
|
141
152
|
{sortedProcesses
|
|
142
|
-
.filter(
|
|
153
|
+
.filter(
|
|
154
|
+
(process) =>
|
|
155
|
+
!process.isTask || (process.workflowTarget === WorkflowTarget.VALIDATE && !hideValidateTasks),
|
|
156
|
+
)
|
|
143
157
|
.map((subscriptionDetailProcess, index) => (
|
|
144
158
|
<WfoRenderSubscriptionProcess key={index} subscriptionDetailProcess={subscriptionDetailProcess} />
|
|
145
159
|
))}
|
|
@@ -18,7 +18,7 @@ import { PolicyResource } from '@/configuration/policy-resources';
|
|
|
18
18
|
import { useOrchestratorTheme, usePolicy } from '@/hooks';
|
|
19
19
|
import { WfoDotsHorizontal } from '@/icons/WfoDotsHorizontal';
|
|
20
20
|
import { useGetSubscriptionActionsQuery, useGetSubscriptionDetailQuery, useStartProcessMutation } from '@/rtk';
|
|
21
|
-
import { WorkflowTarget } from '@/types';
|
|
21
|
+
import { SubscriptionAction, WorkflowTarget } from '@/types';
|
|
22
22
|
|
|
23
23
|
type MenuBlockProps = {
|
|
24
24
|
title: string;
|
|
@@ -129,12 +129,28 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
const getActionItems = (workflowTarget: WorkflowTarget): SubscriptionAction[] => {
|
|
133
|
+
// Core versions 5.2 and lower return subscriptionActions result with lowercase keynames. Higher version return them uppercased to align
|
|
134
|
+
// with all the other places they are used. We support both for now. The lowercase keys are deliberately not part of the
|
|
135
|
+
// SubscriptionActions type, so the fallback is a runtime-only check behind a cast.
|
|
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);
|
|
147
|
+
|
|
132
148
|
const compactItems = (
|
|
133
149
|
<>
|
|
134
|
-
{isAllowed(SUBSCRIPTION_VALIDATE + subscriptionId) &&
|
|
150
|
+
{isAllowed(SUBSCRIPTION_VALIDATE + subscriptionId) && validateActionItems.length > 0 && (
|
|
135
151
|
<>
|
|
136
152
|
{!compactMode && <MenuBlock title={t('tasks')} />}
|
|
137
|
-
{
|
|
153
|
+
{validateActionItems.map((subscriptionAction, index) => (
|
|
138
154
|
<WfoSubscriptionActionsMenuItem
|
|
139
155
|
key={`s_${index}`}
|
|
140
156
|
subscriptionAction={subscriptionAction}
|
|
@@ -148,10 +164,10 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
148
164
|
</>
|
|
149
165
|
)}
|
|
150
166
|
|
|
151
|
-
{isAllowed(SUBSCRIPTION_RECONCILE + subscriptionId) &&
|
|
167
|
+
{isAllowed(SUBSCRIPTION_RECONCILE + subscriptionId) && reconcileActionItems.length > 0 && (
|
|
152
168
|
<>
|
|
153
169
|
{!compactMode && <MenuBlock title={t('reconcile')} />}
|
|
154
|
-
{
|
|
170
|
+
{reconcileActionItems.map((subscriptionAction, index) => (
|
|
155
171
|
<WfoSubscriptionActionsMenuItem
|
|
156
172
|
key={`r_${index}`}
|
|
157
173
|
subscriptionAction={
|
|
@@ -183,10 +199,10 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
183
199
|
|
|
184
200
|
const fullItems = (
|
|
185
201
|
<>
|
|
186
|
-
{isAllowed(SUBSCRIPTION_MODIFY + subscriptionId) &&
|
|
202
|
+
{isAllowed(SUBSCRIPTION_MODIFY + subscriptionId) && modifyActionItems.length > 0 && (
|
|
187
203
|
<>
|
|
188
204
|
<MenuBlock title={t('modify')} />
|
|
189
|
-
{
|
|
205
|
+
{modifyActionItems.map((subscriptionAction, index) => (
|
|
190
206
|
<WfoSubscriptionActionsMenuItem
|
|
191
207
|
key={`m_${index}`}
|
|
192
208
|
subscriptionAction={subscriptionAction}
|
|
@@ -201,10 +217,10 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
|
|
|
201
217
|
</>
|
|
202
218
|
)}
|
|
203
219
|
{compactItems}
|
|
204
|
-
{isAllowed(SUBSCRIPTION_TERMINATE + subscriptionId) &&
|
|
220
|
+
{isAllowed(SUBSCRIPTION_TERMINATE + subscriptionId) && terminateActionItems.length > 0 && (
|
|
205
221
|
<>
|
|
206
222
|
<MenuBlock title={t('terminate')} />
|
|
207
|
-
{
|
|
223
|
+
{terminateActionItems.map((subscriptionAction, index) => (
|
|
208
224
|
<WfoSubscriptionActionsMenuItem
|
|
209
225
|
key={`t_${index}`}
|
|
210
226
|
subscriptionAction={subscriptionAction}
|
|
@@ -67,7 +67,7 @@ export const WfoSubscriptionDetailSection = ({ subscriptionDetail }: WfoSubscrip
|
|
|
67
67
|
key: t('lastRunValidation'),
|
|
68
68
|
value: (() => {
|
|
69
69
|
const lastValidate = processes?.page
|
|
70
|
-
?.filter((process) => process.workflowTarget
|
|
70
|
+
?.filter((process) => process.workflowTarget === WorkflowTarget.VALIDATE)
|
|
71
71
|
.slice(-1)[0];
|
|
72
72
|
|
|
73
73
|
if (!lastValidate) {
|
|
@@ -54,6 +54,11 @@ export const getSubscriptionDetailStyles = ({ theme, isDarkModeActive }: WfoThem
|
|
|
54
54
|
flexDirection: 'column',
|
|
55
55
|
alignItems: 'flex-start',
|
|
56
56
|
});
|
|
57
|
+
|
|
58
|
+
const processToggleStyles = css({
|
|
59
|
+
justifySelf: 'flex-end',
|
|
60
|
+
});
|
|
61
|
+
|
|
57
62
|
return {
|
|
58
63
|
rowStyle,
|
|
59
64
|
labelCellStyle,
|
|
@@ -66,5 +71,6 @@ export const getSubscriptionDetailStyles = ({ theme, isDarkModeActive }: WfoThem
|
|
|
66
71
|
productBlockTreeWidth,
|
|
67
72
|
customerDescriptionsCustomerNameStyle,
|
|
68
73
|
customerDescriptionsFormStyle,
|
|
74
|
+
processToggleStyles,
|
|
69
75
|
};
|
|
70
76
|
};
|
|
@@ -59,8 +59,7 @@ export const flattenSubscriptionActionProps = (action?: SubscriptionAction | nul
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
export const getWorkflowTargetColor = (workflowTarget: WorkflowTarget, theme: EuiThemeComputed) => {
|
|
62
|
-
|
|
63
|
-
switch (workflowTarget.toLocaleLowerCase()) {
|
|
62
|
+
switch (workflowTarget) {
|
|
64
63
|
case WorkflowTarget.CREATE:
|
|
65
64
|
return theme.colors.textSuccess;
|
|
66
65
|
case WorkflowTarget.MODIFY:
|
|
@@ -78,8 +77,7 @@ export const getWorkflowTargetColor = (workflowTarget: WorkflowTarget, theme: Eu
|
|
|
78
77
|
};
|
|
79
78
|
|
|
80
79
|
export const getWorkflowTargetIconContent = (workflowTarget: WorkflowTarget) => {
|
|
81
|
-
|
|
82
|
-
switch (workflowTarget.toLocaleLowerCase()) {
|
|
80
|
+
switch (workflowTarget) {
|
|
83
81
|
case WorkflowTarget.CREATE:
|
|
84
82
|
return 'C';
|
|
85
83
|
case WorkflowTarget.SYSTEM:
|
|
@@ -19,7 +19,6 @@ export const WfoExpandingSearchRow = ({ score, matchingFields }: WfoExpandingSea
|
|
|
19
19
|
|
|
20
20
|
if (!matchingFields || matchingFields.length === 0) return null;
|
|
21
21
|
|
|
22
|
-
// Note: The row is shown on hover by the row above is as configured in the table component expandableRow config
|
|
23
22
|
return (
|
|
24
23
|
<tr css={hideExpandedRowStyle}>
|
|
25
24
|
<td colSpan={999} css={expandingSearchRowStyles}>
|
|
@@ -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,
|
|
@@ -3,7 +3,16 @@ import type { RuleGroupType } from 'react-querybuilder';
|
|
|
3
3
|
|
|
4
4
|
import { useTranslations } from 'next-intl';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
EuiButton,
|
|
8
|
+
EuiFlexGroup,
|
|
9
|
+
EuiFlexItem,
|
|
10
|
+
EuiFormRow,
|
|
11
|
+
EuiSelect,
|
|
12
|
+
EuiSpacer,
|
|
13
|
+
EuiSwitch,
|
|
14
|
+
EuiText,
|
|
15
|
+
} from '@elastic/eui';
|
|
7
16
|
|
|
8
17
|
import {
|
|
9
18
|
DEFAULT_PAGE_SIZE,
|
|
@@ -70,6 +79,8 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
|
|
|
70
79
|
tableColumnConfig: WfoStructuredSearchTableColumnConfig<T>;
|
|
71
80
|
rowExpandingConfiguration: WfoTableProps<T>['rowExpandingConfiguration'];
|
|
72
81
|
defaultHiddenColumns?: TableColumnKeys<T>;
|
|
82
|
+
defaultShowMatchDetails?: boolean;
|
|
83
|
+
defaultAdvancedNestedSearch?: boolean;
|
|
73
84
|
queryText?: string;
|
|
74
85
|
localStorageKey: string;
|
|
75
86
|
exportDataIsLoading?: boolean;
|
|
@@ -91,7 +102,7 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
|
|
|
91
102
|
onUpdateQueryBuilder: (ruleGroup: RuleGroupType | false) => void;
|
|
92
103
|
handleSearch: (searchParams?: SearchParams) => void;
|
|
93
104
|
pageSize: number;
|
|
94
|
-
setPageSize:
|
|
105
|
+
setPageSize: (updatedPageSize: number) => void;
|
|
95
106
|
totalItems: number | false;
|
|
96
107
|
hasNextPage: boolean;
|
|
97
108
|
prefilledFieldOptions: FieldToOperatorMap;
|
|
@@ -100,6 +111,8 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
|
|
|
100
111
|
export const WfoStructuredSearchTable = <T extends object>({
|
|
101
112
|
tableColumnConfig,
|
|
102
113
|
defaultHiddenColumns = [],
|
|
114
|
+
defaultShowMatchDetails = false,
|
|
115
|
+
defaultAdvancedNestedSearch = true,
|
|
103
116
|
queryText,
|
|
104
117
|
localStorageKey,
|
|
105
118
|
exportDataIsLoading,
|
|
@@ -136,6 +149,8 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
136
149
|
const [showTableSettingsModal, setShowTableSettingsModal] = useState(false);
|
|
137
150
|
const [rowDetailModalData, setRowDetailModalData] = useState<T | undefined>(undefined);
|
|
138
151
|
const [showInformationModal, setShowInformationModal] = useState(false);
|
|
152
|
+
const [showMatchDetails, setShowMatchDetails] = useState(defaultShowMatchDetails);
|
|
153
|
+
const [advancedNestedSearch, setAdvancedNestedSearch] = useState(defaultAdvancedNestedSearch);
|
|
139
154
|
const t = useTranslations('common');
|
|
140
155
|
|
|
141
156
|
useEffect(() => {
|
|
@@ -144,6 +159,14 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
144
159
|
}
|
|
145
160
|
}, [defaultHiddenColumns]);
|
|
146
161
|
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
setShowMatchDetails(defaultShowMatchDetails);
|
|
164
|
+
}, [defaultShowMatchDetails]);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
setAdvancedNestedSearch(defaultAdvancedNestedSearch);
|
|
168
|
+
}, [defaultAdvancedNestedSearch]);
|
|
169
|
+
|
|
147
170
|
useEffect(() => {
|
|
148
171
|
if (filterString) {
|
|
149
172
|
setIsFilterBuilderVisible(true);
|
|
@@ -182,6 +205,30 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
182
205
|
setTableConfigToLocalStorage(localStorageKey, {
|
|
183
206
|
hiddenColumns: updatedHiddenColumns,
|
|
184
207
|
selectedPageSize: updatedTableConfig.selectedPageSize,
|
|
208
|
+
showMatchDetails,
|
|
209
|
+
advancedNestedSearch,
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// The toggles apply live, so persist them immediately alongside the currently committed
|
|
214
|
+
// hidden columns and page size instead of waiting for the modal's "Update" action.
|
|
215
|
+
const handleToggleShowMatchDetails = (checked: boolean) => {
|
|
216
|
+
setShowMatchDetails(checked);
|
|
217
|
+
setTableConfigToLocalStorage(localStorageKey, {
|
|
218
|
+
hiddenColumns,
|
|
219
|
+
selectedPageSize: pageSize ?? DEFAULT_PAGE_SIZE,
|
|
220
|
+
showMatchDetails: checked,
|
|
221
|
+
advancedNestedSearch,
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const handleToggleAdvancedNestedSearch = (checked: boolean) => {
|
|
226
|
+
setAdvancedNestedSearch(checked);
|
|
227
|
+
setTableConfigToLocalStorage(localStorageKey, {
|
|
228
|
+
hiddenColumns,
|
|
229
|
+
selectedPageSize: pageSize ?? DEFAULT_PAGE_SIZE,
|
|
230
|
+
showMatchDetails,
|
|
231
|
+
advancedNestedSearch: checked,
|
|
185
232
|
});
|
|
186
233
|
};
|
|
187
234
|
|
|
@@ -189,6 +236,8 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
189
236
|
const defaultTableConfig = getDefaultTableConfig<T>(localStorageKey);
|
|
190
237
|
setHiddenColumns(defaultTableConfig.hiddenColumns);
|
|
191
238
|
setPageSize(defaultTableConfig.selectedPageSize);
|
|
239
|
+
setShowMatchDetails(defaultTableConfig.showMatchDetails ?? false);
|
|
240
|
+
setAdvancedNestedSearch(defaultTableConfig.advancedNestedSearch ?? false);
|
|
192
241
|
setShowTableSettingsModal(false);
|
|
193
242
|
clearTableConfigFromLocalStorage(localStorageKey);
|
|
194
243
|
};
|
|
@@ -243,6 +292,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
243
292
|
handleSearch={handleSearch}
|
|
244
293
|
onToggleFilterBuilder={setIsFilterBuilderVisible}
|
|
245
294
|
prefilledFieldOptions={prefilledFieldOptions}
|
|
295
|
+
useAdvancedNestedSearch={advancedNestedSearch}
|
|
246
296
|
/>
|
|
247
297
|
</>
|
|
248
298
|
)}
|
|
@@ -255,11 +305,13 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
255
305
|
columnConfig={tableColumnsWithControlColumns}
|
|
256
306
|
hiddenColumns={hiddenColumns}
|
|
257
307
|
rowExpandingConfiguration={rowExpandingConfiguration}
|
|
308
|
+
showExpandedRows={showMatchDetails}
|
|
258
309
|
onUpdateDataSorting={onUpdateDataSorting}
|
|
259
310
|
onUpdateDataSearch={handleColumnFilterSearch}
|
|
260
311
|
dataSorting={dataSorting}
|
|
261
312
|
data={data}
|
|
262
313
|
isLoading={isLoading}
|
|
314
|
+
loadingSkeletonRowCount={pageSize}
|
|
263
315
|
{...tableProps}
|
|
264
316
|
/>
|
|
265
317
|
|
|
@@ -284,6 +336,24 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
284
336
|
onResetToDefaults={handleResetToDefaults}
|
|
285
337
|
extraSettings={
|
|
286
338
|
<>
|
|
339
|
+
<EuiFormRow label={t('showMatchDetails')} display="columnCompressed">
|
|
340
|
+
<EuiSwitch
|
|
341
|
+
showLabel={false}
|
|
342
|
+
label={t('showMatchDetails')}
|
|
343
|
+
checked={showMatchDetails}
|
|
344
|
+
onChange={(event) => handleToggleShowMatchDetails(event.target.checked)}
|
|
345
|
+
compressed
|
|
346
|
+
/>
|
|
347
|
+
</EuiFormRow>
|
|
348
|
+
<EuiFormRow label={t('advancedNestedSearch')} display="columnCompressed">
|
|
349
|
+
<EuiSwitch
|
|
350
|
+
showLabel={false}
|
|
351
|
+
label={t('advancedNestedSearch')}
|
|
352
|
+
checked={advancedNestedSearch}
|
|
353
|
+
onChange={(event) => handleToggleAdvancedNestedSearch(event.target.checked)}
|
|
354
|
+
compressed
|
|
355
|
+
/>
|
|
356
|
+
</EuiFormRow>
|
|
287
357
|
<EuiFormRow label={t('retrieval')} display="columnCompressed">
|
|
288
358
|
<EuiSelect
|
|
289
359
|
options={[
|
|
@@ -301,7 +371,7 @@ export const WfoStructuredSearchTable = <T extends object>({
|
|
|
301
371
|
<>
|
|
302
372
|
<EuiSpacer size="m" />
|
|
303
373
|
<EuiButton isLoading={exportDataIsLoading} onClick={() => onExportData()} fullWidth>
|
|
304
|
-
{t('export')}
|
|
374
|
+
{totalItems ? t('exportRows', { numberOfRows: totalItems }) : t('export')}
|
|
305
375
|
</EuiButton>
|
|
306
376
|
</>
|
|
307
377
|
)}
|