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,27 @@
1
+ import { API_OPERATIONS, put } from 'foremanReact/redux/API';
2
+ import { translate as __ } from 'foremanReact/common/I18n';
3
+
4
+ export const subscribeProductsWithReposAction = (
5
+ sccAccountId,
6
+ sccProductData,
7
+ handleSubscription,
8
+ sccSubscriptionData
9
+ ) =>
10
+ put({
11
+ type: API_OPERATIONS.PUT,
12
+ key: `subscribe_key_${sccAccountId}_${sccProductData[0].scc_product_id}_${sccProductData[0].repository_list}`,
13
+ url: `/api/scc_accounts/${sccAccountId}/bulk_subscribe_with_repos`,
14
+ params: { scc_product_data: sccProductData },
15
+ errorToast: (error) => __('Starting the subscription task failed.'),
16
+ handleSuccess: (response) =>
17
+ handleSubscription(response.data.id, sccSubscriptionData),
18
+ });
19
+
20
+ export const syncSccAccountAction = (sccAccountId) =>
21
+ put({
22
+ type: API_OPERATIONS.PUT,
23
+ key: `syncSccAccountKey_${sccAccountId}`,
24
+ url: `/api/scc_accounts/${sccAccountId}/sync`,
25
+ successToast: () => __('Sync task started.'),
26
+ errorToast: (error) => __('Failed to add task to queue.'),
27
+ });
@@ -0,0 +1,5 @@
1
+ export const SCCPRODUCTPAGE_SUMMARY_MODAL_ID = 'SCCTreePickerForemanModal';
2
+ export const SCCPRODUCTPAGE_SUBSCRIBE = 'SCCPRODUCTPAGE_SUBSCRIBE';
3
+ export const SCCPRODUCTPAGE_SCCPRODUCTS = {
4
+ key: 'SCCPRODUCTPAGE_GET_PRODUCTS',
5
+ };
@@ -0,0 +1,34 @@
1
+ import Immutable from 'seamless-immutable';
2
+ import { actionTypeGenerator } from 'foremanReact/redux/API';
3
+
4
+ export const initialState = Immutable({
5
+ sccProducts: [],
6
+ sccAccountId: undefined,
7
+ });
8
+
9
+ export default (state = initialState, action) => {
10
+ const { payload } = action;
11
+
12
+ const listTypes = actionTypeGenerator('SCC_PRODUCT_LIST');
13
+
14
+ switch (action.type) {
15
+ case 'FETCH_PRODUCT_SUCCESS':
16
+ return state.merge({
17
+ sccProducts: payload,
18
+ });
19
+ case listTypes.REQUEST:
20
+ return state.merge({
21
+ sccProducts: [],
22
+ });
23
+ case listTypes.SUCCESS:
24
+ return state.merge({
25
+ sccProducts: payload.data,
26
+ });
27
+ case listTypes.FAILURE:
28
+ return state.merge({
29
+ sccProducts: [payload.error],
30
+ });
31
+ default:
32
+ return state;
33
+ }
34
+ };
@@ -0,0 +1,7 @@
1
+ const SCCProductPageSelector = (state) => state.foremanSccManager;
2
+
3
+ export const selectSCCProducts = (state) =>
4
+ SCCProductPageSelector(state).sccProducts;
5
+
6
+ export const selectSCCAccountId = (state) =>
7
+ SCCProductPageSelector(state).sccAccountId;
@@ -0,0 +1,80 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { ContextSelector, ContextSelectorItem } from '@patternfly/react-core';
4
+
5
+ const GenericSelector = ({
6
+ selectionItems,
7
+ setGlobalSelected,
8
+ screenReaderLabel,
9
+ initialLabel,
10
+ }) => {
11
+ const [isOpen, setIsOpen] = useState(false);
12
+ const [selected, setSelected] = useState(initialLabel);
13
+ const [searchValue, setSearchValue] = useState('');
14
+ const [filteredItems, setFilteredItems] = useState(selectionItems);
15
+
16
+ useEffect(() => {
17
+ setFilteredItems(selectionItems);
18
+ setSelected(initialLabel);
19
+ }, [selectionItems, initialLabel]);
20
+
21
+ const onToggle = (selectorOpen) => {
22
+ setIsOpen(selectorOpen);
23
+ };
24
+
25
+ const onSelect = (event, value) => {
26
+ setSelected(value);
27
+ setIsOpen(!isOpen);
28
+ setGlobalSelected(value);
29
+ };
30
+
31
+ const onSearchInputChange = (value) => {
32
+ setSearchValue(value);
33
+ };
34
+
35
+ const onSearchButtonClick = (event) => {
36
+ const filtered =
37
+ searchValue === ''
38
+ ? selectionItems
39
+ : selectionItems.filter((item) => {
40
+ const str = item.text ? item.text : item;
41
+ return str.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1;
42
+ });
43
+
44
+ setFilteredItems(filtered || []);
45
+ };
46
+
47
+ return (
48
+ <ContextSelector
49
+ toggleText={selected}
50
+ onSearchInputChange={onSearchInputChange}
51
+ isOpen={isOpen}
52
+ searchInputValue={searchValue}
53
+ onToggle={onToggle}
54
+ onSelect={onSelect}
55
+ onSearchButtonClick={onSearchButtonClick}
56
+ screenReaderLabel={screenReaderLabel}
57
+ >
58
+ {filteredItems.map((item, index) => (
59
+ <ContextSelectorItem key={index}>
60
+ {item || 'no arch'}
61
+ </ContextSelectorItem>
62
+ ))}
63
+ </ContextSelector>
64
+ );
65
+ };
66
+
67
+ GenericSelector.propTypes = {
68
+ selectionItems: PropTypes.array,
69
+ setGlobalSelected: PropTypes.func.isRequired,
70
+ screenReaderLabel: PropTypes.string,
71
+ initialLabel: PropTypes.string,
72
+ };
73
+
74
+ GenericSelector.defaultProps = {
75
+ selectionItems: [],
76
+ screenReaderLabel: '',
77
+ initialLabel: '',
78
+ };
79
+
80
+ export default GenericSelector;
@@ -0,0 +1,174 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
4
+ import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
5
+
6
+ import './styles.scss';
7
+
8
+ const createRepoSelectOption = (repo, disableRepos) => (
9
+ <SelectOption
10
+ key={repo.id}
11
+ isDisabled={repo.katello_root_repository_id !== null || disableRepos}
12
+ value={repo.name}
13
+ />
14
+ );
15
+
16
+ const setRepoSelection = (
17
+ sccRepos,
18
+ disableRepos,
19
+ activateDebugFilter,
20
+ productAlreadySynced
21
+ ) => {
22
+ let res = [];
23
+ // this is necessary because the logic of this option was inverted
24
+ // Instead of filtering the debug repositories with the corresponding option,
25
+ // this option now includes the repositories, instead - means, it does exactly the opposite.
26
+ activateDebugFilter = !activateDebugFilter;
27
+ if (!disableRepos && !productAlreadySynced) {
28
+ if (activateDebugFilter) {
29
+ res = sccRepos.filter(
30
+ (repo) =>
31
+ (!repo.name.includes('Debug') &&
32
+ !repo.name.includes('Source-Pool') &&
33
+ repo.katello_root_repository_id === null) ||
34
+ repo.katello_root_repository_id !== null
35
+ );
36
+ } else {
37
+ res = sccRepos;
38
+ }
39
+ } else {
40
+ res = sccRepos.filter((repo) => repo.katello_root_repository_id !== null);
41
+ }
42
+ return res.map((repo) => repo.name);
43
+ };
44
+
45
+ // disableRepos makes sure that repos can only be selected if the corresponding product
46
+ // is also selected
47
+ const SCCRepoPicker = ({
48
+ sccRepos,
49
+ disableRepos,
50
+ activateDebugFilter,
51
+ productAlreadySynced,
52
+ sccProductId,
53
+ sccProductName,
54
+ setSelectedReposFromChild,
55
+ }) => {
56
+ const [isOpen, setIsOpen] = useState(false);
57
+ // set initial selected values to the already checked repos
58
+ const [selected, setSelected] = useState(
59
+ setRepoSelection(
60
+ sccRepos,
61
+ disableRepos,
62
+ activateDebugFilter,
63
+ productAlreadySynced
64
+ )
65
+ );
66
+ const onToggle = (toggle) => {
67
+ setIsOpen(toggle);
68
+ };
69
+
70
+ useEffect(() => {
71
+ const selectedRepos = setRepoSelection(
72
+ sccRepos,
73
+ disableRepos,
74
+ activateDebugFilter,
75
+ productAlreadySynced
76
+ );
77
+ setSelected(selectedRepos);
78
+ setSelectedReposFromChild(
79
+ sccProductId,
80
+ sccProductName,
81
+ sccRepos
82
+ // make sure that we do not request already subscribed repositories
83
+ .filter(
84
+ (repo) =>
85
+ selectedRepos.includes(repo.name) &&
86
+ repo.katello_root_repository_id === null
87
+ )
88
+ .map((repo) => repo.id),
89
+ sccRepos
90
+ // make sure that we do not request already subscribed repositories
91
+ .filter(
92
+ (repo) =>
93
+ selectedRepos.includes(repo.name) &&
94
+ repo.katello_root_repository_id === null
95
+ )
96
+ .map((repo) => repo.name)
97
+ );
98
+ }, [
99
+ sccRepos,
100
+ disableRepos,
101
+ activateDebugFilter,
102
+ productAlreadySynced,
103
+ sccProductId,
104
+ setSelectedReposFromChild,
105
+ ]);
106
+
107
+ const onSelect = (event, selection) => {
108
+ let selectedRepos = [];
109
+ if (event.target.checked) {
110
+ selectedRepos = [...new Set(selected.concat([selection]))];
111
+ } else {
112
+ selectedRepos = selected.filter((item) => item !== selection);
113
+ }
114
+ setSelected(selectedRepos);
115
+ setSelectedReposFromChild(
116
+ sccProductId,
117
+ sccProductName,
118
+ sccRepos
119
+ // make sure that we do not request already subscribed repositories
120
+ .filter(
121
+ (repo) =>
122
+ selectedRepos.includes(repo.name) &&
123
+ repo.katello_root_repository_id === null
124
+ )
125
+ .map((repo) => repo.id),
126
+ sccRepos
127
+ // make sure that we do not request already subscribed repositories
128
+ .filter(
129
+ (repo) =>
130
+ selectedRepos.includes(repo.name) &&
131
+ repo.katello_root_repository_id === null
132
+ )
133
+ .map((repo) => repo.name)
134
+ );
135
+ };
136
+
137
+ const selectOptions = sccRepos.map((repo) =>
138
+ createRepoSelectOption(repo, disableRepos)
139
+ );
140
+
141
+ return (
142
+ <Select
143
+ variant={SelectVariant.checkbox}
144
+ isCheckboxSelectionBadgeHidden
145
+ onToggle={onToggle}
146
+ onSelect={onSelect}
147
+ selections={selected}
148
+ isOpen={isOpen}
149
+ isDisabled={disableRepos}
150
+ placeholderText={sprintf(__('%s/%s'), selected.length, sccRepos.length)}
151
+ >
152
+ {selectOptions}
153
+ </Select>
154
+ );
155
+ };
156
+
157
+ SCCRepoPicker.propTypes = {
158
+ sccRepos: PropTypes.array,
159
+ disableRepos: PropTypes.bool,
160
+ activateDebugFilter: PropTypes.bool,
161
+ productAlreadySynced: PropTypes.bool,
162
+ sccProductId: PropTypes.number.isRequired,
163
+ sccProductName: PropTypes.string.isRequired,
164
+ setSelectedReposFromChild: PropTypes.func.isRequired,
165
+ };
166
+
167
+ SCCRepoPicker.defaultProps = {
168
+ sccRepos: [],
169
+ disableRepos: false,
170
+ activateDebugFilter: false,
171
+ productAlreadySynced: false,
172
+ };
173
+
174
+ export default SCCRepoPicker;
@@ -0,0 +1,3 @@
1
+ .pf-c-tree-view__node .pf-c-tree-view__node-count .pf-c-badge.pf-m-read {
2
+ --pf-c-badge--m-read--BackgroundColor: transparent;
3
+ }
@@ -0,0 +1,303 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import {
6
+ TreeView,
7
+ Button,
8
+ Tooltip,
9
+ Switch,
10
+ Flex,
11
+ FlexItem,
12
+ Card,
13
+ CardBody,
14
+ } from '@patternfly/react-core';
15
+ import { cloneDeep, merge, clone } from 'lodash';
16
+ import SCCRepoPicker from './components/SCCRepoPicker';
17
+ import { subscribeProductsWithReposAction } from '../../../../SCCProductPageActions';
18
+ import SCCProductTreeExpander from '../../../common/SCCProductTreeExpander';
19
+
20
+ const addCheckBoxToTree = (tree) => {
21
+ const checkProps = {};
22
+ checkProps.checked = tree.product_id !== null;
23
+ checkProps.disabled = tree.product_id !== null;
24
+ tree.checkProps = checkProps;
25
+
26
+ return tree;
27
+ };
28
+
29
+ const addParentToTree = (tree, par) => {
30
+ tree.parent = par;
31
+ return tree;
32
+ };
33
+
34
+ const addSCCRepoPickerToTree = (
35
+ tree,
36
+ disableRepos,
37
+ activateDebugFilter,
38
+ setSelectedReposFromChild
39
+ ) => {
40
+ tree.customBadgeContent[0] = (
41
+ <Tooltip content={__('Filter repositories')}>
42
+ <SCCRepoPicker
43
+ sccRepos={tree.scc_repositories}
44
+ disableRepos={tree.product_id === null && !tree.checkProps.checked}
45
+ activateDebugFilter={activateDebugFilter}
46
+ productAlreadySynced={tree.product_id !== null}
47
+ sccProductId={tree.id}
48
+ sccProductName={tree.name}
49
+ setSelectedReposFromChild={setSelectedReposFromChild}
50
+ />{' '}
51
+ </Tooltip>
52
+ );
53
+ return tree;
54
+ };
55
+
56
+ const setupTreeViewListItem = (
57
+ tree,
58
+ isRoot,
59
+ activateDebugFilter,
60
+ setSelectedReposFromChild
61
+ ) => {
62
+ tree.customBadgeContent = [];
63
+ addCheckBoxToTree(tree);
64
+ addSCCRepoPickerToTree(
65
+ tree,
66
+ tree.product_id === null,
67
+ activateDebugFilter,
68
+ setSelectedReposFromChild
69
+ );
70
+ if ('children' in tree) {
71
+ tree.children = tree.children.map((p) =>
72
+ setupTreeViewListItem(
73
+ p,
74
+ false,
75
+ activateDebugFilter,
76
+ setSelectedReposFromChild
77
+ )
78
+ );
79
+ tree.children.map((child) => addParentToTree(child, tree));
80
+ }
81
+ return tree;
82
+ };
83
+
84
+ const checkAllParents = (
85
+ tree,
86
+ activateDebugFilter,
87
+ setSelectedReposFromChild
88
+ ) => {
89
+ if (!tree.checkProps.checked) {
90
+ tree.checkProps.checked = true;
91
+ addSCCRepoPickerToTree(
92
+ tree,
93
+ false,
94
+ activateDebugFilter,
95
+ setSelectedReposFromChild
96
+ );
97
+ }
98
+ if (tree.parent)
99
+ checkAllParents(
100
+ tree.parent,
101
+ activateDebugFilter,
102
+ setSelectedReposFromChild
103
+ );
104
+
105
+ return tree;
106
+ };
107
+
108
+ const uncheckAllChildren = (
109
+ tree,
110
+ activateDebugFilter,
111
+ setSelectedReposFromChild
112
+ ) => {
113
+ if (tree.product_id === null) {
114
+ tree.checkProps.checked = false;
115
+ addSCCRepoPickerToTree(
116
+ tree,
117
+ true,
118
+ activateDebugFilter,
119
+ setSelectedReposFromChild
120
+ );
121
+ }
122
+ if ('children' in tree)
123
+ tree.children = tree.children.map((c) =>
124
+ uncheckAllChildren(c, activateDebugFilter, setSelectedReposFromChild)
125
+ );
126
+
127
+ return tree;
128
+ };
129
+
130
+ const getRootParent = (tree) => {
131
+ if (tree.parent) return getRootParent(tree.parent);
132
+
133
+ return tree;
134
+ };
135
+
136
+ const SCCTreePicker = ({
137
+ sccProducts,
138
+ sccAccountId,
139
+ resetFormFromParent,
140
+ handleSubscribeCallback,
141
+ }) => {
142
+ const dispatch = useDispatch();
143
+ // this needs to be uninitialized such that the first call to setAllExpanded can actually
144
+ // change the value of allExpanded
145
+ const [expandAll, setExpandAll] = useState();
146
+ const [selectedRepos, setSelectedRepos] = useState({});
147
+ // the debug filter is actually a 'includeDebugRepos' setting which should not be active by default
148
+ const [activateDebugFilter, setActivateDebugFilter] = useState(false);
149
+
150
+ const setSelectedReposFromChild = (
151
+ productId,
152
+ productName,
153
+ repoIds,
154
+ repoNames
155
+ ) => {
156
+ if (repoIds.length !== 0) {
157
+ selectedRepos[productId] = {};
158
+ selectedRepos[productId].repoIds = repoIds;
159
+ selectedRepos[productId].productName = productName;
160
+ selectedRepos[productId].repoNames = repoNames;
161
+ const newSelectedRepos = clone(selectedRepos);
162
+ setSelectedRepos(newSelectedRepos);
163
+ } else if (selectedRepos !== {} && productId in selectedRepos) {
164
+ delete selectedRepos[productId];
165
+ const newSelectedRepos = clone(selectedRepos);
166
+ setSelectedRepos(newSelectedRepos);
167
+ }
168
+ };
169
+
170
+ const [sccProductTree, setSccProductTree] = useState(
171
+ cloneDeep(sccProducts).map((p) =>
172
+ setupTreeViewListItem(
173
+ p,
174
+ true,
175
+ activateDebugFilter,
176
+ setSelectedReposFromChild
177
+ )
178
+ )
179
+ );
180
+
181
+ useEffect(() => {
182
+ setSccProductTree(
183
+ cloneDeep(sccProducts).map((p) =>
184
+ setupTreeViewListItem(
185
+ p,
186
+ true,
187
+ activateDebugFilter,
188
+ setSelectedReposFromChild
189
+ )
190
+ )
191
+ );
192
+ // some thorough cleaning is required for hash maps
193
+ Object.keys(selectedRepos).forEach((k) => delete selectedRepos[k]);
194
+ }, [sccProducts]);
195
+
196
+ const setExpandAllFromChild = (expandAllFromChild) => {
197
+ setExpandAll(expandAllFromChild);
198
+ };
199
+
200
+ const debugFilterChange = (evt) => {
201
+ setActivateDebugFilter(!activateDebugFilter);
202
+ };
203
+
204
+ const onCheck = (evt, treeViewItem) => {
205
+ if (evt.target.checked) {
206
+ checkAllParents(
207
+ treeViewItem,
208
+ activateDebugFilter,
209
+ setSelectedReposFromChild
210
+ );
211
+ } else {
212
+ uncheckAllChildren(
213
+ treeViewItem,
214
+ activateDebugFilter,
215
+ setSelectedReposFromChild
216
+ );
217
+ }
218
+
219
+ setSccProductTree([...merge(sccProductTree, getRootParent(treeViewItem))]);
220
+ };
221
+
222
+ const submitForm = (evt) => {
223
+ const productsToSubscribe = [];
224
+ Object.keys(selectedRepos).forEach((k) => {
225
+ const repo = {
226
+ scc_product_id: parseInt(k, 10),
227
+ repository_list: selectedRepos[k].repoIds,
228
+ };
229
+ productsToSubscribe.push(repo);
230
+ });
231
+ dispatch(
232
+ subscribeProductsWithReposAction(
233
+ sccAccountId,
234
+ productsToSubscribe,
235
+ handleSubscribeCallback,
236
+ selectedRepos
237
+ )
238
+ );
239
+ // reset data structure and form
240
+ setSelectedRepos({});
241
+ resetFormFromParent();
242
+ };
243
+
244
+ return (
245
+ <Card>
246
+ <CardBody>
247
+ <Flex direction={{ default: 'column' }}>
248
+ <Flex>
249
+ <SCCProductTreeExpander
250
+ setExpandAllInParent={setExpandAllFromChild}
251
+ />
252
+ <FlexItem>
253
+ <Tooltip
254
+ content={__(
255
+ 'If this option is enabled, debug and source pool repositories are automatically selected if you select a product. This option is disabled by default. It applies for unselected products, only. Already selected products are not filtered.'
256
+ )}
257
+ >
258
+ <Switch
259
+ id="filter-debug-switch"
260
+ onChange={debugFilterChange}
261
+ isChecked={activateDebugFilter}
262
+ label={__('Include Debug and Source Pool repositories')}
263
+ />
264
+ </Tooltip>
265
+ </FlexItem>
266
+ </Flex>
267
+ <Flex>
268
+ <TreeView
269
+ data={sccProductTree}
270
+ allExpanded={expandAll}
271
+ onCheck={onCheck}
272
+ hasChecks
273
+ hasBadges
274
+ hasGuides
275
+ />
276
+ </Flex>
277
+ <Flex>
278
+ <Button
279
+ variant="primary"
280
+ onClick={submitForm}
281
+ isDisabled={Object.keys(selectedRepos).length === 0}
282
+ >
283
+ {__('Add product(s)')}
284
+ </Button>
285
+ </Flex>
286
+ </Flex>
287
+ </CardBody>
288
+ </Card>
289
+ );
290
+ };
291
+
292
+ SCCTreePicker.propTypes = {
293
+ sccProducts: PropTypes.array,
294
+ sccAccountId: PropTypes.number.isRequired,
295
+ resetFormFromParent: PropTypes.func.isRequired,
296
+ handleSubscribeCallback: PropTypes.func.isRequired,
297
+ };
298
+
299
+ SCCTreePicker.defaultProps = {
300
+ sccProducts: [],
301
+ };
302
+
303
+ export default SCCTreePicker;