Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ def authorize_remove_environments(view, options) # rubocop:disable Metrics/Cyclo
return deny_access unless KTEnvironment.promotable.where(:id => env_ids).count == env_ids.size && view.promotable_or_removable?

total_count = Katello::Host::ContentFacet.with_content_views(view).with_environments(env_ids).count
if total_count > 0
single_env_host_count = Katello::Host::ContentFacet
.with_content_views(view)
.with_environments(env_ids)
.count { |facet| !facet.multi_content_view_environment? }
if single_env_host_count > 0
unless options[:system_content_view_id] && options[:system_environment_id]
fail _("Unable to reassign content hosts. Please provide system_content_view_id and system_environment_id.")
end
Expand All @@ -132,7 +136,9 @@ def authorize_remove_environments(view, options) # rubocop:disable Metrics/Cyclo
end
end

if Katello::ActivationKey.with_content_views(view).with_environments(env_ids).count > 0
keys = Katello::ActivationKey.with_content_views(view).with_environments(env_ids)
single_env_keys_exist = keys.any? { |key| !key.multi_content_view_environment? }
if single_env_keys_exist
# if we are reassigning activation key environments/ cv
# make sure the activation key using present environments or cv are editable.
unless options[:key_content_view_id] && options[:key_environment_id]
Expand Down
12 changes: 8 additions & 4 deletions app/lib/actions/katello/content_view/remove.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,17 @@ def validate_options(_content_view, cv_envs, versions, options)
end
all_cv_envs = combined_cv_envs(cv_envs, versions)

if all_cv_envs.flat_map(&:hosts).any? && !cve_exists?(options[:system_environment_id],
options[:system_content_view_id])
single_env_hosts_exist = all_cv_envs.flat_map(&:hosts).any? do |host|
!host.content_facet.multi_content_view_environment?
end
if single_env_hosts_exist && !cve_exists?(options[:system_environment_id], options[:system_content_view_id])
fail _("Unable to reassign systems. Please check system_content_view_id and system_environment_id.")
end

if all_cv_envs.flat_map(&:activation_keys).any? && !cve_exists?(options[:key_environment_id],
options[:key_content_view_id])
single_env_keys_exist = all_cv_envs.flat_map(&:activation_keys).any? do |key|
!key.multi_content_view_environment?
end
if single_env_keys_exist && !cve_exists?(options[:key_environment_id], options[:key_content_view_id])
fail _("Unable to reassign activation_keys. Please check activation_key_content_view_id and activation_key_environment_id.")
end
end
Expand Down
12 changes: 12 additions & 0 deletions app/views/katello/api/v2/content_view_versions/base.json.rabl
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,21 @@ child :sorted_organization_readable_environments => :environments do
::Host.authorized('view_hosts').in_content_view_environment(:content_view => version.content_view, :lifecycle_environment => env).count
end

node :multi_env_host_count do |env|
hosts = ::Host.authorized('view_hosts')
.in_content_view_environments(content_views: [version.content_view], lifecycle_environments: [env])
hosts.count { |host| host.content_facet.multi_content_view_environment? }
end

node :activation_key_count do |env|
Katello::ActivationKey.with_content_views(version.content_view).with_environments(env).count
end

node :multi_env_ak_count do |env|
keys = Katello::ActivationKey.with_content_views(version.content_view)
.with_environments(env)
keys.count { |key| key.multi_content_view_environment? }
end
end

child :archived_repos => :repositories do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ const CVEnvironmentSelectionForm = () => {

// Based on env selected for removal, decide if we need to reassign hosts and activation keys.
useDeepCompareEffect(() => {
const selectedEnv = versionEnvironments.filter(env => selectedEnvSet.has(env.id));
setAffectedActivationKeys(!!(selectedEnv.filter(env => env.activation_key_count > 0).length));
setAffectedHosts(!!(selectedEnv.filter(env => env.host_count > 0).length));
const selectedEnvironments = versionEnvironments.filter(env => selectedEnvSet.has(env.id));

const needsHostReassignment = selectedEnvironments.some(env =>
(env.host_count || 0) > (env.multi_env_host_count || 0));
setAffectedHosts(needsHostReassignment);

const needsAKReassignment = selectedEnvironments.some(env =>
(env.activation_key_count || 0) > (env.multi_env_ak_count || 0));
setAffectedActivationKeys(needsAKReassignment);
}, [setAffectedActivationKeys, setAffectedHosts,
versionEnvironments, selectedEnvSet, selectedEnvSet.size]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const CVReassignActivationKeysForm = () => {
contentViewsInEnvError, selectedEnvForAK, setSelectedCVForAK, setSelectedCVNameForAK,
cvInEnvLoading, selectedCVForAK, cvId, versionEnvironments, selectedEnvSet]);

const multiCVWarning = activationKeysResponse?.results?.some?.(key =>
const multiCVInfo = activationKeysResponse?.results?.some?.(key =>
key.multi_content_view_environment);

const fetchSelectedCVName = (id) => {
Expand Down Expand Up @@ -117,12 +117,12 @@ const CVReassignActivationKeysForm = () => {

return (
<>
{!alertDismissed && multiCVWarning && (
{!alertDismissed && multiCVInfo && (
<Alert
ouiaId="multi-cv-warning-alert"
variant="warning"
ouiaId="multi-cv-info-alert"
variant="info"
isInline
title={__('Warning')}
title={__('Multi-environment activation key(s) affected')}
actionClose={<AlertActionCloseButton onClose={() => setAlertDismissed(true)} />}
>
<p>{multiCVRemovalInfo}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const CVReassignHostsForm = () => {
const [alertDismissed, setAlertDismissed] = useState(false);
const hostResponse = useSelector(selectCVHosts);

const multiCVWarning = hostResponse?.results?.some?.(host =>
const multiCVInfo = hostResponse?.results?.some?.(host =>
host.content_facet_attributes?.multi_content_view_environment);

const multiCVRemovalInfo = __('This content view version is used in one or more multi-environment hosts. The version will simply be removed from the multi-environment hosts. The content view and lifecycle environment you select here will only apply to single-environment hosts. See hammer activation-key --help for more details.');
Expand Down Expand Up @@ -112,12 +112,12 @@ const CVReassignHostsForm = () => {

return (
<>
{!alertDismissed && multiCVWarning && (
{!alertDismissed && multiCVInfo && (
<Alert
ouiaId="multi-cv-warning-alert"
variant="warning"
ouiaId="multi-cv-info-alert"
variant="info"
isInline
title={__('Warning')}
title={__('Multi-environment host(s) affected')}
actionClose={<AlertActionCloseButton onClose={() => setAlertDismissed(true)} />}
>
<p>{multiCVRemovalInfo}</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import React, { useContext, useState } from 'react';
import { useSelector } from 'react-redux';
import { Alert, Flex, FlexItem, Label, AlertActionCloseButton } from '@patternfly/react-core';
import { Alert, Flex, FlexItem, Label, AlertActionCloseButton, ExpandableSection } from '@patternfly/react-core';
import { ExclamationTriangleIcon } from '@patternfly/react-icons';
import { FormattedMessage } from 'react-intl';
import { translate as __ } from 'foremanReact/common/I18n';
import { selectCVActivationKeys, selectCVHosts, selectCVVersions } from '../../../ContentViewDetailSelectors';
import { selectCVVersions } from '../../../ContentViewDetailSelectors';
import DeleteContext from '../DeleteContext';
import WizardHeader from '../../../../components/WizardHeader';
import AffectedHosts from '../affectedHosts';
import AffectedActivationKeys from '../affectedActivationKeys';

const CVVersionRemoveReview = () => {
const [alertDismissed, setAlertDismissed] = useState(false);
const [showHosts, setShowHosts] = useState(false);
const [showAKs, setShowAKs] = useState(false);
const {
cvId, versionIdToRemove, versionNameToRemove, selectedEnvSet,
cvId, versionEnvironments, versionIdToRemove, versionNameToRemove, selectedEnvSet,
selectedEnvForAK, selectedCVNameForAK, selectedCVNameForHosts,
selectedEnvForHost, affectedActivationKeys, affectedHosts, deleteFlow, removeDeletionFlow,
selectedEnvForHost, deleteFlow, removeDeletionFlow,
} = useContext(DeleteContext);
const activationKeysResponse = useSelector(state => selectCVActivationKeys(state, cvId));
const hostsResponse = useSelector(state => selectCVHosts(state, cvId));
const { results: hostResponse = [] } = hostsResponse || {};
const { results: akResponse = [] } = activationKeysResponse || {};
const cvVersions = useSelector(state => selectCVVersions(state, cvId));
const versionDeleteInfo = __(`Version ${versionNameToRemove} will be deleted from all environments. It will no longer be available for promotion.`);
const removalNotice = __(`Version ${versionNameToRemove} will be removed from the environments listed below, and will remain available for later promotion. ` +
Expand All @@ -29,16 +29,19 @@ const CVVersionRemoveReview = () => {
.flatMap(cv => cv.content_view_environments || [])
.filter(env => selectedEnvSet.has(env.environment_id));

const multiCVHosts = hostResponse?.filter(host =>
host.content_facet_attributes?.multi_content_view_environment) || [];
const multiCVHostsCount = multiCVHosts.length;
const selectedEnvs = versionEnvironments.filter(env => selectedEnvSet.has(env.id));

const singleCVHostsCount = (hostResponse?.length || 0) - multiCVHostsCount;
const hostCount = selectedEnvs.reduce((sum, env) =>
sum + (env.host_count || 0), 0);
const multiCVHostsCount = selectedEnvs.reduce((sum, env) =>
sum + (env.multi_env_host_count || 0), 0);
const singleCVHostsCount = hostCount - multiCVHostsCount;

const multiCVActivationKeys = akResponse.filter(key => key.multi_content_view_environment);
const multiCVActivationKeysCount = multiCVActivationKeys.length;

const singleCVActivationKeysCount = akResponse.length - multiCVActivationKeysCount;
const akCount = selectedEnvs.reduce((sum, env) =>
sum + (env.activation_key_count || 0), 0);
const multiCVActivationKeysCount = selectedEnvs.reduce((sum, env) =>
sum + (env.multi_env_ak_count || 0), 0);
const singleCVActivationKeysCount = akCount - multiCVActivationKeysCount;

return (
<>
Expand Down Expand Up @@ -66,7 +69,7 @@ const CVVersionRemoveReview = () => {
<FlexItem key={name}><Label color="purple" href={`/lifecycle_environments/${id}`}>{name}</Label></FlexItem>)}
</Flex>
</>}
{affectedHosts &&
{hostCount > 0 &&
<>
<h3>{__('Content hosts')}</h3>
{singleCVHostsCount > 0 && (
Expand Down Expand Up @@ -121,8 +124,22 @@ const CVVersionRemoveReview = () => {
</FlexItem>
</Flex>
)}
<ExpandableSection
toggleText={showHosts ? 'Hide hosts' : 'Show hosts'}
onToggle={() => setShowHosts(prev => !prev)}
isExpanded={showHosts}
>
<AffectedHosts
{...{
cvId,
versionEnvironments,
selectedEnvSet,
}}
deleteCV={false}
/>
</ExpandableSection>
</>}
{affectedActivationKeys &&
{akCount > 0 &&
<>
<h3>{__('Activation keys')}</h3>
{singleCVActivationKeysCount > 0 && (
Expand Down Expand Up @@ -177,6 +194,20 @@ const CVVersionRemoveReview = () => {
</FlexItem>
</Flex>
)}
<ExpandableSection
toggleText={showAKs ? 'Hide activation keys' : 'Show activation keys'}
onToggle={() => setShowAKs(prev => !prev)}
isExpanded={showAKs}
>
<AffectedActivationKeys
{...{
cvId,
versionEnvironments,
selectedEnvSet,
}}
deleteCV={false}
/>
</ExpandableSection>
</>}
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { renderWithRedux, patientlyWaitFor, fireEvent } from 'react-testing-lib-wrapper';
import { nockInstance, assertNockRequest, mockAutocomplete, mockForemanAutocomplete } from '../../../../../../test-utils/nockWrapper';
import { nockInstance, assertNockRequest, mockAutocomplete } from '../../../../../../test-utils/nockWrapper';
import api, { foremanApi } from '../../../../../../services/api';
import CONTENT_VIEWS_KEY from '../../../../ContentViewsConstants';
import ContentViewVersions from '../../ContentViewVersions';
Expand Down Expand Up @@ -123,7 +123,11 @@ test('Can open Remove wizard and remove version from simple environment', async
test('Can open Remove wizard and remove version from environment with hosts', async (done) => {
const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
const hostAutocompleteUrl = '/hosts/auto_complete_search';
const hostAutocompleteScope = mockForemanAutocomplete(nockInstance, hostAutocompleteUrl);
const hostAutocompleteScope = nockInstance
.get(foremanApi.getApiUrl(hostAutocompleteUrl))
.query(true)
.times(2)
.reply(200, []);

const scope = nockInstance
.get(cvVersions)
Expand All @@ -138,6 +142,7 @@ test('Can open Remove wizard and remove version from environment with hosts', as
const hostScope = nockInstance
.get(hostURL)
.query(true)
.times(2)
.reply(200, affectedHostData);

const cVDropDownOptionsScope = nockInstance
Expand Down Expand Up @@ -211,7 +216,11 @@ test('Can open Remove wizard and remove version from environment with hosts', as
test('Can open Remove wizard and remove version from environment with activation keys', async (done) => {
const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
const akAutocompleteUrl = '/activation_keys/auto_complete_search';
const akAutocompleteScope = mockAutocomplete(nockInstance, akAutocompleteUrl);
const akAutocompleteScope = nockInstance
.get(api.getApiUrl(akAutocompleteUrl))
.query(true)
.times(2)
.reply(200, []);

const scope = nockInstance
.get(cvVersions)
Expand All @@ -226,6 +235,7 @@ test('Can open Remove wizard and remove version from environment with activation
const activationKeysScope = nockInstance
.get(activationKeyURL)
.query(true)
.times(2)
.reply(200, affectedActivationKeysData);

const cVDropDownOptionsScope = nockInstance
Expand Down
Loading