foreman_scc_manager 1.8.19 → 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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -10
  3. data/app/controllers/api/v2/scc_accounts_controller.rb +48 -5
  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 +61 -19
  7. data/app/models/scc_account.rb +4 -3
  8. data/app/models/scc_katello_repository.rb +4 -0
  9. data/app/models/scc_product.rb +7 -1
  10. data/app/models/scc_repository.rb +12 -4
  11. data/app/views/scc_accounts/show.html.erb +7 -51
  12. data/config/routes.rb +1 -0
  13. data/db/migrate/20220425132605_add_scc_katello_repository_relation.rb +10 -0
  14. data/db/migrate/20220429100843_remove_root_repository_id_from_scc_repository.rb +6 -0
  15. data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +21 -0
  16. data/db/migrate/20220513132652_populate_upstream_authentication_token.rb +23 -0
  17. data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
  18. data/lib/foreman_scc_manager/engine.rb +1 -1
  19. data/lib/foreman_scc_manager/version.rb +1 -1
  20. data/lib/tasks/republish_repositories.rake +13 -0
  21. data/lib/tasks/setup_authentication_token.rake +27 -0
  22. data/package.json +54 -0
  23. data/test/controllers/scc_accounts_controller_test.rb +6 -0
  24. data/test/fixtures/models/katello_root_repositories.yml +44 -0
  25. data/test/fixtures/models/scc_repositories.yml +34 -0
  26. data/test/support/fixtures_support.rb +3 -1
  27. data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
  28. data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
  29. data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
  30. data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
  31. data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
  32. data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
  33. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
  34. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
  35. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
  36. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
  37. data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
  38. data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
  39. data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
  40. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
  41. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
  42. data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
  43. data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
  44. data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
  45. data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
  46. data/webpack/components/SCCProductPage/index.js +18 -0
  47. data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
  48. data/webpack/index.js +11 -0
  49. data/webpack/reducer.js +7 -0
  50. metadata +48 -14
data/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "foreman_scc_manager",
3
+ "version": "1.8.16",
4
+ "description": "Foreman plugin to sync SUSE Customer Center products and repositories into Katello ",
5
+ "main": "index.js",
6
+ "directories": {
7
+ "lib": "lib",
8
+ "test": "test"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "create-react-component": "yo react-domain"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+ssh://git@github.com/ATIX-AG/foreman_scc_manager.git"
17
+ },
18
+ "peerDependencies": {
19
+ "@theforeman/vendor": "^4.14.0"
20
+ },
21
+ "devDependencies": {
22
+ "@babel/core": "^7.7.0",
23
+ "babel-eslint": "^10.0.0",
24
+ "babel-loader": "^8.0.0",
25
+ "@theforeman/builder": "^4.14.0",
26
+ "@theforeman/eslint-plugin-foreman": "^8.16.0",
27
+ "@theforeman/find-foreman": "^4.14.0",
28
+ "@theforeman/stories": "^4.14.0",
29
+ "@theforeman/test": "^4.14.0",
30
+ "@theforeman/vendor-dev": "^4.14.0",
31
+ "eslint": "^6.7.2",
32
+ "eslint-plugin-spellcheck": "0.0.17",
33
+ "eslint-plugin-react": "^7.27.1",
34
+ "eslint-plugin-react-hooks": "^4.3.0",
35
+ "prettier": "^2.5.1",
36
+ "react-redux-test-utils": "^0.2.0"
37
+ },
38
+ "keywords": [
39
+ "SUSE",
40
+ "Katello",
41
+ "products",
42
+ "repositories"
43
+ ],
44
+ "author": "ATIX AG",
45
+ "license": "GPL-3.0",
46
+ "bugs": {
47
+ "url": "https://github.com/ATIX-AG/foreman_scc_manager/issues"
48
+ },
49
+ "homepage": "https://github.com/ATIX-AG/foreman_scc_manager#readme",
50
+ "dependencies": {
51
+ "react-native-elements": "^3.4.2",
52
+ "react-native-vector-icons": "^9.0.0"
53
+ }
54
+ }
@@ -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
 
@@ -0,0 +1,44 @@
1
+ sles12_sp4_updates:
2
+ name: SLES12-SP4-Updates for sle-12-x86_64
3
+ content_type: yum
4
+ label: 1759_SLES12-SP4-Updates_for_sle-12-x86_64_description
5
+ product_id: <%= ActiveRecord::FixtureSet.identify(:suse) %>
6
+ url: www.not-valid.com
7
+ download_policy: immediate
8
+ <% if ActiveRecord::Base.connection.column_exists? :katello_root_repositories, :mirroring_policy %>
9
+ mirroring_policy: "mirror_content_only"
10
+ <% else %>
11
+ mirror_on_sync: "true"
12
+ <% end %>
13
+ created_at: <%= Time.now %>
14
+ updated_at: <%= Time.now %>
15
+
16
+ sles12_sp4_installer_updates:
17
+ name: SLES12-SP4-Installer-Updates for sle-12-x86_64
18
+ content_type: yum
19
+ label: 1759_product_name_SLES12-SP4-Installer-Updates_for_sle-12-x86_64_description
20
+ product_id: <%= ActiveRecord::FixtureSet.identify(:suse) %>
21
+ url: www.not-valid.com
22
+ download_policy: on_demand
23
+ <% if ActiveRecord::Base.connection.column_exists? :katello_root_repositories, :mirroring_policy %>
24
+ mirroring_policy: "mirror_content_only"
25
+ <% else %>
26
+ mirror_on_sync: "true"
27
+ <% end %>
28
+ created_at: <%= Time.now %>
29
+ updated_at: <%= Time.now %>
30
+
31
+ sles12_sp4_installer_updates_2:
32
+ name: SLES12-SP4-Installer-Updates for sle-12-x86_64
33
+ content_type: yum
34
+ label: 1760_product_name_SLES12-SP4-Installer-Updates_for_sle-12-x86_64_description
35
+ product_id: <%= ActiveRecord::FixtureSet.identify(:suse) %>
36
+ url: www.not-valid.com
37
+ download_policy: on_demand
38
+ <% if ActiveRecord::Base.connection.column_exists? :katello_root_repositories, :mirroring_policy %>
39
+ mirroring_policy: "mirror_content_only"
40
+ <% else %>
41
+ mirror_on_sync: "true"
42
+ <% end %>
43
+ created_at: <%= Time.now %>
44
+ updated_at: <%= Time.now %>
@@ -25,5 +25,39 @@ repo_9:
25
25
  autorefresh: true
26
26
  installer_updates: false
27
27
 
28
+ sles12_updates:
29
+ id: 10
30
+ scc_account_id: 1
31
+ scc_id: 1
32
+ name: SLES12-SP4-Updates for sle-12-x86_64
33
+ distro_target: sles_12
34
+ description: SLES12-SP4-Updates for sle-12-x86_64
35
+ url: www.not-valid.com
36
+ token: token_10
37
+ autorefresh: true
38
+ installer_updates: false
28
39
 
29
40
 
41
+ sles12_installer_updates:
42
+ id: 11
43
+ scc_account_id: 1
44
+ scc_id: 2
45
+ name: SLES12-SP4-Installer-Updates for sle-12-x86_64
46
+ distro_target: sles_12
47
+ description: SLES12-SP4-Installer-Updates for sle-12-x86_64
48
+ url: www.not-valid.com
49
+ token: token_11
50
+ autorefresh: true
51
+ installer_updates: false
52
+
53
+ sles12_installer_updates_2:
54
+ id: 12
55
+ scc_account_id: 1
56
+ scc_id: 3
57
+ name: SLES12-SP4-Installer-Updates for sle-12-x86_64_2
58
+ distro_target: sles_9
59
+ description: SLES12-SP4-Installer-Updates for sle-12-x86_64_2
60
+ url: www.not-valid.com
61
+ token: token_12
62
+ autorefresh: true
63
+ installer_updates: false
@@ -3,7 +3,9 @@ module ForemanSccManager
3
3
  FIXTURE_CLASSES = {
4
4
  scc_accounts: ::SccAccount,
5
5
  scc_products: ::SccProduct,
6
- scc_repositories: ::SccRepository
6
+ scc_repositories: ::SccRepository,
7
+ katello_root_repositories: ::Katello::RootRepository,
8
+ scc_katello_repositories: SccKatelloRepository
7
9
  }.freeze
8
10
 
9
11
  def self.set_fixture_classes(test_class)
@@ -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
+ }