[#128] Add variales handling in data provider
This commit is contained in:
parent
aec5d54820
commit
e3a1142579
@ -1,7 +1,6 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { produce } from 'immer';
|
||||
import merge from 'lodash/merge';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AsyncRenderer } from '../../components/async-renderer';
|
||||
import { MarketListTable, getRowNodeId } from '@vegaprotocol/market-list';
|
||||
@ -10,60 +9,51 @@ import {
|
||||
Markets_markets_data,
|
||||
marketsDataProvider,
|
||||
} from '@vegaprotocol/graphql';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
const Markets = () => {
|
||||
const { pathname, push } = useRouter();
|
||||
const [markets, setMarkets] = useState<Markets_markets[] | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<Error>();
|
||||
const client = useApolloClient();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const initialized = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
return marketsDataProvider(client, ({ data, error, loading, delta }) => {
|
||||
setError(error);
|
||||
setLoading(loading);
|
||||
if (!error && !loading) {
|
||||
if (!initialized.current || !gridRef.current) {
|
||||
initialized.current = true;
|
||||
setMarkets(data);
|
||||
} else {
|
||||
const update: Markets_markets[] = [];
|
||||
const add: Markets_markets[] = [];
|
||||
|
||||
// split into updates and adds
|
||||
if (!gridRef.current) return;
|
||||
const rowNode = gridRef.current.api.getRowNode(
|
||||
getRowNodeId(delta.market)
|
||||
);
|
||||
|
||||
if (rowNode) {
|
||||
const updatedData = produce(
|
||||
rowNode.data.data,
|
||||
(draft: Markets_markets_data) => merge(draft, delta)
|
||||
);
|
||||
if (updatedData !== rowNode.data.data) {
|
||||
update.push({ ...rowNode.data, data: delta });
|
||||
}
|
||||
}
|
||||
// @TODO - else add new market
|
||||
if (update.length || add.length) {
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
const gridRef = useRef<AgGridReact>();
|
||||
const update = useCallback(
|
||||
(delta: Markets_markets_data) => {
|
||||
const update: Markets_markets[] = [];
|
||||
const add: Markets_markets[] = [];
|
||||
if (!gridRef.current) {
|
||||
return false;
|
||||
}
|
||||
const rowNode = gridRef.current.api.getRowNode(
|
||||
getRowNodeId(delta.market)
|
||||
);
|
||||
if (rowNode) {
|
||||
const updatedData = produce<Markets_markets_data>(
|
||||
rowNode.data.data,
|
||||
(draft: Markets_markets_data) => merge(draft, delta)
|
||||
);
|
||||
if (updatedData !== rowNode.data.data) {
|
||||
update.push({ ...rowNode.data, data: updatedData });
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [client, initialized]);
|
||||
// @TODO - else add new market
|
||||
if (update.length || add.length) {
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
const { data, error, loading } = useDataProvider<
|
||||
Markets_markets,
|
||||
Markets_markets_data
|
||||
>(marketsDataProvider, update);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={markets}>
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
{(data) => (
|
||||
<MarketListTable
|
||||
ref={gridRef}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { useRef, useCallback, useMemo } from 'react';
|
||||
import { produce } from 'immer';
|
||||
import assign from 'assign-deep';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -10,12 +10,15 @@ import {
|
||||
positionSubscribe_positions,
|
||||
positionsDataProvider,
|
||||
} from '@vegaprotocol/graphql';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
export const Positions = () => {
|
||||
const { pathname, push } = useRouter();
|
||||
const gridRef = useRef<AgGridReact>();
|
||||
const { keypair } = useVegaWallet();
|
||||
const variables = useMemo(() => ({ partyId: keypair.pub }), [keypair]);
|
||||
const update = useCallback(
|
||||
(delta: positionSubscribe_positions) => {
|
||||
const update: positions_party_positions[] = [];
|
||||
@ -25,12 +28,14 @@ export const Positions = () => {
|
||||
}
|
||||
const rowNode = gridRef.current.api.getRowNode(getRowNodeId(delta));
|
||||
if (rowNode) {
|
||||
const updatedData = produce(
|
||||
const updatedData = produce<positions_party_positions>(
|
||||
rowNode.data,
|
||||
(draft: positions_party_positions) => assign(draft, delta)
|
||||
(draft: positions_party_positions) => {
|
||||
assign(draft, delta);
|
||||
}
|
||||
);
|
||||
if (updatedData !== rowNode.data) {
|
||||
update.push(delta);
|
||||
update.push(updatedData);
|
||||
}
|
||||
} else {
|
||||
add.push(delta);
|
||||
@ -49,7 +54,7 @@ export const Positions = () => {
|
||||
const { data, error, loading } = useDataProvider<
|
||||
positions_party_positions,
|
||||
positionSubscribe_positions
|
||||
>(positionsDataProvider, update);
|
||||
>(positionsDataProvider, update, variables);
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
{(data) => (
|
||||
|
@ -4,32 +4,60 @@ import type {
|
||||
ApolloClient,
|
||||
DocumentNode,
|
||||
TypedDocumentNode,
|
||||
OperationVariables,
|
||||
} from '@apollo/client';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
query: DocumentNode | TypedDocumentNode<QueryData, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
subscriptionQuery: DocumentNode | TypedDocumentNode<SubscriptionData, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
update: (draft: Draft<Data>[], delta: Delta) => void,
|
||||
getData: (subscriptionData: QueryData) => Data[] | null,
|
||||
getDelta: (subscriptionData: SubscriptionData) => Delta
|
||||
) {
|
||||
type C = (arg: {
|
||||
export interface UpdateCallback<Data, Delta> {
|
||||
(arg: {
|
||||
data: Data[] | null;
|
||||
error?: Error;
|
||||
loading: boolean;
|
||||
delta?: Delta;
|
||||
}) => void;
|
||||
const callbacks: C[] = [];
|
||||
}): void;
|
||||
}
|
||||
export interface Subscribe<Data, Delta> {
|
||||
(
|
||||
callback: UpdateCallback<Data, Delta>,
|
||||
client: ApolloClient<object>,
|
||||
variables?: OperationVariables
|
||||
): void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
|
||||
|
||||
interface Update<Data, Delta> {
|
||||
(draft: Draft<Data>[], delta: Delta): void;
|
||||
}
|
||||
|
||||
interface GetData<QueryData, Data> {
|
||||
(subscriptionData: QueryData): Data[] | null;
|
||||
}
|
||||
|
||||
interface GetDelta<SubscriptionData, Delta> {
|
||||
(subscriptionData: SubscriptionData): Delta;
|
||||
}
|
||||
|
||||
function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
query: Query<QueryData>,
|
||||
subscriptionQuery: Query<SubscriptionData>,
|
||||
update: Update<Data, Delta>,
|
||||
getData: GetData<QueryData, Data>,
|
||||
getDelta: GetDelta<SubscriptionData, Delta>
|
||||
): Subscribe<Data, Delta> {
|
||||
const callbacks: UpdateCallback<Data, Delta>[] = [];
|
||||
const updateQueue: Delta[] = [];
|
||||
|
||||
let variables: OperationVariables | undefined = undefined;
|
||||
let data: Data[] | null = null;
|
||||
let error: Error | undefined = undefined;
|
||||
let loading = false;
|
||||
let client: ApolloClient<object> | undefined = undefined;
|
||||
let subscription: Subscription | undefined = undefined;
|
||||
|
||||
const notify = (callback: C, delta?: Delta) => {
|
||||
const notify = (callback: UpdateCallback<Data, Delta>, delta?: Delta) => {
|
||||
callback({
|
||||
data,
|
||||
error,
|
||||
@ -55,6 +83,7 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
subscription = client
|
||||
.subscribe<SubscriptionData>({
|
||||
query: subscriptionQuery,
|
||||
variables,
|
||||
})
|
||||
.subscribe(({ data: subscriptionData }) => {
|
||||
if (!subscriptionData) {
|
||||
@ -75,7 +104,7 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
}
|
||||
});
|
||||
try {
|
||||
const res = await client.query<QueryData>({ query });
|
||||
const res = await client.query<QueryData>({ query, variables });
|
||||
data = getData(res.data);
|
||||
if (data && updateQueue && updateQueue.length > 0) {
|
||||
data = produce(data, (draft) => {
|
||||
@ -97,7 +126,7 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
}
|
||||
};
|
||||
|
||||
const unsubscribe = (callback: C) => {
|
||||
const unsubscribe = (callback: UpdateCallback<Data, Delta>) => {
|
||||
callbacks.splice(callbacks.indexOf(callback), 1);
|
||||
if (callbacks.length === 0) {
|
||||
if (subscription) {
|
||||
@ -110,12 +139,11 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
}
|
||||
};
|
||||
|
||||
return (c: ApolloClient<object>, callback: C) => {
|
||||
if (!client) {
|
||||
client = c;
|
||||
}
|
||||
return (callback, c, v) => {
|
||||
callbacks.push(callback);
|
||||
if (callbacks.length === 1) {
|
||||
client = c;
|
||||
variables = v;
|
||||
initialize();
|
||||
} else {
|
||||
notify(callback);
|
||||
@ -123,3 +151,41 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
return () => unsubscribe(callback);
|
||||
};
|
||||
}
|
||||
|
||||
const memoize = <Data, Delta>(
|
||||
fn: (variables?: OperationVariables) => Subscribe<Data, Delta>
|
||||
) => {
|
||||
const cache: {
|
||||
subscribe: Subscribe<Data, Delta>;
|
||||
variables?: OperationVariables;
|
||||
}[] = [];
|
||||
return (variables?: OperationVariables) => {
|
||||
const cached = cache.find((c) => isEqual(c.variables, variables));
|
||||
if (cached) {
|
||||
return cached.subscribe;
|
||||
}
|
||||
const subscribe = fn(variables);
|
||||
cache.push({ subscribe, variables });
|
||||
return subscribe;
|
||||
};
|
||||
};
|
||||
|
||||
export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||
query: Query<QueryData>,
|
||||
subscriptionQuery: Query<SubscriptionData>,
|
||||
update: Update<Data, Delta>,
|
||||
getData: GetData<QueryData, Data>,
|
||||
getDelta: GetDelta<SubscriptionData, Delta>
|
||||
): Subscribe<Data, Delta> {
|
||||
const getInstance = memoize<Data, Delta>((variables) =>
|
||||
makeDataProviderInternal(
|
||||
query,
|
||||
subscriptionQuery,
|
||||
update,
|
||||
getData,
|
||||
getDelta
|
||||
)
|
||||
);
|
||||
return (callback, client, variables) =>
|
||||
getInstance(variables)(callback, client, variables);
|
||||
}
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './markets-data-provider';
|
||||
export * from './positions-data-provider';
|
||||
export type { Subscribe } from './generic-data-provider';
|
||||
|
@ -55,22 +55,21 @@ const MARKET_DATA_SUB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const update = (draft: Markets_markets[], delta: MarketDataSub_marketData) => {
|
||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index].data = delta;
|
||||
}
|
||||
// @TODO - else push new market to draft
|
||||
};
|
||||
const getData = (responseData: Markets): Markets_markets[] | null =>
|
||||
responseData.markets;
|
||||
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
|
||||
subscriptionData.marketData;
|
||||
|
||||
export const marketsDataProvider = makeDataProvider<
|
||||
Markets,
|
||||
Markets_markets,
|
||||
MarketDataSub,
|
||||
MarketDataSub_marketData
|
||||
>(
|
||||
MARKETS_QUERY,
|
||||
MARKET_DATA_SUB,
|
||||
(draft: Markets_markets[], delta: MarketDataSub_marketData) => {
|
||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index].data = delta;
|
||||
}
|
||||
// @TODO - else push new market to draft
|
||||
},
|
||||
(responseData: Markets): Markets_markets[] | null => responseData.markets,
|
||||
(subscriptionData: MarketDataSub): MarketDataSub_marketData =>
|
||||
subscriptionData.marketData
|
||||
);
|
||||
>(MARKETS_QUERY, MARKET_DATA_SUB, update, getData, getDelta);
|
||||
|
@ -69,24 +69,26 @@ export const POSITIONS_SUB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const update = (
|
||||
draft: positions_party_positions[],
|
||||
delta: positionSubscribe_positions
|
||||
) => {
|
||||
const index = draft.findIndex((m) => m.market.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index] = delta;
|
||||
} else {
|
||||
draft.push(delta);
|
||||
}
|
||||
};
|
||||
const getData = (responseData: positions): positions_party_positions[] | null =>
|
||||
responseData.party ? responseData.party.positions : null;
|
||||
const getDelta = (
|
||||
subscriptionData: positionSubscribe
|
||||
): positionSubscribe_positions => subscriptionData.positions;
|
||||
|
||||
export const positionsDataProvider = makeDataProvider<
|
||||
positions,
|
||||
positions_party_positions,
|
||||
positionSubscribe,
|
||||
positionSubscribe_positions
|
||||
>(
|
||||
POSITION_QUERY,
|
||||
POSITIONS_SUB,
|
||||
(draft: positions_party_positions[], delta: positionSubscribe_positions) => {
|
||||
const index = draft.findIndex((m) => m.market.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index] = delta;
|
||||
} else {
|
||||
draft.push(delta);
|
||||
}
|
||||
},
|
||||
(responseData: positions): positions_party_positions[] | null =>
|
||||
responseData.party ? responseData.party.positions : null,
|
||||
(subscriptionData: positionSubscribe): positionSubscribe_positions =>
|
||||
subscriptionData.positions
|
||||
);
|
||||
>(POSITION_QUERY, POSITIONS_SUB, update, getData, getDelta);
|
||||
|
@ -1,26 +1,20 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import type { ApolloClient } from '@apollo/client';
|
||||
import type { OperationVariables } from '@apollo/client';
|
||||
import type { Subscribe } from '@vegaprotocol/graphql';
|
||||
|
||||
export function useDataProvider<Data, Delta>(
|
||||
dataProvider: (
|
||||
client: ApolloClient<object>,
|
||||
callback: (arg: {
|
||||
data: Data[] | null;
|
||||
error?: Error;
|
||||
loading: boolean;
|
||||
delta?: Delta;
|
||||
}) => void
|
||||
) => () => void,
|
||||
update: (delta: Delta) => boolean = () => false
|
||||
dataProvider: Subscribe<Data, Delta>,
|
||||
update: (delta: Delta) => boolean = () => false,
|
||||
variables?: OperationVariables
|
||||
) {
|
||||
const client = useApolloClient();
|
||||
const [data, setData] = useState<Data[] | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
const initialized = useRef<boolean>(false);
|
||||
useEffect(() => {
|
||||
return dataProvider(client, ({ data, error, loading, delta }) => {
|
||||
const callback = useCallback(
|
||||
({ data, error, loading, delta }) => {
|
||||
setError(error);
|
||||
setLoading(loading);
|
||||
if (!error && !loading) {
|
||||
@ -29,7 +23,11 @@ export function useDataProvider<Data, Delta>(
|
||||
setData(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [client, initialized, dataProvider, update]);
|
||||
},
|
||||
[update]
|
||||
);
|
||||
useEffect(() => {
|
||||
return dataProvider(callback, client, variables);
|
||||
}, [client, initialized, dataProvider, callback, variables]);
|
||||
return { data, loading, error };
|
||||
}
|
||||
|
@ -5,16 +5,16 @@ import * as React from 'react';
|
||||
import { PriceCell } from './price-cell';
|
||||
|
||||
describe('<PriceCell />', () => {
|
||||
it('Displayes formatted value', () => {
|
||||
it('Displays formatted value', () => {
|
||||
render(<PriceCell value={100} valueFormatted="100.00" />);
|
||||
expect(screen.getByTestId('price')).toHaveTextContent('100.00');
|
||||
});
|
||||
it('Displayes 0', () => {
|
||||
it('Displays 0', () => {
|
||||
render(<PriceCell value={0} valueFormatted="0.00" />);
|
||||
expect(screen.getByTestId('price')).toHaveTextContent('0.00');
|
||||
});
|
||||
|
||||
it('Displayes - if value is not a number', () => {
|
||||
it('Displays - if value is not a number', () => {
|
||||
render(<PriceCell value={null} valueFormatted="" />);
|
||||
expect(screen.getByTestId('price')).toHaveTextContent('-');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user