Skip to content

Commit

Permalink
fix(admin): Use cursor based pagination on all APIs that support it […
Browse files Browse the repository at this point in the history
…EP-2971]
  • Loading branch information
ancashoria authored Oct 7, 2020
1 parent 7c4337e commit 49f4869
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 34 deletions.
7 changes: 3 additions & 4 deletions packages/earth-admin/src/pages-client/dashboards/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ import { DashboardsHome } from './home';
import { NewDashboard } from './new';

const PAGE_TYPE = setPage('Dashboards');
const EXCLUDED_FIELDS = '-geojson,-bbox2d,-centroid';
const PAGE_SIZE = 20;

export default function DashboardsPage(props) {
const { selectedGroup } = useAuth0();
const [searchValue, setSearchValue] = useState('');

const getQuery = (pageIndex) => {
const getQuery = (cursor) => {
const query = {
search: searchValue,
sort: 'name',
page: { size: PAGE_SIZE, number: pageIndex },
select: EXCLUDED_FIELDS,
page: { size: PAGE_SIZE, cursor },
select: 'name,slug',
group: selectedGroup,
};
return encodeQueryToURL('dashboards', query);
Expand Down
5 changes: 3 additions & 2 deletions packages/earth-admin/src/pages-client/layers/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ export default function LayersPage(props) {
const { selectedGroup } = useAuth0();
const [searchValue, setSearchValue] = useState('');

const getQuery = (pageIndex) => {
const getQuery = (cursor) => {
const query = {
search: searchValue,
sort: 'name',
page: { size: PAGE_SIZE, number: pageIndex },
page: { size: PAGE_SIZE, cursor },
select: 'name,slug',
group: selectedGroup,
};
return encodeQueryToURL('layers', query);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Auth0ListItem, DataListing } from '@app/components/data-listing';
import { SidebarLayout } from '@app/layouts';
import { getAllOrganizations } from '@app/services/organizations';
import { encodeQueryToURL, setPage } from '@app/utils';
import { useInfiniteList } from '@app/utils/hooks';
import { useInfiniteListPaged } from '@app/utils/hooks';

import { OrganizationDetails } from './details';
import { OrganizationHome } from './home';
Expand All @@ -40,11 +40,12 @@ export default function PlacesPage(props) {
const getQuery = (pageIndex) => {
const query = {
page: { size: PAGE_SIZE, number: pageIndex },
select: 'name,slug',
group: selectedGroup,
};
return encodeQueryToURL('organizations', query);
};
const { listProps, mutate } = useInfiniteList(getQuery, getAllOrganizations);
const { listProps, mutate } = useInfiniteListPaged(getQuery, getAllOrganizations);

return (
<>
Expand Down
7 changes: 3 additions & 4 deletions packages/earth-admin/src/pages-client/places/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,19 @@ import { PlaceDetail } from './details';
import { PlacesHome } from './home';
import { NewPlace } from './new';

const EXCLUDED_FIELDS = '-geojson,-bbox2d,-centroid';
const PAGE_TYPE = setPage('Places');
const PAGE_SIZE = 20;

export default function PlacesPage(props) {
const { selectedGroup } = useAuth0();
const [searchValue, setSearchValue] = useState('');

const getQuery = (pageIndex) => {
const getQuery = (cursor) => {
const query = {
search: searchValue,
sort: 'name',
page: { size: PAGE_SIZE, number: pageIndex },
select: EXCLUDED_FIELDS,
page: { size: PAGE_SIZE, cursor },
select: 'name,slug',
group: selectedGroup,
};
return encodeQueryToURL('locations', query);
Expand Down
4 changes: 2 additions & 2 deletions packages/earth-admin/src/pages-client/users/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { ContentLayout } from '@app/layouts';
import { getAvailableGroups } from '@app/services';
import { addUsers, getAllUsers } from '@app/services/users';
import { encodeQueryToURL } from '@app/utils';
import { useInfiniteList } from '@app/utils/hooks';
import { useInfiniteListPaged } from '@app/utils/hooks';
import { validEmail } from '@app/utils/validations';

import { CUSTOM_STYLES, SELECT_THEME } from '../../theme';
Expand All @@ -56,7 +56,7 @@ export function UsersHome(props: any) {
};
return encodeQueryToURL('users', query);
};
const { listProps: userListProps, mutate } = useInfiniteList(getQuery, getAllUsers);
const { listProps: userListProps, mutate } = useInfiniteListPaged(getQuery, getAllUsers);

const { watch, setValue, control, getValues } = useForm({
mode: 'onChange',
Expand Down
9 changes: 4 additions & 5 deletions packages/earth-admin/src/pages-client/widgets/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,18 @@ import { WidgetsHome } from './home';
import { NewWidget } from './new';

const PAGE_TYPE = setPage('Widgets');
const EXCLUDED_FIELDS = '-geojson,-bbox2d,-centroid';
const PAGE_SIZE = 20;

export default function DashboardsPage(props) {
const { selectedGroup } = useAuth0();
const [searchValue, setSearchValue] = useState('');

const getQuery = (pageIndex) => {
const getQuery = (cursor) => {
const query = {
search: searchValue,
sort: 'name',
page: { size: PAGE_SIZE, number: pageIndex },
select: EXCLUDED_FIELDS,
page: { size: PAGE_SIZE, cursor },
select: 'name,slug',
group: selectedGroup,
};
return encodeQueryToURL('widgets', query);
Expand All @@ -58,7 +57,7 @@ export default function DashboardsPage(props) {
// it might not return filters
// TODO: create a custom hook for reuse on multiple pages
const metricsQuery = {
select: EXCLUDED_FIELDS,
select: 'name,slug',
page: { size: 1, number: 1 },
group: selectedGroup,
};
Expand Down
100 changes: 85 additions & 15 deletions packages/earth-admin/src/utils/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
specific language governing permissions and limitations under the License.
*/

import { add, compose, noop } from 'lodash/fp';
import { noop } from 'lodash/fp';
import { useSWRInfinite } from 'swr';

/**
Expand All @@ -27,12 +27,19 @@ import { useSWRInfinite } from 'swr';
* @param options
*/
export function useInfiniteList(
getQuery: (pageIndex: number) => string,
getQuery: (cursor: number | string) => string,
fetcher: (any) => Promise<any>,
options: object = {}
) {
// Our api starts with page 1 instead of 0, so we increment the pageIndex
const offsetGetQuery = compose(getQuery, add(1));
const wrappedQuery = (pageIndex: number, previousPageData: any): string => {
// reached the end
if (previousPageData && !previousPageData.data) {
return null;
}
const cursor = pageIndex === 0 ? -1 : previousPageData.pagination.nextCursor;

return getQuery(cursor);
};
const {
data: response = [],
error,
Expand All @@ -41,19 +48,57 @@ export function useInfiniteList(
setSize,
mutate,
revalidate,
} = useSWRInfinite(offsetGetQuery, fetcher, options);
} = useSWRInfinite(wrappedQuery, fetcher, options);

// Merge multiple page results into a single list of results
const items = response.reduce(
(acc: any, { data, ...rest }: any) => {
return {
data: acc.data.concat(data),
...rest,
};
const items = mergePages(response);
const [firstPage] = response;
const lastPage = response[response.length - 1];
const totalResults = firstPage?.total;
const isNoMore = !lastPage?.pagination.nextCursor;
const awaitMore = !isValidating && !isNoMore;

const returnValues = {
// props for <DataListing />
listProps: {
data: items.data,
// TODO: find out why onIntersection is called even if awaitMore=false
// Even though onIntersection will fire, it will be a noop
onIntersection: awaitMore ? () => setSize(size + 1) : noop,
isLoading: isValidating,
awaitMore,
isNoMore,
totalResults,
},
{ data: [] }
);
revalidate,
mutate,
error,
};

return returnValues;
}

export function useInfiniteListPaged(
getQuery: (pageIndex: number) => string,
fetcher: (any) => Promise<any>,
options: object = {}
) {
const wrappedQuery = (pageIndex: number): string => {
const offsetPageIndex = pageIndex + 1;
return getQuery(offsetPageIndex);
};
const {
data: response = [],
error,
isValidating,
size,
setSize,
mutate,
revalidate,
} = useSWRInfinite(wrappedQuery, fetcher, options);

const items = mergePages(response);
const isNoMore = items.data.length >= items.total;
const totalResults = items.total;
const awaitMore = !isValidating && !isNoMore;

const returnValues = {
Expand All @@ -66,7 +111,7 @@ export function useInfiniteList(
isLoading: isValidating,
awaitMore,
isNoMore,
totalResults: items.total,
totalResults,
},
revalidate,
mutate,
Expand All @@ -75,3 +120,28 @@ export function useInfiniteList(

return returnValues;
}

interface IMergedResults {
data: Array<any>;
pagination?: {
size: number;
total: number;
nextCursor?: string;
};
total?: number;
}

/**
* Merge multiple page results into a single list of results
*/
export function mergePages(pagedResponse: Array<any>): IMergedResults {
return pagedResponse.reduce(
(acc: any, { data, ...rest }: any) => {
return {
data: acc.data.concat(data),
...rest,
};
},
{ data: [] }
);
}

0 comments on commit 49f4869

Please sign in to comment.