mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 17:37:37 -02:30
Merge pull request #5912 from keithjgrant/4239-pagination-on-delete
Adjust pagination when deleting final page of items Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation, useHistory } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { CredentialsAPI } from '@api';
|
import { CredentialsAPI } from '@api';
|
||||||
@@ -11,7 +11,12 @@ import PaginatedDataList, {
|
|||||||
ToolbarAddButton,
|
ToolbarAddButton,
|
||||||
ToolbarDeleteButton,
|
ToolbarDeleteButton,
|
||||||
} from '@components/PaginatedDataList';
|
} from '@components/PaginatedDataList';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import {
|
||||||
|
getQSConfig,
|
||||||
|
parseQueryString,
|
||||||
|
replaceParams,
|
||||||
|
encodeNonDefaultQueryString,
|
||||||
|
} from '@util/qs';
|
||||||
import { CredentialListItem } from '.';
|
import { CredentialListItem } from '.';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('credential', {
|
const QS_CONFIG = getQSConfig('credential', {
|
||||||
@@ -30,6 +35,7 @@ function CredentialList({ i18n }) {
|
|||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const loadCredentials = async ({ search }) => {
|
const loadCredentials = async ({ search }) => {
|
||||||
const params = parseQueryString(QS_CONFIG, search);
|
const params = parseQueryString(QS_CONFIG, search);
|
||||||
@@ -92,20 +98,21 @@ function CredentialList({ i18n }) {
|
|||||||
setDeletionError(error);
|
setDeletionError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adjustPagination();
|
||||||
|
setSelected([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const adjustPagination = () => {
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
try {
|
if (params.page > 1 && selected.length === credentials.length) {
|
||||||
const {
|
const newParams = encodeNonDefaultQueryString(
|
||||||
data: { count, results },
|
QS_CONFIG,
|
||||||
} = await CredentialsAPI.read(params);
|
replaceParams(params, { page: params.page - 1 })
|
||||||
|
);
|
||||||
setCredentials(results);
|
history.push(`${location.pathname}?${newParams}`);
|
||||||
setCredentialCount(count);
|
} else {
|
||||||
setSelected([]);
|
loadCredentials(location);
|
||||||
} catch (error) {
|
|
||||||
setContentError(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setHasContentLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const canAdd =
|
const canAdd =
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useLocation, useRouteMatch } from 'react-router-dom';
|
import { useLocation, useHistory, useRouteMatch } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
@@ -13,7 +13,12 @@ import PaginatedDataList, {
|
|||||||
ToolbarAddButton,
|
ToolbarAddButton,
|
||||||
ToolbarDeleteButton,
|
ToolbarDeleteButton,
|
||||||
} from '@components/PaginatedDataList';
|
} from '@components/PaginatedDataList';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import {
|
||||||
|
getQSConfig,
|
||||||
|
parseQueryString,
|
||||||
|
replaceParams,
|
||||||
|
encodeNonDefaultQueryString,
|
||||||
|
} from '@util/qs';
|
||||||
|
|
||||||
import OrganizationListItem from './OrganizationListItem';
|
import OrganizationListItem from './OrganizationListItem';
|
||||||
|
|
||||||
@@ -25,6 +30,7 @@ const QS_CONFIG = getQSConfig('organization', {
|
|||||||
|
|
||||||
function OrganizationsList({ i18n }) {
|
function OrganizationsList({ i18n }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
@@ -81,7 +87,21 @@ function OrganizationsList({ i18n }) {
|
|||||||
|
|
||||||
const handleOrgDelete = async () => {
|
const handleOrgDelete = async () => {
|
||||||
await deleteOrganizations();
|
await deleteOrganizations();
|
||||||
await fetchOrganizations();
|
await adjustPagination();
|
||||||
|
setSelected([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const adjustPagination = () => {
|
||||||
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
if (params.page > 1 && selected.length === organizations.length) {
|
||||||
|
const newParams = encodeNonDefaultQueryString(
|
||||||
|
QS_CONFIG,
|
||||||
|
replaceParams(params, { page: params.page - 1 })
|
||||||
|
);
|
||||||
|
history.push(`${location.pathname}?${newParams}`);
|
||||||
|
} else {
|
||||||
|
fetchOrganizations();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasContentLoading = isDeleteLoading || isOrgsLoading;
|
const hasContentLoading = isDeleteLoading || isOrgsLoading;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const QS_CONFIG = getQSConfig('template', {
|
|||||||
type: 'job_template,workflow_job_template',
|
type: 'job_template,workflow_job_template',
|
||||||
});
|
});
|
||||||
|
|
||||||
function TemplatesList({ i18n }) {
|
function TemplateList({ i18n }) {
|
||||||
const { id: projectId } = useParams();
|
const { id: projectId } = useParams();
|
||||||
const { pathname, search } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
|
|
||||||
@@ -268,5 +268,4 @@ function TemplatesList({ i18n }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TemplatesList as _TemplatesList };
|
export default withI18n()(TemplateList);
|
||||||
export default withI18n()(TemplatesList);
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@api';
|
} from '@api';
|
||||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
|
||||||
import TemplatesList from './TemplateList';
|
import TemplateList from './TemplateList';
|
||||||
|
|
||||||
jest.mock('@api');
|
jest.mock('@api');
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ const mockTemplates = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('<TemplatesList />', () => {
|
describe('<TemplateList />', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UnifiedJobTemplatesAPI.read.mockResolvedValue({
|
UnifiedJobTemplatesAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
@@ -94,7 +94,7 @@ describe('<TemplatesList />', () => {
|
|||||||
test('initially renders successfully', async () => {
|
test('initially renders successfully', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
mountWithContexts(
|
mountWithContexts(
|
||||||
<TemplatesList
|
<TemplateList
|
||||||
match={{ path: '/templates', url: '/templates' }}
|
match={{ path: '/templates', url: '/templates' }}
|
||||||
location={{ search: '', pathname: '/templates' }}
|
location={{ search: '', pathname: '/templates' }}
|
||||||
/>
|
/>
|
||||||
@@ -105,7 +105,7 @@ describe('<TemplatesList />', () => {
|
|||||||
test('Templates are retrieved from the api and the components finishes loading', async () => {
|
test('Templates are retrieved from the api and the components finishes loading', async () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<TemplatesList />);
|
wrapper = mountWithContexts(<TemplateList />);
|
||||||
});
|
});
|
||||||
expect(UnifiedJobTemplatesAPI.read).toBeCalled();
|
expect(UnifiedJobTemplatesAPI.read).toBeCalled();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
@@ -115,7 +115,7 @@ describe('<TemplatesList />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSelect is called when a template list item is selected', async () => {
|
test('handleSelect is called when a template list item is selected', async () => {
|
||||||
const wrapper = mountWithContexts(<TemplatesList />);
|
const wrapper = mountWithContexts(<TemplateList />);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
@@ -143,7 +143,7 @@ describe('<TemplatesList />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSelectAll is called when a template list item is selected', async () => {
|
test('handleSelectAll is called when a template list item is selected', async () => {
|
||||||
const wrapper = mountWithContexts(<TemplatesList />);
|
const wrapper = mountWithContexts(<TemplateList />);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
@@ -158,7 +158,7 @@ describe('<TemplatesList />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('delete button is disabled if user does not have delete capabilities on a selected template', async () => {
|
test('delete button is disabled if user does not have delete capabilities on a selected template', async () => {
|
||||||
const wrapper = mountWithContexts(<TemplatesList />);
|
const wrapper = mountWithContexts(<TemplateList />);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
@@ -217,7 +217,7 @@ describe('<TemplatesList />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('api is called to delete templates for each selected template.', async () => {
|
test('api is called to delete templates for each selected template.', async () => {
|
||||||
const wrapper = mountWithContexts(<TemplatesList />);
|
const wrapper = mountWithContexts(<TemplateList />);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
@@ -277,7 +277,7 @@ describe('<TemplatesList />', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const wrapper = mountWithContexts(<TemplatesList />);
|
const wrapper = mountWithContexts(<TemplateList />);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
@@ -315,7 +315,7 @@ describe('<TemplatesList />', () => {
|
|||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<Route
|
<Route
|
||||||
path="/projects/:id/job_templates"
|
path="/projects/:id/job_templates"
|
||||||
component={() => <TemplatesList />}
|
component={() => <TemplateList />}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
@@ -2,12 +2,12 @@ import React from 'react';
|
|||||||
|
|
||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
|
|
||||||
import TemplatesListItem from './TemplateListItem';
|
import TemplateListItem from './TemplateListItem';
|
||||||
|
|
||||||
describe('<TemplatesListItem />', () => {
|
describe('<TemplateListItem />', () => {
|
||||||
test('launch button shown to users with start capabilities', () => {
|
test('launch button shown to users with start capabilities', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -26,7 +26,7 @@ describe('<TemplatesListItem />', () => {
|
|||||||
});
|
});
|
||||||
test('launch button hidden from users without start capabilities', () => {
|
test('launch button hidden from users without start capabilities', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -45,7 +45,7 @@ describe('<TemplatesListItem />', () => {
|
|||||||
});
|
});
|
||||||
test('edit button shown to users with edit capabilities', () => {
|
test('edit button shown to users with edit capabilities', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -64,7 +64,7 @@ describe('<TemplatesListItem />', () => {
|
|||||||
});
|
});
|
||||||
test('edit button hidden from users without edit capabilities', () => {
|
test('edit button hidden from users without edit capabilities', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -83,7 +83,7 @@ describe('<TemplatesListItem />', () => {
|
|||||||
});
|
});
|
||||||
test('missing resource icon is shown.', () => {
|
test('missing resource icon is shown.', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -102,7 +102,7 @@ describe('<TemplatesListItem />', () => {
|
|||||||
});
|
});
|
||||||
test('missing resource icon is not shown when there is a project and an inventory.', () => {
|
test('missing resource icon is not shown when there is a project and an inventory.', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -123,7 +123,7 @@ describe('<TemplatesListItem />', () => {
|
|||||||
});
|
});
|
||||||
test('missing resource icon is not shown when inventory is prompt_on_launch, and a project', () => {
|
test('missing resource icon is not shown when inventory is prompt_on_launch, and a project', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -144,7 +144,7 @@ describe('<TemplatesListItem />', () => {
|
|||||||
});
|
});
|
||||||
test('missing resource icon is not shown type is workflow_job_template', () => {
|
test('missing resource icon is not shown type is workflow_job_template', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<TemplatesListItem
|
<TemplateListItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
template={{
|
template={{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -90,7 +90,6 @@ export { addDefaultsToObject as _addDefaultsToObject };
|
|||||||
/**
|
/**
|
||||||
* Convert query param object to url query string
|
* Convert query param object to url query string
|
||||||
* Used to encode params for interacting with the api
|
* Used to encode params for interacting with the api
|
||||||
* @param {object} qs config object for namespacing params, filtering defaults
|
|
||||||
* @param {object} query param object
|
* @param {object} query param object
|
||||||
* @return {string} url query string
|
* @return {string} url query string
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user