@orchestrator-ui/orchestrator-ui-components 8.9.1 → 8.9.3
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 +7 -7
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +291 -15
- package/CHANGELOG.md +16 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +1662 -1463
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/WfoKeyValueTable/styles.ts +24 -0
- package/src/components/WfoPydanticForm/fields/WfoLabel.tsx +5 -1
- package/src/components/WfoPydanticForm/fields/styles.ts +12 -3
- package/src/components/WfoSearchPage/utils.ts +1 -0
- package/src/components/WfoSettingsModal/WfoInformationModal.tsx +3 -2
- package/src/components/WfoTable/WfoAdvancedTable/WfoAdvancedTable.tsx +5 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoApplyFilterButton.tsx +37 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoDebounceCountdown.tsx +58 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.spec.tsx +196 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +71 -50
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +24 -90
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRangeEditor.tsx +0 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +2 -2
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +9 -13
- package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +7 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/useSearchWithDebouncedCallback.spec.tsx +94 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +109 -1
- package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.tsx +3 -2
- package/src/components/WfoTable/WfoTableSettingsModal/styles.ts +10 -0
- package/src/configuration/version.ts +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useDebouncedCallback.ts +48 -0
- package/src/hooks/useGetPydanticFormsConfig.tsx +2 -1
- package/src/hooks/usePathAutoComplete.spec.tsx +94 -9
- package/src/hooks/usePathAutoComplete.ts +10 -15
- package/src/hooks/useSearchPagination.ts +1 -1
- package/src/messages/en-GB.json +3 -1
- package/src/messages/nl-NL.json +3 -1
- package/src/pages/WfoSearchPocPage.tsx +8 -8
- package/src/types/search.ts +1 -2
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { renderHook, waitFor } from '@testing-library/react';
|
|
1
|
+
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useSearchPathsQuery } from '@/rtk/endpoints';
|
|
4
|
+
import { EntityKind, PathAutocompleteResponse } from '@/types';
|
|
4
5
|
|
|
5
|
-
import { useFieldsPathInfo } from './usePathAutoComplete';
|
|
6
|
+
import { useFieldsPathInfo, usePathAutocomplete } from './usePathAutoComplete';
|
|
6
7
|
|
|
7
8
|
const fetchPathsMock = jest.fn();
|
|
8
9
|
|
|
9
|
-
jest.mock('@/rtk/endpoints', () =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
useSearchDefinitionsQuery: () => ({
|
|
10
|
+
jest.mock('@/rtk/endpoints', () => {
|
|
11
|
+
// Stable like RTK Query's cached data: the hooks depend on it in effects.
|
|
12
|
+
const definitionsResult = {
|
|
13
13
|
data: {
|
|
14
14
|
boolean: {
|
|
15
15
|
operators: ['eq', 'neq'],
|
|
@@ -17,8 +17,13 @@ jest.mock('@/rtk/endpoints', () => ({
|
|
|
17
17
|
},
|
|
18
18
|
},
|
|
19
19
|
isError: false,
|
|
20
|
-
}
|
|
21
|
-
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
useSearchPathsQuery: jest.fn(),
|
|
23
|
+
useLazySearchPathsQuery: () => [fetchPathsMock],
|
|
24
|
+
useSearchDefinitionsQuery: () => definitionsResult,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
22
27
|
|
|
23
28
|
describe('useFieldsPathInfo', () => {
|
|
24
29
|
beforeEach(() => {
|
|
@@ -62,3 +67,83 @@ describe('useFieldsPathInfo', () => {
|
|
|
62
67
|
expect(fetchPathsMock).toHaveBeenCalledTimes(1);
|
|
63
68
|
});
|
|
64
69
|
});
|
|
70
|
+
|
|
71
|
+
describe('usePathAutocomplete', () => {
|
|
72
|
+
const SAPS_RESPONSE = {
|
|
73
|
+
leaves: [],
|
|
74
|
+
components: [{ name: 'saps', ui_types: ['component'], paths: ['saps.port', 'saps.vlan'] }],
|
|
75
|
+
};
|
|
76
|
+
const EMPTY_RESPONSE: PathAutocompleteResponse = { leaves: [], components: [] };
|
|
77
|
+
|
|
78
|
+
// Only the fields the hook reads; the full RTK Query result type is far larger.
|
|
79
|
+
const mockSearchPaths = (
|
|
80
|
+
getData: (args: { q: string }, options?: { skip?: boolean }) => PathAutocompleteResponse | undefined,
|
|
81
|
+
) =>
|
|
82
|
+
jest.mocked(useSearchPathsQuery).mockImplementation(((args: { q: string }, options?: { skip?: boolean }) => ({
|
|
83
|
+
data: getData(args, options),
|
|
84
|
+
isFetching: false,
|
|
85
|
+
isError: false,
|
|
86
|
+
})) as unknown as typeof useSearchPathsQuery);
|
|
87
|
+
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
jest.useFakeTimers();
|
|
90
|
+
mockSearchPaths((args, options) => {
|
|
91
|
+
if (options?.skip) return undefined;
|
|
92
|
+
return args.q === 'sa' ? SAPS_RESPONSE : EMPTY_RESPONSE;
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
afterEach(() => {
|
|
97
|
+
jest.useRealTimers();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('skips the paths request and offers no paths while the prefix is empty', () => {
|
|
101
|
+
const { result } = renderHook(() => usePathAutocomplete('', EntityKind.SUBSCRIPTION));
|
|
102
|
+
|
|
103
|
+
act(() => {
|
|
104
|
+
jest.advanceTimersByTime(300);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(useSearchPathsQuery).toHaveBeenCalled();
|
|
108
|
+
jest.mocked(useSearchPathsQuery).mock.calls.forEach(([, options]) => expect(options?.skip).toBe(true));
|
|
109
|
+
expect(result.current.paths).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('reports loading from the keystroke until the debounced request has settled', async () => {
|
|
113
|
+
const { result, rerender } = renderHook(({ prefix }) => usePathAutocomplete(prefix, EntityKind.SUBSCRIPTION), {
|
|
114
|
+
initialProps: { prefix: '' },
|
|
115
|
+
});
|
|
116
|
+
expect(result.current.loading).toBe(false);
|
|
117
|
+
|
|
118
|
+
rerender({ prefix: 'sa' });
|
|
119
|
+
expect(result.current.loading).toBe(true);
|
|
120
|
+
expect(result.current.paths).toEqual([]);
|
|
121
|
+
|
|
122
|
+
act(() => {
|
|
123
|
+
jest.advanceTimersByTime(300);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await waitFor(() => expect(result.current.loading).toBe(false));
|
|
127
|
+
expect(result.current.paths).toHaveLength(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('maps the components returned for a typed prefix, e.g. saps for "sa"', async () => {
|
|
131
|
+
const { result } = renderHook(() => usePathAutocomplete('sa', EntityKind.SUBSCRIPTION));
|
|
132
|
+
|
|
133
|
+
act(() => {
|
|
134
|
+
jest.advanceTimersByTime(300);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await waitFor(() => expect(result.current.paths).toHaveLength(1));
|
|
138
|
+
expect(useSearchPathsQuery).toHaveBeenLastCalledWith(
|
|
139
|
+
{ q: 'sa', entity_type: EntityKind.SUBSCRIPTION },
|
|
140
|
+
{ skip: false },
|
|
141
|
+
);
|
|
142
|
+
expect(result.current.paths[0]).toMatchObject({
|
|
143
|
+
path: 'saps',
|
|
144
|
+
type: 'component',
|
|
145
|
+
group: 'component',
|
|
146
|
+
availablePaths: ['saps.port', 'saps.vlan'],
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useLazySearchPathsQuery, useSearchDefinitionsQuery, useSearchPathsQuery } from '@/rtk/endpoints';
|
|
4
4
|
import { EntityKind, PathAutocompleteResponse, PathInfo, value_schema } from '@/types';
|
|
@@ -97,35 +97,30 @@ const mapPathAutocompleteResponseToPathInfos = (
|
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
export const usePathAutocomplete = (prefix: string, entityType: EntityKind) => {
|
|
100
|
-
const [paths, setPaths] = useState<PathInfo[]>([]);
|
|
101
100
|
const debouncedPrefix = useDebounce(prefix, 300);
|
|
102
101
|
const { data: definitions = FALLBACK_DEFINITIONS, isError: defError } = useSearchDefinitionsQuery();
|
|
103
102
|
|
|
104
103
|
const {
|
|
105
104
|
data: pathData,
|
|
106
|
-
|
|
105
|
+
isFetching,
|
|
107
106
|
isError,
|
|
108
107
|
} = useSearchPathsQuery({ q: debouncedPrefix, entity_type: entityType }, { skip: debouncedPrefix.length < 1 });
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (!pathData) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
109
|
+
const paths = useMemo(
|
|
110
|
+
() =>
|
|
111
|
+
debouncedPrefix.length < 1 || !pathData ? [] : mapPathAutocompleteResponseToPathInfos(pathData, definitions),
|
|
112
|
+
[pathData, definitions, debouncedPrefix.length],
|
|
113
|
+
);
|
|
119
114
|
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
const isDebouncing = prefix.length >= 1 && prefix !== debouncedPrefix;
|
|
116
|
+
const loading = isDebouncing || isFetching;
|
|
122
117
|
|
|
123
118
|
const errorMessage =
|
|
124
119
|
isError ? 'Failed to load paths'
|
|
125
120
|
: defError ? 'Failed to load definitions'
|
|
126
121
|
: null;
|
|
127
122
|
|
|
128
|
-
return { paths, loading
|
|
123
|
+
return { paths, loading, error: errorMessage };
|
|
129
124
|
};
|
|
130
125
|
|
|
131
126
|
/**
|
package/src/messages/en-GB.json
CHANGED
|
@@ -372,6 +372,7 @@
|
|
|
372
372
|
"previousPage": "Previous page",
|
|
373
373
|
"removeConditionAriaLabel": "Remove condition",
|
|
374
374
|
"removeGroup": "Remove group",
|
|
375
|
+
"loadingOptions": "Loading options",
|
|
375
376
|
"removeRule": "Remove rule",
|
|
376
377
|
"resultsOnPage": "{resultCount} result(s) on this page",
|
|
377
378
|
"retrieval": "Retrieval",
|
|
@@ -380,7 +381,7 @@
|
|
|
380
381
|
"retrieverHybrid": "Hybrid",
|
|
381
382
|
"retrieverSemantic": "Semantic",
|
|
382
383
|
"searchError": "Error",
|
|
383
|
-
"searchFieldsPlaceholder": "
|
|
384
|
+
"searchFieldsPlaceholder": "Type to select fields",
|
|
384
385
|
"searchPlaceholder": "Search for {entityType}…",
|
|
385
386
|
"searchResultsPagination": "Search results pagination",
|
|
386
387
|
"selectDateAndTime": "Select date",
|
|
@@ -388,6 +389,7 @@
|
|
|
388
389
|
"selectOrEnterValue": "Select or type value",
|
|
389
390
|
"selectSpecificPathPlaceholder": "Select a specific path...",
|
|
390
391
|
"showFilters": "Show filters",
|
|
392
|
+
"startTypingToLoadOptions": "Start typing to load options",
|
|
391
393
|
"structuredFilters": "Structured filters",
|
|
392
394
|
"toDate": "To date",
|
|
393
395
|
"toNumber": "To",
|
package/src/messages/nl-NL.json
CHANGED
|
@@ -372,6 +372,7 @@
|
|
|
372
372
|
"previousPage": "Vorige pagina",
|
|
373
373
|
"removeConditionAriaLabel": "Conditie verwijderen",
|
|
374
374
|
"removeGroup": "Groep verwijderen",
|
|
375
|
+
"loadingOptions": "Opties laden",
|
|
375
376
|
"removeRule": "Regel verwijderen",
|
|
376
377
|
"resultsOnPage": "{resultCount} resulta(a)t(en) op deze pagina",
|
|
377
378
|
"retrieval": "Retrieval",
|
|
@@ -380,7 +381,7 @@
|
|
|
380
381
|
"retrieverHybrid": "Hybride",
|
|
381
382
|
"retrieverSemantic": "Semantic",
|
|
382
383
|
"searchError": "Fout",
|
|
383
|
-
"searchFieldsPlaceholder": "
|
|
384
|
+
"searchFieldsPlaceholder": "Typ om velden te selecteren",
|
|
384
385
|
"searchPlaceholder": "Zoek naar {entityType}…",
|
|
385
386
|
"searchResultsPagination": "Paginering zoekresultaten",
|
|
386
387
|
"selectDateAndTime": "Selecteer datum",
|
|
@@ -388,6 +389,7 @@
|
|
|
388
389
|
"selectOrEnterValue": "Selecteer of typ een waarde",
|
|
389
390
|
"selectSpecificPathPlaceholder": "Selecteer een specifiek pad...",
|
|
390
391
|
"showFilters": "Toon filters",
|
|
392
|
+
"startTypingToLoadOptions": "Begin met typen om opties te laden",
|
|
391
393
|
"structuredFilters": "Gestructureerde filters",
|
|
392
394
|
"toDate": "Tot datum",
|
|
393
395
|
"toNumber": "Tot",
|
|
@@ -129,17 +129,17 @@ const resultColumToPropertyMap: ResultColumToPropertyMap<SubscriptionListItem> =
|
|
|
129
129
|
|
|
130
130
|
/* These options will be added as the first options in the field dropdown in the FieldSelector */
|
|
131
131
|
const prefilledFieldOptions: FieldToOperatorMap = new Map([
|
|
132
|
-
['subscription.subscription_id', ['eq', 'neq', 'like']],
|
|
133
|
-
['subscription.description', ['eq', 'neq', 'like']],
|
|
134
|
-
['subscription.status', ['eq', 'neq', 'like']],
|
|
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
135
|
['subscription.insync', ['eq', 'neq']],
|
|
136
|
-
['subscription.product.name', ['eq', 'neq', 'like']],
|
|
137
|
-
['subscription.product.tag', ['eq', 'neq', 'like']],
|
|
138
|
-
['subscription.customer_name', ['eq', 'neq', 'like']],
|
|
139
|
-
['subscription.customer_abbreviation', ['eq', 'neq', 'like']],
|
|
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
140
|
['subscription.start_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
141
141
|
['subscription.end_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
142
|
-
['subscription.note', ['eq', 'neq', 'like']],
|
|
142
|
+
['subscription.note', ['eq', 'neq', 'like', 'not_regexp']],
|
|
143
143
|
]);
|
|
144
144
|
|
|
145
145
|
export const WfoSearchPocPage = () => {
|
package/src/types/search.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type PaginatedSearchResults = {
|
|
|
35
35
|
total_items: number;
|
|
36
36
|
start_cursor: number;
|
|
37
37
|
end_cursor: number;
|
|
38
|
-
};
|
|
38
|
+
} | null;
|
|
39
39
|
page_info: {
|
|
40
40
|
has_next_page: boolean;
|
|
41
41
|
next_page_cursor: string | null;
|
|
@@ -219,6 +219,5 @@ export type WfoQueryBuilderContext = {
|
|
|
219
219
|
onFieldSelected: (field: string, operators: string[], pathInfo?: PathInfo) => void;
|
|
220
220
|
prefilledFieldOptions: FieldToOperatorMap;
|
|
221
221
|
fieldPathInfoMap: Map<string, PathInfo>;
|
|
222
|
-
onValueEditorEnter: () => void;
|
|
223
222
|
useAdvancedNestedSearch: boolean;
|
|
224
223
|
};
|