Skip to content

Commit

Permalink
Fixes #36822 - Design new hosts page
Browse files Browse the repository at this point in the history
Update the TableIndexPage as a part of this action to accept things like
select all

Refs #36822 - Added a new change concent source action
  • Loading branch information
parthaa committed Oct 12, 2023
1 parent d329c93 commit 4933bdb
Show file tree
Hide file tree
Showing 13 changed files with 758 additions and 53 deletions.
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,13 @@
mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/api/graphql'
end


match 'host_statuses' => 'react#index', :via => :get
constraints(id: /[^\/]+/) do
match 'new/hosts/:id' => 'react#index', :via => :get, :as => :host_details_page
end
match 'new/hosts/' => 'react#index', :via => :get

get 'page-not-found' => 'react#index'
get 'links/:type(/:section)' => 'links#show', :as => 'external_link', :constraints => { section: %r{.*} }
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { CubeIcon } from '@patternfly/react-icons';
import { translate as __ } from '../../common/I18n';
import TableIndexPage from '../PF4/TableIndexPage/TableIndexPage';
import { HOSTS_API_PATH, API_REQUEST_KEY } from '../../routes/Hosts/constants';

const HostsIndex = () => {
const columns = {
name: {
title: __('Name'),
wrapper: ({ can_edit: canEdit, id, name }) =>
canEdit ? (
<a href={`/models/${id}/edit`}>{name}</a>
) : (
<span>{name}</span>
),
isSorted: true,
},
};

const computeContentSource = search =>
`/change_host_content_source?search=${search}`;

const customActionKebabs = [
{
title: __('Change content source'),
icon: <CubeIcon />,
computeHref: computeContentSource,
},
];

return (
<TableIndexPage
apiUrl={HOSTS_API_PATH}
apiOptions={{ key: API_REQUEST_KEY }}
header={__('Hosts')}
controller="hosts"
isDeleteable
columns={columns}
displaySelectAllCheckbox
customActionKebabs={customActionKebabs}
creatable={false}
/>
);
};

export default HostsIndex;
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import React, { useState } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
Dropdown,
KebabToggle,
DropdownItem,
} from '@patternfly/react-core';
import { Button } from '@patternfly/react-core';

/**
* Generate a button or a dropdown of buttons
Expand All @@ -15,43 +10,20 @@ import {
*/
export const ActionButtons = ({ buttons: originalButtons }) => {
const buttons = [...originalButtons];
const [isOpen, setIsOpen] = useState(false);
if (!buttons.length) return null;
const firstButton = buttons.shift();
return (
<>
<Button
ouiaId="action-buttons-button"
component={firstButton.action?.href ? 'a' : undefined}
{...firstButton.action}
>
{firstButton.title}
</Button>
{buttons.length > 0 && (
<Dropdown
ouiaId="action-buttons-dropdown"
toggle={
<KebabToggle
aria-label="toggle action dropdown"
onToggle={setIsOpen}
/>
}
isOpen={isOpen}
isPlain
dropdownItems={buttons.map(button => (
<DropdownItem
ouiaId={`${button.title}-dropdown-item`}
key={button.title}
title={button.title}
{...button.action}
>
{button.icon} {button.title}
</DropdownItem>
))}
/>
)}
</>
);

const pfButtons = buttons.map(button => (
<Button
key={button.title}
ouiaId="action-buttons-button"
component={button.action?.href ? 'a' : undefined}
{...button.action}
>
{button.title}
</Button>
));

return <>{pfButtons}</>;
};

ActionButtons.propTypes = {
Expand All @@ -60,6 +32,7 @@ ActionButtons.propTypes = {
action: PropTypes.object,
title: PropTypes.string,
icon: PropTypes.node,
isDisabled: PropTypes.bool,
})
),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {

Check failure on line 3 in webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/ActionKebab.js

View workflow job for this annotation

GitHub Actions / test (12)

Replace `⏎··Dropdown,⏎··KebabToggle,⏎··DropdownItem,⏎` with `·Dropdown,·KebabToggle,·DropdownItem·`

Check failure on line 3 in webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/ActionKebab.js

View workflow job for this annotation

GitHub Actions / test (14)

Replace `⏎··Dropdown,⏎··KebabToggle,⏎··DropdownItem,⏎` with `·Dropdown,·KebabToggle,·DropdownItem·`
Dropdown,
KebabToggle,
DropdownItem,
} from '@patternfly/react-core';

/**
* 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: originalItems }) => {
const items = [...originalItems];
const [isOpen, setIsOpen] = useState(false);
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.map(item => (
<DropdownItem
ouiaId={`${item.title}-dropdown-item`}
key={item.title}
title={item.title}
{...item.action}
isDisabled={item.isDisabled}
>
{item.icon} {item.title}
</DropdownItem>
))}
/>
)}
</>
);
};

ActionKebab.propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
action: PropTypes.object,
title: PropTypes.string,
icon: PropTypes.node,
isDisabled: PropTypes.bool,
})
),
};

ActionKebab.defaultProps = {
items: [],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import {
Dropdown,
DropdownToggle,
DropdownToggleCheckbox,
DropdownItem,
} from '@patternfly/react-core';
import { translate as __ } from '../../../../common/I18n';
import { noop } from '../../../../common/helpers';

import './SelectAllCheckbox.scss';

const SelectAllCheckbox = ({
selectNone,
selectPage,
selectedCount,
pageRowCount,
totalCount,
areAllRowsOnPageSelected,
areAllRowsSelected,
selectAll,
}) => {
const [isSelectAllDropdownOpen, setSelectAllDropdownOpen] = useState(false);
const [selectionToggle, setSelectionToggle] = useState(false);

const canSelectAll = selectAll !== noop;
// Checkbox states: false = unchecked, null = partially-checked, true = checked
// Flow: All are selected -> click -> none are selected
// Some are selected -> click -> none are selected
// None are selected -> click -> page is selected
const onSelectAllCheckboxChange = checked => {
if (checked && selectionToggle !== null) {
if (!canSelectAll) {
selectPage();
} else {
selectAll(true);
}
} else {
selectNone();
}
};

const onSelectAllDropdownToggle = () =>
setSelectAllDropdownOpen(isOpen => !isOpen);

const handleSelectAll = () => {
setSelectAllDropdownOpen(false);
setSelectionToggle(true);
selectAll(true);
};
const handleSelectPage = () => {
setSelectAllDropdownOpen(false);
setSelectionToggle(true);
selectPage();
};
const handleSelectNone = () => {
setSelectAllDropdownOpen(false);
setSelectionToggle(false);
selectNone();
};

useEffect(() => {
let newCheckedState = null; // null is partially-checked state

if (areAllRowsSelected) {
newCheckedState = true;
} else if (selectedCount === 0) {
newCheckedState = false;
}
setSelectionToggle(newCheckedState);
}, [selectedCount, areAllRowsSelected]);

const selectAllDropdownItems = [
<DropdownItem
key="select-none"
ouiaId="select-none"
component="button"
isDisabled={selectedCount === 0}
onClick={handleSelectNone}
>
{`${__('Select none')} (0)`}
</DropdownItem>,
<DropdownItem
key="select-page"
ouiaId="select-page"
component="button"
isDisabled={pageRowCount === 0 || areAllRowsOnPageSelected}
onClick={handleSelectPage}
>
{`${__('Select page')} (${pageRowCount})`}
</DropdownItem>,
];
if (canSelectAll) {
selectAllDropdownItems.push(
<DropdownItem
key="select-all"
id="all"
ouiaId="select-all"
component="button"
isDisabled={totalCount === 0 || areAllRowsSelected}
onClick={handleSelectAll}
>
{`${__('Select all')} (${totalCount})`}
</DropdownItem>
);
}

return (
<Dropdown
toggle={
<DropdownToggle
onToggle={onSelectAllDropdownToggle}
id="select-all-checkbox-dropdown-toggle"
ouiaId="select-all-checkbox-dropdown-toggle"
splitButtonItems={[
<DropdownToggleCheckbox
className="tablewrapper-select-all-checkbox"
key="tablewrapper-select-all-checkbox"
ouiaId="select-all-checkbox-dropdown-toggle-checkbox"
aria-label="Select all"
onChange={checked => onSelectAllCheckboxChange(checked)}
isChecked={selectionToggle}
isDisabled={totalCount === 0 && selectedCount === 0}
>
{selectedCount > 0 && `${selectedCount} selected`}
</DropdownToggleCheckbox>,
]}
/>
}
isOpen={isSelectAllDropdownOpen}
dropdownItems={selectAllDropdownItems}
id="selection-checkbox"
ouiaId="selection-checkbox"
/>
);
};

SelectAllCheckbox.propTypes = {
selectedCount: PropTypes.number.isRequired,
selectNone: PropTypes.func.isRequired,
selectPage: PropTypes.func.isRequired,
selectAll: PropTypes.func,
pageRowCount: PropTypes.number,
totalCount: PropTypes.number,
areAllRowsOnPageSelected: PropTypes.bool.isRequired,
areAllRowsSelected: PropTypes.bool.isRequired,
};

SelectAllCheckbox.defaultProps = {
selectAll: noop,
pageRowCount: 0,
totalCount: 0,
};

export default SelectAllCheckbox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.tablewrapper-select-all-checkbox {
font-weight: normal;
}
Loading

0 comments on commit 4933bdb

Please sign in to comment.