@orchestrator-ui/orchestrator-ui-components 8.9.3 → 9.0.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 +8 -8
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +17 -50
- package/CHANGELOG.md +19 -0
- package/dist/index.d.ts +2081 -2517
- package/dist/index.js +1247 -1579
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/WfoError/WfoError.tsx +2 -2
- package/src/components/WfoInlineNoteEdit/WfoSubscriptionDetailNoteEdit.tsx +4 -8
- package/src/components/WfoInlineNoteEdit/WfoSubscriptionNoteEdit.tsx +6 -7
- package/src/components/WfoPageTemplate/paths.ts +0 -1
- package/src/components/WfoSubscriptionsList/index.ts +1 -2
- package/src/components/WfoSubscriptionsList/subscriptionListItem.ts +14 -0
- package/src/components/WfoSubscriptionsList/subscriptionListTabs.ts +1 -20
- package/src/components/WfoSummary/WfoLatestActiveSubscriptionsSummaryCard.tsx +17 -13
- package/src/components/WfoSummary/WfoLatestOutOfSyncSubscriptionSummaryCard.tsx +28 -15
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.spec.tsx +0 -16
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +4 -10
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +9 -6
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRestoreLoop.spec.tsx +2 -2
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +3 -5
- package/src/components/WfoTable/WfoStructuredSearchTable/useSearchWithDebouncedCallback.spec.tsx +56 -3
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.spec.ts +60 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +27 -3
- package/src/configuration/version.ts +1 -1
- package/src/messages/en-GB.json +1 -1
- package/src/messages/nl-NL.json +1 -1
- package/src/pages/index.ts +0 -1
- package/src/pages/startPage/index.ts +1 -0
- package/src/pages/startPage/mappers.ts +15 -8
- package/src/pages/startPage/queryVariables.ts +0 -29
- package/src/pages/startPage/searchPayloads.ts +22 -0
- package/src/pages/subscriptions/WfoSubscriptionsListPage.tsx +511 -39
- package/src/rtk/endpoints/index.ts +0 -1
- package/src/rtk/endpoints/search.ts +3 -0
- package/src/rtk/endpoints/subscriptionListMutation.spec.ts +80 -0
- package/src/rtk/endpoints/subscriptionListMutation.ts +71 -52
- package/src/rtk/utils.spec.ts +30 -1
- package/src/rtk/utils.ts +2 -1
- package/src/types/search.ts +0 -1
- package/src/types/types.ts +0 -2
- package/src/utils/getDefaultTableConfig.ts +1 -6
- package/src/utils/getQueryParams.ts +1 -0
- package/src/components/WfoSubscriptionsList/WfoSubscriptionsList.tsx +0 -243
- package/src/components/WfoSubscriptionsList/subscriptionResultMappers.ts +0 -49
- package/src/pages/WfoSearchPocPage.tsx +0 -575
- package/src/rtk/endpoints/subscriptionListSummary.ts +0 -66
|
@@ -1,575 +0,0 @@
|
|
|
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
|
-
|
|
5
|
-
import { useTranslations } from 'next-intl';
|
|
6
|
-
import Link from 'next/link';
|
|
7
|
-
import { StringParam, useQueryParam, withDefault } from 'use-query-params';
|
|
8
|
-
|
|
9
|
-
import { EuiSpacer } from '@elastic/eui';
|
|
10
|
-
|
|
11
|
-
import { SearchParams, addStatusFilterFromTab, removeTabStatusMatchingFields } from '@/components';
|
|
12
|
-
import {
|
|
13
|
-
DEFAULT_PAGE_SIZE,
|
|
14
|
-
StoredTableConfig,
|
|
15
|
-
SubscriptionListItem,
|
|
16
|
-
WfoContentHeader,
|
|
17
|
-
WfoDataSorting,
|
|
18
|
-
WfoDateTime,
|
|
19
|
-
WfoExpandingSearchRow,
|
|
20
|
-
WfoFilterTabs,
|
|
21
|
-
WfoFirstPartUUID,
|
|
22
|
-
WfoInlineJson,
|
|
23
|
-
WfoInsyncIcon,
|
|
24
|
-
WfoJsonCodeBlock,
|
|
25
|
-
WfoStructuredSearchTable,
|
|
26
|
-
WfoStructuredSearchTableColumnConfig,
|
|
27
|
-
WfoSubscriptionActions,
|
|
28
|
-
WfoSubscriptionListTab,
|
|
29
|
-
WfoSubscriptionNoteEdit,
|
|
30
|
-
WfoSubscriptionStatusBadge,
|
|
31
|
-
WfoTableColumnConfig,
|
|
32
|
-
subscriptionListTabs,
|
|
33
|
-
} from '@/components';
|
|
34
|
-
import { parseCelToRuleGroup } from '@/components/WfoTable/WfoStructuredSearchTable/utils';
|
|
35
|
-
import { ColumnType, WfoTableProps } from '@/components/WfoTable/WfoTable';
|
|
36
|
-
import { mapSortableAndFilterableValuesToTableColumnConfig } from '@/components/WfoTable/WfoTable/utils';
|
|
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
|
-
selectedTab: WfoSubscriptionListTab,
|
|
60
|
-
): {
|
|
61
|
-
items: T[];
|
|
62
|
-
rowExpandingConfiguration?: WfoTableProps<T>['rowExpandingConfiguration'];
|
|
63
|
-
} => {
|
|
64
|
-
const searchResult = data?.data;
|
|
65
|
-
if (!searchResult)
|
|
66
|
-
return {
|
|
67
|
-
items: [],
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const responseColumns: Record<string, string | number | null>[] =
|
|
71
|
-
searchResult.map(({ response_columns }) => response_columns) || [];
|
|
72
|
-
|
|
73
|
-
const rowExpandingConfiguration: WfoTableProps<T>['rowExpandingConfiguration'] = {
|
|
74
|
-
uniqueRowId: uniqueRowId as keyof WfoTableColumnConfig<T>,
|
|
75
|
-
uniqueRowIdToExpandedRowMap: searchResult.reduce(
|
|
76
|
-
(rowMap, { response_columns, score, perfect_match, matching_fields }) => {
|
|
77
|
-
const idColumnInResponseColumn = getKeyByValueFromMap<T>(resultColumToPropertyMap, uniqueRowId);
|
|
78
|
-
const rowId = response_columns[idColumnInResponseColumn];
|
|
79
|
-
if (rowId) {
|
|
80
|
-
rowMap[rowId] = (
|
|
81
|
-
<WfoExpandingSearchRow
|
|
82
|
-
score={score}
|
|
83
|
-
matchingFields={removeTabStatusMatchingFields(matching_fields, selectedTab, EntityKind.SUBSCRIPTION)}
|
|
84
|
-
perfectMatch={perfect_match}
|
|
85
|
-
/>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
return rowMap;
|
|
89
|
-
},
|
|
90
|
-
{} as Record<string, ReactNode>,
|
|
91
|
-
),
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const items: T[] = responseColumns.map((responseColumn) => {
|
|
95
|
-
const item = Object.entries(responseColumn).reduce((acc, [key, value]) => {
|
|
96
|
-
const itemKey = resultColumToPropertyMap.get(key);
|
|
97
|
-
if (itemKey) {
|
|
98
|
-
acc[itemKey] = value as unknown as T[keyof T];
|
|
99
|
-
}
|
|
100
|
-
return acc;
|
|
101
|
-
}, {} as T);
|
|
102
|
-
return item;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
items,
|
|
107
|
-
rowExpandingConfiguration,
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const getTotalItemsFromResponse = (data: PaginatedSearchResults | undefined): number | false => {
|
|
112
|
-
return data?.cursor?.total_items || false;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const resultColumToPropertyMap: ResultColumToPropertyMap<SubscriptionListItem> = new Map([
|
|
116
|
-
['subscription.subscription_id', 'subscriptionId'],
|
|
117
|
-
['subscription.description', 'description'],
|
|
118
|
-
['subscription.status', 'status'],
|
|
119
|
-
['subscription.insync', 'insync'],
|
|
120
|
-
['subscription.product.name', 'productName'],
|
|
121
|
-
['subscription.product.tag', 'tag'],
|
|
122
|
-
['subscription.customer_name', 'customerFullname'],
|
|
123
|
-
['subscription.customer_abbreviation', 'customerShortcode'],
|
|
124
|
-
['subscription.start_date', 'startDate'],
|
|
125
|
-
['subscription.end_date', 'endDate'],
|
|
126
|
-
['subscription.note', 'note'],
|
|
127
|
-
['subscription.metadata', 'metadata'],
|
|
128
|
-
]);
|
|
129
|
-
|
|
130
|
-
/* These options will be added as the first options in the field dropdown in the FieldSelector */
|
|
131
|
-
const prefilledFieldOptions: FieldToOperatorMap = new Map([
|
|
132
|
-
['subscription.subscription_id', ['eq', 'neq', 'like', 'not_regexp']],
|
|
133
|
-
['subscription.description', ['eq', 'neq', 'like', 'not_regexp']],
|
|
134
|
-
['subscription.status', ['eq', 'neq', 'like', 'not_regexp']],
|
|
135
|
-
['subscription.insync', ['eq', 'neq']],
|
|
136
|
-
['subscription.product.name', ['eq', 'neq', 'like', 'not_regexp']],
|
|
137
|
-
['subscription.product.tag', ['eq', 'neq', 'like', 'not_regexp']],
|
|
138
|
-
['subscription.customer_name', ['eq', 'neq', 'like', 'not_regexp']],
|
|
139
|
-
['subscription.customer_abbreviation', ['eq', 'neq', 'like', 'not_regexp']],
|
|
140
|
-
['subscription.start_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
141
|
-
['subscription.end_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
142
|
-
['subscription.note', ['eq', 'neq', 'like', 'not_regexp']],
|
|
143
|
-
]);
|
|
144
|
-
|
|
145
|
-
export const WfoSearchPocPage = () => {
|
|
146
|
-
const t = useTranslations('subscriptions.index');
|
|
147
|
-
const [activeTab, setActiveTab] = useQueryParam('activeTab', withDefault(StringParam, WfoSubscriptionListTab.ACTIVE));
|
|
148
|
-
const selectedTab = subscriptionListTabs.find(({ id }) => id === activeTab)?.id ?? WfoSubscriptionListTab.ACTIVE;
|
|
149
|
-
const getStoredTableConfig = useStoredTableConfig<SubscriptionListItem>(SEARCH_TABLE_LOCAL_STORAGE_KEY);
|
|
150
|
-
const [retrieverType, setRetrieverType] = useState<RetrieverType>(RetrieverType.Auto);
|
|
151
|
-
|
|
152
|
-
// Part of the search endpoint payload that is passed in the queryString parameter
|
|
153
|
-
const [queryString, setQueryString] = useState<string>('');
|
|
154
|
-
|
|
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 [committedQueryString, setCommittedQueryString] = 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 lastSelfCommittedQueryString = useRef('');
|
|
170
|
-
const commitQueryString = (queryString: string) => {
|
|
171
|
-
lastSelfCommittedQueryString.current = queryString;
|
|
172
|
-
setCommittedQueryString(queryString || undefined);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// String that is displayed in the filter textarea. This is transformed and if valid passed to the search endpoint in the filter parameter
|
|
176
|
-
const [filterString, setFilterString] = useState<string>('');
|
|
177
|
-
const [queryBuilderRuleGroup, setQueryBuilderRuleGroup] = useState<RuleGroupType | undefined>();
|
|
178
|
-
const committedRuleGroup = useMemo<RuleGroupType | undefined>(
|
|
179
|
-
() => parseCelToRuleGroup(committedFilterString),
|
|
180
|
-
[committedFilterString],
|
|
181
|
-
);
|
|
182
|
-
const [isValidFilterString, setIsValidFilterString] = useState<boolean>(true);
|
|
183
|
-
// Set when a commit attempt (Apply filter, or enter in the search bar) is refused because the
|
|
184
|
-
// filter draft is invalid; used to hide the search results — see isSearchBlocked below.
|
|
185
|
-
const [isCommitRefused, setIsCommitRefused] = useState<boolean>(false);
|
|
186
|
-
const [tableDefaults, setTableDefaults] = useState<StoredTableConfig<SubscriptionListItem>>();
|
|
187
|
-
const [pageSize, setPageSize] = useState<number>(DEFAULT_PAGE_SIZE);
|
|
188
|
-
const [pageCursor, setPageCursor] = useState<{ cursor: string; searchKey: string } | undefined>(undefined);
|
|
189
|
-
const [dataSorting, setDataSorting] = useState<WfoDataSorting<SubscriptionListItem>>({
|
|
190
|
-
field: 'subscriptionId',
|
|
191
|
-
sortOrder: SortOrder.DESC,
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// A next-page cursor is only valid for the committed search that produced it. Binding it to a key
|
|
195
|
-
// of that search covers commits that bypass the handlers below, e.g. browser back/forward changing
|
|
196
|
-
// the queryString/filterString/activeTab URL params: the key no longer matches, so the stale
|
|
197
|
-
// cursor is not sent along with the new search.
|
|
198
|
-
const committedSearchKey = JSON.stringify([
|
|
199
|
-
committedQueryString,
|
|
200
|
-
committedFilterString,
|
|
201
|
-
selectedTab,
|
|
202
|
-
retrieverType,
|
|
203
|
-
dataSorting,
|
|
204
|
-
]);
|
|
205
|
-
const cursor = pageCursor?.searchKey === committedSearchKey ? pageCursor.cursor : undefined;
|
|
206
|
-
|
|
207
|
-
// The search payload is derived entirely from the committed search state, which lives in the URL
|
|
208
|
-
// (queryString, filterString and activeTab params). Changing any of these values — whether via the
|
|
209
|
-
// UI or a browser back/forward that updates the URL — produces a new payload and RTK Query re-runs
|
|
210
|
-
// the search automatically. No imperative trigger or effect (and no eslint-disable) is needed.
|
|
211
|
-
// Cursor is stripped from the RTK cache key, so a cursor change appends to the accumulated
|
|
212
|
-
// result set instead of creating a new cache entry — see search endpoint's merge() logic.
|
|
213
|
-
const searchPayload: SearchPayload = useMemo(() => {
|
|
214
|
-
const filters = addStatusFilterFromTab(committedRuleGroup, selectedTab);
|
|
215
|
-
const order_by = {
|
|
216
|
-
element: getKeyByValueFromMap(resultColumToPropertyMap, dataSorting.field),
|
|
217
|
-
direction: dataSorting.sortOrder.toLowerCase(),
|
|
218
|
-
};
|
|
219
|
-
return {
|
|
220
|
-
query: committedQueryString,
|
|
221
|
-
limit: pageSize,
|
|
222
|
-
entity_type: EntityKind.SUBSCRIPTION,
|
|
223
|
-
response_columns: Array.from(resultColumToPropertyMap.keys()),
|
|
224
|
-
order_by,
|
|
225
|
-
...(retrieverType !== RetrieverType.Auto && { retriever: retrieverType }),
|
|
226
|
-
...(filters && { filters }),
|
|
227
|
-
...(cursor && { cursor }),
|
|
228
|
-
};
|
|
229
|
-
}, [committedQueryString, committedRuleGroup, selectedTab, retrieverType, pageSize, dataSorting, cursor]);
|
|
230
|
-
|
|
231
|
-
const { data, isFetching } = useSearchQuery(searchPayload);
|
|
232
|
-
|
|
233
|
-
const [getSubscriptionListTrigger] = useLazySearchQuery();
|
|
234
|
-
const getSubscriptionListForExport = (exportLimit: number) =>
|
|
235
|
-
getSubscriptionListTrigger({ ...searchPayload, limit: exportLimit, cursor: undefined }).unwrap();
|
|
236
|
-
|
|
237
|
-
useEffect(() => {
|
|
238
|
-
const storedConfig = getStoredTableConfig();
|
|
239
|
-
if (storedConfig) {
|
|
240
|
-
setTableDefaults(storedConfig);
|
|
241
|
-
setPageSize(storedConfig.selectedPageSize);
|
|
242
|
-
}
|
|
243
|
-
}, [getStoredTableConfig]);
|
|
244
|
-
|
|
245
|
-
const tableColumnConfig: WfoStructuredSearchTableColumnConfig<SubscriptionListItem> = {
|
|
246
|
-
actions: {
|
|
247
|
-
columnType: ColumnType.CONTROL,
|
|
248
|
-
width: '50px',
|
|
249
|
-
renderControl: (row) => <WfoSubscriptionActions compactMode={true} subscriptionId={row.subscriptionId} />,
|
|
250
|
-
},
|
|
251
|
-
subscriptionId: {
|
|
252
|
-
columnType: ColumnType.DATA,
|
|
253
|
-
label: t('id'),
|
|
254
|
-
width: '100px',
|
|
255
|
-
renderData: (value) => <WfoFirstPartUUID UUID={value} />,
|
|
256
|
-
renderDetails: (value) => value,
|
|
257
|
-
renderTooltip: (value) => value,
|
|
258
|
-
},
|
|
259
|
-
description: {
|
|
260
|
-
columnType: ColumnType.DATA,
|
|
261
|
-
label: t('description'),
|
|
262
|
-
width: '500px',
|
|
263
|
-
renderData: (value, record) => <Link href={`/subscriptions/${record.subscriptionId}`}>{value}</Link>,
|
|
264
|
-
renderTooltip: (value) => value,
|
|
265
|
-
},
|
|
266
|
-
status: {
|
|
267
|
-
columnType: ColumnType.DATA,
|
|
268
|
-
label: t('status'),
|
|
269
|
-
width: '120px',
|
|
270
|
-
renderData: (value) => <WfoSubscriptionStatusBadge status={value} />,
|
|
271
|
-
},
|
|
272
|
-
insync: {
|
|
273
|
-
columnType: ColumnType.DATA,
|
|
274
|
-
label: t('insync'),
|
|
275
|
-
width: '75px',
|
|
276
|
-
renderData: (value) => <WfoInsyncIcon inSync={value} />,
|
|
277
|
-
},
|
|
278
|
-
productName: {
|
|
279
|
-
columnType: ColumnType.DATA,
|
|
280
|
-
width: '260px',
|
|
281
|
-
label: t('product'),
|
|
282
|
-
},
|
|
283
|
-
tag: {
|
|
284
|
-
columnType: ColumnType.DATA,
|
|
285
|
-
label: t('tag'),
|
|
286
|
-
width: '100px',
|
|
287
|
-
},
|
|
288
|
-
customerFullname: {
|
|
289
|
-
columnType: ColumnType.DATA,
|
|
290
|
-
label: t('customerFullname'),
|
|
291
|
-
},
|
|
292
|
-
customerShortcode: {
|
|
293
|
-
columnType: ColumnType.DATA,
|
|
294
|
-
label: t('customerShortcode'),
|
|
295
|
-
width: '150px',
|
|
296
|
-
},
|
|
297
|
-
startDate: {
|
|
298
|
-
columnType: ColumnType.DATA,
|
|
299
|
-
label: t('startDate'),
|
|
300
|
-
width: '100px',
|
|
301
|
-
renderData: (value) => <WfoDateTime dateOrIsoString={value} />,
|
|
302
|
-
renderDetails: parseDateToLocaleDateTimeString,
|
|
303
|
-
clipboardText: parseDateToLocaleDateTimeString,
|
|
304
|
-
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
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
|
-
},
|
|
315
|
-
note: {
|
|
316
|
-
columnType: ColumnType.DATA,
|
|
317
|
-
label: t('note'),
|
|
318
|
-
width: '300px',
|
|
319
|
-
renderData: (cellValue, row) => {
|
|
320
|
-
return (
|
|
321
|
-
<WfoSubscriptionNoteEdit
|
|
322
|
-
onlyShowOnHover={true}
|
|
323
|
-
endpointName={''}
|
|
324
|
-
queryVariables={{}}
|
|
325
|
-
subscriptionId={row.subscriptionId}
|
|
326
|
-
note={cellValue}
|
|
327
|
-
/>
|
|
328
|
-
);
|
|
329
|
-
},
|
|
330
|
-
},
|
|
331
|
-
metadata: {
|
|
332
|
-
columnType: ColumnType.DATA,
|
|
333
|
-
label: t('metadata'),
|
|
334
|
-
width: '100px',
|
|
335
|
-
renderData: (value) => <WfoInlineJson data={value} />,
|
|
336
|
-
renderDetails: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle />,
|
|
337
|
-
renderTooltip: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle={false} />,
|
|
338
|
-
},
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const sortableAndFilterableFieldNames = Object.keys(tableColumnConfig).filter((fieldName) => fieldName !== 'actions');
|
|
342
|
-
const isSortingAllowed = queryString === '';
|
|
343
|
-
const tableColumnConfigWithSortingAndFiltering =
|
|
344
|
-
mapSortableAndFilterableValuesToTableColumnConfig<SubscriptionListItem>(
|
|
345
|
-
tableColumnConfig,
|
|
346
|
-
isSortingAllowed ? sortableAndFilterableFieldNames : [],
|
|
347
|
-
sortableAndFilterableFieldNames,
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
// Formats the given rule group to CEL and commits it. Returns false without committing when
|
|
351
|
-
// the CEL would not survive the URL round trip: formatQuery escapes double quotes in values
|
|
352
|
-
// but parseCEL has no escape support, so such a filter would silently be dropped after
|
|
353
|
-
// committing. Refusing keeps the URL and the search results consistent.
|
|
354
|
-
const commitFilterDraft = (effectiveRuleGroup: RuleGroupType | undefined): boolean => {
|
|
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
|
-
if (celQuery && !parseCelToRuleGroup(celQuery)) {
|
|
359
|
-
setIsValidFilterString(false);
|
|
360
|
-
setIsCommitRefused(true);
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
commitFilterString(celQuery);
|
|
364
|
-
setIsCommitRefused(false);
|
|
365
|
-
return true;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
const handleApplyFilter = (searchParams?: SearchParams) => {
|
|
369
|
-
const ruleGroupParam = searchParams?.ruleGroup;
|
|
370
|
-
// Use an explicitly passed rule group when provided (e.g. a column-header search), a cleared filter
|
|
371
|
-
// when `false`, and otherwise the current query builder state (the "Apply filter" button).
|
|
372
|
-
const effectiveRuleGroup = ruleGroupParam === false ? undefined : (ruleGroupParam ?? queryBuilderRuleGroup);
|
|
373
|
-
if (!commitFilterDraft(effectiveRuleGroup)) {
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
// Also commit the draft search text, so applying a filter picks up text typed in the
|
|
377
|
-
// search bar without pressing enter.
|
|
378
|
-
commitQueryString(queryString);
|
|
379
|
-
setPageCursor(undefined);
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
const onChangeQueryString = (queryString: string) => {
|
|
383
|
-
setQueryString(queryString);
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
const onSearchQueryString = (queryString: string) => {
|
|
387
|
-
setQueryString(queryString);
|
|
388
|
-
// Mirror handleApplyFilter: searching also commits the pending filter draft, and an invalid
|
|
389
|
-
// draft refuses the whole search — nothing commits and the results are hidden until the
|
|
390
|
-
// draft is fixed. The isValidFilterString check guards the textarea state, which the
|
|
391
|
-
// queryBuilderRuleGroup (holding the last *valid* parse) does not reflect while invalid.
|
|
392
|
-
if (!isValidFilterString) {
|
|
393
|
-
setIsCommitRefused(true);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
if (!commitFilterDraft(queryBuilderRuleGroup)) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
commitQueryString(queryString);
|
|
400
|
-
setPageCursor(undefined);
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
const onUpdateRetrieverType = (retrieverType: RetrieverType) => {
|
|
404
|
-
setRetrieverType(retrieverType);
|
|
405
|
-
setPageCursor(undefined);
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
const handleChangeTab = (updatedTab: WfoSubscriptionListTab) => {
|
|
409
|
-
setActiveTab(updatedTab);
|
|
410
|
-
setPageCursor(undefined);
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
const safeCelParse = useCallback((celString: string) => {
|
|
414
|
-
if (celString === '') {
|
|
415
|
-
setIsValidFilterString(true);
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
// parseCelToRuleGroup returns undefined when the string parses to no rules — in that
|
|
419
|
-
// case we assume the string is not valid. In any case it would not do anything to the
|
|
420
|
-
// search results. It also assigns the rule ids parseCEL leaves out, which the query
|
|
421
|
-
// builder needs to keep rule identity stable across query updates.
|
|
422
|
-
const ruleGroup = parseCelToRuleGroup(celString);
|
|
423
|
-
if (ruleGroup) {
|
|
424
|
-
setIsValidFilterString(true);
|
|
425
|
-
setQueryBuilderRuleGroup(ruleGroup);
|
|
426
|
-
} else {
|
|
427
|
-
setIsValidFilterString(false);
|
|
428
|
-
}
|
|
429
|
-
}, []);
|
|
430
|
-
|
|
431
|
-
// Populate the search bar and the filter builder from the URL, both on page load and when
|
|
432
|
-
// back/forward navigation changes the committed search. Commits made by this page are skipped —
|
|
433
|
-
// see the lastSelfCommitted refs above.
|
|
434
|
-
useEffect(() => {
|
|
435
|
-
if (committedQueryString === lastSelfCommittedQueryString.current) {
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
lastSelfCommittedQueryString.current = committedQueryString;
|
|
439
|
-
setQueryString(committedQueryString);
|
|
440
|
-
}, [committedQueryString]);
|
|
441
|
-
|
|
442
|
-
useEffect(() => {
|
|
443
|
-
if (committedFilterString === lastSelfCommittedFilter.current) {
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
lastSelfCommittedFilter.current = committedFilterString;
|
|
447
|
-
setFilterString(committedFilterString);
|
|
448
|
-
if (committedFilterString) {
|
|
449
|
-
safeCelParse(committedFilterString);
|
|
450
|
-
} else {
|
|
451
|
-
setQueryBuilderRuleGroup(undefined);
|
|
452
|
-
setIsValidFilterString(true);
|
|
453
|
-
}
|
|
454
|
-
}, [committedFilterString, safeCelParse]);
|
|
455
|
-
|
|
456
|
-
const onUpdateQueryBuilder = (ruleGroup: RuleGroupType | false) => {
|
|
457
|
-
if (ruleGroup === false) {
|
|
458
|
-
setQueryBuilderRuleGroup(undefined);
|
|
459
|
-
setFilterString('');
|
|
460
|
-
setIsValidFilterString(true);
|
|
461
|
-
} else {
|
|
462
|
-
setQueryBuilderRuleGroup({ ...ruleGroup });
|
|
463
|
-
const celQuery = formatQuery({ ...ruleGroup }, { format: 'cel', fallbackExpression: '' });
|
|
464
|
-
// 1 == 1 indicates the query can't be parsed. This is a fallback to allow it to still be used as
|
|
465
|
-
// part of other queries.
|
|
466
|
-
if (!celQuery || celQuery === '1 == 1') {
|
|
467
|
-
setFilterString('');
|
|
468
|
-
setIsValidFilterString(true);
|
|
469
|
-
} else {
|
|
470
|
-
setFilterString(celQuery);
|
|
471
|
-
// formatQuery output is normally valid CEL, but not unconditionally — a rule can
|
|
472
|
-
// hold state formatQuery renders as unparseable CEL (e.g. a 'field' value source
|
|
473
|
-
// renders its value unquoted). Validate the round trip so the invalid marker and
|
|
474
|
-
// the Apply button track what the textarea actually shows.
|
|
475
|
-
setIsValidFilterString(!!parseCelToRuleGroup(celQuery));
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
const onUpdateFilterString = (filterString: string) => {
|
|
481
|
-
setFilterString(filterString);
|
|
482
|
-
safeCelParse(filterString);
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
// A refused commit hides the search results for as long as the filter draft stays invalid:
|
|
486
|
-
// showing them would suggest the attempted search ran. Editing the draft back to valid CEL
|
|
487
|
-
// (or a later successful commit) lifts the block.
|
|
488
|
-
const isSearchBlocked = isCommitRefused && !isValidFilterString;
|
|
489
|
-
|
|
490
|
-
const { items: subscriptionListItems, rowExpandingConfiguration } =
|
|
491
|
-
data && !isSearchBlocked ?
|
|
492
|
-
getDataFromResponse<SubscriptionListItem>(data, resultColumToPropertyMap, 'subscriptionId', selectedTab)
|
|
493
|
-
: { items: [] };
|
|
494
|
-
|
|
495
|
-
const totalItems = !isSearchBlocked && getTotalItemsFromResponse(data);
|
|
496
|
-
const hasNextPage = !isSearchBlocked && (data?.page_info?.has_next_page ?? false);
|
|
497
|
-
const nextPageCursor = data?.page_info?.next_page_cursor ?? undefined;
|
|
498
|
-
|
|
499
|
-
const exportData = async () => {
|
|
500
|
-
const exportResult = await getSubscriptionListForExport(totalItems || pageSize);
|
|
501
|
-
const { items: exportItems } = getDataFromResponse<SubscriptionListItem>(
|
|
502
|
-
exportResult,
|
|
503
|
-
resultColumToPropertyMap,
|
|
504
|
-
'subscriptionId',
|
|
505
|
-
selectedTab,
|
|
506
|
-
);
|
|
507
|
-
if (!exportItems.length) {
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
initiateCsvFileDownload(exportItems, Object.keys(tableColumnConfig), getCsvFileNameWithDate('Subscriptions'));
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
const onShowMore = () => {
|
|
514
|
-
if (!isFetching && nextPageCursor) {
|
|
515
|
-
setPageCursor({ cursor: nextPageCursor, searchKey: committedSearchKey });
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const onUpdateDataSorting = ({ field, sortOrder }: WfoDataSorting<SubscriptionListItem>) => {
|
|
520
|
-
setDataSorting({ field, sortOrder });
|
|
521
|
-
setPageCursor(undefined);
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
const onUpdatePageSize = (updatedPageSize: number) => {
|
|
525
|
-
if (updatedPageSize === pageSize) {
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
setPageSize(updatedPageSize);
|
|
529
|
-
setPageCursor(undefined);
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
return (
|
|
533
|
-
<>
|
|
534
|
-
<WfoContentHeader title="Subscriptions (Beta)" />
|
|
535
|
-
<WfoFilterTabs
|
|
536
|
-
tabs={subscriptionListTabs}
|
|
537
|
-
selectedTab={selectedTab}
|
|
538
|
-
translationNamespace="subscriptions.tabs"
|
|
539
|
-
onChangeTab={handleChangeTab}
|
|
540
|
-
/>
|
|
541
|
-
<EuiSpacer size="l" />
|
|
542
|
-
<WfoStructuredSearchTable<SubscriptionListItem>
|
|
543
|
-
data={subscriptionListItems}
|
|
544
|
-
rowExpandingConfiguration={rowExpandingConfiguration}
|
|
545
|
-
defaultHiddenColumns={tableDefaults?.hiddenColumns}
|
|
546
|
-
defaultShowMatchDetails={tableDefaults?.showMatchDetails}
|
|
547
|
-
defaultAdvancedNestedSearch={tableDefaults?.advancedNestedSearch}
|
|
548
|
-
filterString={filterString}
|
|
549
|
-
handleSearch={handleApplyFilter}
|
|
550
|
-
isLoading={isFetching}
|
|
551
|
-
dataSorting={[dataSorting]}
|
|
552
|
-
isValidFilterString={isValidFilterString}
|
|
553
|
-
localStorageKey={SEARCH_TABLE_LOCAL_STORAGE_KEY}
|
|
554
|
-
onUpdateFilterString={onUpdateFilterString}
|
|
555
|
-
onUpdateQueryBuilder={onUpdateQueryBuilder}
|
|
556
|
-
onChangeQueryString={onChangeQueryString}
|
|
557
|
-
onSearchQueryString={onSearchQueryString}
|
|
558
|
-
onShowMore={onShowMore}
|
|
559
|
-
onUpdateRetrieverType={onUpdateRetrieverType}
|
|
560
|
-
queryBuilderRuleGroup={queryBuilderRuleGroup}
|
|
561
|
-
queryString={queryString}
|
|
562
|
-
retrieverType={retrieverType}
|
|
563
|
-
tableColumnConfig={tableColumnConfigWithSortingAndFiltering}
|
|
564
|
-
getColumnSearchFieldName={(field) => getKeyByValueFromMap(resultColumToPropertyMap, field)}
|
|
565
|
-
pageSize={pageSize}
|
|
566
|
-
onUpdateDataSorting={onUpdateDataSorting}
|
|
567
|
-
setPageSize={onUpdatePageSize}
|
|
568
|
-
totalItems={totalItems}
|
|
569
|
-
hasNextPage={hasNextPage}
|
|
570
|
-
prefilledFieldOptions={prefilledFieldOptions}
|
|
571
|
-
onExportData={exportData}
|
|
572
|
-
/>
|
|
573
|
-
</>
|
|
574
|
-
);
|
|
575
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { orchestratorApi } from '@/rtk';
|
|
2
|
-
import { CacheTagType } from '@/types';
|
|
3
|
-
import {
|
|
4
|
-
BaseGraphQlResult,
|
|
5
|
-
GraphqlQueryVariables,
|
|
6
|
-
Subscription,
|
|
7
|
-
SubscriptionSummary,
|
|
8
|
-
SubscriptionsResult,
|
|
9
|
-
} from '@/types';
|
|
10
|
-
import { getCacheTag } from '@/utils/cacheTag';
|
|
11
|
-
|
|
12
|
-
export const subscriptionListSummaryQuery = `
|
|
13
|
-
query SubscriptionsList(
|
|
14
|
-
$first: Int!
|
|
15
|
-
$after: Int!
|
|
16
|
-
$sortBy: [GraphqlSort!]
|
|
17
|
-
$filterBy: [GraphqlFilter!]
|
|
18
|
-
$query: String
|
|
19
|
-
) {
|
|
20
|
-
subscriptions(
|
|
21
|
-
first: $first
|
|
22
|
-
after: $after
|
|
23
|
-
sortBy: $sortBy
|
|
24
|
-
filterBy: $filterBy
|
|
25
|
-
query: $query
|
|
26
|
-
) {
|
|
27
|
-
page {
|
|
28
|
-
startDate
|
|
29
|
-
description
|
|
30
|
-
subscriptionId
|
|
31
|
-
}
|
|
32
|
-
pageInfo {
|
|
33
|
-
totalItems
|
|
34
|
-
startCursor
|
|
35
|
-
endCursor
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
`;
|
|
40
|
-
|
|
41
|
-
export type SubscriptionListSummaryResponse = {
|
|
42
|
-
subscriptions: SubscriptionSummary[];
|
|
43
|
-
} & BaseGraphQlResult;
|
|
44
|
-
|
|
45
|
-
const subscriptionListSummaryApi = orchestratorApi.injectEndpoints({
|
|
46
|
-
endpoints: (builder) => ({
|
|
47
|
-
getSubscriptionSummaryList: builder.query<SubscriptionListSummaryResponse, GraphqlQueryVariables<Subscription>>({
|
|
48
|
-
query: (variables) => ({
|
|
49
|
-
document: subscriptionListSummaryQuery,
|
|
50
|
-
variables,
|
|
51
|
-
}),
|
|
52
|
-
transformResponse: (response: SubscriptionsResult<SubscriptionSummary>): SubscriptionListSummaryResponse => {
|
|
53
|
-
const subscriptions = response.subscriptions.page || [];
|
|
54
|
-
const pageInfo = response.subscriptions.pageInfo || {};
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
subscriptions,
|
|
58
|
-
pageInfo,
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
providesTags: getCacheTag(CacheTagType.subscriptions),
|
|
62
|
-
}),
|
|
63
|
-
}),
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
export const { useGetSubscriptionSummaryListQuery } = subscriptionListSummaryApi;
|