@orchestrator-ui/orchestrator-ui-components 8.8.1 → 8.8.2

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 (28) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +12 -12
  4. package/CHANGELOG.md +12 -0
  5. package/dist/index.d.ts +22 -5
  6. package/dist/index.js +1247 -1135
  7. package/dist/index.js.map +1 -1
  8. package/package.json +4 -3
  9. package/src/components/WfoPydanticForm/fields/WfoMultiCheckboxField.tsx +2 -1
  10. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +12 -8
  11. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +23 -10
  12. package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +34 -3
  13. package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +4 -2
  14. package/src/components/WfoTable/WfoTable/WfoTable.tsx +77 -46
  15. package/src/components/WfoTable/WfoTable/WfoTableSkeletonRows.tsx +52 -0
  16. package/src/components/WfoTable/WfoTable/index.ts +1 -0
  17. package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.tsx +21 -18
  18. package/src/components/WfoTable/WfoTableSettingsModal/styles.ts +17 -0
  19. package/src/components/WfoTable/utils/tableConfigPersistence.ts +1 -0
  20. package/src/configuration/version.ts +1 -1
  21. package/src/hooks/useGetPydanticFormsConfig.tsx +5 -3
  22. package/src/hooks/useSearch.ts +2 -2
  23. package/src/hooks/useStoredTableConfig.ts +1 -0
  24. package/src/messages/en-GB.json +2 -0
  25. package/src/messages/nl-NL.json +2 -0
  26. package/src/pages/WfoSearchPocPage.tsx +28 -23
  27. package/src/pages/processes/WfoProductInformationWithLink.tsx +9 -4
  28. package/src/types/search.ts +8 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchestrator-ui/orchestrator-ui-components",
3
- "version": "8.8.1",
3
+ "version": "8.8.2",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Library of UI Components used to display the workflow orchestrator frontend",
6
6
  "author": {
@@ -51,7 +51,7 @@
51
51
  "next-query-params": "^5.0.0",
52
52
  "object-hash": "^3.0.0",
53
53
  "prism-themes": "^1.9.0",
54
- "pydantic-forms": "^3.0.1",
54
+ "pydantic-forms": "^3.1.0",
55
55
  "react-diff-view": "^3.2.0",
56
56
  "react-draggable": "^4.4.6",
57
57
  "react-querybuilder": "^8.18.0",
@@ -59,7 +59,8 @@
59
59
  "react-select": "^5.8.0",
60
60
  "scroll-into-view": "^1.16.2",
61
61
  "unidiff": "^1.0.4",
62
- "use-query-params": "^2.2.2"
62
+ "use-query-params": "^2.2.2",
63
+ "zod": "^4.4.3"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@elastic/eui": "^113.0.0",
@@ -24,7 +24,8 @@ export const WfoMultiCheckboxField: PydanticFormControlledElement = ({ pydanticF
24
24
 
25
25
  const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState<Record<string, boolean>>({});
26
26
 
27
- const { options, id } = pydanticFormField;
27
+ const { arrayItem, id } = pydanticFormField;
28
+ const options = arrayItem?.options || [];
28
29
 
29
30
  const checkboxes = options?.map((option, index) => ({
30
31
  label: option.label,
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { FC, useEffect, useRef, useState } from 'react';
2
2
  import { FieldSelectorProps } from 'react-querybuilder';
3
3
 
4
4
  import { useTranslations } from 'next-intl';
@@ -7,7 +7,7 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui';
7
7
  import { EuiComboBox } from '@elastic/eui';
8
8
 
9
9
  import { usePathAutocomplete } from '@/hooks';
10
- import { EntityKind, FieldToOperatorMap, PathInfo } from '@/types';
10
+ import { EntityKind, PathInfo, WfoQueryBuilderContext } from '@/types';
11
11
 
12
12
  // react-querybuilder applies the `.rule` class to the rule container and `.rule-value` to
13
13
  // the value-editor cell (see `standardClassnames` in @react-querybuilder/core). We hop from
@@ -30,9 +30,13 @@ const focusValueEditorAfterRender = (searchInput: HTMLInputElement | null) => {
30
30
  });
31
31
  };
32
32
 
33
- export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: FieldSelectorProps) => {
33
+ interface WfoFieldSelectorProps extends Omit<FieldSelectorProps, 'context'> {
34
+ context: WfoQueryBuilderContext;
35
+ }
36
+
37
+ export const WfoFieldSelector: FC<WfoFieldSelectorProps> = ({ handleOnChange, disabled, rule, context }) => {
34
38
  const { field } = rule;
35
- const prefilledFieldOptions: FieldToOperatorMap = context.prefilledFieldOptions;
39
+ const { useAdvancedNestedSearch, prefilledFieldOptions, onFieldSelected } = context;
36
40
  const [selectedValue, setSelectedValue] = useState<string>(field);
37
41
  const [searchInput, setSearchInput] = useState<HTMLInputElement | null>(null);
38
42
  const optionsRef = useRef<EuiComboBoxOptionOption<string>[]>([]);
@@ -43,13 +47,13 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
43
47
  label: path,
44
48
  });
45
49
 
50
+ const isSelectablePath = (path: string) => useAdvancedNestedSearch || !path.includes('.');
51
+
46
52
  const getOptionsFromPathInfo = (pathInfos: PathInfo[]): EuiComboBoxOptionOption<string>[] => {
47
53
  const pathOptions: EuiComboBoxOptionOption<string>[] = [];
48
54
 
49
55
  pathInfos.forEach((pathInfo) => {
50
- pathOptions.push(getOption(pathInfo.path));
51
- // Adds more specific paths
52
- pathInfo.availablePaths?.forEach((path) => {
56
+ [pathInfo.path, ...(pathInfo.availablePaths ?? [])].filter(isSelectablePath).forEach((path) => {
53
57
  pathOptions.push(getOption(path));
54
58
  });
55
59
  });
@@ -85,7 +89,7 @@ export const WfoFieldSelector = ({ handleOnChange, disabled, rule, context }: Fi
85
89
  ?? paths.find((path) => path.availablePaths?.includes(selectedValue));
86
90
  const operators = matchingPath?.operators ?? prefilledFieldOptions.get(selectedValue) ?? [];
87
91
 
88
- context?.onFieldSelected?.(selectedValue, operators, matchingPath);
92
+ onFieldSelected(selectedValue, operators, matchingPath);
89
93
  };
90
94
 
91
95
  const handleFieldSelection = (selectedOptions: EuiComboBoxOptionOption<string>[]) => {
@@ -1,5 +1,11 @@
1
- import React, { useEffect, useMemo, useRef, useState } from 'react';
2
- import { FullOperator, QueryBuilder, type RuleGroupType, generateID } from 'react-querybuilder';
1
+ import React, { type ComponentType, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ type FieldSelectorProps,
4
+ FullOperator,
5
+ QueryBuilder,
6
+ type RuleGroupType,
7
+ generateID,
8
+ } from 'react-querybuilder';
3
9
  import 'react-querybuilder/dist/query-builder.css';
4
10
 
5
11
  import { useTranslations } from 'next-intl';
@@ -10,7 +16,7 @@ import { SearchParams, WfoAutoExpandableTextArea, WfoTextAnchor } from '@/compon
10
16
  import { WfoCombinatorSelector } from '@/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector';
11
17
  import { useFieldsPathInfo, useWithOrchestratorTheme } from '@/hooks';
12
18
  import { EntityKind, OperatorDisplay } from '@/types';
13
- import type { FieldToOperatorMap, PathInfo } from '@/types';
19
+ import type { FieldToOperatorMap, PathInfo, WfoQueryBuilderContext } from '@/types';
14
20
 
15
21
  import { WfoFieldSelector } from './WfoFieldSelector';
16
22
  import { WfoInlineCombinator } from './WfoInlineCombinator';
@@ -65,6 +71,7 @@ interface WfoFilterBuilderProps {
65
71
  handleSearch: (searchParams?: SearchParams) => void;
66
72
  onToggleFilterBuilder: (isVisible: boolean) => void;
67
73
  prefilledFieldOptions: FieldToOperatorMap;
74
+ useAdvancedNestedSearch?: boolean;
68
75
  }
69
76
 
70
77
  const initialRuleGroup: RuleGroupType = {
@@ -87,6 +94,7 @@ export const WfoFilterBuilder = ({
87
94
  handleSearch,
88
95
  prefilledFieldOptions,
89
96
  onToggleFilterBuilder,
97
+ useAdvancedNestedSearch = true,
90
98
  }: WfoFilterBuilderProps) => {
91
99
  const mapOperatorsToRQBOperatorOptions = (operators?: string[]): FullOperator[] => {
92
100
  return (operators ?? []).map((operator) => {
@@ -143,6 +151,14 @@ export const WfoFilterBuilder = ({
143
151
  );
144
152
  const resolvedFieldsPathInfo = useFieldsPathInfo(unresolvedFields, EntityKind.SUBSCRIPTION);
145
153
 
154
+ const queryBuilderContext: WfoQueryBuilderContext = {
155
+ onFieldSelected: handleFieldSelected,
156
+ prefilledFieldOptions,
157
+ fieldPathInfoMap,
158
+ onValueEditorEnter: handleValueEditorEnter,
159
+ useAdvancedNestedSearch,
160
+ };
161
+
146
162
  useEffect(() => {
147
163
  resolvedFieldsPathInfo.forEach((pathInfo, field) => {
148
164
  if (pathInfo && !fieldPathInfoMap.has(field)) {
@@ -162,18 +178,15 @@ export const WfoFilterBuilder = ({
162
178
  latestRuleGroupRef.current = ruleGroup;
163
179
  onUpdateQueryBuilder(ruleGroup);
164
180
  }}
165
- context={{
166
- onFieldSelected: handleFieldSelected,
167
- prefilledFieldOptions,
168
- fieldPathInfoMap,
169
- onValueEditorEnter: handleValueEditorEnter,
170
- }}
181
+ context={queryBuilderContext}
171
182
  getOperators={(field) => {
172
183
  const operators = fieldToOperatorMap.get(field);
173
184
  return mapOperatorsToRQBOperatorOptions(operators);
174
185
  }}
175
186
  controlElements={{
176
- fieldSelector: WfoFieldSelector,
187
+ // WfoFieldSelector requires the context this QueryBuilder always provides,
188
+ // while react-querybuilder declares it optional — hence the cast.
189
+ fieldSelector: WfoFieldSelector as ComponentType<FieldSelectorProps>,
177
190
  operatorSelector: WfoOperatorSelector,
178
191
  valueEditor: WfoValueEditor,
179
192
  ruleGroup: WfoRuleGroup,
@@ -80,6 +80,7 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
80
80
  rowExpandingConfiguration: WfoTableProps<T>['rowExpandingConfiguration'];
81
81
  defaultHiddenColumns?: TableColumnKeys<T>;
82
82
  defaultShowMatchDetails?: boolean;
83
+ defaultAdvancedNestedSearch?: boolean;
83
84
  queryText?: string;
84
85
  localStorageKey: string;
85
86
  exportDataIsLoading?: boolean;
@@ -101,7 +102,7 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
101
102
  onUpdateQueryBuilder: (ruleGroup: RuleGroupType | false) => void;
102
103
  handleSearch: (searchParams?: SearchParams) => void;
103
104
  pageSize: number;
104
- setPageSize: React.Dispatch<React.SetStateAction<number>>;
105
+ setPageSize: (updatedPageSize: number) => void;
105
106
  totalItems: number | false;
106
107
  hasNextPage: boolean;
107
108
  prefilledFieldOptions: FieldToOperatorMap;
@@ -111,6 +112,7 @@ export const WfoStructuredSearchTable = <T extends object>({
111
112
  tableColumnConfig,
112
113
  defaultHiddenColumns = [],
113
114
  defaultShowMatchDetails = false,
115
+ defaultAdvancedNestedSearch = true,
114
116
  queryText,
115
117
  localStorageKey,
116
118
  exportDataIsLoading,
@@ -148,6 +150,7 @@ export const WfoStructuredSearchTable = <T extends object>({
148
150
  const [rowDetailModalData, setRowDetailModalData] = useState<T | undefined>(undefined);
149
151
  const [showInformationModal, setShowInformationModal] = useState(false);
150
152
  const [showMatchDetails, setShowMatchDetails] = useState(defaultShowMatchDetails);
153
+ const [advancedNestedSearch, setAdvancedNestedSearch] = useState(defaultAdvancedNestedSearch);
151
154
  const t = useTranslations('common');
152
155
 
153
156
  useEffect(() => {
@@ -160,6 +163,10 @@ export const WfoStructuredSearchTable = <T extends object>({
160
163
  setShowMatchDetails(defaultShowMatchDetails);
161
164
  }, [defaultShowMatchDetails]);
162
165
 
166
+ useEffect(() => {
167
+ setAdvancedNestedSearch(defaultAdvancedNestedSearch);
168
+ }, [defaultAdvancedNestedSearch]);
169
+
163
170
  useEffect(() => {
164
171
  if (filterString) {
165
172
  setIsFilterBuilderVisible(true);
@@ -199,10 +206,11 @@ export const WfoStructuredSearchTable = <T extends object>({
199
206
  hiddenColumns: updatedHiddenColumns,
200
207
  selectedPageSize: updatedTableConfig.selectedPageSize,
201
208
  showMatchDetails,
209
+ advancedNestedSearch,
202
210
  });
203
211
  };
204
212
 
205
- // The toggle applies live, so persist it immediately alongside the currently committed
213
+ // The toggles apply live, so persist them immediately alongside the currently committed
206
214
  // hidden columns and page size instead of waiting for the modal's "Update" action.
207
215
  const handleToggleShowMatchDetails = (checked: boolean) => {
208
216
  setShowMatchDetails(checked);
@@ -210,6 +218,17 @@ export const WfoStructuredSearchTable = <T extends object>({
210
218
  hiddenColumns,
211
219
  selectedPageSize: pageSize ?? DEFAULT_PAGE_SIZE,
212
220
  showMatchDetails: checked,
221
+ advancedNestedSearch,
222
+ });
223
+ };
224
+
225
+ const handleToggleAdvancedNestedSearch = (checked: boolean) => {
226
+ setAdvancedNestedSearch(checked);
227
+ setTableConfigToLocalStorage(localStorageKey, {
228
+ hiddenColumns,
229
+ selectedPageSize: pageSize ?? DEFAULT_PAGE_SIZE,
230
+ showMatchDetails,
231
+ advancedNestedSearch: checked,
213
232
  });
214
233
  };
215
234
 
@@ -218,6 +237,7 @@ export const WfoStructuredSearchTable = <T extends object>({
218
237
  setHiddenColumns(defaultTableConfig.hiddenColumns);
219
238
  setPageSize(defaultTableConfig.selectedPageSize);
220
239
  setShowMatchDetails(defaultTableConfig.showMatchDetails ?? false);
240
+ setAdvancedNestedSearch(defaultTableConfig.advancedNestedSearch ?? false);
221
241
  setShowTableSettingsModal(false);
222
242
  clearTableConfigFromLocalStorage(localStorageKey);
223
243
  };
@@ -272,6 +292,7 @@ export const WfoStructuredSearchTable = <T extends object>({
272
292
  handleSearch={handleSearch}
273
293
  onToggleFilterBuilder={setIsFilterBuilderVisible}
274
294
  prefilledFieldOptions={prefilledFieldOptions}
295
+ useAdvancedNestedSearch={advancedNestedSearch}
275
296
  />
276
297
  </>
277
298
  )}
@@ -290,6 +311,7 @@ export const WfoStructuredSearchTable = <T extends object>({
290
311
  dataSorting={dataSorting}
291
312
  data={data}
292
313
  isLoading={isLoading}
314
+ loadingSkeletonRowCount={pageSize}
293
315
  {...tableProps}
294
316
  />
295
317
 
@@ -323,6 +345,15 @@ export const WfoStructuredSearchTable = <T extends object>({
323
345
  compressed
324
346
  />
325
347
  </EuiFormRow>
348
+ <EuiFormRow label={t('advancedNestedSearch')} display="columnCompressed">
349
+ <EuiSwitch
350
+ showLabel={false}
351
+ label={t('advancedNestedSearch')}
352
+ checked={advancedNestedSearch}
353
+ onChange={(event) => handleToggleAdvancedNestedSearch(event.target.checked)}
354
+ compressed
355
+ />
356
+ </EuiFormRow>
326
357
  <EuiFormRow label={t('retrieval')} display="columnCompressed">
327
358
  <EuiSelect
328
359
  options={[
@@ -340,7 +371,7 @@ export const WfoStructuredSearchTable = <T extends object>({
340
371
  <>
341
372
  <EuiSpacer size="m" />
342
373
  <EuiButton isLoading={exportDataIsLoading} onClick={() => onExportData()} fullWidth>
343
- {t('export')}
374
+ {totalItems ? t('exportRows', { numberOfRows: totalItems }) : t('export')}
344
375
  </EuiButton>
345
376
  </>
346
377
  )}
@@ -10,6 +10,7 @@ import { EuiButtonGroup, EuiDatePicker, EuiFieldNumber, EuiFieldText } from '@el
10
10
  import { WfoRangeEditor } from '@/components/WfoTable/WfoStructuredSearchTable/WfoRangeEditor';
11
11
  import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
12
12
  import { useWithOrchestratorTheme } from '@/hooks';
13
+ import type { WfoQueryBuilderContext } from '@/types';
13
14
 
14
15
  export interface EditorInputFieldProps<T = string> {
15
16
  handleOnChange: ValueEditorProps['handleOnChange'];
@@ -176,7 +177,8 @@ export const WfoValueEditor = ({
176
177
  return TextEditor;
177
178
  };
178
179
 
179
- const fieldPathInfoMap = context?.fieldPathInfoMap;
180
+ const queryBuilderContext: WfoQueryBuilderContext | undefined = context;
181
+ const fieldPathInfoMap = queryBuilderContext?.fieldPathInfoMap;
180
182
 
181
183
  const fieldInfo = fieldPathInfoMap && fieldPathInfoMap.has(fieldName) ? fieldPathInfoMap.get(fieldName) : undefined;
182
184
  const uiFieldType = fieldInfo?.ui_types?.[0] || UiFieldType.text;
@@ -198,7 +200,7 @@ export const WfoValueEditor = ({
198
200
  // the boolean buttons means "select" — its click fires only after this keydown, so
199
201
  // searching there would use the pre-toggle value.
200
202
  if (!(event.target instanceof HTMLInputElement)) return;
201
- context?.onValueEditorEnter?.();
203
+ queryBuilderContext?.onValueEditorEnter();
202
204
  };
203
205
 
204
206
  // react-querybuilder delivers the standard `rule-value` class via this prop; the wrapper
@@ -13,6 +13,7 @@ import { DEFAULT_PAGE_SIZES } from '../utils/constants';
13
13
  import { getPageCount } from '../utils/tableUtils';
14
14
  import { WfoTableDataRows } from './WfoTableDataRows';
15
15
  import { WfoTableHeaderRow } from './WfoTableHeaderRow';
16
+ import { WfoTableSkeletonRows } from './WfoTableSkeletonRows';
16
17
  import { getWfoTableStyles } from './styles';
17
18
  import { getColumnWidthsFromConfig, getSortedVisibleColumns, usePageIndexBoundsGuard } from './utils';
18
19
 
@@ -78,6 +79,7 @@ export type WfoTableProps<T extends object> = {
78
79
  hiddenColumns?: TableColumnKeys<T>;
79
80
  columnOrder?: TableColumnKeys<T>;
80
81
  isLoading?: boolean;
82
+ loadingSkeletonRowCount?: number;
81
83
  dataSorting?: WfoDataSorting<T>[];
82
84
  rowExpandingConfiguration?: {
83
85
  uniqueRowId: keyof WfoTableColumnConfig<T>;
@@ -106,6 +108,7 @@ export const WfoTable = <T extends object>({
106
108
  hiddenColumns = [],
107
109
  columnOrder = [],
108
110
  isLoading = false,
111
+ loadingSkeletonRowCount,
109
112
  dataSorting = [],
110
113
  rowExpandingConfiguration,
111
114
  showExpandedRows = false,
@@ -125,6 +128,8 @@ export const WfoTable = <T extends object>({
125
128
  const dataLength = data.length;
126
129
  usePageIndexBoundsGuard({ dataLength, isLoading, pagination });
127
130
 
131
+ const showLoadingSkeleton = !!loadingSkeletonRowCount && isLoading && dataLength === 0;
132
+
128
133
  const parentRef = useRef<HTMLDivElement>(null);
129
134
 
130
135
  const columnConfigWithFiller: WfoTableColumnConfig<T> =
@@ -192,6 +197,77 @@ export const WfoTable = <T extends object>({
192
197
  const virtualTrHeight = virtualItems[0]?.start ?? 0;
193
198
  const bottomSpacerHeight = totalSize - lastVirtualItemEnd;
194
199
 
200
+ const WfoTableBody = () => {
201
+ if (showLoadingSkeleton) {
202
+ return (
203
+ <tbody css={bodyLoadingStyle} aria-busy={true}>
204
+ <WfoTableSkeletonRows
205
+ rowCount={loadingSkeletonRowCount}
206
+ columnConfig={configWithLocalWidths}
207
+ hiddenColumns={hiddenColumns}
208
+ columnOrder={columnOrder}
209
+ />
210
+ </tbody>
211
+ );
212
+ }
213
+
214
+ if (dataLength === 0) {
215
+ return (
216
+ <tbody css={isLoading && bodyLoadingStyle}>
217
+ <tr css={rowStyle}>
218
+ <td colSpan={sortedVisibleColumns.length} css={[cellStyle, emptyTableMessageStyle]}>
219
+ {isLoading ? t('loading') : t('noItemsFound')}
220
+ </td>
221
+ </tr>
222
+ </tbody>
223
+ );
224
+ }
225
+
226
+ if (isVirtualized && height) {
227
+ return (
228
+ <tbody>
229
+ <tr
230
+ style={{
231
+ height: virtualTrHeight,
232
+ }}
233
+ />
234
+
235
+ {virtualItems.map((virtualRow) => (
236
+ <WfoTableDataRows
237
+ key={virtualRow.key}
238
+ data={[data[virtualRow.index]]}
239
+ columnConfig={configWithLocalWidths}
240
+ hiddenColumns={hiddenColumns}
241
+ columnOrder={columnOrder}
242
+ rowExpandingConfiguration={rowExpandingConfiguration}
243
+ showExpandedRows={showExpandedRows}
244
+ onRowClick={onRowClick}
245
+ />
246
+ ))}
247
+ <tr
248
+ style={{
249
+ height: bottomSpacerHeight,
250
+ }}
251
+ />
252
+ </tbody>
253
+ );
254
+ }
255
+
256
+ return (
257
+ <tbody css={isLoading && bodyLoadingStyle}>
258
+ <WfoTableDataRows
259
+ data={data}
260
+ columnConfig={configWithLocalWidths}
261
+ hiddenColumns={hiddenColumns}
262
+ columnOrder={columnOrder}
263
+ rowExpandingConfiguration={rowExpandingConfiguration}
264
+ showExpandedRows={showExpandedRows}
265
+ onRowClick={onRowClick}
266
+ />
267
+ </tbody>
268
+ );
269
+ };
270
+
195
271
  return (
196
272
  <>
197
273
  <div
@@ -214,52 +290,7 @@ export const WfoTable = <T extends object>({
214
290
  />
215
291
  </thead>
216
292
  }
217
- {dataLength === 0 ?
218
- <tbody css={isLoading && bodyLoadingStyle}>
219
- <tr css={rowStyle}>
220
- <td colSpan={sortedVisibleColumns.length} css={[cellStyle, emptyTableMessageStyle]}>
221
- {isLoading ? t('loading') : t('noItemsFound')}
222
- </td>
223
- </tr>
224
- </tbody>
225
- : isVirtualized && height ?
226
- <tbody>
227
- <tr
228
- style={{
229
- height: virtualTrHeight,
230
- }}
231
- />
232
-
233
- {virtualItems.map((virtualRow) => (
234
- <WfoTableDataRows
235
- key={virtualRow.key}
236
- data={[data[virtualRow.index]]}
237
- columnConfig={configWithLocalWidths}
238
- hiddenColumns={hiddenColumns}
239
- columnOrder={columnOrder}
240
- rowExpandingConfiguration={rowExpandingConfiguration}
241
- showExpandedRows={showExpandedRows}
242
- onRowClick={onRowClick}
243
- />
244
- ))}
245
- <tr
246
- style={{
247
- height: bottomSpacerHeight,
248
- }}
249
- />
250
- </tbody>
251
- : <tbody css={isLoading && bodyLoadingStyle}>
252
- <WfoTableDataRows
253
- data={data}
254
- columnConfig={configWithLocalWidths}
255
- hiddenColumns={hiddenColumns}
256
- columnOrder={columnOrder}
257
- rowExpandingConfiguration={rowExpandingConfiguration}
258
- showExpandedRows={showExpandedRows}
259
- onRowClick={onRowClick}
260
- />
261
- </tbody>
262
- }
293
+ {<WfoTableBody />}
263
294
  </table>
264
295
  </div>
265
296
  {pagination && (
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+
3
+ import { EuiSkeletonText } from '@elastic/eui';
4
+
5
+ import { useWithOrchestratorTheme } from '@/hooks';
6
+ import { toOptionalArrayEntry } from '@/utils';
7
+
8
+ import { ColumnType, WfoTableProps } from './WfoTable';
9
+ import { getWfoTableStyles } from './styles';
10
+ import { getSortedVisibleColumns } from './utils';
11
+
12
+ export type WfoTableSkeletonRowsProps<T extends object> = Pick<
13
+ WfoTableProps<T>,
14
+ 'columnConfig' | 'hiddenColumns' | 'columnOrder'
15
+ > & {
16
+ rowCount: number;
17
+ };
18
+
19
+ export const WfoTableSkeletonRows = <T extends object>({
20
+ rowCount,
21
+ columnConfig,
22
+ hiddenColumns = [],
23
+ columnOrder = [],
24
+ }: WfoTableSkeletonRowsProps<T>) => {
25
+ const { cellStyle, cellContentStyle, rowStyle, setWidth } = useWithOrchestratorTheme(getWfoTableStyles);
26
+
27
+ const sortedVisibleColumns = getSortedVisibleColumns(columnConfig, columnOrder, hiddenColumns);
28
+
29
+ return (
30
+ <>
31
+ {[...Array(rowCount)].map((_, rowIndex) => (
32
+ <tr key={`skeleton-row-${rowIndex}`} css={rowStyle}>
33
+ {sortedVisibleColumns.map(([key, columnConfig]) => (
34
+ <td
35
+ key={key}
36
+ css={[
37
+ ...toOptionalArrayEntry(cellStyle, !columnConfig.disableDefaultCellStyle),
38
+ setWidth(columnConfig.width),
39
+ ]}
40
+ >
41
+ {columnConfig.columnType === ColumnType.DATA && (
42
+ <div css={cellContentStyle}>
43
+ <EuiSkeletonText lines={1} size="m" isLoading />
44
+ </div>
45
+ )}
46
+ </td>
47
+ ))}
48
+ </tr>
49
+ ))}
50
+ </>
51
+ );
52
+ };
@@ -11,6 +11,7 @@ export * from './WfoMultilineCell';
11
11
  export * from './WfoTable';
12
12
  export * from './WfoTableHeaderCell';
13
13
  export * from './WfoTableDataRows';
14
+ export * from './WfoTableSkeletonRows';
14
15
 
15
16
  export * from './WfoTruncateCell';
16
17
  export * from './WfoDataCell';
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
2
2
 
3
3
  import { useTranslations } from 'next-intl';
4
4
 
5
- import { EuiForm, EuiFormRow, EuiHorizontalRule, EuiSelect, EuiSpacer, EuiSwitch } from '@elastic/eui';
5
+ import { EuiForm, EuiFormRow, EuiHorizontalRule, EuiSelect, EuiSpacer, EuiSwitch, useEuiScrollBar } from '@elastic/eui';
6
6
 
7
7
  import { WfoSettingsModal } from '@/components';
8
8
  import { getWfoTableSettingsModalStyles } from '@/components/WfoTable/WfoTableSettingsModal/styles';
@@ -37,7 +37,8 @@ export const TableSettingsModal = <T,>({
37
37
  extraSettings,
38
38
  }: TableSettingsModalProps<T>) => {
39
39
  const t = useTranslations('main');
40
- const { formRowStyle, selectFieldStyle } = useWithOrchestratorTheme(getWfoTableSettingsModalStyles);
40
+ const { formRowStyle, columnsListStyle, selectFieldStyle } = useWithOrchestratorTheme(getWfoTableSettingsModalStyles);
41
+ const scrollBarStyle = useEuiScrollBar();
41
42
 
42
43
  const [columns, setColumns] = useState(tableConfig.columns);
43
44
  const [selectedPageSize, setSelectedPageSize] = useState(tableConfig.selectedPageSize);
@@ -72,22 +73,24 @@ export const TableSettingsModal = <T,>({
72
73
  }
73
74
  >
74
75
  <EuiForm>
75
- {columns.map(({ field, name, isVisible }) => (
76
- <div key={field.toString()}>
77
- <EuiFormRow display="columnCompressed" label={name} css={formRowStyle}>
78
- <EuiSwitch
79
- showLabel={false}
80
- label={name}
81
- checked={isVisible}
82
- onChange={() => {
83
- handleUpdateColumnVisibility(field);
84
- }}
85
- compressed
86
- />
87
- </EuiFormRow>
88
- <EuiHorizontalRule margin="xs" />
89
- </div>
90
- ))}
76
+ <div css={[columnsListStyle, scrollBarStyle]}>
77
+ {columns.map(({ field, name, isVisible }) => (
78
+ <div key={field.toString()}>
79
+ <EuiFormRow display="columnCompressed" label={name} css={formRowStyle}>
80
+ <EuiSwitch
81
+ showLabel={false}
82
+ label={name}
83
+ checked={isVisible}
84
+ onChange={() => {
85
+ handleUpdateColumnVisibility(field);
86
+ }}
87
+ compressed
88
+ />
89
+ </EuiFormRow>
90
+ <EuiHorizontalRule margin="xs" />
91
+ </div>
92
+ ))}
93
+ </div>
91
94
  <EuiSpacer size="xs" />
92
95
 
93
96
  <EuiFormRow css={formRowStyle} hasEmptyLabelSpace label={t('numberOfRows')} display="columnCompressed">
@@ -1,3 +1,4 @@
1
+ import { transparentize } from '@elastic/eui';
1
2
  import { css } from '@emotion/react';
2
3
 
3
4
  import { WfoThemeHelpers } from '@/hooks';
@@ -13,8 +14,24 @@ export const getWfoTableSettingsModalStyles = (wfoThemeHelpers: WfoThemeHelpers)
13
14
  },
14
15
  });
15
16
 
17
+ const { theme } = wfoThemeHelpers;
18
+
19
+ const columnsListStyle = css({
20
+ maxHeight: theme.base * 16,
21
+ overflowY: 'auto',
22
+ paddingLeft: theme.base / 4,
23
+ marginBottom: theme.base / 2,
24
+ backgroundImage: `linear-gradient(to top, ${theme.colors.textGhost} ${theme.base}px, transparent),
25
+ radial-gradient(farthest-side at 50% 100%, ${transparentize(theme.colors.shadow, 0.24)}, transparent)`,
26
+ backgroundPosition: `center bottom, center bottom -${theme.base / 2}px`,
27
+ backgroundSize: `100% ${theme.base * 2}px, 100% ${theme.base}px`,
28
+ backgroundRepeat: 'no-repeat',
29
+ backgroundAttachment: 'local, scroll',
30
+ });
31
+
16
32
  return {
17
33
  formRowStyle,
34
+ columnsListStyle,
18
35
  selectFieldStyle: formFieldBaseStyle,
19
36
  };
20
37
  };
@@ -4,6 +4,7 @@ export type StoredTableConfig<T> = {
4
4
  hiddenColumns: TableColumnKeys<T>;
5
5
  selectedPageSize: number;
6
6
  showMatchDetails?: boolean;
7
+ advancedNestedSearch?: boolean;
7
8
  };
8
9
 
9
10
  export const isValidLocalStorageTableConfig = <T>(object: StoredTableConfig<T>): object is StoredTableConfig<T> =>
@@ -1 +1 @@
1
- export const ORCHESTRATOR_UI_LIBRARY_VERSION = '8.8.1';
1
+ export const ORCHESTRATOR_UI_LIBRARY_VERSION = '8.8.2';
@@ -163,11 +163,13 @@ const useGetComponentMatcherExtender = (): ComponentMatcherExtender => {
163
163
  isControlledElement: true,
164
164
  },
165
165
  matcher(field) {
166
+ const fieldOptions = field.arrayItem?.options;
167
+
166
168
  return (
167
169
  field.type === PydanticFormFieldType.ARRAY
168
- && _.isArray(field.options)
169
- && field.options?.length > 0
170
- && field.options?.length <= 5
170
+ && _.isArray(fieldOptions)
171
+ && fieldOptions?.length > 0
172
+ && fieldOptions?.length <= 5
171
173
  );
172
174
  },
173
175
  validator: zodValidationPresets.multiSelect,