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.
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;