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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -10
  3. data/app/controllers/api/v2/scc_accounts_controller.rb +47 -4
  4. data/app/controllers/api/v2/scc_products_controller.rb +1 -1
  5. data/app/controllers/scc_accounts_controller.rb +47 -0
  6. data/app/lib/actions/scc_manager/subscribe_product.rb +51 -15
  7. data/app/models/scc_account.rb +1 -1
  8. data/app/views/scc_accounts/show.html.erb +7 -51
  9. data/config/routes.rb +1 -0
  10. data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +5 -0
  11. data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
  12. data/lib/foreman_scc_manager/engine.rb +1 -1
  13. data/lib/foreman_scc_manager/version.rb +1 -1
  14. data/lib/tasks/republish_repositories.rake +13 -0
  15. data/package.json +54 -0
  16. data/test/controllers/scc_accounts_controller_test.rb +6 -0
  17. data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
  18. data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
  19. data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
  20. data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
  21. data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
  22. data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
  23. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
  24. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
  25. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
  26. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
  27. data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
  28. data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
  29. data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
  30. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
  31. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
  32. data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
  33. data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
  34. data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
  35. data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
  36. data/webpack/components/SCCProductPage/index.js +18 -0
  37. data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
  38. data/webpack/index.js +11 -0
  39. data/webpack/reducer.js +7 -0
  40. 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;
@@ -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;
@@ -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;