@orchestrator-ui/orchestrator-ui-components 4.2.0 → 5.1.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 (44) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +6 -6
  4. package/CHANGELOG.md +26 -0
  5. package/dist/index.d.ts +2115 -336
  6. package/dist/index.js +2147 -1670
  7. package/dist/index.js.map +1 -1
  8. package/package.json +2 -2
  9. package/src/components/WfoBadges/WfoSubscriptionStatusBadge/WfoSubscriptionStatusBadge.tsx +6 -5
  10. package/src/components/WfoBadges/WfoWorkflowTargetBadge/WfoWorkflowTargetBadge.tsx +1 -0
  11. package/src/components/WfoButtonComboBox/WfoButtonComboBox.tsx +79 -0
  12. package/src/components/WfoButtonComboBox/index.ts +1 -0
  13. package/src/components/WfoButtonComboBox/styles.ts +28 -0
  14. package/src/components/WfoForms/formFields/SubscriptionSummaryField.tsx +1 -1
  15. package/src/components/WfoForms/formFields/commonStyles.ts +2 -0
  16. package/src/components/WfoForms/formFields/index.ts +1 -0
  17. package/src/components/WfoPydanticForm/Footer.tsx +75 -34
  18. package/src/components/WfoPydanticForm/Row.tsx +2 -2
  19. package/src/components/WfoPydanticForm/WfoPydanticForm.tsx +99 -8
  20. package/src/components/WfoPydanticForm/fields/Checkbox.tsx +22 -0
  21. package/src/components/WfoPydanticForm/fields/Divider.tsx +17 -0
  22. package/src/components/WfoPydanticForm/fields/Label.tsx +23 -0
  23. package/src/components/WfoPydanticForm/fields/Summary.tsx +125 -0
  24. package/src/components/WfoPydanticForm/fields/Text.tsx +28 -0
  25. package/src/components/WfoPydanticForm/fields/index.ts +5 -0
  26. package/src/components/WfoSubscription/WfoInSyncField.tsx +2 -2
  27. package/src/components/WfoSubscription/WfoSubscription.tsx +3 -2
  28. package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +3 -3
  29. package/src/components/WfoSubscription/WfoSubscriptionDetailTree.tsx +64 -14
  30. package/src/components/WfoSubscription/utils/utils.spec.ts +121 -0
  31. package/src/components/WfoSubscription/utils/utils.ts +42 -6
  32. package/src/configuration/version.ts +1 -1
  33. package/src/contexts/TreeContext.tsx +16 -0
  34. package/src/icons/WfoExclamationTriangle.tsx +29 -0
  35. package/src/icons/index.ts +1 -0
  36. package/src/messages/en-GB.json +2 -0
  37. package/src/messages/nl-NL.json +2 -0
  38. package/src/pages/metadata/WfoTasksPage.tsx +11 -26
  39. package/src/pages/metadata/WfoWorkflowsPage.tsx +11 -12
  40. package/src/rtk/endpoints/index.ts +1 -0
  41. package/src/rtk/endpoints/metadata/tasks.ts +9 -4
  42. package/src/rtk/endpoints/metadata/workflows.ts +10 -5
  43. package/src/rtk/endpoints/startOptions.ts +9 -3
  44. package/src/types/types.ts +7 -3
@@ -0,0 +1,125 @@
1
+ import React from 'react';
2
+
3
+ import type { PydanticFormElement } from 'pydantic-forms';
4
+
5
+ import { EuiFlexItem, EuiFormRow, EuiText } from '@elastic/eui';
6
+ import { tint } from '@elastic/eui';
7
+ import { css } from '@emotion/react';
8
+ import type { WfoTheme } from '@orchestrator-ui/orchestrator-ui-components';
9
+
10
+ import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles';
11
+ import { useWithOrchestratorTheme } from '@/hooks';
12
+
13
+ export const getStyles = ({ theme }: WfoTheme) => {
14
+ const toShadeColor = (color: string) => tint(color, 0.9);
15
+
16
+ const summaryFieldStyle = css({
17
+ 'div.emailMessage': {
18
+ td: {
19
+ color: theme.colors.text,
20
+ },
21
+ p: {
22
+ color: theme.colors.text,
23
+ },
24
+ html: {
25
+ marginLeft: '-10px',
26
+ },
27
+ },
28
+ 'section.table-summary': {
29
+ marginTop: '20px',
30
+ width: '100%',
31
+ td: {
32
+ padding: '10px',
33
+ verticalAlign: 'top',
34
+ },
35
+ 'td:not(:first-child):not(:last-child)': {
36
+ borderRight: `1px solid ${theme.colors.lightestShade}`,
37
+ },
38
+ '.label': {
39
+ fontWeight: 'bold',
40
+ color: theme.colors.lightestShade,
41
+ backgroundColor: theme.colors.primary,
42
+ borderRight: `2px solid ${theme.colors.lightestShade}`,
43
+ borderBottom: `1px solid ${theme.colors.lightestShade}`,
44
+ },
45
+ '.value': {
46
+ backgroundColor: toShadeColor(theme.colors.primary),
47
+ borderBottom: `1px solid ${theme.colors.lightestShade}`,
48
+ },
49
+ },
50
+ });
51
+ return {
52
+ summaryFieldStyle: summaryFieldStyle,
53
+ };
54
+ };
55
+
56
+ export const Summary: PydanticFormElement = ({ pydanticFormField }) => {
57
+ const { summaryFieldStyle } = useWithOrchestratorTheme(getStyles);
58
+ const { formRowStyle } = useWithOrchestratorTheme(getCommonFormFieldStyles);
59
+
60
+ const { id, title, description } = pydanticFormField;
61
+ const uniforms = pydanticFormField.schema.uniforms;
62
+ const summaryData = uniforms?.data as unknown as {
63
+ headers: string[][];
64
+ labels: string[];
65
+ columns: string[][];
66
+ };
67
+
68
+ const headers = summaryData?.headers as string[][];
69
+ const labels = summaryData?.labels as string[];
70
+ const columns = summaryData?.columns || [];
71
+
72
+ const extraColumnsData = columns.filter((_, index) => index !== 0);
73
+
74
+ const rows = columns[0].map((row, index) => (
75
+ <tr key={index}>
76
+ {labels && <td className={`label`}>{labels[index]}</td>}
77
+ <td className={`value`}>
78
+ {typeof row === 'string' && row.includes('<!doctype html>') ? (
79
+ <div
80
+ className="emailMessage"
81
+ dangerouslySetInnerHTML={{ __html: row }}
82
+ ></div>
83
+ ) : (
84
+ row
85
+ )}
86
+ </td>
87
+ {extraColumnsData &&
88
+ extraColumnsData.map((_, idx) => (
89
+ <td className={`value`} key={idx}>
90
+ {extraColumnsData[idx][index]}
91
+ </td>
92
+ ))}
93
+ </tr>
94
+ ));
95
+
96
+ const tableHeader =
97
+ !headers || headers.length === 0 ? null : (
98
+ <tr>
99
+ {labels && <th />}
100
+ {headers.map((header, idx) => (
101
+ <th key={idx}>{header}</th>
102
+ ))}
103
+ </tr>
104
+ );
105
+
106
+ return (
107
+ <EuiFlexItem css={[summaryFieldStyle, formRowStyle]}>
108
+ <section>
109
+ <EuiFormRow
110
+ label={description}
111
+ labelAppend={<EuiText size="m">{title}</EuiText>}
112
+ id={id}
113
+ fullWidth
114
+ >
115
+ <section className="table-summary">
116
+ <table id={`${id}-table`}>
117
+ <thead>{tableHeader}</thead>
118
+ <tbody>{rows}</tbody>
119
+ </table>
120
+ </section>
121
+ </EuiFormRow>
122
+ </section>
123
+ </EuiFlexItem>
124
+ );
125
+ };
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+
3
+ import type { PydanticFormControlledElement } from 'pydantic-forms';
4
+
5
+ import { EuiFieldText } from '@elastic/eui';
6
+
7
+ import { useWithOrchestratorTheme } from '@/hooks';
8
+ import { getFormFieldsBaseStyle } from '@/theme';
9
+
10
+ export const Text: PydanticFormControlledElement = ({
11
+ onChange,
12
+ value,
13
+ disabled,
14
+ }) => {
15
+ const { formFieldBaseStyle } = useWithOrchestratorTheme(
16
+ getFormFieldsBaseStyle,
17
+ );
18
+
19
+ return (
20
+ <EuiFieldText
21
+ css={formFieldBaseStyle}
22
+ disabled={disabled}
23
+ onChange={(event) => onChange(event.target.value)}
24
+ value={value ?? ''}
25
+ fullWidth
26
+ />
27
+ );
28
+ };
@@ -1 +1,6 @@
1
1
  export * from './TextArea';
2
+ export * from './Text';
3
+ export * from './Label';
4
+ export * from './Divider';
5
+ export * from './Checkbox';
6
+ export * from './Summary';
@@ -26,10 +26,10 @@ export const WfoInSyncField = ({ subscriptionDetail }: WfoInSyncFieldProps) => {
26
26
  const { theme } = useOrchestratorTheme();
27
27
  const [inSync, setInSync] = useState<boolean>(subscriptionDetail.insync);
28
28
  const lastTaskRunDate = getLatestTaskDate(
29
- subscriptionDetail.processes.page,
29
+ subscriptionDetail.processes?.page,
30
30
  );
31
31
  const lastUncompletedProcess = getLastUncompletedProcess(
32
- subscriptionDetail.processes.page,
32
+ subscriptionDetail?.processes?.page,
33
33
  );
34
34
  const [setSubscriptionInSync, { isLoading }] =
35
35
  useSetSubscriptionInSyncMutation();
@@ -102,10 +102,11 @@ export const WfoSubscription = ({ subscriptionId }: WfoSubscriptionProps) => {
102
102
  />
103
103
  )}
104
104
  {selectedTab === SubscriptionDetailTab.PROCESSES_TAB &&
105
- data && (
105
+ data &&
106
+ subscriptionDetail.processes?.page && (
106
107
  <WfoProcessesTimeline
107
108
  subscriptionDetailProcesses={
108
- subscriptionDetail.processes.page
109
+ subscriptionDetail.processes?.page
109
110
  }
110
111
  />
111
112
  )}
@@ -238,16 +238,16 @@ export const WfoSubscriptionActions: FC<WfoSubscriptionActionsProps> = ({
238
238
  PolicyResource.SUBSCRIPTION_VALIDATE +
239
239
  subscriptionId,
240
240
  ) &&
241
- subscriptionActions.system && (
241
+ subscriptionActions.validate && (
242
242
  <>
243
243
  <MenuBlock title={t('tasks')}></MenuBlock>
244
- {subscriptionActions.system.map(
244
+ {subscriptionActions.validate.map(
245
245
  (action, index) => (
246
246
  <MenuItem
247
247
  key={`s_${index}`}
248
248
  action={action}
249
249
  index={index}
250
- target={WorkflowTarget.SYSTEM}
250
+ target={WorkflowTarget.VALIDATE}
251
251
  isTask={true}
252
252
  />
253
253
  ),
@@ -2,9 +2,22 @@ import React, { useState } from 'react';
2
2
 
3
3
  import { useTranslations } from 'next-intl';
4
4
 
5
- import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
5
+ import {
6
+ EuiCallOut,
7
+ EuiFlexGroup,
8
+ EuiFlexItem,
9
+ EuiSelectableOption,
10
+ EuiText,
11
+ } from '@elastic/eui';
12
+ import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option';
6
13
 
7
- import { PATH_SUBSCRIPTIONS, WfoLoading, WfoTextAnchor } from '@/components';
14
+ import {
15
+ PATH_SUBSCRIPTIONS,
16
+ WfoLoading,
17
+ WfoTextAnchor,
18
+ mapProductBlockInstancesToEuiSelectableOptions,
19
+ } from '@/components';
20
+ import { WfoButtonComboBox } from '@/components/WfoButtonComboBox';
8
21
  import { TreeContext, TreeContextType } from '@/contexts';
9
22
  import { useOrchestratorTheme, useWithOrchestratorTheme } from '@/hooks';
10
23
  import {
@@ -21,6 +34,8 @@ import { WfoSubscriptionProductBlock } from './WfoSubscriptionProductBlock';
21
34
  import { getSubscriptionDetailStyles } from './styles';
22
35
  import { getProductBlockTitle } from './utils';
23
36
 
37
+ const EUI_OPTION_CHECKED_STATE_ON: EuiSelectableOptionCheckedType = 'on';
38
+
24
39
  interface WfoSubscriptionDetailTreeProps {
25
40
  productBlockInstances: ProductBlockInstance[];
26
41
  subscriptionId: Subscription['subscriptionId'];
@@ -38,8 +53,14 @@ export const WfoSubscriptionDetailTree = ({
38
53
  const { productBlockTreeWidth } = useWithOrchestratorTheme(
39
54
  getSubscriptionDetailStyles,
40
55
  );
41
- const { selectedIds, expandAll, collapseAll, resetSelection, selectAll } =
42
- React.useContext(TreeContext) as TreeContextType;
56
+ const {
57
+ selectedIds,
58
+ expandAll,
59
+ resetSelection,
60
+ selectAll,
61
+ selectIds,
62
+ deselectIds,
63
+ } = React.useContext(TreeContext) as TreeContextType;
43
64
 
44
65
  let tree: TreeBlock | null = null;
45
66
  const depthList: number[] = [];
@@ -99,7 +120,6 @@ export const WfoSubscriptionDetailTree = ({
99
120
  const toggleShowAll = () => {
100
121
  if (selectedIds.length === productBlockInstances.length) {
101
122
  resetSelection();
102
- collapseAll();
103
123
  } else {
104
124
  selectAll();
105
125
  expandAll();
@@ -130,6 +150,18 @@ export const WfoSubscriptionDetailTree = ({
130
150
 
131
151
  const headerHeight = 265; // The height of the header part of the page that needs to be subtracted from 100vh to fit the page
132
152
 
153
+ const handleOptionChange = (changedOption: EuiSelectableOption) => {
154
+ if (changedOption.data?.ids === undefined) {
155
+ return;
156
+ }
157
+
158
+ const { checked, data } = changedOption;
159
+ const shouldAddIds = checked === EUI_OPTION_CHECKED_STATE_ON;
160
+
161
+ expandAll();
162
+ return shouldAddIds ? selectIds(data.ids) : deselectIds(data.ids);
163
+ };
164
+
133
165
  return (
134
166
  <EuiFlexGroup
135
167
  css={{
@@ -158,15 +190,33 @@ export const WfoSubscriptionDetailTree = ({
158
190
  </EuiText>
159
191
  </EuiFlexItem>
160
192
  <EuiFlexItem grow={false}>
161
- <WfoTextAnchor
162
- text={t(
163
- selectedIds.length ===
164
- productBlockInstances.length
165
- ? 'hideAll'
166
- : 'showAll',
167
- )}
168
- onClick={toggleShowAll}
169
- />
193
+ <EuiFlexGroup>
194
+ <WfoTextAnchor
195
+ text={t(
196
+ selectedIds.length ===
197
+ productBlockInstances.length
198
+ ? 'hideAll'
199
+ : 'showAll',
200
+ )}
201
+ onClick={toggleShowAll}
202
+ />
203
+ <WfoButtonComboBox
204
+ options={mapProductBlockInstancesToEuiSelectableOptions(
205
+ productBlockInstances,
206
+ )}
207
+ onOptionChange={handleOptionChange}
208
+ title={t('selectByNameTitle')}
209
+ >
210
+ {(togglePopover) => (
211
+ <WfoTextAnchor
212
+ text={t(
213
+ 'selectByNameButtonText',
214
+ )}
215
+ onClick={togglePopover}
216
+ />
217
+ )}
218
+ </WfoButtonComboBox>
219
+ </EuiFlexGroup>
170
220
  </EuiFlexItem>
171
221
  </EuiFlexGroup>
172
222
  </EuiFlexItem>
@@ -3,6 +3,7 @@ import { EuiThemeComputed } from '@elastic/eui';
3
3
  import {
4
4
  FieldValue,
5
5
  ProcessStatus,
6
+ ProductBlockInstance,
6
7
  SubscriptionAction,
7
8
  SubscriptionDetailProcess,
8
9
  WorkflowTarget,
@@ -15,6 +16,7 @@ import {
15
16
  getProductBlockTitle,
16
17
  getWorkflowTargetColor,
17
18
  getWorkflowTargetIconContent,
19
+ mapProductBlockInstancesToEuiSelectableOptions,
18
20
  } from './utils';
19
21
 
20
22
  describe('getFieldFromProductBlockInstanceValues()', () => {
@@ -347,3 +349,122 @@ describe('getLatestTaskDate', () => {
347
349
  expect(lastTaskDate).toBe('2021-04-01T00:00:00Z');
348
350
  });
349
351
  });
352
+
353
+ describe('mapProductBlockInstancesToEuiSelectableOptions', () => {
354
+ const baseProductBlockInstance: Omit<
355
+ ProductBlockInstance,
356
+ 'id' | 'productBlockInstanceValues'
357
+ > = {
358
+ subscriptionInstanceId: '',
359
+ parent: null,
360
+ inUseByRelations: [],
361
+ subscription: {
362
+ subscriptionId: '',
363
+ description: '',
364
+ },
365
+ };
366
+
367
+ it('maps product block instances to EuiSelectableOptions', () => {
368
+ // Given
369
+ const productBlockInstances: ProductBlockInstance[] = [
370
+ {
371
+ ...baseProductBlockInstance,
372
+ id: 0,
373
+ productBlockInstanceValues: [
374
+ {
375
+ field: 'name',
376
+ value: 'test1',
377
+ },
378
+ ],
379
+ },
380
+ {
381
+ ...baseProductBlockInstance,
382
+ id: 1,
383
+ productBlockInstanceValues: [
384
+ {
385
+ field: 'name',
386
+ value: 'test2',
387
+ },
388
+ ],
389
+ },
390
+ ];
391
+
392
+ // When
393
+ const result = mapProductBlockInstancesToEuiSelectableOptions(
394
+ productBlockInstances,
395
+ );
396
+
397
+ // Then
398
+ expect(result).toEqual([
399
+ {
400
+ label: 'test1',
401
+ data: {
402
+ ids: [0],
403
+ },
404
+ },
405
+ {
406
+ label: 'test2',
407
+ data: {
408
+ ids: [1],
409
+ },
410
+ },
411
+ ]);
412
+ });
413
+
414
+ it('maps product block instances to EuiSelectableOptions and groups ids by name', () => {
415
+ // Given
416
+ const productBlockInstances: ProductBlockInstance[] = [
417
+ {
418
+ ...baseProductBlockInstance,
419
+ id: 0,
420
+ productBlockInstanceValues: [
421
+ {
422
+ field: 'name',
423
+ value: 'test1',
424
+ },
425
+ ],
426
+ },
427
+ {
428
+ ...baseProductBlockInstance,
429
+ id: 1,
430
+ productBlockInstanceValues: [
431
+ {
432
+ field: 'name',
433
+ value: 'test2',
434
+ },
435
+ ],
436
+ },
437
+ {
438
+ ...baseProductBlockInstance,
439
+ id: 2,
440
+ productBlockInstanceValues: [
441
+ {
442
+ field: 'name',
443
+ value: 'test1',
444
+ },
445
+ ],
446
+ },
447
+ ];
448
+
449
+ // When
450
+ const result = mapProductBlockInstancesToEuiSelectableOptions(
451
+ productBlockInstances,
452
+ );
453
+
454
+ // Then
455
+ expect(result).toEqual([
456
+ {
457
+ label: 'test1',
458
+ data: {
459
+ ids: [0, 2],
460
+ },
461
+ },
462
+ {
463
+ label: 'test2',
464
+ data: {
465
+ ids: [1],
466
+ },
467
+ },
468
+ ]);
469
+ });
470
+ });
@@ -1,16 +1,20 @@
1
1
  import { TranslationValues } from 'next-intl';
2
2
 
3
- import { EuiThemeComputed } from '@elastic/eui';
3
+ import { EuiSelectableOption, EuiThemeComputed } from '@elastic/eui';
4
4
 
5
5
  import {
6
6
  FieldValue,
7
7
  ProcessStatus,
8
+ ProductBlockDefinition,
9
+ ProductBlockInstance,
8
10
  SortOrder,
9
11
  SubscriptionAction,
10
12
  SubscriptionDetailProcess,
11
13
  WorkflowTarget,
12
14
  } from '@/types';
13
15
 
16
+ const PRODUCT_BLOCK_NAME_FIELD: keyof ProductBlockDefinition = 'name';
17
+
14
18
  export enum SubscriptionDetailTab {
15
19
  GENERAL_TAB = 'general',
16
20
  SERVICE_CONFIGURATION_TAB = 'service-configuration',
@@ -68,6 +72,7 @@ export const getWorkflowTargetColor = (
68
72
  case WorkflowTarget.MODIFY:
69
73
  return theme.colors.primaryText;
70
74
  case WorkflowTarget.SYSTEM:
75
+ case WorkflowTarget.VALIDATE:
71
76
  return theme.colors.warning;
72
77
  case WorkflowTarget.TERMINATE:
73
78
  return theme.colors.danger;
@@ -84,6 +89,7 @@ export const getWorkflowTargetIconContent = (
84
89
  case WorkflowTarget.CREATE:
85
90
  return 'C';
86
91
  case WorkflowTarget.SYSTEM:
92
+ case WorkflowTarget.VALIDATE:
87
93
  return 'T';
88
94
  case WorkflowTarget.TERMINATE:
89
95
  return 'X';
@@ -93,9 +99,9 @@ export const getWorkflowTargetIconContent = (
93
99
  };
94
100
 
95
101
  export const getLastUncompletedProcess = (
96
- processes: SubscriptionDetailProcess[],
102
+ processes?: SubscriptionDetailProcess[],
97
103
  ): SubscriptionDetailProcess | undefined => {
98
- if (processes.length === 0) {
104
+ if (!processes || processes.length === 0) {
99
105
  return;
100
106
  }
101
107
 
@@ -107,13 +113,13 @@ export const getLastUncompletedProcess = (
107
113
  return dateB.getTime() - dateA.getTime();
108
114
  });
109
115
 
110
- return uncompletedProcesses.length > 0
116
+ return (uncompletedProcesses && uncompletedProcesses.length) > 0
111
117
  ? uncompletedProcesses[0]
112
118
  : undefined;
113
119
  };
114
120
 
115
- export const getLatestTaskDate = (processes: SubscriptionDetailProcess[]) => {
116
- if (processes.length === 0) {
121
+ export const getLatestTaskDate = (processes?: SubscriptionDetailProcess[]) => {
122
+ if (!processes || processes.length === 0) {
117
123
  return '';
118
124
  }
119
125
 
@@ -143,3 +149,33 @@ export const sortProcessesByDate = (
143
149
  }
144
150
  });
145
151
  };
152
+
153
+ export const mapProductBlockInstancesToEuiSelectableOptions = (
154
+ productBlockInstances: ProductBlockInstance[],
155
+ ): EuiSelectableOption[] => {
156
+ const items2Map = productBlockInstances.reduce((acc, curr) => {
157
+ const name = getFieldFromProductBlockInstanceValues(
158
+ curr.productBlockInstanceValues,
159
+ PRODUCT_BLOCK_NAME_FIELD,
160
+ ).toString();
161
+
162
+ if (!name) {
163
+ console.error('Name field is missing', curr);
164
+ }
165
+
166
+ if (acc.has(name)) {
167
+ acc.get(name)?.push(curr.id);
168
+ } else {
169
+ acc.set(name, [curr.id]);
170
+ }
171
+
172
+ return acc;
173
+ }, new Map<string, number[]>());
174
+
175
+ return Array.from(items2Map).map(([label, ids]) => ({
176
+ label,
177
+ data: {
178
+ ids,
179
+ },
180
+ }));
181
+ };
@@ -1 +1 @@
1
- export const ORCHESTRATOR_UI_LIBRARY_VERSION = '4.2.0';
1
+ export const ORCHESTRATOR_UI_LIBRARY_VERSION = '5.1.0';
@@ -12,6 +12,8 @@ export type TreeContextType = {
12
12
  collapseAll: () => void;
13
13
  resetSelection: () => void;
14
14
  selectAll: () => void;
15
+ selectIds: (ids: number[]) => void;
16
+ deselectIds: (ids: number[]) => void;
15
17
  };
16
18
 
17
19
  export const TreeContext = React.createContext<TreeContextType | null>(null);
@@ -39,6 +41,18 @@ export const TreeProvider: React.FC<TreeProviderProps> = ({ children }) => {
39
41
  setSelectedIds(Array.from(Array(depths.length).keys()));
40
42
  };
41
43
 
44
+ const selectIds = (ids: number[]) => {
45
+ setSelectedIds((prevSelectedIds) =>
46
+ Array.from(new Set([...prevSelectedIds, ...ids])),
47
+ );
48
+ };
49
+
50
+ const deselectIds = (ids: number[]) => {
51
+ setSelectedIds((prevSelectedIds) =>
52
+ prevSelectedIds.filter((id) => !ids.includes(id)),
53
+ );
54
+ };
55
+
42
56
  const expandAll = () => {
43
57
  setExpandedIds(Array.from(Array(depths.length).keys()));
44
58
  };
@@ -96,6 +110,8 @@ export const TreeProvider: React.FC<TreeProviderProps> = ({ children }) => {
96
110
  collapseAll,
97
111
  resetSelection,
98
112
  selectAll,
113
+ selectIds,
114
+ deselectIds,
99
115
  }}
100
116
  >
101
117
  {children}
@@ -0,0 +1,29 @@
1
+ import React, { FC } from 'react';
2
+
3
+ import { WfoIconProps } from './WfoIconProps';
4
+
5
+ export const WfoExclamationTriangle: FC<WfoIconProps> = ({
6
+ width = 24,
7
+ height = 24,
8
+ color = '#000000',
9
+ }) => (
10
+ <svg
11
+ width={width}
12
+ height={height}
13
+ viewBox="0 0 24 24"
14
+ version="1.1"
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ color={color}
17
+ >
18
+ <title>icon/exclamation-circle</title>
19
+ <g id="Symbols" strokeWidth="1.5" fill="currentColor">
20
+ <g id="icon/exclamation-triangle" fill="currentColor">
21
+ <path
22
+ fillRule="evenodd"
23
+ d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
24
+ clipRule="evenodd"
25
+ />
26
+ </g>
27
+ </g>
28
+ </svg>
29
+ );
@@ -39,3 +39,4 @@ export * from './WfoActualWork';
39
39
  export * from './WfoMalfunction';
40
40
  export * from './WfoPlannedWork';
41
41
  export * from './WfoCubeFill';
42
+ export * from './WfoExclamationTriangle';
@@ -363,6 +363,8 @@
363
363
  },
364
364
  "showAll": "Show all",
365
365
  "hideAll": "Hide all",
366
+ "selectByNameTitle": "Select product blocks by name",
367
+ "selectByNameButtonText": "Select by name",
366
368
  "see": "See",
367
369
  "setInSync": "Set in Sync",
368
370
  "setInSyncQuestion": "Are you sure you want to do this? You're about to force a subscription in sync. When it's clear why the subscription is out of sync this could enable you to start or finish a change on this subscription. When you're in doubt, please consult the network automators first as running workflows on subscriptions that are not in sync can potentially do great harm to the network.",
@@ -360,6 +360,8 @@
360
360
  },
361
361
  "showAll": "Toon alles",
362
362
  "hideAll": "Verberg alles",
363
+ "selectByNameTitle": "Selecteer product blocks op naam",
364
+ "selectByNameButtonText": "Selecteer op naam",
363
365
  "see": "Bekijk",
364
366
  "setInSync": "Set in Sync",
365
367
  "setInSyncQuestion": "Weet je zeker dat je de subscription in-sync wilt zetten? Je gaat een subscription geforceerd in-sync zetten. Alleen als je zeker weet wat de reden is van out-of-sync kun je de actie uitvoeren, zodat je daarna wijzigingen kunt doorvoeren op deze subscription. Bij twijfel - check eerst bij de network automators of het in sync zetten van de subscription mogelijk schadelijke gevolgen kan hebben op het netwerk.",