foreman_scc_manager 1.8.20 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }