@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
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { RuleGroupType } from 'react-querybuilder';
|
|
3
|
+
import { formatQuery } from 'react-querybuilder/formatQuery';
|
|
4
|
+
import { parseCEL } from 'react-querybuilder/parseCEL';
|
|
5
|
+
|
|
6
|
+
import { useTranslations } from 'next-intl';
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
import { StringParam, useQueryParam, withDefault } from 'use-query-params';
|
|
9
|
+
|
|
10
|
+
import { EuiSpacer } from '@elastic/eui';
|
|
11
|
+
|
|
12
|
+
import { SearchParams, combineSearchFilters } from '@/components';
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_PAGE_SIZE,
|
|
15
|
+
StoredTableConfig,
|
|
16
|
+
SubscriptionListItem,
|
|
17
|
+
WfoContentHeader,
|
|
18
|
+
WfoDataSorting,
|
|
19
|
+
WfoDateTime,
|
|
20
|
+
WfoExpandingSearchRow,
|
|
21
|
+
WfoFilterTabs,
|
|
22
|
+
WfoFirstPartUUID,
|
|
23
|
+
WfoInlineJson,
|
|
24
|
+
WfoInsyncIcon,
|
|
25
|
+
WfoJsonCodeBlock,
|
|
26
|
+
WfoStructuredSearchTable,
|
|
27
|
+
WfoStructuredSearchTableColumnConfig,
|
|
28
|
+
WfoSubscriptionActions,
|
|
29
|
+
WfoSubscriptionListTab,
|
|
30
|
+
WfoSubscriptionNoteEdit,
|
|
31
|
+
WfoSubscriptionStatusBadge,
|
|
32
|
+
WfoTableColumnConfig,
|
|
33
|
+
subscriptionListTabs,
|
|
34
|
+
} from '@/components';
|
|
35
|
+
import { parseCelToRuleGroup } from '@/components/WfoTable/WfoStructuredSearchTable/utils';
|
|
36
|
+
import { ColumnType, WfoTableProps } from '@/components/WfoTable/WfoTable';
|
|
37
|
+
import { useStoredTableConfig } from '@/hooks';
|
|
38
|
+
import { SearchPayload, useLazySearchQuery, useSearchQuery } from '@/rtk';
|
|
39
|
+
import {
|
|
40
|
+
EntityKind,
|
|
41
|
+
FieldToOperatorMap,
|
|
42
|
+
PaginatedSearchResults,
|
|
43
|
+
ResultColumToPropertyMap,
|
|
44
|
+
RetrieverType,
|
|
45
|
+
SortOrder,
|
|
46
|
+
} from '@/types';
|
|
47
|
+
import { getCsvFileNameWithDate, initiateCsvFileDownload, parseDateToLocaleDateTimeString } from '@/utils';
|
|
48
|
+
|
|
49
|
+
const SEARCH_TABLE_LOCAL_STORAGE_KEY = 'SEARCH_TABLE_LOCAL_STORAGE_KEY';
|
|
50
|
+
|
|
51
|
+
const getKeyByValueFromMap = <T,>(resultColumToPropertyMap: ResultColumToPropertyMap<T>, field: keyof T) => {
|
|
52
|
+
return [...resultColumToPropertyMap.entries()].find(([, v]) => v === field)?.[0] || '';
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getDataFromResponse = <T extends object>(
|
|
56
|
+
data: PaginatedSearchResults,
|
|
57
|
+
resultColumToPropertyMap: ResultColumToPropertyMap<T>,
|
|
58
|
+
uniqueRowId: keyof T,
|
|
59
|
+
): {
|
|
60
|
+
items: T[];
|
|
61
|
+
rowExpandingConfiguration?: WfoTableProps<T>['rowExpandingConfiguration'];
|
|
62
|
+
} => {
|
|
63
|
+
const searchResult = data?.data;
|
|
64
|
+
if (!searchResult)
|
|
65
|
+
return {
|
|
66
|
+
items: [],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const responseColumns: Record<string, string | number | null>[] =
|
|
70
|
+
searchResult.map(({ response_columns }) => response_columns) || [];
|
|
71
|
+
|
|
72
|
+
const rowExpandingConfiguration: WfoTableProps<T>['rowExpandingConfiguration'] = {
|
|
73
|
+
uniqueRowId: uniqueRowId as keyof WfoTableColumnConfig<T>,
|
|
74
|
+
uniqueRowIdToExpandedRowMap: searchResult.reduce(
|
|
75
|
+
(rowMap, { response_columns, score, perfect_match, matching_field }) => {
|
|
76
|
+
const idColumnInResponseColumn = getKeyByValueFromMap<T>(resultColumToPropertyMap, uniqueRowId);
|
|
77
|
+
const rowId = response_columns[idColumnInResponseColumn];
|
|
78
|
+
if (rowId) {
|
|
79
|
+
rowMap[rowId] = (
|
|
80
|
+
<WfoExpandingSearchRow score={score} matchingField={matching_field} perfectMatch={perfect_match} />
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return rowMap;
|
|
84
|
+
},
|
|
85
|
+
{} as Record<string, ReactNode>,
|
|
86
|
+
),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const items: T[] = responseColumns.map((responseColumn) => {
|
|
90
|
+
const item = Object.entries(responseColumn).reduce((acc, [key, value]) => {
|
|
91
|
+
const itemKey = resultColumToPropertyMap.get(key);
|
|
92
|
+
if (itemKey) {
|
|
93
|
+
acc[itemKey] = value as unknown as T[keyof T];
|
|
94
|
+
}
|
|
95
|
+
return acc;
|
|
96
|
+
}, {} as T);
|
|
97
|
+
return item;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
items,
|
|
102
|
+
rowExpandingConfiguration,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getTotalItemsFromResponse = (data: PaginatedSearchResults | undefined): number | false => {
|
|
107
|
+
return data?.cursor?.total_items || false;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const resultColumToPropertyMap: ResultColumToPropertyMap<SubscriptionListItem> = new Map([
|
|
111
|
+
['subscription.subscription_id', 'subscriptionId'],
|
|
112
|
+
['subscription.description', 'description'],
|
|
113
|
+
['subscription.status', 'status'],
|
|
114
|
+
['subscription.insync', 'insync'],
|
|
115
|
+
['subscription.product.name', 'productName'],
|
|
116
|
+
['subscription.product.tag', 'tag'],
|
|
117
|
+
['subscription.customer_name', 'customerFullname'],
|
|
118
|
+
['subscription.customer_abbreviation', 'customerShortcode'],
|
|
119
|
+
['subscription.start_date', 'startDate'],
|
|
120
|
+
['subscription.end_date', 'endDate'],
|
|
121
|
+
['subscription.note', 'note'],
|
|
122
|
+
['subscription.metadata', 'metadata'],
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
/* These options will be added as the first options in the field dropdown in the FieldSelector */
|
|
126
|
+
const prefilledFieldOptions: FieldToOperatorMap = new Map([
|
|
127
|
+
['subscription.subscription_id', ['eq', 'neq', 'like']],
|
|
128
|
+
['subscription.description', ['eq', 'neq', 'like']],
|
|
129
|
+
['subscription.status', ['eq', 'neq', 'like']],
|
|
130
|
+
['subscription.insync', ['eq', 'neq']],
|
|
131
|
+
['subscription.product.name', ['eq', 'neq', 'like']],
|
|
132
|
+
['subscription.product.tag', ['eq', 'neq', 'like']],
|
|
133
|
+
['subscription.customer_name', ['eq', 'neq', 'like']],
|
|
134
|
+
['subscription.customer_abbreviation', ['eq', 'neq', 'like']],
|
|
135
|
+
['subscription.start_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
136
|
+
['subscription.end_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
137
|
+
['subscription.note', ['eq', 'neq', 'like']],
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
export const WfoSearchPocPage = () => {
|
|
141
|
+
const t = useTranslations('subscriptions.index');
|
|
142
|
+
const [activeTab, setActiveTab] = useQueryParam('activeTab', withDefault(StringParam, WfoSubscriptionListTab.ACTIVE));
|
|
143
|
+
const selectedTab = subscriptionListTabs.find(({ id }) => id === activeTab)?.id ?? WfoSubscriptionListTab.ACTIVE;
|
|
144
|
+
const getStoredTableConfig = useStoredTableConfig<SubscriptionListItem>(SEARCH_TABLE_LOCAL_STORAGE_KEY);
|
|
145
|
+
const [retrieverType, setRetrieverType] = useState<RetrieverType>(RetrieverType.Auto);
|
|
146
|
+
|
|
147
|
+
// Part of the search endpoint payload that is passed in the q parameter
|
|
148
|
+
const [queryText, setQueryText] = useState<string>('');
|
|
149
|
+
// The committed query and filter live in the URL so a link reproduces the search and browser
|
|
150
|
+
// back/forward re-runs it. The filter is stored as a CEL string and parsed back to a rule group.
|
|
151
|
+
const [committedSearchQuery, setCommittedSearchQuery] = useQueryParam('queryString', withDefault(StringParam, ''));
|
|
152
|
+
const [committedFilterString, setCommittedFilterString] = useQueryParam('filterString', withDefault(StringParam, ''));
|
|
153
|
+
// Track the last value this page committed, so the URL->state sync effects below only rebuild the
|
|
154
|
+
// inputs for external changes (page load, back/forward). Rebuilding on own commits would revert
|
|
155
|
+
// characters typed right after committing (search bar) or visibly restructure builder rules the
|
|
156
|
+
// CEL round trip cannot preserve, such as 'between'. The refs hold the value as the params decode
|
|
157
|
+
// it (absent param -> ''); the setters receive undefined to remove an empty param from the URL.
|
|
158
|
+
const lastSelfCommittedFilter = useRef('');
|
|
159
|
+
const commitFilterString = (celString: string) => {
|
|
160
|
+
lastSelfCommittedFilter.current = celString;
|
|
161
|
+
setCommittedFilterString(celString || undefined);
|
|
162
|
+
};
|
|
163
|
+
const lastSelfCommittedQuery = useRef('');
|
|
164
|
+
const commitSearchQuery = (queryText: string) => {
|
|
165
|
+
lastSelfCommittedQuery.current = queryText;
|
|
166
|
+
setCommittedSearchQuery(queryText || undefined);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// String that is displayed in the filter textarea. This is transformed and if valid passed to the search endpoint in the filter parameter
|
|
170
|
+
const [filterString, setFilterString] = useState<string>('');
|
|
171
|
+
const [queryBuilderRuleGroup, setQueryBuilderRuleGroup] = useState<RuleGroupType | undefined>();
|
|
172
|
+
const committedRuleGroup = useMemo<RuleGroupType | undefined>(
|
|
173
|
+
() => parseCelToRuleGroup(committedFilterString),
|
|
174
|
+
[committedFilterString],
|
|
175
|
+
);
|
|
176
|
+
const [isValidFilterString, setIsValidFilterString] = useState<boolean>(true);
|
|
177
|
+
const [tableDefaults, setTableDefaults] = useState<StoredTableConfig<SubscriptionListItem>>();
|
|
178
|
+
const [pageSize, setPageSize] = useState<number>(tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE);
|
|
179
|
+
const [limit, setLimit] = useState<number>(pageSize);
|
|
180
|
+
const [pageCursor, setPageCursor] = useState<{ cursor: string; searchKey: string } | undefined>(undefined);
|
|
181
|
+
const [dataSorting, setDataSorting] = useState<WfoDataSorting<SubscriptionListItem>>({
|
|
182
|
+
field: 'subscriptionId',
|
|
183
|
+
sortOrder: SortOrder.DESC,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// A next-page cursor is only valid for the committed search that produced it. Binding it to a key
|
|
187
|
+
// of that search covers commits that bypass the handlers below, e.g. browser back/forward changing
|
|
188
|
+
// the queryString/filterString/activeTab URL params: the key no longer matches, so the stale
|
|
189
|
+
// cursor is not sent along with the new search.
|
|
190
|
+
const committedSearchKey = JSON.stringify([
|
|
191
|
+
committedSearchQuery,
|
|
192
|
+
committedFilterString,
|
|
193
|
+
selectedTab,
|
|
194
|
+
retrieverType,
|
|
195
|
+
dataSorting,
|
|
196
|
+
]);
|
|
197
|
+
const cursor = pageCursor?.searchKey === committedSearchKey ? pageCursor.cursor : undefined;
|
|
198
|
+
|
|
199
|
+
// The search payload is derived entirely from the committed search state, which lives in the URL
|
|
200
|
+
// (queryString, filterString and activeTab params). Changing any of these values — whether via the
|
|
201
|
+
// UI or a browser back/forward that updates the URL — produces a new payload and RTK Query re-runs
|
|
202
|
+
// the search automatically. No imperative trigger or effect (and no eslint-disable) is needed.
|
|
203
|
+
// Cursor is stripped from the RTK cache key, so a cursor change appends to the accumulated
|
|
204
|
+
// result set instead of creating a new cache entry — see search endpoint's merge() logic.
|
|
205
|
+
const searchPayload: SearchPayload = useMemo(() => {
|
|
206
|
+
const filters = combineSearchFilters(committedRuleGroup, selectedTab);
|
|
207
|
+
const order_by = {
|
|
208
|
+
element: getKeyByValueFromMap(resultColumToPropertyMap, dataSorting.field),
|
|
209
|
+
direction: dataSorting.sortOrder.toLowerCase(),
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
query: committedSearchQuery,
|
|
213
|
+
limit,
|
|
214
|
+
entity_type: EntityKind.SUBSCRIPTION,
|
|
215
|
+
response_columns: Array.from(resultColumToPropertyMap.keys()),
|
|
216
|
+
order_by,
|
|
217
|
+
...(retrieverType !== RetrieverType.Auto && { retriever: retrieverType }),
|
|
218
|
+
...(filters && { filters }),
|
|
219
|
+
...(cursor && { cursor }),
|
|
220
|
+
};
|
|
221
|
+
}, [committedSearchQuery, committedRuleGroup, selectedTab, retrieverType, limit, dataSorting, cursor]);
|
|
222
|
+
|
|
223
|
+
const { data, isFetching } = useSearchQuery(searchPayload);
|
|
224
|
+
|
|
225
|
+
const [getSubscriptionListTrigger] = useLazySearchQuery();
|
|
226
|
+
const getSubscriptionListForExport = () => getSubscriptionListTrigger(searchPayload).unwrap();
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
const storedConfig = getStoredTableConfig();
|
|
230
|
+
if (storedConfig) {
|
|
231
|
+
setTableDefaults(storedConfig);
|
|
232
|
+
}
|
|
233
|
+
}, [getStoredTableConfig]);
|
|
234
|
+
|
|
235
|
+
const tableColumnConfig: WfoStructuredSearchTableColumnConfig<SubscriptionListItem> = {
|
|
236
|
+
actions: {
|
|
237
|
+
columnType: ColumnType.CONTROL,
|
|
238
|
+
width: '50px',
|
|
239
|
+
renderControl: (row) => <WfoSubscriptionActions compactMode={true} subscriptionId={row.subscriptionId} />,
|
|
240
|
+
},
|
|
241
|
+
subscriptionId: {
|
|
242
|
+
columnType: ColumnType.DATA,
|
|
243
|
+
label: t('id'),
|
|
244
|
+
width: '100px',
|
|
245
|
+
renderData: (value) => <WfoFirstPartUUID UUID={value} />,
|
|
246
|
+
renderDetails: (value) => value,
|
|
247
|
+
renderTooltip: (value) => value,
|
|
248
|
+
isSortable: true,
|
|
249
|
+
isFilterable: true,
|
|
250
|
+
},
|
|
251
|
+
description: {
|
|
252
|
+
columnType: ColumnType.DATA,
|
|
253
|
+
label: t('description'),
|
|
254
|
+
width: '500px',
|
|
255
|
+
renderData: (value, record) => <Link href={`/subscriptions/${record.subscriptionId}`}>{value}</Link>,
|
|
256
|
+
renderTooltip: (value) => value,
|
|
257
|
+
isFilterable: true,
|
|
258
|
+
},
|
|
259
|
+
status: {
|
|
260
|
+
columnType: ColumnType.DATA,
|
|
261
|
+
label: t('status'),
|
|
262
|
+
width: '120px',
|
|
263
|
+
renderData: (value) => <WfoSubscriptionStatusBadge status={value} />,
|
|
264
|
+
isFilterable: true,
|
|
265
|
+
},
|
|
266
|
+
insync: {
|
|
267
|
+
columnType: ColumnType.DATA,
|
|
268
|
+
label: t('insync'),
|
|
269
|
+
width: '75px',
|
|
270
|
+
renderData: (value) => <WfoInsyncIcon inSync={value} />,
|
|
271
|
+
isFilterable: true,
|
|
272
|
+
},
|
|
273
|
+
productName: {
|
|
274
|
+
columnType: ColumnType.DATA,
|
|
275
|
+
width: '260px',
|
|
276
|
+
label: t('product'),
|
|
277
|
+
isFilterable: true,
|
|
278
|
+
},
|
|
279
|
+
tag: {
|
|
280
|
+
columnType: ColumnType.DATA,
|
|
281
|
+
label: t('tag'),
|
|
282
|
+
width: '100px',
|
|
283
|
+
isFilterable: true,
|
|
284
|
+
},
|
|
285
|
+
customerFullname: {
|
|
286
|
+
columnType: ColumnType.DATA,
|
|
287
|
+
label: t('customerFullname'),
|
|
288
|
+
isFilterable: true,
|
|
289
|
+
},
|
|
290
|
+
customerShortcode: {
|
|
291
|
+
columnType: ColumnType.DATA,
|
|
292
|
+
label: t('customerShortcode'),
|
|
293
|
+
width: '150px',
|
|
294
|
+
isFilterable: true,
|
|
295
|
+
},
|
|
296
|
+
startDate: {
|
|
297
|
+
columnType: ColumnType.DATA,
|
|
298
|
+
label: t('startDate'),
|
|
299
|
+
width: '100px',
|
|
300
|
+
renderData: (value) => <WfoDateTime dateOrIsoString={value} />,
|
|
301
|
+
renderDetails: parseDateToLocaleDateTimeString,
|
|
302
|
+
clipboardText: parseDateToLocaleDateTimeString,
|
|
303
|
+
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
304
|
+
isFilterable: true,
|
|
305
|
+
},
|
|
306
|
+
endDate: {
|
|
307
|
+
columnType: ColumnType.DATA,
|
|
308
|
+
label: t('endDate'),
|
|
309
|
+
width: '100px',
|
|
310
|
+
renderData: (value) => <WfoDateTime dateOrIsoString={value} />,
|
|
311
|
+
renderDetails: parseDateToLocaleDateTimeString,
|
|
312
|
+
clipboardText: parseDateToLocaleDateTimeString,
|
|
313
|
+
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
314
|
+
isFilterable: true,
|
|
315
|
+
},
|
|
316
|
+
note: {
|
|
317
|
+
columnType: ColumnType.DATA,
|
|
318
|
+
label: t('note'),
|
|
319
|
+
width: '300px',
|
|
320
|
+
renderData: (cellValue, row) => {
|
|
321
|
+
return (
|
|
322
|
+
<WfoSubscriptionNoteEdit
|
|
323
|
+
onlyShowOnHover={true}
|
|
324
|
+
endpointName={''}
|
|
325
|
+
queryVariables={{}}
|
|
326
|
+
subscriptionId={row.subscriptionId}
|
|
327
|
+
note={cellValue}
|
|
328
|
+
/>
|
|
329
|
+
);
|
|
330
|
+
},
|
|
331
|
+
isFilterable: true,
|
|
332
|
+
},
|
|
333
|
+
metadata: {
|
|
334
|
+
columnType: ColumnType.DATA,
|
|
335
|
+
label: t('metadata'),
|
|
336
|
+
width: '100px',
|
|
337
|
+
renderData: (value) => <WfoInlineJson data={value} />,
|
|
338
|
+
renderDetails: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle />,
|
|
339
|
+
renderTooltip: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle={false} />,
|
|
340
|
+
isFilterable: true,
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const handleApplyFilter = (searchParams?: SearchParams) => {
|
|
345
|
+
const ruleGroupParam = searchParams?.ruleGroup;
|
|
346
|
+
// Use an explicitly passed rule group when provided (e.g. a column-header search), a cleared filter
|
|
347
|
+
// when `false`, and otherwise the current query builder state (the "Apply filter" button).
|
|
348
|
+
const effectiveRuleGroup = ruleGroupParam === false ? undefined : (ruleGroupParam ?? queryBuilderRuleGroup);
|
|
349
|
+
// '' (no rule group, or only placeholder rules) commits an empty filter, clearing the URL param.
|
|
350
|
+
const celQuery =
|
|
351
|
+
effectiveRuleGroup ? formatQuery(effectiveRuleGroup, { format: 'cel', fallbackExpression: '' }) : '';
|
|
352
|
+
// A non-empty CEL string must survive the round trip through the URL: formatQuery escapes double
|
|
353
|
+
// quotes in values but parseCEL has no escape support, so such a filter would silently be dropped
|
|
354
|
+
// after committing. Refuse the commit and flag the filter instead, keeping the URL and the search
|
|
355
|
+
// results consistent.
|
|
356
|
+
if (celQuery && !parseCelToRuleGroup(celQuery)) {
|
|
357
|
+
setIsValidFilterString(false);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
commitFilterString(celQuery);
|
|
361
|
+
setPageCursor(undefined);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const onChangeQueryText = (queryText: string) => {
|
|
365
|
+
setQueryText(queryText);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const onSearchQueryText = (queryText: string) => {
|
|
369
|
+
setQueryText(queryText);
|
|
370
|
+
commitSearchQuery(queryText);
|
|
371
|
+
setPageCursor(undefined);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const onUpdateRetrieverType = (retrieverType: RetrieverType) => {
|
|
375
|
+
setRetrieverType(retrieverType);
|
|
376
|
+
setPageCursor(undefined);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const handleChangeTab = (updatedTab: WfoSubscriptionListTab) => {
|
|
380
|
+
setActiveTab(updatedTab);
|
|
381
|
+
setLimit(pageSize);
|
|
382
|
+
setPageCursor(undefined);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const safeCelParse = useCallback((celString: string) => {
|
|
386
|
+
try {
|
|
387
|
+
const ruleGroup = parseCEL(celString);
|
|
388
|
+
if (celString === '') {
|
|
389
|
+
setIsValidFilterString(true);
|
|
390
|
+
} else if (ruleGroup?.rules?.length > 0) {
|
|
391
|
+
// parseCEL returns a query object — check if it has any rules
|
|
392
|
+
setIsValidFilterString(true);
|
|
393
|
+
setQueryBuilderRuleGroup(ruleGroup);
|
|
394
|
+
} else {
|
|
395
|
+
// If there are no rules created based on this string then
|
|
396
|
+
// we assume the string is not valid. In any case it will not do anything
|
|
397
|
+
// to the search results
|
|
398
|
+
setIsValidFilterString(false);
|
|
399
|
+
}
|
|
400
|
+
} catch {
|
|
401
|
+
setIsValidFilterString(false);
|
|
402
|
+
}
|
|
403
|
+
}, []);
|
|
404
|
+
|
|
405
|
+
// Populate the search bar and the filter builder from the URL, both on page load and when
|
|
406
|
+
// back/forward navigation changes the committed search. Commits made by this page are skipped —
|
|
407
|
+
// see the lastSelfCommitted refs above.
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (committedSearchQuery === lastSelfCommittedQuery.current) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
lastSelfCommittedQuery.current = committedSearchQuery;
|
|
413
|
+
setQueryText(committedSearchQuery);
|
|
414
|
+
}, [committedSearchQuery]);
|
|
415
|
+
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
if (committedFilterString === lastSelfCommittedFilter.current) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
lastSelfCommittedFilter.current = committedFilterString;
|
|
421
|
+
setFilterString(committedFilterString);
|
|
422
|
+
if (committedFilterString) {
|
|
423
|
+
safeCelParse(committedFilterString);
|
|
424
|
+
} else {
|
|
425
|
+
setQueryBuilderRuleGroup(undefined);
|
|
426
|
+
setIsValidFilterString(true);
|
|
427
|
+
}
|
|
428
|
+
}, [committedFilterString, safeCelParse]);
|
|
429
|
+
|
|
430
|
+
const onUpdateQueryBuilder = (ruleGroup: RuleGroupType | false) => {
|
|
431
|
+
if (ruleGroup === false) {
|
|
432
|
+
setQueryBuilderRuleGroup(undefined);
|
|
433
|
+
setFilterString('');
|
|
434
|
+
setIsValidFilterString(true);
|
|
435
|
+
} else {
|
|
436
|
+
setQueryBuilderRuleGroup({ ...ruleGroup });
|
|
437
|
+
const celQuery = formatQuery({ ...ruleGroup }, { format: 'cel', fallbackExpression: '' });
|
|
438
|
+
// 1 == 1 indicates the query can't be parsed. This is a fallback to allow it to still be used as
|
|
439
|
+
// part of other queries.
|
|
440
|
+
if (!celQuery || celQuery === '1 == 1') {
|
|
441
|
+
setFilterString('');
|
|
442
|
+
setIsValidFilterString(true);
|
|
443
|
+
} else {
|
|
444
|
+
setFilterString(celQuery);
|
|
445
|
+
setIsValidFilterString(true);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const onUpdateFilterString = (filterString: string) => {
|
|
451
|
+
setFilterString(filterString);
|
|
452
|
+
safeCelParse(filterString);
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const { items: subscriptionListItems, rowExpandingConfiguration } =
|
|
456
|
+
data ? getDataFromResponse<SubscriptionListItem>(data, resultColumToPropertyMap, 'subscriptionId') : { items: [] };
|
|
457
|
+
|
|
458
|
+
const totalItems = getTotalItemsFromResponse(data);
|
|
459
|
+
const hasNextPage = data?.page_info?.has_next_page ?? false;
|
|
460
|
+
const nextPageCursor = data?.page_info?.next_page_cursor ?? undefined;
|
|
461
|
+
|
|
462
|
+
const exportData = async () => {
|
|
463
|
+
const exportResult = await getSubscriptionListForExport();
|
|
464
|
+
const { items: exportItems } = getDataFromResponse<SubscriptionListItem>(
|
|
465
|
+
exportResult,
|
|
466
|
+
resultColumToPropertyMap,
|
|
467
|
+
'subscriptionId',
|
|
468
|
+
);
|
|
469
|
+
if (!exportItems.length) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
initiateCsvFileDownload(exportItems, Object.keys(tableColumnConfig), getCsvFileNameWithDate('Subscriptions'));
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const onShowMore = () => {
|
|
476
|
+
if (!isFetching && nextPageCursor) {
|
|
477
|
+
setPageCursor({ cursor: nextPageCursor, searchKey: committedSearchKey });
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const onUpdateDataSorting = ({ field, sortOrder }: WfoDataSorting<SubscriptionListItem>) => {
|
|
482
|
+
setDataSorting({ field, sortOrder });
|
|
483
|
+
setLimit(pageSize);
|
|
484
|
+
setPageCursor(undefined);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
return (
|
|
488
|
+
<>
|
|
489
|
+
<WfoContentHeader title="Subscriptions (Beta)" />
|
|
490
|
+
<WfoFilterTabs
|
|
491
|
+
tabs={subscriptionListTabs}
|
|
492
|
+
selectedTab={selectedTab}
|
|
493
|
+
translationNamespace="subscriptions.tabs"
|
|
494
|
+
onChangeTab={handleChangeTab}
|
|
495
|
+
/>
|
|
496
|
+
<EuiSpacer size="l" />
|
|
497
|
+
<WfoStructuredSearchTable<SubscriptionListItem>
|
|
498
|
+
data={subscriptionListItems}
|
|
499
|
+
rowExpandingConfiguration={rowExpandingConfiguration}
|
|
500
|
+
defaultHiddenColumns={tableDefaults?.hiddenColumns}
|
|
501
|
+
filterString={filterString}
|
|
502
|
+
handleSearch={handleApplyFilter}
|
|
503
|
+
isLoading={isFetching}
|
|
504
|
+
dataSorting={[dataSorting]}
|
|
505
|
+
isValidFilterString={isValidFilterString}
|
|
506
|
+
localStorageKey={SEARCH_TABLE_LOCAL_STORAGE_KEY}
|
|
507
|
+
onUpdateFilterString={onUpdateFilterString}
|
|
508
|
+
onUpdateQueryBuilder={onUpdateQueryBuilder}
|
|
509
|
+
onChangeQueryText={onChangeQueryText}
|
|
510
|
+
onSearchQueryText={onSearchQueryText}
|
|
511
|
+
onShowMore={onShowMore}
|
|
512
|
+
onUpdateRetrieverType={onUpdateRetrieverType}
|
|
513
|
+
queryBuilderRuleGroup={queryBuilderRuleGroup}
|
|
514
|
+
queryText={queryText}
|
|
515
|
+
retrieverType={retrieverType}
|
|
516
|
+
tableColumnConfig={tableColumnConfig}
|
|
517
|
+
getColumnSearchFieldName={(field) => getKeyByValueFromMap(resultColumToPropertyMap, field)}
|
|
518
|
+
pageSize={pageSize}
|
|
519
|
+
onUpdateDataSorting={onUpdateDataSorting}
|
|
520
|
+
setPageSize={setPageSize}
|
|
521
|
+
totalItems={totalItems}
|
|
522
|
+
hasNextPage={hasNextPage}
|
|
523
|
+
prefilledFieldOptions={prefilledFieldOptions}
|
|
524
|
+
onExportData={exportData}
|
|
525
|
+
/>
|
|
526
|
+
</>
|
|
527
|
+
);
|
|
528
|
+
};
|
package/src/pages/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
TimelineItem,
|
|
12
12
|
WfoIsAllowedToRender,
|
|
13
13
|
WfoLoading,
|
|
14
|
-
WfoProcessListNoteEdit,
|
|
15
14
|
WfoTimeline,
|
|
16
15
|
WfoTitleWithWebsocketBadge,
|
|
17
16
|
} from '@/components';
|
|
@@ -181,15 +180,16 @@ export const WfoProcessDetail = ({
|
|
|
181
180
|
return (
|
|
182
181
|
<>
|
|
183
182
|
<WfoContentHeader
|
|
184
|
-
title={
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
183
|
+
title={
|
|
184
|
+
<WfoTitleWithWebsocketBadge
|
|
185
|
+
title={pageTitle}
|
|
186
|
+
extraElement={
|
|
187
|
+
<WfoProductInformationWithLink
|
|
188
|
+
productNames={productNames}
|
|
189
|
+
workflowName={processDetail?.workflowName ?? ''}
|
|
190
|
+
/>
|
|
191
|
+
}
|
|
192
|
+
/>
|
|
193
193
|
}
|
|
194
194
|
>
|
|
195
195
|
<WfoIsAllowedToRender resource={PolicyResource.PROCESS_RETRY}>
|
|
@@ -59,6 +59,7 @@ export const WfoProcessDetailPage = ({ processId }: WfoProcessDetailPageProps) =
|
|
|
59
59
|
ref={stepListRef}
|
|
60
60
|
lastStatus={processDetail.lastStatus}
|
|
61
61
|
processId={processDetail.processId}
|
|
62
|
+
workflowName={processDetail.workflowName}
|
|
62
63
|
steps={groupedSteps.flatMap((groupedStep) => groupedStep.steps)}
|
|
63
64
|
traceBack={processDetail.traceback}
|
|
64
65
|
userInputForm={processDetail.form}
|
|
@@ -6,7 +6,7 @@ import { useRouter } from 'next/router';
|
|
|
6
6
|
|
|
7
7
|
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiText } from '@elastic/eui';
|
|
8
8
|
|
|
9
|
-
import { WfoError,
|
|
9
|
+
import { WfoError, WfoPageWithUserGuide } from '@/components';
|
|
10
10
|
import { WfoPydanticForm } from '@/components/WfoPydanticForm';
|
|
11
11
|
import { WfoStepStatusIcon } from '@/components/WfoWorkflowSteps';
|
|
12
12
|
import { getWorkflowStepsStyles } from '@/components/WfoWorkflowSteps/styles';
|
|
@@ -140,25 +140,21 @@ export const WfoStartProcessPage = ({ processName, isTask = false }: WfoStartPro
|
|
|
140
140
|
timelineItems={timeLineItems}
|
|
141
141
|
isLoading={isLoading}
|
|
142
142
|
>
|
|
143
|
-
<
|
|
144
|
-
css={{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
<EuiText>{t(isTask ? 'submitTaskFormLabel' : 'submitWorkflowFormLabel')}</EuiText>
|
|
155
|
-
</EuiFlexItem>
|
|
156
|
-
</EuiFlexGroup>
|
|
157
|
-
<EuiHorizontalRule />
|
|
158
|
-
{(hasError && <WfoError />) || (
|
|
143
|
+
<WfoPageWithUserGuide workflowName={processName}>
|
|
144
|
+
<EuiPanel css={{ backgroundColor: theme.colors.backgroundBaseNeutral }}>
|
|
145
|
+
<EuiFlexGroup gutterSize="none" css={getStepHeaderStyle(false)}>
|
|
146
|
+
<WfoStepStatusIcon stepStatus={StepStatus.FORM} />
|
|
147
|
+
<EuiFlexItem grow={0}>
|
|
148
|
+
<EuiText css={stepListContentBoldTextStyle}>{t('userInput')}</EuiText>
|
|
149
|
+
<EuiText>{t(isTask ? 'submitTaskFormLabel' : 'submitWorkflowFormLabel')}</EuiText>
|
|
150
|
+
</EuiFlexItem>
|
|
151
|
+
</EuiFlexGroup>
|
|
152
|
+
<EuiHorizontalRule />
|
|
153
|
+
{(hasError && <WfoError />) || (
|
|
159
154
|
<WfoPydanticForm processName={processName} startProcessPayload={startProcessPayload} isTask={isTask} />
|
|
160
|
-
)
|
|
161
|
-
|
|
155
|
+
)}
|
|
156
|
+
</EuiPanel>
|
|
157
|
+
</WfoPageWithUserGuide>
|
|
162
158
|
</WfoProcessDetail>
|
|
163
159
|
);
|
|
164
160
|
};
|
package/src/rtk/api.ts
CHANGED