@orchestrator-ui/orchestrator-ui-components 5.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchestrator-ui/orchestrator-ui-components",
3
- "version": "5.0.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",
@@ -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
+ };
@@ -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';
@@ -1 +1 @@
1
- export const ORCHESTRATOR_UI_LIBRARY_VERSION = '5.0.0';
1
+ export const ORCHESTRATOR_UI_LIBRARY_VERSION = '5.1.0';
@@ -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';
@@ -128,33 +128,18 @@ export const WfoTasksPage = () => {
128
128
  columnType: ColumnType.DATA,
129
129
  label: t('description'),
130
130
  width: '700px',
131
- renderData: (value, row) =>
132
- value ? (
133
- <WfoMetadataDescriptionField
134
- onSave={(updatedNote) =>
135
- updateWorkflow({
136
- id: row.workflowId,
137
- description: updatedNote,
138
- })
139
- }
140
- description={value}
141
- />
142
- ) : null,
131
+ renderData: (value, row) => (
132
+ <WfoMetadataDescriptionField
133
+ onSave={(updatedNote) =>
134
+ updateWorkflow({
135
+ id: row.workflowId,
136
+ description: updatedNote,
137
+ })
138
+ }
139
+ description={value}
140
+ />
141
+ ),
143
142
  },
144
-
145
- // description: {
146
- // columnType: ColumnType.DATA,
147
- // label: t('description'),
148
- // width: '450px',
149
- // renderData: (value, row) =>
150
- // value ? (
151
- // <WfoWorkflowDescriptionField
152
- // workflow_id={row.workflowId}
153
- // description={value}
154
- // />
155
- // ) : null,
156
- // },
157
-
158
143
  target: {
159
144
  columnType: ColumnType.DATA,
160
145
  label: t('target'),