katello 4.8.0.rc1 → 4.8.0.rc2

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 (40) 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/tasks/upgrades/4.8/regenerate_imported_repository_metadata.rake +33 -0
  24. data/lib/katello/version.rb +1 -1
  25. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ChangeHostCVModal.js +10 -72
  26. data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -3
  27. data/webpack/scenes/AlternateContentSources/Details/ACSExpandableDetails.js +6 -5
  28. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditCredentials.js +1 -0
  29. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditDetails.js +3 -2
  30. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditProducts.js +1 -0
  31. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditSmartProxies.js +2 -0
  32. data/webpack/scenes/AlternateContentSources/Details/EditModals/ACSEditURLPaths.js +1 -0
  33. data/webpack/scenes/ContentViews/Details/ContentViewDetails.js +1 -0
  34. data/webpack/scenes/ContentViews/components/ContentViewSelect/ContentViewSelectOption.js +87 -0
  35. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths.js +1 -1
  36. data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +153 -28
  37. data/webpack/scenes/Hosts/ChangeContentSource/index.js +14 -15
  38. data/webpack/scenes/Hosts/ChangeContentSource/selectors.js +4 -0
  39. data/webpack/scenes/Hosts/ChangeContentSource/styles.scss +4 -0
  40. metadata +6 -3
@@ -1,11 +1,7 @@
1
1
  import React, { useState, useCallback } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { FormattedMessage } from 'react-intl';
4
3
  import { useDispatch, useSelector } from 'react-redux';
5
- import { Modal, Button, SelectOption, Alert, Flex } from '@patternfly/react-core';
6
- import {
7
- global_palette_black_600 as pfDescriptionColor,
8
- } from '@patternfly/react-tokens';
4
+ import { Modal, Button, Alert } from '@patternfly/react-core';
9
5
  import { translate as __ } from 'foremanReact/common/I18n';
10
6
  import { STATUS } from 'foremanReact/constants';
11
7
  import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
@@ -15,42 +11,15 @@ import { ENVIRONMENT_PATHS_KEY } from '../../../../../scenes/ContentViews/compon
15
11
  import api from '../../../../../services/api';
16
12
  import getContentViews from '../../../../../scenes/ContentViews/ContentViewsActions';
17
13
  import { selectContentViews, selectContentViewStatus } from '../../../../../scenes/ContentViews/ContentViewSelectors';
18
- import { uniq } from '../../../../../utils/helpers';
19
- import ContentViewIcon from '../../../../../scenes/ContentViews/components/ContentViewIcon';
20
14
  import updateHostContentViewAndEnvironment from './HostContentViewActions';
21
15
  import HOST_CV_AND_ENV_KEY from './HostContentViewConstants';
22
16
  import { getHostDetails } from '../../HostDetailsActions';
23
17
  import ContentViewSelect from '../../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelect';
18
+ import ContentViewSelectOption
19
+ from '../../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelectOption';
24
20
 
25
21
  const ENV_PATH_OPTIONS = { key: ENVIRONMENT_PATHS_KEY };
26
22
 
27
- const ContentViewDescription = ({ cv, versionNumber }) => {
28
- const descriptionStyle = {
29
- fontSize: '12px',
30
- fontWeight: 400,
31
- color: pfDescriptionColor.value,
32
- };
33
- if (cv.default) return <span style={descriptionStyle}>{__('Library')}</span>;
34
- return (
35
- <span style={descriptionStyle}>
36
- <FormattedMessage
37
- id={`content-view-${cv.id}-version-${cv.latest_version}`}
38
- defaultMessage="Version {versionNumber}"
39
- values={{ versionNumber }}
40
- />
41
- </span>
42
- );
43
- };
44
-
45
- ContentViewDescription.propTypes = {
46
- cv: PropTypes.shape({
47
- default: PropTypes.bool.isRequired,
48
- id: PropTypes.number.isRequired,
49
- latest_version: PropTypes.string.isRequired,
50
- }).isRequired,
51
- versionNumber: PropTypes.string.isRequired,
52
- };
53
-
54
23
  const ChangeHostCVModal = ({
55
24
  isOpen,
56
25
  closeModal,
@@ -66,6 +35,7 @@ const ChangeHostCVModal = ({
66
35
  const [cvSelectOpen, setCVSelectOpen] = useState(false);
67
36
  const dispatch = useDispatch();
68
37
  const contentViewsInEnvResponse = useSelector(state => selectContentViews(state, `FOR_ENV_${hostEnvId}`));
38
+ const { results } = contentViewsInEnvResponse;
69
39
  const contentViewsInEnvStatus = useSelector(state => selectContentViewStatus(state, `FOR_ENV_${hostEnvId}`));
70
40
  const hostUpdateStatus = useSelector(state => selectAPIStatus(state, HOST_CV_AND_ENV_KEY));
71
41
  useAPI( // No TableWrapper here, so we can useAPI from Foreman
@@ -73,6 +43,7 @@ const ChangeHostCVModal = ({
73
43
  api.getApiUrl(`/organizations/${orgId}/environments/paths?permission_type=promotable`),
74
44
  ENV_PATH_OPTIONS,
75
45
  );
46
+ const selectedCVForHostId = results?.find(cv => cv.name === selectedCVForHost)?.id;
76
47
 
77
48
  const handleModalClose = () => {
78
49
  setCVSelectOpen(false);
@@ -102,13 +73,6 @@ const ChangeHostCVModal = ({
102
73
  const { results: contentViewsInEnv = [] } = contentViewsInEnvResponse;
103
74
  const canSave = !!(selectedCVForHost && selectedEnvForHost.length);
104
75
 
105
- const relevantVersionObjFromCv = (cv, env) => { // returns the entire version object
106
- const versions = cv.versions.filter(version => new Set(version.environment_ids).has(env.id));
107
- return uniq(versions)?.[0];
108
- };
109
- const relevantVersionFromCv = (cv, env) =>
110
- relevantVersionObjFromCv(cv, env)?.version; // returns the version text e.g. "1.0"
111
-
112
76
  const refreshHostDetails = () => {
113
77
  handleModalClose();
114
78
  return dispatch(getHostDetails({ hostname: hostName }));
@@ -119,7 +83,7 @@ const ChangeHostCVModal = ({
119
83
  id: hostId,
120
84
  host: {
121
85
  content_facet_attributes: {
122
- content_view_id: selectedCVForHost,
86
+ content_view_id: selectedCVForHostId,
123
87
  lifecycle_environment_id: selectedEnvId,
124
88
  },
125
89
  },
@@ -183,7 +147,7 @@ const ChangeHostCVModal = ({
183
147
  headerText={__('Select environment')}
184
148
  isDisabled={hostUpdateStatus === STATUS.PENDING}
185
149
  />
186
- {selectedEnvForHost.length > 0 &&
150
+ {selectedEnvForHost.length > 0 && contentViewsInEnvStatus !== STATUS.PENDING &&
187
151
  <ContentViewSelect
188
152
  selections={selectedCVForHost}
189
153
  onClear={() => setSelectedCVForHost(null)}
@@ -193,35 +157,9 @@ const ChangeHostCVModal = ({
193
157
  onToggle={isExpanded => setCVSelectOpen(isExpanded)}
194
158
  placeholderText={cvPlaceholderText()}
195
159
  >
196
- {contentViewsInEnv?.map(cv => (
197
- <SelectOption
198
- key={cv.id}
199
- value={cv.id}
200
- >
201
- <Flex
202
- direction={{ default: 'row', sm: 'row' }}
203
- flexWrap={{ default: 'nowrap' }}
204
- alignItems={{ default: 'alignItemsCenter', sm: 'alignItemsCenter' }}
205
- >
206
- <ContentViewIcon
207
- composite={cv.composite}
208
- size="sm"
209
- />
210
- <Flex
211
- direction={{ default: 'column', sm: 'column' }}
212
- flexWrap={{ default: 'nowrap' }}
213
- alignItems={{ default: 'alignItemsFlexStart', sm: 'alignItemsFlexStart' }}
214
- >
215
- {cv.name}
216
- <ContentViewDescription
217
- cv={cv}
218
- versionNumber={relevantVersionFromCv(cv, selectedEnv)}
219
- />
220
- </Flex>
221
- </Flex>
222
- </SelectOption>
223
- ))
224
- }
160
+ {(contentViewsInEnv.length !== 0) &&
161
+ contentViewsInEnv?.map(cv =>
162
+ <ContentViewSelectOption key={cv.id} cv={cv} env={selectedEnvForHost[0]} />)}
225
163
  </ContentViewSelect>
226
164
  }
227
165
  </Modal>
@@ -11,7 +11,9 @@ const repoTypeSearchQueryMap = {
11
11
 
12
12
  const recommendedRepositoriesRHEL = [
13
13
  'rhel-9-for-x86_64-baseos-rpms',
14
+ 'rhel-9-for-x86_64-baseos-kickstart',
14
15
  'rhel-9-for-x86_64-appstream-rpms',
16
+ 'rhel-9-for-x86_64-appstream-kickstart',
15
17
  'rhel-8-for-x86_64-baseos-rpms',
16
18
  'rhel-8-for-x86_64-baseos-kickstart',
17
19
  'rhel-8-for-x86_64-appstream-rpms',
@@ -33,10 +35,10 @@ const recommendedRepositoriesSatTools = [
33
35
  ];
34
36
 
35
37
  const recommendedRepositoriesMisc = [
36
- 'satellite-capsule-6.12-for-rhel-8-x86_64-rpms',
38
+ 'satellite-capsule-6.13-for-rhel-8-x86_64-rpms',
37
39
  'ansible-2-for-rhel-8-x86_64-rpms',
38
- 'satellite-maintenance-6.12-for-rhel-8-x86_64-rpms',
39
- 'satellite-utils-6.12-for-rhel-8-x86_64-rpms',
40
+ 'satellite-maintenance-6.13-for-rhel-8-x86_64-rpms',
41
+ 'satellite-utils-6.13-for-rhel-8-x86_64-rpms',
40
42
  ];
41
43
 
42
44
  const recommendedRepositorySetLables = recommendedRepositoriesRHEL
@@ -19,6 +19,7 @@ import {
19
19
  TextListItem,
20
20
  TextListItemVariants,
21
21
  TextListVariants,
22
+ Text,
22
23
  Flex,
23
24
  FlexItem,
24
25
  } from '@patternfly/react-core';
@@ -104,7 +105,7 @@ const ACSExpandableDetails = ({ expandedId }) => {
104
105
  }}
105
106
  contentId="showDetails"
106
107
  >
107
- {__('Details')}
108
+ <Text ouiaId="expandable-details-text">{__('Details')}</Text>
108
109
  </ExpandableSectionToggle>
109
110
  </SplitItem>
110
111
  {canEdit &&
@@ -184,7 +185,7 @@ const ACSExpandableDetails = ({ expandedId }) => {
184
185
  }}
185
186
  contentId="showSmartProxies"
186
187
  >
187
- {__('Smart proxies')}
188
+ <Text ouiaId="expandable-smart-proxies-text">{__('Smart proxies')}</Text>
188
189
  </ExpandableSectionToggle>
189
190
  </SplitItem>
190
191
  {canEdit &&
@@ -255,7 +256,7 @@ const ACSExpandableDetails = ({ expandedId }) => {
255
256
  isExpanded={showProducts}
256
257
  contentId="showProducts"
257
258
  >
258
- {__('Products')}
259
+ <Text ouiaId="expandable-products-text">{__('Products')}</Text>
259
260
  </ExpandableSectionToggle>
260
261
  </SplitItem>
261
262
  {canEdit &&
@@ -306,7 +307,7 @@ const ACSExpandableDetails = ({ expandedId }) => {
306
307
  isExpanded={showUrlPaths}
307
308
  contentId="showUrlPaths"
308
309
  >
309
- {__('URL and subpaths')}
310
+ <Text ouiaId="expandable-url-paths-text">{__('URL and subpaths')}</Text>
310
311
  </ExpandableSectionToggle>
311
312
  </SplitItem>
312
313
  {canEdit &&
@@ -367,7 +368,7 @@ const ACSExpandableDetails = ({ expandedId }) => {
367
368
  isExpanded={showCredentials}
368
369
  contentId="showCredentials"
369
370
  >
370
- {__('Credentials')}
371
+ <Text ouiaId="expandable-credentials-text">{__('Credentials')}</Text>
371
372
  </ExpandableSectionToggle>
372
373
  </SplitItem>
373
374
  {canEdit &&
@@ -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();
@@ -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}
@@ -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) ||
@@ -1,4 +1,5 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
+ import { useSelector } from 'react-redux';
2
3
  import {
3
4
  ActionGroup,
4
5
  Alert,
@@ -6,20 +7,87 @@ import {
6
7
  Form,
7
8
  Grid,
8
9
  GridItem,
10
+ Select,
11
+ SelectOption,
12
+ SelectVariant,
13
+ TextContent,
9
14
  } from '@patternfly/react-core';
10
15
  import { translate as __ } from 'foremanReact/common/I18n';
11
16
  import PropTypes from 'prop-types';
17
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
18
+ import { STATUS } from 'foremanReact/constants';
19
+ import api, { orgId } from '../../../../services/api';
20
+ import { ENVIRONMENT_PATHS_KEY } from '../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPathConstants';
21
+ import EnvironmentPaths from '../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths';
22
+ import ContentViewSelect from '../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelect';
23
+ import ContentViewSelectOption from '../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelectOption';
24
+ import { selectContentViewsStatus } from '../selectors';
12
25
 
13
- import FormField from './FormField';
26
+ const ENV_PATH_OPTIONS = { key: ENVIRONMENT_PATHS_KEY };
27
+
28
+ const ContentSourceSelect = ({
29
+ contentSources,
30
+ selections,
31
+ onToggle,
32
+ onSelect,
33
+ isOpen,
34
+ isDisabled,
35
+ onClear,
36
+ }) => (
37
+ <div className="content_source_section">
38
+ <TextContent>{__('Content source')}</TextContent>
39
+ <Select
40
+ variant={SelectVariant.single}
41
+ aria-label="content-source-select"
42
+ ouiaId="content-source-select"
43
+ onToggle={onToggle}
44
+ onSelect={onSelect}
45
+ selections={selections}
46
+ isOpen={isOpen}
47
+ isDisabled={isDisabled}
48
+ onClear={onClear}
49
+ className="set-select-width"
50
+ placeholderText={__('Select a source')}
51
+ >
52
+ {contentSources.map(cs => (
53
+ <SelectOption
54
+ key={cs.id}
55
+ value={`${cs.id}`}
56
+ >
57
+ {cs.name}
58
+ </SelectOption>
59
+ ))}
60
+ </Select>
61
+ </div>
62
+ );
63
+
64
+ ContentSourceSelect.propTypes = {
65
+ contentSources: PropTypes.arrayOf(PropTypes.shape({})),
66
+ selections: PropTypes.string,
67
+ onToggle: PropTypes.func,
68
+ onSelect: PropTypes.func,
69
+ onClear: PropTypes.func,
70
+ isOpen: PropTypes.bool,
71
+ isDisabled: PropTypes.bool,
72
+ };
73
+
74
+ ContentSourceSelect.defaultProps = {
75
+ contentSources: [],
76
+ selections: null,
77
+ onToggle: undefined,
78
+ onSelect: undefined,
79
+ onClear: undefined,
80
+ isOpen: false,
81
+ isDisabled: false,
82
+ };
14
83
 
15
84
  const ContentSourceForm = ({
16
85
  handleSubmit,
17
86
  environments,
18
87
  handleEnvironment,
19
- environmentId,
20
88
  contentViews,
21
89
  handleContentView,
22
- contentViewId,
90
+ contentViewName,
23
91
  contentSources,
24
92
  handleContentSource,
25
93
  contentSourceId,
@@ -27,17 +95,36 @@ const ContentSourceForm = ({
27
95
  isLoading,
28
96
  hostsUpdated,
29
97
  }) => {
30
- const formIsValid = () => (!!environmentId &&
31
- !!contentViewId &&
98
+ useAPI( // No TableWrapper here, so we can useAPI from Foreman
99
+ 'get',
100
+ api.getApiUrl(`/organizations/${orgId()}/environments/paths?permission_type=promotable`),
101
+ ENV_PATH_OPTIONS,
102
+ );
103
+ const contentViewsStatus = useSelector(selectContentViewsStatus);
104
+ const [csSelectOpen, setCSSelectOpen] = useState(false);
105
+ const [cvSelectOpen, setCVSelectOpen] = useState(false);
106
+
107
+ const handleCSSelect = (_event, selection) => {
108
+ handleContentSource(selection);
109
+ setCSSelectOpen(false);
110
+ };
111
+
112
+ const handleCVSelect = (_event, selection) => {
113
+ handleContentView(selection);
114
+ setCVSelectOpen(false);
115
+ };
116
+
117
+ const formIsValid = () => (!!environments &&
118
+ !!contentViewName &&
32
119
  !!contentSourceId &&
33
120
  contentHosts.length !== 0);
34
121
 
35
122
  const contentSourcesIsDisabled = (isLoading || contentSources.length === 0 ||
36
123
  contentHosts.length === 0);
37
- const environmentIsDisabled = (isLoading || environments.length === 0 ||
124
+ const environmentIsDisabled = (isLoading || environments === [] ||
38
125
  contentSourceId === '');
39
126
  const viewIsDisabled = (isLoading || contentViews.length === 0 ||
40
- contentSourceId === '' || environmentId === '');
127
+ contentSourceId === '' || environments === []);
41
128
 
42
129
  return (
43
130
  <Form
@@ -56,24 +143,64 @@ const ContentSourceForm = ({
56
143
  />
57
144
  </GridItem>
58
145
  )}
59
- <FormField label={__('Content source')} id="change_cs_content_source" value={contentSourceId} items={contentSources} onChange={handleContentSource} isDisabled={contentSourcesIsDisabled} />
60
- <FormField label={__('Environment')} id="change_cs_environment" value={environmentId} items={environments} onChange={handleEnvironment} isDisabled={environmentIsDisabled} />
61
- <FormField label={__('Content view')} id="change_cs_content_view" value={contentViewId} items={contentViews} onChange={handleContentView} isDisabled={viewIsDisabled} />
62
-
63
- <GridItem>
64
- <ActionGroup>
65
- <Button
66
- variant="primary"
67
- id="generate_btn"
68
- onClick={e => handleSubmit(e)}
69
- isDisabled={isLoading || !formIsValid() || hostsUpdated}
70
- isLoading={isLoading}
146
+ {contentViewsStatus === STATUS.RESOLVED &&
147
+ !!environments.length && contentViews.length === 0 &&
148
+ <Alert
149
+ variant="warning"
150
+ className="margin-top-20"
151
+ title={__('No content views available for the selected environment')}
152
+ style={{ marginBottom: '1rem' }}
71
153
  >
72
- {__('Update')}
73
- </Button>
74
- </ActionGroup>
75
- </GridItem>
154
+ <a href="/content_views">{__('View the Content Views page')}</a>
155
+ {__(' to manage and promote content views, or select a different environment.')}
156
+ </Alert>
157
+ }
76
158
  </Grid>
159
+ <ContentSourceSelect
160
+ contentSources={contentSources}
161
+ selections={contentSourceId}
162
+ onToggle={isExpanded => setCSSelectOpen(isExpanded)}
163
+ onSelect={handleCSSelect}
164
+ onClear={() => handleContentSource(null)}
165
+ isOpen={csSelectOpen}
166
+ isDisabled={contentSourcesIsDisabled || hostsUpdated}
167
+ />
168
+ <EnvironmentPaths
169
+ style={{ display: 'block' }}
170
+ userCheckedItems={environments}
171
+ setUserCheckedItems={handleEnvironment}
172
+ publishing={false}
173
+ multiSelect={false}
174
+ headerText={__('Environment')}
175
+ isDisabled={environmentIsDisabled || hostsUpdated}
176
+ />
177
+ {environments.length > 0 && contentViewsStatus !== STATUS.PENDING &&
178
+ <ContentViewSelect
179
+ selections={contentViewName}
180
+ onClear={() => handleContentView(null)}
181
+ onSelect={handleCVSelect}
182
+ isOpen={cvSelectOpen}
183
+ isDisabled={viewIsDisabled || hostsUpdated}
184
+ onToggle={isExpanded => setCVSelectOpen(isExpanded)}
185
+ headerText={__('Content view')}
186
+ ouiaId="SelectContentView"
187
+ className="set-select-width"
188
+ placeholderText={(contentViews.length === 0) ? __('No content views available') : __('Select a content view')}
189
+ >
190
+ {contentViews?.map(cv => <ContentViewSelectOption key={`${cv.id}`} cv={cv} env={environments[0]} />)}
191
+ </ContentViewSelect>
192
+ }
193
+ <ActionGroup style={{ display: 'block' }}>
194
+ <Button
195
+ variant="primary"
196
+ id="generate_btn"
197
+ onClick={e => handleSubmit(e)}
198
+ isDisabled={isLoading || !formIsValid() || hostsUpdated}
199
+ isLoading={isLoading}
200
+ >
201
+ {__('Update')}
202
+ </Button>
203
+ </ActionGroup>
77
204
  </Form>);
78
205
  };
79
206
 
@@ -81,10 +208,9 @@ ContentSourceForm.propTypes = {
81
208
  handleSubmit: PropTypes.func.isRequired,
82
209
  environments: PropTypes.arrayOf(PropTypes.shape({})),
83
210
  handleEnvironment: PropTypes.func.isRequired,
84
- environmentId: PropTypes.string,
85
211
  contentViews: PropTypes.arrayOf(PropTypes.shape({})),
86
212
  handleContentView: PropTypes.func.isRequired,
87
- contentViewId: PropTypes.string,
213
+ contentViewName: PropTypes.string,
88
214
  contentSources: PropTypes.arrayOf(PropTypes.shape({})),
89
215
  handleContentSource: PropTypes.func.isRequired,
90
216
  contentSourceId: PropTypes.string,
@@ -95,9 +221,8 @@ ContentSourceForm.propTypes = {
95
221
 
96
222
  ContentSourceForm.defaultProps = {
97
223
  environments: [],
98
- environmentId: '',
99
224
  contentViews: [],
100
- contentViewId: '',
225
+ contentViewName: '',
101
226
  contentSources: [],
102
227
  contentSourceId: '',
103
228
  contentHosts: [],