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:
softwarefactory-project-zuul[bot] 2020-02-12 21:48:16 +00:00 committed by GitHub
commit 1ae86ae752
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 41 deletions

View File

@ -1,5 +1,5 @@
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 { t } from '@lingui/macro';
import { CredentialsAPI } from '@api';
@ -11,7 +11,12 @@ import PaginatedDataList, {
ToolbarAddButton,
ToolbarDeleteButton,
} from '@components/PaginatedDataList';
import { getQSConfig, parseQueryString } from '@util/qs';
import {
getQSConfig,
parseQueryString,
replaceParams,
encodeNonDefaultQueryString,
} from '@util/qs';
import { CredentialListItem } from '.';
const QS_CONFIG = getQSConfig('credential', {
@ -30,6 +35,7 @@ function CredentialList({ i18n }) {
const [selected, setSelected] = useState([]);
const location = useLocation();
const history = useHistory();
const loadCredentials = async ({ search }) => {
const params = parseQueryString(QS_CONFIG, search);
@ -92,20 +98,21 @@ function CredentialList({ i18n }) {
setDeletionError(error);
}
adjustPagination();
setSelected([]);
};
const adjustPagination = () => {
const params = parseQueryString(QS_CONFIG, location.search);
try {
const {
data: { count, results },
} = await CredentialsAPI.read(params);
setCredentials(results);
setCredentialCount(count);
setSelected([]);
} catch (error) {
setContentError(error);
if (params.page > 1 && selected.length === credentials.length) {
const newParams = encodeNonDefaultQueryString(
QS_CONFIG,
replaceParams(params, { page: params.page - 1 })
);
history.push(`${location.pathname}?${newParams}`);
} else {
loadCredentials(location);
}
setHasContentLoading(false);
};
const canAdd =

View File

@ -1,5 +1,5 @@
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 { t } from '@lingui/macro';
import { Card, PageSection } from '@patternfly/react-core';
@ -13,7 +13,12 @@ import PaginatedDataList, {
ToolbarAddButton,
ToolbarDeleteButton,
} from '@components/PaginatedDataList';
import { getQSConfig, parseQueryString } from '@util/qs';
import {
getQSConfig,
parseQueryString,
replaceParams,
encodeNonDefaultQueryString,
} from '@util/qs';
import OrganizationListItem from './OrganizationListItem';
@ -25,6 +30,7 @@ const QS_CONFIG = getQSConfig('organization', {
function OrganizationsList({ i18n }) {
const location = useLocation();
const history = useHistory();
const match = useRouteMatch();
const [selected, setSelected] = useState([]);
@ -81,7 +87,21 @@ function OrganizationsList({ i18n }) {
const handleOrgDelete = async () => {
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;

View File

@ -29,7 +29,7 @@ const QS_CONFIG = getQSConfig('template', {
type: 'job_template,workflow_job_template',
});
function TemplatesList({ i18n }) {
function TemplateList({ i18n }) {
const { id: projectId } = useParams();
const { pathname, search } = useLocation();
@ -268,5 +268,4 @@ function TemplatesList({ i18n }) {
);
}
export { TemplatesList as _TemplatesList };
export default withI18n()(TemplatesList);
export default withI18n()(TemplateList);

View File

@ -9,7 +9,7 @@ import {
} from '@api';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import TemplatesList from './TemplateList';
import TemplateList from './TemplateList';
jest.mock('@api');
@ -71,7 +71,7 @@ const mockTemplates = [
},
];
describe('<TemplatesList />', () => {
describe('<TemplateList />', () => {
beforeEach(() => {
UnifiedJobTemplatesAPI.read.mockResolvedValue({
data: {
@ -94,7 +94,7 @@ describe('<TemplatesList />', () => {
test('initially renders successfully', async () => {
await act(async () => {
mountWithContexts(
<TemplatesList
<TemplateList
match={{ path: '/templates', url: '/templates' }}
location={{ search: '', pathname: '/templates' }}
/>
@ -105,7 +105,7 @@ describe('<TemplatesList />', () => {
test('Templates are retrieved from the api and the components finishes loading', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(<TemplatesList />);
wrapper = mountWithContexts(<TemplateList />);
});
expect(UnifiedJobTemplatesAPI.read).toBeCalled();
await act(async () => {
@ -115,7 +115,7 @@ describe('<TemplatesList />', () => {
});
test('handleSelect is called when a template list item is selected', async () => {
const wrapper = mountWithContexts(<TemplatesList />);
const wrapper = mountWithContexts(<TemplateList />);
await act(async () => {
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 () => {
const wrapper = mountWithContexts(<TemplatesList />);
const wrapper = mountWithContexts(<TemplateList />);
await act(async () => {
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 () => {
const wrapper = mountWithContexts(<TemplatesList />);
const wrapper = mountWithContexts(<TemplateList />);
await act(async () => {
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 () => {
const wrapper = mountWithContexts(<TemplatesList />);
const wrapper = mountWithContexts(<TemplateList />);
await act(async () => {
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 waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
});
@ -315,7 +315,7 @@ describe('<TemplatesList />', () => {
const wrapper = mountWithContexts(
<Route
path="/projects/:id/job_templates"
component={() => <TemplatesList />}
component={() => <TemplateList />}
/>,
{
context: {

View File

@ -2,12 +2,12 @@ import React from 'react';
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', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,
@ -26,7 +26,7 @@ describe('<TemplatesListItem />', () => {
});
test('launch button hidden from users without start capabilities', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,
@ -45,7 +45,7 @@ describe('<TemplatesListItem />', () => {
});
test('edit button shown to users with edit capabilities', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,
@ -64,7 +64,7 @@ describe('<TemplatesListItem />', () => {
});
test('edit button hidden from users without edit capabilities', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,
@ -83,7 +83,7 @@ describe('<TemplatesListItem />', () => {
});
test('missing resource icon is shown.', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,
@ -102,7 +102,7 @@ describe('<TemplatesListItem />', () => {
});
test('missing resource icon is not shown when there is a project and an inventory.', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,
@ -123,7 +123,7 @@ describe('<TemplatesListItem />', () => {
});
test('missing resource icon is not shown when inventory is prompt_on_launch, and a project', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,
@ -144,7 +144,7 @@ describe('<TemplatesListItem />', () => {
});
test('missing resource icon is not shown type is workflow_job_template', () => {
const wrapper = mountWithContexts(
<TemplatesListItem
<TemplateListItem
isSelected={false}
template={{
id: 1,

View File

@ -90,7 +90,6 @@ export { addDefaultsToObject as _addDefaultsToObject };
/**
* Convert query param object to url query string
* Used to encode params for interacting with the api
* @param {object} qs config object for namespacing params, filtering defaults
* @param {object} query param object
* @return {string} url query string
*/