@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import type { RuleGroupType } from 'react-querybuilder';
|
|
3
3
|
import { formatQuery } from 'react-querybuilder/formatQuery';
|
|
4
4
|
import { parseCEL } from 'react-querybuilder/parseCEL';
|
|
@@ -9,7 +9,7 @@ import { StringParam, useQueryParam, withDefault } from 'use-query-params';
|
|
|
9
9
|
|
|
10
10
|
import { EuiSpacer } from '@elastic/eui';
|
|
11
11
|
|
|
12
|
-
import { SearchParams,
|
|
12
|
+
import { SearchParams, addStatusFilterFromTab, removeTabStatusMatchingFields } from '@/components';
|
|
13
13
|
import {
|
|
14
14
|
DEFAULT_PAGE_SIZE,
|
|
15
15
|
StoredTableConfig,
|
|
@@ -32,16 +32,22 @@ import {
|
|
|
32
32
|
WfoTableColumnConfig,
|
|
33
33
|
subscriptionListTabs,
|
|
34
34
|
} from '@/components';
|
|
35
|
+
import { parseCelToRuleGroup } from '@/components/WfoTable/WfoStructuredSearchTable/utils';
|
|
35
36
|
import { ColumnType, WfoTableProps } from '@/components/WfoTable/WfoTable';
|
|
36
37
|
import { useStoredTableConfig } from '@/hooks';
|
|
37
|
-
import { SearchPayload, useSearchQuery } from '@/rtk';
|
|
38
|
-
import {
|
|
39
|
-
|
|
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';
|
|
40
48
|
|
|
41
49
|
const SEARCH_TABLE_LOCAL_STORAGE_KEY = 'SEARCH_TABLE_LOCAL_STORAGE_KEY';
|
|
42
50
|
|
|
43
|
-
type ResultColumToPropertyMap<T> = Map<string, keyof T>;
|
|
44
|
-
|
|
45
51
|
const getKeyByValueFromMap = <T,>(resultColumToPropertyMap: ResultColumToPropertyMap<T>, field: keyof T) => {
|
|
46
52
|
return [...resultColumToPropertyMap.entries()].find(([, v]) => v === field)?.[0] || '';
|
|
47
53
|
};
|
|
@@ -50,6 +56,7 @@ const getDataFromResponse = <T extends object>(
|
|
|
50
56
|
data: PaginatedSearchResults,
|
|
51
57
|
resultColumToPropertyMap: ResultColumToPropertyMap<T>,
|
|
52
58
|
uniqueRowId: keyof T,
|
|
59
|
+
selectedTab: WfoSubscriptionListTab,
|
|
53
60
|
): {
|
|
54
61
|
items: T[];
|
|
55
62
|
rowExpandingConfiguration?: WfoTableProps<T>['rowExpandingConfiguration'];
|
|
@@ -66,18 +73,23 @@ const getDataFromResponse = <T extends object>(
|
|
|
66
73
|
const rowExpandingConfiguration: WfoTableProps<T>['rowExpandingConfiguration'] = {
|
|
67
74
|
uniqueRowId: uniqueRowId as keyof WfoTableColumnConfig<T>,
|
|
68
75
|
uniqueRowIdToExpandedRowMap: searchResult.reduce(
|
|
69
|
-
(rowMap, { response_columns, score, perfect_match,
|
|
76
|
+
(rowMap, { response_columns, score, perfect_match, matching_fields }) => {
|
|
70
77
|
const idColumnInResponseColumn = getKeyByValueFromMap<T>(resultColumToPropertyMap, uniqueRowId);
|
|
71
78
|
const rowId = response_columns[idColumnInResponseColumn];
|
|
72
79
|
if (rowId) {
|
|
73
80
|
rowMap[rowId] = (
|
|
74
|
-
<WfoExpandingSearchRow
|
|
81
|
+
<WfoExpandingSearchRow
|
|
82
|
+
score={score}
|
|
83
|
+
matchingFields={removeTabStatusMatchingFields(matching_fields, selectedTab, EntityKind.SUBSCRIPTION)}
|
|
84
|
+
perfectMatch={perfect_match}
|
|
85
|
+
/>
|
|
75
86
|
);
|
|
76
87
|
}
|
|
77
88
|
return rowMap;
|
|
78
89
|
},
|
|
79
90
|
{} as Record<string, ReactNode>,
|
|
80
91
|
),
|
|
92
|
+
shouldOnlyShowOnHover: true,
|
|
81
93
|
};
|
|
82
94
|
|
|
83
95
|
const items: T[] = responseColumns.map((responseColumn) => {
|
|
@@ -116,6 +128,21 @@ const resultColumToPropertyMap: ResultColumToPropertyMap<SubscriptionListItem> =
|
|
|
116
128
|
['subscription.metadata', 'metadata'],
|
|
117
129
|
]);
|
|
118
130
|
|
|
131
|
+
/* These options will be added as the first options in the field dropdown in the FieldSelector */
|
|
132
|
+
const prefilledFieldOptions: FieldToOperatorMap = new Map([
|
|
133
|
+
['subscription.subscription_id', ['eq', 'neq', 'like']],
|
|
134
|
+
['subscription.description', ['eq', 'neq', 'like']],
|
|
135
|
+
['subscription.status', ['eq', 'neq', 'like']],
|
|
136
|
+
['subscription.insync', ['eq', 'neq']],
|
|
137
|
+
['subscription.product.name', ['eq', 'neq', 'like']],
|
|
138
|
+
['subscription.product.tag', ['eq', 'neq', 'like']],
|
|
139
|
+
['subscription.customer_name', ['eq', 'neq', 'like']],
|
|
140
|
+
['subscription.customer_abbreviation', ['eq', 'neq', 'like']],
|
|
141
|
+
['subscription.start_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
142
|
+
['subscription.end_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
143
|
+
['subscription.note', ['eq', 'neq', 'like']],
|
|
144
|
+
]);
|
|
145
|
+
|
|
119
146
|
export const WfoSearchPocPage = () => {
|
|
120
147
|
const t = useTranslations('subscriptions.index');
|
|
121
148
|
const [activeTab, setActiveTab] = useQueryParam('activeTab', withDefault(StringParam, WfoSubscriptionListTab.ACTIVE));
|
|
@@ -125,30 +152,64 @@ export const WfoSearchPocPage = () => {
|
|
|
125
152
|
|
|
126
153
|
// Part of the search endpoint payload that is passed in the q parameter
|
|
127
154
|
const [queryText, setQueryText] = useState<string>('');
|
|
128
|
-
|
|
155
|
+
// The committed query and filter live in the URL so a link reproduces the search and browser
|
|
156
|
+
// back/forward re-runs it. The filter is stored as a CEL string and parsed back to a rule group.
|
|
157
|
+
const [committedSearchQuery, setCommittedSearchQuery] = useQueryParam('queryString', withDefault(StringParam, ''));
|
|
158
|
+
const [committedFilterString, setCommittedFilterString] = useQueryParam('filterString', withDefault(StringParam, ''));
|
|
159
|
+
// Track the last value this page committed, so the URL->state sync effects below only rebuild the
|
|
160
|
+
// inputs for external changes (page load, back/forward). Rebuilding on own commits would revert
|
|
161
|
+
// characters typed right after committing (search bar) or visibly restructure builder rules the
|
|
162
|
+
// CEL round trip cannot preserve, such as 'between'. The refs hold the value as the params decode
|
|
163
|
+
// it (absent param -> ''); the setters receive undefined to remove an empty param from the URL.
|
|
164
|
+
const lastSelfCommittedFilter = useRef('');
|
|
165
|
+
const commitFilterString = (celString: string) => {
|
|
166
|
+
lastSelfCommittedFilter.current = celString;
|
|
167
|
+
setCommittedFilterString(celString || undefined);
|
|
168
|
+
};
|
|
169
|
+
const lastSelfCommittedQuery = useRef('');
|
|
170
|
+
const commitSearchQuery = (queryText: string) => {
|
|
171
|
+
lastSelfCommittedQuery.current = queryText;
|
|
172
|
+
setCommittedSearchQuery(queryText || undefined);
|
|
173
|
+
};
|
|
129
174
|
|
|
130
175
|
// String that is displayed in the filter textarea. This is transformed and if valid passed to the search endpoint in the filter parameter
|
|
131
|
-
const [filterString, setFilterString] = useState<string>();
|
|
176
|
+
const [filterString, setFilterString] = useState<string>('');
|
|
132
177
|
const [queryBuilderRuleGroup, setQueryBuilderRuleGroup] = useState<RuleGroupType | undefined>();
|
|
133
|
-
const
|
|
178
|
+
const committedRuleGroup = useMemo<RuleGroupType | undefined>(
|
|
179
|
+
() => parseCelToRuleGroup(committedFilterString),
|
|
180
|
+
[committedFilterString],
|
|
181
|
+
);
|
|
134
182
|
const [isValidFilterString, setIsValidFilterString] = useState<boolean>(true);
|
|
135
183
|
const [tableDefaults, setTableDefaults] = useState<StoredTableConfig<SubscriptionListItem>>();
|
|
136
184
|
const [pageSize, setPageSize] = useState<number>(tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE);
|
|
137
185
|
const [limit, setLimit] = useState<number>(pageSize);
|
|
138
|
-
const [
|
|
186
|
+
const [pageCursor, setPageCursor] = useState<{ cursor: string; searchKey: string } | undefined>(undefined);
|
|
139
187
|
const [dataSorting, setDataSorting] = useState<WfoDataSorting<SubscriptionListItem>>({
|
|
140
188
|
field: 'subscriptionId',
|
|
141
189
|
sortOrder: SortOrder.DESC,
|
|
142
190
|
});
|
|
143
191
|
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
192
|
+
// A next-page cursor is only valid for the committed search that produced it. Binding it to a key
|
|
193
|
+
// of that search covers commits that bypass the handlers below, e.g. browser back/forward changing
|
|
194
|
+
// the queryString/filterString/activeTab URL params: the key no longer matches, so the stale
|
|
195
|
+
// cursor is not sent along with the new search.
|
|
196
|
+
const committedSearchKey = JSON.stringify([
|
|
197
|
+
committedSearchQuery,
|
|
198
|
+
committedFilterString,
|
|
199
|
+
selectedTab,
|
|
200
|
+
retrieverType,
|
|
201
|
+
dataSorting,
|
|
202
|
+
]);
|
|
203
|
+
const cursor = pageCursor?.searchKey === committedSearchKey ? pageCursor.cursor : undefined;
|
|
204
|
+
|
|
205
|
+
// The search payload is derived entirely from the committed search state, which lives in the URL
|
|
206
|
+
// (queryString, filterString and activeTab params). Changing any of these values — whether via the
|
|
207
|
+
// UI or a browser back/forward that updates the URL — produces a new payload and RTK Query re-runs
|
|
208
|
+
// the search automatically. No imperative trigger or effect (and no eslint-disable) is needed.
|
|
148
209
|
// Cursor is stripped from the RTK cache key, so a cursor change appends to the accumulated
|
|
149
210
|
// result set instead of creating a new cache entry — see search endpoint's merge() logic.
|
|
150
211
|
const searchPayload: SearchPayload = useMemo(() => {
|
|
151
|
-
const filters =
|
|
212
|
+
const filters = addStatusFilterFromTab(committedRuleGroup, selectedTab);
|
|
152
213
|
const order_by = {
|
|
153
214
|
element: getKeyByValueFromMap(resultColumToPropertyMap, dataSorting.field),
|
|
154
215
|
direction: dataSorting.sortOrder.toLowerCase(),
|
|
@@ -167,6 +228,9 @@ export const WfoSearchPocPage = () => {
|
|
|
167
228
|
|
|
168
229
|
const { data, isFetching } = useSearchQuery(searchPayload);
|
|
169
230
|
|
|
231
|
+
const [getSubscriptionListTrigger] = useLazySearchQuery();
|
|
232
|
+
const getSubscriptionListForExport = () => getSubscriptionListTrigger(searchPayload).unwrap();
|
|
233
|
+
|
|
170
234
|
useEffect(() => {
|
|
171
235
|
const storedConfig = getStoredTableConfig();
|
|
172
236
|
if (storedConfig) {
|
|
@@ -188,6 +252,7 @@ export const WfoSearchPocPage = () => {
|
|
|
188
252
|
renderDetails: (value) => value,
|
|
189
253
|
renderTooltip: (value) => value,
|
|
190
254
|
isSortable: true,
|
|
255
|
+
isFilterable: true,
|
|
191
256
|
},
|
|
192
257
|
description: {
|
|
193
258
|
columnType: ColumnType.DATA,
|
|
@@ -195,37 +260,44 @@ export const WfoSearchPocPage = () => {
|
|
|
195
260
|
width: '500px',
|
|
196
261
|
renderData: (value, record) => <Link href={`/subscriptions/${record.subscriptionId}`}>{value}</Link>,
|
|
197
262
|
renderTooltip: (value) => value,
|
|
263
|
+
isFilterable: true,
|
|
198
264
|
},
|
|
199
265
|
status: {
|
|
200
266
|
columnType: ColumnType.DATA,
|
|
201
267
|
label: t('status'),
|
|
202
268
|
width: '120px',
|
|
203
269
|
renderData: (value) => <WfoSubscriptionStatusBadge status={value} />,
|
|
270
|
+
isFilterable: true,
|
|
204
271
|
},
|
|
205
272
|
insync: {
|
|
206
273
|
columnType: ColumnType.DATA,
|
|
207
274
|
label: t('insync'),
|
|
208
275
|
width: '75px',
|
|
209
276
|
renderData: (value) => <WfoInsyncIcon inSync={value} />,
|
|
277
|
+
isFilterable: true,
|
|
210
278
|
},
|
|
211
279
|
productName: {
|
|
212
280
|
columnType: ColumnType.DATA,
|
|
213
281
|
width: '260px',
|
|
214
282
|
label: t('product'),
|
|
283
|
+
isFilterable: true,
|
|
215
284
|
},
|
|
216
285
|
tag: {
|
|
217
286
|
columnType: ColumnType.DATA,
|
|
218
287
|
label: t('tag'),
|
|
219
288
|
width: '100px',
|
|
289
|
+
isFilterable: true,
|
|
220
290
|
},
|
|
221
291
|
customerFullname: {
|
|
222
292
|
columnType: ColumnType.DATA,
|
|
223
293
|
label: t('customerFullname'),
|
|
294
|
+
isFilterable: true,
|
|
224
295
|
},
|
|
225
296
|
customerShortcode: {
|
|
226
297
|
columnType: ColumnType.DATA,
|
|
227
298
|
label: t('customerShortcode'),
|
|
228
299
|
width: '150px',
|
|
300
|
+
isFilterable: true,
|
|
229
301
|
},
|
|
230
302
|
startDate: {
|
|
231
303
|
columnType: ColumnType.DATA,
|
|
@@ -235,6 +307,7 @@ export const WfoSearchPocPage = () => {
|
|
|
235
307
|
renderDetails: parseDateToLocaleDateTimeString,
|
|
236
308
|
clipboardText: parseDateToLocaleDateTimeString,
|
|
237
309
|
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
310
|
+
isFilterable: true,
|
|
238
311
|
},
|
|
239
312
|
endDate: {
|
|
240
313
|
columnType: ColumnType.DATA,
|
|
@@ -244,6 +317,7 @@ export const WfoSearchPocPage = () => {
|
|
|
244
317
|
renderDetails: parseDateToLocaleDateTimeString,
|
|
245
318
|
clipboardText: parseDateToLocaleDateTimeString,
|
|
246
319
|
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
320
|
+
isFilterable: true,
|
|
247
321
|
},
|
|
248
322
|
note: {
|
|
249
323
|
columnType: ColumnType.DATA,
|
|
@@ -260,6 +334,7 @@ export const WfoSearchPocPage = () => {
|
|
|
260
334
|
/>
|
|
261
335
|
);
|
|
262
336
|
},
|
|
337
|
+
isFilterable: true,
|
|
263
338
|
},
|
|
264
339
|
metadata: {
|
|
265
340
|
columnType: ColumnType.DATA,
|
|
@@ -268,12 +343,28 @@ export const WfoSearchPocPage = () => {
|
|
|
268
343
|
renderData: (value) => <WfoInlineJson data={value} />,
|
|
269
344
|
renderDetails: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle />,
|
|
270
345
|
renderTooltip: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle={false} />,
|
|
346
|
+
isFilterable: true,
|
|
271
347
|
},
|
|
272
348
|
};
|
|
273
349
|
|
|
274
350
|
const handleApplyFilter = (searchParams?: SearchParams) => {
|
|
275
|
-
|
|
276
|
-
|
|
351
|
+
const ruleGroupParam = searchParams?.ruleGroup;
|
|
352
|
+
// Use an explicitly passed rule group when provided (e.g. a column-header search), a cleared filter
|
|
353
|
+
// when `false`, and otherwise the current query builder state (the "Apply filter" button).
|
|
354
|
+
const effectiveRuleGroup = ruleGroupParam === false ? undefined : (ruleGroupParam ?? queryBuilderRuleGroup);
|
|
355
|
+
// '' (no rule group, or only placeholder rules) commits an empty filter, clearing the URL param.
|
|
356
|
+
const celQuery =
|
|
357
|
+
effectiveRuleGroup ? formatQuery(effectiveRuleGroup, { format: 'cel', fallbackExpression: '' }) : '';
|
|
358
|
+
// A non-empty CEL string must survive the round trip through the URL: formatQuery escapes double
|
|
359
|
+
// quotes in values but parseCEL has no escape support, so such a filter would silently be dropped
|
|
360
|
+
// after committing. Refuse the commit and flag the filter instead, keeping the URL and the search
|
|
361
|
+
// results consistent.
|
|
362
|
+
if (celQuery && !parseCelToRuleGroup(celQuery)) {
|
|
363
|
+
setIsValidFilterString(false);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
commitFilterString(celQuery);
|
|
367
|
+
setPageCursor(undefined);
|
|
277
368
|
};
|
|
278
369
|
|
|
279
370
|
const onChangeQueryText = (queryText: string) => {
|
|
@@ -282,22 +373,22 @@ export const WfoSearchPocPage = () => {
|
|
|
282
373
|
|
|
283
374
|
const onSearchQueryText = (queryText: string) => {
|
|
284
375
|
setQueryText(queryText);
|
|
285
|
-
|
|
286
|
-
|
|
376
|
+
commitSearchQuery(queryText);
|
|
377
|
+
setPageCursor(undefined);
|
|
287
378
|
};
|
|
288
379
|
|
|
289
380
|
const onUpdateRetrieverType = (retrieverType: RetrieverType) => {
|
|
290
381
|
setRetrieverType(retrieverType);
|
|
291
|
-
|
|
382
|
+
setPageCursor(undefined);
|
|
292
383
|
};
|
|
293
384
|
|
|
294
385
|
const handleChangeTab = (updatedTab: WfoSubscriptionListTab) => {
|
|
295
386
|
setActiveTab(updatedTab);
|
|
296
387
|
setLimit(pageSize);
|
|
297
|
-
|
|
388
|
+
setPageCursor(undefined);
|
|
298
389
|
};
|
|
299
390
|
|
|
300
|
-
const safeCelParse = (celString: string) => {
|
|
391
|
+
const safeCelParse = useCallback((celString: string) => {
|
|
301
392
|
try {
|
|
302
393
|
const ruleGroup = parseCEL(celString);
|
|
303
394
|
if (celString === '') {
|
|
@@ -315,7 +406,32 @@ export const WfoSearchPocPage = () => {
|
|
|
315
406
|
} catch {
|
|
316
407
|
setIsValidFilterString(false);
|
|
317
408
|
}
|
|
318
|
-
};
|
|
409
|
+
}, []);
|
|
410
|
+
|
|
411
|
+
// Populate the search bar and the filter builder from the URL, both on page load and when
|
|
412
|
+
// back/forward navigation changes the committed search. Commits made by this page are skipped —
|
|
413
|
+
// see the lastSelfCommitted refs above.
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
if (committedSearchQuery === lastSelfCommittedQuery.current) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
lastSelfCommittedQuery.current = committedSearchQuery;
|
|
419
|
+
setQueryText(committedSearchQuery);
|
|
420
|
+
}, [committedSearchQuery]);
|
|
421
|
+
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
if (committedFilterString === lastSelfCommittedFilter.current) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
lastSelfCommittedFilter.current = committedFilterString;
|
|
427
|
+
setFilterString(committedFilterString);
|
|
428
|
+
if (committedFilterString) {
|
|
429
|
+
safeCelParse(committedFilterString);
|
|
430
|
+
} else {
|
|
431
|
+
setQueryBuilderRuleGroup(undefined);
|
|
432
|
+
setIsValidFilterString(true);
|
|
433
|
+
}
|
|
434
|
+
}, [committedFilterString, safeCelParse]);
|
|
319
435
|
|
|
320
436
|
const onUpdateQueryBuilder = (ruleGroup: RuleGroupType | false) => {
|
|
321
437
|
if (ruleGroup === false) {
|
|
@@ -343,27 +459,43 @@ export const WfoSearchPocPage = () => {
|
|
|
343
459
|
};
|
|
344
460
|
|
|
345
461
|
const { items: subscriptionListItems, rowExpandingConfiguration } =
|
|
346
|
-
data ?
|
|
462
|
+
data ?
|
|
463
|
+
getDataFromResponse<SubscriptionListItem>(data, resultColumToPropertyMap, 'subscriptionId', selectedTab)
|
|
464
|
+
: { items: [] };
|
|
347
465
|
|
|
348
466
|
const totalItems = getTotalItemsFromResponse(data);
|
|
349
467
|
const hasNextPage = data?.page_info?.has_next_page ?? false;
|
|
350
468
|
const nextPageCursor = data?.page_info?.next_page_cursor ?? undefined;
|
|
351
469
|
|
|
470
|
+
const exportData = async () => {
|
|
471
|
+
const exportResult = await getSubscriptionListForExport();
|
|
472
|
+
const { items: exportItems } = getDataFromResponse<SubscriptionListItem>(
|
|
473
|
+
exportResult,
|
|
474
|
+
resultColumToPropertyMap,
|
|
475
|
+
'subscriptionId',
|
|
476
|
+
selectedTab,
|
|
477
|
+
);
|
|
478
|
+
if (!exportItems.length) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
initiateCsvFileDownload(exportItems, Object.keys(tableColumnConfig), getCsvFileNameWithDate('Subscriptions'));
|
|
482
|
+
};
|
|
483
|
+
|
|
352
484
|
const onShowMore = () => {
|
|
353
485
|
if (!isFetching && nextPageCursor) {
|
|
354
|
-
|
|
486
|
+
setPageCursor({ cursor: nextPageCursor, searchKey: committedSearchKey });
|
|
355
487
|
}
|
|
356
488
|
};
|
|
357
489
|
|
|
358
490
|
const onUpdateDataSorting = ({ field, sortOrder }: WfoDataSorting<SubscriptionListItem>) => {
|
|
359
491
|
setDataSorting({ field, sortOrder });
|
|
360
492
|
setLimit(pageSize);
|
|
361
|
-
|
|
493
|
+
setPageCursor(undefined);
|
|
362
494
|
};
|
|
363
495
|
|
|
364
496
|
return (
|
|
365
497
|
<>
|
|
366
|
-
<WfoContentHeader title="Subscriptions (
|
|
498
|
+
<WfoContentHeader title="Subscriptions (Beta)" />
|
|
367
499
|
<WfoFilterTabs
|
|
368
500
|
tabs={subscriptionListTabs}
|
|
369
501
|
selectedTab={selectedTab}
|
|
@@ -391,11 +523,14 @@ export const WfoSearchPocPage = () => {
|
|
|
391
523
|
queryText={queryText}
|
|
392
524
|
retrieverType={retrieverType}
|
|
393
525
|
tableColumnConfig={tableColumnConfig}
|
|
526
|
+
getColumnSearchFieldName={(field) => getKeyByValueFromMap(resultColumToPropertyMap, field)}
|
|
394
527
|
pageSize={pageSize}
|
|
395
528
|
onUpdateDataSorting={onUpdateDataSorting}
|
|
396
529
|
setPageSize={setPageSize}
|
|
397
530
|
totalItems={totalItems}
|
|
398
531
|
hasNextPage={hasNextPage}
|
|
532
|
+
prefilledFieldOptions={prefilledFieldOptions}
|
|
533
|
+
onExportData={exportData}
|
|
399
534
|
/>
|
|
400
535
|
</>
|
|
401
536
|
);
|
|
@@ -10,7 +10,6 @@ export const getFormFieldsBaseStyle = ({ theme }: WfoThemeHelpers) => {
|
|
|
10
10
|
backgroundColor: theme.colors.backgroundBaseNeutral,
|
|
11
11
|
boxShadow: `0 0 0 1px ${theme.colors.primary}`,
|
|
12
12
|
},
|
|
13
|
-
// boxShadow: `0 0 0 1px ${theme.colors.borderBaseSubdued} !important`,
|
|
14
13
|
});
|
|
15
14
|
|
|
16
15
|
return {
|
package/src/types/search.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface SearchResult {
|
|
|
24
24
|
entity_title: string;
|
|
25
25
|
score: number;
|
|
26
26
|
perfect_match: number;
|
|
27
|
-
|
|
27
|
+
matching_fields?: MatchingField[] | null;
|
|
28
28
|
response_columns: Record<string, string | number | null>;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -211,3 +211,6 @@ export type ExportArtifact = {
|
|
|
211
211
|
download_url: string;
|
|
212
212
|
description: string;
|
|
213
213
|
};
|
|
214
|
+
|
|
215
|
+
export type ResultColumToPropertyMap<T> = Map<string, keyof T>;
|
|
216
|
+
export type FieldToOperatorMap = Map<string, string[]>;
|
package/src/types/types.ts
CHANGED
|
@@ -471,7 +471,7 @@ export type Subscription = {
|
|
|
471
471
|
status: SubscriptionStatus;
|
|
472
472
|
product: Pick<ProductDefinition, 'name' | 'tag' | 'productType'>;
|
|
473
473
|
productBlockInstances: ProductBlockInstance[];
|
|
474
|
-
customer: Pick<Customer, 'fullname' | 'shortcode'>;
|
|
474
|
+
customer: Pick<Customer, 'customerId' | 'fullname' | 'shortcode'>;
|
|
475
475
|
metadata: object;
|
|
476
476
|
};
|
|
477
477
|
|
|
@@ -95,7 +95,12 @@ export const getDefaultTableConfig = <T>(storageKey: string) => {
|
|
|
95
95
|
return getTableConfig<T>(completedTasksColumns as (keyof T)[]);
|
|
96
96
|
}
|
|
97
97
|
case SUBSCRIPTIONS_TABLE_LOCAL_STORAGE_KEY: {
|
|
98
|
-
const subscriptionColumns: (keyof SubscriptionListItem)[] = [
|
|
98
|
+
const subscriptionColumns: (keyof SubscriptionListItem)[] = [
|
|
99
|
+
'productName',
|
|
100
|
+
'customerId',
|
|
101
|
+
'customerFullname',
|
|
102
|
+
'metadata',
|
|
103
|
+
];
|
|
99
104
|
return getTableConfig<T>(subscriptionColumns as (keyof T)[]);
|
|
100
105
|
}
|
|
101
106
|
default:
|
package/src/utils/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './getProductNamesFromProcess';
|
|
|
10
10
|
export * from './getQueryVariablesForExport';
|
|
11
11
|
export * from './getStatusBadgeColor';
|
|
12
12
|
export * from './getTypedFieldFromObject';
|
|
13
|
+
export * from './integer';
|
|
13
14
|
export * from './onlyUnique';
|
|
14
15
|
export * from './optionalArray';
|
|
15
16
|
export * from './resultFlattener';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { toPercentage } from './integer';
|
|
2
|
+
|
|
3
|
+
describe('toPercentage()', () => {
|
|
4
|
+
it('converts a fraction to a percentage string with one decimal', () => {
|
|
5
|
+
expect(toPercentage(0.5)).toEqual('50.0%');
|
|
6
|
+
});
|
|
7
|
+
it('converts 1 to 100.0%', () => {
|
|
8
|
+
expect(toPercentage(1)).toEqual('100.0%');
|
|
9
|
+
});
|
|
10
|
+
it('converts 0 to 0.0%', () => {
|
|
11
|
+
expect(toPercentage(0)).toEqual('0.0%');
|
|
12
|
+
});
|
|
13
|
+
it('rounds to one decimal place', () => {
|
|
14
|
+
expect(toPercentage(0.12345)).toEqual('12.3%');
|
|
15
|
+
expect(toPercentage(0.6789)).toEqual('67.9%');
|
|
16
|
+
});
|
|
17
|
+
it('handles fractions greater than 1', () => {
|
|
18
|
+
expect(toPercentage(1.5)).toEqual('150.0%');
|
|
19
|
+
});
|
|
20
|
+
});
|