foreman_scc_manager 4.0.1 → 5.0.1

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 (19) hide show
  1. checksums.yaml +4 -4
  2. data/app/views/scc_accounts/_form.html.erb +2 -2
  3. data/lib/foreman_scc_manager/version.rb +1 -1
  4. data/package.json +6 -6
  5. data/webpack/components/SCCProductPage/EmptySccProducts.js +7 -2
  6. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +238 -50
  7. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +26 -12
  8. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +2 -2
  9. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +5 -3
  10. data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +48 -33
  11. data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +5 -5
  12. data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +13 -6
  13. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +36 -23
  14. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +10 -10
  15. data/webpack/components/SCCProductPage/components/SCCProductView/index.js +26 -8
  16. data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/SCCGenericExpander.test.js +2 -2
  17. data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +28 -10
  18. data/webpack/components/SCCProductPage/sccProductPage.scss +5 -5
  19. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5307af5cb7f7122f8b8da70689196f3581fe1cc5f4c2f7d6cd1c810de9e31edf
4
- data.tar.gz: 7ca7452f2537fc702da58bf28343bf3ccf22ae41e86b4a3b579eb68f5571e00d
3
+ metadata.gz: f5923a02d6b72dbf102333663649e450179368e8b0d6a1566b0d7d213d95109b
4
+ data.tar.gz: 86fd3c0181f90d5d2c216e10392b5923785424feb8a3b4c11d3a4cf1fe98ba20
5
5
  SHA512:
6
- metadata.gz: 52ad207e5912f9f12413c108f0040e4aeb3c7489ad5b7d85e661d5272db07251d121455581ea63e0b8db7792d4b7d5583001772395cb3aa7b2c3643748bc0d4b
7
- data.tar.gz: 69abae49566440f0720ef7b32dd655130826b570a014cbfc328ef6c26a9912bef1272c1337e8f66505a2f9e62bd7d273c216b8a18404a74ad66cdbf770013d82
6
+ metadata.gz: 75a87ef0e985142ddd8613158fe033c0ed2019657384877d405b337d53016d221db3e78d1db3ac87269bb91592d8044e0d60d264303b565e9e584295136c8016
7
+ data.tar.gz: 35702ad2e304588c68273bab350d12ec1e1c676b02a0767edeaecc75a9cc199c7087bd22970edddad76f10463c0602a957f3e0b647effc81ca3f040958781ced
@@ -15,8 +15,8 @@
15
15
  <%= password_f f, :password %>
16
16
  <%= text_f f, :base_url, label: _('Base URL') %>
17
17
  <%= selectable_f f, :interval, SccAccount::TYPES, {},
18
- { :label => _('Sync interval'), :help_block => _("The sync interval is used to periodically update the SCC authentication tokens of any imported products.") } %>
19
- <%= field f, :sync_date, label: _('Sync Date') do
18
+ { :label => _('Token refresh interval'), :help_block => _("The token refresh interval is used to periodically update the SCC authentication tokens of any imported products.") } %>
19
+ <%= field f, :sync_date, label: _('Token refresh time'), :help_block => _("Specifies the daily time when the SCC authentication token refresh process starts. Set this to a time outside of business hours (e.g., during the night) to minimize disruption.") do
20
20
  f.datetime_field :sync_date, placeholder: Time.now
21
21
  end %>
22
22
  <%= selectable_f f, :katello_gpg_key_id, @selectable_gpg_keys, {},
@@ -1,3 +1,3 @@
1
1
  module ForemanSccManager
2
- VERSION = '4.0.1'.freeze
2
+ VERSION = '5.0.1'.freeze
3
3
  end
data/package.json CHANGED
@@ -22,16 +22,16 @@
22
22
  "devDependencies": {
23
23
  "@babel/core": "^7.7.0",
24
24
  "babel-eslint": ">= 10.0.3",
25
- "@theforeman/builder": ">= 10.0",
26
- "@theforeman/eslint-plugin-foreman": ">= 10.0",
27
- "@theforeman/find-foreman": ">= 10.0",
28
- "@theforeman/test": ">= 10.0",
29
- "@theforeman/vendor-dev": ">= 10.0",
25
+ "@theforeman/builder": "^15.0.0",
26
+ "@theforeman/eslint-plugin-foreman": "^15.0.0",
27
+ "@theforeman/find-foreman": "^15.0.0",
28
+ "@theforeman/test": "^15.0.0",
29
+ "@theforeman/vendor-dev": "^15.0.0",
30
30
  "eslint": "^6.7.2",
31
31
  "eslint-plugin-spellcheck": ">= 0.0.17",
32
32
  "eslint-plugin-react": ">= 7.27.1",
33
33
  "eslint-plugin-react-hooks": ">= 4.3.0",
34
- "prettier": "^2.5.1"
34
+ "prettier": "^1.19.1"
35
35
  },
36
36
  "keywords": [
37
37
  "SUSE",
@@ -23,10 +23,15 @@ export const EmptySccProducts = ({ canCreate, sccAccountId }) => {
23
23
  header={__('SUSE Customer Center')}
24
24
  description={<div dangerouslySetInnerHTML={{ __html: content }} />}
25
25
  documentation={{
26
- url: 'https://docs.orcharhino.com/or/docs/sources/usage_guides/managing_sles_systems_guide.html#mssg_adding_scc_accounts',
26
+ url:
27
+ 'https://docs.orcharhino.com/or/docs/sources/usage_guides/managing_sles_systems_guide.html#mssg_adding_scc_accounts',
27
28
  }}
28
29
  />
29
- <Button onClick={onSyncStart} className="btn btn-primary">
30
+ <Button
31
+ onClick={onSyncStart}
32
+ ouiaId="scc-manager-welcome-sync-products"
33
+ className="btn btn-primary"
34
+ >
30
35
  {__('Synchronize SUSE Account')}
31
36
  </Button>
32
37
  </>
@@ -1,79 +1,267 @@
1
+ // Reference: https://v5-archive.patternfly.org/components/menus/select#typeahead
2
+
1
3
  import React, { useState, useEffect } from 'react';
2
4
  import PropTypes from 'prop-types';
3
- import { ContextSelector, ContextSelectorItem } from '@patternfly/react-core';
5
+ import {
6
+ Select,
7
+ SelectOption,
8
+ SelectList,
9
+ MenuToggle,
10
+ TextInputGroup,
11
+ TextInputGroupMain,
12
+ TextInputGroupUtilities,
13
+ Button,
14
+ } from '@patternfly/react-core';
15
+ import TimesIcon from '@patternfly/react-icons';
16
+ import { translate as __ } from 'foremanReact/common/I18n';
4
17
 
5
18
  const GenericSelector = ({
6
- selectionItems,
7
- setGlobalSelected,
8
- screenReaderLabel,
19
+ initialSelectOptions,
20
+ setSelected,
9
21
  initialLabel,
22
+ selected,
23
+ inputValue,
24
+ setInputValue,
10
25
  }) => {
11
26
  const [isOpen, setIsOpen] = useState(false);
12
- const [selected, setSelected] = useState(initialLabel);
13
- const [searchValue, setSearchValue] = useState('');
14
- const [filteredItems, setFilteredItems] = useState(selectionItems);
27
+ const [filterValue, setFilterValue] = useState('');
28
+ const [selectOptions, setSelectOptions] = useState(
29
+ initialSelectOptions.map((option) => ({
30
+ children: option,
31
+ value: option,
32
+ }))
33
+ );
34
+ const [focusedItemIndex, setFocusedItemIndex] = useState(null);
35
+ const [activeItemId, setActiveItemId] = useState(null);
36
+ const NO_RESULTS = __('no results');
15
37
 
16
38
  useEffect(() => {
17
- setFilteredItems(selectionItems);
18
- setSelected(initialLabel);
19
- }, [selectionItems, initialLabel]);
20
-
21
- const onToggle = (selectorOpen) => {
22
- setIsOpen(selectorOpen);
39
+ let newSelectOptions = initialSelectOptions.map((option) => ({
40
+ children: option,
41
+ value: option,
42
+ }));
43
+ if (filterValue) {
44
+ newSelectOptions = newSelectOptions.filter((menuItem) =>
45
+ String(menuItem.children)
46
+ .toLowerCase()
47
+ .includes(filterValue.toLowerCase())
48
+ );
49
+ if (!newSelectOptions.length) {
50
+ newSelectOptions = [
51
+ {
52
+ isAriaDisabled: true,
53
+ children: `No results found for "${filterValue}"`,
54
+ value: NO_RESULTS,
55
+ },
56
+ ];
57
+ }
58
+ if (!isOpen) {
59
+ setIsOpen(true);
60
+ }
61
+ }
62
+ setSelectOptions(newSelectOptions);
63
+ }, [filterValue, initialSelectOptions]);
64
+ const createItemId = (value) =>
65
+ `select-typeahead-${value?.replace(' ', '-')}`;
66
+ const setActiveAndFocusedItem = (itemIndex) => {
67
+ setFocusedItemIndex(itemIndex);
68
+ const focusedItem = selectOptions[itemIndex];
69
+ setActiveItemId(createItemId(focusedItem.value));
23
70
  };
24
-
25
- const onSelect = (event, value) => {
26
- setSelected(value);
27
- setIsOpen(!isOpen);
28
- setGlobalSelected(value);
71
+ const resetActiveAndFocusedItem = () => {
72
+ setFocusedItemIndex(null);
73
+ setActiveItemId(null);
29
74
  };
30
-
31
- const onSearchInputChange = (value) => {
32
- setSearchValue(value);
75
+ const closeMenu = () => {
76
+ setIsOpen(false);
77
+ resetActiveAndFocusedItem();
33
78
  };
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 || []);
79
+ const onInputClick = () => {
80
+ if (!isOpen) {
81
+ setIsOpen(true);
82
+ } else if (!inputValue) {
83
+ closeMenu();
84
+ }
85
+ };
86
+ const selectOption = (value, content) => {
87
+ setInputValue(String(content));
88
+ setFilterValue('');
89
+ setSelected(String(value));
90
+ closeMenu();
45
91
  };
92
+ const onSelect = (_event, value) => {
93
+ if (value && value !== NO_RESULTS) {
94
+ const optionText = selectOptions.find((option) => option.value === value)
95
+ ?.children;
96
+ selectOption(value, optionText);
97
+ }
98
+ };
99
+ const onTextInputChange = (_event, value) => {
100
+ setInputValue(value);
101
+ setFilterValue(value);
102
+ resetActiveAndFocusedItem();
103
+ if (value !== selected) {
104
+ setSelected('');
105
+ }
106
+ };
107
+ const handleMenuArrowKeys = (key) => {
108
+ let indexToFocus = 0;
109
+ if (!isOpen) {
110
+ setIsOpen(true);
111
+ }
112
+ if (selectOptions.every((option) => option.isDisabled)) {
113
+ return;
114
+ }
115
+ if (key === 'ArrowUp') {
116
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
117
+ indexToFocus = selectOptions.length - 1;
118
+ } else {
119
+ indexToFocus = focusedItemIndex - 1;
120
+ }
121
+ while (selectOptions[indexToFocus].isDisabled) {
122
+ indexToFocus--;
123
+ if (indexToFocus === -1) {
124
+ indexToFocus = selectOptions.length - 1;
125
+ }
126
+ }
127
+ }
128
+ if (key === 'ArrowDown') {
129
+ if (
130
+ focusedItemIndex === null ||
131
+ focusedItemIndex === selectOptions.length - 1
132
+ ) {
133
+ indexToFocus = 0;
134
+ } else {
135
+ indexToFocus = focusedItemIndex + 1;
136
+ }
137
+ while (selectOptions[indexToFocus].isDisabled) {
138
+ indexToFocus++;
139
+ if (indexToFocus === selectOptions.length) {
140
+ indexToFocus = 0;
141
+ }
142
+ }
143
+ }
144
+ setActiveAndFocusedItem(indexToFocus);
145
+ };
146
+ const onInputKeyDown = (event) => {
147
+ const focusedItem =
148
+ focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null;
149
+ switch (event.key) {
150
+ case 'Enter':
151
+ if (
152
+ isOpen &&
153
+ focusedItem &&
154
+ focusedItem.value !== NO_RESULTS &&
155
+ !focusedItem.isAriaDisabled
156
+ ) {
157
+ selectOption(focusedItem.value, focusedItem.children);
158
+ }
159
+ if (!isOpen) {
160
+ setIsOpen(true);
161
+ }
162
+ break;
163
+ case 'ArrowUp':
164
+ case 'ArrowDown':
165
+ event.preventDefault();
166
+ handleMenuArrowKeys(event.key);
167
+ break;
168
+ default:
169
+ break;
170
+ }
171
+ };
172
+ const onToggleClick = () => {
173
+ setIsOpen(!isOpen);
174
+ };
175
+ const onClearButtonClick = () => {
176
+ setSelected('');
177
+ setInputValue('');
178
+ setFilterValue('');
179
+ resetActiveAndFocusedItem();
180
+ };
181
+ const toggle = (toggleRef) => (
182
+ <MenuToggle
183
+ ref={toggleRef}
184
+ variant="typeahead"
185
+ onClick={onToggleClick}
186
+ isExpanded={isOpen}
187
+ isFullWidth
188
+ >
189
+ <TextInputGroup isPlain>
190
+ <TextInputGroupMain
191
+ value={inputValue}
192
+ onClick={onInputClick}
193
+ onChange={onTextInputChange}
194
+ onKeyDown={onInputKeyDown}
195
+ id={initialLabel.concat('typeahead-select-input')}
196
+ autoComplete="off"
197
+ placeholder={initialLabel}
198
+ {...(activeItemId && {
199
+ 'aria-activedescendant': activeItemId,
200
+ })}
201
+ role="combobox"
202
+ isExpanded={isOpen}
203
+ />
46
204
 
205
+ <TextInputGroupUtilities
206
+ {...(!inputValue
207
+ ? {
208
+ style: {
209
+ display: 'none',
210
+ },
211
+ }
212
+ : {})}
213
+ >
214
+ <Button
215
+ variant="plain"
216
+ ouiaId={initialLabel.concat('scc-product-picker-button')}
217
+ onClick={onClearButtonClick}
218
+ >
219
+ <TimesIcon aria-hidden />
220
+ </Button>
221
+ </TextInputGroupUtilities>
222
+ </TextInputGroup>
223
+ </MenuToggle>
224
+ );
47
225
  return (
48
- <ContextSelector
49
- toggleText={selected}
50
- onSearchInputChange={onSearchInputChange}
226
+ <Select
227
+ id={initialLabel}
228
+ ouiaId={initialLabel.concat('scc-product-picker-select')}
51
229
  isOpen={isOpen}
52
- searchInputValue={searchValue}
53
- onToggle={onToggle}
230
+ selected={selected}
54
231
  onSelect={onSelect}
55
- onSearchButtonClick={onSearchButtonClick}
56
- screenReaderLabel={screenReaderLabel}
232
+ onOpenChange={(isOpenSelect) => {
233
+ !isOpenSelect && closeMenu();
234
+ }}
235
+ toggle={toggle}
236
+ shouldFocusFirstItemOnOpen={false}
57
237
  >
58
- {filteredItems.map((item, index) => (
59
- <ContextSelectorItem key={index}>
60
- {item || 'no arch'}
61
- </ContextSelectorItem>
62
- ))}
63
- </ContextSelector>
238
+ <SelectList id={initialLabel.concat('select-typeahead-listbox')}>
239
+ {selectOptions.map((option, index) => (
240
+ <SelectOption
241
+ key={option.value || option.children}
242
+ isFocused={focusedItemIndex === index}
243
+ id={createItemId(option.value)}
244
+ className={option.className}
245
+ {...option}
246
+ ref={null}
247
+ />
248
+ ))}
249
+ </SelectList>
250
+ </Select>
64
251
  );
65
252
  };
66
253
 
67
254
  GenericSelector.propTypes = {
68
- selectionItems: PropTypes.array,
69
- setGlobalSelected: PropTypes.func.isRequired,
70
- screenReaderLabel: PropTypes.string,
255
+ initialSelectOptions: PropTypes.array,
71
256
  initialLabel: PropTypes.string,
257
+ selected: PropTypes.string.isRequired,
258
+ setSelected: PropTypes.func.isRequired,
259
+ inputValue: PropTypes.string.isRequired,
260
+ setInputValue: PropTypes.func.isRequired,
72
261
  };
73
262
 
74
263
  GenericSelector.defaultProps = {
75
- selectionItems: [],
76
- screenReaderLabel: '',
264
+ initialSelectOptions: [],
77
265
  initialLabel: '',
78
266
  };
79
267
 
@@ -1,16 +1,25 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { sprintf, translate as __ } from 'foremanReact/common/I18n';
4
- import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
4
+ import {
5
+ Select,
6
+ SelectOption,
7
+ SelectList,
8
+ MenuToggle,
9
+ } from '@patternfly/react-core';
5
10
 
6
11
  import './styles.scss';
7
12
 
8
- const createRepoSelectOption = (repo, disableRepos) => (
13
+ const createRepoSelectOption = (repo, disableRepos, selectedItems) => (
9
14
  <SelectOption
15
+ hasCheckbox
10
16
  key={repo.id}
11
17
  isDisabled={repo.katello_repository_id !== null || disableRepos}
12
18
  value={repo.name}
13
- />
19
+ isSelected={selectedItems.includes(repo.name)}
20
+ >
21
+ {repo.name}
22
+ </SelectOption>
14
23
  );
15
24
 
16
25
  const setRepoSelection = (
@@ -63,8 +72,9 @@ const SCCRepoPicker = ({
63
72
  productAlreadySynced
64
73
  )
65
74
  );
66
- const onToggle = (toggle) => {
67
- setIsOpen(toggle);
75
+
76
+ const onToggleClick = () => {
77
+ setIsOpen(!isOpen);
68
78
  };
69
79
 
70
80
  useEffect(() => {
@@ -135,21 +145,25 @@ const SCCRepoPicker = ({
135
145
  };
136
146
 
137
147
  const selectOptions = sccRepos.map((repo) =>
138
- createRepoSelectOption(repo, disableRepos)
148
+ createRepoSelectOption(repo, disableRepos, selected)
149
+ );
150
+
151
+ const toggle = (toggleRef) => (
152
+ <MenuToggle ref={toggleRef} onClick={onToggleClick} isExpanded={isOpen}>
153
+ {sprintf(__('%s/%s'), selected.length, sccRepos.length)}
154
+ </MenuToggle>
139
155
  );
140
156
 
141
157
  return (
142
158
  <Select
143
- variant={SelectVariant.checkbox}
144
- isCheckboxSelectionBadgeHidden
145
- onToggle={onToggle}
159
+ ouiaId={sccProductId.toString().concat('scc-manager-repo-picker')}
160
+ toggle={toggle}
146
161
  onSelect={onSelect}
147
162
  selections={selected}
148
163
  isOpen={isOpen}
149
- isDisabled={disableRepos}
150
- placeholderText={sprintf(__('%s/%s'), selected.length, sccRepos.length)}
164
+ onOpenChange={(nextOpen) => setIsOpen(nextOpen)}
151
165
  >
152
- {selectOptions}
166
+ <SelectList>{selectOptions}</SelectList>
153
167
  </Select>
154
168
  );
155
169
  };
@@ -1,3 +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;
1
+ .pf-v5-c-tree-view__node .pf-v5-c-tree-view__node-count .pf-v5-c-badge.pf-m-read {
2
+ --pf-v5-c-badge--m-read--BackgroundColor: transparent;
3
3
  }
@@ -38,7 +38,7 @@ const addSCCRepoPickerToTree = (
38
38
  ) => {
39
39
  tree.customBadgeContent = [];
40
40
  tree.customBadgeContent.push(
41
- <Tooltip content={__('Filter repositories')}>
41
+ <Tooltip aria="none" aria-live="polite" content={__('Filter repositories')}>
42
42
  <SCCRepoPicker
43
43
  sccRepos={tree.scc_repositories}
44
44
  disableRepos={tree.product_id === null && !tree.checkProps.checked}
@@ -234,7 +234,7 @@ const SCCTreePicker = ({
234
234
  };
235
235
 
236
236
  return (
237
- <Card>
237
+ <Card ouiaId="scc-manager-product-picker-card">
238
238
  <CardBody>
239
239
  <Flex direction={{ default: 'column' }}>
240
240
  <Flex>
@@ -249,6 +249,7 @@ const SCCTreePicker = ({
249
249
  >
250
250
  <Switch
251
251
  id="filter-debug-switch"
252
+ ouiaId="scc-manager-filter-debug-switch"
252
253
  onChange={debugFilterChange}
253
254
  isChecked={activateDebugFilter}
254
255
  label={__('Include Debug and Source Pool repositories')}
@@ -261,13 +262,14 @@ const SCCTreePicker = ({
261
262
  data={sccProductTree}
262
263
  allExpanded={expandAll}
263
264
  onCheck={onCheck}
264
- hasChecks
265
+ hasCheckboxes
265
266
  hasBadges
266
267
  hasGuides
267
268
  />
268
269
  </Flex>
269
270
  <Flex>
270
271
  <Button
272
+ ouiaId="scc-manager-add-products-button"
271
273
  variant="primary"
272
274
  onClick={submitForm}
273
275
  isDisabled={Object.keys(selectedRepos).length === 0}
@@ -24,9 +24,6 @@ const resetSelectionStringArch = __(' -- Select Architecture --');
24
24
  const genericFilter = (object, comparator) =>
25
25
  // we can have architectures that are not set
26
26
  comparator === '' ||
27
- comparator === resetSelectionStringProduct ||
28
- comparator === resetSelectionStringVersion ||
29
- comparator === resetSelectionStringArch ||
30
27
  object === comparator ||
31
28
  (object === null && comparator === 'no arch');
32
29
 
@@ -61,6 +58,9 @@ const SCCProductPicker = ({
61
58
  const [filteredSccProducts, setFilteredSccProducts] = useState([]);
62
59
  const [showSearchTree, setShowSearchTree] = useState(false);
63
60
  const [isExpanded, setIsExpanded] = useState(false);
61
+ const [searchInputProduct, setSearchInputProduct] = useState('');
62
+ const [searchInputVersion, setSearchInputVersion] = useState('');
63
+ const [searchInputArch, setSearchInputArch] = useState('');
64
64
 
65
65
  useEffect(() => {
66
66
  if (editProductId !== 0) {
@@ -78,10 +78,12 @@ const SCCProductPicker = ({
78
78
  }, [editProductId, sccProducts]);
79
79
 
80
80
  const onProductSelectionChange = (value) => {
81
- if (value !== resetSelectionStringProduct) {
82
- setVersionItems(filterVersionByProduct(sccProducts, value));
83
- } else {
81
+ if (value === '') {
84
82
  setVersionItems([]);
83
+ setSearchInputVersion('');
84
+ setSearchInputArch('');
85
+ } else {
86
+ setVersionItems(filterVersionByProduct(sccProducts, value));
85
87
  }
86
88
  setSelectedProduct(value);
87
89
  setArchItems([]);
@@ -90,8 +92,9 @@ const SCCProductPicker = ({
90
92
  };
91
93
 
92
94
  const onVersionSelectionChange = (value) => {
93
- if (value === resetSelectionStringVersion) {
95
+ if (value === '') {
94
96
  setArchItems([]);
97
+ setSearchInputArch('');
95
98
  } else {
96
99
  setArchItems(
97
100
  filterArchByVersionAndProduct(sccProducts, selectedProduct, value)
@@ -102,6 +105,9 @@ const SCCProductPicker = ({
102
105
  };
103
106
 
104
107
  const onArchSelectionChange = (value) => {
108
+ if (value === '') {
109
+ setArchItems([]);
110
+ }
105
111
  setSelectedArch(value);
106
112
  };
107
113
 
@@ -126,10 +132,20 @@ const SCCProductPicker = ({
126
132
  setSelectedProduct('');
127
133
  setSelectedVersion('');
128
134
  setSelectedArch('');
135
+ setVersionItems([]);
136
+ setArchItems([]);
137
+ setSearchInputProduct('');
138
+ setSearchInputVersion('');
139
+ setSearchInputArch('');
129
140
  };
130
141
 
131
142
  return (
132
- <Card border="dark" id="product-selection-card" isExpanded={isExpanded}>
143
+ <Card
144
+ border="dark"
145
+ ouiaId="scc-manager-product-selection-card"
146
+ id="product-selection-card"
147
+ isExpanded={isExpanded}
148
+ >
133
149
  <CardHeader onExpand={onExpand}>
134
150
  <CardTitle>{__('Select SUSE products')}</CardTitle>
135
151
  </CardHeader>
@@ -139,62 +155,61 @@ const SCCProductPicker = ({
139
155
  <Flex>
140
156
  <FlexItem>
141
157
  <SCCGenericPicker
142
- key="prod-select"
143
- selectionItems={
144
- selectedProduct === ''
145
- ? productItems
146
- : [resetSelectionStringProduct].concat(productItems)
147
- }
148
- setGlobalSelected={onProductSelectionChange}
149
- screenReaderLabel={resetSelectionStringProduct}
158
+ key="scc-prod-select"
159
+ initialSelectOptions={productItems}
160
+ setSelected={onProductSelectionChange}
150
161
  initialLabel={
151
162
  selectedProduct === ''
152
163
  ? resetSelectionStringProduct
153
164
  : selectedProduct
154
165
  }
166
+ selected={selectedProduct}
167
+ inputValue={searchInputProduct}
168
+ setInputValue={setSearchInputProduct}
155
169
  />
156
170
  </FlexItem>
157
171
  <FlexItem>
158
172
  <SCCGenericPicker
159
- key="vers-select"
160
- selectionItems={
161
- selectedVersion === ''
162
- ? versionItems
163
- : [resetSelectionStringVersion].concat(versionItems)
164
- }
165
- setGlobalSelected={onVersionSelectionChange}
166
- screenReaderLabel={resetSelectionStringVersion}
173
+ key="scc-vers-select"
174
+ initialSelectOptions={versionItems}
175
+ setSelected={onVersionSelectionChange}
167
176
  initialLabel={
168
177
  selectedVersion === ''
169
178
  ? resetSelectionStringVersion
170
179
  : selectedVersion
171
180
  }
181
+ selected={selectedVersion}
182
+ inputValue={searchInputVersion}
183
+ setInputValue={setSearchInputVersion}
172
184
  />
173
185
  </FlexItem>
174
186
  <FlexItem>
175
187
  <SCCGenericPicker
176
- key="arch-select"
177
- selectionItems={
178
- selectedArch === ''
179
- ? archItems
180
- : [resetSelectionStringArch].concat(archItems)
181
- }
182
- setGlobalSelected={onArchSelectionChange}
183
- screenReaderLabel={resetSelectionStringArch}
188
+ key="scc-arch-select"
189
+ initialSelectOptions={archItems}
190
+ setSelected={onArchSelectionChange}
184
191
  initialLabel={
185
192
  selectedArch === ''
186
193
  ? resetSelectionStringArch
187
194
  : selectedArch
188
195
  }
196
+ selected={selectedArch}
197
+ inputValue={searchInputArch}
198
+ setInputValue={setSearchInputArch}
189
199
  />
190
200
  </FlexItem>
191
201
  <FlexItem>
192
- <Button variant="primary" onClick={filterProducts}>
202
+ <Button
203
+ ouiaId="scc-manager-product-search-button"
204
+ variant="primary"
205
+ onClick={filterProducts}
206
+ >
193
207
  {__('Search')}
194
208
  </Button>
195
209
  </FlexItem>
196
210
  <FlexItem>
197
211
  <Button
212
+ ouiaId="scc-manager-product-search-reset-button"
198
213
  variant="link"
199
214
  icon={<TimesIcon />}
200
215
  onClick={resetTreeForm}
@@ -1,10 +1,10 @@
1
1
  @import '~@theforeman/vendor/scss/variables';
2
2
 
3
- .pf-c-select__toggle {
4
- font-size: var(--pf-global--FontSize--xs);
5
- font-weight: var(--pf-global--FontWeight--bold);
3
+ .pf-v5-c-select__toggle {
4
+ font-size: var(--pf-v5-global--FontSize--xs);
5
+ font-weight: var(--pf-v5-global--FontWeight--bold);
6
6
  };
7
7
 
8
- .pf-c-switch__input ~ .pf-c-switch__label {
9
- font-size: var(--pf-global--FontSize--xs);
8
+ .pf-v5-c-switch__input ~ .pf-v5-c-switch__label {
9
+ font-size: var(--pf-v5-global--FontSize--xs);
10
10
  };
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { Fragment } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import ForemanModal from 'foremanReact/components/ForemanModal';
4
4
 
@@ -15,14 +15,14 @@ import {
15
15
  } from '@patternfly/react-core';
16
16
 
17
17
  const generateRepoList = (repoNames, productName) => (
18
- <>
18
+ <Fragment key={productName}>
19
19
  <TextListItem key={productName}>{productName}</TextListItem>
20
20
  <TextList key={repoNames}>
21
21
  {repoNames.map((repo) => (
22
22
  <TextListItem key={repo}>{repo}</TextListItem>
23
23
  ))}
24
24
  </TextList>
25
- </>
25
+ </Fragment>
26
26
  );
27
27
 
28
28
  const generateProductList = (reposToSubscribe) => (
@@ -41,12 +41,16 @@ const SCCProductPickerModal = ({ id, taskId, reposToSubscribe }) => (
41
41
  title={__('Summary of SCC product subscription')}
42
42
  enforceFocus
43
43
  >
44
- <Card>
44
+ <Card ouiaId="scc-product-picker-modal">
45
45
  <CardBody>
46
46
  <TextContent key={taskId}>
47
- <Text key={'modal1'.concat(taskId)}>
47
+ <Text
48
+ ouiaId={'modal1'.concat(taskId)}
49
+ key={'modal1'.concat(taskId)}
50
+ >
48
51
  {__('The subscription task with id ')}
49
52
  <Text
53
+ ouiaId={'modal2'.concat(taskId)}
50
54
  key={'modal2'.concat(taskId)}
51
55
  component={TextVariants.a}
52
56
  target="_blank"
@@ -56,7 +60,10 @@ const SCCProductPickerModal = ({ id, taskId, reposToSubscribe }) => (
56
60
  </Text>
57
61
  {__(' has started successfully.')}
58
62
  </Text>
59
- <Text key={'modal3'.concat(taskId)}>
63
+ <Text
64
+ ouiaId={'modal3'.concat(taskId)}
65
+ key={'modal3'.concat(taskId)}
66
+ >
60
67
  {__('The following products will be imported:')}
61
68
  </Text>
62
69
  {generateProductList(reposToSubscribe)}
@@ -5,10 +5,11 @@ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
5
5
  import PropTypes from 'prop-types';
6
6
  import React, { useState } from 'react';
7
7
  import {
8
+ Tooltip,
8
9
  Dropdown,
9
10
  DropdownItem,
10
- BadgeToggle,
11
- Tooltip,
11
+ DropdownList,
12
+ MenuToggle,
12
13
  } from '@patternfly/react-core';
13
14
  import { Icon } from 'patternfly-react';
14
15
  import { BrowserRouter, Link } from 'react-router-dom';
@@ -20,7 +21,10 @@ const createKatelloRepoLink = (repo, sccProductId) => {
20
21
  `/products/${sccProductId}/repositories/${repo.katello_repository_id}`
21
22
  );
22
23
  return (
23
- <Tooltip content={__('Go to Repository page')}>
24
+ <Tooltip
25
+ key={'scc-manager-repo-view-tooltip-'.concat(repo.id)}
26
+ content={__('Go to Repository page')}
27
+ >
24
28
  <BrowserRouter>
25
29
  <Link to={url} target="_blank">
26
30
  {repo.name}
@@ -33,18 +37,25 @@ const createKatelloRepoLink = (repo, sccProductId) => {
33
37
  const createRepoDropDownItem = (repo, sccProductId) => (
34
38
  <DropdownItem
35
39
  key={repo.id}
40
+ ouiaId={repo.id}
36
41
  component="button"
37
42
  icon={
38
43
  repo.subscription_valid ? (
39
44
  repo.katello_repository_id !== null ? (
40
45
  <Icon name="check" type="fa" />
41
46
  ) : (
42
- <Tooltip content={__('Repository not imported')}>
47
+ <Tooltip
48
+ key={'scc-manager-repo-link-tooltip-'.concat(repo.id)}
49
+ content={__('Repository not imported')}
50
+ >
43
51
  <Icon name="ban" type="fa" />
44
52
  </Tooltip>
45
53
  )
46
54
  ) : (
47
- <Tooltip content={__('Please check your SUSE subscription')}>
55
+ <Tooltip
56
+ key={'scc-manager-repo-link-tooltip-'.concat(repo.id)}
57
+ content={__('Please check your SUSE subscription')}
58
+ >
48
59
  <Icon name="warning-triangle-o" type="pf" size="2x" />
49
60
  </Tooltip>
50
61
  )
@@ -58,8 +69,8 @@ const createRepoDropDownItem = (repo, sccProductId) => (
58
69
 
59
70
  const SCCRepoView = ({ sccRepos, sccProductId }) => {
60
71
  const [isOpen, setIsOpen] = useState(false);
61
- const onToggle = (toggle) => {
62
- setIsOpen(toggle);
72
+ const onToggleClick = () => {
73
+ setIsOpen(!isOpen);
63
74
  };
64
75
 
65
76
  const onFocus = () => {
@@ -70,7 +81,7 @@ const SCCRepoView = ({ sccRepos, sccProductId }) => {
70
81
  };
71
82
 
72
83
  const onSelect = (event) => {
73
- setIsOpen(!isOpen);
84
+ setIsOpen(false);
74
85
  onFocus();
75
86
  };
76
87
 
@@ -78,25 +89,27 @@ const SCCRepoView = ({ sccRepos, sccProductId }) => {
78
89
  createRepoDropDownItem(repo, sccProductId)
79
90
  );
80
91
 
92
+ const toggle = (toggleRef) => (
93
+ <MenuToggle ref={toggleRef} onClick={onToggleClick} isExpanded={isOpen}>
94
+ {sprintf(
95
+ __('Repositories (%s/%s)'),
96
+ sccRepos.filter((r) => r.katello_repository_id !== null).length,
97
+ sccRepos.length
98
+ )}
99
+ </MenuToggle>
100
+ );
101
+
81
102
  return (
82
103
  <Dropdown
104
+ ouiaId={sccProductId?.toString().concat('scc-manager-repo-view')}
105
+ onClick={onToggleClick}
83
106
  onSelect={onSelect}
84
- toggle={
85
- <BadgeToggle
86
- id={sprintf('scc-repo-show-toggle-id-%s', sccProductId)}
87
- key={sprintf('scc-repo-show-toggle-id-%s', sccProductId)}
88
- onToggle={onToggle}
89
- >
90
- {sprintf(
91
- __('Repositories (%s/%s)'),
92
- sccRepos.filter((r) => r.katello_repository_id !== null).length,
93
- sccRepos.length
94
- )}
95
- </BadgeToggle>
96
- }
107
+ toggle={toggle}
97
108
  isOpen={isOpen}
98
- dropdownItems={dropdownItems}
99
- />
109
+ onOpenChange={(isOpenMenu) => setIsOpen(isOpenMenu)}
110
+ >
111
+ <DropdownList>{dropdownItems}</DropdownList>
112
+ </Dropdown>
100
113
  );
101
114
  };
102
115
 
@@ -1,14 +1,14 @@
1
- .pf-c-button.pf-m-plain {
2
- --pf-c-button--PaddingTop: var(--pf-global--spacer--xs);
3
- --pf-c-button--PaddingRight: var(--pf-global--spacer--sm);
4
- --pf-c-button--PaddingBottom: var(--pf-global--spacer--xs);
5
- --pf-c-button--PaddingLeft: var(--pf-global--spacer--sm);
1
+ .pf-v5-c-button.pf-m-plain {
2
+ --pf-v5-c-button--PaddingTop: var(--pf-v5-global--spacer--xs);
3
+ --pf-v5-c-button--PaddingRight: var(--pf-v5-global--spacer--sm);
4
+ --pf-v5-c-button--PaddingBottom: var(--pf-v5-global--spacer--xs);
5
+ --pf-v5-c-button--PaddingLeft: var(--pf-v5-global--spacer--sm);
6
6
  }
7
7
 
8
- .pf-c-dropdown__toggle::before {
9
- --pf-c-button--PaddingTop: var(--pf-global--spacer--xs);
10
- --pf-c-button--PaddingRight: var(--pf-global--spacer--xs);
11
- --pf-c-button--PaddingBottom: var(--pf-global--spacer--xs);
12
- --pf-c-button--PaddingLeft: var(--pf-global--spacer--xs);
8
+ .pf-v5-c-dropdown__toggle::before {
9
+ --pf-v5-c-button--PaddingTop: var(--pf-v5-global--spacer--xs);
10
+ --pf-v5-c-button--PaddingRight: var(--pf-v5-global--spacer--xs);
11
+ --pf-v5-c-button--PaddingBottom: var(--pf-v5-global--spacer--xs);
12
+ --pf-v5-c-button--PaddingLeft: var(--pf-v5-global--spacer--xs);
13
13
  }
14
14
 
@@ -33,7 +33,13 @@ const addKatelloLinkToTree = (tree) => {
33
33
  <BrowserRouter>
34
34
  <Link to={url} target="_blank">
35
35
  <Tooltip content={__('Go to Product page')}>
36
- <Button variant="plain" id="tt-ref">
36
+ <Button
37
+ ouiaId={tree.product_id
38
+ .toString()
39
+ .concat('scc-manager-product-page-link')}
40
+ variant="plain"
41
+ id="tt-ref"
42
+ >
37
43
  <Icon name="external-link" type="fa" />
38
44
  </Button>
39
45
  </Tooltip>
@@ -45,8 +51,14 @@ const addKatelloLinkToTree = (tree) => {
45
51
 
46
52
  const addEditIcon = (tree, editProductTree) => {
47
53
  tree.customBadgeContent.push(
48
- <Tooltip content={__('Add more sub products to Product tree')}>
54
+ <Tooltip
55
+ key={'scc-manager-add-sub-tooltip'.concat(tree.id.toString())}
56
+ content={__('Add more sub products to Product tree')}
57
+ >
49
58
  <Button
59
+ ouiaId={tree.id
60
+ .toString()
61
+ .concat('scc-manager-add-sub-products-button')}
50
62
  variant="plain"
51
63
  id={tree.id.toString()}
52
64
  onClick={(evt) => editProductTree(evt, tree.id)}
@@ -61,7 +73,10 @@ const addEditIcon = (tree, editProductTree) => {
61
73
 
62
74
  const addReposToTree = (tree) => {
63
75
  tree.customBadgeContent.push(
64
- <Tooltip content={__('Show currently added repositories')}>
76
+ <Tooltip
77
+ key="scc-manager-repoview-show-repos-tooltip"
78
+ content={__('Show currently added repositories')}
79
+ >
65
80
  <SCCRepoView
66
81
  sccRepos={tree.scc_repositories}
67
82
  sccProductId={tree.product_id}
@@ -74,7 +89,10 @@ const addReposToTree = (tree) => {
74
89
  const addValidationStatusToTree = (tree) => {
75
90
  tree.customBadgeContent.push(
76
91
  <Tooltip content={__('Please check your SUSE subscription')}>
77
- <Button variant="plain">
92
+ <Button
93
+ ouiaId="scc-manager-check-suse-subscription-button"
94
+ variant="plain"
95
+ >
78
96
  <Icon name="warning-triangle-o" type="pf" size="2x" />
79
97
  </Button>
80
98
  </Tooltip>
@@ -157,7 +175,7 @@ const SCCProductView = ({
157
175
  };
158
176
 
159
177
  return (
160
- <Card>
178
+ <Card ouiaId="scc-manager-select-suse-products-card">
161
179
  <CardTitle>{__('Selected SUSE Products')}</CardTitle>
162
180
  {sccProducts.length > 0 && (
163
181
  <CardBody>
@@ -173,7 +191,7 @@ const SCCProductView = ({
173
191
  <TreeView
174
192
  data={showAll ? allProducts : subscribedProducts}
175
193
  allExpanded={expandAll}
176
- hasChecks
194
+ hasCheckboxes
177
195
  hasBadges
178
196
  hasGuides
179
197
  />
@@ -190,7 +208,7 @@ const SCCProductView = ({
190
208
  <Flex>
191
209
  {sccProducts.length > 0 && (
192
210
  <FlexItem>
193
- <Button variant="link">
211
+ <Button ouiaId="scc-manager-show-all-tasks-button" variant="link">
194
212
  <BrowserRouter>
195
213
  <Link
196
214
  to="/foreman_tasks/tasks/?search=Subscribe+SCC+Product"
@@ -204,7 +222,7 @@ const SCCProductView = ({
204
222
  )}
205
223
  {subscriptionTaskId !== '' && (
206
224
  <FlexItem>
207
- <Button variant="link">
225
+ <Button ouiaId="scc-manager-show-last-task-button" variant="link">
208
226
  <BrowserRouter>
209
227
  <Link
210
228
  to={sprintf('/foreman_tasks/tasks/%s', subscriptionTaskId)}
@@ -34,8 +34,8 @@ describe('SCCGenericExpander', () => {
34
34
  // Select
35
35
  const select = wrapper.find('Select');
36
36
  expect(select.exists()).toBe(true);
37
- expect(select.props()).toHaveProperty('selections');
38
- expect(select.prop('selections')).toContain('open/close');
37
+ expect(select.props()).toHaveProperty('selected');
38
+ expect(select.prop('selected')).toContain('open/close');
39
39
 
40
40
  // SelectOptions
41
41
  const selectOptions = wrapper.find('SelectOption');
@@ -1,6 +1,13 @@
1
1
  import React, { useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Flex, FlexItem, Select, SelectOption } from '@patternfly/react-core';
3
+ import {
4
+ Flex,
5
+ FlexItem,
6
+ Select,
7
+ SelectList,
8
+ MenuToggle,
9
+ SelectOption,
10
+ } from '@patternfly/react-core';
4
11
 
5
12
  const SCCGenericExpander = ({
6
13
  setInParent,
@@ -11,12 +18,14 @@ const SCCGenericExpander = ({
11
18
  const [label, setLabel] = useState(initLabel);
12
19
  const [isOpen, setIsOpen] = useState(false);
13
20
 
14
- const options = [
15
- <SelectOption key={0} value={selectOptionOpen} />,
16
- <SelectOption key={1} value={selectOptionClose} />,
17
- ];
21
+ const options = (
22
+ <SelectList>
23
+ <SelectOption value={selectOptionOpen}>{selectOptionOpen}</SelectOption>
24
+ <SelectOption value={selectOptionClose}>{selectOptionClose}</SelectOption>
25
+ </SelectList>
26
+ );
18
27
 
19
- const onSelect = (evt, selection) => {
28
+ const onSelect = (_evt, selection) => {
20
29
  if (selection === selectOptionOpen) {
21
30
  setInParent(true);
22
31
  } else {
@@ -26,18 +35,27 @@ const SCCGenericExpander = ({
26
35
  setIsOpen(false);
27
36
  };
28
37
 
29
- const onToggle = (isOpenSelect) => {
30
- setIsOpen(isOpenSelect);
38
+ const onToggleClick = () => {
39
+ setIsOpen(!isOpen);
31
40
  };
32
41
 
42
+ const toggle = (toggleRef) => (
43
+ <MenuToggle ref={toggleRef} onClick={onToggleClick} isExpanded={isOpen}>
44
+ {label}
45
+ </MenuToggle>
46
+ );
47
+
33
48
  return (
34
49
  <Flex>
35
50
  <FlexItem>
36
51
  <Select
37
- onToggle={onToggle}
52
+ ouiaId={initLabel.concat('scc-manager-generic-expander-select')}
53
+ toggle={toggle}
38
54
  onSelect={onSelect}
39
- selections={label}
55
+ selected={label}
40
56
  isOpen={isOpen}
57
+ onOpenChange={(nextOpen) => setIsOpen(isOpen)}
58
+ shouldFocusToggleOnSelect
41
59
  >
42
60
  {options}
43
61
  </Select>
@@ -1,8 +1,8 @@
1
1
  @import '~@theforeman/vendor/scss/variables';
2
2
 
3
- .pf-c-tile {
4
- --pf-c-tile--PaddingTop: var(--pf-global--spacer--xs);
5
- --pf-c-tile--PaddingRight: var(--pf-global--spacer--xs);
6
- --pf-c-tile--PaddingBottom: var(--pf-global--spacer--xs);
7
- --pf-c-tile--PaddingLeft: var(--pf-global--spacer--xs);
3
+ .pf-v5-c-tile {
4
+ --pf-v5-c-tile--PaddingTop: var(--pf-v5-global--spacer--xs);
5
+ --pf-v5-c-tile--PaddingRight: var(--pf-v5-global--spacer--xs);
6
+ --pf-v5-c-tile--PaddingBottom: var(--pf-v5-global--spacer--xs);
7
+ --pf-v5-c-tile--PaddingLeft: var(--pf-v5-global--spacer--xs);
8
8
  }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_scc_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ATIX AG
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-08 00:00:00.000000000 Z
10
+ date: 2025-08-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rdoc
@@ -254,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
254
  - !ruby/object:Gem::Version
255
255
  version: '0'
256
256
  requirements: []
257
- rubygems_version: 3.6.7
257
+ rubygems_version: 3.6.9
258
258
  specification_version: 4
259
259
  summary: Suse Customer Center plugin for Foreman
260
260
  test_files: