chore: set default fetchPolicy, handle subscription errors like query errors, timeout unsubscribe (#1482)
* chore: set default fetchPolicy, handle subscription errors like query errors,add unsubscribe timeout * chore: improve no data handling in fills, ordes and trades * chore: make reset delay optional, fix pagination and useOrderListData spec
This commit is contained in:
parent
adf6059701
commit
e310f04034
@ -15,19 +15,30 @@ import type {
|
||||
TabToNextCellParams,
|
||||
CellKeyDownEvent,
|
||||
FullWidthCellKeyDownEvent,
|
||||
IGetRowsParams,
|
||||
IDatasource,
|
||||
} from 'ag-grid-community';
|
||||
import { NO_DATA_MESSAGE } from '../../constants';
|
||||
import * as constants from '../simple-market-list/constants';
|
||||
|
||||
export interface GetRowsParams<T>
|
||||
extends Omit<IGetRowsParams, 'successCallback'> {
|
||||
successCallback(rowsThisBlock: T[], lastRow?: number): void;
|
||||
}
|
||||
|
||||
export interface Datasource<T> extends IDatasource {
|
||||
getRows(params: GetRowsParams<T>): void;
|
||||
}
|
||||
interface Props<T> extends GridOptions {
|
||||
data?: T[];
|
||||
rowData?: T[];
|
||||
datasource?: Datasource<T>;
|
||||
handleRowClicked?: (event: { data: T }) => void;
|
||||
components?: Record<string, unknown>;
|
||||
classNamesParam?: string | string[];
|
||||
}
|
||||
|
||||
const ConsoleLiteGrid = <T extends { id?: string }>(
|
||||
{ data, handleRowClicked, getRowId, classNamesParam, ...props }: Props<T>,
|
||||
{ handleRowClicked, getRowId, classNamesParam, ...props }: Props<T>,
|
||||
ref?: React.Ref<AgGridReact>
|
||||
) => {
|
||||
const { isMobile, screenSize } = useScreenDimensions();
|
||||
@ -70,7 +81,6 @@ const ConsoleLiteGrid = <T extends { id?: string }>(
|
||||
return (
|
||||
<AgGrid
|
||||
className={classNames(classNamesParam)}
|
||||
rowData={data}
|
||||
rowHeight={60}
|
||||
customThemeParams={
|
||||
theme === 'dark'
|
||||
|
@ -44,7 +44,7 @@ const AccountsManager = () => {
|
||||
noDataMessage={NO_DATA_MESSAGE}
|
||||
>
|
||||
<ConsoleLiteGrid<AccountObj>
|
||||
data={data as AccountObj[]}
|
||||
rowData={data as AccountObj[]}
|
||||
columnDefs={columnDefs}
|
||||
defaultColDef={defaultColDef}
|
||||
components={{ PriceCell }}
|
||||
|
@ -56,7 +56,8 @@ const OrdersManager = () => {
|
||||
>
|
||||
<ConsoleLiteGrid<OrderWithMarket>
|
||||
ref={gridRef}
|
||||
rowModelType="infinite"
|
||||
rowModelType={data?.length ? 'infinite' : 'clientSide'}
|
||||
rowData={data?.length ? undefined : []}
|
||||
datasource={{ getRows }}
|
||||
onBodyScrollEnd={onBodyScrollEnd}
|
||||
onBodyScroll={onBodyScroll}
|
||||
|
@ -47,7 +47,7 @@ const PositionsAsset = ({ partyId, assetSymbol }: Props) => {
|
||||
defaultColDef={defaultColDef}
|
||||
getRowId={getRowId}
|
||||
rowModelType={data?.length ? 'infinite' : 'clientSide'}
|
||||
data={data?.length ? undefined : []}
|
||||
rowData={data?.length ? undefined : []}
|
||||
datasource={{ getRows }}
|
||||
components={{ PriceFlashCell }}
|
||||
/>
|
||||
|
@ -67,7 +67,7 @@ const SimpleMarketList = () => {
|
||||
<ConsoleLiteGrid<MarketWithPercentChange>
|
||||
classNamesParam="mb-32 min-h-[300px]"
|
||||
columnDefs={columnDefs}
|
||||
data={localData}
|
||||
rowData={localData}
|
||||
defaultColDef={defaultColDef}
|
||||
handleRowClicked={handleRowClicked}
|
||||
/>
|
||||
|
@ -33,8 +33,9 @@ export const FillsManager = ({ partyId }: FillsManagerProps) => {
|
||||
<FillsTable
|
||||
ref={gridRef}
|
||||
partyId={partyId}
|
||||
rowModelType={data?.length ? 'infinite' : 'clientSide'}
|
||||
rowData={data?.length ? undefined : []}
|
||||
datasource={{ getRows }}
|
||||
rowModelType="infinite"
|
||||
onBodyScrollEnd={onBodyScrollEnd}
|
||||
onBodyScroll={onBodyScroll}
|
||||
/>
|
||||
|
@ -47,6 +47,7 @@ export const useFillsList = ({ partyId, gridRef, scrolledToTop }: Props) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
if (dataRef.current?.length) {
|
||||
if (!scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
@ -58,6 +59,9 @@ export const useFillsList = ({ partyId, gridRef, scrolledToTop }: Props) => {
|
||||
dataRef.current = data;
|
||||
gridRef.current.api.refreshInfiniteCache();
|
||||
return true;
|
||||
}
|
||||
dataRef.current = data;
|
||||
return false;
|
||||
},
|
||||
[gridRef, scrolledToTop]
|
||||
);
|
||||
|
@ -83,6 +83,7 @@ export const marketsProvider = makeDataProvider<
|
||||
>({
|
||||
query: MARKET_LIST_QUERY,
|
||||
getData,
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
export const activeMarketsProvider = makeDerivedDataProvider<Market[], never>(
|
||||
|
@ -34,7 +34,8 @@ export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<OrderList
|
||||
ref={gridRef}
|
||||
rowModelType="infinite"
|
||||
rowModelType={data?.length ? 'infinite' : 'clientSide'}
|
||||
rowData={data?.length ? undefined : []}
|
||||
datasource={{ getRows }}
|
||||
onBodyScrollEnd={onBodyScrollEnd}
|
||||
onBodyScroll={onBodyScroll}
|
||||
|
@ -13,7 +13,7 @@ let mockDataProviderData = {
|
||||
error: undefined,
|
||||
loading: true,
|
||||
load: loadMock,
|
||||
totalCount: 0,
|
||||
totalCount: undefined,
|
||||
};
|
||||
|
||||
let updateMock: jest.Mock;
|
||||
@ -98,7 +98,7 @@ describe('useOrderListData Hook', () => {
|
||||
expect(mockRefreshAgGridApi).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('methods for pagination should works', async () => {
|
||||
it('methods for pagination should work', async () => {
|
||||
const successCallback = jest.fn();
|
||||
mockData = [
|
||||
{
|
||||
@ -114,12 +114,10 @@ describe('useOrderListData Hook', () => {
|
||||
},
|
||||
} as unknown as Orders_party_ordersConnection_edges,
|
||||
];
|
||||
mockDataProviderData = {
|
||||
...mockDataProviderData,
|
||||
Object.assign(mockDataProviderData, {
|
||||
data: mockData,
|
||||
loading: false,
|
||||
totalCount: 4,
|
||||
};
|
||||
});
|
||||
const mockDelta = [
|
||||
{
|
||||
node: {
|
||||
@ -142,8 +140,6 @@ describe('useOrderListData Hook', () => {
|
||||
}
|
||||
);
|
||||
|
||||
updateMock({ data: mockNextData, delta: mockDelta });
|
||||
|
||||
const getRowsParams = {
|
||||
successCallback,
|
||||
failCallback: jest.fn(),
|
||||
@ -152,12 +148,14 @@ describe('useOrderListData Hook', () => {
|
||||
} as unknown as IGetRowsParams;
|
||||
|
||||
await waitFor(async () => {
|
||||
await result.current.getRows(getRowsParams);
|
||||
const promise = result.current.getRows(getRowsParams);
|
||||
updateMock({ data: mockNextData, delta: mockDelta });
|
||||
await promise;
|
||||
});
|
||||
expect(loadMock).toHaveBeenCalled();
|
||||
expect(successCallback).toHaveBeenLastCalledWith(
|
||||
mockDelta.map((item) => item.node),
|
||||
4
|
||||
-1
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -50,6 +50,7 @@ export const useOrderListData = ({
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
if (dataRef.current?.length) {
|
||||
if (!scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
@ -61,6 +62,9 @@ export const useOrderListData = ({
|
||||
dataRef.current = data;
|
||||
gridRef.current.api.refreshInfiniteCache();
|
||||
return true;
|
||||
}
|
||||
dataRef.current = data;
|
||||
return false;
|
||||
},
|
||||
[gridRef, scrolledToTop]
|
||||
);
|
||||
|
@ -24,11 +24,7 @@ import type {
|
||||
ICellRendererParams,
|
||||
ValueFormatterParams,
|
||||
} from 'ag-grid-community';
|
||||
import type {
|
||||
AgGridReact,
|
||||
AgGridReactProps,
|
||||
AgReactUiProps,
|
||||
} from 'ag-grid-react';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import type { Orders_party_ordersConnection_edges_node } from '../';
|
||||
@ -40,7 +36,7 @@ import { OrderEditDialog } from './order-edit-dialog';
|
||||
import type { OrderWithMarket } from '../';
|
||||
import { OrderFeedback } from '../order-feedback';
|
||||
|
||||
type OrderListProps = AgGridReactProps | AgReactUiProps;
|
||||
type OrderListProps = AgGridReactProps;
|
||||
|
||||
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
(props, ref) => {
|
||||
@ -104,7 +100,7 @@ type OrderListTableValueFormatterParams = Omit<
|
||||
data: OrderWithMarket | null;
|
||||
};
|
||||
|
||||
type OrderListTableProps = (AgGridReactProps | AgReactUiProps) & {
|
||||
type OrderListTableProps = AgGridReactProps & {
|
||||
cancel: (order: OrderWithMarket) => void;
|
||||
setEditOrder: (order: OrderWithMarket) => void;
|
||||
};
|
||||
|
@ -559,15 +559,15 @@ describe('derived data provider', () => {
|
||||
expect(callback.mock.calls[0][0].error).toBe(error);
|
||||
expect(callback.mock.calls[0][0].loading).toBe(false);
|
||||
subscription.reload();
|
||||
expect(callback.mock.calls.length).toBe(3);
|
||||
expect(callback.mock.calls[2][0].loading).toBe(true);
|
||||
expect(callback.mock.calls.length).toBe(2);
|
||||
expect(callback.mock.calls[1][0].loading).toBe(true);
|
||||
await resolveQuery({ data: part1 });
|
||||
expect(callback.mock.calls.length).toBe(3);
|
||||
expect(callback.mock.calls.length).toBe(2);
|
||||
await resolveQuery({ data: part2 });
|
||||
expect(callback.mock.calls.length).toBe(4);
|
||||
expect(callback.mock.calls[3][0].data).toStrictEqual(data);
|
||||
expect(callback.mock.calls[3][0].loading).toBe(false);
|
||||
expect(callback.mock.calls[3][0].error).toBeUndefined();
|
||||
expect(callback.mock.calls.length).toBe(3);
|
||||
expect(callback.mock.calls[2][0].data).toStrictEqual(data);
|
||||
expect(callback.mock.calls[2][0].loading).toBe(false);
|
||||
expect(callback.mock.calls[2][0].error).toBeUndefined();
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
@ -76,7 +76,7 @@ export interface Append<Data> {
|
||||
}
|
||||
|
||||
interface GetData<QueryData, Data> {
|
||||
(queryData: QueryData): Data | null;
|
||||
(queryData: QueryData, variables?: OperationVariables): Data | null;
|
||||
}
|
||||
|
||||
interface GetPageInfo<QueryData> {
|
||||
@ -88,7 +88,7 @@ interface GetTotalCount<QueryData> {
|
||||
}
|
||||
|
||||
interface GetDelta<SubscriptionData, Delta> {
|
||||
(subscriptionData: SubscriptionData): Delta;
|
||||
(subscriptionData: SubscriptionData, variables?: OperationVariables): Delta;
|
||||
}
|
||||
|
||||
export function defaultAppend<Data>(
|
||||
@ -144,6 +144,7 @@ interface DataProviderParams<QueryData, Data, SubscriptionData, Delta> {
|
||||
first: number;
|
||||
};
|
||||
fetchPolicy?: FetchPolicy;
|
||||
resetDelay?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,6 +163,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
getDelta,
|
||||
pagination,
|
||||
fetchPolicy,
|
||||
resetDelay,
|
||||
}: DataProviderParams<QueryData, Data, SubscriptionData, Delta>): Subscribe<
|
||||
Data,
|
||||
Delta
|
||||
@ -171,6 +173,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
// subscription is started before initial query, all deltas that will arrive before initial query response are put on queue
|
||||
const updateQueue: Delta[] = [];
|
||||
|
||||
let resetTimer: ReturnType<typeof setTimeout>;
|
||||
let variables: OperationVariables | undefined;
|
||||
let data: Data | null = null;
|
||||
let error: Error | undefined;
|
||||
@ -242,7 +245,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
},
|
||||
fetchPolicy: fetchPolicy || 'no-cache',
|
||||
});
|
||||
const insertionData = getData(res.data);
|
||||
const insertionData = getData(res.data, variables);
|
||||
const insertionPageInfo = pagination.getPageInfo(res.data);
|
||||
({ data, totalCount } = pagination.append(
|
||||
data,
|
||||
@ -269,10 +272,10 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
variables: pagination
|
||||
? { ...variables, pagination: { first: pagination.first } }
|
||||
: variables,
|
||||
fetchPolicy,
|
||||
fetchPolicy: fetchPolicy || 'no-cache',
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
data = getData(res.data);
|
||||
data = getData(res.data, variables);
|
||||
if (data && pagination) {
|
||||
if (!(data instanceof Array)) {
|
||||
throw new Error(
|
||||
@ -332,6 +335,9 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
|
||||
const initialize = async () => {
|
||||
if (subscription) {
|
||||
if (resetTimer) {
|
||||
clearTimeout(resetTimer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
@ -352,7 +358,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
if (!subscriptionData) {
|
||||
return;
|
||||
}
|
||||
const delta = getDelta(subscriptionData);
|
||||
const delta = getDelta(subscriptionData, variables);
|
||||
if (loading || !data) {
|
||||
updateQueue.push(delta);
|
||||
} else {
|
||||
@ -364,17 +370,25 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
notifyAll({ delta, isUpdate: true });
|
||||
}
|
||||
},
|
||||
() => reload()
|
||||
(e) => {
|
||||
error = e as Error;
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
}
|
||||
notifyAll();
|
||||
}
|
||||
);
|
||||
}
|
||||
await initialFetch();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
if (subscription) {
|
||||
if (!subscription) {
|
||||
return;
|
||||
}
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
}
|
||||
data = null;
|
||||
error = undefined;
|
||||
loading = false;
|
||||
@ -386,8 +400,12 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
|
||||
const unsubscribe = (callback: UpdateCallback<Data, Delta>) => {
|
||||
callbacks.splice(callbacks.indexOf(callback), 1);
|
||||
if (callbacks.length === 0) {
|
||||
if (resetDelay) {
|
||||
resetTimer = setTimeout(reset, resetDelay);
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (callback, c, v) => {
|
||||
|
@ -37,15 +37,22 @@ export const makeInfiniteScrollGetRows =
|
||||
startRow += newRows.current;
|
||||
endRow += newRows.current;
|
||||
try {
|
||||
if (data.current && data.current.indexOf(null) < endRow) {
|
||||
if (data.current) {
|
||||
const firstMissingRowIndex = data.current.indexOf(null);
|
||||
if (
|
||||
endRow > data.current.length ||
|
||||
(firstMissingRowIndex !== -1 && firstMissingRowIndex < endRow)
|
||||
) {
|
||||
await load();
|
||||
}
|
||||
}
|
||||
const rowsThisBlock = data.current
|
||||
? data.current.slice(startRow, endRow).map((edge) => edge?.node)
|
||||
: [];
|
||||
successCallback(
|
||||
rowsThisBlock,
|
||||
getLastRow(startRow, endRow, rowsThisBlock.length, totalCount.current)
|
||||
getLastRow(startRow, endRow, rowsThisBlock.length, totalCount.current) -
|
||||
newRows.current
|
||||
);
|
||||
} catch (e) {
|
||||
failCallback();
|
||||
|
@ -55,6 +55,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
if (dataRef.current?.length) {
|
||||
if (!scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
@ -66,6 +67,9 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
||||
dataRef.current = data;
|
||||
gridRef.current.api.refreshInfiniteCache();
|
||||
return true;
|
||||
}
|
||||
dataRef.current = data;
|
||||
return false;
|
||||
},
|
||||
[]
|
||||
);
|
||||
@ -115,7 +119,8 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<TradesTable
|
||||
ref={gridRef}
|
||||
rowModelType="infinite"
|
||||
rowModelType={data?.length ? 'infinite' : 'clientSide'}
|
||||
rowData={data?.length ? undefined : []}
|
||||
datasource={{ getRows }}
|
||||
onBodyScrollEnd={onBodyScrollEnd}
|
||||
onBodyScroll={onBodyScroll}
|
||||
|
Loading…
Reference in New Issue
Block a user