@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchestrator-ui/orchestrator-ui-components",
3
- "version": "4.2.0",
3
+ "version": "5.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Library of UI Components used to display the workflow orchestrator frontend",
6
6
  "author": {
@@ -48,7 +48,7 @@
48
48
  "next-query-params": "^5.0.0",
49
49
  "object-hash": "^3.0.0",
50
50
  "prism-themes": "^1.9.0",
51
- "pydantic-forms": "^0.1.5",
51
+ "pydantic-forms": "^0.3.1",
52
52
  "react-diff-view": "^3.2.0",
53
53
  "react-draggable": "^4.4.6",
54
54
  "react-redux": "^9.1.2",
@@ -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
+ };
@@ -29,7 +29,7 @@ interface SubscriptionSummaryDisplayProps {
29
29
  subscriptionId: string;
30
30
  }
31
31
 
32
- const SubscriptionSummaryDisplay = ({
32
+ export const SubscriptionSummaryDisplay = ({
33
33
  subscriptionId,
34
34
  }: SubscriptionSummaryDisplayProps) => {
35
35
  const { data } = useGetSubscriptionDetailQuery({
@@ -4,6 +4,8 @@ import { WfoTheme } from '@/hooks';
4
4
 
5
5
  export const getCommonFormFieldStyles = ({ theme }: WfoTheme) => {
6
6
  const formRowStyle = css({
7
+ marginBottom: theme.base * 2,
8
+
7
9
  '.euiText': {
8
10
  color: theme.colors.text,
9
11
  },
@@ -25,3 +25,4 @@ export * from './SummaryField';
25
25
  export * from './CustomerField';
26
26
  export * from './ConnectedSelectField';
27
27
  export * from './deprecated/FileUploadField';
28
+ export * from './commonStyles';
@@ -11,13 +11,19 @@ import { usePydanticFormContext } from 'pydantic-forms';
11
11
  import { EuiButton, EuiHorizontalRule } from '@elastic/eui';
12
12
 
13
13
  import { useOrchestratorTheme } from '@/hooks';
14
- import { WfoPlayFill } from '@/icons';
15
14
 
16
15
  export const Footer = () => {
16
+ const {
17
+ rhf,
18
+ onCancel,
19
+ onPrevious,
20
+ allowUntouchedSubmit,
21
+ hasNext,
22
+ formInputData,
23
+ } = usePydanticFormContext();
24
+
17
25
  const { theme } = useOrchestratorTheme();
18
- const t = useTranslations();
19
- const { rhf, onCancel, allowUntouchedSubmit, isLoading } =
20
- usePydanticFormContext();
26
+ const t = useTranslations('pydanticForms.userInputForm');
21
27
 
22
28
  const isDisabled: boolean =
23
29
  !rhf.formState.isValid ||
@@ -25,42 +31,77 @@ export const Footer = () => {
25
31
  !rhf.formState.isDirty &&
26
32
  !rhf.formState.isSubmitting);
27
33
 
34
+ const handlePrevious = () => {
35
+ if (onCancel) {
36
+ onCancel();
37
+ }
38
+ };
39
+
40
+ const PreviousButton = () => (
41
+ <EuiButton
42
+ id="button-submit-form-submit"
43
+ tabIndex={0}
44
+ fill
45
+ onClick={() => {
46
+ if (onPrevious) {
47
+ onPrevious();
48
+ }
49
+ }}
50
+ color={'primary'}
51
+ iconSide="right"
52
+ aria-label={t('previous')}
53
+ disabled={isDisabled}
54
+ >
55
+ {t('previous')}
56
+ </EuiButton>
57
+ );
58
+
59
+ const CancelButton = () => (
60
+ <div
61
+ onClick={handlePrevious}
62
+ css={{
63
+ cursor: 'pointer',
64
+ color: theme.colors.link,
65
+ fontWeight: theme.font.weight.bold,
66
+ marginLeft: theme.base / 2,
67
+ display: 'flex',
68
+ alignItems: 'center',
69
+ }}
70
+ >
71
+ {t('cancel')}
72
+ </div>
73
+ );
74
+
75
+ const SubmitButton = () => {
76
+ const submitButtonLabel = hasNext ? t('next') : t('startWorkflow');
77
+
78
+ return (
79
+ <EuiButton
80
+ id="button-submit-form-submit"
81
+ tabIndex={0}
82
+ fill
83
+ color={'primary'}
84
+ type="submit"
85
+ iconSide="right"
86
+ aria-label={submitButtonLabel}
87
+ disabled={isDisabled}
88
+ >
89
+ {submitButtonLabel}
90
+ </EuiButton>
91
+ );
92
+ };
93
+
28
94
  return (
29
95
  <div>
30
96
  <EuiHorizontalRule />
31
97
  <div style={{ display: 'flex', justifyContent: 'space-between' }}>
32
- <div
33
- onClick={() => {
34
- if (onCancel) {
35
- onCancel();
36
- }
37
- }}
38
- css={{
39
- cursor: 'pointer',
40
- color: theme.colors.link,
41
- fontWeight: theme.font.weight.bold,
42
- marginLeft: theme.base / 2,
43
- display: 'flex',
44
- alignItems: 'center',
45
- }}
46
- >
47
- {t('cancel')}
98
+ <div>
99
+ {(formInputData && formInputData.length > 0 && (
100
+ <PreviousButton />
101
+ )) || <CancelButton />}
48
102
  </div>
49
103
 
50
- <EuiButton
51
- id="button-submit-form-submit"
52
- tabIndex={0}
53
- fill
54
- color={'primary'}
55
- isLoading={isLoading}
56
- type="submit"
57
- iconType={() => <WfoPlayFill color="#FFF" />}
58
- iconSide="right"
59
- aria-label={t('startWorkflow')}
60
- disabled={isDisabled}
61
- >
62
- {t('startWorkflow')}
63
- </EuiButton>
104
+ <SubmitButton />
64
105
  </div>
65
106
  </div>
66
107
  );
@@ -1,13 +1,13 @@
1
1
  import React from 'react';
2
2
 
3
- import type { RowRenderer } from 'pydantic-forms';
3
+ import type { RowRenderComponent } from 'pydantic-forms';
4
4
 
5
5
  import { EuiFormRow, EuiText } from '@elastic/eui';
6
6
 
7
7
  import { getCommonFormFieldStyles } from '@/components/WfoForms/formFields/commonStyles';
8
8
  import { useWithOrchestratorTheme } from '@/hooks';
9
9
 
10
- export const Row: RowRenderer = ({
10
+ export const Row: RowRenderComponent = ({
11
11
  title,
12
12
  description,
13
13
  error,
@@ -2,17 +2,18 @@ import React from 'react';
2
2
 
3
3
  import { AbstractIntlMessages, useMessages, useTranslations } from 'next-intl';
4
4
  import { useRouter } from 'next/router';
5
- import {
6
- PydanticForm,
7
- PydanticFormFieldFormat,
8
- PydanticFormFieldType,
9
- } from 'pydantic-forms';
10
5
  import type {
11
6
  ComponentMatcher,
12
7
  PydanticComponentMatcher,
13
8
  PydanticFormApiProvider,
14
9
  PydanticFormLabelProvider,
15
10
  } from 'pydantic-forms';
11
+ import {
12
+ PydanticForm,
13
+ PydanticFormFieldFormat,
14
+ PydanticFormFieldType,
15
+ zodValidationPresets,
16
+ } from 'pydantic-forms';
16
17
 
17
18
  import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
18
19
 
@@ -21,10 +22,11 @@ import { StartWorkflowPayload } from '@/pages/processes/WfoStartProcessPage';
21
22
  import { HttpStatus } from '@/rtk';
22
23
  import { useStartProcessMutation } from '@/rtk/endpoints/forms';
23
24
  import { useAppSelector } from '@/rtk/hooks';
25
+ import { FormValidationError } from '@/types';
24
26
 
25
27
  import { Footer } from './Footer';
26
28
  import { Row } from './Row';
27
- import { TextArea } from './fields/TextArea';
29
+ import { Checkbox, Divider, Label, Summary, Text, TextArea } from './fields';
28
30
 
29
31
  interface WfoPydanticFormProps {
30
32
  processName: string;
@@ -54,6 +56,11 @@ export const WfoPydanticForm = ({
54
56
  typeof translationMessages?.pydanticForms !== 'string'
55
57
  ? translationMessages.pydanticForms.backendTranslations
56
58
  : {};
59
+ const widgetsTranslations =
60
+ translationMessages?.pydanticForms &&
61
+ typeof translationMessages?.pydanticForms !== 'string'
62
+ ? translationMessages.pydanticForms.widgets
63
+ : {};
57
64
 
58
65
  const onSuccess = (_fieldValues: object, req: object) => {
59
66
  const request = req as { response: StartProcessResponse };
@@ -88,6 +95,18 @@ export const WfoPydanticForm = ({
88
95
  object | string
89
96
  >;
90
97
  resolve(data);
98
+ } else if (
99
+ typeof error === 'object' &&
100
+ error !== null
101
+ ) {
102
+ const validationError =
103
+ error as FormValidationError;
104
+ if (validationError?.status === 400) {
105
+ resolve({
106
+ ...validationError.data,
107
+ status: validationError.status.toString(),
108
+ });
109
+ }
91
110
  }
92
111
  } else if (result.data) {
93
112
  resolve(result.data);
@@ -112,11 +131,13 @@ export const WfoPydanticForm = ({
112
131
  return pydanticFormProvider;
113
132
  };
114
133
 
134
+ const orchestratorTranslations = formTranslations as unknown;
135
+
115
136
  const pydanticLabelProvider: PydanticFormLabelProvider = async () => {
116
137
  return new Promise((resolve) => {
117
138
  resolve({
118
139
  labels: {
119
- ...(formTranslations as object),
140
+ ...(orchestratorTranslations as object),
120
141
  },
121
142
  data: {},
122
143
  });
@@ -138,17 +159,83 @@ export const WfoPydanticForm = ({
138
159
  );
139
160
  },
140
161
  },
141
- ...currentMatchers,
162
+ {
163
+ id: 'summary',
164
+ ElementMatch: {
165
+ Element: Summary,
166
+ isControlledElement: false,
167
+ },
168
+ matcher(field) {
169
+ return (
170
+ field.type === PydanticFormFieldType.STRING &&
171
+ (field.format as string) === 'summary'
172
+ );
173
+ },
174
+ },
175
+ {
176
+ id: 'label',
177
+ ElementMatch: {
178
+ Element: Label,
179
+ isControlledElement: false,
180
+ },
181
+ matcher(field) {
182
+ return (
183
+ field.type === PydanticFormFieldType.STRING &&
184
+ field.format === PydanticFormFieldFormat.LABEL
185
+ );
186
+ },
187
+ },
188
+ {
189
+ id: 'divider',
190
+ ElementMatch: {
191
+ Element: Divider,
192
+ isControlledElement: false,
193
+ },
194
+ matcher(field) {
195
+ return (
196
+ field.type === PydanticFormFieldType.STRING &&
197
+ field.format === PydanticFormFieldFormat.DIVIDER
198
+ );
199
+ },
200
+ },
201
+ {
202
+ id: 'checkbox',
203
+ ElementMatch: {
204
+ Element: Checkbox,
205
+ isControlledElement: true,
206
+ },
207
+ matcher(field) {
208
+ return field.type === PydanticFormFieldType.BOOLEAN;
209
+ },
210
+ },
211
+ ...currentMatchers.filter((matcher) => matcher.id !== 'text'),
212
+ {
213
+ id: 'text',
214
+ ElementMatch: {
215
+ Element: Text,
216
+ isControlledElement: true,
217
+ },
218
+ matcher(field) {
219
+ return field.type === PydanticFormFieldType.STRING;
220
+ },
221
+ validator: zodValidationPresets.string,
222
+ },
142
223
  ];
143
224
 
144
225
  return componentMatcher ? componentMatcher(wfoMatchers) : wfoMatchers;
145
226
  };
146
227
 
228
+ const handleCancel = () => {
229
+ const pfBasePath = isTask ? PATH_TASKS : PATH_WORKFLOWS;
230
+ router.replace(pfBasePath);
231
+ };
232
+
147
233
  return (
148
234
  <PydanticForm
149
235
  title={''}
150
236
  id={processName}
151
237
  onSuccess={onSuccess}
238
+ onCancel={handleCancel}
152
239
  loadingComponent={<WfoLoading />}
153
240
  config={{
154
241
  apiProvider: getPydanticFormProvider(),
@@ -161,6 +248,10 @@ export const WfoPydanticForm = ({
161
248
  customTranslations: {
162
249
  cancel: t('cancel'),
163
250
  startWorkflow: t('startWorkflow'),
251
+ widgets: {
252
+ ...(widgetsTranslations as object),
253
+ },
254
+ ...translationMessages,
164
255
  },
165
256
  }}
166
257
  />
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+
3
+ import type { PydanticFormControlledElement } from 'pydantic-forms';
4
+
5
+ import { EuiCheckbox } from '@elastic/eui';
6
+
7
+ export const Checkbox: PydanticFormControlledElement = ({
8
+ pydanticFormField,
9
+ onChange,
10
+ value,
11
+ disabled,
12
+ }) => {
13
+ return (
14
+ <EuiCheckbox
15
+ checked={value || false}
16
+ disabled={disabled}
17
+ id={pydanticFormField.id}
18
+ label={pydanticFormField.title || <div>&nbsp;</div>}
19
+ onChange={() => !disabled && onChange(!value)}
20
+ />
21
+ );
22
+ };
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+
3
+ import type { PydanticFormElement } from 'pydantic-forms';
4
+
5
+ import { EuiHorizontalRule } from '@elastic/eui';
6
+
7
+ import { useOrchestratorTheme } from '@/hooks';
8
+
9
+ export const Divider: PydanticFormElement = ({ pydanticFormField }) => {
10
+ const { theme } = useOrchestratorTheme();
11
+ return (
12
+ <EuiHorizontalRule
13
+ style={{ marginTop: theme.base }}
14
+ id={pydanticFormField.id}
15
+ />
16
+ );
17
+ };
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+
3
+ import { PydanticFormElement } from 'pydantic-forms';
4
+
5
+ import { useOrchestratorTheme } from '@/hooks';
6
+
7
+ export const Label: PydanticFormElement = ({ pydanticFormField }) => {
8
+ const { theme } = useOrchestratorTheme();
9
+
10
+ return (
11
+ <div>
12
+ <label
13
+ css={{
14
+ color: theme.colors.text,
15
+ display: 'block',
16
+ }}
17
+ id={pydanticFormField.id}
18
+ >
19
+ {pydanticFormField.title}
20
+ </label>
21
+ </div>
22
+ );
23
+ };