@orchestrator-ui/orchestrator-ui-components 8.4.4 → 8.5.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/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +7 -7
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +1890 -562
- package/dist/index.js +3814 -2103
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/components/WfoPydanticForm/WfoPydanticForm.tsx +23 -1
- package/src/components/WfoSearchBar/WfoSearchField.tsx +0 -1
- package/src/components/WfoSearchPage/WfoFilterGroup/WfoFilterGroup.tsx +5 -5
- package/src/components/WfoSearchPage/WfoSearch/WfoSearch.tsx +12 -5
- package/src/components/WfoSearchPage/constants.ts +0 -4
- package/src/components/WfoSearchPage/utils.ts +75 -20
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionExpandableMenuItem.tsx +36 -18
- package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem.tsx +38 -2
- package/src/components/WfoSubscription/utils/index.ts +3 -0
- package/src/components/WfoSubscription/utils/utils.spec.ts +39 -4
- package/src/components/WfoSubscription/utils/utils.ts +10 -4
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoAddGroupAction.tsx +19 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoAddRuleAction.tsx +23 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector.tsx +30 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoExpandingSearchRow.tsx +38 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +84 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +189 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoOperatorSelector.tsx +33 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRangeEditor.tsx +64 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRemoveGroupAction.tsx +21 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRemoveRuleAction.tsx +20 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRule.tsx +16 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRuleGroup.tsx +73 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +323 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoValueEditor.tsx +159 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/index.ts +2 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +146 -0
- package/src/components/WfoTable/WfoTable/WfoDragHandler.tsx +4 -2
- package/src/components/WfoTable/index.ts +2 -6
- package/src/components/WfoTitleWithWebsocketBadge/WfoTitleWithWebsocketBadge.tsx +14 -6
- package/src/components/WfoWorkflowSteps/WfoStep/WfoStep.tsx +1 -0
- package/src/components/WfoWorkflowSteps/WfoStepList/WfoStepList.tsx +3 -0
- package/src/components/WfoWorkflowSteps/WfoWorkflowStepList/WfoWorkflowStepList.tsx +32 -12
- package/src/components/WfoWorkflowUserGuide/WfoPageWithUserGuide.tsx +38 -0
- package/src/components/WfoWorkflowUserGuide/WfoWorkflowGuideExpandablePanel.tsx +151 -0
- package/src/components/WfoWorkflowUserGuide/index.ts +2 -0
- package/src/components/WfoWorkflowUserGuide/styles.ts +87 -0
- package/src/components/index.ts +1 -0
- package/src/configuration/version.ts +1 -1
- package/src/contexts/OrchestratorConfigContext.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/usePathAutoComplete.ts +3 -4
- package/src/hooks/useSearch.ts +20 -15
- package/src/hooks/useSearchPagination.ts +11 -8
- package/src/hooks/useUrlParams.ts +3 -5
- package/src/icons/WfoTrashFilled.tsx +13 -0
- package/src/icons/WfoXMarkSmall.tsx +2 -2
- package/src/icons/index.ts +1 -0
- package/src/messages/en-GB.json +94 -8
- package/src/messages/nl-NL.json +23 -5
- package/src/pages/WfoSearchPocPage.tsx +402 -0
- package/src/pages/index.ts +1 -0
- package/src/pages/processes/WfoProcessDetail.tsx +10 -10
- package/src/pages/processes/WfoProcessDetailPage.tsx +1 -0
- package/src/pages/processes/WfoStartProcessPage.tsx +15 -19
- package/src/rtk/api.ts +1 -0
- package/src/rtk/endpoints/index.ts +1 -0
- package/src/rtk/endpoints/search.ts +45 -10
- package/src/rtk/endpoints/workflowGuides.ts +25 -0
- package/src/types/search.ts +21 -5
- package/src/types/types.ts +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchestrator-ui/orchestrator-ui-components",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Library of UI Components used to display the workflow orchestrator frontend",
|
|
6
6
|
"author": {
|
|
@@ -49,9 +49,10 @@
|
|
|
49
49
|
"next-query-params": "^5.0.0",
|
|
50
50
|
"object-hash": "^3.0.0",
|
|
51
51
|
"prism-themes": "^1.9.0",
|
|
52
|
-
"pydantic-forms": "^3.0.
|
|
52
|
+
"pydantic-forms": "^3.0.1",
|
|
53
53
|
"react-diff-view": "^3.2.0",
|
|
54
54
|
"react-draggable": "^4.4.6",
|
|
55
|
+
"react-querybuilder": "^8.18.0",
|
|
55
56
|
"react-redux": "^9.3.0",
|
|
56
57
|
"react-select": "^5.8.0",
|
|
57
58
|
"scroll-into-view": "^1.16.2",
|
|
@@ -80,9 +81,9 @@
|
|
|
80
81
|
"peerDependencies": {
|
|
81
82
|
"@elastic/eui": "^113.0.0",
|
|
82
83
|
"next": "^15.5.18",
|
|
84
|
+
"next-intl": "^3.26.5",
|
|
83
85
|
"react": "^18.3.1",
|
|
84
|
-
"react-dom": "^18.3.1"
|
|
85
|
-
"next-intl": "^3.26.5"
|
|
86
|
+
"react-dom": "^18.3.1"
|
|
86
87
|
},
|
|
87
88
|
"type": "module",
|
|
88
89
|
"main": "./dist/index.js",
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import React, { useCallback, useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { useRouter } from 'next/router';
|
|
5
6
|
import type { PydanticFormApiProvider } from 'pydantic-forms';
|
|
6
7
|
import { PydanticForm } from 'pydantic-forms';
|
|
7
8
|
|
|
8
9
|
import { PATH_TASKS, PATH_WORKFLOWS } from '@/components';
|
|
9
10
|
import { Footer } from '@/components/WfoPydanticForm/Footer';
|
|
11
|
+
import { useShowToastMessage } from '@/hooks';
|
|
10
12
|
import { useGetPydanticFormsConfig } from '@/hooks/useGetPydanticFormsConfig';
|
|
11
13
|
import { StartWorkflowPayload } from '@/pages/processes/WfoStartProcessPage';
|
|
12
14
|
import { HttpStatus, isFetchBaseQueryError, isRecord } from '@/rtk';
|
|
13
15
|
import { useStartProcessMutation } from '@/rtk/endpoints/forms';
|
|
16
|
+
import { ToastTypes } from '@/types';
|
|
14
17
|
|
|
15
18
|
interface WfoPydanticFormProps {
|
|
16
19
|
processName: string;
|
|
@@ -29,6 +32,8 @@ export const WfoPydanticForm = ({ processName, startProcessPayload, isTask }: Wf
|
|
|
29
32
|
|
|
30
33
|
const [startProcess] = useStartProcessMutation();
|
|
31
34
|
const router = useRouter();
|
|
35
|
+
const t = useTranslations('pydanticForms.userInputForm');
|
|
36
|
+
const { showToastMessage } = useShowToastMessage();
|
|
32
37
|
|
|
33
38
|
const onSuccess = useCallback(
|
|
34
39
|
(_fieldValues: object, req: object) => {
|
|
@@ -64,6 +69,23 @@ export const WfoPydanticForm = ({ processName, startProcessPayload, isTask }: Wf
|
|
|
64
69
|
...error.data,
|
|
65
70
|
status: error.status,
|
|
66
71
|
});
|
|
72
|
+
} else if (error.status === HttpStatus.PreconditionFailed) {
|
|
73
|
+
const detail = typeof error.data.detail === 'string' ? error.data.detail : '';
|
|
74
|
+
showToastMessage(
|
|
75
|
+
ToastTypes.ERROR,
|
|
76
|
+
detail || t('preconditionFailedFallback'),
|
|
77
|
+
t('preconditionFailedTitle'),
|
|
78
|
+
);
|
|
79
|
+
router.replace(isTask ? PATH_TASKS : PATH_WORKFLOWS);
|
|
80
|
+
resolve({
|
|
81
|
+
validation_errors: [
|
|
82
|
+
{
|
|
83
|
+
loc: ['__root__'],
|
|
84
|
+
msg: detail || t('preconditionFailedFallback'),
|
|
85
|
+
type: 'precondition_failed',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
67
89
|
}
|
|
68
90
|
} else if (data) {
|
|
69
91
|
resolve({
|
|
@@ -86,7 +108,7 @@ export const WfoPydanticForm = ({ processName, startProcessPayload, isTask }: Wf
|
|
|
86
108
|
};
|
|
87
109
|
|
|
88
110
|
return pydanticFormProvider;
|
|
89
|
-
}, [startProcess, startProcessPayload]);
|
|
111
|
+
}, [startProcess, startProcessPayload, t, showToastMessage, router, isTask]);
|
|
90
112
|
|
|
91
113
|
const config = useGetPydanticFormsConfig(getPydanticFormProvider, (props) => <Footer {...props} isTask={isTask} />);
|
|
92
114
|
|
|
@@ -39,7 +39,6 @@ export const WfoSearchField = ({ queryString, queryIsValid = true, onUpdateQuery
|
|
|
39
39
|
placeholder={`${t('search')}...`}
|
|
40
40
|
onChange={(event) => setCurrentQuery(event.target.value)}
|
|
41
41
|
onSearch={handleSearch.current}
|
|
42
|
-
onBlur={(event) => handleSearch.current(event.target.value)}
|
|
43
42
|
isInvalid={!queryIsValid}
|
|
44
43
|
fullWidth
|
|
45
44
|
/>
|
|
@@ -17,14 +17,14 @@ import {
|
|
|
17
17
|
import { WfoToolTip } from '@/components';
|
|
18
18
|
import { isCondition } from '@/components/WfoSearchPage/utils';
|
|
19
19
|
import { useOrchestratorTheme } from '@/hooks';
|
|
20
|
-
import { Condition, EntityKind,
|
|
20
|
+
import type { Condition, EntityKind, Filter } from '@/types';
|
|
21
21
|
|
|
22
22
|
import { ConditionRow } from '../WfoConditionRow';
|
|
23
23
|
|
|
24
24
|
interface FilterGroupProps {
|
|
25
|
-
group:
|
|
25
|
+
group: Filter;
|
|
26
26
|
entityType: EntityKind;
|
|
27
|
-
onChange: (group:
|
|
27
|
+
onChange: (group: Filter) => void;
|
|
28
28
|
onRemove?: () => void;
|
|
29
29
|
depth?: number;
|
|
30
30
|
isRoot?: boolean;
|
|
@@ -58,7 +58,7 @@ export const FilterGroup: FC<FilterGroupProps> = ({
|
|
|
58
58
|
const addGroup = () => {
|
|
59
59
|
if (!canAddGroup) return;
|
|
60
60
|
|
|
61
|
-
const newGroup:
|
|
61
|
+
const newGroup: Filter = {
|
|
62
62
|
op: 'AND',
|
|
63
63
|
children: [],
|
|
64
64
|
};
|
|
@@ -68,7 +68,7 @@ export const FilterGroup: FC<FilterGroupProps> = ({
|
|
|
68
68
|
});
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
const updateChild = (index: number, child:
|
|
71
|
+
const updateChild = (index: number, child: Filter | Condition) => {
|
|
72
72
|
const newChildren = [...group.children];
|
|
73
73
|
newChildren[index] = child;
|
|
74
74
|
onChange({
|
|
@@ -29,7 +29,7 @@ import { useDebounce } from '@/hooks/useDebounce';
|
|
|
29
29
|
import { useSearch } from '@/hooks/useSearch';
|
|
30
30
|
import { useSearchPagination } from '@/hooks/useSearchPagination';
|
|
31
31
|
import { useUrlParams } from '@/hooks/useUrlParams';
|
|
32
|
-
import { EntityKind,
|
|
32
|
+
import { EntityKind, Filter, RetrieverType } from '@/types';
|
|
33
33
|
|
|
34
34
|
import { DEFAULT_DEBOUNCE_DELAY, DEFAULT_PAGE_SIZE, LAYOUT_RATIOS, SMALL_RESULT_THRESHOLD } from '../constants';
|
|
35
35
|
|
|
@@ -53,7 +53,7 @@ export const WfoSearch = () => {
|
|
|
53
53
|
|
|
54
54
|
const pageSize = DEFAULT_PAGE_SIZE;
|
|
55
55
|
|
|
56
|
-
const [filterGroup, setFilterGroup] = useState<
|
|
56
|
+
const [filterGroup, setFilterGroup] = useState<Filter>({
|
|
57
57
|
op: 'AND',
|
|
58
58
|
children: [],
|
|
59
59
|
});
|
|
@@ -105,6 +105,11 @@ export const WfoSearch = () => {
|
|
|
105
105
|
});
|
|
106
106
|
setResults({
|
|
107
107
|
data: [],
|
|
108
|
+
cursor: {
|
|
109
|
+
total_items: 0,
|
|
110
|
+
start_cursor: 0,
|
|
111
|
+
end_cursor: 0,
|
|
112
|
+
},
|
|
108
113
|
page_info: {
|
|
109
114
|
has_next_page: false,
|
|
110
115
|
next_page_cursor: null,
|
|
@@ -189,7 +194,7 @@ export const WfoSearch = () => {
|
|
|
189
194
|
|
|
190
195
|
<EuiFieldSearch
|
|
191
196
|
placeholder={t('searchPlaceholder', {
|
|
192
|
-
entityType: currentTab?.label.toLowerCase(),
|
|
197
|
+
entityType: currentTab?.label.toLowerCase() || '',
|
|
193
198
|
})}
|
|
194
199
|
value={searchValue || ''}
|
|
195
200
|
onChange={(event) => {
|
|
@@ -272,7 +277,7 @@ export const WfoSearch = () => {
|
|
|
272
277
|
<EuiCallOut title={t('noResults')} color="primary" iconType="search" size="m">
|
|
273
278
|
<p>
|
|
274
279
|
{t('noResultsMessage', {
|
|
275
|
-
entityType: currentTab?.label.toLowerCase(),
|
|
280
|
+
entityType: currentTab?.label.toLowerCase() || '',
|
|
276
281
|
})}
|
|
277
282
|
</p>
|
|
278
283
|
<EuiSpacer size="s" />
|
|
@@ -304,7 +309,9 @@ export const WfoSearch = () => {
|
|
|
304
309
|
<EuiFlexItem grow={false}>
|
|
305
310
|
<WfoSearchPaginationInfo
|
|
306
311
|
has_next_page={results.page_info.has_next_page}
|
|
307
|
-
next_page_cursor={
|
|
312
|
+
next_page_cursor={
|
|
313
|
+
results.page_info.next_page_cursor ? parseInt(results.page_info.next_page_cursor, 0) : null
|
|
314
|
+
}
|
|
308
315
|
onNextPage={handleNextPage}
|
|
309
316
|
onPrevPage={handlePrevPage}
|
|
310
317
|
isLoading={isLoadingMore}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RuleGroupType } from 'react-querybuilder';
|
|
2
|
+
import { formatQuery } from 'react-querybuilder/formatQuery';
|
|
3
|
+
|
|
4
|
+
import { WfoSubscriptionListTab } from '@/components';
|
|
5
|
+
import { SearchPaginationPayload } from '@/rtk';
|
|
6
|
+
import {
|
|
7
|
+
Condition,
|
|
8
|
+
EntityKind,
|
|
9
|
+
Filter,
|
|
10
|
+
OperatorDisplay,
|
|
11
|
+
PathInfo,
|
|
12
|
+
RetrieverType,
|
|
13
|
+
SearchResult,
|
|
14
|
+
SubscriptionStatus,
|
|
15
|
+
} from '@/types';
|
|
2
16
|
|
|
3
17
|
export function isSubscriptionSearchResult(item: SearchResult): boolean {
|
|
4
18
|
return item.entity_type === 'SUBSCRIPTION';
|
|
@@ -16,7 +30,7 @@ export function isWorkflowSearchResult(item: SearchResult): boolean {
|
|
|
16
30
|
return item.entity_type === 'WORKFLOW';
|
|
17
31
|
}
|
|
18
32
|
|
|
19
|
-
export const isCondition = (item:
|
|
33
|
+
export const isCondition = (item: Filter | Condition): item is Condition => {
|
|
20
34
|
return 'path' in item && 'condition' in item;
|
|
21
35
|
};
|
|
22
36
|
|
|
@@ -37,10 +51,10 @@ export const getDetailUrl = (result: SearchResult, baseUrl: string): string => {
|
|
|
37
51
|
};
|
|
38
52
|
|
|
39
53
|
export const ENTITY_TABS = [
|
|
40
|
-
{ id:
|
|
41
|
-
{ id:
|
|
42
|
-
{ id:
|
|
43
|
-
{ id:
|
|
54
|
+
{ id: EntityKind.SUBSCRIPTION, label: 'Subscriptions' },
|
|
55
|
+
{ id: EntityKind.PRODUCT, label: 'Products' },
|
|
56
|
+
{ id: EntityKind.WORKFLOW, label: 'Workflows' },
|
|
57
|
+
{ id: EntityKind.PROCESS, label: 'Processes' },
|
|
44
58
|
];
|
|
45
59
|
|
|
46
60
|
interface ThemeColors {
|
|
@@ -68,16 +82,6 @@ export const getTypeColor = (type: string, theme: Theme): string => {
|
|
|
68
82
|
return colorKey ? theme.colors[colorKey] : theme.colors.textSubdued;
|
|
69
83
|
};
|
|
70
84
|
|
|
71
|
-
interface PathInfo {
|
|
72
|
-
type?: string;
|
|
73
|
-
[key: string]: unknown;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface OperatorDisplay {
|
|
77
|
-
symbol: string;
|
|
78
|
-
description: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
85
|
const OPERATOR_MAP: Record<string, OperatorDisplay> = {
|
|
82
86
|
eq: { symbol: '=', description: 'equals' },
|
|
83
87
|
neq: { symbol: '≠', description: 'not equals' },
|
|
@@ -88,6 +92,7 @@ const OPERATOR_MAP: Record<string, OperatorDisplay> = {
|
|
|
88
92
|
between: { symbol: '⟷', description: 'between (range)' },
|
|
89
93
|
has_component: { symbol: '✓', description: 'has component' },
|
|
90
94
|
not_has_component: { symbol: '✗', description: 'does not have component' },
|
|
95
|
+
like: { symbol: '∋', description: 'contains' },
|
|
91
96
|
};
|
|
92
97
|
|
|
93
98
|
const BOOLEAN_OPERATOR_MAP: Record<string, OperatorDisplay> = {
|
|
@@ -117,7 +122,7 @@ export const getButtonFill = (op: string, pathInfo: PathInfo | null, condition:
|
|
|
117
122
|
return condition.condition.op === op;
|
|
118
123
|
};
|
|
119
124
|
|
|
120
|
-
export const isFilterValid = (group:
|
|
125
|
+
export const isFilterValid = (group: Filter): boolean => {
|
|
121
126
|
return group.children.every((child) => {
|
|
122
127
|
if (isCondition(child)) {
|
|
123
128
|
return child.path && child.condition.op && child.condition.value !== undefined;
|
|
@@ -133,18 +138,68 @@ interface SearchQuery {
|
|
|
133
138
|
export const buildSearchParams = (
|
|
134
139
|
debouncedQuery: SearchQuery | string,
|
|
135
140
|
selectedEntityTab: EntityKind,
|
|
136
|
-
filterGroup:
|
|
141
|
+
filterGroup: Filter,
|
|
137
142
|
pageSize: number,
|
|
143
|
+
cursor: number,
|
|
138
144
|
retriever?: Exclude<RetrieverType, 'auto'>,
|
|
139
|
-
) => {
|
|
145
|
+
): SearchPaginationPayload => {
|
|
140
146
|
const queryText = typeof debouncedQuery === 'string' ? debouncedQuery : debouncedQuery?.text?.trim() || '';
|
|
141
147
|
|
|
142
148
|
return {
|
|
143
|
-
action: 'select' as const,
|
|
144
149
|
entity_type: selectedEntityTab,
|
|
145
150
|
query: queryText || '',
|
|
146
151
|
filters: filterGroup?.children.length > 0 ? filterGroup : undefined,
|
|
147
152
|
limit: pageSize,
|
|
148
153
|
retriever,
|
|
154
|
+
cursor,
|
|
155
|
+
response_columns: [],
|
|
149
156
|
};
|
|
150
157
|
};
|
|
158
|
+
|
|
159
|
+
const parseRuleGroupToFilters = (ruleGroup?: RuleGroupType) => {
|
|
160
|
+
const elasticQuery =
|
|
161
|
+
ruleGroup ? formatQuery(ruleGroup, { format: 'elasticsearch', fallbackExpression: '' }) : undefined;
|
|
162
|
+
return elasticQuery as unknown as Filter;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const getSubscriptionStatusesFromTab = (tab: WfoSubscriptionListTab) => {
|
|
166
|
+
switch (tab) {
|
|
167
|
+
case WfoSubscriptionListTab.ACTIVE:
|
|
168
|
+
return [SubscriptionStatus.ACTIVE];
|
|
169
|
+
case WfoSubscriptionListTab.TERMINATED:
|
|
170
|
+
return [SubscriptionStatus.TERMINATED];
|
|
171
|
+
case WfoSubscriptionListTab.TRANSIENT:
|
|
172
|
+
return [SubscriptionStatus.INITIAL, SubscriptionStatus.PROVISIONING, SubscriptionStatus.MIGRATING];
|
|
173
|
+
case WfoSubscriptionListTab.ALL:
|
|
174
|
+
return [
|
|
175
|
+
SubscriptionStatus.ACTIVE,
|
|
176
|
+
SubscriptionStatus.TERMINATED,
|
|
177
|
+
SubscriptionStatus.INITIAL,
|
|
178
|
+
SubscriptionStatus.MIGRATING,
|
|
179
|
+
SubscriptionStatus.PROVISIONING,
|
|
180
|
+
];
|
|
181
|
+
default:
|
|
182
|
+
return [SubscriptionStatus.ACTIVE];
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const buildSubscriptionStatusFilter = (tab: WfoSubscriptionListTab) => {
|
|
187
|
+
return {
|
|
188
|
+
combinator: 'or',
|
|
189
|
+
rules: getSubscriptionStatusesFromTab(tab).map((status) => ({
|
|
190
|
+
field: 'subscription.status',
|
|
191
|
+
operator: '=',
|
|
192
|
+
value: status,
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const combineSearchFilters = (ruleGroup: RuleGroupType | false | undefined, tab: WfoSubscriptionListTab) => {
|
|
198
|
+
const userRuleGroup = ruleGroup === false ? undefined : ruleGroup;
|
|
199
|
+
const ruleGroups = [buildSubscriptionStatusFilter(tab), userRuleGroup].filter(Boolean) as RuleGroupType[];
|
|
200
|
+
|
|
201
|
+
const combinedRuleGroup: RuleGroupType =
|
|
202
|
+
ruleGroups.length === 1 ? ruleGroups[0] : { combinator: 'and', rules: ruleGroups };
|
|
203
|
+
|
|
204
|
+
return parseRuleGroupToFilters(combinedRuleGroup);
|
|
205
|
+
};
|
|
@@ -7,13 +7,13 @@ import { EuiButtonIcon, EuiText } from '@elastic/eui';
|
|
|
7
7
|
|
|
8
8
|
import { PATH_SUBSCRIPTIONS } from '@/components';
|
|
9
9
|
import { useWithOrchestratorTheme } from '@/hooks';
|
|
10
|
-
import { SubscriptionAction } from '@/types';
|
|
10
|
+
import { SubscriptionAction, SubscriptionRelation } from '@/types';
|
|
11
11
|
|
|
12
12
|
import { getSubscriptionActionStyles } from './styles';
|
|
13
13
|
|
|
14
14
|
export type WfoSubscriptionActionExpandableMenuItemProps = {
|
|
15
15
|
subscriptionAction: SubscriptionAction;
|
|
16
|
-
onClickLockedRelation: (relation:
|
|
16
|
+
onClickLockedRelation: (relation: SubscriptionRelation) => void;
|
|
17
17
|
children: React.ReactNode;
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -28,16 +28,19 @@ export const WfoSubscriptionActionExpandableMenuItem: FC<WfoSubscriptionActionEx
|
|
|
28
28
|
useWithOrchestratorTheme(getSubscriptionActionStyles);
|
|
29
29
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// TODO: remove lockedRelationsUuids fallback when orchestrator-core 6.0.0 is released and only use the _detail variant
|
|
32
|
+
const lockedRelationsDetail = subscriptionAction.locked_relations_detail;
|
|
33
|
+
const lockedRelationsUuids = subscriptionAction.locked_relations;
|
|
34
|
+
const hasLockedRelations = (lockedRelationsDetail?.length ?? 0) > 0 || (lockedRelationsUuids?.length ?? 0) > 0;
|
|
32
35
|
|
|
33
36
|
return (
|
|
34
37
|
<div>
|
|
35
38
|
<div
|
|
36
|
-
css={[expandableMenuItemStyle,
|
|
39
|
+
css={[expandableMenuItemStyle, hasLockedRelations && clickableStyle]}
|
|
37
40
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
38
41
|
>
|
|
39
42
|
<div>{children}</div>
|
|
40
|
-
{
|
|
43
|
+
{hasLockedRelations && (
|
|
41
44
|
<EuiButtonIcon
|
|
42
45
|
css={expandButtonStyle}
|
|
43
46
|
iconType={isExpanded ? 'arrowDown' : 'arrowRight'}
|
|
@@ -46,21 +49,36 @@ export const WfoSubscriptionActionExpandableMenuItem: FC<WfoSubscriptionActionEx
|
|
|
46
49
|
/>
|
|
47
50
|
)}
|
|
48
51
|
</div>
|
|
49
|
-
{
|
|
52
|
+
{hasLockedRelations && isExpanded && (
|
|
50
53
|
<div css={expandedContentStyle}>
|
|
51
54
|
<EuiText size="xs">{t('lockedBySubscriptions')}</EuiText>
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
<ul css={{ margin: 0, paddingLeft: 16, listStyleType: 'disc' }}>
|
|
56
|
+
{lockedRelationsDetail ?
|
|
57
|
+
lockedRelationsDetail.map((relation) => (
|
|
58
|
+
<li key={relation.subscription_id}>
|
|
59
|
+
<EuiText size="xs">
|
|
60
|
+
<Link
|
|
61
|
+
css={linkStyle}
|
|
62
|
+
href={`${PATH_SUBSCRIPTIONS}/${relation.subscription_id}`}
|
|
63
|
+
target="_blank"
|
|
64
|
+
onClick={() => onClickLockedRelation(relation)}
|
|
65
|
+
>
|
|
66
|
+
{relation.subscription_description}
|
|
67
|
+
</Link>
|
|
68
|
+
</EuiText>
|
|
69
|
+
</li>
|
|
70
|
+
))
|
|
71
|
+
: lockedRelationsUuids?.map((id) => (
|
|
72
|
+
<li key={id}>
|
|
73
|
+
<EuiText size="xs">
|
|
74
|
+
<Link css={linkStyle} href={`${PATH_SUBSCRIPTIONS}/${id}`} target="_blank">
|
|
75
|
+
{id}
|
|
76
|
+
</Link>
|
|
77
|
+
</EuiText>
|
|
78
|
+
</li>
|
|
79
|
+
))
|
|
80
|
+
}
|
|
81
|
+
</ul>
|
|
64
82
|
</div>
|
|
65
83
|
)}
|
|
66
84
|
</div>
|
package/src/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionsMenuItem.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { useTranslations } from 'next-intl';
|
|
|
4
4
|
|
|
5
5
|
import { EuiContextMenuItem, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { flattenSubscriptionActionProps } from '@/components';
|
|
8
8
|
import { WfoSubscriptionActionExpandableMenuItem } from '@/components/WfoSubscription/WfoSubscriptionActions/WfoSubscriptionActionExpandableMenuItem';
|
|
9
9
|
import { getSubscriptionActionStyles } from '@/components/WfoSubscription/WfoSubscriptionActions/styles';
|
|
10
10
|
import { useCheckEngineStatus, useOrchestratorTheme, useWithOrchestratorTheme } from '@/hooks';
|
|
@@ -56,9 +56,45 @@ export const WfoSubscriptionActionsMenuItem: FC<MenuItemProps> = ({
|
|
|
56
56
|
);
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
// TODO: remove UUID-only fallback when orchestrator-core 6.0.0 is released and only use the _detail variants
|
|
60
|
+
const getRelationsList = () => {
|
|
61
|
+
const detailRelations = [
|
|
62
|
+
...(subscriptionAction.locked_relations_detail ?? []),
|
|
63
|
+
...(subscriptionAction.unterminated_in_use_by_subscriptions_detail ?? []),
|
|
64
|
+
];
|
|
65
|
+
if (detailRelations.length > 0) {
|
|
66
|
+
return (
|
|
67
|
+
<ul css={{ margin: 0, paddingLeft: 16, listStyleType: 'disc' }}>
|
|
68
|
+
{detailRelations.map((r) => (
|
|
69
|
+
<li key={r.subscription_id}>{r.subscription_description || r.subscription_id}</li>
|
|
70
|
+
))}
|
|
71
|
+
</ul>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const uuidRelations = [
|
|
75
|
+
...(subscriptionAction.locked_relations ?? []),
|
|
76
|
+
...(subscriptionAction.unterminated_in_use_by_subscriptions ?? []),
|
|
77
|
+
];
|
|
78
|
+
if (uuidRelations.length === 0) return null;
|
|
79
|
+
return (
|
|
80
|
+
<ul css={{ margin: 0, paddingLeft: 16, listStyleType: 'disc' }}>
|
|
81
|
+
{uuidRelations.map((id) => (
|
|
82
|
+
<li key={id}>{id}</li>
|
|
83
|
+
))}
|
|
84
|
+
</ul>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
59
88
|
const tooltipIt = (actionItem: React.ReactNode) => {
|
|
60
89
|
if (!subscriptionActionReason) return actionItem;
|
|
61
|
-
const
|
|
90
|
+
const relationsList = getRelationsList();
|
|
91
|
+
const tooltipContent =
|
|
92
|
+
relationsList ?
|
|
93
|
+
<span css={{ whiteSpace: 'pre-line' }}>
|
|
94
|
+
{t(subscriptionActionReason, flattenSubscriptionActionProps(subscriptionAction))}
|
|
95
|
+
{relationsList}
|
|
96
|
+
</span>
|
|
97
|
+
: t(subscriptionActionReason, flattenSubscriptionActionProps(subscriptionAction));
|
|
62
98
|
|
|
63
99
|
return (
|
|
64
100
|
<div css={tooltipMenuItemStyle}>
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
WorkflowTarget,
|
|
10
10
|
} from '../../../types';
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
flattenSubscriptionActionProps,
|
|
13
13
|
getFieldFromProductBlockInstanceValues,
|
|
14
14
|
getLastUncompletedProcess,
|
|
15
15
|
getLatestTaskDate,
|
|
@@ -61,7 +61,7 @@ describe('getProductBlockTitle()', () => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
describe('
|
|
64
|
+
describe('flattenSubscriptionActionProps', () => {
|
|
65
65
|
it('should flatten an object with array values into a comma-separated string', () => {
|
|
66
66
|
const action: SubscriptionAction = {
|
|
67
67
|
name: 'action name',
|
|
@@ -69,7 +69,7 @@ describe('flattenArrayProps', () => {
|
|
|
69
69
|
usable_when: ['Status1', 'Status2', 'Status3'],
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const result =
|
|
72
|
+
const result = flattenSubscriptionActionProps(action);
|
|
73
73
|
|
|
74
74
|
expect(result).toEqual({
|
|
75
75
|
name: 'action name',
|
|
@@ -78,12 +78,47 @@ describe('flattenArrayProps', () => {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
it('should flatten locked_relations_detail objects using subscription_description', () => {
|
|
82
|
+
const action: SubscriptionAction = {
|
|
83
|
+
name: 'action name',
|
|
84
|
+
description: 'action description',
|
|
85
|
+
locked_relations_detail: [
|
|
86
|
+
{ subscription_id: 'uuid-1', subscription_description: 'Sub A' },
|
|
87
|
+
{ subscription_id: 'uuid-2', subscription_description: 'Sub B' },
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = flattenSubscriptionActionProps(action);
|
|
92
|
+
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
name: 'action name',
|
|
95
|
+
description: 'action description',
|
|
96
|
+
locked_relations_detail: 'Sub A, Sub B',
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should flatten locked_relations UUIDs', () => {
|
|
101
|
+
const action: SubscriptionAction = {
|
|
102
|
+
name: 'action name',
|
|
103
|
+
description: 'action description',
|
|
104
|
+
locked_relations: ['uuid-1', 'uuid-2'],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const result = flattenSubscriptionActionProps(action);
|
|
108
|
+
|
|
109
|
+
expect(result).toEqual({
|
|
110
|
+
name: 'action name',
|
|
111
|
+
description: 'action description',
|
|
112
|
+
locked_relations: 'uuid-1, uuid-2',
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
81
116
|
it('should handle an object with non-array values', () => {
|
|
82
117
|
const action: SubscriptionAction = {
|
|
83
118
|
name: 'action name',
|
|
84
119
|
description: 'action description',
|
|
85
120
|
};
|
|
86
|
-
const result =
|
|
121
|
+
const result = flattenSubscriptionActionProps(action);
|
|
87
122
|
expect(result).toEqual({
|
|
88
123
|
name: 'action name',
|
|
89
124
|
description: 'action description',
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
SortOrder,
|
|
11
11
|
SubscriptionAction,
|
|
12
12
|
SubscriptionDetailProcess,
|
|
13
|
+
SubscriptionRelation,
|
|
13
14
|
WorkflowTarget,
|
|
14
15
|
} from '@/types';
|
|
15
16
|
|
|
@@ -40,16 +41,21 @@ export const getProductBlockTitle = (instanceValues: FieldValue[]): string | num
|
|
|
40
41
|
return title;
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
const toLabel = (item: string | SubscriptionRelation): string =>
|
|
45
|
+
typeof item === 'object' ? (item.subscription_description ?? item.subscription_id) : item;
|
|
46
|
+
|
|
47
|
+
export const flattenSubscriptionActionProps = (action?: SubscriptionAction | null): TranslationValues => {
|
|
48
|
+
if (!action) return {};
|
|
49
|
+
|
|
44
50
|
const flatObject: TranslationValues = {};
|
|
45
51
|
for (const [key, value] of Object.entries(action)) {
|
|
46
52
|
if (Array.isArray(value)) {
|
|
47
|
-
flatObject[key] = value.join(', ');
|
|
48
|
-
} else {
|
|
53
|
+
flatObject[key] = value.map(toLabel).join(', ');
|
|
54
|
+
} else if (typeof value !== 'object' || value === null) {
|
|
49
55
|
flatObject[key] = value;
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
|
-
return
|
|
58
|
+
return flatObject;
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
export const getWorkflowTargetColor = (workflowTarget: WorkflowTarget, theme: EuiThemeComputed) => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ActionProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
|
|
6
|
+
import { EuiFlexItem } from '@elastic/eui';
|
|
7
|
+
|
|
8
|
+
import { getWfoStructuredSearchTableStyles } from '@/components/WfoTable/WfoStructuredSearchTable/styles';
|
|
9
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
10
|
+
|
|
11
|
+
export const WfoAddGroupAction = ({ disabled, handleOnClick }: ActionProps) => {
|
|
12
|
+
const t = useTranslations('search.page');
|
|
13
|
+
const { addGroupStyles, addRulePlusStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
14
|
+
return disabled ? null : (
|
|
15
|
+
<EuiFlexItem css={addGroupStyles} onClick={() => handleOnClick()}>
|
|
16
|
+
<span css={addRulePlusStyles}>+</span> {t('addGroup')}
|
|
17
|
+
</EuiFlexItem>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ActionProps } from 'react-querybuilder';
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
|
|
6
|
+
import { EuiFlexItem } from '@elastic/eui';
|
|
7
|
+
|
|
8
|
+
import { useWithOrchestratorTheme } from '@/hooks';
|
|
9
|
+
|
|
10
|
+
import { getWfoStructuredSearchTableStyles } from './styles';
|
|
11
|
+
|
|
12
|
+
export const WfoAddRuleAction = (props: ActionProps) => {
|
|
13
|
+
const { addRulePlusStyles, addRuleContainerStyles } = useWithOrchestratorTheme(getWfoStructuredSearchTableStyles);
|
|
14
|
+
|
|
15
|
+
const t = useTranslations('search.page');
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<EuiFlexItem grow={false} onClick={() => props.handleOnClick()} css={addRuleContainerStyles}>
|
|
19
|
+
<span css={addRulePlusStyles}>+</span>
|
|
20
|
+
{t('addRule')}
|
|
21
|
+
</EuiFlexItem>
|
|
22
|
+
);
|
|
23
|
+
};
|