@orchestrator-ui/orchestrator-ui-components 4.1.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) 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 +21 -0
  5. package/dist/index.d.ts +59 -52
  6. package/dist/index.js +1383 -1186
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  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 +2 -5
  15. package/src/components/WfoSubscription/WfoInSyncField.tsx +17 -15
  16. package/src/components/WfoSubscription/WfoSubscription.tsx +6 -7
  17. package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActions.tsx +3 -3
  18. package/src/components/WfoSubscription/WfoSubscriptionDetailTree.tsx +64 -14
  19. package/src/components/WfoSubscription/WfoSubscriptionGeneral.tsx +0 -3
  20. package/src/components/WfoSubscription/WfoSubscriptionGeneralSections/WfoSubscriptionDetailSection.tsx +1 -8
  21. package/src/components/WfoSubscription/utils/utils.spec.ts +121 -0
  22. package/src/components/WfoSubscription/utils/utils.ts +42 -6
  23. package/src/components/WfoTable/WfoTable/WfoTableHeaderCell/WfoPopoverContent.tsx +53 -0
  24. package/src/components/WfoTable/WfoTable/WfoTableHeaderCell/WfoTableHeaderCell.tsx +7 -23
  25. package/src/configuration/version.ts +1 -1
  26. package/src/contexts/TreeContext.tsx +16 -0
  27. package/src/messages/en-GB.json +2 -0
  28. package/src/messages/nl-NL.json +2 -0
  29. package/src/rtk/endpoints/metadata/tasks.ts +9 -4
  30. package/src/rtk/endpoints/metadata/workflows.ts +10 -5
  31. package/src/rtk/endpoints/startOptions.ts +9 -3
  32. package/src/types/types.ts +5 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchestrator-ui/orchestrator-ui-components",
3
- "version": "4.1.1",
3
+ "version": "5.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Library of UI Components used to display the workflow orchestrator frontend",
6
6
  "author": {
@@ -5,15 +5,16 @@ import { SubscriptionStatus } from '../../../types';
5
5
  import { WfoBadge } from '../WfoBadge';
6
6
 
7
7
  export type WfoSubscriptionStatusBadgeProps = {
8
- status: SubscriptionStatus;
8
+ status?: SubscriptionStatus;
9
9
  };
10
10
 
11
11
  export const WfoSubscriptionStatusBadge: FC<
12
12
  WfoSubscriptionStatusBadgeProps
13
13
  > = ({ status }) => {
14
14
  const { theme, toSecondaryColor } = useOrchestratorTheme();
15
+ const lowerCaseStatus = status?.toLowerCase() || '';
15
16
 
16
- const getBadgeColorFromStatus = (status: string) => {
17
+ const getBadgeColorFromStatus = () => {
17
18
  const {
18
19
  primary,
19
20
  darkestShade,
@@ -23,7 +24,7 @@ export const WfoSubscriptionStatusBadge: FC<
23
24
  successText,
24
25
  } = theme.colors;
25
26
 
26
- switch (status.toLowerCase()) {
27
+ switch (lowerCaseStatus) {
27
28
  case SubscriptionStatus.ACTIVE:
28
29
  return {
29
30
  badgeColor: toSecondaryColor(success),
@@ -43,11 +44,11 @@ export const WfoSubscriptionStatusBadge: FC<
43
44
  }
44
45
  };
45
46
 
46
- const { badgeColor, textColor } = getBadgeColorFromStatus(status);
47
+ const { badgeColor, textColor } = getBadgeColorFromStatus();
47
48
 
48
49
  return (
49
50
  <WfoBadge textColor={textColor} color={badgeColor}>
50
- {status.toLowerCase()}
51
+ {lowerCaseStatus}
51
52
  </WfoBadge>
52
53
  );
53
54
  };
@@ -38,6 +38,7 @@ export const WfoWorkflowTargetBadge: FC<WfoWorkflowTargetBadgeProps> = ({
38
38
  textColor: primaryText,
39
39
  };
40
40
  case WorkflowTarget.SYSTEM:
41
+ case WorkflowTarget.VALIDATE:
41
42
  return {
42
43
  badgeColor: toSecondaryColor(warning),
43
44
  textColor: warningText,
@@ -0,0 +1,79 @@
1
+ import React, { FC, useEffect, useState } from 'react';
2
+
3
+ import {
4
+ EuiPopover,
5
+ EuiSelectable,
6
+ EuiSelectableOption,
7
+ EuiSpacer,
8
+ EuiText,
9
+ } from '@elastic/eui';
10
+
11
+ import { getWfoButtonComboBoxStyles } from '@/components/WfoButtonComboBox/styles';
12
+ import { useWithOrchestratorTheme } from '@/hooks';
13
+
14
+ export type WfoStartButtonComboBoxProps = {
15
+ options: EuiSelectableOption[];
16
+ onOptionChange: (selectedOption: EuiSelectableOption) => void;
17
+ title?: string;
18
+ children: (togglePopover: () => void) => React.ReactElement;
19
+ className?: string;
20
+ };
21
+
22
+ export const WfoButtonComboBox: FC<WfoStartButtonComboBoxProps> = ({
23
+ options,
24
+ onOptionChange,
25
+ title,
26
+ children,
27
+ className,
28
+ }) => {
29
+ const [isPopoverOpen, setPopoverOpen] = useState(false);
30
+ const [optionsState, setOptionsState] =
31
+ useState<EuiSelectableOption[]>(options);
32
+
33
+ const { selectableStyle, titleStyle } = useWithOrchestratorTheme(
34
+ getWfoButtonComboBoxStyles,
35
+ );
36
+
37
+ useEffect(() => {
38
+ if (!isPopoverOpen) {
39
+ setOptionsState(options);
40
+ }
41
+ }, [isPopoverOpen, options]);
42
+
43
+ return (
44
+ <EuiPopover
45
+ initialFocus={`.euiSelectable .euiFieldSearch`}
46
+ button={children(() => setPopoverOpen(!isPopoverOpen))}
47
+ isOpen={isPopoverOpen}
48
+ closePopover={() => setPopoverOpen(false)}
49
+ >
50
+ {title && (
51
+ <>
52
+ <EuiText size="s" css={titleStyle}>
53
+ {title}
54
+ </EuiText>
55
+ <EuiSpacer size="s"></EuiSpacer>
56
+ </>
57
+ )}
58
+ <EuiSelectable
59
+ className={className}
60
+ css={selectableStyle}
61
+ options={optionsState}
62
+ searchable
63
+ onChange={(options, _, changedOption) => {
64
+ onOptionChange(changedOption);
65
+ setOptionsState(options);
66
+ }}
67
+ height={200}
68
+ >
69
+ {(list, search) => (
70
+ <>
71
+ {search}
72
+ <EuiSpacer size="s" />
73
+ {list}
74
+ </>
75
+ )}
76
+ </EuiSelectable>
77
+ </EuiPopover>
78
+ );
79
+ };
@@ -0,0 +1 @@
1
+ export * from './WfoButtonComboBox';
@@ -0,0 +1,28 @@
1
+ import { css } from '@emotion/react';
2
+
3
+ import { WfoTheme } from '@/hooks';
4
+
5
+ export const getWfoButtonComboBoxStyles = ({ theme }: WfoTheme) => {
6
+ const selectableStyle = css({
7
+ '.euiFieldSearch': {
8
+ backgroundColor: theme.colors.body,
9
+ color: theme.colors.text,
10
+ '&:focus': {
11
+ backgroundColor: theme.colors.emptyShade,
12
+ },
13
+ },
14
+
15
+ '.euiSelectableList .euiSelectableListItem': {
16
+ borderColor: theme.colors.lightShade,
17
+ },
18
+ });
19
+
20
+ const titleStyle = css({
21
+ fontWeight: theme.font.weight.semiBold,
22
+ });
23
+
24
+ return {
25
+ selectableStyle,
26
+ titleStyle,
27
+ };
28
+ };
@@ -32,7 +32,7 @@ interface SubscriptionSummaryDisplayProps {
32
32
  const SubscriptionSummaryDisplay = ({
33
33
  subscriptionId,
34
34
  }: SubscriptionSummaryDisplayProps) => {
35
- const { data, isFetching } = useGetSubscriptionDetailQuery({
35
+ const { data } = useGetSubscriptionDetailQuery({
36
36
  subscriptionId,
37
37
  });
38
38
  const subscriptionDetail = data?.subscription;
@@ -42,10 +42,7 @@ const SubscriptionSummaryDisplay = ({
42
42
  }
43
43
 
44
44
  return (
45
- <WfoSubscriptionDetailSection
46
- subscriptionDetail={subscriptionDetail}
47
- isFetching={isFetching}
48
- />
45
+ <WfoSubscriptionDetailSection subscriptionDetail={subscriptionDetail} />
49
46
  );
50
47
  };
51
48
 
@@ -1,10 +1,13 @@
1
- import React, { useContext } from 'react';
1
+ import React, { useContext, useEffect, useState } from 'react';
2
2
 
3
3
  import { useTranslations } from 'next-intl';
4
4
  import Link from 'next/link';
5
5
 
6
6
  import { EuiButton } from '@elastic/eui';
7
7
 
8
+ import { WfoIsAllowedToRender } from '@/components';
9
+ import { WfoInsyncIcon } from '@/components';
10
+ import { PATH_TASKS, PATH_WORKFLOWS } from '@/components';
8
11
  import { PolicyResource } from '@/configuration/policy-resources';
9
12
  import { ConfirmationDialogContext } from '@/contexts';
10
13
  import { useOrchestratorTheme, useShowToastMessage } from '@/hooks';
@@ -12,37 +15,36 @@ import { useSetSubscriptionInSyncMutation } from '@/rtk/endpoints';
12
15
  import { SubscriptionDetail, ToastTypes } from '@/types';
13
16
  import { formatDate } from '@/utils';
14
17
 
15
- import { WfoIsAllowedToRender } from '../WfoAuth/WfoIsAllowedToRender';
16
- import { WfoInsyncIcon } from '../WfoInsyncIcon/WfoInsyncIcon';
17
- import { PATH_TASKS, PATH_WORKFLOWS } from '../WfoPageTemplate';
18
18
  import { getLastUncompletedProcess, getLatestTaskDate } from './utils';
19
19
 
20
20
  interface WfoInSyncFieldProps {
21
21
  subscriptionDetail: SubscriptionDetail;
22
- isFetching: boolean;
23
22
  }
24
23
 
25
- export const WfoInSyncField = ({
26
- subscriptionDetail,
27
- isFetching,
28
- }: WfoInSyncFieldProps) => {
24
+ export const WfoInSyncField = ({ subscriptionDetail }: WfoInSyncFieldProps) => {
29
25
  const t = useTranslations('subscriptions.detail');
30
26
  const { theme } = useOrchestratorTheme();
31
- const inSync = subscriptionDetail.insync;
27
+ const [inSync, setInSync] = useState<boolean>(subscriptionDetail.insync);
32
28
  const lastTaskRunDate = getLatestTaskDate(
33
- subscriptionDetail.processes.page,
29
+ subscriptionDetail.processes?.page,
34
30
  );
35
31
  const lastUncompletedProcess = getLastUncompletedProcess(
36
- subscriptionDetail.processes.page,
32
+ subscriptionDetail?.processes?.page,
37
33
  );
38
- const [setSubscriptionInSync] = useSetSubscriptionInSyncMutation();
34
+ const [setSubscriptionInSync, { isLoading }] =
35
+ useSetSubscriptionInSyncMutation();
39
36
  const { showToastMessage } = useShowToastMessage();
40
37
  const { showConfirmDialog } = useContext(ConfirmationDialogContext);
41
38
 
39
+ useEffect(() => {
40
+ setInSync(subscriptionDetail.insync);
41
+ }, [subscriptionDetail.insync]);
42
+
42
43
  const setInSyncAction = () => {
43
44
  setSubscriptionInSync(subscriptionDetail.subscriptionId)
44
45
  .unwrap()
45
46
  .then(() => {
47
+ setInSync(true);
46
48
  showToastMessage(
47
49
  ToastTypes.SUCCESS,
48
50
  t('setInSyncSuccess.text'),
@@ -88,8 +90,8 @@ export const WfoInSyncField = ({
88
90
  </Link>
89
91
  <WfoIsAllowedToRender resource={PolicyResource.PROCESS_RETRY}>
90
92
  <EuiButton
91
- isLoading={isFetching}
92
- isDisabled={isFetching}
93
+ isLoading={isLoading}
94
+ isDisabled={isLoading}
93
95
  color="danger"
94
96
  size="s"
95
97
  onClick={confirmSetInSync}
@@ -43,10 +43,9 @@ export const WfoSubscription = ({ subscriptionId }: WfoSubscriptionProps) => {
43
43
  );
44
44
  })();
45
45
 
46
- const { data, isLoading, isError, isFetching } =
47
- useGetSubscriptionDetailQuery({
48
- subscriptionId,
49
- });
46
+ const { data, isLoading, isError } = useGetSubscriptionDetailQuery({
47
+ subscriptionId,
48
+ });
50
49
 
51
50
  const onSelectedTabChanged = (tab: SubscriptionDetailTab) => {
52
51
  setActiveTab(tab);
@@ -100,14 +99,14 @@ export const WfoSubscription = ({ subscriptionId }: WfoSubscriptionProps) => {
100
99
  {selectedTab === SubscriptionDetailTab.GENERAL_TAB && (
101
100
  <WfoSubscriptionGeneral
102
101
  subscriptionDetail={subscriptionDetail}
103
- isFetching={isFetching}
104
102
  />
105
103
  )}
106
104
  {selectedTab === SubscriptionDetailTab.PROCESSES_TAB &&
107
- data && (
105
+ data &&
106
+ subscriptionDetail.processes?.page && (
108
107
  <WfoProcessesTimeline
109
108
  subscriptionDetailProcesses={
110
- subscriptionDetail.processes.page
109
+ subscriptionDetail.processes?.page
111
110
  }
112
111
  />
113
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>
@@ -15,7 +15,6 @@ import { toOptionalArrayEntry } from '@/utils';
15
15
 
16
16
  interface WfoSubscriptionGeneralProps {
17
17
  subscriptionDetail: SubscriptionDetail;
18
- isFetching: boolean;
19
18
  }
20
19
 
21
20
  export enum WfoSubscriptionGeneralSections {
@@ -27,7 +26,6 @@ export enum WfoSubscriptionGeneralSections {
27
26
 
28
27
  export const WfoSubscriptionGeneral = ({
29
28
  subscriptionDetail,
30
- isFetching,
31
29
  }: WfoSubscriptionGeneralProps) => {
32
30
  const { overrideSections } =
33
31
  useSubscriptionDetailGeneralSectionConfigurationOverride();
@@ -43,7 +41,6 @@ export const WfoSubscriptionGeneral = ({
43
41
  node: (
44
42
  <WfoSubscriptionDetailSection
45
43
  subscriptionDetail={subscriptionDetail}
46
- isFetching={isFetching}
47
44
  />
48
45
  ),
49
46
  },
@@ -13,12 +13,10 @@ import { SubscriptionDetail } from '@/types';
13
13
  import { formatDate } from '@/utils';
14
14
 
15
15
  interface WfoSubscriptionDetailSectionProps {
16
- isFetching: boolean;
17
16
  subscriptionDetail: SubscriptionDetail;
18
17
  }
19
18
 
20
19
  export const WfoSubscriptionDetailSection = ({
21
- isFetching,
22
20
  subscriptionDetail,
23
21
  }: WfoSubscriptionDetailSectionProps) => {
24
22
  const t = useTranslations('subscriptions.detail');
@@ -62,12 +60,7 @@ export const WfoSubscriptionDetailSection = ({
62
60
  },
63
61
  {
64
62
  key: t('insync'),
65
- value: (
66
- <WfoInSyncField
67
- subscriptionDetail={subscriptionDetail}
68
- isFetching={isFetching}
69
- />
70
- ),
63
+ value: <WfoInSyncField subscriptionDetail={subscriptionDetail} />,
71
64
  },
72
65
  {
73
66
  key: t('customer'),
@@ -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
+ });