mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 18:40:01 -03:30
Improves naming and updates resource list and adds search functionality
This commit is contained in:
parent
4f6d7e56eb
commit
585ca082e3
@ -258,7 +258,7 @@ class AddResourceRole extends React.Component {
|
||||
sortColumns={userSortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={readUsers}
|
||||
fetchItems={readUsers}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
sortedColumnKey="username"
|
||||
@ -269,7 +269,7 @@ class AddResourceRole extends React.Component {
|
||||
searchColumns={teamSearchColumns}
|
||||
sortColumns={teamSortColumns}
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={readTeams}
|
||||
fetchItems={readTeams}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
/>
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withRouter, useLocation } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import useRequest from '../../util/useRequest';
|
||||
|
||||
import { SearchColumns, SortColumns } from '../../types';
|
||||
import PaginatedDataList from '../PaginatedDataList';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
@ -10,124 +12,94 @@ import CheckboxListItem from '../CheckboxListItem';
|
||||
import SelectedList from '../SelectedList';
|
||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||
|
||||
class SelectResourceStep extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const QS_Config = sortColumns => {
|
||||
return getQSConfig('resource', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: `${
|
||||
sortColumns.filter(col => col.key === 'name').length ? 'name' : 'username'
|
||||
}`,
|
||||
});
|
||||
};
|
||||
function SelectResourceStep({
|
||||
searchColumns,
|
||||
sortColumns,
|
||||
displayKey,
|
||||
onRowClick,
|
||||
selectedLabel,
|
||||
selectedResourceRows,
|
||||
fetchItems,
|
||||
i18n,
|
||||
}) {
|
||||
const location = useLocation();
|
||||
|
||||
this.state = {
|
||||
isInitialized: false,
|
||||
count: null,
|
||||
error: false,
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
request: readResourceList,
|
||||
result: { resources, itemCount },
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const queryParams = parseQueryString(
|
||||
QS_Config(sortColumns),
|
||||
location.search
|
||||
);
|
||||
|
||||
const {
|
||||
data: { count, results },
|
||||
} = await fetchItems(queryParams);
|
||||
return { resources: results, itemCount: count };
|
||||
}, [location, fetchItems, sortColumns]),
|
||||
{
|
||||
resources: [],
|
||||
};
|
||||
|
||||
this.qsConfig = getQSConfig('resource', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: `${
|
||||
props.sortColumns.filter(col => col.key === 'name').length
|
||||
? 'name'
|
||||
: 'username'
|
||||
}`,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.readResourceList();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
this.readResourceList();
|
||||
itemCount: 0,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async readResourceList() {
|
||||
const { onSearch, location } = this.props;
|
||||
const queryParams = parseQueryString(this.qsConfig, location.search);
|
||||
useEffect(() => {
|
||||
readResourceList();
|
||||
}, [readResourceList]);
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: false,
|
||||
});
|
||||
try {
|
||||
const { data } = await onSearch(queryParams);
|
||||
const { count, results } = data;
|
||||
|
||||
this.setState({
|
||||
resources: results,
|
||||
count,
|
||||
isInitialized: true,
|
||||
isLoading: false,
|
||||
error: false,
|
||||
});
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isInitialized, isLoading, count, error, resources } = this.state;
|
||||
|
||||
const {
|
||||
searchColumns,
|
||||
sortColumns,
|
||||
displayKey,
|
||||
onRowClick,
|
||||
selectedLabel,
|
||||
selectedResourceRows,
|
||||
i18n,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{isInitialized && (
|
||||
<Fragment>
|
||||
<div>
|
||||
{i18n._(
|
||||
t`Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step.`
|
||||
)}
|
||||
</div>
|
||||
{selectedResourceRows.length > 0 && (
|
||||
<SelectedList
|
||||
displayKey={displayKey}
|
||||
label={selectedLabel}
|
||||
onRemove={onRowClick}
|
||||
selected={selectedResourceRows}
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
hasContentLoading={isLoading}
|
||||
items={resources}
|
||||
itemCount={count}
|
||||
qsConfig={this.qsConfig}
|
||||
onRowClick={onRowClick}
|
||||
toolbarSearchColumns={searchColumns}
|
||||
toolbarSortColumns={sortColumns}
|
||||
renderItem={item => (
|
||||
<CheckboxListItem
|
||||
isSelected={selectedResourceRows.some(i => i.id === item.id)}
|
||||
itemId={item.id}
|
||||
key={item.id}
|
||||
name={item[displayKey]}
|
||||
label={item[displayKey]}
|
||||
onSelect={() => onRowClick(item)}
|
||||
onDeselect={() => onRowClick(item)}
|
||||
/>
|
||||
)}
|
||||
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||
showPageSizeOptions={false}
|
||||
/>
|
||||
</Fragment>
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
{i18n._(
|
||||
t`Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step.`
|
||||
)}
|
||||
{error ? <div>error</div> : ''}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
{selectedResourceRows.length > 0 && (
|
||||
<SelectedList
|
||||
displayKey={displayKey}
|
||||
label={selectedLabel}
|
||||
onRemove={onRowClick}
|
||||
selected={selectedResourceRows}
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
hasContentLoading={isLoading}
|
||||
contentError={error}
|
||||
items={resources}
|
||||
itemCount={itemCount}
|
||||
qsConfig={QS_Config(sortColumns)}
|
||||
onRowClick={onRowClick}
|
||||
toolbarSearchColumns={searchColumns}
|
||||
toolbarSortColumns={sortColumns}
|
||||
renderItem={item => (
|
||||
<CheckboxListItem
|
||||
isSelected={selectedResourceRows.some(i => i.id === item.id)}
|
||||
itemId={item.id}
|
||||
key={item.id}
|
||||
name={item[displayKey]}
|
||||
label={item[displayKey]}
|
||||
onSelect={() => onRowClick(item)}
|
||||
onDeselect={() => onRowClick(item)}
|
||||
/>
|
||||
)}
|
||||
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||
showPageSizeOptions={false}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
SelectResourceStep.propTypes = {
|
||||
@ -135,7 +107,7 @@ SelectResourceStep.propTypes = {
|
||||
sortColumns: SortColumns,
|
||||
displayKey: PropTypes.string,
|
||||
onRowClick: PropTypes.func,
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
fetchItems: PropTypes.func.isRequired,
|
||||
selectedLabel: PropTypes.string,
|
||||
selectedResourceRows: PropTypes.arrayOf(PropTypes.object),
|
||||
};
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import React from 'react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
} from '../../../testUtils/enzymeHelpers';
|
||||
import { sleep } from '../../../testUtils/testUtils';
|
||||
import SelectResourceStep from './SelectResourceStep';
|
||||
|
||||
@ -30,12 +34,12 @@ describe('<SelectResourceStep />', () => {
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={() => {}}
|
||||
fetchItems={() => {}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
test('fetches resources on mount', async () => {
|
||||
test('fetches resources on mount and adds items to list', async () => {
|
||||
const handleSearch = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
count: 2,
|
||||
@ -45,61 +49,24 @@ describe('<SelectResourceStep />', () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
mountWithContexts(
|
||||
<SelectResourceStep
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
);
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
fetchItems={handleSearch}
|
||||
/>
|
||||
);
|
||||
});
|
||||
expect(handleSearch).toHaveBeenCalledWith({
|
||||
order_by: 'username',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
|
||||
test('readResourceList properly adds rows to state', async () => {
|
||||
const selectedResourceRows = [{ id: 1, username: 'foo', url: 'item/1' }];
|
||||
const handleSearch = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
count: 2,
|
||||
results: [
|
||||
{ id: 1, username: 'foo', url: 'item/1' },
|
||||
{ id: 2, username: 'bar', url: 'item/2' },
|
||||
],
|
||||
},
|
||||
});
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [
|
||||
'/organizations/1/access?resource.page=1&resource.order_by=-username',
|
||||
],
|
||||
});
|
||||
const wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={handleSearch}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
/>,
|
||||
{
|
||||
context: { router: { history, route: { location: history.location } } },
|
||||
}
|
||||
).find('SelectResourceStep');
|
||||
await wrapper.instance().readResourceList();
|
||||
expect(handleSearch).toHaveBeenCalledWith({
|
||||
order_by: '-username',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
expect(wrapper.state('resources')).toEqual([
|
||||
{ id: 1, username: 'foo', url: 'item/1' },
|
||||
{ id: 2, username: 'bar', url: 'item/2' },
|
||||
]);
|
||||
waitForElement(wrapper, 'CheckBoxListItem', el => el.length === 2);
|
||||
});
|
||||
|
||||
test('clicking on row fires callback with correct params', async () => {
|
||||
@ -111,20 +78,24 @@ describe('<SelectResourceStep />', () => {
|
||||
{ id: 2, username: 'bar', url: 'item/2' },
|
||||
],
|
||||
};
|
||||
const wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={handleRowClick}
|
||||
onSearch={() => ({ data })}
|
||||
selectedResourceRows={[]}
|
||||
/>
|
||||
);
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
displayKey="username"
|
||||
onRowClick={handleRowClick}
|
||||
fetchItems={() => ({ data })}
|
||||
selectedResourceRows={[]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
const checkboxListItemWrapper = wrapper.find('CheckboxListItem');
|
||||
expect(checkboxListItemWrapper.length).toBe(2);
|
||||
|
||||
checkboxListItemWrapper
|
||||
.first()
|
||||
.find('input[type="checkbox"]')
|
||||
|
||||
@ -11,7 +11,7 @@ import Wizard from '../Wizard/Wizard';
|
||||
import useSelected from '../../util/useSelected';
|
||||
import SelectResourceStep from '../AddRole/SelectResourceStep';
|
||||
import SelectRoleStep from '../AddRole/SelectRoleStep';
|
||||
import getResourceTypes from './resources.data';
|
||||
import getResourceAccessConfig from './getResourceAccessConfig';
|
||||
|
||||
const Grid = styled.div`
|
||||
display: grid;
|
||||
@ -28,7 +28,7 @@ function UserAndTeamAccessAdd({
|
||||
apiModel,
|
||||
onClose,
|
||||
}) {
|
||||
const [selectedResourceType, setSelectedResourceType] = useState();
|
||||
const [selectedResourceType, setSelectedResourceType] = useState(null);
|
||||
const [stepIdReached, setStepIdReached] = useState(1);
|
||||
const { id: userId } = useParams();
|
||||
const {
|
||||
@ -70,7 +70,7 @@ function UserAndTeamAccessAdd({
|
||||
name: i18n._(t`Add resource type`),
|
||||
component: (
|
||||
<Grid>
|
||||
{getResourceTypes(i18n).map(resource => (
|
||||
{getResourceAccessConfig(i18n).map(resource => (
|
||||
<SelectableCard
|
||||
key={resource.selectedResource}
|
||||
isSelected={
|
||||
@ -84,6 +84,7 @@ function UserAndTeamAccessAdd({
|
||||
))}
|
||||
</Grid>
|
||||
),
|
||||
enableNext: selectedResourceType !== null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -94,7 +95,7 @@ function UserAndTeamAccessAdd({
|
||||
sortColumns={selectedResourceType.sortColumns}
|
||||
displayKey="name"
|
||||
onRowClick={handleResourceSelect}
|
||||
onSearch={selectedResourceType.fetchItems}
|
||||
fetchItems={selectedResourceType.fetchItems}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={resourcesSelected}
|
||||
sortedColumnKey="username"
|
||||
@ -65,9 +65,10 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
expect(wrapper.find('PFWizard').length).toBe(1);
|
||||
});
|
||||
test('should disable steps', async () => {
|
||||
expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true);
|
||||
expect(
|
||||
wrapper
|
||||
.find('WizardNavItem[text="Select ttems from list"]')
|
||||
.find('WizardNavItem[text="Select items from list"]')
|
||||
.prop('isDisabled')
|
||||
).toBe(true);
|
||||
expect(
|
||||
@ -93,7 +94,7 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
).toBe(false);
|
||||
expect(
|
||||
wrapper
|
||||
.find('WizardNavItem[text="Select ttems from list"]')
|
||||
.find('WizardNavItem[text="Select items from list"]')
|
||||
.prop('isDisabled')
|
||||
).toBe(false);
|
||||
expect(
|
||||
@ -119,6 +120,12 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
||||
await act(async () =>
|
||||
wrapper.find('Button[type="submit"]').prop('onClick')()
|
||||
);
|
||||
expect(JobTemplatesAPI.read).toHaveBeenCalledWith({
|
||||
order_by: 'name',
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
|
||||
await waitForElement(wrapper, 'SelectResourceStep', el => el.length > 0);
|
||||
expect(JobTemplatesAPI.read).toHaveBeenCalled();
|
||||
await act(async () =>
|
||||
@ -8,7 +8,7 @@ import {
|
||||
OrganizationsAPI,
|
||||
} from '../../api';
|
||||
|
||||
export default function getResourceTypes(i18n) {
|
||||
export default function getResourceAccessConfig(i18n) {
|
||||
return [
|
||||
{
|
||||
selectedResource: 'jobTemplate',
|
||||
@ -38,7 +38,7 @@ export default function getResourceTypes(i18n) {
|
||||
key: 'name',
|
||||
},
|
||||
],
|
||||
fetchItems: () => JobTemplatesAPI.read(),
|
||||
fetchItems: queryParams => JobTemplatesAPI.read(queryParams),
|
||||
},
|
||||
{
|
||||
selectedResource: 'workflowJobTemplate',
|
||||
@ -68,7 +68,7 @@ export default function getResourceTypes(i18n) {
|
||||
key: 'name',
|
||||
},
|
||||
],
|
||||
fetchItems: () => WorkflowJobTemplatesAPI.read(),
|
||||
fetchItems: queryParams => WorkflowJobTemplatesAPI.read(queryParams),
|
||||
},
|
||||
{
|
||||
selectedResource: 'credential',
|
||||
@ -109,7 +109,7 @@ export default function getResourceTypes(i18n) {
|
||||
key: 'name',
|
||||
},
|
||||
],
|
||||
fetchItems: () => CredentialsAPI.read(),
|
||||
fetchItems: queryParams => CredentialsAPI.read(queryParams),
|
||||
},
|
||||
{
|
||||
selectedResource: 'inventory',
|
||||
@ -135,7 +135,7 @@ export default function getResourceTypes(i18n) {
|
||||
key: 'name',
|
||||
},
|
||||
],
|
||||
fetchItems: () => InventoriesAPI.read(),
|
||||
fetchItems: queryParams => InventoriesAPI.read(queryParams),
|
||||
},
|
||||
{
|
||||
selectedResource: 'project',
|
||||
@ -176,7 +176,7 @@ export default function getResourceTypes(i18n) {
|
||||
key: 'name',
|
||||
},
|
||||
],
|
||||
fetchItems: () => ProjectsAPI.read(),
|
||||
fetchItems: queryParams => ProjectsAPI.read(queryParams),
|
||||
},
|
||||
{
|
||||
selectedResource: 'organization',
|
||||
@ -202,7 +202,7 @@ export default function getResourceTypes(i18n) {
|
||||
key: 'name',
|
||||
},
|
||||
],
|
||||
fetchItems: () => OrganizationsAPI.read(),
|
||||
fetchItems: queryParams => OrganizationsAPI.read(queryParams),
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -12,7 +12,7 @@ import DataListToolbar from '../../../components/DataListToolbar';
|
||||
import PaginatedDataList from '../../../components/PaginatedDataList';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
import TeamAccessListItem from './TeamAccessListItem';
|
||||
import UserAndTeamAccessAdd from '../../../components/UserAccessAdd/UserAndTeamAccessAdd';
|
||||
import UserAndTeamAccessAdd from '../../../components/UserAndTeamAccessAdd/UserAndTeamAccessAdd';
|
||||
|
||||
const QS_CONFIG = getQSConfig('team', {
|
||||
page: 1,
|
||||
|
||||
@ -10,7 +10,7 @@ import useRequest from '../../../util/useRequest';
|
||||
import PaginatedDataList from '../../../components/PaginatedDataList';
|
||||
import DatalistToolbar from '../../../components/DataListToolbar';
|
||||
import UserAccessListItem from './UserAccessListItem';
|
||||
import UserAndTeamAccessAdd from '../../../components/UserAccessAdd/UserAndTeamAccessAdd';
|
||||
import UserAndTeamAccessAdd from '../../../components/UserAndTeamAccessAdd/UserAndTeamAccessAdd';
|
||||
|
||||
const QS_CONFIG = getQSConfig('roles', {
|
||||
page: 1,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user