katello 4.8.0.rc1 → 4.8.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of katello might be problematic. Click here for more details.

Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +2 -18
  3. data/app/controllers/katello/api/v2/alternate_content_sources_controller.rb +7 -5
  4. data/app/controllers/katello/api/v2/repositories_controller.rb +3 -18
  5. data/app/helpers/katello/hosts_and_hostgroups_helper.rb +13 -8
  6. data/app/lib/actions/katello/alternate_content_source/create.rb +3 -1
  7. data/app/lib/actions/katello/alternate_content_source/update.rb +3 -1
  8. data/app/lib/actions/pulp3/orchestration/content_view_version/copy_version_units_to_library.rb +1 -1
  9. data/app/lib/actions/pulp3/orchestration/content_view_version/export.rb +11 -11
  10. data/app/lib/actions/pulp3/orchestration/content_view_version/syncable_export.rb +0 -2
  11. data/app/lib/actions/pulp3/repository/reclaim_space.rb +1 -1
  12. data/app/lib/katello/api/v2/error_handling.rb +12 -2
  13. data/app/lib/katello/concerns/base_template_scope_extensions.rb +7 -3
  14. data/app/models/katello/alternate_content_source.rb +54 -4
  15. data/app/models/katello/concerns/host_managed_extensions.rb +14 -0
  16. data/app/models/katello/glue/provider.rb +1 -1
  17. data/app/models/katello/host/content_facet.rb +2 -0
  18. data/app/services/katello/pulp3/content_view_version/export_validation_error.rb +1 -1
  19. data/app/services/katello/pulp3/content_view_version/export_validator.rb +16 -0
  20. data/app/views/foreman/smart_proxies/_content_sync.html.erb +1 -1
  21. data/db/migrate/20230203141353_set_new_acs_verify_ssl_default.rb +5 -0
  22. data/db/seeds.d/111-upgrade_tasks.rb +2 -1
  23. data/lib/katello/plugin.rb +0 -12
  24. data/lib/katello/tasks/upgrades/4.8/regenerate_imported_repository_metadata.rake +33 -0
  25. data/lib/katello/version.rb +1 -1
  26. data/webpack/components/Content/{ContentPage.js → GenericContentPage.js} +7 -4
  27. data/webpack/components/Content/__tests__/ContentTable.test.js +1 -1
  28. data/webpack/components/Content/__tests__/GenericContentPage.test.js +35 -0
  29. data/webpack/components/Search/SearchText.js +70 -0
  30. data/webpack/components/Table/EmptyStateMessage.js +2 -2
  31. data/webpack/components/Table/TableWrapper.js +4 -0
  32. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ChangeHostCVModal.js +10 -72
  33. data/webpack/components/extensions/HostDetails/HostDetailsConstants.js +0 -1
  34. data/webpack/components/extensions/HostDetails/HostDetailsSelectors.js +0 -6
  35. data/webpack/components/extensions/HostDetails/Tabs/__tests__/moduleStreamsTab.test.js +76 -0
  36. data/webpack/components/extensions/SearchBar/SearchBarConstants.js +3 -0
  37. data/webpack/components/extensions/SearchBar/SearchBarHooks.js +50 -0
  38. data/webpack/components/extensions/SearchBar/SearchBarReducer.js +14 -0
  39. data/webpack/components/extensions/SearchBar/SearchBarSelectors.js +5 -0
  40. data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -3
  41. data/webpack/redux/reducers/index.js +2 -2
  42. data/webpack/scenes/AlternateContentSources/Create/__tests__/acsCreate.test.js +1 -13
  43. data/webpack/scenes/AlternateContentSources/Details/ACSExpandableDetails.js +6 -5
  44. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditCredentials.js +1 -0
  45. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditDetails.js +3 -2
  46. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditProducts.js +1 -0
  47. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditSmartProxies.js +2 -0
  48. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditURLPaths.js +1 -0
  49. data/webpack/scenes/AlternateContentSources/MainTable/ACSTable.js +1 -0
  50. data/webpack/scenes/Content/{ContentPage.js → GenericContentPage.js} +2 -2
  51. data/webpack/scenes/Content/__tests__/contentTable.test.js +2 -2
  52. data/webpack/scenes/Content/index.js +2 -2
  53. data/webpack/scenes/ContentViews/Details/ContentViewDetails.js +1 -0
  54. data/webpack/scenes/ContentViews/Details/Filters/Rules/ContainerTag/AddEditContainerTagRuleModal.js +14 -17
  55. data/webpack/scenes/ContentViews/Details/Filters/Rules/Package/AddEditPackageRuleModal.js +24 -28
  56. data/webpack/scenes/ContentViews/Details/Filters/__tests__/CVContainerImageFilterContent.test.js +11 -18
  57. data/webpack/scenes/ContentViews/Details/Filters/__tests__/CVRpmFilterContent.test.js +10 -23
  58. data/webpack/scenes/ContentViews/Details/Filters/__tests__/ContentViewPackageGroupFilter.test.js +0 -2
  59. data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewVersions.test.js +1 -7
  60. data/webpack/scenes/ContentViews/components/ContentViewSelect/ContentViewSelectOption.js +87 -0
  61. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths.js +1 -1
  62. data/webpack/scenes/ContentViews/expansions/__tests__/contentViewComponentsModal.test.js +0 -2
  63. data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +153 -28
  64. data/webpack/scenes/Hosts/ChangeContentSource/index.js +14 -15
  65. data/webpack/scenes/Hosts/ChangeContentSource/selectors.js +4 -0
  66. data/webpack/scenes/Hosts/ChangeContentSource/styles.scss +4 -0
  67. data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +2 -2
  68. data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +2 -2
  69. data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamPage.test.js.snap +1 -1
  70. data/webpack/scenes/Settings/SettingsConstants.js +2 -3
  71. data/webpack/scenes/Settings/SettingsReducer.js +2 -16
  72. data/webpack/scenes/Settings/SettingsSelectors.js +2 -2
  73. data/webpack/test-utils/react-testing-lib-wrapper.js +0 -6
  74. metadata +16 -25
  75. data/webpack/components/Content/__tests__/ContentPage.test.js +0 -32
  76. data/webpack/components/Content/__tests__/__snapshots__/ContentPage.test.js.snap +0 -89
  77. data/webpack/components/Search/Search.js +0 -156
  78. data/webpack/components/Search/__tests__/search.test.js +0 -104
  79. data/webpack/components/Search/helpers.js +0 -6
  80. data/webpack/components/Search/index.js +0 -15
  81. data/webpack/components/TypeAhead/TypeAhead.js +0 -157
  82. data/webpack/components/TypeAhead/TypeAhead.scss +0 -7
  83. data/webpack/components/TypeAhead/helpers/commonPropTypes.js +0 -35
  84. data/webpack/components/TypeAhead/helpers/helpers.js +0 -32
  85. data/webpack/components/TypeAhead/index.js +0 -3
  86. data/webpack/components/TypeAhead/pf3Search/TypeAheadInput.js +0 -44
  87. data/webpack/components/TypeAhead/pf3Search/TypeAheadItems.js +0 -56
  88. data/webpack/components/TypeAhead/pf3Search/TypeAheadSearch.js +0 -53
  89. data/webpack/components/TypeAhead/pf4Search/TypeAheadInput.js +0 -66
  90. data/webpack/components/TypeAhead/pf4Search/TypeAheadInput.scss +0 -12
  91. data/webpack/components/TypeAhead/pf4Search/TypeAheadItems.js +0 -59
  92. data/webpack/components/TypeAhead/pf4Search/TypeAheadSearch.js +0 -81
@@ -122,6 +122,7 @@ const ACSEditCredentials = ({ onClose, acsId, acsDetails }) => {
122
122
  isOpen
123
123
  onClose={onClose}
124
124
  appendTo={document.body}
125
+ ouiaId="acs-edit-credentials-modal"
125
126
  >
126
127
  <Form onSubmit={(e) => {
127
128
  e.preventDefault();
@@ -34,6 +34,7 @@ const ACSEditDetails = ({ onClose, acsId, acsDetails }) => {
34
34
  isOpen
35
35
  onClose={onClose}
36
36
  appendTo={document.body}
37
+ ouiaId="acs-edit-details-modal"
37
38
  >
38
39
  <Form onSubmit={(e) => {
39
40
  e.preventDefault();
@@ -43,9 +44,9 @@ const ACSEditDetails = ({ onClose, acsId, acsDetails }) => {
43
44
  <FormGroup label={__('Name')} isRequired fieldId="acs_name">
44
45
  <TextInput
45
46
  isRequired
47
+ ouiaId="acs-edit-name-field"
46
48
  type="text"
47
- id="acs_name_field"
48
- ouiaId="acs_name_field"
49
+ id={`acs-edit-name-field-${acsId}`}
49
50
  name="acs_name_field"
50
51
  aria-label="acs_name_field"
51
52
  value={acsName}
@@ -67,6 +67,7 @@ const ACSEditProducts = ({ onClose, acsId, acsDetails }) => {
67
67
  title={__('Edit products')}
68
68
  variant={ModalVariant.small}
69
69
  isOpen
70
+ ouiaId="acs-edit-products-modal"
70
71
  onClose={onClose}
71
72
  appendTo={document.body}
72
73
  >
@@ -80,6 +80,7 @@ const ACSEditSmartProxies = ({ onClose, acsId, acsDetails }) => {
80
80
  isOpen
81
81
  onClose={onClose}
82
82
  appendTo={document.body}
83
+ ouiaId="acs-edit-smart-proxies-modal"
83
84
  >
84
85
  <Form onSubmit={(e) => {
85
86
  e.preventDefault();
@@ -110,6 +111,7 @@ const ACSEditSmartProxies = ({ onClose, acsId, acsDetails }) => {
110
111
  <Switch
111
112
  id="use-http-proxies-switch"
112
113
  aria-label="use-http-proxies-switch"
114
+ ouiaId="use-http-proxies-switch"
113
115
  isChecked={acsUseHttpProxies}
114
116
  onChange={checked => setAcsUseHttpProxies(checked)}
115
117
  />
@@ -52,6 +52,7 @@ const ACSEditURLPaths = ({ onClose, acsId, acsDetails }) => {
52
52
  isOpen
53
53
  onClose={onClose}
54
54
  appendTo={document.body}
55
+ ouiaId="acs-edit-url-paths-modal"
55
56
  >
56
57
  <Form onSubmit={(e) => {
57
58
  e.preventDefault();
@@ -107,6 +107,7 @@ const ACSTable = () => {
107
107
  const onBulkDelete = (ids) => {
108
108
  setDeleting(true);
109
109
  dispatch(bulkDeleteACS({ ids }, () => {
110
+ setDeleting(false);
110
111
  if (acsId && ids.has(Number(acsId))) {
111
112
  push('/alternate_content_sources');
112
113
  } else {
@@ -12,7 +12,7 @@ import { getContentTypes } from './ContentActions';
12
12
  import Loading from '../../components/Loading';
13
13
  import EmptyStateMessage from '../../components/Table/EmptyStateMessage';
14
14
 
15
- const ContentPage = () => {
15
+ const GenericContentPage = () => {
16
16
  const dispatch = useDispatch();
17
17
  const contentTypesResponse = useSelector(selectContentTypes);
18
18
  const contentTypesStatus = useSelector(selectContentTypesStatus);
@@ -86,4 +86,4 @@ const ContentPage = () => {
86
86
  );
87
87
  };
88
88
 
89
- export default ContentPage;
89
+ export default GenericContentPage;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { renderWithRedux, patientlyWaitFor } from 'react-testing-lib-wrapper';
3
3
  import { nockInstance, assertNockRequest, mockAutocomplete } from '../../../test-utils/nockWrapper';
4
4
  import api from '../../../services/api';
5
- import ContentPage from '../ContentPage';
5
+ import GenericContentPage from '../GenericContentPage';
6
6
  import ansibleCollectionsResponse from './ansibleCollections.fixtures';
7
7
  import contentTypesResponse from './contentTypes.fixtures.json';
8
8
  import pythonPackagesResponse from './pythonPackages.fixtures.json';
@@ -29,7 +29,7 @@ test('Can call API for Python Packages and show table on page load', async (done
29
29
  .reply(200, contentTypesResponse);
30
30
 
31
31
  const { queryByText, getAllByText } =
32
- renderWithRedux(<ContentPage />);
32
+ renderWithRedux(<GenericContentPage />);
33
33
 
34
34
  expect(queryByText(firstPackage.name)).toBeNull();
35
35
  await patientlyWaitFor(() => {
@@ -1,4 +1,4 @@
1
1
  import { withRouter } from 'react-router-dom';
2
- import ContentPage from './ContentPage';
2
+ import GenericContentPage from './GenericContentPage';
3
3
 
4
- export default withRouter(ContentPage);
4
+ export default withRouter(GenericContentPage);
@@ -199,6 +199,7 @@ export default () => {
199
199
  <FlexItem>
200
200
  <Dropdown
201
201
  position={DropdownPosition.right}
202
+ ouiaId="cv-details-actions"
202
203
  style={{ marginLeft: 'auto' }}
203
204
  toggle={<KebabToggle onToggle={setDropdownOpen} id="toggle-dropdown" />}
204
205
  isOpen={dropDownOpen}
@@ -5,7 +5,7 @@ import { translate as __ } from 'foremanReact/common/I18n';
5
5
  import { Modal, ModalVariant, Form, FormGroup, ActionGroup, Button } from '@patternfly/react-core';
6
6
  import { addCVFilterRule, editCVFilterRule, getCVFilterRules } from '../../../ContentViewDetailActions';
7
7
  import { orgId } from '../../../../../../services/api';
8
- import Search from '../../../../../../components/Search/Search';
8
+ import SearchText from '../../../../../../components/Search/SearchText';
9
9
 
10
10
  const AddEditContainerTagRuleModal = ({
11
11
  onClose, filterId, selectedFilterRuleData, repositoryIds,
@@ -16,7 +16,7 @@ const AddEditContainerTagRuleModal = ({
16
16
  const [saving, setSaving] = useState(false);
17
17
  const isEditing = name && id;
18
18
 
19
- const autoCompleteEndpoint = '/docker_tags/auto_complete_name';
19
+ const autoCompleteEndpoint = '/katello/api/v2/docker_tags/auto_complete_name';
20
20
 
21
21
  const onSubmit = () => {
22
22
  setSaving(true);
@@ -36,13 +36,10 @@ const AddEditContainerTagRuleModal = ({
36
36
  onClose();
37
37
  };
38
38
 
39
- const getAutoCompleteParams = term => ({
40
- endpoint: autoCompleteEndpoint,
41
- params: {
42
- organization_id: orgId(),
43
- term,
44
- repoids: repositoryIds,
45
- },
39
+ const searchDataProp = term => ({
40
+ organization_id: orgId(),
41
+ term,
42
+ repoids: repositoryIds,
46
43
  });
47
44
 
48
45
  return (
@@ -59,14 +56,14 @@ const AddEditContainerTagRuleModal = ({
59
56
  }}
60
57
  >
61
58
  <FormGroup label={__('Tag name')} isRequired fieldId="tag_name">
62
- <Search
63
- patternfly4
64
- initialInputValue={tagName}
65
- onSearch={() => {}}
66
- getAutoCompleteParams={getAutoCompleteParams}
67
- foremanApiAutoComplete={false}
68
- isTextInput
69
- setTextInputValue={setTagName}
59
+ <SearchText
60
+ data={{
61
+ autocomplete: {
62
+ url: autoCompleteEndpoint,
63
+ apiParams: tag => searchDataProp(tag),
64
+ },
65
+ }}
66
+ onSearchChange={setTagName}
70
67
  />
71
68
  </FormGroup>
72
69
  <ActionGroup>
@@ -13,7 +13,7 @@ import {
13
13
  selectCreateFilterRuleStatus,
14
14
  } from '../../../ContentViewDetailSelectors';
15
15
  import { orgId } from '../../../../../../services/api';
16
- import Search from '../../../../../../components/Search/Search';
16
+ import SearchText from '../../../../../../components/Search/SearchText';
17
17
 
18
18
  const AddEditPackageRuleModal = ({
19
19
  filterId, onClose, selectedFilterRuleData, repositoryIds,
@@ -27,8 +27,8 @@ const AddEditPackageRuleModal = ({
27
27
  max_version: editingMaxVersion,
28
28
  } = selectedFilterRuleData || {};
29
29
 
30
- const architectureAutoCompleteEndpoint = '/packages/auto_complete_arch';
31
- const nameAutoCompleteEndpoint = '/packages/auto_complete_name';
30
+ const architectureAutoCompleteEndpoint = '/katello/api/v2/packages/auto_complete_arch';
31
+ const nameAutoCompleteEndpoint = '/katello/api/v2/packages/auto_complete_name';
32
32
 
33
33
  const isEditing = !!selectedFilterRuleData;
34
34
 
@@ -118,14 +118,11 @@ const AddEditPackageRuleModal = ({
118
118
  }
119
119
  }, [status, setSaving]);
120
120
 
121
- const getAutoCompleteParams = (term, autoCompleteEndpoint) => ({
122
- endpoint: autoCompleteEndpoint,
123
- params: {
124
- organization_id: orgId(),
125
- term,
126
- repoids: repositoryIds,
127
- non_modular: true,
128
- },
121
+ const searchDataProp = term => ({
122
+ organization_id: orgId(),
123
+ term,
124
+ repoids: repositoryIds,
125
+ non_modular: true,
129
126
  });
130
127
 
131
128
  return (
@@ -142,26 +139,25 @@ const AddEditPackageRuleModal = ({
142
139
  }}
143
140
  >
144
141
  <FormGroup label={__('RPM name')} isRequired fieldId="name">
145
- <Search
146
- patternfly4
147
- initialInputValue={name}
148
- onSearch={() => {}}
149
- getAutoCompleteParams={term => getAutoCompleteParams(term, nameAutoCompleteEndpoint)}
150
- foremanApiAutoComplete={false}
151
- isTextInput
152
- setTextInputValue={setName}
142
+ <SearchText
143
+ data={{
144
+ autocomplete: {
145
+ url: nameAutoCompleteEndpoint,
146
+ apiParams: input => searchDataProp(input),
147
+ },
148
+ }}
149
+ onSearchChange={setName}
153
150
  />
154
151
  </FormGroup>
155
152
  <FormGroup label={__('Architecture')} fieldId="architecture">
156
- <Search
157
- patternfly4
158
- initialInputValue={architecture}
159
- onSearch={() => {}}
160
- getAutoCompleteParams={term =>
161
- getAutoCompleteParams(term, architectureAutoCompleteEndpoint)}
162
- foremanApiAutoComplete={false}
163
- isTextInput
164
- setTextInputValue={setArchitecture}
153
+ <SearchText
154
+ data={{
155
+ autocomplete: {
156
+ url: architectureAutoCompleteEndpoint,
157
+ apiParams: arch => searchDataProp(arch),
158
+ },
159
+ }}
160
+ onSearchChange={setArchitecture}
165
161
  />
166
162
  </FormGroup>
167
163
  <FormGroup label={__('Version')} fieldId="version_comparator">
@@ -7,7 +7,6 @@ import {
7
7
  nockInstance,
8
8
  assertNockRequest,
9
9
  mockAutocomplete,
10
- mockSetting,
11
10
  } from '../../../../../test-utils/nockWrapper';
12
11
  import api from '../../../../../services/api';
13
12
  import CVContainerImageFilterContent from '../CVContainerImageFilterContent';
@@ -140,8 +139,6 @@ test('Can add filter rules', async (done) => {
140
139
  2,
141
140
  );
142
141
  const autocompleteNameScope = mockAutocomplete(nockInstance, autocompleteNameUrl, true, [], 2);
143
- const searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0);
144
- const autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing');
145
142
 
146
143
  const cvFiltersScope = nockInstance
147
144
  .get(cvFilterRulesPath)
@@ -158,7 +155,7 @@ test('Can add filter rules', async (done) => {
158
155
  .query(true)
159
156
  .reply(200, { ...cvFilterFixtures, results: [...cvFilterFixtures.results, addedRule] });
160
157
 
161
- const { queryByText, getByLabelText } =
158
+ const { queryByText, getByLabelText, getAllByLabelText } =
162
159
  renderWithRedux(
163
160
  withCVRoute(<CVContainerImageFilterContent filterId={195} details={details} />),
164
161
  renderOptions,
@@ -177,7 +174,9 @@ test('Can add filter rules', async (done) => {
177
174
  expect(getByLabelText('add_edit_filter_rule')).toHaveAttribute('aria-disabled', 'true');
178
175
  });
179
176
 
180
- fireEvent.change(getByLabelText('text input for search'), { target: { value: 'new-rule' } });
177
+ const searchInput = getAllByLabelText('Search input')[1];
178
+ searchInput.focus();
179
+ fireEvent.change(searchInput, { target: { value: 'new-rule' } });
181
180
  fireEvent.submit(getByLabelText('add_edit_filter_rule'));
182
181
 
183
182
  await patientlyWaitFor(() => {
@@ -185,8 +184,6 @@ test('Can add filter rules', async (done) => {
185
184
  });
186
185
 
187
186
  assertNockRequest(autocompleteScope);
188
- assertNockRequest(searchDelayScope);
189
- assertNockRequest(autoSearchScope);
190
187
  assertNockRequest(cvFiltersScope);
191
188
  assertNockRequest(cvFilterAddScope);
192
189
  assertNockRequest(autocompleteNameScope);
@@ -205,8 +202,6 @@ test('Can edit filter rules', async (done) => {
205
202
  2,
206
203
  );
207
204
  const autocompleteNameScope = mockAutocomplete(nockInstance, autocompleteNameUrl, true, [], 2);
208
- const searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0);
209
- const autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing');
210
205
 
211
206
  const cvFiltersScope = nockInstance
212
207
  .get(cvFilterRulesPath)
@@ -231,7 +226,9 @@ test('Can edit filter rules', async (done) => {
231
226
  }),
232
227
  });
233
228
 
234
- const { queryByText, getAllByLabelText, getByLabelText } =
229
+ const {
230
+ queryByText, getAllByLabelText, getByLabelText,
231
+ } =
235
232
  renderWithRedux(
236
233
  withCVRoute(<CVContainerImageFilterContent filterId={195} details={details} />),
237
234
  renderOptions,
@@ -258,7 +255,9 @@ test('Can edit filter rules', async (done) => {
258
255
  expect(getByLabelText('add_edit_filter_rule')).toHaveAttribute('aria-disabled', 'false');
259
256
  });
260
257
 
261
- fireEvent.change(getByLabelText('text input for search'), { target: { value: 'changed-name' } });
258
+ const searchInput = getAllByLabelText('Search input')[1];
259
+ searchInput.focus();
260
+ fireEvent.change(searchInput, { target: { value: 'changed-name' } });
262
261
  fireEvent.submit(getByLabelText('add_edit_filter_rule'));
263
262
 
264
263
  await patientlyWaitFor(() => {
@@ -266,8 +265,6 @@ test('Can edit filter rules', async (done) => {
266
265
  });
267
266
 
268
267
  assertNockRequest(autocompleteScope);
269
- assertNockRequest(searchDelayScope);
270
- assertNockRequest(autoSearchScope);
271
268
  assertNockRequest(cvFiltersScope);
272
269
  assertNockRequest(cvFilterAddScope);
273
270
  assertNockRequest(autocompleteNameScope);
@@ -284,8 +281,6 @@ test('Shows call-to-action when there are no filter rules', async (done) => {
284
281
  2,
285
282
  );
286
283
  const autocompleteNameScope = mockAutocomplete(nockInstance, autocompleteNameUrl, true, [], 2);
287
- const searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0);
288
- const autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing');
289
284
  const cvFiltersScope = nockInstance
290
285
  .get(cvFilterRulesPath)
291
286
  .times(2)
@@ -302,13 +297,11 @@ test('Shows call-to-action when there are no filter rules', async (done) => {
302
297
  await patientlyWaitFor(() => expect(queryByLabelText('add_filter_rule_empty_state')).toBeInTheDocument());
303
298
  fireEvent.click(queryByLabelText('add_filter_rule_empty_state'));
304
299
  await patientlyWaitFor(() => {
305
- expect(getAllByLabelText('text input for search')[0]).toBeInTheDocument();
300
+ expect(getAllByLabelText('Search input')[0]).toBeInTheDocument();
306
301
  });
307
302
 
308
303
  assertNockRequest(autocompleteScope);
309
304
  assertNockRequest(autocompleteNameScope);
310
- assertNockRequest(searchDelayScope);
311
- assertNockRequest(autoSearchScope);
312
305
  assertNockRequest(cvFiltersScope, done);
313
306
  act(done);
314
307
  });
@@ -8,7 +8,6 @@ import {
8
8
  nockInstance,
9
9
  assertNockRequest,
10
10
  mockAutocomplete,
11
- mockSetting,
12
11
  } from '../../../../../test-utils/nockWrapper';
13
12
  import api from '../../../../../services/api';
14
13
  import cvFilterDetails from './cvPackageFilterDetail.fixtures.json';
@@ -163,8 +162,6 @@ test('Can add package rules to filter in a self-closing modal', async (done) =>
163
162
  nockInstance, autocompleteArchUrl, true,
164
163
  undefined, 2,
165
164
  );
166
- const searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0, 3);
167
- const autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing', undefined, 3);
168
165
 
169
166
  const cvFiltersScope = nockInstance
170
167
  .get(cvFiltersPath)
@@ -214,20 +211,22 @@ test('Can add package rules to filter in a self-closing modal', async (done) =>
214
211
  });
215
212
  getByLabelText('add_rpm_rule').click();
216
213
  await patientlyWaitFor(() => {
217
- expect(getAllByLabelText('text input for search')[0]).toBeInTheDocument();
218
- expect(getAllByLabelText('text input for search')[1]).toBeInTheDocument();
214
+ expect(getAllByLabelText('Search input')[0]).toBeInTheDocument();
215
+ expect(getAllByLabelText('Search input')[1]).toBeInTheDocument();
219
216
  expect(getByLabelText('add_package_filter_rule')).toBeInTheDocument();
220
217
  });
221
- fireEvent.change(getAllByLabelText('text input for search')[0], { target: { value: 'elephant' } });
222
- fireEvent.change(getAllByLabelText('text input for search')[1], { target: { value: 'noarch' } });
218
+ const nameSearchInput = getAllByLabelText('Search input')[1];
219
+ const archSearchInput = getAllByLabelText('Search input')[2];
220
+ nameSearchInput.focus();
221
+ fireEvent.change(nameSearchInput, { target: { value: 'elephant' } });
222
+ archSearchInput.focus();
223
+ fireEvent.change(archSearchInput, { target: { value: 'noarch' } });
223
224
  fireEvent.submit(getByLabelText('add_package_filter_rule'));
224
225
 
225
226
  await patientlyWaitFor(() => {
226
227
  expect(queryByText('Add rule')).not.toBeInTheDocument();
227
228
  });
228
229
  assertNockRequest(autocompleteScope);
229
- assertNockRequest(searchDelayScope);
230
- assertNockRequest(autoSearchScope);
231
230
  assertNockRequest(autocompleteNameScope);
232
231
  assertNockRequest(autocompleteArchScope);
233
232
  assertNockRequest(cvFiltersScope);
@@ -241,8 +240,6 @@ test('Can add package rules to filter in a self-closing modal', async (done) =>
241
240
  test('Remove rpm filter rule in a self-closing modal', async (done) => {
242
241
  const { name: cvFilterName } = cvFilterDetails;
243
242
  const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
244
- const searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0);
245
- const autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing');
246
243
 
247
244
  const cvFiltersScope = nockInstance
248
245
  .get(cvFiltersPath)
@@ -289,8 +286,6 @@ test('Remove rpm filter rule in a self-closing modal', async (done) => {
289
286
  });
290
287
 
291
288
  assertNockRequest(autocompleteScope);
292
- assertNockRequest(searchDelayScope);
293
- assertNockRequest(autoSearchScope);
294
289
  assertNockRequest(cvFiltersScope);
295
290
  assertNockRequest(cvFilterDetailsScope);
296
291
  assertNockRequest(cvPackageFilterRulesScope);
@@ -310,8 +305,6 @@ test('Edit rpm filter rule in a self-closing modal', async (done) => {
310
305
  nockInstance, autocompleteArchUrl, true,
311
306
  undefined, 2,
312
307
  );
313
- const searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0, 3);
314
- const autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing', undefined, 3);
315
308
  const cvFiltersScope = nockInstance
316
309
  .get(cvFiltersPath)
317
310
  .times(2)
@@ -366,8 +359,6 @@ test('Edit rpm filter rule in a self-closing modal', async (done) => {
366
359
  });
367
360
 
368
361
  assertNockRequest(autocompleteScope);
369
- assertNockRequest(searchDelayScope);
370
- assertNockRequest(autoSearchScope);
371
362
  assertNockRequest(autocompleteNameScope);
372
363
  assertNockRequest(autocompleteArchScope);
373
364
  assertNockRequest(cvFiltersScope);
@@ -381,8 +372,6 @@ test('Shows call-to-action when there are no RPM filters', async (done) => {
381
372
  const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
382
373
  const autocompleteNameScope = mockAutocomplete(nockInstance, autocompleteNameUrl);
383
374
  const autocompleteArchScope = mockAutocomplete(nockInstance, autocompleteArchUrl);
384
- const searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0, 3);
385
- const autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing', undefined, 3);
386
375
  const cvFilterDetailScope = nockInstance
387
376
  .get(cvFilterDetailsPath)
388
377
  .query(true)
@@ -397,7 +386,7 @@ test('Shows call-to-action when there are no RPM filters', async (done) => {
397
386
  .query(true)
398
387
  .reply(200, emptyCVPackageFilterRules);
399
388
  const {
400
- queryByLabelText, getAllByLabelText, queryByText,
389
+ queryByLabelText, queryByText, getAllByLabelText,
401
390
  } =
402
391
  renderWithRedux(withCVRoute(<ContentViewFilterDetails
403
392
  cvId={1}
@@ -408,7 +397,7 @@ test('Shows call-to-action when there are no RPM filters', async (done) => {
408
397
  fireEvent.click(queryByLabelText('add_rpm_rule_empty_state'));
409
398
 
410
399
  await patientlyWaitFor(() => {
411
- expect(getAllByLabelText('text input for search')[0]).toBeInTheDocument();
400
+ expect(getAllByLabelText('Search input')[0]).toBeInTheDocument();
412
401
  expect(queryByText('Cancel')).toBeInTheDocument();
413
402
  });
414
403
  fireEvent.click(queryByText('Cancel'));
@@ -418,8 +407,6 @@ test('Shows call-to-action when there are no RPM filters', async (done) => {
418
407
  assertNockRequest(autocompleteNameScope);
419
408
  assertNockRequest(autocompleteArchScope);
420
409
  assertNockRequest(autocompleteScope);
421
- assertNockRequest(searchDelayScope);
422
- assertNockRequest(autoSearchScope);
423
410
  assertNockRequest(cvFiltersScope);
424
411
  assertNockRequest(cvFilterDetailScope);
425
412
  assertNockRequest(cvPackageFilterRulesScope);
@@ -46,8 +46,6 @@ const renderOptions = {
46
46
 
47
47
  const withCVRoute = component => <Route path="/content_views/:id([0-9]+)#/filters/:filterId([0-9]+)">{component}</Route>;
48
48
 
49
- jest.mock('../../../../../components/Search', () => () => 'mocked!');
50
-
51
49
  test('Can enable and disable add filter button', async (done) => {
52
50
  const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl, autocompleteQuery);
53
51
  const { name: cvFilterName } = cvFilterDetails;
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { act, renderWithRedux, patientlyWaitFor, fireEvent } from 'react-testing-lib-wrapper';
3
3
  import { Route } from 'react-router-dom';
4
- import { nockInstance, assertNockRequest, mockAutocomplete, mockSetting } from '../../../../../test-utils/nockWrapper';
4
+ import { nockInstance, assertNockRequest, mockAutocomplete } from '../../../../../test-utils/nockWrapper';
5
5
  import api from '../../../../../services/api';
6
6
  import CONTENT_VIEWS_KEY from '../../../ContentViewsConstants';
7
7
  import ContentViewVersions from '../ContentViewVersions';
@@ -36,15 +36,11 @@ const autocompleteUrl = '/content_view_versions/auto_complete_search';
36
36
  const taskPollingUrl = '/foreman_tasks/api/tasks/6b900ff8-62bb-42ac-8c45-da86b7258520';
37
37
 
38
38
  let firstVersion;
39
- let searchDelayScope;
40
- let autoSearchScope;
41
39
  let envScope;
42
40
 
43
41
  beforeEach(() => {
44
42
  const { results } = cvVersionsData;
45
43
  [firstVersion] = results;
46
- searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0);
47
- autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing');
48
44
  envScope = nockInstance
49
45
  .get(environmentPathsPath)
50
46
  .query(true)
@@ -53,8 +49,6 @@ beforeEach(() => {
53
49
 
54
50
  afterEach(() => {
55
51
  assertNockRequest(envScope);
56
- assertNockRequest(searchDelayScope);
57
- assertNockRequest(autoSearchScope);
58
52
  });
59
53
 
60
54
  test('Can call API and show versions on page load', async (done) => {
@@ -0,0 +1,87 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Flex, SelectOption } from '@patternfly/react-core';
4
+ import { FormattedMessage } from 'react-intl';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import {
7
+ global_palette_black_600 as pfDescriptionColor,
8
+ } from '@patternfly/react-tokens';
9
+ import ContentViewIcon from '../../../../scenes/ContentViews/components/ContentViewIcon';
10
+ import { uniq } from '../../../../utils/helpers';
11
+
12
+ export const ContentViewDescription = ({ cv, versionNumber }) => {
13
+ const descriptionStyle = {
14
+ fontSize: '12px',
15
+ fontWeight: 400,
16
+ color: pfDescriptionColor.value,
17
+ };
18
+ if (cv.default) return <span style={descriptionStyle}>{__('Library')}</span>;
19
+ return (
20
+ <span style={descriptionStyle}>
21
+ <FormattedMessage
22
+ id={`content-view-${cv.id}-version-${cv.latest_version}`}
23
+ defaultMessage="Version {versionNumber}"
24
+ values={{ versionNumber }}
25
+ />
26
+ </span>
27
+ );
28
+ };
29
+
30
+ ContentViewDescription.propTypes = {
31
+ cv: PropTypes.shape({
32
+ default: PropTypes.bool.isRequired,
33
+ id: PropTypes.number.isRequired,
34
+ latest_version: PropTypes.string.isRequired,
35
+ }).isRequired,
36
+ versionNumber: PropTypes.string.isRequired,
37
+ };
38
+
39
+ export const relevantVersionObjFromCv = (cv, env) => { // returns the entire version object
40
+ const versions = cv.versions.filter(version => new Set(version.environment_ids).has(env.id));
41
+ return uniq(versions)?.[0];
42
+ };
43
+
44
+ export const relevantVersionFromCv = (cv, env) =>
45
+ relevantVersionObjFromCv(cv, env)?.version; // returns the version text e.g. "1.0"
46
+
47
+ const ContentViewSelectOption = ({ cv, env }) => (
48
+ <SelectOption
49
+ key={cv.id}
50
+ value={`${cv.name}`}
51
+ >
52
+ <Flex
53
+ direction={{ default: 'row', sm: 'row' }}
54
+ flexWrap={{ default: 'nowrap' }}
55
+ alignItems={{ default: 'alignItemsCenter', sm: 'alignItemsCenter' }}
56
+ >
57
+ <ContentViewIcon
58
+ composite={cv.composite}
59
+ size="sm"
60
+ />
61
+ <Flex
62
+ direction={{ default: 'column', sm: 'column' }}
63
+ flexWrap={{ default: 'nowrap' }}
64
+ alignItems={{ default: 'alignItemsFlexStart', sm: 'alignItemsFlexStart' }}
65
+ >
66
+ {cv.name}
67
+ <ContentViewDescription
68
+ cv={cv}
69
+ versionNumber={relevantVersionFromCv(cv, env)}
70
+ />
71
+ </Flex>
72
+ </Flex>
73
+ </SelectOption>
74
+ );
75
+
76
+ ContentViewSelectOption.propTypes = {
77
+ cv: PropTypes.shape({
78
+ id: PropTypes.number.isRequired,
79
+ name: PropTypes.string.isRequired,
80
+ composite: PropTypes.bool.isRequired,
81
+ }).isRequired,
82
+ env: PropTypes.shape({
83
+ id: PropTypes.number.isRequired,
84
+ }).isRequired,
85
+ };
86
+
87
+ export default ContentViewSelectOption;
@@ -47,7 +47,7 @@ const EnvironmentPaths = ({
47
47
  return (
48
48
  <div className="env-path" key={index}>
49
49
  {index === 0 && <hr />}
50
- <FormGroup key={`fg-${index}`} isInline fieldId="environment-checkbox-group">
50
+ <FormGroup key={`fg-${index}`} isInline fieldId="environment-checkbox-group" style={{ display: 'block' }}>
51
51
  {environments.map(env =>
52
52
  (<CheckboxOrRadio
53
53
  isChecked={(publishing && env.library) ||
@@ -9,8 +9,6 @@ import RelatedCompositeContentViewsModal from '../RelatedCompositeContentViewsMo
9
9
 
10
10
  import contentViewComponentsResponse from './contentViewComponentsResponse.fixtures.json';
11
11
 
12
- jest.mock('../../../../components/Search', () => () => 'Mocked!');
13
-
14
12
  test('Can call API and show Related Content Views Components Modal', async (done) => {
15
13
  const cvId = 5;
16
14
  const relatedCvCount = 2;