@orchestrator-ui/orchestrator-ui-components 8.5.0 → 8.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.turbo/turbo-build.log +10 -14
  2. package/.turbo/turbo-lint.log +2 -4
  3. package/.turbo/turbo-test.log +16 -15
  4. package/CHANGELOG.md +17 -0
  5. package/dist/index.d.ts +188 -80
  6. package/dist/index.js +3509 -2731
  7. package/dist/index.js.map +1 -1
  8. package/package.json +3 -1
  9. package/src/components/WfoAutoExpandableTextArea/WfoAutoExpandableTextArea.tsx +54 -0
  10. package/src/components/WfoAutoExpandableTextArea/index.ts +1 -0
  11. package/src/components/WfoAutoExpandableTextArea/styles.ts +18 -0
  12. package/src/components/WfoPageTemplate/paths.ts +1 -0
  13. package/src/components/WfoPydanticForm/fields/WfoCron.spec.ts +88 -0
  14. package/src/components/WfoPydanticForm/fields/WfoCron.tsx +233 -0
  15. package/src/components/WfoPydanticForm/fields/index.ts +1 -0
  16. package/src/components/WfoPydanticForm/fields/styles.ts +92 -0
  17. package/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResultItem.tsx +2 -1
  18. package/src/components/WfoSearchPage/utils.ts +37 -1
  19. package/src/components/WfoSubscription/WfoInUseByRelations.tsx +6 -2
  20. package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionExpandableMenuItem.tsx +19 -14
  21. package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +13 -1
  22. package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem.tsx +3 -0
  23. package/src/components/WfoSubscription/WfoSubscriptionProductBlock/WfoSubscriptionProductBlock.tsx +1 -1
  24. package/src/components/WfoSubscriptionsList/WfoSubscriptionsList.tsx +5 -0
  25. package/src/components/WfoSubscriptionsList/subscriptionResultMappers.ts +3 -1
  26. package/src/components/WfoTable/WfoAdvancedTable/WfoAdvancedTable.tsx +2 -12
  27. package/src/components/WfoTable/WfoStructuredSearchTable/WfoAddGroupAction.tsx +1 -1
  28. package/src/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector.tsx +3 -0
  29. package/src/components/WfoTable/WfoStructuredSearchTable/WfoExpandingSearchRow.tsx +21 -14
  30. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +84 -4
  31. package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +119 -104
  32. package/src/components/WfoTable/WfoStructuredSearchTable/WfoInlineCombinator.tsx +17 -0
  33. package/src/components/WfoTable/WfoStructuredSearchTable/WfoOperatorSelector.tsx +32 -7
  34. package/src/components/WfoTable/WfoStructuredSearchTable/WfoRemoveRuleAction.tsx +3 -3
  35. package/src/components/WfoTable/WfoStructuredSearchTable/WfoRuleGroup.tsx +11 -7
  36. package/src/components/WfoTable/WfoStructuredSearchTable/WfoSearchFieldWithActions.tsx +56 -0
  37. package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +92 -87
  38. package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +41 -11
  39. package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +74 -21
  40. package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +45 -0
  41. package/src/components/WfoTable/WfoTable/WfoTable.tsx +1 -0
  42. package/src/components/WfoTable/WfoTable/WfoTableDataRows.tsx +16 -4
  43. package/src/components/WfoTable/WfoTable/styles.ts +8 -0
  44. package/src/components/WfoTable/WfoTable/utils.spec.ts +62 -1
  45. package/src/components/WfoTable/WfoTable/utils.ts +4 -1
  46. package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.spec.ts +45 -0
  47. package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.tsx +9 -0
  48. package/src/components/WfoTable/WfoTableSettingsModal/index.ts +1 -0
  49. package/src/components/WfoTable/WfoTableSettingsModal/utils.ts +17 -0
  50. package/src/components/index.ts +1 -0
  51. package/src/configuration/version.ts +1 -1
  52. package/src/hooks/index.ts +1 -0
  53. package/src/hooks/useGetPydanticFormsConfig.tsx +12 -0
  54. package/src/hooks/useLanguageCode.ts +11 -0
  55. package/src/messages/en-GB.json +20 -0
  56. package/src/messages/nl-NL.json +20 -0
  57. package/src/pages/WfoSearchPocPage.tsx +165 -30
  58. package/src/rtk/endpoints/subscriptionList.ts +1 -0
  59. package/src/theme/baseStyles/formFieldsBaseStyle.ts +0 -1
  60. package/src/types/search.ts +4 -1
  61. package/src/types/types.ts +1 -1
  62. package/src/utils/getDefaultTableConfig.ts +6 -1
  63. package/src/utils/index.ts +1 -0
  64. package/src/utils/integer.spec.ts +20 -0
  65. package/src/utils/integer.ts +3 -0
@@ -3,31 +3,22 @@ import type { RuleGroupType } from 'react-querybuilder';
3
3
 
4
4
  import { useTranslations } from 'next-intl';
5
5
 
6
- import {
7
- EuiButton,
8
- EuiButtonIcon,
9
- EuiFieldSearch,
10
- EuiFlexGroup,
11
- EuiFlexItem,
12
- EuiFormRow,
13
- EuiSelect,
14
- EuiSpacer,
15
- EuiText,
16
- } from '@elastic/eui';
6
+ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSpacer, EuiText } from '@elastic/eui';
17
7
 
18
8
  import {
19
9
  DEFAULT_PAGE_SIZE,
20
10
  DEFAULT_PAGE_SIZES,
21
11
  TableColumnKeys,
22
- TableSettingsColumnConfig,
23
12
  TableSettingsConfig,
24
13
  TableSettingsModal,
14
+ WfoDataSearch,
25
15
  WfoDataSorting,
26
16
  WfoErrorWithMessage,
27
17
  WfoInformationModal,
28
18
  WfoKeyValueTable,
29
19
  WfoKeyValueTableDataType,
30
20
  clearTableConfigFromLocalStorage,
21
+ getTableSettingsColumns,
31
22
  setTableConfigToLocalStorage,
32
23
  } from '@/components';
33
24
  import { getRowDetailData } from '@/components/WfoTable/WfoAdvancedTable/getRowDetailData';
@@ -39,12 +30,14 @@ import {
39
30
  import { useOrchestratorTheme, useWithOrchestratorTheme } from '@/hooks';
40
31
  import { WfoArrowsExpand } from '@/icons';
41
32
  import { WfoGraphqlError } from '@/rtk';
42
- import { getFormFieldsBaseStyle } from '@/theme';
43
- import { RetrieverType } from '@/types';
33
+ import { FieldToOperatorMap, RetrieverType } from '@/types';
44
34
  import { getDefaultTableConfig } from '@/utils';
45
35
 
46
36
  import { ColumnType, WfoTable, WfoTableProps } from '../WfoTable';
47
37
  import { WfoFilterBuilder } from './WfoFilterBuilder';
38
+ import { WfoSearchFieldWithActions } from './WfoSearchFieldWithActions';
39
+ import { getWfoStructuredSearchTableStyles } from './styles';
40
+ import { buildColumnFilter } from './utils';
48
41
 
49
42
  export type WfoStructuredSearchTableDataColumnConfigItem<
50
43
  T extends object,
@@ -85,6 +78,9 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
85
78
  onSearchQueryText: (queryString: string) => void;
86
79
  onShowMore: () => void;
87
80
  onUpdateDataSorting: (updateSorting: WfoDataSorting<T>) => void;
81
+ // Resolves a column key to its search field path (e.g. "status" -> "subscription.status"), used
82
+ // when a column header search adds a condition to the filter query.
83
+ getColumnSearchFieldName?: (field: keyof T) => string;
88
84
  onExportData?: () => void;
89
85
  retrieverType: RetrieverType;
90
86
  onUpdateRetrieverType: (newRetrieverType: RetrieverType) => void;
@@ -93,11 +89,12 @@ export type WfoStructuredSearchTableProps<T extends object> = Omit<
93
89
  isValidFilterString?: boolean;
94
90
  queryBuilderRuleGroup?: RuleGroupType;
95
91
  onUpdateQueryBuilder: (ruleGroup: RuleGroupType | false) => void;
96
- handleSearch: () => void;
92
+ handleSearch: (searchParams?: SearchParams) => void;
97
93
  pageSize: number;
98
94
  setPageSize: React.Dispatch<React.SetStateAction<number>>;
99
95
  totalItems: number | false;
100
96
  hasNextPage: boolean;
97
+ prefilledFieldOptions: FieldToOperatorMap;
101
98
  };
102
99
 
103
100
  export const WfoStructuredSearchTable = <T extends object>({
@@ -119,6 +116,7 @@ export const WfoStructuredSearchTable = <T extends object>({
119
116
  queryBuilderRuleGroup,
120
117
  onUpdateQueryBuilder,
121
118
  onUpdateDataSorting,
119
+ getColumnSearchFieldName,
122
120
  handleSearch,
123
121
  pageSize,
124
122
  setPageSize,
@@ -128,12 +126,13 @@ export const WfoStructuredSearchTable = <T extends object>({
128
126
  hasNextPage,
129
127
  data,
130
128
  isLoading,
129
+ prefilledFieldOptions,
131
130
  ...tableProps
132
131
  }: WfoStructuredSearchTableProps<T>) => {
133
132
  const { theme } = useOrchestratorTheme();
134
- const { formFieldBaseStyle } = useWithOrchestratorTheme(getFormFieldsBaseStyle);
135
-
133
+ const { toggleButtonStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
136
134
  const [hiddenColumns, setHiddenColumns] = useState<TableColumnKeys<T>>(defaultHiddenColumns);
135
+ const [isFilterBuilderVisible, setIsFilterBuilderVisible] = useState(false);
137
136
  const [showTableSettingsModal, setShowTableSettingsModal] = useState(false);
138
137
  const [rowDetailModalData, setRowDetailModalData] = useState<T | undefined>(undefined);
139
138
  const [showInformationModal, setShowInformationModal] = useState(false);
@@ -145,6 +144,12 @@ export const WfoStructuredSearchTable = <T extends object>({
145
144
  }
146
145
  }, [defaultHiddenColumns]);
147
146
 
147
+ useEffect(() => {
148
+ if (filterString) {
149
+ setIsFilterBuilderVisible(true);
150
+ }
151
+ }, [filterString]);
152
+
148
153
  const detailsIconColumn: WfoStructuredSearchTableColumnConfig<T> = {
149
154
  viewDetails: {
150
155
  columnType: ColumnType.CONTROL,
@@ -162,17 +167,7 @@ export const WfoStructuredSearchTable = <T extends object>({
162
167
  ...tableColumnConfig,
163
168
  };
164
169
 
165
- const tableSettingsColumns: TableSettingsColumnConfig<T>[] = Object.entries(tableColumnConfig).map(
166
- ([key, { label }]): TableSettingsColumnConfig<T> => {
167
- const field = key as keyof T;
168
-
169
- return {
170
- field,
171
- name: label,
172
- isVisible: hiddenColumns.indexOf(field) === -1,
173
- };
174
- },
175
- );
170
+ const tableSettingsColumns = getTableSettingsColumns(tableColumnConfig, hiddenColumns);
176
171
 
177
172
  const rowDetailData: WfoKeyValueTableDataType[] | undefined =
178
173
  rowDetailModalData && getRowDetailData(rowDetailModalData, tableColumnConfig);
@@ -198,10 +193,47 @@ export const WfoStructuredSearchTable = <T extends object>({
198
193
  clearTableConfigFromLocalStorage(localStorageKey);
199
194
  };
200
195
 
196
+ const handleColumnFilterSearch = ({ field, searchText }: WfoDataSearch<T>) => {
197
+ const columnFilter = buildColumnFilter(field, searchText, filterString, getColumnSearchFieldName);
198
+ if (!columnFilter) {
199
+ return;
200
+ }
201
+ onUpdateFilterString(columnFilter.filterString);
202
+ handleSearch({ ruleGroup: columnFilter.ruleGroup });
203
+ };
204
+
201
205
  return (
202
206
  <>
203
- <EuiFlexGroup alignItems="center">
204
- <EuiFlexItem>
207
+ <EuiFlexGroup alignItems="center" gutterSize="s">
208
+ {!isFilterBuilderVisible && (
209
+ <EuiFlexItem grow={false}>
210
+ <EuiButton
211
+ css={toggleButtonStyles}
212
+ onClick={() => setIsFilterBuilderVisible(true)}
213
+ id={'button-toggle-filter-builder'}
214
+ data-test-id={'button-toggle-filter-builder'}
215
+ fill
216
+ type="submit"
217
+ iconType="filter"
218
+ iconSide="left"
219
+ aria-label={t('createFilter')}
220
+ >
221
+ {t('createFilter')}
222
+ </EuiButton>
223
+ </EuiFlexItem>
224
+ )}
225
+ <WfoSearchFieldWithActions
226
+ queryText={queryText}
227
+ onChangeQueryText={onChangeQueryText}
228
+ onSearchQueryText={onSearchQueryText}
229
+ onShowInformation={() => setShowInformationModal(true)}
230
+ onShowTableSettings={() => setShowTableSettingsModal(true)}
231
+ />
232
+ </EuiFlexGroup>
233
+
234
+ {isFilterBuilderVisible && (
235
+ <>
236
+ <EuiSpacer size="s" />
205
237
  <WfoFilterBuilder
206
238
  filterString={filterString}
207
239
  onUpdateFilterString={onUpdateFilterString}
@@ -209,64 +241,11 @@ export const WfoStructuredSearchTable = <T extends object>({
209
241
  queryBuilderRuleGroup={queryBuilderRuleGroup}
210
242
  onUpdateQueryBuilder={onUpdateQueryBuilder}
211
243
  handleSearch={handleSearch}
244
+ onToggleFilterBuilder={setIsFilterBuilderVisible}
245
+ prefilledFieldOptions={prefilledFieldOptions}
212
246
  />
213
- </EuiFlexItem>
214
- </EuiFlexGroup>
215
-
216
- <EuiFlexGroup alignItems="center">
217
- <EuiFlexItem>
218
- <EuiFormRow fullWidth>
219
- <EuiFieldSearch
220
- css={formFieldBaseStyle}
221
- value={queryText}
222
- placeholder={`${t('search')}...`}
223
- onChange={(e) => onChangeQueryText(e.target.value)}
224
- onSearch={(queryText) => onSearchQueryText(queryText)}
225
- fullWidth
226
- />
227
- </EuiFormRow>
228
- </EuiFlexItem>
229
- <EuiFlexItem grow={false}>
230
- <EuiSelect
231
- options={[
232
- {
233
- value: RetrieverType.Auto,
234
- text: t('retrieverAuto'),
235
- },
236
- {
237
- value: RetrieverType.Fuzzy,
238
- text: t('retrieverFuzzy'),
239
- },
240
- {
241
- value: RetrieverType.Semantic,
242
- text: t('retrieverSemantic'),
243
- },
244
- {
245
- value: RetrieverType.Hybrid,
246
- text: t('retrieverHybrid'),
247
- },
248
- ]}
249
- value={retrieverType}
250
- onChange={(e) => onUpdateRetrieverType(e.target.value as RetrieverType)}
251
- compressed
252
- prepend={t('retrieval')}
253
- />
254
- </EuiFlexItem>
255
- <EuiFlexItem grow={false}>
256
- <EuiButtonIcon
257
- onClick={() => setShowInformationModal(true)}
258
- iconSize={'l'}
259
- iconType={'info'}
260
- aria-label={t('searchModalTitle')}
261
- />
262
- </EuiFlexItem>
263
- <EuiButton onClick={() => setShowTableSettingsModal(true)}>{t('editColumns')}</EuiButton>
264
- {onExportData && (
265
- <EuiButton isLoading={exportDataIsLoading} onClick={() => onExportData()}>
266
- {t('export')}
267
- </EuiButton>
268
- )}
269
- </EuiFlexGroup>
247
+ </>
248
+ )}
270
249
 
271
250
  {error && <WfoErrorWithMessage error={error} />}
272
251
 
@@ -277,6 +256,7 @@ export const WfoStructuredSearchTable = <T extends object>({
277
256
  hiddenColumns={hiddenColumns}
278
257
  rowExpandingConfiguration={rowExpandingConfiguration}
279
258
  onUpdateDataSorting={onUpdateDataSorting}
259
+ onUpdateDataSearch={handleColumnFilterSearch}
280
260
  dataSorting={dataSorting}
281
261
  data={data}
282
262
  isLoading={isLoading}
@@ -302,6 +282,31 @@ export const WfoStructuredSearchTable = <T extends object>({
302
282
  onClose={() => setShowTableSettingsModal(false)}
303
283
  onUpdateTableConfig={handleUpdateTableConfig}
304
284
  onResetToDefaults={handleResetToDefaults}
285
+ extraSettings={
286
+ <>
287
+ <EuiFormRow label={t('retrieval')} display="columnCompressed">
288
+ <EuiSelect
289
+ options={[
290
+ { value: RetrieverType.Auto, text: t('retrieverAuto') },
291
+ { value: RetrieverType.Fuzzy, text: t('retrieverFuzzy') },
292
+ { value: RetrieverType.Semantic, text: t('retrieverSemantic') },
293
+ { value: RetrieverType.Hybrid, text: t('retrieverHybrid') },
294
+ ]}
295
+ value={retrieverType}
296
+ onChange={(e) => onUpdateRetrieverType(e.target.value as RetrieverType)}
297
+ compressed
298
+ />
299
+ </EuiFormRow>
300
+ {onExportData && (
301
+ <>
302
+ <EuiSpacer size="m" />
303
+ <EuiButton isLoading={exportDataIsLoading} onClick={() => onExportData()} fullWidth>
304
+ {t('export')}
305
+ </EuiButton>
306
+ </>
307
+ )}
308
+ </>
309
+ }
305
310
  />
306
311
  )}
307
312
 
@@ -137,23 +137,53 @@ const DatePicker = ({ handleOnChange, rangeIndex, value: currentValue }: WfoRang
137
137
  );
138
138
  };
139
139
 
140
- export const WfoValueEditor = ({ field: fieldName, context, handleOnChange, operator, value }: ValueEditorProps) => {
140
+ export const WfoValueEditor = ({
141
+ field: fieldName,
142
+ context,
143
+ handleOnChange,
144
+ operator,
145
+ value,
146
+ className,
147
+ }: ValueEditorProps) => {
141
148
  const fieldPathInfoMap = context?.fieldPathInfoMap;
142
149
 
143
150
  const fieldInfo = fieldPathInfoMap && fieldPathInfoMap.has(fieldName) ? fieldPathInfoMap.get(fieldName) : undefined;
144
151
  const uiFieldType = fieldInfo?.ui_types[0] || UiFieldType.text;
145
152
 
146
- if (uiFieldType === UiFieldType.boolean) {
147
- return <BooleanEditor handleOnChange={handleOnChange} value={value} />;
148
- }
153
+ const getEditor = () => {
154
+ if (uiFieldType === UiFieldType.boolean) {
155
+ return <BooleanEditor handleOnChange={handleOnChange} value={value} />;
156
+ }
149
157
 
150
- if (uiFieldType === UiFieldType.datetime) {
151
- return <WfoRangeEditor handleOnChange={handleOnChange} operator={operator} Element={DatePicker} value={value} />;
152
- }
158
+ if (uiFieldType === UiFieldType.datetime) {
159
+ return <WfoRangeEditor handleOnChange={handleOnChange} operator={operator} Element={DatePicker} value={value} />;
160
+ }
153
161
 
154
- if (uiFieldType === UiFieldType.number) {
155
- return <WfoRangeEditor handleOnChange={handleOnChange} operator={operator} Element={NumberEditor} value={value} />;
156
- }
162
+ if (uiFieldType === UiFieldType.number) {
163
+ return (
164
+ <WfoRangeEditor handleOnChange={handleOnChange} operator={operator} Element={NumberEditor} value={value} />
165
+ );
166
+ }
167
+
168
+ return <TextEditor handleOnChange={handleOnChange} value={value} />;
169
+ };
157
170
 
158
- return <TextEditor handleOnChange={handleOnChange} value={value} />;
171
+ const handleWrapperKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
172
+ if (event.key !== 'Enter') return;
173
+ // Restrict to the editor inputs: the editors' own Enter handlers have already run
174
+ // (bubble phase) and committed the value via blur, so the search sees it. Enter on
175
+ // the boolean buttons means "select" — its click fires only after this keydown, so
176
+ // searching there would use the pre-toggle value.
177
+ if (!(event.target instanceof HTMLInputElement)) return;
178
+ context?.onValueEditorEnter?.();
179
+ };
180
+
181
+ // react-querybuilder delivers the standard `rule-value` class via this prop; the wrapper
182
+ // makes it queryable in the DOM (WfoFieldSelector relies on it to move focus here).
183
+ // `display: contents` keeps the children direct participants in the rule's flex row.
184
+ return (
185
+ <div className={className} style={{ display: 'contents' }} onKeyDown={handleWrapperKeyDown}>
186
+ {getEditor()}
187
+ </div>
188
+ );
159
189
  };
@@ -2,7 +2,7 @@ import { css } from '@emotion/react';
2
2
 
3
3
  import { WfoThemeHelpers } from '@/hooks';
4
4
 
5
- export const getWfoStructuredSearchTableStyles = ({ theme, toSecondaryColor }: WfoThemeHelpers) => {
5
+ export const getWfoStructuredSearchTableStyles = ({ theme }: WfoThemeHelpers) => {
6
6
  const queryBuilderContainerStyles = css({
7
7
  backgroundColor: theme.colors.backgroundBaseSubdued,
8
8
  padding: theme.base / 2,
@@ -13,20 +13,35 @@ export const getWfoStructuredSearchTableStyles = ({ theme, toSecondaryColor }: W
13
13
  const toggleButtonStyles = css({
14
14
  backgroundColor: 'primary',
15
15
  padding: theme.base * 0.75,
16
- marginLeft: theme.base,
17
- });
18
-
19
- const textAreaStyles = css({
20
- width: '100%',
21
- maxInlineSize: '100%',
22
- height: '42px',
23
- border: `this solid ${toSecondaryColor(theme.colors.borderBasePlain)}`,
24
16
  });
25
17
 
26
18
  const buttonGroupStyles = css({
27
- backgroundColor: theme.colors.textGhost,
28
- height: '100%',
29
- borderRadius: theme.border.radius.small,
19
+ alignSelf: 'center',
20
+ backgroundColor: 'transparent',
21
+ height: theme.base * 2,
22
+ '.euiButtonGroup__buttons': {
23
+ backgroundColor: 'transparent',
24
+ height: theme.base * 2,
25
+ minHeight: theme.base * 2,
26
+ },
27
+ 'button.euiButtonGroupButton': {
28
+ height: theme.base * 2,
29
+ minHeight: theme.base * 2,
30
+ blockSize: theme.base * 2,
31
+ borderRadius: 0,
32
+ transition: 'none',
33
+ },
34
+ 'button.euiButtonGroupButton:not(.euiButtonGroupButton-isSelected)': {
35
+ backgroundColor: theme.colors.backgroundBasePlain,
36
+ },
37
+ 'button.euiButtonGroupButton:first-of-type': {
38
+ borderTopLeftRadius: theme.base,
39
+ borderBottomLeftRadius: theme.base,
40
+ },
41
+ 'button.euiButtonGroupButton:last-of-type': {
42
+ borderTopRightRadius: theme.base,
43
+ borderBottomRightRadius: theme.base,
44
+ },
30
45
  });
31
46
 
32
47
  const ruleContainerStyles = css({
@@ -42,7 +57,7 @@ export const getWfoStructuredSearchTableStyles = ({ theme, toSecondaryColor }: W
42
57
 
43
58
  const ruleGroupContainerBase = {
44
59
  padding: theme.base / 2,
45
- marginTop: theme.base / 4,
60
+ border: `1px solid ${theme.colors.borderBaseSubdued}`,
46
61
  };
47
62
 
48
63
  const ruleGroupContainerBlueStyles = css({
@@ -74,12 +89,12 @@ export const getWfoStructuredSearchTableStyles = ({ theme, toSecondaryColor }: W
74
89
  const removeGroupActionStyles = css({
75
90
  backgroundColor: theme.colors.backgroundLightPrimary,
76
91
  width: '40px',
77
- marginTop: theme.base / 4,
78
92
  height: 'stretch',
79
93
  alignItems: 'center',
80
94
  justifyContent: 'center',
81
95
  borderBottomRightRadius: theme.border.radius.small,
82
96
  borderTopRightRadius: theme.border.radius.small,
97
+ border: `thin solid ${theme.colors.borderBasePlain}`,
83
98
  cursor: 'pointer',
84
99
  });
85
100
 
@@ -92,11 +107,12 @@ export const getWfoStructuredSearchTableStyles = ({ theme, toSecondaryColor }: W
92
107
  });
93
108
  const expandingRowBodyStyles = css({
94
109
  display: 'flex',
95
- gap: theme.base,
96
- fontWeight: theme.font.weight.bold,
97
- span: {
98
- fontWeight: 'bold',
99
- },
110
+ justifyContent: 'space-between',
111
+ // Size to the visible scroll viewport (minus the td padding) instead of
112
+ // the full table width, and keep it pinned there on horizontal scroll
113
+ position: 'sticky',
114
+ left: theme.base / 2,
115
+ width: `calc(100cqw - ${theme.base}px)`,
100
116
  });
101
117
 
102
118
  const addRulePlusStyles = css({
@@ -119,17 +135,49 @@ export const getWfoStructuredSearchTableStyles = ({ theme, toSecondaryColor }: W
119
135
  border: `thin dashed ${theme.colors.backgroundFilledPrimary}`,
120
136
  borderRadius: theme.border.radius.medium,
121
137
  alignItems: 'center',
122
- padding: theme.base,
138
+ paddingBlock: theme.base / 3,
139
+ paddingInline: theme.base,
123
140
  color: theme.colors.primary,
124
141
  justifyContent: 'center',
125
142
  flexDirection: 'row',
126
143
  cursor: 'pointer',
127
144
  });
128
145
 
146
+ const inlineCombinatorStyles = css({
147
+ alignSelf: 'start',
148
+ transform: `translateY(calc(-50% - ${theme.base / 8}px))`,
149
+ });
150
+
151
+ const ruleGroupBodyGridStyles = css({
152
+ display: 'grid',
153
+ gridTemplateColumns: 'auto 1fr',
154
+ columnGap: theme.base / 2,
155
+ rowGap: theme.base / 4,
156
+ alignItems: 'center',
157
+ '& > :first-child': {
158
+ gridColumn: '1 / -1',
159
+ },
160
+ '& > :first-child:not(:only-child)': {
161
+ gridColumn: '2 / -1',
162
+ },
163
+ });
164
+
165
+ const hideExpandedRowStyle = css({
166
+ display: 'none',
167
+ });
168
+ const dotStyles = css({
169
+ padding: theme.base / 4,
170
+ });
171
+
172
+ const expandingRowFieldStyles = css({
173
+ display: 'flex',
174
+ padding: theme.base / 4,
175
+ alignItems: 'center',
176
+ });
177
+
129
178
  return {
130
179
  toggleButtonStyles,
131
180
  queryBuilderContainerStyles,
132
- textAreaStyles,
133
181
  buttonGroupStyles,
134
182
  ruleContainerStyles,
135
183
  ruleGroupContainerBlueStyles,
@@ -142,5 +190,10 @@ export const getWfoStructuredSearchTableStyles = ({ theme, toSecondaryColor }: W
142
190
  addRulePlusStyles,
143
191
  addRuleContainerStyles,
144
192
  addGroupStyles,
193
+ inlineCombinatorStyles,
194
+ ruleGroupBodyGridStyles,
195
+ hideExpandedRowStyle,
196
+ expandingRowFieldStyles,
197
+ dotStyles,
145
198
  };
146
199
  };
@@ -0,0 +1,45 @@
1
+ import type { RuleGroupType } from 'react-querybuilder';
2
+ import { parseCEL } from 'react-querybuilder/parseCEL';
3
+
4
+ export const parseCelToRuleGroup = (celString: string): RuleGroupType | undefined => {
5
+ if (!celString) {
6
+ return undefined;
7
+ }
8
+ try {
9
+ const ruleGroup = parseCEL(celString);
10
+ return ruleGroup?.rules?.length > 0 ? ruleGroup : undefined;
11
+ } catch {
12
+ return undefined;
13
+ }
14
+ };
15
+
16
+ /**
17
+ * Builds a CEL filter that appends a single column condition (`field == "value"`) to the current
18
+ * filter and returns it together with its parsed rule group. The column name is resolved via
19
+ * getColumnSearchFieldName, falling back to the field key. Returns undefined when the input can't
20
+ * produce a valid filter (empty/quoted search text, or a filter that doesn't parse back to rules).
21
+ */
22
+ export const buildColumnFilter = <T>(
23
+ field: keyof T,
24
+ searchText: string,
25
+ currentFilter?: string,
26
+ getColumnSearchFieldName?: (field: keyof T) => string,
27
+ ): { filterString: string; ruleGroup: RuleGroupType } | undefined => {
28
+ // A double quote in the value would break the `== "..."` CEL literal and parseCEL has no escaping.
29
+ if (!searchText || searchText.includes('"')) {
30
+ return undefined;
31
+ }
32
+
33
+ const searchFieldName = getColumnSearchFieldName?.(field) ?? String(field);
34
+ const columnFilterCondition = `${searchFieldName} == "${searchText}"`;
35
+ const trimmedCurrentFilter = currentFilter?.trim();
36
+ const filterString =
37
+ trimmedCurrentFilter ? `(${trimmedCurrentFilter}) && ${columnFilterCondition}` : columnFilterCondition;
38
+
39
+ const ruleGroup = parseCelToRuleGroup(filterString);
40
+ if (!ruleGroup) {
41
+ return undefined;
42
+ }
43
+
44
+ return { filterString, ruleGroup };
45
+ };
@@ -82,6 +82,7 @@ export type WfoTableProps<T extends object> = {
82
82
  rowExpandingConfiguration?: {
83
83
  uniqueRowId: keyof WfoTableColumnConfig<T>;
84
84
  uniqueRowIdToExpandedRowMap: Record<string, ReactNode>;
85
+ shouldOnlyShowOnHover?: boolean;
85
86
  };
86
87
  pagination?: Pagination;
87
88
  overrideHeader?: (
@@ -27,8 +27,15 @@ export const WfoTableDataRows = <T extends object>({
27
27
  onRowClick,
28
28
  className,
29
29
  }: WfoTableDataRowsProps<T>) => {
30
- const { cellStyle, cellContentStyle, rowStyle, dataRowStyle, clickableStyle, setWidth } =
31
- useWithOrchestratorTheme(getWfoTableStyles);
30
+ const {
31
+ cellStyle,
32
+ cellContentStyle,
33
+ rowStyle,
34
+ dataRowStyle,
35
+ clickableStyle,
36
+ setWidth,
37
+ toggleExpandedRowOnHoverStyle,
38
+ } = useWithOrchestratorTheme(getWfoTableStyles);
32
39
 
33
40
  const sortedVisibleColumns = getSortedVisibleColumns(columnConfig, columnOrder, hiddenColumns);
34
41
 
@@ -37,8 +44,13 @@ export const WfoTableDataRows = <T extends object>({
37
44
  {data.map((row, index) => (
38
45
  <Fragment key={`table-data-row-${index}`}>
39
46
  <tr
40
- className={`${className} ${DATA_ROW_CLASS}`}
41
- css={[rowStyle, dataRowStyle, onRowClick && clickableStyle]}
47
+ className={className ? `${className} ${DATA_ROW_CLASS}` : DATA_ROW_CLASS}
48
+ css={[
49
+ rowStyle,
50
+ dataRowStyle,
51
+ onRowClick && clickableStyle,
52
+ rowExpandingConfiguration?.shouldOnlyShowOnHover && toggleExpandedRowOnHoverStyle,
53
+ ]}
42
54
  onClick={() => onRowClick?.(row)}
43
55
  >
44
56
  {sortedVisibleColumns.map(([key, columnConfig]) => {
@@ -31,6 +31,9 @@ export const getWfoTableStyles = ({ theme, isDarkModeActive }: WfoThemeHelpers)
31
31
 
32
32
  const tableContainerStyle = css({
33
33
  overflowX: 'auto',
34
+ // Query container so descendants (e.g. expanded rows) can size to the
35
+ // visible table width with cqw units instead of the full table width
36
+ containerType: 'inline-size',
34
37
  });
35
38
 
36
39
  const tableStyle = css({
@@ -157,6 +160,10 @@ export const getWfoTableStyles = ({ theme, isDarkModeActive }: WfoThemeHelpers)
157
160
  '.eui-xScroll': { display: 'flex', justifyContent: 'flex-start' },
158
161
  });
159
162
 
163
+ const toggleExpandedRowOnHoverStyle = css({
164
+ '&:hover + tr, &:focus-within + tr': { display: 'table-row' },
165
+ });
166
+
160
167
  return {
161
168
  tableContainerStyle,
162
169
  tableStyle,
@@ -174,5 +181,6 @@ export const getWfoTableStyles = ({ theme, isDarkModeActive }: WfoThemeHelpers)
174
181
  dragAndDropStyle,
175
182
  paginationStyle,
176
183
  setWidth,
184
+ toggleExpandedRowOnHoverStyle,
177
185
  };
178
186
  };