webpack/scenes/ContentViews/Details/ComponentContentViews/ContentViewComponents.js in katello-4.1.4 vs webpack/scenes/ContentViews/Details/ComponentContentViews/ContentViewComponents.js in katello-4.2.0.rc1
- old
+ new
@@ -1,12 +1,12 @@
-import React, { useState, useEffect } from 'react';
-import { useSelector } from 'react-redux';
-import {
- Bullseye,
-} from '@patternfly/react-core';
+import React, { useState, useCallback } from 'react';
+import useDeepCompareEffect from 'use-deep-compare-effect';
+import { useDispatch, useSelector } from 'react-redux';
+import { Bullseye, Split, SplitItem, Button } from '@patternfly/react-core';
import { Link } from 'react-router-dom';
import { TableVariant, fitContent, TableText } from '@patternfly/react-table';
+import { PencilAltIcon } from '@patternfly/react-icons';
import { STATUS } from 'foremanReact/constants';
import { translate as __ } from 'foremanReact/common/I18n';
import { urlBuilder } from 'foremanReact/common/urlHelpers';
import PropTypes from 'prop-types';
@@ -14,78 +14,184 @@
import onSelect from '../../../../components/Table/helpers';
import {
selectCVComponents,
selectCVComponentsStatus,
selectCVComponentsError,
+ selectCVComponentAddStatus,
+ selectCVComponentRemoveStatus,
} from '../ContentViewDetailSelectors';
-import { getContentViewComponents } from '../ContentViewDetailActions';
+import getContentViewDetails, {
+ addComponent, getContentViewComponents,
+ removeComponent,
+} from '../ContentViewDetailActions';
import AddedStatusLabel from '../../../../components/AddedStatusLabel';
import ComponentVersion from './ComponentVersion';
import ComponentEnvironments from './ComponentEnvironments';
import ContentViewIcon from '../../components/ContentViewIcon';
+import { ADDED, ALL_STATUSES, NOT_ADDED } from '../../ContentViewsConstants';
+import SelectableDropdown from '../../../../components/SelectableDropdown/SelectableDropdown';
+import '../../../../components/EditableTextInput/editableTextInput.scss';
+import ComponentContentViewAddModal from './ComponentContentViewAddModal';
const ContentViewComponents = ({ cvId, details }) => {
const response = useSelector(state => selectCVComponents(state, cvId));
const status = useSelector(state => selectCVComponentsStatus(state, cvId));
const error = useSelector(state => selectCVComponentsError(state, cvId));
-
+ const componentAddedStatus = useSelector(state => selectCVComponentAddStatus(state, cvId));
+ const componentRemovedStatus = useSelector(state => selectCVComponentRemoveStatus(state, cvId));
const [rows, setRows] = useState([]);
const [metadata, setMetadata] = useState({});
const [searchQuery, updateSearchQuery] = useState('');
+ const [statusSelected, setStatusSelected] = useState(ALL_STATUSES);
+ const [versionEditing, setVersionEditing] = useState(false);
+ const [compositeCvEditing, setCompositeCvEditing] = useState(null);
+ const [componentCvEditing, setComponentCvEditing] = useState(null);
+ const [componentLatest, setComponentLatest] = useState(false);
+ const [componentId, setComponentId] = useState(null);
+ const dispatch = useDispatch();
+
const columnHeaders = [
{ title: __('Type'), transforms: [fitContent] },
{ title: __('Name') },
{ title: __('Version') },
{ title: __('Environments') },
{ title: __('Repositories') },
{ title: __('Status') },
{ title: __('Description') },
];
const loading = status === STATUS.PENDING;
+ const addComponentsResolved = componentAddedStatus === STATUS.RESOLVED;
+ const removeComponentsResolved = componentRemovedStatus === STATUS.RESOLVED;
+
const { label } = details || {};
- const buildRows = (results) => {
+ const bulkRemoveEnabled = () => rows.some(row => row.selected && row.added);
+
+ const onAdd = useCallback(({
+ componentCvId, published, added, latest,
+ }) => {
+ if (published) {
+ dispatch(getContentViewDetails(componentCvId));
+ setVersionEditing(true);
+ setCompositeCvEditing(cvId);
+ setComponentCvEditing(componentCvId);
+ setComponentLatest(latest);
+ setComponentId(added);
+ } else {
+ dispatch(addComponent({
+ compositeContentViewId: cvId,
+ components: [{ latest: true, content_view_id: componentCvId }],
+ }));
+ }
+ }, [cvId, dispatch]);
+
+ const removeBulk = () => {
+ const componentIds = [];
+ rows.forEach(row => row.selected && componentIds.push(row.added));
+ dispatch(removeComponent({
+ compositeContentViewId: cvId,
+ component_ids: componentIds,
+ }));
+ };
+
+ const onRemove = (componentIdToRemove) => {
+ dispatch(removeComponent({
+ compositeContentViewId: cvId,
+ component_ids: [componentIdToRemove],
+ }));
+ };
+
+ const buildRows = useCallback((results) => {
const newRows = [];
results.forEach((componentCV) => {
- const { content_view: cv, content_view_version: cvVersion } = componentCV;
+ const {
+ id: componentCvId, content_view: cv, content_view_version: cvVersion, latest,
+ } = componentCV;
const { environments, repositories } = cvVersion || {};
const {
id,
name,
description,
} = cv;
const cells = [
{ title: <Bullseye><ContentViewIcon composite={false} /></Bullseye> },
{ title: <Link to={urlBuilder('labs/content_views', '', id)}>{name}</Link> },
- { title: cvVersion ? <ComponentVersion {...{ componentCV }} /> : 'Not yet published' },
- { title: environments ? <ComponentEnvironments {...{ environments }} /> : 'Not yet published' },
+ {
+ title:
+ <Split>
+ <SplitItem>
+ <ComponentVersion {...{ componentCV }} />
+ </SplitItem>
+ {componentCvId && cvVersion &&
+ <SplitItem>
+ <Button
+ className="foreman-edit-icon"
+ aria-label="edit_version"
+ variant="plain"
+ onClick={() => {
+ onAdd({
+ componentCvId: id, published: cvVersion, added: componentCvId, latest,
+ });
+ }}
+ >
+ <PencilAltIcon />
+ </Button>
+ </SplitItem>}
+ </Split>,
+ },
+ { title: environments ? <ComponentEnvironments {...{ environments }} /> : __('Not yet published') },
{ title: <Link to={urlBuilder(`labs/content_views/${id}#repositories`, '')}>{ repositories ? repositories.length : 0 }</Link> },
{
- title: <AddedStatusLabel added />,
+ title: <AddedStatusLabel added={componentCvId} />,
},
- { title: <TableText wrapModifier="truncate">{description || 'No description'}</TableText> },
+ { title: <TableText wrapModifier="truncate">{description || __('No description')}</TableText> },
];
- newRows.push({ cells });
+ newRows.push({
+ componentCvId: id, added: componentCvId, published: cvVersion, latest, cells,
+ });
});
return newRows;
- };
+ }, [onAdd]);
+ const actionResolver = (rowData, { _rowIndex }) => [
+ {
+ title: __('Add'),
+ isDisabled: rowData.added,
+ onClick: (_event, rowId, rowInfo) => {
+ onAdd({
+ componentCvId: rowInfo.componentCvId,
+ published: rowInfo.published,
+ added: rowInfo.added,
+ latest: rowInfo.latest,
+ });
+ },
+ },
+ {
+ title: __('Remove'),
+ isDisabled: !rowData.added,
+ onClick: (_event, rowId, rowInfo) => {
+ onRemove(rowInfo.added);
+ },
+ },
+ ];
+
const emptyContentTitle = __(`No content views belong to ${label}`);
const emptyContentBody = __('Please add some content views.');
const emptySearchTitle = __('No matching content views found');
const emptySearchBody = __('Try changing your search settings.');
+ const activeFilters = statusSelected && statusSelected !== ALL_STATUSES;
- useEffect(() => {
+ useDeepCompareEffect(() => {
const { results, ...meta } = response;
setMetadata(meta);
if (!loading && results) {
const newRows = buildRows(results);
setRows(newRows);
}
- }, [JSON.stringify(response)]);
+ }, [response, buildRows, loading]);
return (
<TableWrapper
{...{
rows,
@@ -96,16 +202,48 @@
emptySearchBody,
searchQuery,
updateSearchQuery,
error,
status,
+ activeFilters,
+ actionResolver,
}}
onSelect={onSelect(rows, setRows)}
cells={columnHeaders}
variant={TableVariant.compact}
autocompleteEndpoint="/content_views/auto_complete_search"
- fetchItems={params => getContentViewComponents(cvId, params)}
- />
+ fetchItems={useCallback(params =>
+ getContentViewComponents(cvId, params, statusSelected), [cvId, statusSelected])}
+ additionalListeners={[statusSelected, addComponentsResolved, removeComponentsResolved]}
+ >
+ <Split hasGutter>
+ <SplitItem>
+ <SelectableDropdown
+ items={[ADDED, NOT_ADDED, ALL_STATUSES]}
+ title={__('Status')}
+ selected={statusSelected}
+ setSelected={setStatusSelected}
+ placeholderText={__('Status')}
+ />
+ </SplitItem>
+ <SplitItem>
+ <Button onClick={removeBulk} isDisabled={!(bulkRemoveEnabled())} variant="secondary" aria-label="remove_components">
+ {__('Remove content views')}
+ </Button>
+ </SplitItem>
+ </Split>
+ {versionEditing &&
+ <ComponentContentViewAddModal
+ cvId={compositeCvEditing}
+ componentCvId={componentCvEditing}
+ componentId={componentId}
+ latest={componentLatest}
+ show={versionEditing}
+ setIsOpen={setVersionEditing}
+ aria-label="copy_content_view_modal"
+ />
+ }
+ </TableWrapper>
);
};
ContentViewComponents.propTypes = {
cvId: PropTypes.number.isRequired,