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.
- checksums.yaml +4 -4
- data/README.md +14 -10
- data/app/controllers/api/v2/scc_accounts_controller.rb +48 -5
- data/app/controllers/api/v2/scc_products_controller.rb +1 -1
- data/app/controllers/scc_accounts_controller.rb +47 -0
- data/app/lib/actions/scc_manager/subscribe_product.rb +61 -19
- data/app/models/scc_account.rb +4 -3
- data/app/models/scc_katello_repository.rb +4 -0
- data/app/models/scc_product.rb +7 -1
- data/app/models/scc_repository.rb +12 -4
- data/app/views/scc_accounts/show.html.erb +7 -51
- data/config/routes.rb +1 -0
- data/db/migrate/20220425132605_add_scc_katello_repository_relation.rb +10 -0
- data/db/migrate/20220429100843_remove_root_repository_id_from_scc_repository.rb +6 -0
- data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +21 -0
- data/db/migrate/20220513132652_populate_upstream_authentication_token.rb +23 -0
- data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
- data/lib/foreman_scc_manager/engine.rb +1 -1
- data/lib/foreman_scc_manager/version.rb +1 -1
- data/lib/tasks/republish_repositories.rake +13 -0
- data/lib/tasks/setup_authentication_token.rake +27 -0
- data/package.json +54 -0
- data/test/controllers/scc_accounts_controller_test.rb +6 -0
- data/test/fixtures/models/katello_root_repositories.yml +44 -0
- data/test/fixtures/models/scc_repositories.yml +34 -0
- data/test/support/fixtures_support.rb +3 -1
- data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
- data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
- data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
- data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
- data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
- data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
- data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
- data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
- data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
- data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
- data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
- data/webpack/components/SCCProductPage/index.js +18 -0
- data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
- data/webpack/index.js +11 -0
- data/webpack/reducer.js +7 -0
- metadata +48 -14
    
        data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js
    ADDED
    
    | @@ -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;
         | 
| @@ -0,0 +1,235 @@ | |
| 1 | 
            +
            import React, { useState, useEffect } from 'react';
         | 
| 2 | 
            +
            import PropTypes from 'prop-types';
         | 
| 3 | 
            +
            import { TimesIcon } from '@patternfly/react-icons';
         | 
| 4 | 
            +
            import {
         | 
| 5 | 
            +
              Button,
         | 
| 6 | 
            +
              Card,
         | 
| 7 | 
            +
              CardTitle,
         | 
| 8 | 
            +
              CardBody,
         | 
| 9 | 
            +
              CardHeader,
         | 
| 10 | 
            +
              CardExpandableContent,
         | 
| 11 | 
            +
              Flex,
         | 
| 12 | 
            +
              FlexItem,
         | 
| 13 | 
            +
            } from '@patternfly/react-core';
         | 
| 14 | 
            +
            import { translate as __ } from 'foremanReact/common/I18n';
         | 
| 15 | 
            +
            import { uniq } from 'lodash';
         | 
| 16 | 
            +
            import './styles.scss';
         | 
| 17 | 
            +
            import SCCGenericPicker from './components/SCCGenericPicker';
         | 
| 18 | 
            +
            import SCCTreePicker from './components/SCCTreePicker';
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            const resetSelectionStringProduct = __(' -- Select Product --');
         | 
| 21 | 
            +
            const resetSelectionStringVersion = __(' -- Select Version --');
         | 
| 22 | 
            +
            const resetSelectionStringArch = __(' -- Select Architecture --');
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            const genericFilter = (object, comparator) =>
         | 
| 25 | 
            +
              // we can have architectures that are not set
         | 
| 26 | 
            +
              comparator === '' ||
         | 
| 27 | 
            +
              comparator === resetSelectionStringProduct ||
         | 
| 28 | 
            +
              comparator === resetSelectionStringVersion ||
         | 
| 29 | 
            +
              comparator === resetSelectionStringArch ||
         | 
| 30 | 
            +
              object === comparator ||
         | 
| 31 | 
            +
              (object === null && comparator === 'no arch');
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            const filterVersionByProduct = (sccProducts, product) =>
         | 
| 34 | 
            +
              uniq(
         | 
| 35 | 
            +
                sccProducts
         | 
| 36 | 
            +
                  .filter((p) => p.product_category === product)
         | 
| 37 | 
            +
                  .map((i) => i.version)
         | 
| 38 | 
            +
              ).sort();
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            const filterArchByVersionAndProduct = (sccProducts, product, version) =>
         | 
| 41 | 
            +
              uniq(
         | 
| 42 | 
            +
                sccProducts
         | 
| 43 | 
            +
                  .filter((p) => p.product_category === product && p.version === version)
         | 
| 44 | 
            +
                  .map((i) => i.arch)
         | 
| 45 | 
            +
              ).sort();
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            const SCCProductPicker = ({
         | 
| 48 | 
            +
              sccProducts,
         | 
| 49 | 
            +
              sccAccountId,
         | 
| 50 | 
            +
              editProductId,
         | 
| 51 | 
            +
              handleSubscribeCallback,
         | 
| 52 | 
            +
            }) => {
         | 
| 53 | 
            +
              const [productItems, setProductItems] = useState(
         | 
| 54 | 
            +
                uniq(sccProducts.map((p) => p.product_category))
         | 
| 55 | 
            +
              );
         | 
| 56 | 
            +
              const [selectedProduct, setSelectedProduct] = useState('');
         | 
| 57 | 
            +
              const [archItems, setArchItems] = useState([]);
         | 
| 58 | 
            +
              const [selectedArch, setSelectedArch] = useState('');
         | 
| 59 | 
            +
              const [versionItems, setVersionItems] = useState([]);
         | 
| 60 | 
            +
              const [selectedVersion, setSelectedVersion] = useState('');
         | 
| 61 | 
            +
              const [filteredSccProducts, setFilteredSccProducts] = useState([]);
         | 
| 62 | 
            +
              const [showSearchTree, setShowSearchTree] = useState(false);
         | 
| 63 | 
            +
              const [isExpanded, setIsExpanded] = useState(false);
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              useEffect(() => {
         | 
| 66 | 
            +
                if (editProductId !== 0) {
         | 
| 67 | 
            +
                  // the id is unique, so there never should be more than 1 element in the array
         | 
| 68 | 
            +
                  const product = sccProducts.filter((p) => p.id === editProductId);
         | 
| 69 | 
            +
                  if (product.length > 0) {
         | 
| 70 | 
            +
                    setSelectedProduct(product[0].product_category);
         | 
| 71 | 
            +
                    setSelectedArch(product[0].arch);
         | 
| 72 | 
            +
                    setSelectedVersion(product[0].version);
         | 
| 73 | 
            +
                    setFilteredSccProducts(product);
         | 
| 74 | 
            +
                    setShowSearchTree(true);
         | 
| 75 | 
            +
                    setIsExpanded(true);
         | 
| 76 | 
            +
                  }
         | 
| 77 | 
            +
                }
         | 
| 78 | 
            +
              }, [editProductId, sccProducts]);
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              const onProductSelectionChange = (value) => {
         | 
| 81 | 
            +
                if (value !== resetSelectionStringProduct) {
         | 
| 82 | 
            +
                  setVersionItems(filterVersionByProduct(sccProducts, value));
         | 
| 83 | 
            +
                } else {
         | 
| 84 | 
            +
                  setVersionItems([]);
         | 
| 85 | 
            +
                }
         | 
| 86 | 
            +
                setSelectedProduct(value);
         | 
| 87 | 
            +
                setArchItems([]);
         | 
| 88 | 
            +
                setSelectedVersion('');
         | 
| 89 | 
            +
                setSelectedArch('');
         | 
| 90 | 
            +
              };
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              const onVersionSelectionChange = (value) => {
         | 
| 93 | 
            +
                if (value === resetSelectionStringVersion) {
         | 
| 94 | 
            +
                  setArchItems([]);
         | 
| 95 | 
            +
                } else {
         | 
| 96 | 
            +
                  setArchItems(
         | 
| 97 | 
            +
                    filterArchByVersionAndProduct(sccProducts, selectedProduct, value)
         | 
| 98 | 
            +
                  );
         | 
| 99 | 
            +
                }
         | 
| 100 | 
            +
                setSelectedVersion(value);
         | 
| 101 | 
            +
                setSelectedArch('');
         | 
| 102 | 
            +
              };
         | 
| 103 | 
            +
             | 
| 104 | 
            +
              const onArchSelectionChange = (value) => {
         | 
| 105 | 
            +
                setSelectedArch(value);
         | 
| 106 | 
            +
              };
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              const onExpand = (evt, id) => {
         | 
| 109 | 
            +
                setIsExpanded(!isExpanded);
         | 
| 110 | 
            +
              };
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              const filterProducts = (evt) => {
         | 
| 113 | 
            +
                setShowSearchTree(true);
         | 
| 114 | 
            +
                setFilteredSccProducts(
         | 
| 115 | 
            +
                  sccProducts.filter(
         | 
| 116 | 
            +
                    (p) =>
         | 
| 117 | 
            +
                      genericFilter(p.product_category, selectedProduct) &&
         | 
| 118 | 
            +
                      genericFilter(p.arch, selectedArch) &&
         | 
| 119 | 
            +
                      genericFilter(p.version, selectedVersion)
         | 
| 120 | 
            +
                  )
         | 
| 121 | 
            +
                );
         | 
| 122 | 
            +
              };
         | 
| 123 | 
            +
             | 
| 124 | 
            +
              const resetTreeForm = () => {
         | 
| 125 | 
            +
                setShowSearchTree(false);
         | 
| 126 | 
            +
                setSelectedProduct('');
         | 
| 127 | 
            +
                setSelectedVersion('');
         | 
| 128 | 
            +
                setSelectedArch('');
         | 
| 129 | 
            +
              };
         | 
| 130 | 
            +
             | 
| 131 | 
            +
              return (
         | 
| 132 | 
            +
                <Card border="dark" id="product-selection-card" isExpanded={isExpanded}>
         | 
| 133 | 
            +
                  <CardHeader onExpand={onExpand}>
         | 
| 134 | 
            +
                    <CardTitle>{__('Select SUSE products')}</CardTitle>
         | 
| 135 | 
            +
                  </CardHeader>
         | 
| 136 | 
            +
                  <CardExpandableContent>
         | 
| 137 | 
            +
                    <CardBody>
         | 
| 138 | 
            +
                      <Flex direction={{ default: 'column' }}>
         | 
| 139 | 
            +
                        <Flex>
         | 
| 140 | 
            +
                          <FlexItem>
         | 
| 141 | 
            +
                            <SCCGenericPicker
         | 
| 142 | 
            +
                              key="prod-select"
         | 
| 143 | 
            +
                              selectionItems={
         | 
| 144 | 
            +
                                selectedProduct === ''
         | 
| 145 | 
            +
                                  ? productItems
         | 
| 146 | 
            +
                                  : [resetSelectionStringProduct].concat(productItems)
         | 
| 147 | 
            +
                              }
         | 
| 148 | 
            +
                              setGlobalSelected={onProductSelectionChange}
         | 
| 149 | 
            +
                              screenReaderLabel={resetSelectionStringProduct}
         | 
| 150 | 
            +
                              initialLabel={
         | 
| 151 | 
            +
                                selectedProduct === ''
         | 
| 152 | 
            +
                                  ? resetSelectionStringProduct
         | 
| 153 | 
            +
                                  : selectedProduct
         | 
| 154 | 
            +
                              }
         | 
| 155 | 
            +
                            />
         | 
| 156 | 
            +
                          </FlexItem>
         | 
| 157 | 
            +
                          <FlexItem>
         | 
| 158 | 
            +
                            <SCCGenericPicker
         | 
| 159 | 
            +
                              key="vers-select"
         | 
| 160 | 
            +
                              selectionItems={
         | 
| 161 | 
            +
                                selectedVersion === ''
         | 
| 162 | 
            +
                                  ? versionItems
         | 
| 163 | 
            +
                                  : [resetSelectionStringVersion].concat(versionItems)
         | 
| 164 | 
            +
                              }
         | 
| 165 | 
            +
                              setGlobalSelected={onVersionSelectionChange}
         | 
| 166 | 
            +
                              screenReaderLabel={resetSelectionStringVersion}
         | 
| 167 | 
            +
                              initialLabel={
         | 
| 168 | 
            +
                                selectedVersion === ''
         | 
| 169 | 
            +
                                  ? resetSelectionStringVersion
         | 
| 170 | 
            +
                                  : selectedVersion
         | 
| 171 | 
            +
                              }
         | 
| 172 | 
            +
                            />
         | 
| 173 | 
            +
                          </FlexItem>
         | 
| 174 | 
            +
                          <FlexItem>
         | 
| 175 | 
            +
                            <SCCGenericPicker
         | 
| 176 | 
            +
                              key="arch-select"
         | 
| 177 | 
            +
                              selectionItems={
         | 
| 178 | 
            +
                                selectedArch === ''
         | 
| 179 | 
            +
                                  ? archItems
         | 
| 180 | 
            +
                                  : [resetSelectionStringArch].concat(archItems)
         | 
| 181 | 
            +
                              }
         | 
| 182 | 
            +
                              setGlobalSelected={onArchSelectionChange}
         | 
| 183 | 
            +
                              screenReaderLabel={resetSelectionStringArch}
         | 
| 184 | 
            +
                              initialLabel={
         | 
| 185 | 
            +
                                selectedArch === ''
         | 
| 186 | 
            +
                                  ? resetSelectionStringArch
         | 
| 187 | 
            +
                                  : selectedArch
         | 
| 188 | 
            +
                              }
         | 
| 189 | 
            +
                            />
         | 
| 190 | 
            +
                          </FlexItem>
         | 
| 191 | 
            +
                          <FlexItem>
         | 
| 192 | 
            +
                            <Button variant="primary" onClick={filterProducts}>
         | 
| 193 | 
            +
                              {__('Search')}
         | 
| 194 | 
            +
                            </Button>
         | 
| 195 | 
            +
                          </FlexItem>
         | 
| 196 | 
            +
                          <FlexItem>
         | 
| 197 | 
            +
                            <Button
         | 
| 198 | 
            +
                              variant="link"
         | 
| 199 | 
            +
                              icon={<TimesIcon />}
         | 
| 200 | 
            +
                              onClick={resetTreeForm}
         | 
| 201 | 
            +
                            >
         | 
| 202 | 
            +
                              {__('Reset Selection')}
         | 
| 203 | 
            +
                            </Button>
         | 
| 204 | 
            +
                          </FlexItem>
         | 
| 205 | 
            +
                        </Flex>
         | 
| 206 | 
            +
                        <FlexItem>
         | 
| 207 | 
            +
                          {showSearchTree && (
         | 
| 208 | 
            +
                            <SCCTreePicker
         | 
| 209 | 
            +
                              sccProducts={filteredSccProducts}
         | 
| 210 | 
            +
                              sccAccountId={sccAccountId}
         | 
| 211 | 
            +
                              resetFormFromParent={resetTreeForm}
         | 
| 212 | 
            +
                              handleSubscribeCallback={handleSubscribeCallback}
         | 
| 213 | 
            +
                            />
         | 
| 214 | 
            +
                          )}
         | 
| 215 | 
            +
                        </FlexItem>
         | 
| 216 | 
            +
                      </Flex>
         | 
| 217 | 
            +
                    </CardBody>
         | 
| 218 | 
            +
                  </CardExpandableContent>
         | 
| 219 | 
            +
                </Card>
         | 
| 220 | 
            +
              );
         | 
| 221 | 
            +
            };
         | 
| 222 | 
            +
             | 
| 223 | 
            +
            SCCProductPicker.propTypes = {
         | 
| 224 | 
            +
              sccProducts: PropTypes.array,
         | 
| 225 | 
            +
              sccAccountId: PropTypes.number.isRequired,
         | 
| 226 | 
            +
              editProductId: PropTypes.number,
         | 
| 227 | 
            +
              handleSubscribeCallback: PropTypes.func.isRequired,
         | 
| 228 | 
            +
            };
         | 
| 229 | 
            +
             | 
| 230 | 
            +
            SCCProductPicker.defaultProps = {
         | 
| 231 | 
            +
              sccProducts: [],
         | 
| 232 | 
            +
              editProductId: 0,
         | 
| 233 | 
            +
            };
         | 
| 234 | 
            +
             | 
| 235 | 
            +
            export default SCCProductPicker;
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            @import '~@theforeman/vendor/scss/variables';
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            .pf-c-select__toggle {
         | 
| 4 | 
            +
              font-size: var(--pf-global--FontSize--xs);
         | 
| 5 | 
            +
              font-weight: var(--pf-global--FontWeight--bold);
         | 
| 6 | 
            +
            };
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            .pf-c-switch__input ~ .pf-c-switch__label {
         | 
| 9 | 
            +
                font-size: var(--pf-global--FontSize--xs);
         | 
| 10 | 
            +
            };
         | 
| @@ -0,0 +1,81 @@ | |
| 1 | 
            +
            import React from 'react';
         | 
| 2 | 
            +
            import PropTypes from 'prop-types';
         | 
| 3 | 
            +
            import ForemanModal from 'foremanReact/components/ForemanModal';
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            import { sprintf, translate as __ } from 'foremanReact/common/I18n';
         | 
| 6 | 
            +
            import {
         | 
| 7 | 
            +
              Card,
         | 
| 8 | 
            +
              CardBody,
         | 
| 9 | 
            +
              TextContent,
         | 
| 10 | 
            +
              Text,
         | 
| 11 | 
            +
              TextList,
         | 
| 12 | 
            +
              TextListItem,
         | 
| 13 | 
            +
              TextListVariants,
         | 
| 14 | 
            +
              TextVariants,
         | 
| 15 | 
            +
            } from '@patternfly/react-core';
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            const generateRepoList = (repoNames, productName) => (
         | 
| 18 | 
            +
              <>
         | 
| 19 | 
            +
                <TextListItem key={productName}>{productName}</TextListItem>
         | 
| 20 | 
            +
                <TextList key={repoNames}>
         | 
| 21 | 
            +
                  {repoNames.map((repo) => (
         | 
| 22 | 
            +
                    <TextListItem key={repo}>{repo}</TextListItem>
         | 
| 23 | 
            +
                  ))}
         | 
| 24 | 
            +
                </TextList>
         | 
| 25 | 
            +
              </>
         | 
| 26 | 
            +
            );
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            const generateProductList = (reposToSubscribe) => (
         | 
| 29 | 
            +
              <TextList
         | 
| 30 | 
            +
                key={reposToSubscribe.map((p) => p.productName)}
         | 
| 31 | 
            +
                component={TextListVariants.ol}
         | 
| 32 | 
            +
              >
         | 
| 33 | 
            +
                {reposToSubscribe.map((p) => generateRepoList(p.repoNames, p.productName))}
         | 
| 34 | 
            +
              </TextList>
         | 
| 35 | 
            +
            );
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            const SCCProductPickerModal = ({ id, taskId, reposToSubscribe }) => (
         | 
| 38 | 
            +
              <>
         | 
| 39 | 
            +
                <ForemanModal
         | 
| 40 | 
            +
                  id={id}
         | 
| 41 | 
            +
                  title={__('Summary of SCC product subscription')}
         | 
| 42 | 
            +
                  enforceFocus
         | 
| 43 | 
            +
                >
         | 
| 44 | 
            +
                  <Card>
         | 
| 45 | 
            +
                    <CardBody>
         | 
| 46 | 
            +
                      <TextContent key={taskId}>
         | 
| 47 | 
            +
                        <Text key={'modal1'.concat(taskId)}>
         | 
| 48 | 
            +
                          {__('The subscription task with id ')}
         | 
| 49 | 
            +
                          <Text
         | 
| 50 | 
            +
                            key={'modal2'.concat(taskId)}
         | 
| 51 | 
            +
                            component={TextVariants.a}
         | 
| 52 | 
            +
                            target="_blank"
         | 
| 53 | 
            +
                            href={sprintf('/foreman_tasks/tasks/%s', taskId)}
         | 
| 54 | 
            +
                          >
         | 
| 55 | 
            +
                            {sprintf('%s', taskId)}
         | 
| 56 | 
            +
                          </Text>
         | 
| 57 | 
            +
                          {__(' has started successfully.')}
         | 
| 58 | 
            +
                        </Text>
         | 
| 59 | 
            +
                        <Text key={'modal3'.concat(taskId)}>
         | 
| 60 | 
            +
                          {__('The following products will be imported:')}
         | 
| 61 | 
            +
                        </Text>
         | 
| 62 | 
            +
                        {generateProductList(reposToSubscribe)}
         | 
| 63 | 
            +
                      </TextContent>
         | 
| 64 | 
            +
                    </CardBody>
         | 
| 65 | 
            +
                  </Card>
         | 
| 66 | 
            +
                </ForemanModal>
         | 
| 67 | 
            +
              </>
         | 
| 68 | 
            +
            );
         | 
| 69 | 
            +
            SCCProductPickerModal.propTypes = {
         | 
| 70 | 
            +
              id: PropTypes.string,
         | 
| 71 | 
            +
              taskId: PropTypes.string,
         | 
| 72 | 
            +
              reposToSubscribe: PropTypes.array,
         | 
| 73 | 
            +
            };
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            SCCProductPickerModal.defaultProps = {
         | 
| 76 | 
            +
              id: '',
         | 
| 77 | 
            +
              taskId: '',
         | 
| 78 | 
            +
              reposToSubscribe: {},
         | 
| 79 | 
            +
            };
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            export default SCCProductPickerModal;
         |