@orchestrator-ui/orchestrator-ui-components 0.11.1 → 0.13.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/src/api/index.ts CHANGED
@@ -24,9 +24,9 @@ import { ProductDefinition } from '@/types';
24
24
 
25
25
  import { getAxiosInstance } from './axios';
26
26
 
27
- const CIM_FORMS_ENDPOINT = 'surf/cim/forms/';
28
27
  const PROCESS_ENDPOINT = 'processes/';
29
28
  const PRODUCTS_ENDPOINT = 'products/';
29
+ const FORMS_ENDPOINT = 'surf/forms/';
30
30
 
31
31
  export class BaseApiClient {
32
32
  private _axiosInstance: AxiosInstance;
@@ -105,14 +105,20 @@ export class BaseApiClient {
105
105
  };
106
106
  }
107
107
 
108
- abstract class ApiClientInterface extends BaseApiClient {
109
- abstract cimStartForm: (
108
+ export class ApiClient extends BaseApiClient {
109
+ startForm = (
110
110
  formKey: string,
111
111
  userInputs: object[],
112
- ) => Promise<object>;
113
- }
112
+ ): Promise<{ id: string }> => {
113
+ return this.postPutJson(
114
+ `${FORMS_ENDPOINT}${formKey}`,
115
+ userInputs,
116
+ 'post',
117
+ false,
118
+ true,
119
+ );
120
+ };
114
121
 
115
- export class ApiClient extends ApiClientInterface {
116
122
  startProcess = (
117
123
  workflowName: string,
118
124
  processInput: object,
@@ -142,18 +148,6 @@ export class ApiClient extends ApiClientInterface {
142
148
  productById = (productId: string): Promise<ProductDefinition> => {
143
149
  return this.fetchJson(`${PRODUCTS_ENDPOINT}${productId}`);
144
150
  };
145
- cimStartForm = (
146
- formKey: string,
147
- userInputs: object[],
148
- ): Promise<{ id: string }> => {
149
- return this.postPutJson(
150
- `${CIM_FORMS_ENDPOINT}${formKey}`,
151
- userInputs,
152
- 'post',
153
- false,
154
- true,
155
- );
156
- };
157
151
  prefix_filters = (): Promise<IpPrefix[]> => {
158
152
  return this.fetchJson('surf/ipam/prefix_filters');
159
153
  };
@@ -35,7 +35,7 @@ export function CreateForm(props: IProps) {
35
35
 
36
36
  const submit = useCallback(
37
37
  (userInputs: object[]) => {
38
- return apiClient.cimStartForm(formKey, userInputs).then((form) => {
38
+ return apiClient.startForm(formKey, userInputs).then((form) => {
39
39
  handleSubmit(form);
40
40
  });
41
41
  },
@@ -1,5 +1,5 @@
1
- export * from './CreateForm';
2
1
  export * from './AutoFields';
3
2
  export * from './UserInputForm';
4
3
  export * from './UserInputFormWizard';
5
4
  export * from './formFields';
5
+ export * from './CreateForm';
@@ -3,7 +3,6 @@ export * from './productBlocksQuery';
3
3
  export * from './productsQuery';
4
4
  export * from './resourceTypesQuery';
5
5
  export * from './subscriptionDetailQuery';
6
- export * from './processDetailQuery';
7
6
  export * from './subscriptionsDropdownOptionsQuery';
8
7
  export * from './processStepsQuery';
9
8
  export * from './processListQuery';
@@ -1,4 +1,4 @@
1
- import { useQuery } from 'react-query';
1
+ import { UseQueryOptions, useQuery } from 'react-query';
2
2
 
3
3
  import { Variables } from 'graphql-request/build/cjs/types';
4
4
  import { signOut } from 'next-auth/react';
@@ -9,6 +9,7 @@ export const useQueryWithFetch = <T, V extends Variables>(
9
9
  url: string,
10
10
  queryVars: V,
11
11
  queryKey: string,
12
+ options?: UseQueryOptions<T, unknown, T, [string, ...unknown[]]>,
12
13
  ) => {
13
14
  const { session } = useWfoSession();
14
15
  const requestHeaders = {
@@ -29,5 +30,9 @@ export const useQueryWithFetch = <T, V extends Variables>(
29
30
  }
30
31
  return (await response.json()) as T;
31
32
  };
32
- return useQuery([queryKey, ...Object.values(queryVars)], fetchData);
33
+ return useQuery(
34
+ [queryKey, ...Object.values(queryVars)],
35
+ fetchData,
36
+ options,
37
+ );
33
38
  };
@@ -5,6 +5,21 @@ import { useTranslations } from 'next-intl';
5
5
  import { EuiBadgeGroup } from '@elastic/eui';
6
6
  import type { Pagination } from '@elastic/eui/src/components';
7
7
 
8
+ import {
9
+ useDataDisplayParams,
10
+ useQueryWithGraphql,
11
+ useQueryWithGraphqlLazy,
12
+ useShowToastMessage,
13
+ useStoredTableConfig,
14
+ } from '@/hooks';
15
+ import type { GraphqlQueryVariables, WorkflowDefinition } from '@/types';
16
+ import { BadgeType, SortOrder } from '@/types';
17
+ import {
18
+ getQueryVariablesForExport,
19
+ onlyUnique,
20
+ parseDateToLocaleDateTimeString,
21
+ parseIsoString,
22
+ } from '@/utils';
8
23
  import {
9
24
  csvDownloadHandler,
10
25
  getCsvFileNameWithDate,
@@ -28,20 +43,6 @@ import { WfoWorkflowTargetBadge } from '../../components/WfoBadges/WfoWorkflowTa
28
43
  import { WfoDateTime } from '../../components/WfoDateTime/WfoDateTime';
29
44
  import { mapSortableAndFilterableValuesToTableColumnConfig } from '../../components/WfoTable/utils/mapSortableAndFilterableValuesToTableColumnConfig';
30
45
  import { GET_WORKFLOWS_GRAPHQL_QUERY } from '../../graphqlQueries/workflows/workflowsQuery';
31
- import {
32
- useDataDisplayParams,
33
- useQueryWithGraphql,
34
- useQueryWithGraphqlLazy,
35
- useShowToastMessage,
36
- useStoredTableConfig,
37
- } from '../../hooks';
38
- import type { GraphqlQueryVariables, WorkflowDefinition } from '../../types';
39
- import { BadgeType, SortOrder } from '../../types';
40
- import {
41
- getQueryVariablesForExport,
42
- parseDateToLocaleDateTimeString,
43
- parseIsoString,
44
- } from '../../utils';
45
46
  import { WfoMetadataPageLayout } from './WfoMetadataPageLayout';
46
47
  import {
47
48
  graphQlWorkflowListMapper,
@@ -115,26 +116,30 @@ export const WfoWorkflowsPage = () => {
115
116
  name: t('productTags'),
116
117
  render: (productTags) => (
117
118
  <>
118
- {productTags?.map((productTag, index) => (
119
- <WfoProductBlockBadge
120
- key={index}
121
- badgeType={BadgeType.PRODUCT_TAG}
122
- >
123
- {productTag}
124
- </WfoProductBlockBadge>
125
- ))}
119
+ {productTags
120
+ ?.filter(onlyUnique)
121
+ .map((productTag, index) => (
122
+ <WfoProductBlockBadge
123
+ key={index}
124
+ badgeType={BadgeType.PRODUCT_TAG}
125
+ >
126
+ {productTag}
127
+ </WfoProductBlockBadge>
128
+ ))}
126
129
  </>
127
130
  ),
128
131
  renderDetails: (productTags) => (
129
132
  <EuiBadgeGroup gutterSize="s">
130
- {productTags?.map((productTag, index) => (
131
- <WfoProductBlockBadge
132
- key={index}
133
- badgeType={BadgeType.PRODUCT_TAG}
134
- >
135
- {productTag}
136
- </WfoProductBlockBadge>
137
- ))}
133
+ {productTags
134
+ ?.filter(onlyUnique)
135
+ .map((productTag, index) => (
136
+ <WfoProductBlockBadge
137
+ key={index}
138
+ badgeType={BadgeType.PRODUCT_TAG}
139
+ >
140
+ {productTag}
141
+ </WfoProductBlockBadge>
142
+ ))}
138
143
  </EuiBadgeGroup>
139
144
  ),
140
145
  },
@@ -1,13 +1,12 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
2
 
3
3
  import { TimelineItem, WfoError, WfoLoading } from '@/components';
4
+ import { useGetProcessDetailQuery } from '@/rtk/endpoints/processDetail';
4
5
 
5
6
  import {
6
7
  WfoStepListRef,
7
8
  WfoWorkflowStepList,
8
9
  } from '../../components/WfoWorkflowSteps';
9
- import { GET_PROCESS_DETAIL_GRAPHQL_QUERY } from '../../graphqlQueries';
10
- import { useQueryWithGraphql } from '../../hooks';
11
10
  import {
12
11
  ProcessDetail,
13
12
  ProcessDoneStatuses,
@@ -39,13 +38,10 @@ export const WfoProcessDetailPage = ({
39
38
  const stepListRef = useRef<WfoStepListRef>(null);
40
39
  const [fetchInterval, setFetchInterval] = useState<number | undefined>();
41
40
  const [process, setProcess] = useState<ProcessDetail | undefined>();
42
- const { data, isLoading, isError } = useQueryWithGraphql(
43
- GET_PROCESS_DETAIL_GRAPHQL_QUERY,
44
- {
45
- processId,
46
- },
47
- 'processDetail',
48
- { refetchInterval: fetchInterval, refetchOnWindowFocus: false },
41
+
42
+ const { data, isLoading, isError } = useGetProcessDetailQuery(
43
+ { processId },
44
+ { pollingInterval: fetchInterval },
49
45
  );
50
46
 
51
47
  if (isError) {
@@ -55,7 +51,7 @@ export const WfoProcessDetailPage = ({
55
51
  }
56
52
 
57
53
  useEffect(() => {
58
- const process = data?.processes.page[0];
54
+ const process = data?.processes[0];
59
55
  // We need to cast here because the backend might return the string in upperCase and
60
56
  // toLowerCase() will converts the value type to string
61
57
  const lastStatus =
@@ -71,7 +67,7 @@ export const WfoProcessDetailPage = ({
71
67
  }, [data, processDetailRefetchInterval]);
72
68
 
73
69
  useEffect(() => {
74
- const fetchedProcessDetails = data?.processes.page[0];
70
+ const fetchedProcessDetails = data?.processes[0];
75
71
 
76
72
  if (!process) {
77
73
  setProcess(fetchedProcessDetails);
@@ -107,7 +107,7 @@ export const WfoStartPage = () => {
107
107
  ) ?? [],
108
108
  button: {
109
109
  name: t('outOfSyncSubscriptions.buttonText'),
110
- url: `${PATH_SUBSCRIPTIONS}?activeTab=ALL&sortBy=field-startDate_order-ASC&queryString=status%3A%28initial%7Cactive%29+insync%3Afalse`,
110
+ url: `${PATH_SUBSCRIPTIONS}?activeTab=ALL&sortBy=field-startDate_order-ASC&queryString=status%3A%28provisioning%7Cactive%29+insync%3Afalse`,
111
111
  },
112
112
  isLoading: outOfSyncsubscriptionsSummaryIsFetching,
113
113
  };
@@ -0,0 +1,66 @@
1
+ import { orchestratorApi } from '@/rtk';
2
+ import { ProcessDetail, ProcessesDetailResult } from '@/types';
3
+
4
+ export const processDetailQuery = `query ProcessDetail($processId: String!) {
5
+ processes(filterBy: { value: $processId, field: "processId" }) {
6
+ page {
7
+ processId
8
+ lastStatus
9
+ createdBy
10
+ startedAt
11
+ lastModifiedAt
12
+ lastStep
13
+ workflowName
14
+ isTask
15
+ form
16
+ steps {
17
+ name
18
+ status
19
+ stepId
20
+ executed
21
+ stateDelta
22
+ }
23
+ customer {
24
+ fullname
25
+ }
26
+ subscriptions {
27
+ page {
28
+ product {
29
+ name
30
+ }
31
+ description
32
+ subscriptionId
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }`;
38
+
39
+ export type ProcessDetailResponse = {
40
+ processes: ProcessDetail[];
41
+ };
42
+
43
+ const processDetailApi = orchestratorApi.injectEndpoints({
44
+ endpoints: (builder) => ({
45
+ getProcessDetail: builder.query<
46
+ ProcessDetailResponse,
47
+ { processId: string }
48
+ >({
49
+ query: (variables) => ({
50
+ document: processDetailQuery,
51
+ variables,
52
+ }),
53
+ transformResponse: (
54
+ response: ProcessesDetailResult,
55
+ ): ProcessDetailResponse => {
56
+ const processes = response.processes.page || [];
57
+
58
+ return {
59
+ processes,
60
+ };
61
+ },
62
+ }),
63
+ }),
64
+ });
65
+
66
+ export const { useGetProcessDetailQuery } = processDetailApi;
@@ -75,7 +75,7 @@ const processApi = orchestratorApi.injectEndpoints({
75
75
  transformResponse: (
76
76
  response: ProcessListResult,
77
77
  ): ProcessListResponse => {
78
- const processes = response?.processes?.page || [];
78
+ const processes = response.processes.page || [];
79
79
 
80
80
  return {
81
81
  processes,
@@ -1 +1,2 @@
1
1
  export * from './types';
2
+ export * from './forms';
@@ -6,3 +6,4 @@ export * from './uuid';
6
6
  export * from './strings';
7
7
  export * from './getProductNamesFromProcess';
8
8
  export * from './getQueryVariablesForExport';
9
+ export * from './onlyUnique';
@@ -0,0 +1,19 @@
1
+ import { onlyUnique } from './onlyUnique';
2
+
3
+ describe('onlyUnique()', () => {
4
+ it('returns array as array with only unique values', () => {
5
+ const test1 = ['SP', 'SP', 'FW', 'SP', 'FW', 'LP'];
6
+ const result = test1?.filter(onlyUnique);
7
+ expect(result).toEqual(['SP', 'FW', 'LP']);
8
+ });
9
+ it('returns array as array with only single value', () => {
10
+ const test2 = ['SP', 'SP', 'SP'];
11
+ const result = test2?.filter(onlyUnique);
12
+ expect(result).toEqual(['SP']);
13
+ });
14
+ it('returns array as empty array', () => {
15
+ const test3 = [''];
16
+ const result = test3?.filter(onlyUnique);
17
+ expect(result).toEqual(['']);
18
+ });
19
+ });
@@ -0,0 +1,3 @@
1
+ export const onlyUnique = (value: string, index: number, array: string[]) => {
2
+ return array.indexOf(value) === index;
3
+ };
@@ -1,4 +1,10 @@
1
- import { camelToHuman, removeSuffix, upperCaseFirstChar } from './strings';
1
+ import {
2
+ camelToHuman,
3
+ removeSuffix,
4
+ snakeToHuman,
5
+ snakeToKebab,
6
+ upperCaseFirstChar,
7
+ } from './strings';
2
8
 
3
9
  describe('upperCaseFirstChar()', () => {
4
10
  it("Doesn't crash on an empty string but returns empty string", () => {
@@ -68,3 +74,33 @@ describe('camelToHuman()', () => {
68
74
  expect(result).toEqual('A Quick Brown Fox');
69
75
  });
70
76
  });
77
+
78
+ describe('snakeToHuman()', () => {
79
+ it('Returns an empty string when input is an empty string', () => {
80
+ const result = snakeToHuman('');
81
+ expect(result).toEqual('');
82
+ });
83
+ it('Returns two words from a single underscore snake case word', () => {
84
+ const result = snakeToHuman('hello_world');
85
+ expect(result).toEqual('hello world');
86
+ });
87
+ it('Returns multiple words from a multiple underscore snake case word', () => {
88
+ const result = snakeToHuman('quick_brown_fox');
89
+ expect(result).toEqual('quick brown fox');
90
+ });
91
+ });
92
+
93
+ describe('snakeToKebab()', () => {
94
+ it('Returns an empty string when input is an empty string', () => {
95
+ const result = snakeToKebab('');
96
+ expect(result).toEqual('');
97
+ });
98
+ it('Returns kebab case word from a single underscore snake case word', () => {
99
+ const result = snakeToKebab('hello_world');
100
+ expect(result).toEqual('hello-world');
101
+ });
102
+ it('Returns kebab case word from a multiple underscore snake case word', () => {
103
+ const result = snakeToKebab('quick_brown_fox');
104
+ expect(result).toEqual('quick-brown-fox');
105
+ });
106
+ });
@@ -13,3 +13,12 @@ export const camelToHuman = (value: string): string => {
13
13
  const result = value.replace(/([A-Z])/g, ' $1').trimStart();
14
14
  return result.charAt(0).toUpperCase() + result.slice(1);
15
15
  };
16
+
17
+ export const snakeToHuman = (value: string): string => {
18
+ const result = value.replace(/_/g, ' ');
19
+ return result.charAt(0) + result.slice(1);
20
+ };
21
+
22
+ export const snakeToKebab = (value: string): string => {
23
+ return value.replace(/_/g, '-');
24
+ };
@@ -1,46 +0,0 @@
1
- import { parse } from 'graphql';
2
- import { gql } from 'graphql-request';
3
-
4
- import { TypedDocumentNode } from '@graphql-typed-document-node/core';
5
-
6
- import { ProcessesDetailResult } from '../types';
7
-
8
- export const GET_PROCESS_DETAIL_GRAPHQL_QUERY: TypedDocumentNode<
9
- ProcessesDetailResult,
10
- { processId: string }
11
- > = parse(gql`
12
- query ProcessDetail($processId: String!) {
13
- processes(filterBy: { value: $processId, field: "processId" }) {
14
- page {
15
- processId
16
- lastStatus
17
- createdBy
18
- startedAt
19
- lastModifiedAt
20
- lastStep
21
- workflowName
22
- isTask
23
- form
24
- steps {
25
- name
26
- status
27
- stepId
28
- executed
29
- stateDelta
30
- }
31
- customer {
32
- fullname
33
- }
34
- subscriptions {
35
- page {
36
- product {
37
- name
38
- }
39
- description
40
- subscriptionId
41
- }
42
- }
43
- }
44
- }
45
- }
46
- `);