Skip to content

Commit c3eabde

Browse files
authored
bug: Dataset Loading Performance (#112)
* Add server side pagination for datasets
1 parent 989490d commit c3eabde

6 files changed

Lines changed: 121 additions & 12 deletions

File tree

packages/client/src/components/DatasetTable.component.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DataGrid, GridColDef, GridRowId, GridActionsCellItem } from '@mui/x-data-grid';
22
import { useState, useEffect } from 'react';
33
import { Dataset, Entry } from '../graphql/graphql';
4-
import { useEntryForDatasetLazyQuery } from '../graphql/entry/entry';
4+
import { useEntryForDatasetLazyQuery, useCountEntryForDatasetLazyQuery } from '../graphql/entry/entry';
55
import { EntryView } from './EntryView.component';
66
import { useTranslation } from 'react-i18next';
77
import { useSnackbar } from '../context/Snackbar.context';
@@ -22,6 +22,7 @@ export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
2222
const [deleteEntryMutation] = useDeleteEntryMutation();
2323
const confirmation = useConfirmation();
2424
const [selectedRows, setSelectedRows] = useState<GridRowId[]>([]);
25+
const [paginationModel, setPaginationModel] = useState<{ page: number; pageSize: number }>({ page: 0, pageSize: 10 });
2526

2627
const defaultColumns: GridColDef[] = [
2728
{
@@ -97,19 +98,25 @@ export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
9798
};
9899

99100
const [entries, setEntries] = useState<Entry[]>([]);
101+
const [rowCount, setRowCount] = useState<number>(0);
100102
const columns = [...defaultColumns, ...(props.additionalColumns ?? [])];
101103
if (props.supportEntryDelete) {
102104
columns.push(deleteColumn);
103105
}
104106

105107
const [entryForDataset, entryForDatasetResult] = useEntryForDatasetLazyQuery();
108+
const [entryCount, entryCountResult] = useCountEntryForDatasetLazyQuery();
106109

107110
useEffect(() => {
108111
reload();
109-
}, [props.dataset]);
112+
}, [props.dataset, paginationModel]);
110113

111114
const reload = () => {
112-
entryForDataset({ variables: { dataset: props.dataset._id }, fetchPolicy: 'network-only' });
115+
entryForDataset({
116+
variables: { dataset: props.dataset._id, page: paginationModel.page, pageSize: paginationModel.pageSize },
117+
fetchPolicy: 'network-only'
118+
});
119+
entryCount({ variables: { dataset: props.dataset._id } });
113120
};
114121

115122
// TODO: Add in logic to re-fetch data when the presigned URL expires
@@ -122,11 +129,24 @@ export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
122129
}
123130
}, [entryForDatasetResult]);
124131

132+
useEffect(() => {
133+
if (entryCountResult.data) {
134+
setRowCount(entryCountResult.data.countEntryForDataset);
135+
} else if (entryCountResult.error) {
136+
pushSnackbarMessage(t('errors.entryQuery'), 'error');
137+
console.error(entryForDatasetResult.error);
138+
}
139+
}, [entryCountResult]);
140+
125141
return (
126142
<DataGrid
127143
getRowHeight={() => 'auto'}
128144
rows={entries}
145+
rowCount={rowCount}
129146
columns={columns}
147+
paginationMode={'server'}
148+
paginationModel={paginationModel}
149+
onPaginationModelChange={setPaginationModel}
130150
initialState={{
131151
pagination: {
132152
paginationModel: {

packages/client/src/graphql/entry/entry.graphql

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
query entryForDataset($dataset: ID!) {
2-
entryForDataset(dataset: $dataset) {
1+
query entryForDataset($dataset: ID!, $page: Int, $pageSize: Int) {
2+
entryForDataset(dataset: $dataset, page: $page, pageSize: $pageSize) {
33
_id
44
organization
55
entryID
@@ -14,6 +14,10 @@ query entryForDataset($dataset: ID!) {
1414
}
1515
}
1616

17+
query countEntryForDataset($dataset: ID!) {
18+
countEntryForDataset(dataset: $dataset)
19+
}
20+
1721
query entryFromID($entry: ID!) {
1822
entryFromID(entry: $entry) {
1923
_id

packages/client/src/graphql/entry/entry.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@ import * as Apollo from '@apollo/client';
77
const defaultOptions = {} as const;
88
export type EntryForDatasetQueryVariables = Types.Exact<{
99
dataset: Types.Scalars['ID']['input'];
10+
page?: Types.InputMaybe<Types.Scalars['Int']['input']>;
11+
pageSize?: Types.InputMaybe<Types.Scalars['Int']['input']>;
1012
}>;
1113

1214

1315
export type EntryForDatasetQuery = { __typename?: 'Query', entryForDataset: Array<{ __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number, isTraining: boolean }> };
1416

17+
export type CountEntryForDatasetQueryVariables = Types.Exact<{
18+
dataset: Types.Scalars['ID']['input'];
19+
}>;
20+
21+
22+
export type CountEntryForDatasetQuery = { __typename?: 'Query', countEntryForDataset: number };
23+
1524
export type EntryFromIdQueryVariables = Types.Exact<{
1625
entry: Types.Scalars['ID']['input'];
1726
}>;
@@ -28,8 +37,8 @@ export type DeleteEntryMutation = { __typename?: 'Mutation', deleteEntry: boolea
2837

2938

3039
export const EntryForDatasetDocument = gql`
31-
query entryForDataset($dataset: ID!) {
32-
entryForDataset(dataset: $dataset) {
40+
query entryForDataset($dataset: ID!, $page: Int, $pageSize: Int) {
41+
entryForDataset(dataset: $dataset, page: $page, pageSize: $pageSize) {
3342
_id
3443
organization
3544
entryID
@@ -58,6 +67,8 @@ export const EntryForDatasetDocument = gql`
5867
* const { data, loading, error } = useEntryForDatasetQuery({
5968
* variables: {
6069
* dataset: // value for 'dataset'
70+
* page: // value for 'page'
71+
* pageSize: // value for 'pageSize'
6172
* },
6273
* });
6374
*/
@@ -72,6 +83,39 @@ export function useEntryForDatasetLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
7283
export type EntryForDatasetQueryHookResult = ReturnType<typeof useEntryForDatasetQuery>;
7384
export type EntryForDatasetLazyQueryHookResult = ReturnType<typeof useEntryForDatasetLazyQuery>;
7485
export type EntryForDatasetQueryResult = Apollo.QueryResult<EntryForDatasetQuery, EntryForDatasetQueryVariables>;
86+
export const CountEntryForDatasetDocument = gql`
87+
query countEntryForDataset($dataset: ID!) {
88+
countEntryForDataset(dataset: $dataset)
89+
}
90+
`;
91+
92+
/**
93+
* __useCountEntryForDatasetQuery__
94+
*
95+
* To run a query within a React component, call `useCountEntryForDatasetQuery` and pass it any options that fit your needs.
96+
* When your component renders, `useCountEntryForDatasetQuery` returns an object from Apollo Client that contains loading, error, and data properties
97+
* you can use to render your UI.
98+
*
99+
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
100+
*
101+
* @example
102+
* const { data, loading, error } = useCountEntryForDatasetQuery({
103+
* variables: {
104+
* dataset: // value for 'dataset'
105+
* },
106+
* });
107+
*/
108+
export function useCountEntryForDatasetQuery(baseOptions: Apollo.QueryHookOptions<CountEntryForDatasetQuery, CountEntryForDatasetQueryVariables>) {
109+
const options = {...defaultOptions, ...baseOptions}
110+
return Apollo.useQuery<CountEntryForDatasetQuery, CountEntryForDatasetQueryVariables>(CountEntryForDatasetDocument, options);
111+
}
112+
export function useCountEntryForDatasetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CountEntryForDatasetQuery, CountEntryForDatasetQueryVariables>) {
113+
const options = {...defaultOptions, ...baseOptions}
114+
return Apollo.useLazyQuery<CountEntryForDatasetQuery, CountEntryForDatasetQueryVariables>(CountEntryForDatasetDocument, options);
115+
}
116+
export type CountEntryForDatasetQueryHookResult = ReturnType<typeof useCountEntryForDatasetQuery>;
117+
export type CountEntryForDatasetLazyQueryHookResult = ReturnType<typeof useCountEntryForDatasetLazyQuery>;
118+
export type CountEntryForDatasetQueryResult = Apollo.QueryResult<CountEntryForDatasetQuery, CountEntryForDatasetQueryVariables>;
75119
export const EntryFromIdDocument = gql`
76120
query entryFromID($entry: ID!) {
77121
entryFromID(entry: $entry) {

packages/client/src/graphql/graphql.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ export type ProjectPermissionModel = {
468468

469469
export type Query = {
470470
__typename?: 'Query';
471+
countEntryForDataset: Scalars['Int']['output'];
471472
datasetExists: Scalars['Boolean']['output'];
472473
entryForDataset: Array<Entry>;
473474
entryFromID: Entry;
@@ -500,13 +501,20 @@ export type Query = {
500501
};
501502

502503

504+
export type QueryCountEntryForDatasetArgs = {
505+
dataset: Scalars['ID']['input'];
506+
};
507+
508+
503509
export type QueryDatasetExistsArgs = {
504510
name: Scalars['String']['input'];
505511
};
506512

507513

508514
export type QueryEntryForDatasetArgs = {
509515
dataset: Scalars['ID']['input'];
516+
page?: InputMaybe<Scalars['Int']['input']>;
517+
pageSize?: InputMaybe<Scalars['Int']['input']>;
510518
};
511519

512520

packages/server/src/entry/resolvers/entry.resolver.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Args, ID, Resolver, Query, ResolveField, Parent, Mutation } from '@nestjs/graphql';
1+
import { Args, ID, Resolver, Query, ResolveField, Parent, Mutation, Int } from '@nestjs/graphql';
22
import { Dataset } from '../../dataset/dataset.model';
33
import { Entry } from '../models/entry.model';
44
import { EntryService } from '../services/entry.service';
@@ -28,13 +28,27 @@ export class EntryResolver {
2828
@Query(() => [Entry])
2929
async entryForDataset(
3030
@Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset,
31-
@TokenContext() user: TokenPayload
31+
@TokenContext() user: TokenPayload,
32+
@Args('page', { type: () => Int, nullable: true }) page?: number,
33+
@Args('pageSize', { type: () => Int, nullable: true }) pageSize?: number
3234
): Promise<Entry[]> {
3335
if (!(await this.enforcer.enforce(user.user_id, DatasetPermissions.READ, dataset._id.toString()))) {
3436
throw new UnauthorizedException('User cannot read entries on this dataset');
3537
}
3638

37-
return this.entryService.findForDataset(dataset);
39+
return this.entryService.findForDataset(dataset, page, pageSize);
40+
}
41+
42+
@Query(() => Int)
43+
async countEntryForDataset(
44+
@Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset,
45+
@TokenContext() user: TokenPayload
46+
): Promise<Number> {
47+
if (!(await this.enforcer.enforce(user.user_id, DatasetPermissions.READ, dataset._id.toString()))) {
48+
throw new UnauthorizedException('User cannot read entries on this dataset');
49+
}
50+
51+
return this.entryService.countForDataset(dataset);
3852
}
3953

4054
@Query(() => Entry)

packages/server/src/entry/services/entry.service.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class EntryService {
4848
await this.entryModel.deleteOne({ _id: entry._id });
4949
}
5050

51-
async findForDataset(dataset: Dataset | string): Promise<Entry[]> {
51+
async findForDataset(dataset: Dataset | string, page?: number, pageSize?: number): Promise<Entry[]> {
5252
let id: string = '';
5353

5454
if (typeof dataset === 'string') {
@@ -57,7 +57,26 @@ export class EntryService {
5757
id = dataset._id.toString();
5858
}
5959

60-
return this.entryModel.find({ dataset: id, isTraining: false });
60+
const query = this.entryModel.find({ dataset: id, isTraining: false });
61+
62+
if (page !== undefined && pageSize !== undefined) {
63+
const offset = page * pageSize;
64+
return await query.skip(offset).limit(pageSize);
65+
}
66+
67+
return query;
68+
}
69+
70+
async countForDataset(dataset: Dataset | string) {
71+
let id: string = '';
72+
73+
if (typeof dataset === 'string') {
74+
id = dataset;
75+
} else {
76+
id = dataset._id.toString();
77+
}
78+
79+
return this.entryModel.count({ dataset: id, isTraining: false });
6180
}
6281

6382
async exists(entryID: string, dataset: Dataset): Promise<boolean> {

0 commit comments

Comments
 (0)