@orchestrator-ui/orchestrator-ui-components 5.0.0 → 5.2.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 (41) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +4 -4
  4. package/CHANGELOG.md +21 -0
  5. package/dist/index.d.ts +2858 -345
  6. package/dist/index.js +1967 -1714
  7. package/dist/index.js.map +1 -1
  8. package/package.json +2 -2
  9. package/src/components/WfoForms/formFields/SubscriptionSummaryField.tsx +1 -1
  10. package/src/components/WfoForms/formFields/commonStyles.ts +6 -0
  11. package/src/components/WfoForms/formFields/{SubscriptionField.tsx → deprecated/SubscriptionField.tsx} +53 -157
  12. package/src/components/WfoForms/formFields/deprecated/index.ts +1 -0
  13. package/src/components/WfoForms/formFields/index.ts +1 -1
  14. package/src/components/WfoPydanticForm/Footer.tsx +75 -34
  15. package/src/components/WfoPydanticForm/Header.tsx +18 -0
  16. package/src/components/WfoPydanticForm/Row.tsx +17 -5
  17. package/src/components/WfoPydanticForm/WfoPydanticForm.tsx +101 -9
  18. package/src/components/WfoPydanticForm/fields/Checkbox.tsx +22 -0
  19. package/src/components/WfoPydanticForm/fields/Divider.tsx +17 -0
  20. package/src/components/WfoPydanticForm/fields/Label.tsx +23 -0
  21. package/src/components/WfoPydanticForm/fields/Summary.tsx +125 -0
  22. package/src/components/WfoPydanticForm/fields/Text.tsx +28 -0
  23. package/src/components/WfoPydanticForm/fields/index.ts +5 -0
  24. package/src/configuration/constants.ts +2 -0
  25. package/src/configuration/version.ts +1 -1
  26. package/src/hooks/deprecated/useGetSurfSubcriptionDropdownOptions.ts +37 -0
  27. package/src/icons/WfoExclamationTriangle.tsx +29 -0
  28. package/src/icons/index.ts +1 -0
  29. package/src/pages/metadata/WfoTasksPage.tsx +11 -26
  30. package/src/pages/metadata/WfoWorkflowsPage.tsx +11 -12
  31. package/src/rtk/api.ts +3 -1
  32. package/src/rtk/endpoints/deprecated/index.ts +1 -0
  33. package/src/rtk/endpoints/deprecated/surfSubscriptionDropdownOptions.ts +53 -0
  34. package/src/rtk/endpoints/index.ts +2 -1
  35. package/src/types/deprecated/SurfSubscriptionDropdownOptionsFilterParams.ts +10 -0
  36. package/src/types/deprecated/index.ts +1 -0
  37. package/src/types/index.ts +1 -0
  38. package/src/types/types.ts +2 -2
  39. package/src/hooks/deprecated/useGetSubscriptionDropdownOptions.ts +0 -39
  40. package/src/rtk/endpoints/subscriptionsDropdownOptions.ts +0 -71
  41. /package/src/components/WfoForms/formFields/{SubscriptionFieldStyling.ts → deprecated/SubscriptionFieldStyling.ts} +0 -0
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.2.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
  },
@@ -19,7 +21,11 @@ export const getCommonFormFieldStyles = ({ theme }: WfoTheme) => {
19
21
  },
20
22
  });
21
23
 
24
+ const errorStyle = css({
25
+ color: theme.colors.dangerText,
26
+ });
22
27
  return {
28
+ errorStyle,
23
29
  formRowStyle,
24
30
  };
25
31
  };
@@ -33,15 +33,12 @@ import {
33
33
  EuiText,
34
34
  } from '@elastic/eui';
35
35
 
36
- import { PortMode, ProductTag } from '@/components';
37
36
  import { useWithOrchestratorTheme } from '@/hooks';
38
- import { useGetSubscriptionDropdownOptions } from '@/hooks/deprecated/useGetSubscriptionDropdownOptions';
39
- import { SubscriptionDropdownOption } from '@/types';
37
+ import { useGetSurfSubscriptionDropdownOptions } from '@/hooks/deprecated/useGetSurfSubcriptionDropdownOptions';
40
38
 
41
- import { getSelectFieldStyles } from './SelectField/styles';
39
+ import { getSelectFieldStyles } from '../SelectField/styles';
40
+ import { FieldProps, Option } from '../types';
42
41
  import { subscriptionFieldStyling } from './SubscriptionFieldStyling';
43
- import { FieldProps, Option } from './types';
44
- import { getPortMode } from './utils';
45
42
 
46
43
  declare module 'uniforms' {
47
44
  interface FilterDOMProps {
@@ -81,6 +78,18 @@ export type SubscriptionFieldProps = FieldProps<
81
78
  }
82
79
  >;
83
80
 
81
+ function toPortModes(visiblePortMode: string): string[] {
82
+ if (visiblePortMode === 'all') {
83
+ return [];
84
+ }
85
+
86
+ if (visiblePortMode === 'normal') {
87
+ return ['tagged', 'untagged'];
88
+ }
89
+
90
+ return [visiblePortMode];
91
+ }
92
+
84
93
  function SubscriptionFieldDefinition({
85
94
  disabled,
86
95
  id,
@@ -110,9 +119,6 @@ function SubscriptionFieldDefinition({
110
119
  const { reactSelectInnerComponentStyles } =
111
120
  useWithOrchestratorTheme(getSelectFieldStyles);
112
121
 
113
- const { refetch, subscriptions, isFetching } =
114
- useGetSubscriptionDropdownOptions(tags, statuses);
115
-
116
122
  const nameArray = joinName(null, name);
117
123
  let parentName = joinName(nameArray.slice(0, -1));
118
124
 
@@ -136,160 +142,50 @@ function SubscriptionFieldDefinition({
136
142
  ? get(model, customerKey, 'nonExistingOrgToFilterEverything')
137
143
  : customerId;
138
144
 
139
- const makeLabel = (subscription: SubscriptionDropdownOption) => {
140
- const description =
141
- subscription.description ||
142
- t('widgets.subscription.missingDescription');
143
- const subscriptionSubstring = subscription.subscriptionId.substring(
144
- 0,
145
- 8,
146
- );
147
-
148
- if (['Node'].includes(subscription.product.tag)) {
149
- const description =
150
- subscription.description ||
151
- t('widgets.subscription.missingDescription');
152
- return `${subscription.subscriptionId.substring(
153
- 0,
154
- 8,
155
- )} ${description.trim()}`;
156
- } else if (
157
- [
158
- ProductTag.SP,
159
- ProductTag.SPNL,
160
- ProductTag.AGGSP,
161
- ProductTag.AGGSPNL,
162
- ProductTag.MSC,
163
- ProductTag.MSCNL,
164
- ProductTag.IRBSP,
165
- ].includes(subscription.product.tag as ProductTag)
145
+ const getFilteredOptions = (optionsInput: Option[]): Option[] => {
146
+ // Remnant of the old logic in which much more filtering happened clientside, which is now done
147
+ // server-side by setting the required URL parameters.
148
+
149
+ // The 'uniqueItems' filter below should exclude options already chosen in other SubscriptionFields in the same parent Array.
150
+ // Although this partly relies on uniforms magic which will be reworked/replaced with pydantic-forms.
151
+ if (
152
+ parentName !== name &&
153
+ parent.fieldType === Array &&
154
+ // @ts-expect-error Parent field can have the uniqueItems boolean property but this is not part of JSONSchema6 type
155
+ // TODO: Figure out why this is so
156
+ parent.uniqueItems
166
157
  ) {
167
- const portMode = getPortMode(subscription.productBlockInstances);
168
- const subscriptionTitle =
169
- subscription.productBlockInstances[0].productBlockInstanceValues.find(
170
- (item) => item.field === 'title',
171
- );
172
- if (subscriptionTitle) {
173
- return `${subscriptionSubstring} - ${description.trim()} - ${
174
- subscriptionTitle.value
175
- }`;
176
- }
177
- return `${subscriptionSubstring} ${portMode?.toUpperCase()} ${description.trim()} ${
178
- subscription.customer?.fullname
179
- }`;
158
+ const allValues: string[] = get(model, parentName, []);
159
+ const chosenValues = allValues.filter(
160
+ (_item, index) =>
161
+ index.toString() !== nameArray[nameArray.length - 1],
162
+ );
163
+
164
+ return optionsInput.filter((option) =>
165
+ chosenValues.includes(option.value),
166
+ );
180
167
  } else {
181
- return description.trim();
168
+ return optionsInput;
182
169
  }
183
170
  };
184
171
 
185
- // Filter by product, needed because getSubscriptions might return more than we want
186
- const getSubscriptionOptions = (): Option[] => {
187
- const filteredSubscriptions = subscriptions?.filter((subscription) => {
188
- // NOTE: useBandWith, productIds and tags need to be checked in this order as per the V1 logic
189
-
190
- // If a bandwidth filter is supplied it needs to be applied to the subscription product
191
- if (usedBandwidth) {
192
- const portSpeedInput = subscription.fixedInputs.find(
193
- (fixedInput) => fixedInput.field === 'port_speed',
194
- );
195
- if (
196
- portSpeedInput?.value &&
197
- parseInt(portSpeedInput.value.toString(), 10) <
198
- parseInt(usedBandwidth.toString(), 10)
199
- ) {
200
- return false;
201
- }
202
- }
203
-
204
- // If specific productIds are provided the subscriptions needs to have one of those
205
- if (
206
- !usedBandwidth &&
207
- productIds &&
208
- productIds.length > 0 &&
209
- !productIds.includes(subscription.product.productId)
210
- ) {
211
- return false;
212
- }
213
-
214
- if (
215
- !usedBandwidth &&
216
- !productIds &&
217
- tags &&
218
- tags?.length > 0 &&
219
- !tags.includes(subscription.product.tag)
220
- ) {
221
- return false;
222
- }
223
-
224
- // If specific subscriptionIds are excluded the subscription can't be one ot those
225
- if (
226
- excludedSubscriptionIds &&
227
- excludedSubscriptionIds.length > 0 &&
228
- excludedSubscriptionIds.includes(subscription.subscriptionId)
229
- ) {
230
- return false;
231
- }
232
-
233
- // If a Port mode filter is applied we need to filter on that
234
- if (visiblePortMode !== 'all') {
235
- const portMode = getPortMode(
236
- subscription.productBlockInstances,
237
- );
238
- // For normal mode filter out all subscriptions that don't have tagged or untagged ports
239
- if (
240
- visiblePortMode === 'normal' &&
241
- ![PortMode.TAGGED, PortMode.UNTAGGED, undefined].includes(
242
- portMode,
243
- )
244
- ) {
245
- return false;
246
- } else if (
247
- portMode !== visiblePortMode &&
248
- visiblePortMode !== 'normal'
249
- ) {
250
- return false;
251
- }
252
- }
253
-
254
- // If a customer filter is applied we need to filter on that
255
- if (
256
- usedCustomerId &&
257
- subscription.customer?.customerId !== usedCustomerId
258
- ) {
259
- return false;
260
- }
261
-
262
- if (parentName !== name) {
263
- if (
264
- parent.fieldType === Array &&
265
- // @ts-expect-error Parent field can have the uniqueItems boolean property but this is not part of JSONSchema6 type
266
- // TODO: Figure out why this is so
267
- parent.uniqueItems
268
- ) {
269
- const allValues: string[] = get(model, parentName, []);
270
- const chosenValues = allValues.filter(
271
- (_item, index) =>
272
- index.toString() !==
273
- nameArray[nameArray.length - 1],
274
- );
275
- if (!chosenValues.includes(subscription.subscriptionId)) {
276
- return false;
277
- }
278
- }
279
- }
280
-
281
- return true;
282
- });
283
-
284
- return filteredSubscriptions
285
- ? filteredSubscriptions.map((subscription) => ({
286
- label: makeLabel(subscription),
287
- value: subscription.subscriptionId,
288
- }))
289
- : [];
290
- };
172
+ const excludeSubscriptionIds = excludedSubscriptionIds;
173
+ const portModes = toPortModes(visiblePortMode);
174
+ const {
175
+ refetch,
176
+ options: unfilteredOptions,
177
+ isFetching,
178
+ } = useGetSurfSubscriptionDropdownOptions(
179
+ tags,
180
+ statuses,
181
+ productIds,
182
+ excludeSubscriptionIds,
183
+ usedCustomerId,
184
+ portModes,
185
+ usedBandwidth,
186
+ );
291
187
 
292
- const options = getSubscriptionOptions();
188
+ const options = getFilteredOptions(unfilteredOptions);
293
189
 
294
190
  const selectedValue = options.find(
295
191
  (option: Option) => option.value === value,
@@ -12,3 +12,4 @@ export * from './VlanField';
12
12
  export * from './FileUploadField';
13
13
  export * from './types';
14
14
  export * from './utils';
15
+ export * from './SubscriptionField';
@@ -20,8 +20,8 @@ export * from './LocationCodeField';
20
20
  export * from './deprecated';
21
21
  export * from './NestField';
22
22
  export * from './OptGroupField';
23
- export * from './SubscriptionField';
24
23
  export * from './SummaryField';
25
24
  export * from './CustomerField';
26
25
  export * from './ConnectedSelectField';
27
26
  export * from './deprecated/FileUploadField';
27
+ 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
  );
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ import { usePydanticFormContext } from 'pydantic-forms';
4
+
5
+ import { css } from '@emotion/react';
6
+
7
+ const headerStyling = css`
8
+ padding: 20px 0;
9
+ font-size: larger;
10
+ font-weight: bold;
11
+ margin-bottom: 15px;
12
+ `;
13
+
14
+ export const Header = () => {
15
+ const { pydanticFormSchema } = usePydanticFormContext();
16
+
17
+ return <h3 css={headerStyling}>{pydanticFormSchema?.title}</h3>;
18
+ };
@@ -1,30 +1,42 @@
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,
14
14
  isInvalid,
15
+ required,
15
16
  children,
16
17
  }) => {
17
- const { formRowStyle } = useWithOrchestratorTheme(getCommonFormFieldStyles);
18
+ const { formRowStyle, errorStyle } = useWithOrchestratorTheme(
19
+ getCommonFormFieldStyles,
20
+ );
21
+
22
+ const Label = () => {
23
+ return title ? (
24
+ <div css={error && errorStyle}>
25
+ {title} {required && '*'}
26
+ </div>
27
+ ) : undefined;
28
+ };
29
+
18
30
  return (
19
31
  <EuiFormRow
20
32
  css={formRowStyle}
21
- label={title}
33
+ label={<Label />}
22
34
  labelAppend={<EuiText size="m">{description}</EuiText>}
23
35
  error={error}
24
36
  isInvalid={isInvalid}
25
37
  fullWidth
26
38
  >
27
- <div>{children}</div>
39
+ <>{children}</>
28
40
  </EuiFormRow>
29
41
  );
30
42
  };