@orchestrator-ui/orchestrator-ui-components 8.9.2 → 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.
Files changed (63) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +262 -19
  4. package/CHANGELOG.md +28 -0
  5. package/dist/index.d.ts +2082 -2505
  6. package/dist/index.js +2045 -2190
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/WfoError/WfoError.tsx +2 -2
  10. package/src/components/WfoInlineNoteEdit/WfoSubscriptionDetailNoteEdit.tsx +4 -8
  11. package/src/components/WfoInlineNoteEdit/WfoSubscriptionNoteEdit.tsx +6 -7
  12. package/src/components/WfoKeyValueTable/styles.ts +24 -0
  13. package/src/components/WfoPageTemplate/paths.ts +0 -1
  14. package/src/components/WfoPydanticForm/fields/WfoLabel.tsx +5 -1
  15. package/src/components/WfoPydanticForm/fields/styles.ts +12 -3
  16. package/src/components/WfoSettingsModal/WfoInformationModal.tsx +3 -2
  17. package/src/components/WfoSubscriptionsList/index.ts +1 -2
  18. package/src/components/WfoSubscriptionsList/subscriptionListItem.ts +14 -0
  19. package/src/components/WfoSubscriptionsList/subscriptionListTabs.ts +1 -20
  20. package/src/components/WfoSummary/WfoLatestActiveSubscriptionsSummaryCard.tsx +17 -13
  21. package/src/components/WfoSummary/WfoLatestOutOfSyncSubscriptionSummaryCard.tsx +28 -15
  22. package/src/components/WfoTable/WfoAdvancedTable/WfoAdvancedTable.tsx +5 -1
  23. package/src/components/WfoTable/WfoStructuredSearchTable/WfoApplyFilterButton.tsx +37 -0
  24. package/src/components/WfoTable/WfoStructuredSearchTable/WfoDebounceCountdown.tsx +58 -0
  25. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.spec.tsx +180 -0
  26. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +66 -51
  27. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +33 -98
  28. package/src/components/WfoTable/WfoStructuredSearchTable/WfoRangeEditor.tsx +0 -1
  29. package/src/components/WfoTable/WfoStructuredSearchTable/WfoRestoreLoop.spec.tsx +2 -2
  30. package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +5 -7
  31. package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +9 -13
  32. package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +7 -0
  33. package/src/components/WfoTable/WfoStructuredSearchTable/useSearchWithDebouncedCallback.spec.tsx +147 -0
  34. package/src/components/WfoTable/WfoStructuredSearchTable/utils.spec.ts +60 -1
  35. package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +133 -1
  36. package/src/configuration/version.ts +1 -1
  37. package/src/hooks/index.ts +1 -0
  38. package/src/hooks/useDebouncedCallback.ts +48 -0
  39. package/src/hooks/usePathAutoComplete.spec.tsx +94 -9
  40. package/src/hooks/usePathAutoComplete.ts +10 -15
  41. package/src/hooks/useSearchPagination.ts +1 -1
  42. package/src/messages/en-GB.json +4 -2
  43. package/src/messages/nl-NL.json +4 -2
  44. package/src/pages/index.ts +0 -1
  45. package/src/pages/startPage/index.ts +1 -0
  46. package/src/pages/startPage/mappers.ts +15 -8
  47. package/src/pages/startPage/queryVariables.ts +0 -29
  48. package/src/pages/startPage/searchPayloads.ts +22 -0
  49. package/src/pages/subscriptions/WfoSubscriptionsListPage.tsx +511 -39
  50. package/src/rtk/endpoints/index.ts +0 -1
  51. package/src/rtk/endpoints/search.ts +3 -0
  52. package/src/rtk/endpoints/subscriptionListMutation.spec.ts +80 -0
  53. package/src/rtk/endpoints/subscriptionListMutation.ts +71 -52
  54. package/src/rtk/utils.spec.ts +30 -1
  55. package/src/rtk/utils.ts +2 -1
  56. package/src/types/search.ts +1 -3
  57. package/src/types/types.ts +0 -2
  58. package/src/utils/getDefaultTableConfig.ts +1 -6
  59. package/src/utils/getQueryParams.ts +1 -0
  60. package/src/components/WfoSubscriptionsList/WfoSubscriptionsList.tsx +0 -243
  61. package/src/components/WfoSubscriptionsList/subscriptionResultMappers.ts +0 -49
  62. package/src/pages/WfoSearchPocPage.tsx +0 -575
  63. 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;