@orchestrator-ui/orchestrator-ui-components 1.13.2 → 1.14.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": "1.13.2",
3
+ "version": "1.14.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Library of UI Components used to display the workflow orchestrator frontend",
6
6
  "author": {
@@ -14,11 +14,11 @@
14
14
  */
15
15
  import React, { useCallback, useEffect, useState } from 'react';
16
16
 
17
+ import { UserInputFormWizard } from '@/components';
18
+ import { handlePromiseErrorWithCallback } from '@/rtk';
19
+ import { useStartFormMutation } from '@/rtk/endpoints/forms';
17
20
  import { Form, FormNotCompleteResponse } from '@/types/forms';
18
21
 
19
- import UserInputFormWizardDeprecated from './UserInputFormWizardDeprecated';
20
- import { useAxiosApiClient } from './useAxiosApiClient';
21
-
22
22
  interface IProps {
23
23
  /* eslint-disable @typescript-eslint/no-explicit-any */
24
24
  preselectedInput?: unknown;
@@ -31,20 +31,22 @@ export function CreateForm(props: IProps) {
31
31
  const { preselectedInput, formKey, handleSubmit, handleCancel } = props;
32
32
  const [form, setForm] = useState<Form>({});
33
33
  const { stepUserInput, hasNext } = form;
34
- const apiClient = useAxiosApiClient();
34
+ const [startForm] = useStartFormMutation();
35
35
 
36
36
  const submit = useCallback(
37
37
  (userInputs: object[]) => {
38
- return apiClient.startForm(formKey, userInputs).then((form) => {
39
- handleSubmit(form);
40
- });
38
+ return startForm({ formKey, userInputs })
39
+ .unwrap()
40
+ .then((form) => {
41
+ handleSubmit(form);
42
+ });
41
43
  },
42
- [formKey, handleSubmit, apiClient],
44
+ [formKey, handleSubmit, startForm],
43
45
  );
44
46
 
45
47
  useEffect(() => {
46
- if (formKey && apiClient) {
47
- apiClient.catchErrorStatus<FormNotCompleteResponse>(
48
+ if (formKey) {
49
+ handlePromiseErrorWithCallback<FormNotCompleteResponse>(
48
50
  submit([]),
49
51
  510,
50
52
  (json) => {
@@ -55,14 +57,14 @@ export function CreateForm(props: IProps) {
55
57
  },
56
58
  );
57
59
  }
58
- }, [formKey, submit, preselectedInput, apiClient]);
60
+ }, [formKey, submit, preselectedInput]);
59
61
 
60
62
  return (
61
63
  <div>
62
64
  {stepUserInput && (
63
- <UserInputFormWizardDeprecated
65
+ <UserInputFormWizard
64
66
  stepUserInput={stepUserInput}
65
- validSubmit={submit}
67
+ stepSubmit={submit}
66
68
  cancel={handleCancel}
67
69
  hasNext={hasNext ?? false}
68
70
  isTask={false}
@@ -16,7 +16,6 @@
16
16
  */
17
17
  import React, { useContext, useState } from 'react';
18
18
 
19
- import axios from 'axios';
20
19
  import invariant from 'invariant';
21
20
  import { JSONSchema6 } from 'json-schema';
22
21
  import cloneDeep from 'lodash/cloneDeep';
@@ -37,7 +36,7 @@ import {
37
36
  import { ConfirmDialogActions, ConfirmationDialogContext } from '@/contexts';
38
37
  import { useOrchestratorTheme } from '@/hooks';
39
38
  import { WfoPlayFill } from '@/icons';
40
- import { ValidationError } from '@/types/forms';
39
+ import { FormValidationError, ValidationError } from '@/types/forms';
41
40
 
42
41
  import { autoFieldFunction } from './AutoFieldLoader';
43
42
  import AutoFields from './AutoFields';
@@ -440,31 +439,30 @@ function UserInputForm({
440
439
  return null;
441
440
  } catch (error: unknown) {
442
441
  setProcessing(false);
443
-
444
- if (
445
- axios.isAxiosError(error) &&
446
- error.response?.status === 400
447
- ) {
448
- const json = error.response.data;
449
- setNrOfValidationErrors(json.validation_errors.length);
450
- setRootErrors(
451
- json.validation_errors
452
- .filter(
453
- (e: ValidationError) => e.loc[0] === '__root__',
454
- )
455
- .map((e: ValidationError) => e.msg),
456
- );
457
- throw Object.assign(new Error(), {
458
- details: json.validation_errors.map(
459
- (e: ValidationError) => ({
460
- message: e.msg,
461
- params: e.ctx || {},
462
- dataPath: '.' + e.loc.join('.'),
463
- }),
464
- ),
465
- });
442
+ if (typeof error === 'object' && error !== null) {
443
+ const validationError = error as FormValidationError;
444
+ if (validationError?.status === 400) {
445
+ const json = validationError.data;
446
+ setNrOfValidationErrors(json.validation_errors.length);
447
+ setRootErrors(
448
+ json.validation_errors
449
+ .filter(
450
+ (e: ValidationError) =>
451
+ e.loc[0] === '__root__',
452
+ )
453
+ .map((e: ValidationError) => e.msg),
454
+ );
455
+ throw Object.assign(new Error(), {
456
+ details: json.validation_errors.map(
457
+ (e: ValidationError) => ({
458
+ message: e.msg,
459
+ params: e.ctx || {},
460
+ dataPath: '.' + e.loc.join('.'),
461
+ }),
462
+ ),
463
+ });
464
+ }
466
465
  }
467
-
468
466
  // Let the error escape so it can be caught by our own onerror handler instead of being silenced by uniforms
469
467
  setTimeout(() => {
470
468
  throw error;
@@ -44,14 +44,14 @@ function stop(e: React.SyntheticEvent) {
44
44
  }
45
45
  }
46
46
 
47
- export function UserInputFormWizard({
47
+ export const UserInputFormWizard = ({
48
48
  hasNext = false,
49
49
  stepUserInput,
50
50
  stepSubmit,
51
51
  cancel,
52
52
  isTask,
53
53
  isResuming = false,
54
- }: UserInputFormWizardProps) {
54
+ }: UserInputFormWizardProps) => {
55
55
  const router = useRouter();
56
56
  const [forms, setForms] = useState<Form[]>([
57
57
  { form: stepUserInput, hasNext: hasNext },
@@ -118,6 +118,4 @@ export function UserInputFormWizard({
118
118
  isResuming={isResuming}
119
119
  />
120
120
  );
121
- }
122
-
123
- export default UserInputFormWizard;
121
+ };
@@ -1,6 +1,5 @@
1
1
  export * from './AutoFields';
2
2
  export * from './UserInputForm';
3
3
  export * from './UserInputFormWizard';
4
- export * from './UserInputFormWizardDeprecated';
5
4
  export * from './formFields';
6
5
  export * from './CreateForm';
@@ -2,9 +2,9 @@ import React, { useState } from 'react';
2
2
 
3
3
  import { EuiFlexItem } from '@elastic/eui';
4
4
 
5
- import { UserInputFormWizardDeprecated, WfoLoading } from '@/components';
6
- import { useAxiosApiClient } from '@/components/WfoForms/useAxiosApiClient';
5
+ import { UserInputFormWizard, WfoLoading } from '@/components';
7
6
  import { useOrchestratorTheme } from '@/hooks';
7
+ import { useResumeProcessMutation } from '@/rtk/endpoints/forms';
8
8
  import { InputForm } from '@/types/forms';
9
9
 
10
10
  interface WfoStepFormProps {
@@ -20,24 +20,26 @@ export const WfoStepForm = ({
20
20
  }: WfoStepFormProps) => {
21
21
  const [isProcessing, setIsProcessing] = useState<boolean>(false);
22
22
  const { theme } = useOrchestratorTheme();
23
- const apiClient = useAxiosApiClient();
23
+ const [resumeProcess] = useResumeProcessMutation();
24
24
 
25
25
  const submitForm = (processInput: object[]) => {
26
26
  if (!processId) {
27
27
  return Promise.reject();
28
28
  }
29
29
 
30
- return apiClient.resumeProcess(processId, processInput).then(() => {
31
- setIsProcessing(true);
32
- });
30
+ return resumeProcess({ processId, userInputs: processInput }).then(
31
+ () => {
32
+ setIsProcessing(true);
33
+ },
34
+ );
33
35
  };
34
36
 
35
37
  return (
36
38
  <EuiFlexItem css={{ margin: theme.size.m }}>
37
39
  {(isProcessing && <WfoLoading />) || (
38
- <UserInputFormWizardDeprecated
40
+ <UserInputFormWizard
39
41
  stepUserInput={userInputForm}
40
- validSubmit={submitForm}
42
+ stepSubmit={submitForm}
41
43
  hasNext={false}
42
44
  isTask={isTask}
43
45
  isResuming={true}
@@ -1,6 +1,5 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
 
3
- import { AxiosError } from 'axios';
4
3
  import { JSONSchema6 } from 'json-schema';
5
4
  import { useTranslations } from 'next-intl';
6
5
  import { useRouter } from 'next/router';
@@ -13,13 +12,18 @@ import {
13
12
  EuiText,
14
13
  } from '@elastic/eui';
15
14
 
16
- import { PATH_TASKS, WfoError, WfoLoading } from '@/components';
17
- import { PATH_WORKFLOWS } from '@/components';
15
+ import { PATH_TASKS, PATH_WORKFLOWS, WfoError, WfoLoading } from '@/components';
16
+ import { UserInputFormWizard } from '@/components/WfoForms/UserInputFormWizard';
18
17
  import { useAxiosApiClient } from '@/components/WfoForms/useAxiosApiClient';
19
18
  import { WfoStepStatusIcon } from '@/components/WfoWorkflowSteps';
20
19
  import { getStyles } from '@/components/WfoWorkflowSteps/styles';
21
20
  import { useOrchestratorTheme } from '@/hooks';
22
- import { useGetTimeLineItemsQuery } from '@/rtk';
21
+ import {
22
+ HttpStatus,
23
+ handlePromiseErrorWithCallback,
24
+ useGetTimeLineItemsQuery,
25
+ } from '@/rtk';
26
+ import { useStartProcessMutation } from '@/rtk/endpoints/forms';
23
27
  import {
24
28
  EngineStatus,
25
29
  ProcessDetail,
@@ -28,7 +32,6 @@ import {
28
32
  } from '@/types';
29
33
  import { FormNotCompleteResponse } from '@/types/forms';
30
34
 
31
- import UserInputFormWizardDeprecated from '../../components/WfoForms/UserInputFormWizardDeprecated';
32
35
  import { WfoProcessDetail } from './WfoProcessDetail';
33
36
 
34
37
  type StartCreateWorkflowPayload = {
@@ -86,6 +89,8 @@ export const WfoStartProcessPage = ({
86
89
  const [form, setForm] = useState<UserInputForm>({});
87
90
  const { productId, subscriptionId } = router.query as StartProcessPageQuery;
88
91
 
92
+ const [startProcess] = useStartProcessMutation();
93
+
89
94
  const startProcessPayload = useMemo(
90
95
  () => getInitialProcessPayload({ productId, subscriptionId }),
91
96
  [productId, subscriptionId],
@@ -110,17 +115,15 @@ export const WfoStartProcessPage = ({
110
115
 
111
116
  const submit = useCallback(
112
117
  (processInput: object[]) => {
113
- const startProcessPromise = apiClient
114
- .startProcess(
115
- processName,
116
- startProcessPayload
117
- ? [startProcessPayload, ...processInput]
118
- : [...processInput],
119
- )
118
+ const startProcessPromise = startProcess({
119
+ workflowName: processName,
120
+ userInputs: startProcessPayload
121
+ ? [startProcessPayload, ...processInput]
122
+ : [...processInput],
123
+ })
124
+ .unwrap()
120
125
  .then(
121
- // Resolve handler
122
- (result) => {
123
- const process = result as { id: string };
126
+ (process) => {
124
127
  if (process.id) {
125
128
  const basePath = isTask
126
129
  ? PATH_TASKS
@@ -133,13 +136,12 @@ export const WfoStartProcessPage = ({
133
136
  throw e;
134
137
  },
135
138
  )
136
- .catch((error: AxiosError) => {
137
- if (error?.response?.status !== 510) {
138
- if (error?.response?.status === 400) {
139
+ .catch((error) => {
140
+ if (error?.status !== HttpStatus.FormNotComplete) {
141
+ if (error?.status === HttpStatus.BadRequest) {
139
142
  // Rethrow the error so userInputForm can catch it and display validation errors
140
143
  throw error;
141
144
  }
142
-
143
145
  console.error(error);
144
146
  setHasError(true);
145
147
  } else {
@@ -149,9 +151,9 @@ export const WfoStartProcessPage = ({
149
151
 
150
152
  // Catch a 503: Service unavailable error indicating the engine is down. This rethrows other errors
151
153
  // if it's not 503 so we can catch the special 510 error in the catchErrorStatus call in the useEffect hook
152
- return apiClient.catchErrorStatus<EngineStatus>(
154
+ return handlePromiseErrorWithCallback<EngineStatus>(
153
155
  startProcessPromise,
154
- 503,
156
+ HttpStatus.ServiceUnavailable,
155
157
  (json) => {
156
158
  // TODO: Use the toastMessage hook to display an engine down error message
157
159
  console.error('engine down!!!', json);
@@ -159,7 +161,7 @@ export const WfoStartProcessPage = ({
159
161
  },
160
162
  );
161
163
  },
162
- [apiClient, processName, startProcessPayload, isTask, router],
164
+ [startProcess, processName, startProcessPayload, isTask, router],
163
165
  );
164
166
 
165
167
  useEffect(() => {
@@ -171,9 +173,9 @@ export const WfoStartProcessPage = ({
171
173
  });
172
174
  };
173
175
 
174
- apiClient.catchErrorStatus<FormNotCompleteResponse>(
176
+ handlePromiseErrorWithCallback<FormNotCompleteResponse>(
175
177
  submit([]),
176
- 510,
178
+ HttpStatus.FormNotComplete,
177
179
  clientResultCallback,
178
180
  );
179
181
  }
@@ -215,9 +217,9 @@ export const WfoStartProcessPage = ({
215
217
  <EuiHorizontalRule />
216
218
  {(hasError && <WfoError />) ||
217
219
  (stepUserInput && (
218
- <UserInputFormWizardDeprecated
220
+ <UserInputFormWizard
219
221
  stepUserInput={stepUserInput}
220
- validSubmit={submit}
222
+ stepSubmit={submit}
221
223
  cancel={() =>
222
224
  router.push(
223
225
  isTask ? PATH_TASKS : PATH_WORKFLOWS,
package/src/rtk/api.ts CHANGED
@@ -25,6 +25,7 @@ export enum HttpStatus {
25
25
  FormNotComplete = 510,
26
26
  BadGateway = 502,
27
27
  BadRequest = 400,
28
+ ServiceUnavailable = 503,
28
29
  }
29
30
 
30
31
  type ExtraOptions = {
@@ -42,7 +43,7 @@ export const prepareHeaders = async (headers: Headers) => {
42
43
 
43
44
  export const handlePromiseErrorWithCallback = <T>(
44
45
  promise: Promise<unknown>,
45
- status: number,
46
+ status: HttpStatus,
46
47
  callbackAction: (json: T) => void,
47
48
  ) => {
48
49
  return promise.catch((err) => {
@@ -0,0 +1,64 @@
1
+ import { BaseQueryTypes, orchestratorApi } from '@/rtk';
2
+
3
+ const PROCESS_ENDPOINT = 'processes';
4
+ const RESUME_ENDPOINT = 'resume';
5
+ const FORMS_ENDPOINT = 'surf/forms/'; // It is still being used by example-wfo-ui
6
+
7
+ const formsApi = orchestratorApi.injectEndpoints({
8
+ endpoints: (build) => ({
9
+ startProcess: build.mutation<
10
+ { id: string },
11
+ { workflowName: string; userInputs: object[] }
12
+ >({
13
+ query: ({ workflowName, userInputs }) => ({
14
+ url: `${PROCESS_ENDPOINT}/${workflowName}`,
15
+ method: 'POST',
16
+ body: JSON.stringify(userInputs),
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ }),
21
+ extraOptions: {
22
+ baseQueryType: BaseQueryTypes.fetch,
23
+ },
24
+ }),
25
+ resumeProcess: build.mutation<
26
+ void,
27
+ { processId: string; userInputs: object[] }
28
+ >({
29
+ query: ({ processId, userInputs }) => ({
30
+ url: `${PROCESS_ENDPOINT}/${processId}/${RESUME_ENDPOINT}`,
31
+ method: 'PUT',
32
+ body: JSON.stringify(userInputs),
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ },
36
+ }),
37
+ extraOptions: {
38
+ baseQueryType: BaseQueryTypes.fetch,
39
+ },
40
+ }),
41
+ startForm: build.mutation<
42
+ void,
43
+ { formKey: string; userInputs: object[] }
44
+ >({
45
+ query: ({ formKey, userInputs }) => ({
46
+ url: `${FORMS_ENDPOINT}${formKey}`,
47
+ method: 'POST',
48
+ body: JSON.stringify(userInputs),
49
+ headers: {
50
+ 'Content-Type': 'application/json',
51
+ },
52
+ }),
53
+ extraOptions: {
54
+ baseQueryType: BaseQueryTypes.fetch,
55
+ },
56
+ }),
57
+ }),
58
+ });
59
+
60
+ export const {
61
+ useStartProcessMutation,
62
+ useResumeProcessMutation,
63
+ useStartFormMutation,
64
+ } = formsApi;
@@ -3,6 +3,8 @@ import { Ref } from 'react';
3
3
  import { JSONSchema6 } from 'json-schema';
4
4
  import { HTMLFieldProps } from 'uniforms';
5
5
 
6
+ import { HttpStatus } from '@/rtk';
7
+
6
8
  export interface ValidationError {
7
9
  input_type: string;
8
10
  loc: (string | number)[];
@@ -38,3 +40,17 @@ export type FieldProps<
38
40
  description?: string;
39
41
  } & Extra
40
42
  >;
43
+
44
+ type ValidationErrorData = {
45
+ detail: string;
46
+ status: HttpStatus;
47
+ title: string;
48
+ traceback: string;
49
+ type: string;
50
+ validation_errors: [];
51
+ };
52
+
53
+ export type FormValidationError = {
54
+ data: ValidationErrorData;
55
+ status: HttpStatus;
56
+ };
@@ -1,125 +0,0 @@
1
- /*
2
- * Copyright 2019-2023 SURF.
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- * http://www.apache.org/licenses/LICENSE-2.0
7
- *
8
- * Unless required by applicable law or agreed to in writing, software
9
- * distributed under the License is distributed on an "AS IS" BASIS,
10
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
- * See the License for the specific language governing permissions and
12
- * limitations under the License.
13
- *
14
- */
15
- import React, { useEffect, useState } from 'react';
16
-
17
- import { useRouter } from 'next/router';
18
- import hash from 'object-hash';
19
-
20
- import { ConfirmDialogActions } from '@/contexts';
21
- import { FormNotCompleteResponse, InputForm } from '@/types/forms';
22
-
23
- import UserInputForm from './UserInputForm';
24
- import { useAxiosApiClient } from './useAxiosApiClient';
25
-
26
- interface Form {
27
- form: InputForm;
28
- hasNext?: boolean;
29
- }
30
-
31
- interface UserInputFormWizardProps {
32
- stepUserInput: InputForm;
33
- validSubmit: (processInput: object[]) => Promise<unknown>;
34
- cancel?: () => void;
35
- isTask: boolean;
36
- hasNext?: boolean;
37
- isResuming?: boolean;
38
- }
39
-
40
- function stop(e: React.SyntheticEvent) {
41
- if (e !== undefined && e !== null) {
42
- e.preventDefault();
43
- e.stopPropagation();
44
- }
45
- }
46
-
47
- export function UserInputFormWizardDeprecated({
48
- hasNext = false,
49
- stepUserInput,
50
- validSubmit,
51
- cancel,
52
- isTask,
53
- isResuming = false,
54
- }: UserInputFormWizardProps) {
55
- const router = useRouter();
56
- const apiClient = useAxiosApiClient();
57
- const [forms, setForms] = useState<Form[]>([
58
- { form: stepUserInput, hasNext: hasNext },
59
- ]);
60
- const [userInputs, setUserInputs] = useState<object[]>([]);
61
-
62
- useEffect(() => {
63
- setForms([{ form: stepUserInput, hasNext: hasNext }]);
64
- }, [hasNext, stepUserInput]);
65
-
66
- const previous: ConfirmDialogActions['closeConfirmDialog'] = (e) => {
67
- if (e) {
68
- stop(e);
69
- }
70
- const current = forms.pop();
71
- setForms(forms.filter((item) => item !== current));
72
- };
73
-
74
- const submit = (currentFormData: object) => {
75
- const newUserInputs = userInputs.slice(0, forms.length - 1);
76
- newUserInputs.push(currentFormData);
77
-
78
- const result = validSubmit(newUserInputs);
79
- return apiClient.catchErrorStatus<FormNotCompleteResponse>(
80
- result,
81
- 510,
82
- (json) => {
83
- window.scrollTo(0, 0);
84
- setForms([
85
- ...forms,
86
- { form: json.form, hasNext: json.hasNext },
87
- ]);
88
- setUserInputs(newUserInputs);
89
- },
90
- );
91
- };
92
-
93
- const currentForm = forms[forms.length - 1];
94
- const currentUserInput = userInputs[forms.length - 1];
95
- if (!currentForm || !currentForm.form.properties) {
96
- return null;
97
- }
98
-
99
- /* Generate a key based on input widget names that results in a new
100
- * clean instance + rerender of UserInputForm if the form changes. Without this, state of previous,
101
- * wizard step can cause wrong/weird default values for forms inputs.
102
- *
103
- * Note: to ensure a new form for multiple form wizard steps with exactly the same fields and labels on
104
- * the form the hash is calculated on the form object itself + length, which generates a unique hash as it
105
- * has a changing ".length" attribute.
106
- * */
107
- const key = hash.sha1({ form: currentForm.form, length: forms.length });
108
- return (
109
- <UserInputForm
110
- key={key}
111
- router={router}
112
- stepUserInput={currentForm.form}
113
- validSubmit={submit}
114
- previous={previous}
115
- hasNext={currentForm.hasNext}
116
- hasPrev={forms.length > 1}
117
- cancel={cancel}
118
- userInput={currentUserInput}
119
- isTask={isTask}
120
- isResuming={isResuming}
121
- />
122
- );
123
- }
124
-
125
- export default UserInputFormWizardDeprecated;