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