mirror of
https://github.com/ansible/awx.git
synced 2026-01-14 11:20:39 -03:30
Refactors to show add button properly in Advanced Search mode
This commit is contained in:
parent
9620da287c
commit
f604065246
@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { arrayOf, func, object, string } from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Button, Tooltip } from '@patternfly/react-core';
|
||||
import { Button, Tooltip, DropdownItem } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
import { KebabifiedContext } from '../../contexts/Kebabified';
|
||||
|
||||
import AlertModal from '../AlertModal';
|
||||
|
||||
const ModalNote = styled.div`
|
||||
@ -19,12 +21,19 @@ function DisassociateButton({
|
||||
verifyCannotDisassociate = true,
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
|
||||
|
||||
function handleDisassociate() {
|
||||
onDisassociate();
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isKebabified) {
|
||||
onKebabModalChange(isOpen);
|
||||
}
|
||||
}, [isKebabified, isOpen, onKebabModalChange]);
|
||||
|
||||
function cannotDisassociate(item) {
|
||||
return !item.summary_fields?.user_capabilities?.delete;
|
||||
}
|
||||
@ -67,18 +76,29 @@ function DisassociateButton({
|
||||
// See: https://github.com/patternfly/patternfly-react/issues/1894
|
||||
return (
|
||||
<>
|
||||
<Tooltip content={renderTooltip()} position="top">
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-label={i18n._(t`Disassociate`)}
|
||||
onClick={() => setIsOpen(true)}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{i18n._(t`Disassociate`)}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{isKebabified ? (
|
||||
<DropdownItem
|
||||
key="add"
|
||||
isDisabled={isDisabled}
|
||||
component="button"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DropdownItem>
|
||||
) : (
|
||||
<Tooltip content={renderTooltip()} position="top">
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-label={i18n._(t`Disassociate`)}
|
||||
onClick={() => setIsOpen(true)}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{i18n._(t`Disassociate`)}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isOpen && (
|
||||
<AlertModal
|
||||
@ -107,11 +127,7 @@ function DisassociateButton({
|
||||
>
|
||||
{modalNote && <ModalNote>{modalNote}</ModalNote>}
|
||||
|
||||
<div>
|
||||
{i18n._(
|
||||
t`This action will disassociate the following and any of their descendents:`
|
||||
)}
|
||||
</div>
|
||||
<div>{i18n._(t`This action will disassociate the following:`)}</div>
|
||||
|
||||
{itemsToDisassociate.map(item => (
|
||||
<span key={item.id}>
|
||||
|
||||
@ -134,7 +134,7 @@ function InventoryGroup({ i18n, setBreadcrumb, inventory }) {
|
||||
key="relatedGroups"
|
||||
path="/inventories/inventory/:id/groups/:groupId/nested_groups"
|
||||
>
|
||||
<InventoryGroupsRelatedGroup inventoryGroup={inventoryGroup} />
|
||||
<InventoryGroupsRelatedGroup />
|
||||
</Route>,
|
||||
]}
|
||||
<Route key="not-found" path="*">
|
||||
|
||||
@ -166,7 +166,26 @@ function InventoryGroupHostList({ i18n }) {
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
const addFormUrl = `/inventories/inventory/${inventoryId}/groups/${groupId}/nested_hosts/add`;
|
||||
const addButtonOptions = [];
|
||||
|
||||
if (canAdd) {
|
||||
addButtonOptions.push(
|
||||
{
|
||||
onAdd: () => setIsModalOpen(true),
|
||||
title: i18n._(t`Add existing host`),
|
||||
label: i18n._(t`host`),
|
||||
key: 'existing',
|
||||
},
|
||||
{
|
||||
onAdd: () => history.push(addFormUrl),
|
||||
title: i18n._(t`Add new host`),
|
||||
label: i18n._(t`host`),
|
||||
key: 'new',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// const addButton = <AddDropdown key="add" dropdownItems={addButtonOptions} />;
|
||||
return (
|
||||
<>
|
||||
<PaginatedDataList
|
||||
|
||||
@ -190,11 +190,11 @@ describe('<InventoryGroupHostList />', () => {
|
||||
|
||||
test('should show associate host modal when adding an existing host', () => {
|
||||
const dropdownToggle = wrapper.find(
|
||||
'DropdownToggle button[aria-label="add host"]'
|
||||
'DropdownToggle button[aria-label="add"]'
|
||||
);
|
||||
dropdownToggle.simulate('click');
|
||||
wrapper
|
||||
.find('DropdownItem[aria-label="add existing host"]')
|
||||
.find('DropdownItem[aria-label="Add existing host"]')
|
||||
.simulate('click');
|
||||
expect(wrapper.find('AssociateModal').length).toBe(1);
|
||||
wrapper.find('ModalBoxCloseButton').simulate('click');
|
||||
@ -209,12 +209,10 @@ describe('<InventoryGroupHostList />', () => {
|
||||
results: [{ id: 123, name: 'foo', url: '/api/v2/hosts/123/' }],
|
||||
},
|
||||
});
|
||||
wrapper
|
||||
.find('DropdownToggle button[aria-label="add host"]')
|
||||
.simulate('click');
|
||||
wrapper.find('DropdownToggle button[aria-label="add"]').simulate('click');
|
||||
await act(async () => {
|
||||
wrapper
|
||||
.find('DropdownItem[aria-label="add existing host"]')
|
||||
.find('DropdownItem[aria-label="Add existing host"]')
|
||||
.simulate('click');
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
@ -241,12 +239,10 @@ describe('<InventoryGroupHostList />', () => {
|
||||
results: [{ id: 123, name: 'foo', url: '/api/v2/hosts/123/' }],
|
||||
},
|
||||
});
|
||||
wrapper
|
||||
.find('DropdownToggle button[aria-label="add host"]')
|
||||
.simulate('click');
|
||||
wrapper.find('DropdownToggle button[aria-label="add"]').simulate('click');
|
||||
await act(async () => {
|
||||
wrapper
|
||||
.find('DropdownItem[aria-label="add existing host"]')
|
||||
.find('DropdownItem[aria-label="Add existing host"]')
|
||||
.simulate('click');
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
@ -288,10 +284,10 @@ describe('<InventoryGroupHostList />', () => {
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
const dropdownToggle = wrapper.find(
|
||||
'DropdownToggle button[aria-label="add host"]'
|
||||
'DropdownToggle button[aria-label="add"]'
|
||||
);
|
||||
dropdownToggle.simulate('click');
|
||||
wrapper.find('DropdownItem[aria-label="add new host"]').simulate('click');
|
||||
wrapper.find('DropdownItem[aria-label="Add new host"]').simulate('click');
|
||||
expect(history.location.pathname).toEqual(
|
||||
'/inventories/inventory/1/groups/2/nested_hosts/add'
|
||||
);
|
||||
|
||||
@ -28,7 +28,7 @@ const QS_CONFIG = getQSConfig('group', {
|
||||
page_size: 20,
|
||||
order_by: 'name',
|
||||
});
|
||||
function InventoryRelatedGroupList({ i18n, inventoryGroup }) {
|
||||
function InventoryRelatedGroupList({ i18n }) {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const { id: inventoryId, groupId } = useParams();
|
||||
const location = useLocation();
|
||||
@ -92,6 +92,26 @@ function InventoryRelatedGroupList({ i18n, inventoryGroup }) {
|
||||
);
|
||||
|
||||
const addFormUrl = `/home`;
|
||||
const addButtonOptions = [];
|
||||
|
||||
if (canAdd) {
|
||||
addButtonOptions.push(
|
||||
{
|
||||
onAdd: () => setIsModalOpen(true),
|
||||
title: i18n._(t`Add existing group`),
|
||||
label: i18n._(t`group`),
|
||||
key: 'existing',
|
||||
},
|
||||
{
|
||||
onAdd: () => history.push(addFormUrl),
|
||||
title: i18n._(t`Add new group`),
|
||||
label: i18n._(t`group`),
|
||||
key: 'new',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const addButton = <AddDropdown key="add" dropdownItems={addButtonOptions} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -136,18 +156,7 @@ function InventoryRelatedGroupList({ i18n, inventoryGroup }) {
|
||||
}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
? [
|
||||
<AddDropdown
|
||||
key="associate"
|
||||
onAddExisting={() => setIsModalOpen(true)}
|
||||
onAddNew={() => history.push(addFormUrl)}
|
||||
newTitle={i18n._(t`Add new group`)}
|
||||
existingTitle={i18n._(t`Add existing group`)}
|
||||
label={i18n._(t`group`)}
|
||||
/>,
|
||||
]
|
||||
: []),
|
||||
...(canAdd ? [addButton] : []),
|
||||
<Kebabified>
|
||||
{({ isKebabified }) =>
|
||||
isKebabified ? (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { func, string } from 'prop-types';
|
||||
import React, { useState, useRef, useEffect, Fragment } from 'react';
|
||||
import { func, string, arrayOf, shape } from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
@ -8,61 +8,81 @@ import {
|
||||
DropdownPosition,
|
||||
DropdownToggle,
|
||||
} from '@patternfly/react-core';
|
||||
import { useKebabifiedMenu } from '../../../contexts/Kebabified';
|
||||
|
||||
function AddDropdown({
|
||||
i18n,
|
||||
onAddNew,
|
||||
onAddExisting,
|
||||
newTitle,
|
||||
existingTitle,
|
||||
label,
|
||||
}) {
|
||||
function AddDropdown({ dropdownItems, i18n }) {
|
||||
const { isKebabified } = useKebabifiedMenu();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const element = useRef(null);
|
||||
|
||||
const dropdownItems = [
|
||||
<DropdownItem
|
||||
key="add-new"
|
||||
aria-label={`add new ${label}`}
|
||||
component="button"
|
||||
onClick={onAddNew}
|
||||
>
|
||||
{newTitle}
|
||||
</DropdownItem>,
|
||||
<DropdownItem
|
||||
key="add-existing"
|
||||
aria-label={`add existing ${label}`}
|
||||
component="button"
|
||||
onClick={onAddExisting}
|
||||
>
|
||||
{existingTitle}
|
||||
</DropdownItem>,
|
||||
];
|
||||
useEffect(() => {
|
||||
const toggle = e => {
|
||||
if (!isKebabified && (!element || !element.current.contains(e.target))) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', toggle, false);
|
||||
return () => {
|
||||
document.removeEventListener('click', toggle);
|
||||
};
|
||||
}, [isKebabified]);
|
||||
|
||||
if (isKebabified) {
|
||||
return (
|
||||
<Fragment>
|
||||
{dropdownItems.map(item => (
|
||||
<DropdownItem
|
||||
key={item.key}
|
||||
aria-label={item.title}
|
||||
onClick={item.onAdd}
|
||||
>
|
||||
{item.title}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
position={DropdownPosition.right}
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
id={`add-${label}-dropdown`}
|
||||
aria-label={`add ${label}`}
|
||||
isPrimary
|
||||
onToggle={() => setIsOpen(prevState => !prevState)}
|
||||
>
|
||||
{i18n._(t`Add`)}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={dropdownItems}
|
||||
/>
|
||||
<div ref={element} key="add">
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
position={DropdownPosition.right}
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
id="add"
|
||||
aria-label="add"
|
||||
isPrimary
|
||||
onToggle={() => setIsOpen(prevState => !prevState)}
|
||||
>
|
||||
{i18n._(t`Add`)}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={dropdownItems.map(item => (
|
||||
<DropdownItem
|
||||
className="pf-c-dropdown__menu-item"
|
||||
key={item.key}
|
||||
aria-label={item.title}
|
||||
onClick={item.onAdd}
|
||||
>
|
||||
{item.title}
|
||||
</DropdownItem>
|
||||
))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AddDropdown.propTypes = {
|
||||
onAddNew: func.isRequired,
|
||||
onAddExisting: func.isRequired,
|
||||
newTitle: string.isRequired,
|
||||
existingTitle: string.isRequired,
|
||||
label: string.isRequired,
|
||||
dropdownItems: arrayOf(
|
||||
shape({
|
||||
label: string.isRequired,
|
||||
onAdd: func.isRequired,
|
||||
key: string.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
export { AddDropdown as _AddDropdown };
|
||||
export default withI18n()(AddDropdown);
|
||||
|
||||
@ -5,13 +5,23 @@ import AddDropdown from './AddDropdown';
|
||||
describe('<AddDropdown />', () => {
|
||||
let wrapper;
|
||||
let dropdownToggle;
|
||||
const onAddNew = jest.fn();
|
||||
const onAddExisting = jest.fn();
|
||||
const dropdownItems = [
|
||||
{
|
||||
onAdd: () => {},
|
||||
title: 'Add existing group',
|
||||
label: 'group',
|
||||
key: 'existing',
|
||||
},
|
||||
{
|
||||
onAdd: () => {},
|
||||
title: 'Add new group',
|
||||
label: 'group',
|
||||
key: 'new',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mountWithContexts(
|
||||
<AddDropdown onAddNew={onAddNew} onAddExisting={onAddExisting} />
|
||||
);
|
||||
wrapper = mountWithContexts(<AddDropdown dropdownItems={dropdownItems} />);
|
||||
dropdownToggle = wrapper.find('DropdownToggle button');
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user