katello 4.3.0 → 4.3.1
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.
- checksums.yaml +4 -4
- data/app/controllers/katello/api/v2/repositories_controller.rb +2 -1
- data/app/controllers/katello/remote_execution_controller.rb +5 -4
- data/app/lib/actions/katello/content_view/publish.rb +5 -0
- data/app/lib/actions/katello/content_view_version/incremental_update.rb +17 -3
- data/app/lib/actions/pulp3/content_view_version/import.rb +7 -0
- data/app/lib/actions/pulp3/orchestration/content_view_version/import.rb +7 -5
- data/app/lib/actions/pulp3/repository/copy_content.rb +1 -1
- data/app/lib/actions/pulp3/repository/save_artifact.rb +1 -0
- data/app/lib/katello/resources/cdn.rb +1 -1
- data/app/models/katello/concerns/pulp_database_unit.rb +1 -1
- data/app/models/katello/docker_meta_tag.rb +1 -1
- data/app/models/katello/repository.rb +5 -0
- data/app/services/cert/rhsm_client.rb +1 -5
- data/app/services/katello/pulp3/content_view_version/import.rb +11 -2
- data/app/services/katello/pulp3/erratum.rb +9 -1
- data/app/services/katello/pulp3/generic_content_unit.rb +2 -1
- data/app/services/katello/pulp3/pulp_content_unit.rb +5 -7
- data/app/services/katello/pulp3/repository/yum.rb +10 -2
- data/app/services/katello/pulp3/repository.rb +11 -4
- data/app/views/foreman/job_templates/install_errata.erb +6 -9
- data/app/views/foreman/job_templates/install_errata_by_search_query.erb +26 -0
- data/db/migrate/20211019192121_create_cdn_configuration.katello.rb +11 -2
- data/db/seeds.d/111-upgrade_tasks.rb +2 -1
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/packages/packages.controller.js +1 -0
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/repository-details-manage-content.controller.js +2 -3
- data/lib/katello/plugin.rb +1 -0
- data/lib/katello/repository_types/ostree.rb +2 -0
- data/lib/katello/tasks/content_view_import_only.rake +34 -0
- data/lib/katello/tasks/upgrades/4.4/publish_import_cvvs.rake +17 -0
- data/lib/katello/version.rb +1 -1
- data/webpack/components/WithOrganization/withOrganization.js +1 -0
- data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionActions.js +2 -2
- data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionConstants.js +1 -1
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/errataTab.test.js +5 -5
- data/webpack/components/extensions/HostDetails/Tabs/customizedRexUrlHelpers.js +1 -1
- data/webpack/scenes/Content/ContentConfig.js +55 -5
- data/webpack/scenes/Content/ContentPage.js +1 -1
- data/webpack/scenes/Content/Details/ContentDetails.js +1 -1
- data/webpack/scenes/Content/Details/ContentInfo.js +1 -1
- data/webpack/scenes/Content/Details/ContentRepositories.js +1 -1
- data/webpack/scenes/Content/Table/ContentTable.js +1 -1
- data/webpack/scenes/ContentViews/ContentViewsConstants.js +2 -1
- data/webpack/scenes/ContentViews/Details/ContentViewDetailActions.js +11 -10
- data/webpack/scenes/ContentViews/Details/ContentViewDetailSelectors.js +5 -5
- data/webpack/scenes/ContentViews/Details/Repositories/ContentCounts.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/ContentViewVersionContent.js +16 -17
- data/webpack/scenes/ContentViews/Details/Versions/ContentViewVersions.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionDetailConfig.js +30 -34
- data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionDetails.js +8 -8
- data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionRepositoryCell.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewVersions.test.js +1 -1
- metadata +11 -2
@@ -2,5 +2,5 @@ export const REX_JOB_INVOCATIONS_KEY = 'REX_JOB_INVOCATIONS';
|
|
2
2
|
export const REX_FEATURES = {
|
3
3
|
KATELLO_PACKAGE_INSTALL: 'katello_package_install',
|
4
4
|
KATELLO_HOST_TRACER_RESOLVE: 'katello_host_tracer_resolve',
|
5
|
-
|
5
|
+
KATELLO_HOST_ERRATA_INSTALL_BY_SEARCH: 'katello_errata_install_by_search',
|
6
6
|
};
|
@@ -981,9 +981,9 @@ test('Can bulk apply via remote execution', async (done) => {
|
|
981
981
|
// eslint-disable-next-line camelcase
|
982
982
|
const jobInvocationBody = ({ job_invocation: { inputs, feature, search_query } }) =>
|
983
983
|
inputs[ERRATA_SEARCH_QUERY] === `errata_id ^ (${results[0].errata_id},${results[1].errata_id})` &&
|
984
|
-
|
985
|
-
|
986
|
-
|
984
|
+
feature === REX_FEATURES.KATELLO_HOST_ERRATA_INSTALL_BY_SEARCH &&
|
985
|
+
// eslint-disable-next-line camelcase
|
986
|
+
search_query === `name ^ (${hostName})`;
|
987
987
|
|
988
988
|
const resolveErrataScope = nockInstance
|
989
989
|
.post(jobInvocations, jobInvocationBody)
|
@@ -1076,7 +1076,7 @@ test('Can apply errata in bulk via customized remote execution', async (done) =>
|
|
1076
1076
|
getByLabelText('Select row 0').click();
|
1077
1077
|
getByLabelText('Select row 1').click();
|
1078
1078
|
const errata = `${results[0].errata_id},${results[1].errata_id}`;
|
1079
|
-
const feature = REX_FEATURES.
|
1079
|
+
const feature = REX_FEATURES.KATELLO_HOST_ERRATA_INSTALL_BY_SEARCH;
|
1080
1080
|
const actionMenu = getByLabelText('bulk_actions');
|
1081
1081
|
actionMenu.click();
|
1082
1082
|
const viaRexAction = queryByText('Apply via customized remote execution');
|
@@ -1176,7 +1176,7 @@ test('Can apply a single erratum to the host via customized remote execution', a
|
|
1176
1176
|
const mockErrata = makeMockErrata({});
|
1177
1177
|
const { results } = mockErrata;
|
1178
1178
|
const { errata_id: errataId } = results[0];
|
1179
|
-
const feature = REX_FEATURES.
|
1179
|
+
const feature = REX_FEATURES.KATELLO_HOST_ERRATA_INSTALL_BY_SEARCH;
|
1180
1180
|
const scope = nockInstance
|
1181
1181
|
.get(hostErrata)
|
1182
1182
|
.query(defaultQuery)
|
@@ -29,6 +29,6 @@ export const resolveTraceUrl = ({ hostname, search }) => createJob({
|
|
29
29
|
|
30
30
|
export const errataInstallUrl = ({ hostname, search }) => createJob({
|
31
31
|
hostname,
|
32
|
-
feature: REX_FEATURES.
|
32
|
+
feature: REX_FEATURES.KATELLO_HOST_ERRATA_INSTALL_BY_SEARCH,
|
33
33
|
inputs: { [ERRATA_SEARCH_QUERY]: search },
|
34
34
|
});
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import { Link } from 'react-router-dom';
|
3
2
|
import { urlBuilder } from 'foremanReact/common/urlHelpers';
|
4
3
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
4
|
import ContentInfo from './Details/ContentInfo';
|
@@ -7,7 +6,8 @@ import LastSync from '../ContentViews/Details/Repositories/LastSync';
|
|
7
6
|
import ContentRepositories from './Details/ContentRepositories';
|
8
7
|
import ContentCounts from '../ContentViews/Details/Repositories/ContentCounts';
|
9
8
|
|
10
|
-
|
9
|
+
// Keep in mind when editing this file that the ContentViewVersionDetailConfig consumes this array.
|
10
|
+
export default [
|
11
11
|
{
|
12
12
|
names: {
|
13
13
|
pluralTitle: __('Python Packages'),
|
@@ -18,7 +18,7 @@ export default () => [
|
|
18
18
|
singularLabel: 'python_package',
|
19
19
|
},
|
20
20
|
columnHeaders: [
|
21
|
-
{ title: __('Name'), getProperty: unit => (<
|
21
|
+
{ title: __('Name'), getProperty: unit => (<a href={urlBuilder(`content/python_packages/${unit?.id}`, '')}>{unit?.name}</a>) },
|
22
22
|
{ title: __('Version'), getProperty: unit => unit?.version },
|
23
23
|
{ title: __('Filename'), getProperty: unit => unit?.filename },
|
24
24
|
],
|
@@ -82,6 +82,57 @@ export default () => [
|
|
82
82
|
pluralLabel: 'ostree_refs',
|
83
83
|
singularLabel: 'ostree_ref',
|
84
84
|
},
|
85
|
+
columnHeaders: [
|
86
|
+
{ title: __('Name'), getProperty: unit => (<a href={urlBuilder(`content/ostree_refs/${unit?.id}`, '')}>{unit?.name}</a>) },
|
87
|
+
{ title: __('Version'), getProperty: unit => unit?.version },
|
88
|
+
],
|
89
|
+
tabs: [
|
90
|
+
{
|
91
|
+
tabKey: 'details',
|
92
|
+
title: __('Details'),
|
93
|
+
getContent: (contentType, id, tabKey) => <ContentInfo {...{ contentType, id, tabKey }} />,
|
94
|
+
columnHeaders: [
|
95
|
+
{ title: __('Name'), getProperty: unit => unit?.name },
|
96
|
+
{ title: __('Version'), getProperty: unit => unit?.version },
|
97
|
+
],
|
98
|
+
},
|
99
|
+
{
|
100
|
+
tabKey: 'repositories',
|
101
|
+
title: __('Repositories'),
|
102
|
+
getContent: (contentType, id, tabKey) =>
|
103
|
+
<ContentRepositories {...{ contentType, id, tabKey }} />,
|
104
|
+
columnHeaders: [
|
105
|
+
{
|
106
|
+
title: __('Name'),
|
107
|
+
getProperty: unit =>
|
108
|
+
<a href={urlBuilder(`products/${unit?.product.id}/repositories/${unit?.id}`, '')}>{unit?.name}</a>,
|
109
|
+
},
|
110
|
+
{
|
111
|
+
title: __('Product'),
|
112
|
+
getProperty: unit =>
|
113
|
+
<a href={urlBuilder(`products/${unit?.product.id}`, '')}>{unit?.product.name}</a>,
|
114
|
+
},
|
115
|
+
{
|
116
|
+
title: __('Sync Status'),
|
117
|
+
getProperty: unit =>
|
118
|
+
(<LastSync
|
119
|
+
startedAt={unit?.started_at}
|
120
|
+
lastSyncWords={unit?.last_sync_words}
|
121
|
+
lastSync={unit?.last_sync}
|
122
|
+
/>),
|
123
|
+
},
|
124
|
+
{
|
125
|
+
title: __('Content Count'),
|
126
|
+
getProperty: unit =>
|
127
|
+
(<ContentCounts
|
128
|
+
productId={unit.product.id}
|
129
|
+
repoId={unit.id}
|
130
|
+
counts={unit.content_counts}
|
131
|
+
/>),
|
132
|
+
},
|
133
|
+
],
|
134
|
+
},
|
135
|
+
],
|
85
136
|
},
|
86
137
|
{
|
87
138
|
names: {
|
@@ -93,11 +144,10 @@ export default () => [
|
|
93
144
|
singularLabel: 'ansible_collection',
|
94
145
|
},
|
95
146
|
columnHeaders: [
|
96
|
-
{ title: __('Name'), getProperty: unit => (<
|
147
|
+
{ title: __('Name'), getProperty: unit => (<a href={urlBuilder(`content/ansible_collections/${unit?.id}`, '')}>{unit?.name}</a>) },
|
97
148
|
{ title: __('Author'), getProperty: unit => unit?.namespace },
|
98
149
|
{ title: __('Version'), getProperty: unit => unit?.version },
|
99
150
|
{ title: __('Checksum'), getProperty: unit => unit?.checksum },
|
100
|
-
|
101
151
|
],
|
102
152
|
tabs: [
|
103
153
|
{
|
@@ -27,7 +27,7 @@ const ContentPage = () => {
|
|
27
27
|
const types = {};
|
28
28
|
contentTypesResponse.forEach((type) => {
|
29
29
|
if (type.generic_browser) {
|
30
|
-
const typeConfig = ContentConfig
|
30
|
+
const typeConfig = ContentConfig.find(config =>
|
31
31
|
config.names.singularLabel === type.label);
|
32
32
|
if (typeConfig) {
|
33
33
|
const { names } = typeConfig;
|
@@ -16,7 +16,7 @@ const ContentDetails = () => {
|
|
16
16
|
|
17
17
|
const { id, content_type: contentType } = useParams();
|
18
18
|
const contentId = Number(id);
|
19
|
-
const config = ContentConfig
|
19
|
+
const config = ContentConfig.find(type =>
|
20
20
|
type.names.pluralLabel === contentType);
|
21
21
|
const { pluralTitle, pluralLabel } = config.names;
|
22
22
|
|
@@ -19,7 +19,7 @@ const ContentInfo = ({ contentType, id, tabKey }) => {
|
|
19
19
|
const detailsResponse = useSelector(selectContentDetails);
|
20
20
|
const detailsStatus = useSelector(selectContentDetailsStatus);
|
21
21
|
|
22
|
-
const config = contentConfig
|
22
|
+
const config = contentConfig.find(type => type.names.pluralLabel === contentType);
|
23
23
|
const { columnHeaders } = config.tabs.find(header => header.tabKey === tabKey);
|
24
24
|
|
25
25
|
useEffect(() => {
|
@@ -20,7 +20,7 @@ const ContentRepositories = ({ contentType, id, tabKey }) => {
|
|
20
20
|
const [searchQuery, updateSearchQuery] = useState('');
|
21
21
|
const { results, ...metadata } = response;
|
22
22
|
|
23
|
-
const config = contentConfig
|
23
|
+
const config = contentConfig.find(type => type.names.pluralLabel === contentType);
|
24
24
|
const typeSingularLabel = config.names.singularLabel;
|
25
25
|
const { columnHeaders } = config.tabs.find(header => header.tabKey === tabKey);
|
26
26
|
|
@@ -18,7 +18,7 @@ const ContentTable = ({
|
|
18
18
|
const [searchQuery, updateSearchQuery] = useState('');
|
19
19
|
const { results, ...metadata } = response;
|
20
20
|
|
21
|
-
const { columnHeaders } = contentConfig
|
21
|
+
const { columnHeaders } = contentConfig.find(type =>
|
22
22
|
type.names.singularLabel === contentTypes[selectedContentType][0]);
|
23
23
|
|
24
24
|
return (
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { translate as __ } from 'foremanReact/common/I18n';
|
2
|
+
import { toUpper } from 'lodash';
|
2
3
|
|
3
4
|
const CONTENT_VIEWS_KEY = 'CONTENT_VIEWS';
|
4
5
|
export const CREATE_CONTENT_VIEW_KEY = 'CONTENT_VIEW_CREATE';
|
@@ -22,11 +23,11 @@ export const RPM_PACKAGE_GROUPS_CONTENT = 'RPM_PACKAGE_GROUPS_CONTENT';
|
|
22
23
|
export const REPOSITORY_CONTENT = 'REPOSITORY_CONTENT';
|
23
24
|
export const ERRATA_CONTENT = 'ERRATA_CONTENT';
|
24
25
|
export const DOCKER_TAGS_CONTENT = 'DOCKER_TAGS_CONTENT';
|
25
|
-
export const ANSIBLE_COLLECTIONS_CONTENT = 'ANSIBLE_COLLECTIONS_CONTENT';
|
26
26
|
export const MODULE_STREAMS_CONTENT = 'MODULE_STREAMS_CONTENT';
|
27
27
|
export const DEB_PACKAGES_CONTENT = 'DEB_PACKAGES_CONTENT';
|
28
28
|
export const RPM_PACKAGES_CONTENT = 'RPM_PACKAGES_CONTENT';
|
29
29
|
export const FILE_CONTENT = 'FILE_CONTENT';
|
30
|
+
export const generatedContentKey = pluralLabel => `${toUpper(pluralLabel)}_CONTENT`;
|
30
31
|
export const cvDetailsKey = cvId => `${CONTENT_VIEWS_KEY}_${cvId}`;
|
31
32
|
export const cvDetailsRepoKey = cvId => `${CONTENT_VIEWS_KEY}_REPOSITORIES_${cvId}`;
|
32
33
|
export const cvFilterRepoKey = filterId => `CV_FILTER_REPOSITORIES_${filterId}`;
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { API_OPERATIONS, APIActions, get, put, post } from 'foremanReact/redux/API';
|
2
2
|
import { addToast } from 'foremanReact/redux/actions/toasts';
|
3
3
|
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
import { lowerCase } from 'lodash';
|
4
5
|
import {
|
5
6
|
RPM_PACKAGES_CONTENT,
|
6
7
|
RPM_PACKAGE_GROUPS_CONTENT,
|
@@ -45,8 +46,8 @@ import {
|
|
45
46
|
ERRATA_CONTENT,
|
46
47
|
MODULE_STREAMS_CONTENT,
|
47
48
|
DEB_PACKAGES_CONTENT,
|
48
|
-
ANSIBLE_COLLECTIONS_CONTENT,
|
49
49
|
DOCKER_TAGS_CONTENT,
|
50
|
+
generatedContentKey,
|
50
51
|
} from '../ContentViewsConstants';
|
51
52
|
import api, { foremanApi, orgId } from '../../../services/api';
|
52
53
|
import { getResponseErrorMsgs, apiError } from '../../../utils/helpers';
|
@@ -70,6 +71,14 @@ const cvUpdateSuccess = (response, dispatch) => {
|
|
70
71
|
}));
|
71
72
|
};
|
72
73
|
|
74
|
+
export const getContent = (pluralLabel, params) => get({
|
75
|
+
type: API_OPERATIONS.GET,
|
76
|
+
key: generatedContentKey(pluralLabel),
|
77
|
+
url: api.getApiUrl(`/${pluralLabel}`),
|
78
|
+
params,
|
79
|
+
errorToast: error => __(`Something went wrong while fetching ${lowerCase(pluralLabel)}! ${getResponseErrorMsgs(error.response)}`),
|
80
|
+
});
|
81
|
+
|
73
82
|
export const getRPMPackages = params => get({
|
74
83
|
type: API_OPERATIONS.GET,
|
75
84
|
key: RPM_PACKAGES_CONTENT,
|
@@ -135,14 +144,6 @@ export const getDockerTags = params => get({
|
|
135
144
|
errorToast: error => __(`Something went wrong while getting docker tags! ${getResponseErrorMsgs(error.response)}`),
|
136
145
|
});
|
137
146
|
|
138
|
-
export const getAnsibleCollections = params => get({
|
139
|
-
type: API_OPERATIONS.GET,
|
140
|
-
key: ANSIBLE_COLLECTIONS_CONTENT,
|
141
|
-
url: api.getApiUrl('/ansible_collections'),
|
142
|
-
params,
|
143
|
-
errorToast: error => __(`Something went wrong while getting ansible collections! ${getResponseErrorMsgs(error.response)}`),
|
144
|
-
});
|
145
|
-
|
146
147
|
export const getErrata = params => get({
|
147
148
|
type: API_OPERATIONS.GET,
|
148
149
|
key: ERRATA_CONTENT,
|
@@ -176,7 +177,7 @@ export const getRepositories = params => get({
|
|
176
177
|
});
|
177
178
|
|
178
179
|
export const getContentViewRepositories = (cvId, params, status) => {
|
179
|
-
const apiParams = { ...params };
|
180
|
+
const apiParams = { organization_id: orgId(), ...params };
|
180
181
|
let apiUrl = `/content_views/${cvId}/repositories`;
|
181
182
|
|
182
183
|
if (status === ALL_STATUSES) {
|
@@ -33,8 +33,8 @@ import {
|
|
33
33
|
ERRATA_CONTENT,
|
34
34
|
MODULE_STREAMS_CONTENT,
|
35
35
|
DEB_PACKAGES_CONTENT,
|
36
|
-
ANSIBLE_COLLECTIONS_CONTENT,
|
37
36
|
DOCKER_TAGS_CONTENT,
|
37
|
+
generatedContentKey,
|
38
38
|
} from '../ContentViewsConstants';
|
39
39
|
import { pollTaskKey } from '../../Tasks/helpers';
|
40
40
|
|
@@ -131,11 +131,11 @@ export const selectCVFilterRules = (state, filterId) =>
|
|
131
131
|
export const selectCVFilterRulesStatus = (state, filterId) =>
|
132
132
|
selectAPIStatus(state, cvFilterRulesKey(filterId)) || STATUS.PENDING;
|
133
133
|
|
134
|
-
export const
|
135
|
-
selectAPIResponse(state,
|
134
|
+
export const selectContent = (pluralLabel, state) =>
|
135
|
+
selectAPIResponse(state, generatedContentKey(pluralLabel));
|
136
136
|
|
137
|
-
export const
|
138
|
-
selectAPIStatus(state,
|
137
|
+
export const selectContentStatus = (pluralLabel, state) =>
|
138
|
+
selectAPIStatus(state, generatedContentKey(pluralLabel)) || STATUS.PENDING;
|
139
139
|
|
140
140
|
export const selectDockerTags = state =>
|
141
141
|
selectAPIResponse(state, DOCKER_TAGS_CONTENT);
|
@@ -41,7 +41,7 @@ const ContentCounts = ({ productId, repoId, counts }) => {
|
|
41
41
|
Object.keys(counts).forEach((type) => {
|
42
42
|
const count = counts[type];
|
43
43
|
let info = repoLabels[type];
|
44
|
-
const config = ContentConfig
|
44
|
+
const config = ContentConfig.find(typeConfig =>
|
45
45
|
typeConfig.names.singularLabel === type);
|
46
46
|
|
47
47
|
if (config) {
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
|
+
import { camelCase } from 'lodash';
|
3
4
|
import { Link } from 'react-router-dom';
|
4
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
6
|
import { urlBuilder } from 'foremanReact/common/urlHelpers';
|
@@ -17,15 +18,16 @@ const ContentViewVersionContent = ({ cvId, versionId, cvVersion }) => {
|
|
17
18
|
} = cvVersion;
|
18
19
|
|
19
20
|
|
20
|
-
const
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
})
|
21
|
+
const contentConfigTypes = ContentConfig.filter(({ names: { singularLabel } }) =>
|
22
|
+
!!cvVersion[`${singularLabel}_count`]).map(({
|
23
|
+
names: {
|
24
|
+
singularLabel, singularLowercase, pluralLowercase, pluralLabel,
|
25
|
+
},
|
26
|
+
}) => {
|
26
27
|
const countParam = `${singularLabel}_count`;
|
27
28
|
const count = cvVersion[countParam];
|
28
29
|
return {
|
30
|
+
pluralLabel,
|
29
31
|
label: count > 1 ? pluralLowercase : singularLowercase,
|
30
32
|
count,
|
31
33
|
};
|
@@ -34,7 +36,7 @@ const ContentViewVersionContent = ({ cvId, versionId, cvVersion }) => {
|
|
34
36
|
const noCounts =
|
35
37
|
!Number(debCount) && !Number(dockerManifestCount) && !Number(dockerTagCount) &&
|
36
38
|
!Number(fileCount) && !Number(moduleStreamCount) && !Number(ansibleCollectionCount) &&
|
37
|
-
!
|
39
|
+
!contentConfigTypes?.length;
|
38
40
|
|
39
41
|
if (noCounts) {
|
40
42
|
return <InactiveText text={__('No content')} />;
|
@@ -69,16 +71,13 @@ const ContentViewVersionContent = ({ cvId, versionId, cvVersion }) => {
|
|
69
71
|
<a href={urlBuilder(`content_views/${cvId}#/versions/${versionId}/files`, '')}>{`${fileCount} Files`}</a><br />
|
70
72
|
</>
|
71
73
|
}
|
72
|
-
{
|
73
|
-
|
74
|
-
<
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
<span key={label} style={{ whiteSpace: 'pre-line' }}>
|
80
|
-
{`${count} ${label}`}
|
81
|
-
</span>))
|
74
|
+
{contentConfigTypes?.length > 0 &&
|
75
|
+
contentConfigTypes.map(({ label, count, pluralLabel }) => (
|
76
|
+
<React.Fragment key={label}>
|
77
|
+
<a href={urlBuilder(`content_views/${cvId}#/versions/${versionId}/${camelCase(pluralLabel)}`, '')}>
|
78
|
+
{`${count} ${label}`}
|
79
|
+
</a><br />
|
80
|
+
</React.Fragment>))
|
82
81
|
}
|
83
82
|
</>
|
84
83
|
);
|
@@ -92,7 +92,7 @@ const ContentViewVersions = ({ cvId, details }) => {
|
|
92
92
|
{ title: <ContentViewVersionEnvironments {...{ environments }} /> },
|
93
93
|
{
|
94
94
|
title: Number(packageCount) ?
|
95
|
-
<a href={urlBuilder(`content_views/${cvId}#/versions/${versionId}/
|
95
|
+
<a href={urlBuilder(`content_views/${cvId}#/versions/${versionId}/rpmPackages`, '')}>{packageCount}</a> :
|
96
96
|
<InactiveText text={__('No packages')} />,
|
97
97
|
},
|
98
98
|
{ title: <ContentViewVersionErrata {...{ cvId, versionId, errataCounts }} /> },
|
data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionDetailConfig.js
CHANGED
@@ -3,14 +3,13 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import { translate as __ } from 'foremanReact/common/I18n';
|
4
4
|
import { urlBuilder } from 'foremanReact/common/urlHelpers';
|
5
5
|
import LongDateTime from 'foremanReact/components/common/dates/LongDateTime';
|
6
|
-
import { startCase } from 'lodash';
|
6
|
+
import { startCase, camelCase } from 'lodash';
|
7
7
|
import {
|
8
8
|
BugIcon,
|
9
9
|
SecurityIcon,
|
10
10
|
EnhancementIcon,
|
11
11
|
} from '@patternfly/react-icons';
|
12
12
|
import {
|
13
|
-
getAnsibleCollections,
|
14
13
|
getContentViewVersions,
|
15
14
|
getDebPackages,
|
16
15
|
getDockerTags,
|
@@ -20,10 +19,9 @@ import {
|
|
20
19
|
getPackageGroups,
|
21
20
|
getRepositories,
|
22
21
|
getRPMPackages,
|
22
|
+
getContent,
|
23
23
|
} from '../../ContentViewDetailActions';
|
24
24
|
import {
|
25
|
-
selectAnsibleCollections,
|
26
|
-
selectAnsibleCollectionsStatus,
|
27
25
|
selectCVVersions,
|
28
26
|
selectCVVersionsStatus,
|
29
27
|
selectDebPackages,
|
@@ -42,11 +40,15 @@ import {
|
|
42
40
|
selectRPMPackageGroupsStatus,
|
43
41
|
selectRPMPackages,
|
44
42
|
selectRPMPackagesStatus,
|
43
|
+
selectContent,
|
44
|
+
selectContentStatus,
|
45
45
|
} from '../../ContentViewDetailSelectors';
|
46
46
|
import ContentViewVersionRepositoryCell from './ContentViewVersionRepositoryCell';
|
47
|
+
import ContentConfig from '../../../../Content/ContentConfig';
|
47
48
|
|
48
49
|
export const TableType = PropTypes.shape({
|
49
50
|
name: PropTypes.string,
|
51
|
+
route: PropTypes.string,
|
50
52
|
getCountKey: PropTypes.func,
|
51
53
|
repoType: PropTypes.string,
|
52
54
|
responseSelector: PropTypes.func,
|
@@ -64,6 +66,7 @@ export const TableType = PropTypes.shape({
|
|
64
66
|
export default ({ cvId, versionId }) => [
|
65
67
|
{
|
66
68
|
name: __('Components'),
|
69
|
+
route: 'components',
|
67
70
|
getCountKey: item => item?.component_view_count,
|
68
71
|
responseSelector: state => selectCVVersions(state, cvId),
|
69
72
|
statusSelector: state => selectCVVersionsStatus(state, cvId),
|
@@ -91,6 +94,7 @@ export default ({ cvId, versionId }) => [
|
|
91
94
|
},
|
92
95
|
{
|
93
96
|
name: __('Repositories'),
|
97
|
+
route: 'repositories',
|
94
98
|
getCountKey: item => item?.repositories?.length,
|
95
99
|
responseSelector: state => selectRepositories(state),
|
96
100
|
statusSelector: state => selectRepositoriesStatus(state),
|
@@ -127,6 +131,7 @@ export default ({ cvId, versionId }) => [
|
|
127
131
|
},
|
128
132
|
{
|
129
133
|
name: __('RPM Packages'),
|
134
|
+
route: 'rpmPackages',
|
130
135
|
repoType: 'yum',
|
131
136
|
getCountKey: item => item?.rpm_count,
|
132
137
|
responseSelector: state => selectRPMPackages(state),
|
@@ -148,6 +153,7 @@ export default ({ cvId, versionId }) => [
|
|
148
153
|
},
|
149
154
|
{
|
150
155
|
name: __('RPM Package Groups'),
|
156
|
+
route: 'rpmPackageGroups',
|
151
157
|
repoType: 'yum',
|
152
158
|
getCountKey: item => item?.package_group_count,
|
153
159
|
responseSelector: state => selectRPMPackageGroups(state),
|
@@ -161,6 +167,7 @@ export default ({ cvId, versionId }) => [
|
|
161
167
|
},
|
162
168
|
{
|
163
169
|
name: __('Files'),
|
170
|
+
route: 'files',
|
164
171
|
repoType: 'file',
|
165
172
|
getCountKey: item => item?.file_count,
|
166
173
|
responseSelector: state => selectFiles(state),
|
@@ -180,6 +187,7 @@ export default ({ cvId, versionId }) => [
|
|
180
187
|
},
|
181
188
|
{
|
182
189
|
name: __('Errata'),
|
190
|
+
route: 'errata',
|
183
191
|
repoType: 'yum',
|
184
192
|
getCountKey: item => item?.erratum_count,
|
185
193
|
responseSelector: state => selectErrata(state),
|
@@ -233,6 +241,7 @@ export default ({ cvId, versionId }) => [
|
|
233
241
|
},
|
234
242
|
{
|
235
243
|
name: __('Module Streams'),
|
244
|
+
route: 'moduleStreams',
|
236
245
|
repoType: 'yum',
|
237
246
|
getCountKey: item => item?.module_stream_count,
|
238
247
|
responseSelector: state => selectModuleStreams(state),
|
@@ -255,6 +264,7 @@ export default ({ cvId, versionId }) => [
|
|
255
264
|
},
|
256
265
|
{
|
257
266
|
name: __('Deb Packages'),
|
267
|
+
route: 'debPackages',
|
258
268
|
repoType: 'deb',
|
259
269
|
getCountKey: item => item?.deb_count,
|
260
270
|
responseSelector: state => selectDebPackages(state),
|
@@ -273,38 +283,9 @@ export default ({ cvId, versionId }) => [
|
|
273
283
|
{ title: __('Architecture'), getProperty: item => item?.architecture },
|
274
284
|
],
|
275
285
|
},
|
276
|
-
{
|
277
|
-
name: __('Ansible Collections'),
|
278
|
-
repoType: 'ansible_collection',
|
279
|
-
getCountKey: item => item?.ansible_collection_count,
|
280
|
-
responseSelector: state => selectAnsibleCollections(state),
|
281
|
-
statusSelector: state => selectAnsibleCollectionsStatus(state),
|
282
|
-
autocompleteEndpoint: `/ansible_collections/auto_complete_search?content_view_version_id=${versionId}`,
|
283
|
-
fetchItems: params => getAnsibleCollections({ content_view_version_id: versionId, ...params }),
|
284
|
-
columnHeaders: [
|
285
|
-
{
|
286
|
-
title: __('Name'),
|
287
|
-
getProperty: item => (
|
288
|
-
<a href={urlBuilder(`ansible_collections/${item?.id}`, '')}>
|
289
|
-
{item?.name}
|
290
|
-
</a>),
|
291
|
-
},
|
292
|
-
{
|
293
|
-
title: __('Author'),
|
294
|
-
getProperty: item => item?.namespace,
|
295
|
-
},
|
296
|
-
{
|
297
|
-
title: __('Version'),
|
298
|
-
getProperty: item => item?.version,
|
299
|
-
},
|
300
|
-
{
|
301
|
-
title: __('Checksum'),
|
302
|
-
getProperty: item => item?.checksum,
|
303
|
-
},
|
304
|
-
],
|
305
|
-
},
|
306
286
|
{
|
307
287
|
name: __('Docker Tags'),
|
288
|
+
route: 'dockerTags',
|
308
289
|
repoType: 'docker',
|
309
290
|
getCountKey: item => item?.docker_tag_count,
|
310
291
|
responseSelector: state => selectDockerTags(state),
|
@@ -329,4 +310,19 @@ export default ({ cvId, versionId }) => [
|
|
329
310
|
{ title: __('Product Name'), getProperty: item => item?.product?.name },
|
330
311
|
],
|
331
312
|
},
|
313
|
+
...ContentConfig.map(({
|
314
|
+
names: { pluralTitle, pluralLabel, singularLabel },
|
315
|
+
columnHeaders,
|
316
|
+
}) => ({
|
317
|
+
name: pluralTitle,
|
318
|
+
route: camelCase(pluralLabel),
|
319
|
+
repoType: singularLabel,
|
320
|
+
getCountKey: item => item[`${singularLabel}_count`],
|
321
|
+
responseSelector: state => selectContent(pluralLabel, state),
|
322
|
+
statusSelector: state => selectContentStatus(pluralLabel, state),
|
323
|
+
autocompleteEndpoint: `/${pluralLabel}/auto_complete_search?content_view_version_id=${versionId}`,
|
324
|
+
fetchItems: params =>
|
325
|
+
getContent(pluralLabel, { content_view_version_id: versionId, ...params }),
|
326
|
+
columnHeaders,
|
327
|
+
})),
|
332
328
|
];
|
data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionDetails.js
CHANGED
@@ -3,7 +3,7 @@ import useDeepCompareEffect from 'use-deep-compare-effect';
|
|
3
3
|
import { useParams, Route, useHistory, useLocation, Redirect, Switch } from 'react-router-dom';
|
4
4
|
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
|
5
5
|
import { STATUS } from 'foremanReact/constants';
|
6
|
-
import { isEmpty,
|
6
|
+
import { isEmpty, first } from 'lodash';
|
7
7
|
import { Grid, Tabs, Tab, TabTitleText, Label } from '@patternfly/react-core';
|
8
8
|
import { number, shape } from 'prop-types';
|
9
9
|
import './ContentViewVersionDetails.scss';
|
@@ -68,7 +68,7 @@ const ContentViewVersionDetails = ({ cvId, details }) => {
|
|
68
68
|
const filteredTableConfigs = tableConfigs.filter(({ getCountKey }) => !!getCountKey(response));
|
69
69
|
const { repositories } = versionDetails;
|
70
70
|
const showTabs = filteredTableConfigs.length > 0 && repositories;
|
71
|
-
const getCurrentActiveKey = tab ??
|
71
|
+
const getCurrentActiveKey = tab ?? first(filteredTableConfigs)?.route;
|
72
72
|
|
73
73
|
return (
|
74
74
|
<Grid>
|
@@ -84,10 +84,10 @@ const ContentViewVersionDetails = ({ cvId, details }) => {
|
|
84
84
|
onSelect={onSelect}
|
85
85
|
isVertical
|
86
86
|
>
|
87
|
-
{filteredTableConfigs.map(({ name, getCountKey }) => (
|
87
|
+
{filteredTableConfigs.map(({ route, name, getCountKey }) => (
|
88
88
|
<Tab
|
89
|
-
key={
|
90
|
-
eventKey={
|
89
|
+
key={route}
|
90
|
+
eventKey={route}
|
91
91
|
title={
|
92
92
|
<>
|
93
93
|
<TabTitleText>{name}</TabTitleText>
|
@@ -100,9 +100,9 @@ const ContentViewVersionDetails = ({ cvId, details }) => {
|
|
100
100
|
<Switch>
|
101
101
|
{filteredTableConfigs.map(config => (
|
102
102
|
<Route
|
103
|
-
key={
|
103
|
+
key={config.route}
|
104
104
|
exact
|
105
|
-
path={`/versions/:versionId([0-9]+)/${
|
105
|
+
path={`/versions/:versionId([0-9]+)/${config.route}`}
|
106
106
|
>
|
107
107
|
<ContentViewVersionDetailsTable
|
108
108
|
tableConfig={config}
|
@@ -111,7 +111,7 @@ const ContentViewVersionDetails = ({ cvId, details }) => {
|
|
111
111
|
</Route>))
|
112
112
|
}
|
113
113
|
<Redirect
|
114
|
-
to={`/versions/${versionId}/${
|
114
|
+
to={`/versions/${versionId}/${first(filteredTableConfigs).route}`}
|
115
115
|
/>
|
116
116
|
</Switch>
|
117
117
|
</div>
|
data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionRepositoryCell.js
CHANGED
@@ -67,7 +67,7 @@ const ContentViewVersionRepositoryCell = ({
|
|
67
67
|
},
|
68
68
|
};
|
69
69
|
|
70
|
-
ContentConfig
|
70
|
+
ContentConfig.forEach((type) => {
|
71
71
|
CONTENT_COUNTS[type.names.singularLabel] = {
|
72
72
|
name: type.names.pluralLowercase,
|
73
73
|
url: `products/${id}/repositories/${libraryInstanceId}/content/${type.names.pluralLabel}`,
|
@@ -122,7 +122,7 @@ test('Can show package and erratas and link to list page', async () => {
|
|
122
122
|
|
123
123
|
await patientlyWaitFor(() => {
|
124
124
|
expect(getAllByText(8)[0].closest('a'))
|
125
|
-
.toHaveAttribute('href', '/content_views/5#/versions/11/
|
125
|
+
.toHaveAttribute('href', '/content_views/5#/versions/11/rpmPackages/');
|
126
126
|
expect(getAllByText(15)[0].closest('a'))
|
127
127
|
.toHaveAttribute('href', '/content_views/5#/versions/11/errata/');
|
128
128
|
expect(getByText(5)).toBeInTheDocument();
|