Skip to content

Commit

Permalink
Fixes #37675 - Change All Hosts kebab menu to match design
Browse files Browse the repository at this point in the history
Refs #37675 - Add registry for table row kebab actions
  • Loading branch information
jeremylenz committed Jul 25, 2024
1 parent 175b5f2 commit cf38a35
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 38 deletions.
28 changes: 28 additions & 0 deletions .byebug_history
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
continue
cve.content_view.label
cve.environment.label
cve
continue
::Katello::ContentViewEnvironment.where(:content_view_id => @cv4.id, :environment_id => @dev.id).first
@cv4 = katello_content_views(:library_dev_view)
::Katello::ContentViewEnvironment.where(:content_view_id => @cv4.id, :environment_id => @dev.id).first
@cv4 = content_view_environments(:library_dev_view)
::Katello::ContentViewEnvironment.where(:content_view_id => @cv4.id, :environment_id => @dev.id).first
::Katello::ContentViewEnvironment.where(:content_view_id => @cv3.id, :environment_id => @dev.id).first
::Katello::ContentViewEnvironment.where(:content_view_id => @cv2.id, :environment_id => @dev.id).first
@cv3
@cv2
continue
@cv2 = katello_content_views(:library_view_no_version)
models
@dev
@cv2
::Katello::ContentViewEnvironment.where(:content_view_id => @cv2.id, :environment_id => @dev.id).first
::Katello::ContentViewEnvironment.where(:content_view_id => @cv2.id,
93: :environment_id => @dev.id).first
target_cvs_ids
next
continue
host.content_facet.content_view_environments.reload.ids
host.content_facet.content_view_environments.ids
host.content_facet.content_view_environments
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
import React, { useState } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Dropdown, KebabToggle } from '@patternfly/react-core';
import { Menu, MenuToggle } from '@patternfly/react-core';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
import './ActionKebab.scss';

/**
* Generate a button or a dropdown of buttons
* @param {String} title The title of the button for the title and text inside the button
* @param {Object} action action to preform when the button is click can be href with data-method or Onclick
* @return {Function} button component or splitbutton component
*/
export const ActionKebab = ({ items }) => {
const [isOpen, setIsOpen] = useState(false);
export const ActionKebab = ({ items, menuOpen, setMenuOpen }) => {
if (!items.length) return null;
return (
<>
{items.length > 0 && (
<Dropdown
ouiaId="action-buttons-dropdown"
toggle={
<KebabToggle
aria-label="toggle action dropdown"
onToggle={setIsOpen}
/>
}
isOpen={isOpen}
isPlain
dropdownItems={items}
/>
<MenuToggle
variant="plain"
aria-label="plain kebab"
onClick={() => setMenuOpen(prev => !prev)}
isExpanded={menuOpen}
>
<EllipsisVIcon />
</MenuToggle>
{items.length > 0 && menuOpen && (
<Menu
containsFlyout
ouiaId="hosts-index-actions-kebab"
id="hosts-index-actions-kebab"
onSelect={() => setMenuOpen(false)}
>
{items}
</Menu>
)}
</>
);
};

ActionKebab.propTypes = {
items: PropTypes.arrayOf(PropTypes.node),
menuOpen: PropTypes.bool.isRequired,
setMenuOpen: PropTypes.func.isRequired,
};

ActionKebab.defaultProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#hosts-index-actions-kebab {
position: absolute;
top: 3.1em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import forceSingleton from '../../../common/forceSingleton';

const coreTableRowActionsRegistry = forceSingleton(
'coreTableRowActionsRegistry',
() => ({})
);

// Unlike the column registry which is collecting objects that describe table columns,
// here we collect an object containing a single getActions funtion, which returns an array of kebab action items.
export const registerGetActions = ({
pluginName,
getActionsFunc,
tableName = 'hosts',
}) => {
if (!coreTableRowActionsRegistry[pluginName])
coreTableRowActionsRegistry[pluginName] = {};
coreTableRowActionsRegistry[pluginName][tableName] = {
getActions: getActionsFunc,
};
};

export const registeredTableRowActions = ({ tableName = 'hosts' }) => {
const result = {};
Object.keys(coreTableRowActionsRegistry).forEach(pluginName => {
if (coreTableRowActionsRegistry[pluginName]?.[tableName]) {
result[pluginName] = coreTableRowActionsRegistry[pluginName][tableName];
}
});
// { katello: { getActions: [Function: getActions] } }
return result;
};

export const getActions = (hostDetailsResult, { tableName = 'hosts' } = {}) => {
const result = [];
const allGetActionsFuncs = registeredTableRowActions({ tableName });
Object.values(allGetActionsFuncs).forEach(
({ getActions: getActionsFunc }) => {
if (typeof getActionsFunc !== 'function') return;
result.push(...getActionsFunc(hostDetailsResult));
}
);
return result;
};

export default getActions;
74 changes: 55 additions & 19 deletions webpack/assets/javascripts/react_app/components/HostsIndex/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { Tr, Td, ActionsColumn } from '@patternfly/react-table';
import {
ToolbarItem,
Divider,
Dropdown,
DropdownItem,
MenuItem,
KebabToggle,
Flex,
FlexItem,
Expand Down Expand Up @@ -52,13 +54,15 @@ import getColumnData from './Columns/core';
import { categoriesFromFrontendColumnData } from '../ColumnSelector/helpers';
import ColumnSelector from '../ColumnSelector';
import { ForemanActionsBarContext } from '../HostDetails/ActionsBar';
import { registerGetActions, getActions } from './TableRowActions/core';

export const ForemanHostsIndexActionsBarContext = forceSingleton(
'ForemanHostsIndexActionsBarContext',
() => createContext({})
);

const HostsIndex = () => {
const [menuOpen, setMenuOpen] = useState(false);
const [allColumns, setAllColumns] = useState(
getColumnData({ tableName: 'hosts' })
);
Expand Down Expand Up @@ -205,55 +209,87 @@ const HostsIndex = () => {
});

const dropdownItems = [
<DropdownItem
ouiaId="delete=hosts-dropdown-item"
key="delete=hosts-dropdown-item"
onClick={handleBulkDelete}
isDisabled={selectedCount === 0}
>
{__('Delete')}
</DropdownItem>,
<DropdownItem
ouiaId="build-hosts-dropdown-item"
<MenuItem
itemId="build-hosts-dropdown-item"
key="build-hosts-dropdown-item"
onClick={setBuildModalOpen}
isDisabled={selectedCount === 0}
>
{__('Build management')}
</DropdownItem>,
<DropdownItem
ouiaId="reassign-hg-dropdown-item"
</MenuItem>,
<MenuItem
itemId="reassign-hg-dropdown-item"
key="reassign-hg-dropdown-item"
onClick={setHgModalOpen}
isDisabled={selectedCount === 0}
>
{__('Change host group')}
</DropdownItem>,
</MenuItem>,
];

const dangerZoneItems = [
<Divider
component="li"
id="danger-zone-separator"
key="danger-zone-separator"
/>,
<MenuItem
itemId="delete=hosts-dropdown-item"
key="delete=hosts-dropdown-item"
onClick={handleBulkDelete}
isDisabled={selectedCount === 0}
>
{__('Delete')}
</MenuItem>,
];

const registeredItems = useSelector(selectKebabItems, shallowEqual);
const pluginToolbarItems = jsReady => (
<ForemanHostsIndexActionsBarContext.Provider
value={{ ...selectAllOptions, fetchBulkParams }}
value={{ ...selectAllOptions, fetchBulkParams, menuOpen, setMenuOpen }}
>
<ActionKebab items={dropdownItems.concat(registeredItems)} />
<ActionKebab
items={dropdownItems.concat(registeredItems).concat(dangerZoneItems)}
menuOpen={menuOpen}
setMenuOpen={setMenuOpen}
/>
{jsReady && <ColumnSelector data={columnSelectData} />}
</ForemanHostsIndexActionsBarContext.Provider>
);

const rowKebabItems = ({
const coreRowKebabItems = ({
id,
name: hostName,
compute_id: computeId,
can_delete: canDelete,
can_edit: canEdit,
}) => [
{
title: __('Edit'),
onClick: () => {
window.location.href = foremanUrl(`/hosts/${id}/edit`);
},
isDisabled: !canEdit,
},
{
title: __('Clone'),
onClick: () => {
window.location.href = foremanUrl(`/hosts/${id}/clone`);
},
isDisabled: !canEdit,
},
{
title: __('Delete'),
onClick: () => deleteHostHandler({ id, hostName, computeId }),
isDisabled: !canDelete,
},
];

registerGetActions({
pluginName: 'core',
getActionsFunc: coreRowKebabItems,
});

const [legacyUIKebabOpen, setLegacyUIKebabOpen] = useState(false);
const legacyUIKebab = (
<Dropdown
Expand Down Expand Up @@ -345,7 +381,7 @@ const HostsIndex = () => {
ouiaId="hosts-index-table"
params={params}
setParams={setParamsAndAPI}
getActions={rowKebabItems}
getActions={getActions}
itemCount={subtotal}
results={results}
url={HOSTS_API_PATH}
Expand All @@ -364,7 +400,7 @@ const HostsIndex = () => {
isPending={status === STATUS.PENDING}
>
{results?.map((result, rowIndex) => {
const rowActions = rowKebabItems(result);
const rowActions = getActions(result);
return (
<Tr key={rowIndex} ouiaId={`table-row-${rowIndex}`} isHoverable>
{<RowSelectTd rowData={result} {...{ selectOne, isSelected }} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,22 @@ export const Table = ({
setSelectedItem({ id, name });
setDeleteModalOpen(true);
};
const actions = ({ can_delete: canDelete, id, name, ...item }) =>
const actions = ({
can_delete: canDelete,
can_edit: canEdit,
id,
name,
...item
}) =>
[
isDeleteable && {
title: __('Delete'),
onClick: () => onDeleteClick({ id, name }),
isDisabled: !canDelete,
},
...((getActions && getActions({ id, name, canDelete, ...item })) ?? []),
...((getActions &&
getActions({ id, name, canDelete, canEdit, ...item })) ??
[]),
].filter(Boolean);
const RowSelectTd = rowSelectTd;
return (
Expand Down

0 comments on commit cf38a35

Please sign in to comment.