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