import React, {
useCallback,
useEffect,
useState,
useMemo,
} from 'react';
import { propsToCamelCase, noop } from 'foremanReact/common/helpers';
import { translate as __ } from 'foremanReact/common/I18n';
import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { STATUS } from 'foremanReact/constants';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { isEqual } from 'lodash';
import {
ActionList,
ActionListItem,
Alert,
AlertActionCloseButton,
Dropdown,
DropdownItem,
KebabToggle,
Label,
Skeleton,
Split,
SplitItem,
ToggleGroup,
ToggleGroupItem,
Tooltip,
} from '@patternfly/react-core';
import { FlagIcon } from '@patternfly/react-icons';
import {
TableVariant,
Tbody,
Td,
Th,
Thead,
Tr,
} from '@patternfly/react-table';
import { useTableSort } from 'foremanReact/components/PF4/Helpers/useTableSort';
import {
useBulkSelect,
useUrlParams,
} from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
import TableWrapper from '../../../../../components/Table/TableWrapper';
import hostIdNotReady from '../../HostDetailsActions';
import { selectHostDetailsStatus } from '../../HostDetailsSelectors.js';
import {
getHostRepositorySets,
setContentOverrides,
} from './RepositorySetsActions';
import { selectOrganizationStatus } from '../../Cards/SystemPurposeCard/SystemPurposeSelectors';
import { getOrganization } from '../../Cards/SystemPurposeCard/SystemPurposeActions';
import { REPOSITORY_SETS_KEY, STATUSES, STATUS_TO_PARAM, PARAM_TO_FRIENDLY_NAME, PROVIDER_TYPES, PROVIDER_TYPE_PARAM_TO_FRIENDLY_NAME, PROVIDER_TYPE_TO_PARAM } from './RepositorySetsConstants.js';
import { selectRepositorySetsStatus } from './RepositorySetsSelectors';
import './RepositorySetsTab.scss';
import SortableColumnHeaders from '../../../../Table/components/SortableColumnHeaders';
import SelectableDropdown from '../../../../SelectableDropdown';
import {
hasRequiredPermissions as can,
missingRequiredPermissions as cannot,
userPermissionsFromHostDetails,
} from '../../hostDetailsHelpers';
const viewRepoSets = [
'view_hosts', 'view_activation_keys', 'view_products',
];
const createBookmarks = ['create_bookmarks'];
export const hideRepoSetsTab = ({ hostDetails }) =>
cannot(
viewRepoSets,
userPermissionsFromHostDetails({ hostDetails }),
);
const editHosts = ['edit_hosts'];
const getEnabledValue = ({ enabled, enabledContentOverride }) => {
const isOverridden = (enabledContentOverride !== null);
return {
isOverridden,
isEnabled: (isOverridden ? enabledContentOverride : enabled),
};
};
const EnabledIcon = ({ isEnabled, isOverridden }) => {
const enabledLabel = (
: null}
className={`${isEnabled ? 'enabled' : 'disabled'}-label${isOverridden ? ' content-override' : ''}`}
>
{isEnabled ? __('Enabled') : __('Disabled')}
);
if (isOverridden) {
return (
{enabledLabel}
);
}
return enabledLabel;
};
EnabledIcon.propTypes = {
isEnabled: PropTypes.bool.isRequired,
isOverridden: PropTypes.bool.isRequired,
};
const ArchRestrictedIcon = ({ archRestricted }) => (
}
>
);
ArchRestrictedIcon.propTypes = {
archRestricted: PropTypes.string,
};
ArchRestrictedIcon.defaultProps = {
archRestricted: null,
};
const OsRestrictedIcon = ({ osRestricted }) => (
}
>
);
OsRestrictedIcon.propTypes = {
osRestricted: PropTypes.string,
};
OsRestrictedIcon.defaultProps = {
osRestricted: null,
};
const RepositorySetsTab = () => {
const hostDetails = useSelector(state => selectAPIResponse(state, 'HOST_DETAILS'));
const {
id: hostId,
content_facet_attributes: contentFacetAttributes,
organization_id: orgId,
} = hostDetails;
const orgStatus = useSelector(state => selectOrganizationStatus(state, orgId));
const orgNotLoaded = orgStatus !== STATUS.RESOLVED;
const canDoContentOverrides = can(
editHosts,
userPermissionsFromHostDetails({ hostDetails }),
);
const STATUS_LABEL = __('Status');
const REPO_TYPE_LABEL = __('Repository type');
const contentFacet = propsToCamelCase(contentFacetAttributes ?? {});
const {
contentViewDefault,
lifecycleEnvironmentLibrary,
contentView,
lifecycleEnvironment,
} = contentFacet;
const { name: contentViewName } = contentView ?? {};
const { name: lifecycleEnvironmentName } = lifecycleEnvironment ?? {};
const nonLibraryHost = contentViewDefault === false ||
lifecycleEnvironmentLibrary === false;
const [isBulkActionOpen, setIsBulkActionOpen] = useState(false);
const toggleBulkAction = () => setIsBulkActionOpen(prev => !prev);
const dispatch = useDispatch();
const {
searchParam, show, status: initialStatus,
repository_type: initialRepoType,
} = useUrlParams();
const toggleGroupStates = ['noLimit', 'limitToEnvironment'];
const [SHOW_ALL, LIMIT_TO_ENVIRONMENT] = toggleGroupStates;
const defaultToggleGroupState = LIMIT_TO_ENVIRONMENT;
const unfilteredToggleGroupState = SHOW_ALL;
const [toggleGroupState, setToggleGroupState] =
useState(show ?? defaultToggleGroupState);
const [statusSelected, setStatusSelected]
= useState(PARAM_TO_FRIENDLY_NAME[initialStatus] ?? STATUS_LABEL);
const [repoTypeSelected, setRepoTypeSelected]
= useState(PROVIDER_TYPE_PARAM_TO_FRIENDLY_NAME[initialRepoType] ?? REPO_TYPE_LABEL);
const activeFilters = [statusSelected, repoTypeSelected];
const defaultFilters = [STATUS_LABEL, REPO_TYPE_LABEL];
const [alertShowing, setAlertShowing] = useState(false);
const emptyContentTitle = __('No repository sets to show.');
const emptyContentBody = toggleGroupState === SHOW_ALL ?
__('Repository sets will appear here after enabling Red Hat repositories or creating custom products.') :
__('Repository sets will appear here when the host\'s content view and environment has available content.');
const emptySearchTitle = __('No matching repository sets found');
const emptySearchBody = __('Try changing your search query.');
const primaryActionTitle = __('Enable Red Hat repositories');
const secondaryActionTitle = __('Create a custom product');
const primaryActionLink = '/redhat_repositories';
const secondaryActionLink = '/products/new';
const errorSearchTitle = __('Problem searching repository sets');
const columnHeaders = useMemo(() => [
__('Repository'),
__('Product'),
__('Repository path'),
__('Status'),
__('Repository type'),
], []);
const COLUMNS_TO_SORT_PARAMS = {
[columnHeaders[0]]: 'name',
[columnHeaders[1]]: 'product',
[columnHeaders[3]]: 'enabled_by_default',
[columnHeaders[4]]: 'redhat',
};
const {
pfSortParams, apiSortParams,
activeSortColumn, activeSortDirection,
} = useTableSort({
allColumns: columnHeaders,
columnsToSortParams: COLUMNS_TO_SORT_PARAMS,
});
const fetchItems = useCallback(
(params) => {
if (!hostId) return hostIdNotReady;
const modifiedParams = { ...params };
if (statusSelected !== STATUS_LABEL) {
modifiedParams.status = STATUS_TO_PARAM[statusSelected];
}
if (repoTypeSelected !== REPO_TYPE_LABEL) {
modifiedParams.repository_type = PROVIDER_TYPE_TO_PARAM[repoTypeSelected];
}
return getHostRepositorySets({
content_access_mode_env: toggleGroupState === LIMIT_TO_ENVIRONMENT,
content_access_mode_all: true,
host_id: hostId,
...apiSortParams,
...modifiedParams,
});
},
[hostId, statusSelected, STATUS_LABEL, repoTypeSelected,
REPO_TYPE_LABEL, toggleGroupState, LIMIT_TO_ENVIRONMENT, apiSortParams],
);
useEffect(() => {
if (orgId && orgNotLoaded) {
dispatch(getOrganization({ orgId }));
}
}, [orgId, orgNotLoaded, dispatch]);
const status = useSelector(state => selectRepositorySetsStatus(state));
const response = useSelector(state => selectAPIResponse(state, REPOSITORY_SETS_KEY));
const { results, error: errorSearchBody, ...metadata } = response;
const filtersQuery = () => {
const query = [];
if (repoTypeSelected !== REPO_TYPE_LABEL) {
query.push(`redhat=${PROVIDER_TYPE_TO_PARAM[repoTypeSelected] === 'redhat'}`);
}
return query.join(' and ');
};
const repoSetSearchQuery = label => `cp_content_id = ${label}`;
const {
selectOne, isSelected, searchQuery, selectedCount, isSelectable,
updateSearchQuery, selectNone, fetchBulkParams, ...selectAll
} = useBulkSelect({
results,
metadata,
initialSearchQuery: searchParam || '',
filtersQuery: filtersQuery(),
});
if (statusSelected !== STATUS_LABEL) {
selectAll.selectAll = noop; // disable select all when filtering by status
}
const hostDetailsStatus = useSelector(state => selectHostDetailsStatus(state));
// Ignore the toggle group when deciding if there is emptyContent. This will
// ensure the correct EmptyStateMessage
const isFiltering = activeFilters?.length &&
!isEqual(new Set(activeFilters), new Set(defaultFilters));
const emptyContent = (results && results.length === 0) && !searchQuery && !isFiltering;
const showPrimaryAction = (toggleGroupState === SHOW_ALL) && emptyContent;
const showSecondaryAction = showPrimaryAction;
const resetFilters = () => {
setStatusSelected(STATUS_LABEL);
setRepoTypeSelected(REPO_TYPE_LABEL);
if (emptyContent) setToggleGroupState(SHOW_ALL);
};
useEffect(() => {
// wait until host details are loaded to set alertShowing
if (hostDetailsStatus === STATUS.RESOLVED) {
setAlertShowing(nonLibraryHost);
}
}, [hostDetailsStatus, nonLibraryHost]);
if (!hostId || orgNotLoaded) return ;
const updateResults = newResponse => dispatch({
type: `${REPOSITORY_SETS_KEY}_SUCCESS`,
key: REPOSITORY_SETS_KEY,
response: {
...response,
results: results.map((result) => {
const {
enabled,
enabled_content_override: enabledContentOverride,
} = newResponse.results.find(r => r.id === result.id);
if (enabled !== null) {
return { ...result, enabled, enabled_content_override: enabledContentOverride };
}
return result;
}),
},
});
const handleStatusSelected = newType => setStatusSelected((prevType) => {
if (prevType === newType) {
return STATUS_LABEL;
}
return newType;
});
const handleRepoTypeSelected = newType => setRepoTypeSelected((prevType) => {
if (prevType === newType) {
return REPO_TYPE_LABEL;
}
return newType;
});
const updateOverrides = ({
enabled, remove = false, search, singular = false,
}) => {
setIsBulkActionOpen(false);
selectNone();
dispatch(setContentOverrides({
hostId,
search,
enabled,
limit_to_env: toggleGroupState === LIMIT_TO_ENVIRONMENT,
remove,
updateResults: resp => updateResults(resp),
singular: singular || selectedCount === 1,
}));
};
const bulkParams = () => fetchBulkParams({ idColumnName: 'cp_content_id' });
const enableRepoSets = () => updateOverrides({ enabled: true, search: bulkParams() });
const disableRepoSets = () => updateOverrides({ enabled: false, search: bulkParams() });
const resetToDefaultRepoSets = () => updateOverrides({ remove: true, search: bulkParams() });
const enableRepoSet = id => updateOverrides({
enabled: true,
search: repoSetSearchQuery(id),
singular: true,
});
const disableRepoSet = id => updateOverrides({
enabled: false,
search: repoSetSearchQuery(id),
singular: true,
});
const resetToDefaultRepoSet = id => updateOverrides({
remove: true,
search: repoSetSearchQuery(id),
singular: true,
});
const readOnlyBookmarks =
cannot(createBookmarks, userPermissionsFromHostDetails({ hostDetails }));
const dropdownItems = [
{__('Override to enabled')}
,
{__('Override to disabled')}
,
{__('Reset to default')}
,
];
const toggleGroup = (
{nonLibraryHost &&
setToggleGroupState(SHOW_ALL)}
/>
setToggleGroupState(LIMIT_TO_ENVIRONMENT)}
/>
}
);
const actionButtons = canDoContentOverrides ? (
}
isOpen={isBulkActionOpen}
isPlain
dropdownItems={dropdownItems}
ouiaId="repository-sets-bulk-actions"
/>
) : null;
const hostEnvText = 'the "{contentViewName}" content view and "{lifecycleEnvironmentName}" environment';
const alertText = (toggleGroupState === LIMIT_TO_ENVIRONMENT ?
`Showing only repositories in ${hostEnvText}.` :
'Showing all available repositories.');
return (
{__('Red Hat Repositories page')},
}}
/>
{alertShowing &&
}
actionClose={
setAlertShowing(false)} />}
/>
}
|
|
{results?.map((repoSet, rowIndex) => {
const {
id,
content: { name: repoName },
enabled,
enabled_content_override: enabledContentOverride,
contentUrl: repoPath,
product: { name: productName, id: productId },
osRestricted,
archRestricted,
redhat,
} = repoSet;
const { isEnabled, isOverridden } =
getEnabledValue({ enabled, enabledContentOverride });
const showArchRestricted = archRestricted && archRestricted !== 'noarch';
return (
{canDoContentOverrides ? (
selectOne(selected, id),
rowIndex,
variant: 'checkbox',
}}
/>
) : | | }
{repoName}
|
{productName}
|
{repoPath}
|
|
{redhat ? __('Red Hat') : __('Custom')}
{showArchRestricted &&
}
{osRestricted &&
}
|
{canDoContentOverrides ? (
disableRepoSet(id),
},
{
title: __('Override to enabled'),
isDisabled: isOverridden && isEnabled,
onClick: () => enableRepoSet(id),
},
{
title: __('Reset to default'),
isDisabled: !isOverridden,
onClick: () => resetToDefaultRepoSet(id),
},
],
}}
/>
) : | | }
);
})
}
);
};
export default RepositorySetsTab;