foreman_scc_manager 1.8.20 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -10
- data/app/controllers/api/v2/scc_accounts_controller.rb +47 -4
- data/app/controllers/api/v2/scc_products_controller.rb +1 -1
- data/app/controllers/scc_accounts_controller.rb +47 -0
- data/app/lib/actions/scc_manager/subscribe_product.rb +51 -15
- data/app/models/scc_account.rb +1 -1
- data/app/views/scc_accounts/show.html.erb +7 -51
- data/config/routes.rb +1 -0
- data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +5 -0
- data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
- data/lib/foreman_scc_manager/engine.rb +1 -1
- data/lib/foreman_scc_manager/version.rb +1 -1
- data/lib/tasks/republish_repositories.rake +13 -0
- data/package.json +54 -0
- data/test/controllers/scc_accounts_controller_test.rb +6 -0
- data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
- data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
- data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
- data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
- data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
- data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
- data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
- data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
- data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/index.js +18 -0
- data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
- data/webpack/index.js +11 -0
- data/webpack/reducer.js +7 -0
- metadata +40 -14
@@ -0,0 +1,235 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { TimesIcon } from '@patternfly/react-icons';
|
4
|
+
import {
|
5
|
+
Button,
|
6
|
+
Card,
|
7
|
+
CardTitle,
|
8
|
+
CardBody,
|
9
|
+
CardHeader,
|
10
|
+
CardExpandableContent,
|
11
|
+
Flex,
|
12
|
+
FlexItem,
|
13
|
+
} from '@patternfly/react-core';
|
14
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
15
|
+
import { uniq } from 'lodash';
|
16
|
+
import './styles.scss';
|
17
|
+
import SCCGenericPicker from './components/SCCGenericPicker';
|
18
|
+
import SCCTreePicker from './components/SCCTreePicker';
|
19
|
+
|
20
|
+
const resetSelectionStringProduct = __(' -- Select Product --');
|
21
|
+
const resetSelectionStringVersion = __(' -- Select Version --');
|
22
|
+
const resetSelectionStringArch = __(' -- Select Architecture --');
|
23
|
+
|
24
|
+
const genericFilter = (object, comparator) =>
|
25
|
+
// we can have architectures that are not set
|
26
|
+
comparator === '' ||
|
27
|
+
comparator === resetSelectionStringProduct ||
|
28
|
+
comparator === resetSelectionStringVersion ||
|
29
|
+
comparator === resetSelectionStringArch ||
|
30
|
+
object === comparator ||
|
31
|
+
(object === null && comparator === 'no arch');
|
32
|
+
|
33
|
+
const filterVersionByProduct = (sccProducts, product) =>
|
34
|
+
uniq(
|
35
|
+
sccProducts
|
36
|
+
.filter((p) => p.product_category === product)
|
37
|
+
.map((i) => i.version)
|
38
|
+
).sort();
|
39
|
+
|
40
|
+
const filterArchByVersionAndProduct = (sccProducts, product, version) =>
|
41
|
+
uniq(
|
42
|
+
sccProducts
|
43
|
+
.filter((p) => p.product_category === product && p.version === version)
|
44
|
+
.map((i) => i.arch)
|
45
|
+
).sort();
|
46
|
+
|
47
|
+
const SCCProductPicker = ({
|
48
|
+
sccProducts,
|
49
|
+
sccAccountId,
|
50
|
+
editProductId,
|
51
|
+
handleSubscribeCallback,
|
52
|
+
}) => {
|
53
|
+
const [productItems, setProductItems] = useState(
|
54
|
+
uniq(sccProducts.map((p) => p.product_category))
|
55
|
+
);
|
56
|
+
const [selectedProduct, setSelectedProduct] = useState('');
|
57
|
+
const [archItems, setArchItems] = useState([]);
|
58
|
+
const [selectedArch, setSelectedArch] = useState('');
|
59
|
+
const [versionItems, setVersionItems] = useState([]);
|
60
|
+
const [selectedVersion, setSelectedVersion] = useState('');
|
61
|
+
const [filteredSccProducts, setFilteredSccProducts] = useState([]);
|
62
|
+
const [showSearchTree, setShowSearchTree] = useState(false);
|
63
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
64
|
+
|
65
|
+
useEffect(() => {
|
66
|
+
if (editProductId !== 0) {
|
67
|
+
// the id is unique, so there never should be more than 1 element in the array
|
68
|
+
const product = sccProducts.filter((p) => p.id === editProductId);
|
69
|
+
if (product.length > 0) {
|
70
|
+
setSelectedProduct(product[0].product_category);
|
71
|
+
setSelectedArch(product[0].arch);
|
72
|
+
setSelectedVersion(product[0].version);
|
73
|
+
setFilteredSccProducts(product);
|
74
|
+
setShowSearchTree(true);
|
75
|
+
setIsExpanded(true);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}, [editProductId, sccProducts]);
|
79
|
+
|
80
|
+
const onProductSelectionChange = (value) => {
|
81
|
+
if (value !== resetSelectionStringProduct) {
|
82
|
+
setVersionItems(filterVersionByProduct(sccProducts, value));
|
83
|
+
} else {
|
84
|
+
setVersionItems([]);
|
85
|
+
}
|
86
|
+
setSelectedProduct(value);
|
87
|
+
setArchItems([]);
|
88
|
+
setSelectedVersion('');
|
89
|
+
setSelectedArch('');
|
90
|
+
};
|
91
|
+
|
92
|
+
const onVersionSelectionChange = (value) => {
|
93
|
+
if (value === resetSelectionStringVersion) {
|
94
|
+
setArchItems([]);
|
95
|
+
} else {
|
96
|
+
setArchItems(
|
97
|
+
filterArchByVersionAndProduct(sccProducts, selectedProduct, value)
|
98
|
+
);
|
99
|
+
}
|
100
|
+
setSelectedVersion(value);
|
101
|
+
setSelectedArch('');
|
102
|
+
};
|
103
|
+
|
104
|
+
const onArchSelectionChange = (value) => {
|
105
|
+
setSelectedArch(value);
|
106
|
+
};
|
107
|
+
|
108
|
+
const onExpand = (evt, id) => {
|
109
|
+
setIsExpanded(!isExpanded);
|
110
|
+
};
|
111
|
+
|
112
|
+
const filterProducts = (evt) => {
|
113
|
+
setShowSearchTree(true);
|
114
|
+
setFilteredSccProducts(
|
115
|
+
sccProducts.filter(
|
116
|
+
(p) =>
|
117
|
+
genericFilter(p.product_category, selectedProduct) &&
|
118
|
+
genericFilter(p.arch, selectedArch) &&
|
119
|
+
genericFilter(p.version, selectedVersion)
|
120
|
+
)
|
121
|
+
);
|
122
|
+
};
|
123
|
+
|
124
|
+
const resetTreeForm = () => {
|
125
|
+
setShowSearchTree(false);
|
126
|
+
setSelectedProduct('');
|
127
|
+
setSelectedVersion('');
|
128
|
+
setSelectedArch('');
|
129
|
+
};
|
130
|
+
|
131
|
+
return (
|
132
|
+
<Card border="dark" id="product-selection-card" isExpanded={isExpanded}>
|
133
|
+
<CardHeader onExpand={onExpand}>
|
134
|
+
<CardTitle>{__('Select SUSE products')}</CardTitle>
|
135
|
+
</CardHeader>
|
136
|
+
<CardExpandableContent>
|
137
|
+
<CardBody>
|
138
|
+
<Flex direction={{ default: 'column' }}>
|
139
|
+
<Flex>
|
140
|
+
<FlexItem>
|
141
|
+
<SCCGenericPicker
|
142
|
+
key="prod-select"
|
143
|
+
selectionItems={
|
144
|
+
selectedProduct === ''
|
145
|
+
? productItems
|
146
|
+
: [resetSelectionStringProduct].concat(productItems)
|
147
|
+
}
|
148
|
+
setGlobalSelected={onProductSelectionChange}
|
149
|
+
screenReaderLabel={resetSelectionStringProduct}
|
150
|
+
initialLabel={
|
151
|
+
selectedProduct === ''
|
152
|
+
? resetSelectionStringProduct
|
153
|
+
: selectedProduct
|
154
|
+
}
|
155
|
+
/>
|
156
|
+
</FlexItem>
|
157
|
+
<FlexItem>
|
158
|
+
<SCCGenericPicker
|
159
|
+
key="vers-select"
|
160
|
+
selectionItems={
|
161
|
+
selectedVersion === ''
|
162
|
+
? versionItems
|
163
|
+
: [resetSelectionStringVersion].concat(versionItems)
|
164
|
+
}
|
165
|
+
setGlobalSelected={onVersionSelectionChange}
|
166
|
+
screenReaderLabel={resetSelectionStringVersion}
|
167
|
+
initialLabel={
|
168
|
+
selectedVersion === ''
|
169
|
+
? resetSelectionStringVersion
|
170
|
+
: selectedVersion
|
171
|
+
}
|
172
|
+
/>
|
173
|
+
</FlexItem>
|
174
|
+
<FlexItem>
|
175
|
+
<SCCGenericPicker
|
176
|
+
key="arch-select"
|
177
|
+
selectionItems={
|
178
|
+
selectedArch === ''
|
179
|
+
? archItems
|
180
|
+
: [resetSelectionStringArch].concat(archItems)
|
181
|
+
}
|
182
|
+
setGlobalSelected={onArchSelectionChange}
|
183
|
+
screenReaderLabel={resetSelectionStringArch}
|
184
|
+
initialLabel={
|
185
|
+
selectedArch === ''
|
186
|
+
? resetSelectionStringArch
|
187
|
+
: selectedArch
|
188
|
+
}
|
189
|
+
/>
|
190
|
+
</FlexItem>
|
191
|
+
<FlexItem>
|
192
|
+
<Button variant="primary" onClick={filterProducts}>
|
193
|
+
{__('Search')}
|
194
|
+
</Button>
|
195
|
+
</FlexItem>
|
196
|
+
<FlexItem>
|
197
|
+
<Button
|
198
|
+
variant="link"
|
199
|
+
icon={<TimesIcon />}
|
200
|
+
onClick={resetTreeForm}
|
201
|
+
>
|
202
|
+
{__('Reset Selection')}
|
203
|
+
</Button>
|
204
|
+
</FlexItem>
|
205
|
+
</Flex>
|
206
|
+
<FlexItem>
|
207
|
+
{showSearchTree && (
|
208
|
+
<SCCTreePicker
|
209
|
+
sccProducts={filteredSccProducts}
|
210
|
+
sccAccountId={sccAccountId}
|
211
|
+
resetFormFromParent={resetTreeForm}
|
212
|
+
handleSubscribeCallback={handleSubscribeCallback}
|
213
|
+
/>
|
214
|
+
)}
|
215
|
+
</FlexItem>
|
216
|
+
</Flex>
|
217
|
+
</CardBody>
|
218
|
+
</CardExpandableContent>
|
219
|
+
</Card>
|
220
|
+
);
|
221
|
+
};
|
222
|
+
|
223
|
+
SCCProductPicker.propTypes = {
|
224
|
+
sccProducts: PropTypes.array,
|
225
|
+
sccAccountId: PropTypes.number.isRequired,
|
226
|
+
editProductId: PropTypes.number,
|
227
|
+
handleSubscribeCallback: PropTypes.func.isRequired,
|
228
|
+
};
|
229
|
+
|
230
|
+
SCCProductPicker.defaultProps = {
|
231
|
+
sccProducts: [],
|
232
|
+
editProductId: 0,
|
233
|
+
};
|
234
|
+
|
235
|
+
export default SCCProductPicker;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
@import '~@theforeman/vendor/scss/variables';
|
2
|
+
|
3
|
+
.pf-c-select__toggle {
|
4
|
+
font-size: var(--pf-global--FontSize--xs);
|
5
|
+
font-weight: var(--pf-global--FontWeight--bold);
|
6
|
+
};
|
7
|
+
|
8
|
+
.pf-c-switch__input ~ .pf-c-switch__label {
|
9
|
+
font-size: var(--pf-global--FontSize--xs);
|
10
|
+
};
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import ForemanModal from 'foremanReact/components/ForemanModal';
|
4
|
+
|
5
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
6
|
+
import {
|
7
|
+
Card,
|
8
|
+
CardBody,
|
9
|
+
TextContent,
|
10
|
+
Text,
|
11
|
+
TextList,
|
12
|
+
TextListItem,
|
13
|
+
TextListVariants,
|
14
|
+
TextVariants,
|
15
|
+
} from '@patternfly/react-core';
|
16
|
+
|
17
|
+
const generateRepoList = (repoNames, productName) => (
|
18
|
+
<>
|
19
|
+
<TextListItem key={productName}>{productName}</TextListItem>
|
20
|
+
<TextList key={repoNames}>
|
21
|
+
{repoNames.map((repo) => (
|
22
|
+
<TextListItem key={repo}>{repo}</TextListItem>
|
23
|
+
))}
|
24
|
+
</TextList>
|
25
|
+
</>
|
26
|
+
);
|
27
|
+
|
28
|
+
const generateProductList = (reposToSubscribe) => (
|
29
|
+
<TextList
|
30
|
+
key={reposToSubscribe.map((p) => p.productName)}
|
31
|
+
component={TextListVariants.ol}
|
32
|
+
>
|
33
|
+
{reposToSubscribe.map((p) => generateRepoList(p.repoNames, p.productName))}
|
34
|
+
</TextList>
|
35
|
+
);
|
36
|
+
|
37
|
+
const SCCProductPickerModal = ({ id, taskId, reposToSubscribe }) => (
|
38
|
+
<>
|
39
|
+
<ForemanModal
|
40
|
+
id={id}
|
41
|
+
title={__('Summary of SCC product subscription')}
|
42
|
+
enforceFocus
|
43
|
+
>
|
44
|
+
<Card>
|
45
|
+
<CardBody>
|
46
|
+
<TextContent key={taskId}>
|
47
|
+
<Text key={'modal1'.concat(taskId)}>
|
48
|
+
{__('The subscription task with id ')}
|
49
|
+
<Text
|
50
|
+
key={'modal2'.concat(taskId)}
|
51
|
+
component={TextVariants.a}
|
52
|
+
target="_blank"
|
53
|
+
href={sprintf('/foreman_tasks/tasks/%s', taskId)}
|
54
|
+
>
|
55
|
+
{sprintf('%s', taskId)}
|
56
|
+
</Text>
|
57
|
+
{__(' has started successfully.')}
|
58
|
+
</Text>
|
59
|
+
<Text key={'modal3'.concat(taskId)}>
|
60
|
+
{__('The following products will be imported:')}
|
61
|
+
</Text>
|
62
|
+
{generateProductList(reposToSubscribe)}
|
63
|
+
</TextContent>
|
64
|
+
</CardBody>
|
65
|
+
</Card>
|
66
|
+
</ForemanModal>
|
67
|
+
</>
|
68
|
+
);
|
69
|
+
SCCProductPickerModal.propTypes = {
|
70
|
+
id: PropTypes.string,
|
71
|
+
taskId: PropTypes.string,
|
72
|
+
reposToSubscribe: PropTypes.array,
|
73
|
+
};
|
74
|
+
|
75
|
+
SCCProductPickerModal.defaultProps = {
|
76
|
+
id: '',
|
77
|
+
taskId: '',
|
78
|
+
reposToSubscribe: {},
|
79
|
+
};
|
80
|
+
|
81
|
+
export default SCCProductPickerModal;
|
data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
2
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
3
|
+
|
4
|
+
import PropTypes from 'prop-types';
|
5
|
+
import React, { useState } from 'react';
|
6
|
+
import {
|
7
|
+
Dropdown,
|
8
|
+
DropdownItem,
|
9
|
+
BadgeToggle,
|
10
|
+
Tooltip,
|
11
|
+
} from '@patternfly/react-core';
|
12
|
+
import { Icon } from 'patternfly-react';
|
13
|
+
import { BrowserRouter, Link } from 'react-router-dom';
|
14
|
+
|
15
|
+
import './styles.scss';
|
16
|
+
|
17
|
+
const createKatelloRepoLink = (repo, sccProductId) => {
|
18
|
+
const url = foremanUrl(
|
19
|
+
`/products/${sccProductId}/repositories/${repo.katello_root_repository_id}`
|
20
|
+
);
|
21
|
+
return (
|
22
|
+
<Tooltip content={__('Go to Repository page')}>
|
23
|
+
<BrowserRouter>
|
24
|
+
<Link to={url} target="_blank">
|
25
|
+
{repo.name}
|
26
|
+
</Link>
|
27
|
+
</BrowserRouter>
|
28
|
+
</Tooltip>
|
29
|
+
);
|
30
|
+
};
|
31
|
+
|
32
|
+
const createRepoDropDownItem = (repo, sccProductId) => (
|
33
|
+
<DropdownItem
|
34
|
+
key={repo.id}
|
35
|
+
component="button"
|
36
|
+
icon={
|
37
|
+
repo.subscription_valid ? (
|
38
|
+
repo.katello_root_repository_id !== null ? (
|
39
|
+
<Icon name="check" type="fa" />
|
40
|
+
) : (
|
41
|
+
<Tooltip content={__('Repository not imported')}>
|
42
|
+
<Icon name="ban" type="fa" />
|
43
|
+
</Tooltip>
|
44
|
+
)
|
45
|
+
) : (
|
46
|
+
<Tooltip content={__('Please check your SUSE subscription')}>
|
47
|
+
<Icon name="warning-triangle-o" type="pf" size="2x" />
|
48
|
+
</Tooltip>
|
49
|
+
)
|
50
|
+
}
|
51
|
+
>
|
52
|
+
{repo.katello_root_repository_id !== null
|
53
|
+
? createKatelloRepoLink(repo, sccProductId)
|
54
|
+
: repo.name}
|
55
|
+
</DropdownItem>
|
56
|
+
);
|
57
|
+
|
58
|
+
const SCCRepoView = ({ sccRepos, sccProductId }) => {
|
59
|
+
const [isOpen, setIsOpen] = useState(false);
|
60
|
+
const onToggle = (toggle) => {
|
61
|
+
setIsOpen(toggle);
|
62
|
+
};
|
63
|
+
|
64
|
+
const onFocus = () => {
|
65
|
+
const element = document.getElementById(
|
66
|
+
sprintf('scc-repo-show-toggle-id-%s', sccProductId)
|
67
|
+
);
|
68
|
+
element.focus();
|
69
|
+
};
|
70
|
+
|
71
|
+
const onSelect = (event) => {
|
72
|
+
setIsOpen(!isOpen);
|
73
|
+
onFocus();
|
74
|
+
};
|
75
|
+
|
76
|
+
const dropdownItems = sccRepos.map((repo) =>
|
77
|
+
createRepoDropDownItem(repo, sccProductId)
|
78
|
+
);
|
79
|
+
|
80
|
+
return (
|
81
|
+
<Dropdown
|
82
|
+
onSelect={onSelect}
|
83
|
+
toggle={
|
84
|
+
<BadgeToggle
|
85
|
+
id={sprintf('scc-repo-show-toggle-id-%s', sccProductId)}
|
86
|
+
key={sprintf('scc-repo-show-toggle-id-%s', sccProductId)}
|
87
|
+
onToggle={onToggle}
|
88
|
+
>
|
89
|
+
{sprintf(
|
90
|
+
__('Repositories (%s/%s)'),
|
91
|
+
sccRepos.filter((r) => r.katello_root_repository_id !== null)
|
92
|
+
.length,
|
93
|
+
sccRepos.length
|
94
|
+
)}
|
95
|
+
</BadgeToggle>
|
96
|
+
}
|
97
|
+
isOpen={isOpen}
|
98
|
+
dropdownItems={dropdownItems}
|
99
|
+
/>
|
100
|
+
);
|
101
|
+
};
|
102
|
+
|
103
|
+
SCCRepoView.propTypes = {
|
104
|
+
sccRepos: PropTypes.array,
|
105
|
+
sccProductId: PropTypes.number,
|
106
|
+
};
|
107
|
+
|
108
|
+
SCCRepoView.defaultProps = {
|
109
|
+
sccRepos: undefined,
|
110
|
+
sccProductId: undefined,
|
111
|
+
};
|
112
|
+
|
113
|
+
export default SCCRepoView;
|
data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
.pf-c-button.pf-m-plain {
|
2
|
+
--pf-c-button--PaddingTop: var(--pf-global--spacer--xs);
|
3
|
+
--pf-c-button--PaddingRight: var(--pf-global--spacer--sm);
|
4
|
+
--pf-c-button--PaddingBottom: var(--pf-global--spacer--xs);
|
5
|
+
--pf-c-button--PaddingLeft: var(--pf-global--spacer--sm);
|
6
|
+
}
|
7
|
+
|
8
|
+
.pf-c-dropdown__toggle::before {
|
9
|
+
--pf-c-button--PaddingTop: var(--pf-global--spacer--xs);
|
10
|
+
--pf-c-button--PaddingRight: var(--pf-global--spacer--xs);
|
11
|
+
--pf-c-button--PaddingBottom: var(--pf-global--spacer--xs);
|
12
|
+
--pf-c-button--PaddingLeft: var(--pf-global--spacer--xs);
|
13
|
+
}
|
14
|
+
|
@@ -0,0 +1,236 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { Icon } from 'patternfly-react';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
6
|
+
import {
|
7
|
+
TreeView,
|
8
|
+
Button,
|
9
|
+
Card,
|
10
|
+
CardTitle,
|
11
|
+
CardBody,
|
12
|
+
Tooltip,
|
13
|
+
Flex,
|
14
|
+
FlexItem,
|
15
|
+
} from '@patternfly/react-core';
|
16
|
+
import { BrowserRouter, Link } from 'react-router-dom';
|
17
|
+
import { cloneDeep, filter, clone } from 'lodash';
|
18
|
+
import SCCProductTreeExpander from '../common/SCCProductTreeExpander';
|
19
|
+
import SCCSubscribedProductsExpander from '../common/SCCSubscribedProductsExpander';
|
20
|
+
import SCCRepoView from './components/SCCRepoView';
|
21
|
+
|
22
|
+
const addCheckBoxToTree = (tree) => {
|
23
|
+
const checkProps = {};
|
24
|
+
checkProps.checked = tree.product_id !== null;
|
25
|
+
checkProps.disabled = true;
|
26
|
+
tree.checkProps = checkProps;
|
27
|
+
};
|
28
|
+
|
29
|
+
const addKatelloLinkToTree = (tree) => {
|
30
|
+
const url = foremanUrl(`/products/${tree.product_id}`);
|
31
|
+
// Link component needs to be wrapped in a Router
|
32
|
+
tree.customBadgeContent.push(
|
33
|
+
<BrowserRouter>
|
34
|
+
<Link to={url} target="_blank">
|
35
|
+
<Tooltip content={__('Go to Product page')}>
|
36
|
+
<Button variant="plain" id="tt-ref">
|
37
|
+
<Icon name="external-link" type="fa" />
|
38
|
+
</Button>
|
39
|
+
</Tooltip>
|
40
|
+
</Link>
|
41
|
+
</BrowserRouter>
|
42
|
+
);
|
43
|
+
return tree;
|
44
|
+
};
|
45
|
+
|
46
|
+
const addEditIcon = (tree, editProductTree) => {
|
47
|
+
tree.customBadgeContent.push(
|
48
|
+
<Tooltip content={__('Add more sub products to Product tree')}>
|
49
|
+
<Button
|
50
|
+
variant="plain"
|
51
|
+
id={tree.id.toString()}
|
52
|
+
onClick={(evt) => editProductTree(evt, tree.id)}
|
53
|
+
>
|
54
|
+
<Icon name="edit" type="pf" size="2x" />
|
55
|
+
</Button>
|
56
|
+
</Tooltip>
|
57
|
+
);
|
58
|
+
|
59
|
+
return tree;
|
60
|
+
};
|
61
|
+
|
62
|
+
const addReposToTree = (tree) => {
|
63
|
+
tree.customBadgeContent.push(
|
64
|
+
<Tooltip content={__('Show currently added repositories')}>
|
65
|
+
<SCCRepoView
|
66
|
+
sccRepos={tree.scc_repositories}
|
67
|
+
sccProductId={tree.product_id}
|
68
|
+
/>
|
69
|
+
</Tooltip>
|
70
|
+
);
|
71
|
+
return tree;
|
72
|
+
};
|
73
|
+
|
74
|
+
const addValidationStatusToTree = (tree) => {
|
75
|
+
tree.customBadgeContent.push(
|
76
|
+
<Tooltip content={__('Please check your SUSE subscription')}>
|
77
|
+
<Button variant="plain">
|
78
|
+
<Icon name="warning-triangle-o" type="pf" size="2x" />
|
79
|
+
</Button>
|
80
|
+
</Tooltip>
|
81
|
+
);
|
82
|
+
return tree;
|
83
|
+
};
|
84
|
+
|
85
|
+
const setupTreeViewListItem = (tree, isRoot, editProductTree) => {
|
86
|
+
tree.key = 'view'.concat(tree.id.toString());
|
87
|
+
tree.customBadgeContent = [];
|
88
|
+
if (!tree.subscription_valid) addValidationStatusToTree(tree);
|
89
|
+
addReposToTree(tree);
|
90
|
+
addCheckBoxToTree(tree);
|
91
|
+
if (tree.product_id !== null) {
|
92
|
+
addKatelloLinkToTree(tree);
|
93
|
+
if (isRoot) {
|
94
|
+
addEditIcon(tree, editProductTree);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
if ('children' in tree) {
|
98
|
+
tree.children = tree.children.map((c) =>
|
99
|
+
setupTreeViewListItem(c, false, editProductTree)
|
100
|
+
);
|
101
|
+
}
|
102
|
+
return tree;
|
103
|
+
};
|
104
|
+
|
105
|
+
const filterDeep = (tree) => {
|
106
|
+
if (tree.product_id !== null) {
|
107
|
+
const filtered = clone(tree);
|
108
|
+
if ('children' in tree) {
|
109
|
+
filtered.children = filter(
|
110
|
+
tree.children,
|
111
|
+
(child) => child.product_id !== null
|
112
|
+
).map(filterDeep);
|
113
|
+
if (filtered.children.length === 0) {
|
114
|
+
delete filtered.children;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
return filtered;
|
118
|
+
}
|
119
|
+
return null;
|
120
|
+
};
|
121
|
+
|
122
|
+
const SCCProductView = ({
|
123
|
+
sccProducts,
|
124
|
+
editProductTreeGlobal,
|
125
|
+
subscriptionTaskId,
|
126
|
+
}) => {
|
127
|
+
const editProductTree = (evt, productId) => {
|
128
|
+
editProductTreeGlobal(productId);
|
129
|
+
};
|
130
|
+
const sccProductsClone = cloneDeep(sccProducts);
|
131
|
+
// wrap actual iterator function into anonymous function to pass extra parameters
|
132
|
+
const [allProducts, setAllProducts] = useState(
|
133
|
+
sccProductsClone.map((tree) =>
|
134
|
+
setupTreeViewListItem(tree, true, editProductTree)
|
135
|
+
)
|
136
|
+
);
|
137
|
+
const [subscribedProducts, setSubscribedProducts] = useState(null);
|
138
|
+
const [showAll, setShowAll] = useState(true);
|
139
|
+
const [expandAll, setExpandAll] = useState();
|
140
|
+
|
141
|
+
const showSubscribed = () => {
|
142
|
+
if (subscribedProducts === null) {
|
143
|
+
const test = allProducts.map(filterDeep);
|
144
|
+
setSubscribedProducts(test);
|
145
|
+
}
|
146
|
+
};
|
147
|
+
|
148
|
+
const setShowAllFromChild = (showAllFromChild) => {
|
149
|
+
if (!showAllFromChild) {
|
150
|
+
showSubscribed();
|
151
|
+
}
|
152
|
+
setShowAll(showAllFromChild);
|
153
|
+
};
|
154
|
+
|
155
|
+
const setExpandAllFromChild = (expandAllFromChild) => {
|
156
|
+
setExpandAll(expandAllFromChild);
|
157
|
+
};
|
158
|
+
|
159
|
+
return (
|
160
|
+
<Card>
|
161
|
+
<CardTitle>{__('Selected SUSE Products')}</CardTitle>
|
162
|
+
{sccProducts.length > 0 && (
|
163
|
+
<CardBody>
|
164
|
+
<Flex>
|
165
|
+
<SCCProductTreeExpander
|
166
|
+
setExpandAllInParent={setExpandAllFromChild}
|
167
|
+
/>
|
168
|
+
<SCCSubscribedProductsExpander
|
169
|
+
setExpandAllInParent={setShowAllFromChild}
|
170
|
+
/>
|
171
|
+
</Flex>
|
172
|
+
<Flex>
|
173
|
+
<TreeView
|
174
|
+
data={showAll ? allProducts : subscribedProducts}
|
175
|
+
allExpanded={expandAll}
|
176
|
+
hasChecks
|
177
|
+
hasBadges
|
178
|
+
hasGuides
|
179
|
+
/>
|
180
|
+
</Flex>
|
181
|
+
</CardBody>
|
182
|
+
)}
|
183
|
+
{sccProducts.length === 0 && (
|
184
|
+
<CardBody>
|
185
|
+
{__(
|
186
|
+
'You currently have no SUSE products selected. Search and add SUSE products in the section below.'
|
187
|
+
)}
|
188
|
+
</CardBody>
|
189
|
+
)}
|
190
|
+
<Flex>
|
191
|
+
{sccProducts.length > 0 && (
|
192
|
+
<FlexItem>
|
193
|
+
<Button variant="link">
|
194
|
+
<BrowserRouter>
|
195
|
+
<Link
|
196
|
+
to="/foreman_tasks/tasks/?search=Subscribe+SCC+Product"
|
197
|
+
target="_blank"
|
198
|
+
>
|
199
|
+
{__('Show all subscription tasks')}
|
200
|
+
</Link>
|
201
|
+
</BrowserRouter>
|
202
|
+
</Button>
|
203
|
+
</FlexItem>
|
204
|
+
)}
|
205
|
+
{subscriptionTaskId !== '' && (
|
206
|
+
<FlexItem>
|
207
|
+
<Button variant="link">
|
208
|
+
<BrowserRouter>
|
209
|
+
<Link
|
210
|
+
to={sprintf('/foreman_tasks/tasks/%s', subscriptionTaskId)}
|
211
|
+
target="_blank"
|
212
|
+
>
|
213
|
+
{__('Show last product subscription task')}
|
214
|
+
</Link>
|
215
|
+
</BrowserRouter>
|
216
|
+
</Button>
|
217
|
+
</FlexItem>
|
218
|
+
)}
|
219
|
+
</Flex>
|
220
|
+
</Card>
|
221
|
+
);
|
222
|
+
};
|
223
|
+
|
224
|
+
SCCProductView.propTypes = {
|
225
|
+
sccProducts: PropTypes.array,
|
226
|
+
editProductTreeGlobal: PropTypes.func,
|
227
|
+
subscriptionTaskId: PropTypes.string,
|
228
|
+
};
|
229
|
+
|
230
|
+
SCCProductView.defaultProps = {
|
231
|
+
sccProducts: undefined,
|
232
|
+
editProductTreeGlobal: undefined,
|
233
|
+
subscriptionTaskId: '',
|
234
|
+
};
|
235
|
+
|
236
|
+
export default SCCProductView;
|