foreman_scc_manager 1.8.20 → 2.1.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -11
  3. data/app/controllers/api/v2/scc_accounts_controller.rb +55 -4
  4. data/app/controllers/api/v2/scc_products_controller.rb +1 -1
  5. data/app/controllers/scc_accounts_controller.rb +49 -0
  6. data/app/lib/actions/scc_manager/subscribe_product.rb +56 -22
  7. data/app/models/scc_account.rb +22 -1
  8. data/app/models/scc_product.rb +2 -5
  9. data/app/views/api/v2/scc_accounts/main.json.rabl +1 -1
  10. data/app/views/scc_accounts/_form.html.erb +8 -4
  11. data/app/views/scc_accounts/show.html.erb +7 -51
  12. data/config/routes.rb +1 -0
  13. data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +5 -0
  14. data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
  15. data/db/migrate/20221013214310_add_sync_options_to_scc_account.rb +11 -0
  16. data/lib/foreman_scc_manager/engine.rb +1 -1
  17. data/lib/foreman_scc_manager/version.rb +1 -1
  18. data/lib/tasks/republish_repositories.rake +13 -0
  19. data/package.json +54 -0
  20. data/test/controllers/scc_accounts_controller_test.rb +7 -1
  21. data/test/fixtures/models/katello_root_repositories.yml +0 -12
  22. data/test/test_plugin_helper.rb +0 -6
  23. data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
  24. data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
  25. data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
  26. data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
  27. data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
  28. data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
  29. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
  30. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
  31. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
  32. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
  33. data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
  34. data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
  35. data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
  36. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
  37. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
  38. data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
  39. data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
  40. data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
  41. data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
  42. data/webpack/components/SCCProductPage/index.js +18 -0
  43. data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
  44. data/webpack/index.js +11 -0
  45. data/webpack/reducer.js +7 -0
  46. metadata +43 -15
@@ -2,6 +2,12 @@ require 'test_plugin_helper'
2
2
 
3
3
  class SccAccountsControllerTest < ActionController::TestCase
4
4
  def setup
5
+ # rubocop: disable Lint/SuppressedException
6
+ begin
7
+ ::Katello::ContentCredential
8
+ rescue NameError
9
+ end
10
+ # rubocop: enable Lint/SuppressedException
5
11
  @scc_account = scc_accounts(:one)
6
12
  end
7
13
 
@@ -13,7 +19,7 @@ class SccAccountsControllerTest < ActionController::TestCase
13
19
  'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
14
20
  'Authorization' => auth,
15
21
  'Host' => host,
16
- 'User-Agent' => "rest-client/2.1.0 (linux-gnu x86_64) ruby/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
22
+ 'User-Agent' => "rest-client/2.1.0 (linux x86_64) ruby/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
17
23
  }
18
24
  )
19
25
  .to_return(status: 200, body: '', headers: {})
@@ -5,11 +5,7 @@ sles12_sp4_updates:
5
5
  product_id: <%= ActiveRecord::FixtureSet.identify(:suse) %>
6
6
  url: www.not-valid.com
7
7
  download_policy: immediate
8
- <% if ActiveRecord::Base.connection.column_exists? :katello_root_repositories, :mirroring_policy %>
9
8
  mirroring_policy: "mirror_content_only"
10
- <% else %>
11
- mirror_on_sync: "true"
12
- <% end %>
13
9
  created_at: <%= Time.now %>
14
10
  updated_at: <%= Time.now %>
15
11
 
@@ -20,11 +16,7 @@ sles12_sp4_installer_updates:
20
16
  product_id: <%= ActiveRecord::FixtureSet.identify(:suse) %>
21
17
  url: www.not-valid.com
22
18
  download_policy: on_demand
23
- <% if ActiveRecord::Base.connection.column_exists? :katello_root_repositories, :mirroring_policy %>
24
19
  mirroring_policy: "mirror_content_only"
25
- <% else %>
26
- mirror_on_sync: "true"
27
- <% end %>
28
20
  created_at: <%= Time.now %>
29
21
  updated_at: <%= Time.now %>
30
22
 
@@ -35,10 +27,6 @@ sles12_sp4_installer_updates_2:
35
27
  product_id: <%= ActiveRecord::FixtureSet.identify(:suse) %>
36
28
  url: www.not-valid.com
37
29
  download_policy: on_demand
38
- <% if ActiveRecord::Base.connection.column_exists? :katello_root_repositories, :mirroring_policy %>
39
30
  mirroring_policy: "mirror_content_only"
40
- <% else %>
41
- mirror_on_sync: "true"
42
- <% end %>
43
31
  created_at: <%= Time.now %>
44
32
  updated_at: <%= Time.now %>
@@ -29,8 +29,6 @@ module FixtureTestCase
29
29
  fixtures(:all)
30
30
  FIXTURES = load_fixtures(ActiveRecord::Base)
31
31
 
32
- Setting::Content.load_defaults
33
-
34
32
  User.current = ::User.unscoped.find(FIXTURES['users']['admin']['id'])
35
33
  end
36
34
  end
@@ -41,10 +39,6 @@ class ActiveSupport::TestCase
41
39
  include FixtureTestCase
42
40
  include ForemanTasks::TestHelpers::WithInThreadExecutor
43
41
 
44
- before do
45
- Setting::Content.load_defaults
46
- end
47
-
48
42
  def get_organization(org = nil)
49
43
  saved_user = User.current
50
44
  User.current = User.unscoped.find(users(:admin).id)
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+ import { Button } from '@patternfly/react-core';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import EmptyState from 'foremanReact/components/common/EmptyState';
7
+ import { syncSccAccountAction } from './SCCProductPageActions';
8
+
9
+ export const EmptySccProducts = ({ canCreate, sccAccountId }) => {
10
+ const dispatch = useDispatch();
11
+ const onSyncStart = (evt) => {
12
+ dispatch(syncSccAccountAction(sccAccountId));
13
+ };
14
+
15
+ const content = __(
16
+ `Please synchronize your SUSE account before you can subscribe to SUSE products.`
17
+ );
18
+ return (
19
+ <>
20
+ <EmptyState
21
+ icon="th"
22
+ iconType="fa"
23
+ header={__('SUSE Customer Center')}
24
+ description={<div dangerouslySetInnerHTML={{ __html: content }} />}
25
+ documentation={{
26
+ url: 'https://docs.orcharhino.com/or/docs/sources/usage_guides/managing_sles_systems_guide.html#mssg_adding_scc_accounts',
27
+ }}
28
+ />
29
+ <Button onClick={onSyncStart} className="btn btn-primary">
30
+ {__('Synchronize SUSE Account')}
31
+ </Button>
32
+ </>
33
+ );
34
+ };
35
+
36
+ EmptySccProducts.propTypes = {
37
+ canCreate: PropTypes.bool,
38
+ sccAccountId: PropTypes.number,
39
+ };
40
+
41
+ EmptySccProducts.defaultProps = {
42
+ canCreate: false,
43
+ sccAccountId: undefined,
44
+ };
45
+
46
+ export default EmptySccProducts;
@@ -0,0 +1,96 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { useState } from 'react';
3
+ import { Stack, StackItem } from '@patternfly/react-core';
4
+ import { useDispatch } from 'react-redux';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks';
7
+ import SCCProductView from './components/SCCProductView';
8
+ import EmptySccProducts from './EmptySccProducts';
9
+ import SCCProductPicker from './components/SCCProductPicker';
10
+ import SCCProductPickerModal from './components/SCCProductPickerModal';
11
+ import { SCCPRODUCTPAGE_SUMMARY_MODAL_ID } from './SCCProductPageConstants';
12
+ import './sccProductPage.scss';
13
+
14
+ const SCCProductPage = ({
15
+ canCreate,
16
+ sccAccountId,
17
+ sccProductsInit,
18
+ ...props
19
+ }) => {
20
+ const dispatch = useDispatch();
21
+ const [productToEdit, setProductToEdit] = useState(0);
22
+ const [reposToSubscribe, setReposToSubscribe] = useState([]);
23
+ const [subscriptionTaskId, setSubscriptionTaskId] = useState();
24
+
25
+ const editProductTree = (productId) => {
26
+ setProductToEdit(productId);
27
+ };
28
+
29
+ const { setModalOpen, setModalClosed } = useForemanModal({
30
+ id: SCCPRODUCTPAGE_SUMMARY_MODAL_ID,
31
+ });
32
+
33
+ const handleSubscribeCallback = (
34
+ subscriptionTaskIdFromAction,
35
+ reposToSubscribeFromAction
36
+ ) => {
37
+ setSubscriptionTaskId(subscriptionTaskIdFromAction);
38
+ const newReposToSubscribe = [];
39
+ Object.keys(reposToSubscribeFromAction).forEach((k) => {
40
+ const repo = {
41
+ productName: reposToSubscribeFromAction[k].productName,
42
+ repoNames: reposToSubscribeFromAction[k].repoNames,
43
+ };
44
+ newReposToSubscribe.push(repo);
45
+ });
46
+ setReposToSubscribe(newReposToSubscribe);
47
+ dispatch(setModalOpen({ id: SCCPRODUCTPAGE_SUMMARY_MODAL_ID }));
48
+ };
49
+
50
+ return sccProductsInit.length > 0 ? (
51
+ <Stack>
52
+ <StackItem>
53
+ <SCCProductPickerModal
54
+ id={SCCPRODUCTPAGE_SUMMARY_MODAL_ID}
55
+ title={__('The subscription task has been started successfully')}
56
+ taskId={subscriptionTaskId}
57
+ reposToSubscribe={reposToSubscribe}
58
+ />
59
+ </StackItem>
60
+ <StackItem>
61
+ <SCCProductView
62
+ sccProducts={sccProductsInit.filter(
63
+ (prod) => prod.product_id !== null
64
+ )}
65
+ subscriptionTaskId={subscriptionTaskId}
66
+ editProductTreeGlobal={editProductTree}
67
+ />
68
+ </StackItem>
69
+ <br />
70
+ <StackItem />
71
+ <StackItem>
72
+ <SCCProductPicker
73
+ sccProducts={sccProductsInit}
74
+ sccAccountId={sccAccountId}
75
+ editProductId={productToEdit}
76
+ handleSubscribeCallback={handleSubscribeCallback}
77
+ />
78
+ </StackItem>
79
+ </Stack>
80
+ ) : (
81
+ <EmptySccProducts sccAccountId={sccAccountId} canCreate={canCreate} />
82
+ );
83
+ };
84
+
85
+ SCCProductPage.propTypes = {
86
+ canCreate: PropTypes.bool,
87
+ sccAccountId: PropTypes.number.isRequired,
88
+ sccProductsInit: PropTypes.array,
89
+ };
90
+
91
+ SCCProductPage.defaultProps = {
92
+ canCreate: false,
93
+ sccProductsInit: [],
94
+ };
95
+
96
+ export default SCCProductPage;
@@ -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
+ }