foreman_scc_manager 1.8.20 → 2.0.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.
- 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;
|