mirror of
https://github.com/ansible/awx.git
synced 2026-01-10 15:32:07 -03:30
Adds smart inventory button on host list
This commit is contained in:
parent
684998cd51
commit
87604749b7
@ -1,8 +1,8 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useLocation, useRouteMatch } from 'react-router-dom';
|
||||
import { useHistory, useLocation, useRouteMatch } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Card, PageSection } from '@patternfly/react-core';
|
||||
import { Button, Card, PageSection, Tooltip } from '@patternfly/react-core';
|
||||
|
||||
import { HostsAPI } from '../../../api';
|
||||
import AlertModal from '../../../components/AlertModal';
|
||||
@ -13,7 +13,11 @@ import PaginatedDataList, {
|
||||
ToolbarDeleteButton,
|
||||
} from '../../../components/PaginatedDataList';
|
||||
import useRequest, { useDeleteItems } from '../../../util/useRequest';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
import {
|
||||
encodeQueryString,
|
||||
getQSConfig,
|
||||
parseQueryString,
|
||||
} from '../../../util/qs';
|
||||
|
||||
import HostListItem from './HostListItem';
|
||||
|
||||
@ -24,9 +28,21 @@ const QS_CONFIG = getQSConfig('host', {
|
||||
});
|
||||
|
||||
function HostList({ i18n }) {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
const [selected, setSelected] = useState([]);
|
||||
const parsedQueryStrings = parseQueryString(QS_CONFIG, location.search);
|
||||
const nonDefaultSearchParams = {};
|
||||
|
||||
Object.keys(parsedQueryStrings).forEach(key => {
|
||||
if (!QS_CONFIG.defaultParams[key]) {
|
||||
nonDefaultSearchParams[key] = parsedQueryStrings[key];
|
||||
}
|
||||
});
|
||||
|
||||
const hasNonDefaultSearchParams =
|
||||
Object.keys(nonDefaultSearchParams).length > 0;
|
||||
|
||||
const {
|
||||
result: { hosts, count, actions, relatedSearchableKeys, searchableKeys },
|
||||
@ -99,6 +115,14 @@ function HostList({ i18n }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSmartInventoryClick = () => {
|
||||
history.push(
|
||||
`/inventories/smart_inventory/add?host_filter=${encodeURIComponent(
|
||||
encodeQueryString(nonDefaultSearchParams)
|
||||
)}`
|
||||
);
|
||||
};
|
||||
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
|
||||
@ -157,6 +181,34 @@ function HostList({ i18n }) {
|
||||
itemsToDelete={selected}
|
||||
pluralizedItemName={i18n._(t`Hosts`)}
|
||||
/>,
|
||||
...(canAdd
|
||||
? [
|
||||
<Tooltip
|
||||
key="smartInventory"
|
||||
content={
|
||||
hasNonDefaultSearchParams
|
||||
? i18n._(
|
||||
t`Create a new Smart Inventory with the applied filter`
|
||||
)
|
||||
: i18n._(
|
||||
t`Enter at least one search filter to create a new Smart Inventory`
|
||||
)
|
||||
}
|
||||
position="top"
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => handleSmartInventoryClick()}
|
||||
aria-label={i18n._(t`Smart Inventory`)}
|
||||
variant="secondary"
|
||||
isDisabled={!hasNonDefaultSearchParams}
|
||||
>
|
||||
{i18n._(t`Smart Inventory`)}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>,
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { HostsAPI } from '../../../api';
|
||||
import {
|
||||
mountWithContexts,
|
||||
@ -257,7 +258,7 @@ describe('<HostList />', () => {
|
||||
expect(modal.prop('title')).toEqual('Error!');
|
||||
});
|
||||
|
||||
test('should show Add button according to permissions', async () => {
|
||||
test('should show Add and Smart Inventory buttons according to permissions', async () => {
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<HostList />);
|
||||
@ -265,9 +266,10 @@ describe('<HostList />', () => {
|
||||
await waitForLoaded(wrapper);
|
||||
|
||||
expect(wrapper.find('ToolbarAddButton').length).toBe(1);
|
||||
expect(wrapper.find('Button[aria-label="Smart Inventory"]').length).toBe(1);
|
||||
});
|
||||
|
||||
test('should hide Add button according to permissions', async () => {
|
||||
test('should hide Add and Smart Inventory buttons according to permissions', async () => {
|
||||
HostsAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
actions: {
|
||||
@ -282,5 +284,44 @@ describe('<HostList />', () => {
|
||||
await waitForLoaded(wrapper);
|
||||
|
||||
expect(wrapper.find('ToolbarAddButton').length).toBe(0);
|
||||
expect(wrapper.find('Button[aria-label="Smart Inventory"]').length).toBe(0);
|
||||
});
|
||||
|
||||
test('Smart Inventory button should be disabled when no search params are present', async () => {
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<HostList />);
|
||||
});
|
||||
await waitForLoaded(wrapper);
|
||||
expect(
|
||||
wrapper.find('Button[aria-label="Smart Inventory"]').props().isDisabled
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('Clicking Smart Inventory button should navigate to smart inventory form with correct query param', async () => {
|
||||
let wrapper;
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/hosts?host.name__icontains=foo'],
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<HostList />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
|
||||
await waitForLoaded(wrapper);
|
||||
expect(
|
||||
wrapper.find('Button[aria-label="Smart Inventory"]').props().isDisabled
|
||||
).toBe(false);
|
||||
await act(async () => {
|
||||
wrapper.find('Button[aria-label="Smart Inventory"]').simulate('click');
|
||||
});
|
||||
wrapper.update();
|
||||
expect(history.location.pathname).toEqual(
|
||||
'/inventories/smart_inventory/add'
|
||||
);
|
||||
expect(history.location.search).toEqual(
|
||||
'?host_filter=name__icontains%3Dfoo'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -2,7 +2,8 @@ import React, { useEffect, useCallback } from 'react';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { func, shape, arrayOf } from 'prop-types';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { func, shape, object, arrayOf } from 'prop-types';
|
||||
import { Form } from '@patternfly/react-core';
|
||||
import { InstanceGroup } from '../../../types';
|
||||
import { VariablesField } from '../../../components/CodeMirrorInput';
|
||||
@ -14,6 +15,10 @@ import {
|
||||
FormColumnLayout,
|
||||
FormFullWidthLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
import {
|
||||
toHostFilter,
|
||||
toSearchParams,
|
||||
} from '../../../components/Lookup/shared/HostFilterUtils';
|
||||
import HostFilterLookup from '../../../components/Lookup/HostFilterLookup';
|
||||
import InstanceGroupsLookup from '../../../components/Lookup/InstanceGroupsLookup';
|
||||
import OrganizationLookup from '../../../components/Lookup/OrganizationLookup';
|
||||
@ -109,9 +114,17 @@ function SmartInventoryForm({
|
||||
onCancel,
|
||||
submitError,
|
||||
}) {
|
||||
const { search } = useLocation();
|
||||
const queryParams = new URLSearchParams(search);
|
||||
const hostFilterFromParams = queryParams.get('host_filter');
|
||||
|
||||
const initialValues = {
|
||||
description: inventory.description || '',
|
||||
host_filter: inventory.host_filter || '',
|
||||
host_filter:
|
||||
inventory.host_filter ||
|
||||
(hostFilterFromParams
|
||||
? toHostFilter(toSearchParams(hostFilterFromParams))
|
||||
: ''),
|
||||
instance_groups: instanceGroups || [],
|
||||
kind: 'smart',
|
||||
name: inventory.name || '',
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
@ -135,6 +136,29 @@ describe('<SmartInventoryForm />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should pre-fill the host filter when query param present and not editing', async () => {
|
||||
let wrapper;
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [
|
||||
'/inventories/smart_inventory/add?host_filter=name__icontains%3Dfoo',
|
||||
],
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<SmartInventoryForm onCancel={() => {}} onSubmit={() => {}} />,
|
||||
{
|
||||
context: { router: { history } },
|
||||
}
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
const nameChipGroup = wrapper.find(
|
||||
'HostFilterLookup ChipGroup[categoryName="Name"]'
|
||||
);
|
||||
expect(nameChipGroup.find('Chip').length).toBe(1);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should throw content error when option request fails', async () => {
|
||||
let wrapper;
|
||||
InventoriesAPI.readOptions.mockImplementationOnce(() =>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user