chore: handle not found errors as correct response (#2759)

This commit is contained in:
Bartłomiej Głownia 2023-01-28 03:35:45 +01:00 committed by GitHub
parent 05559f3ea0
commit 00e319b3c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 271 additions and 532 deletions

View File

@ -5,10 +5,12 @@ import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import {
useWithdrawals,
withdrawalProvider,
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '../index';
@ -29,7 +31,12 @@ const Withdrawals = ({ name }: RouteChildProps) => {
const WithdrawPendingContainer = () => {
const openWithdrawalDialog = useWithdrawalDialog((state) => state.open);
const { t } = useTranslation();
const { data, loading, error } = useWithdrawals();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
if (error) {
return (

View File

@ -1,22 +1,28 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { useDeposits } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/react-helpers';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const DepositsContainer = () => {
const { deposits, loading, error } = useDeposits();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const openDepositDialog = useDepositDialog((state) => state.open);
return (
<div className="h-full grid grid-rows-[1fr,min-content]">
<div className="h-full relative">
<DepositsTable
rowData={deposits || []}
rowData={data || []}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={deposits}
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}

View File

@ -1,14 +1,20 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import {
useWithdrawals,
withdrawalProvider,
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { t } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
export const WithdrawalsContainer = () => {
const { data, loading, error } = useWithdrawals();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
return (

View File

@ -50,10 +50,10 @@ export type Account = Omit<AccountFieldsFragment, 'market' | 'asset'> & {
};
const update = (
data: AccountFieldsFragment[],
data: AccountFieldsFragment[] | null,
deltas: AccountEventsSubscription['accounts']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas.forEach((delta) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
@ -73,15 +73,8 @@ const update = (
});
};
const getData = (
responseData: AccountsQuery
): AccountFieldsFragment[] | null => {
return (
removePaginationWrapper(responseData.party?.accountsConnection?.edges) ??
null
);
};
const getData = (responseData: AccountsQuery | null): AccountFieldsFragment[] =>
removePaginationWrapper(responseData?.party?.accountsConnection?.edges) || [];
const getDelta = (
subscriptionData: AccountEventsSubscription
): AccountEventsSubscription['accounts'] => {

View File

@ -1,4 +1,4 @@
import type { ApolloError, InMemoryCacheConfig } from '@apollo/client';
import type { InMemoryCacheConfig } from '@apollo/client';
import {
ApolloClient,
from,
@ -13,7 +13,6 @@ import { createClient as createWSClient } from 'graphql-ws';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import ApolloLinkTimeout from 'apollo-link-timeout';
import type { GraphQLErrors } from '@apollo/client/errors';
import { localLoggerFactory } from '@vegaprotocol/react-helpers';
const isBrowser = typeof window !== 'undefined';
@ -110,21 +109,3 @@ export function createClient({
connectToDevTools,
});
}
const isApolloGraphQLError = (
error: ApolloError | Error | undefined
): error is ApolloError => {
return !!error && !!(error as ApolloError).graphQLErrors;
};
const hasNotFoundGraphQLErrors = (errors: GraphQLErrors) => {
return errors.some((e) => e.extensions && e.extensions['type'] === NOT_FOUND);
};
export const isNotFoundGraphQLError = (
error: Error | ApolloError | undefined
) => {
return (
isApolloGraphQLError(error) && hasNotFoundGraphQLErrors(error.graphQLErrors)
);
};

View File

@ -6,20 +6,15 @@ import { AssetDocument } from './__generated__/Asset';
export type Asset = AssetFieldsFragment;
const getData = (responseData: AssetQuery) => {
const foundAssets = responseData.assetsConnection?.edges
const getData = (responseData: AssetQuery | null) => {
const foundAssets = responseData?.assetsConnection?.edges
?.filter((e) => Boolean(e?.node))
.map((e) => e?.node as Asset);
if (foundAssets && foundAssets?.length > 0) return foundAssets[0];
return null;
};
export const assetProvider = makeDataProvider<
AssetQuery,
Asset | null,
never,
never
>({
export const assetProvider = makeDataProvider<AssetQuery, Asset, never, never>({
query: AssetDocument,
getData,
});

View File

@ -16,14 +16,14 @@ export type BuiltinAsset = Omit<Asset, 'source'> & {
source: BuiltinAssetSource;
};
const getData = (responseData: AssetsQuery) =>
responseData.assetsConnection?.edges
const getData = (responseData: AssetsQuery | null) =>
responseData?.assetsConnection?.edges
?.filter((e) => Boolean(e?.node))
.map((e) => e?.node as Asset) ?? [];
export const assetsProvider = makeDataProvider<
AssetsQuery,
Asset[] | null,
Asset[],
never,
never
>({

View File

@ -0,0 +1,49 @@
import uniqBy from 'lodash/uniqBy';
import orderBy from 'lodash/orderBy';
import {
getEvents,
makeDataProvider,
removePaginationWrapper,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
DepositsDocument,
DepositEventDocument,
} from './__generated__/Deposit';
import type {
DepositFieldsFragment,
DepositsQuery,
DepositEventSubscription,
DepositEventSubscriptionVariables,
} from './__generated__/Deposit';
export const depositsProvider = makeDataProvider<
DepositsQuery,
DepositFieldsFragment[],
DepositEventSubscription,
DepositEventSubscription,
DepositEventSubscriptionVariables
>({
query: DepositsDocument,
subscriptionQuery: DepositEventDocument,
getData: (data: DepositsQuery | null) =>
orderBy(
removePaginationWrapper(data?.party?.depositsConnection?.edges || []),
['createdTimestamp'],
['desc']
),
getDelta: (data: DepositEventSubscription) => data,
update: (
data: DepositFieldsFragment[] | null,
delta: DepositEventSubscription
) => {
if (!delta.busEvents?.length) {
return data;
}
const incoming = getEvents<DepositFieldsFragment>(
Schema.BusEventType.Deposit,
delta.busEvents
);
return uniqBy([...incoming, ...(data || [])], 'id');
},
});

View File

@ -5,7 +5,7 @@ export * from './deposit-limits';
export * from './deposit-manager';
export * from './deposits-table';
export * from './use-deposit-balances';
export * from './use-deposits';
export * from './deposits-provider';
export * from './use-get-allowance';
export * from './use-get-balance-of-erc20-token';
export * from './use-get-deposit-maximum';

View File

@ -1,100 +0,0 @@
import uniqBy from 'lodash/uniqBy';
import orderBy from 'lodash/orderBy';
import { getNodes, getEvents } from '@vegaprotocol/react-helpers';
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEffect, useMemo } from 'react';
import * as Schema from '@vegaprotocol/types';
import {
useDepositsQuery,
DepositEventDocument,
} from './__generated__/Deposit';
import type {
DepositFieldsFragment,
DepositsQuery,
DepositEventSubscription,
DepositEventSubscriptionVariables,
} from './__generated__/Deposit';
export const useDeposits = () => {
const { pubKey } = useVegaWallet();
const { data, loading, error, subscribeToMore } = useDepositsQuery({
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const deposits = useMemo(() => {
if (!data?.party?.depositsConnection?.edges?.length) {
return [];
}
return orderBy(
getNodes<DepositFieldsFragment>(data.party?.depositsConnection),
['createdTimestamp'],
['desc']
);
}, [data]);
useEffect(() => {
if (!pubKey) return;
const unsub = subscribeToMore<
DepositEventSubscription,
DepositEventSubscriptionVariables
>({
document: DepositEventDocument,
variables: { partyId: pubKey },
updateQuery,
});
return () => {
unsub();
};
}, [pubKey, subscribeToMore]);
return { data, loading, error, deposits };
};
const updateQuery: UpdateQueryFn<
DepositsQuery,
DepositEventSubscriptionVariables,
DepositEventSubscription
> = (prev, { subscriptionData, variables }) => {
if (!subscriptionData.data.busEvents?.length || !variables?.partyId) {
return prev;
}
const curr = getNodes<DepositFieldsFragment>(prev.party?.depositsConnection);
const incoming = getEvents<DepositFieldsFragment>(
Schema.BusEventType.Deposit,
subscriptionData.data.busEvents
);
const deposits = uniqBy([...incoming, ...curr], 'id');
if (!prev.party) {
return {
...prev,
party: {
__typename: 'Party',
id: variables?.partyId,
depositsConnection: {
__typename: 'DepositsConnection',
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
},
},
};
}
return {
...prev,
party: {
...prev.party,
id: variables?.partyId,
depositsConnection: {
__typename: 'DepositsConnection',
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
},
},
};
};

View File

@ -59,8 +59,8 @@ const update = (
export type Trade = Omit<FillFieldsFragment, 'market'> & { market?: Market };
export type TradeEdge = Edge<Trade>;
const getData = (responseData: FillsQuery): FillEdgeFragment[] =>
responseData.party?.tradesConnection?.edges || [];
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
responseData?.party?.tradesConnection?.edges || [];
const getPageInfo = (responseData: FillsQuery): PageInfo | null =>
responseData.party?.tradesConnection?.pageInfo || null;

View File

@ -5,8 +5,8 @@ import type {
} from './__generated__/Proposals';
import { ProposalsListDocument } from './__generated__/Proposals';
const getData = (responseData: ProposalsListQuery) =>
responseData.proposalsConnection?.edges
const getData = (responseData: ProposalsListQuery | null) =>
responseData?.proposalsConnection?.edges
?.filter((edge) => Boolean(edge?.node))
.map((edge) => edge?.node as ProposalListFieldsFragment) || null;

View File

@ -35,12 +35,12 @@ export type LedgerEntry = LedgerEntryFragment & {
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
const getData = (responseData: LedgerEntriesQuery) => {
return responseData.ledgerEntries?.edges || [];
const getData = (responseData: LedgerEntriesQuery | null) => {
return responseData?.ledgerEntries?.edges || [];
};
export const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getData>,
reload: () => void,
variables?: LedgerEntriesQueryVariables

View File

@ -35,10 +35,10 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
query: LiquidityProvisionsDocument,
subscriptionQuery: LiquidityProvisionsUpdateDocument,
update: (
data: LiquidityProvisionFieldsFragment[],
data: LiquidityProvisionFieldsFragment[] | null,
deltas: LiquidityProvisionsUpdateSubscription['liquidityProvisions']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
@ -63,9 +63,9 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
});
});
},
getData: (responseData: LiquidityProvisionsQuery) => {
getData: (responseData: LiquidityProvisionsQuery | null) => {
return (
responseData.market?.liquidityProvisionsConnection?.edges?.map(
responseData?.market?.liquidityProvisionsConnection?.edges?.map(
(e) => e?.node
) ?? []
).filter((e) => !!e) as LiquidityProvisionFieldsFragment[];
@ -105,7 +105,7 @@ export const marketLiquidityDataProvider = makeDataProvider<
never
>({
query: MarketLpDocument,
getData: (responseData: MarketLpQuery) => {
getData: (responseData: MarketLpQuery | null) => {
return responseData;
},
});
@ -119,10 +119,10 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
query: LiquidityProviderFeeShareDocument,
subscriptionQuery: LiquidityProviderFeeShareUpdateDocument,
update: (
data: LiquidityProviderFeeShareFieldsFragment[],
data: LiquidityProviderFeeShareFieldsFragment[] | null,
deltas: LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = delta.partyId;
const index = draft.findIndex((a) => a.party.id === id);
@ -143,7 +143,7 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
});
},
getData: (data) => {
return data.market?.data?.liquidityProviderFeeShare || [];
return data?.market?.data?.liquidityProviderFeeShare || [];
},
getDelta: (subscriptionData: LiquidityProviderFeeShareUpdateSubscription) => {
return subscriptionData.marketsData[0].liquidityProviderFeeShare;

View File

@ -58,7 +58,7 @@ export interface Markets {
}
const getData = (
responseData: LiquidityProvisionMarketsQuery
responseData: LiquidityProvisionMarketsQuery | null
): LiquidityProvisionMarket[] | null => {
return (
responseData?.marketsConnection?.edges.map((edge) => {

View File

@ -53,7 +53,7 @@ export const update: Update<
return data;
};
const getData = (responseData: MarketDepthQuery) => responseData.market;
const getData = (responseData: MarketDepthQuery | null) => responseData?.market;
const getDelta = (subscriptionData: MarketDepthUpdateSubscription) =>
subscriptionData.marketsDepthUpdate;

View File

@ -9,5 +9,5 @@ export const marketInfoDataProvider = makeDataProvider<
never
>({
query: MarketInfoDocument,
getData: (responseData: MarketInfoQuery) => responseData,
getData: (responseData: MarketInfoQuery | null) => responseData,
});

View File

@ -24,7 +24,7 @@ export const update = (data: Candle[] | null, delta: Candle) => {
return [delta];
};
const getData = (responseData: MarketCandlesQuery): Candle[] | null =>
const getData = (responseData: MarketCandlesQuery | null): Candle[] | null =>
responseData?.marketsConnection?.edges[0]?.node.candlesConnection?.edges
?.filter((edge) => edge?.node)
.map((edge) => edge?.node as Candle) || null;

View File

@ -18,14 +18,20 @@ import type {
export type MarketData = MarketDataFieldsFragment;
const update = (data: MarketData, delta: MarketDataUpdateFieldsFragment) => {
return produce(data, (draft) => {
const { marketId, __typename, ...marketData } = delta;
Object.assign(draft, marketData);
});
const update = (
data: MarketData | null,
delta: MarketDataUpdateFieldsFragment
) => {
return (
data &&
produce(data, (draft) => {
const { marketId, __typename, ...marketData } = delta;
Object.assign(draft, marketData);
})
);
};
const getData = (responseData: MarketDataQuery): MarketData | null =>
const getData = (responseData: MarketDataQuery | null): MarketData | null =>
responseData?.marketsConnection?.edges[0].node.data || null;
const getDelta = (

View File

@ -11,7 +11,7 @@ import type { MarketData } from './market-data-provider';
import { marketDataProvider } from './market-data-provider';
const getData = (
responseData: MarketQuery
responseData: MarketQuery | null
): SingleMarketFieldsFragment | null => responseData?.market || null;
export const marketProvider = makeDataProvider<

View File

@ -8,7 +8,9 @@ export interface MarketCandles {
candles: Candle[] | undefined;
}
const getData = (responseData: MarketsCandlesQuery): MarketCandles[] | null =>
const getData = (
responseData: MarketsCandlesQuery | null
): MarketCandles[] | null =>
responseData?.marketsConnection?.edges.map((edge) => ({
marketId: edge.node.id,
candles: edge.node.candlesConnection?.edges

View File

@ -3,8 +3,8 @@ import type { MarketsDataQuery } from './__generated__/markets-data';
import { MarketsDataDocument } from './__generated__/markets-data';
import type { MarketData } from './market-data-provider';
const getData = (responseData: MarketsDataQuery): MarketData[] | null =>
responseData.marketsConnection?.edges
const getData = (responseData: MarketsDataQuery | null): MarketData[] | null =>
responseData?.marketsConnection?.edges
.filter((edge) => edge.node.data)
.map((edge) => edge.node.data as MarketData) || null;

View File

@ -21,7 +21,7 @@ import type { Candle } from './market-candles-provider';
export type Market = MarketFieldsFragment;
const getData = (responseData: MarketsQuery): Market[] | null =>
const getData = (responseData: MarketsQuery | null): Market[] | null =>
responseData?.marketsConnection?.edges.map((edge) => edge.node) || null;
export const marketsProvider = makeDataProvider<

View File

@ -62,7 +62,7 @@ describe('order data provider', () => {
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
delta[4].updatedAt
);
expect(update([], delta, () => null, { partyId: '0x123' }).length).toEqual(
expect(update([], delta, () => null, { partyId: '0x123' })?.length).toEqual(
4
);
});

View File

@ -71,7 +71,7 @@ const orderMatchFilters = (
return true;
};
const getData = (responseData: OrdersQuery) =>
const getData = (responseData: OrdersQuery | null) =>
responseData?.party?.ordersConnection?.edges || [];
const getDelta = (subscriptionData: OrdersUpdateSubscription) =>
@ -81,7 +81,7 @@ const getPageInfo = (responseData: OrdersQuery): PageInfo | null =>
responseData.party?.ordersConnection?.pageInfo || null;
export const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getDelta>,
reload: () => void,
variables?: OrdersQueryVariables

View File

@ -14,10 +14,10 @@ import type {
} from './__generated__/Positions';
const update = (
data: MarginFieldsFragment[],
data: MarginFieldsFragment[] | null,
delta: MarginsSubscriptionSubscription['margins']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
const { marketId } = delta;
const index = draft.findIndex((node) => node.market.id === marketId);
if (index !== -1) {
@ -49,8 +49,9 @@ const update = (
});
};
const getData = (responseData: MarginsQuery) =>
removePaginationWrapper(responseData.party?.marginsConnection?.edges) || [];
const getData = (responseData: MarginsQuery | null) =>
removePaginationWrapper(responseData?.party?.marginsConnection?.edges) || [];
const getDelta = (subscriptionData: MarginsSubscriptionSubscription) =>
subscriptionData.margins;

View File

@ -4,8 +4,8 @@ import BigNumber from 'bignumber.js';
import sortBy from 'lodash/sortBy';
import type { Account } from '@vegaprotocol/accounts';
import { accountsDataProvider } from '@vegaprotocol/accounts';
import { toBigNum } from '@vegaprotocol/react-helpers';
import {
toBigNum,
makeDataProvider,
makeDerivedDataProvider,
removePaginationWrapper,
@ -171,10 +171,10 @@ export const getMetrics = (
};
export const update = (
data: PositionFieldsFragment[],
data: PositionFieldsFragment[] | null,
deltas: PositionsSubscriptionSubscription['positions']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas.forEach((delta) => {
const index = draft.findIndex(
(node) => node.market.id === delta.marketId
@ -212,8 +212,8 @@ export const positionsDataProvider = makeDataProvider<
query: PositionsDocument,
subscriptionQuery: PositionsSubscriptionDocument,
update,
getData: (responseData: PositionsQuery) =>
removePaginationWrapper(responseData.party?.positionsConnection?.edges) ||
getData: (responseData: PositionsQuery | null) =>
removePaginationWrapper(responseData?.party?.positionsConnection?.edges) ||
[],
getDelta: (subscriptionData: PositionsSubscriptionSubscription) =>
subscriptionData.positions,

View File

@ -2,10 +2,7 @@ import { useCallback, useState } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { positionsDataProvider } from './positions-data-providers';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import type {
PositionFieldsFragment,
PositionsSubscriptionSubscription,
} from './__generated__/Positions';
import type { PositionFieldsFragment } from './__generated__/Positions';
export const useMarketPositionOpenVolume = (marketId: string) => {
const { pubKey } = useVegaWallet();
@ -21,10 +18,7 @@ export const useMarketPositionOpenVolume = (marketId: string) => {
[setOpenVolume, marketId]
);
useDataProvider<
PositionFieldsFragment[],
PositionsSubscriptionSubscription['positions']
>({
useDataProvider({
dataProvider: positionsDataProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey || !marketId,

View File

@ -1,20 +1,2 @@
export * from './hooks';
export * from './lib/format';
export * from './lib/generic-data-provider';
export * from './lib/get-nodes';
export * from './lib/get-events';
export * from './lib/grid';
export * from './lib/i18n';
export * from './lib/pagination';
export * from './lib/ag-grid-update';
export * from './lib/remove-0x';
export * from './lib/storage';
export * from './lib/time';
export * from './lib/validate';
export * from './lib/links';
export * from './lib/is-asset-erc20';
export * from './lib/remove-pagination-wrapper';
export * from './lib/__generated__/ChainId';
export * from './lib/data-grid';
export * from './lib/local-logger';
export * from './lib/market-expires';
export * from './lib';

View File

@ -0,0 +1,29 @@
import type { ApolloError } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
const NOT_FOUND = 'NotFound';
const isApolloGraphQLError = (
error: ApolloError | Error | undefined
): error is ApolloError => {
return !!error && !!(error as ApolloError).graphQLErrors;
};
const hasNotFoundGraphQLErrors = (errors: GraphQLErrors, path?: string) => {
return errors.some(
(e) =>
e.extensions &&
e.extensions['type'] === NOT_FOUND &&
(!path || e.path?.[0] === path)
);
};
export const isNotFoundGraphQLError = (
error: Error | ApolloError | undefined,
path?: string
) => {
return (
isApolloGraphQLError(error) &&
hasNotFoundGraphQLErrors(error.graphQLErrors, path)
);
};

View File

@ -62,7 +62,7 @@ const subscribe = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
query,
subscriptionQuery,
update,
getData: (r) => r.data,
getData: (r) => r?.data || null,
getDelta: (r) => r.data,
});
@ -91,7 +91,7 @@ const paginatedSubscribe = makeDataProvider<
query,
subscriptionQuery,
update,
getData: (r) => r.data,
getData: (r) => r?.data || null,
getDelta: (r) => r.data,
pagination: {
first,
@ -215,7 +215,7 @@ describe('data provider', () => {
const subscription = subscribe(callback, client);
await resolveQuery({ data });
const delta: Item[] = [];
update.mockImplementationOnce((data, delta) => [...data, ...delta]);
update.mockImplementationOnce((data, delta) => [...(data || []), ...delta]);
// calling onNext from client.subscribe({ query }).subscribe(onNext)
await clientSubscribeSubscribe.mock.calls[
clientSubscribeSubscribe.mock.calls.length - 1
@ -234,7 +234,7 @@ describe('data provider', () => {
const subscription = subscribe(callback, client);
await resolveQuery({ data });
const delta: Item[] = [];
update.mockImplementationOnce((data, delta) => data);
update.mockImplementationOnce((data, delta) => data || []);
const callbackCallsLength = callback.mock.calls.length;
// calling onNext from client.subscribe({ query }).subscribe(onNext)
await clientSubscribeSubscribe.mock.calls[
@ -591,7 +591,7 @@ describe('derived data provider', () => {
await resolveQuery({ data: part2 });
expect(combineData).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
update.mockImplementation((data, delta) => [...data, ...delta]);
update.mockImplementation((data, delta) => [...(data || []), ...delta]);
combineData.mockReturnValueOnce({ ...data });
const combinedDelta = {};
combineDelta.mockReturnValueOnce(combinedDelta);
@ -629,7 +629,7 @@ describe('derived data provider', () => {
await resolveQuery({ data: [] });
expect(combineData).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
update.mockImplementation((data, delta) => [...data, ...delta]);
update.mockImplementation((data, delta) => [...(data || []), ...delta]);
combineData.mockReturnValueOnce({ ...data });
const combinedInsertionData = {};
combineInsertionData.mockReturnValueOnce(combinedInsertionData);

View File

@ -8,6 +8,7 @@ import type {
} from '@apollo/client';
import type { Subscription } from 'zen-observable-ts';
import isEqual from 'lodash/isEqual';
import { isNotFoundGraphQLError } from './apollo-client';
import type * as Schema from '@vegaprotocol/types';
interface UpdateData<Data, Delta> {
delta?: Delta;
@ -71,7 +72,12 @@ export interface Update<
Delta,
Variables extends OperationVariables = OperationVariables
> {
(data: Data, delta: Delta, reload: Reload, variables?: Variables): Data;
(
data: Data | null,
delta: Delta,
reload: Reload,
variables?: Variables
): Data;
}
export interface Append<Data> {
@ -88,7 +94,7 @@ export interface Append<Data> {
}
interface GetData<QueryData, Data, Variables> {
(queryData: QueryData, variables?: Variables): Data | null;
(queryData: QueryData | null, variables?: Variables): Data | null;
}
interface GetPageInfo<QueryData> {
@ -160,7 +166,7 @@ interface DataProviderParams<
> {
query: Query<QueryData>;
subscriptionQuery?: Query<SubscriptionData>;
update?: Update<Data, Delta, Variables>;
update?: Update<Data | null, Delta, Variables>;
getData: GetData<QueryData, Data, Variables>;
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
pagination?: {
@ -349,6 +355,11 @@ function makeDataProviderInternal<
}
loaded = true;
} catch (e) {
if (isNotFoundGraphQLError(e as Error, 'party')) {
data = getData(null, variables);
loaded = true;
return;
}
// if error will occur data provider stops subscription
error = e as Error;
if (subscription) {
@ -384,7 +395,7 @@ function makeDataProviderInternal<
return;
}
const delta = getDelta(subscriptionData, variables);
if (loading || !data) {
if (loading) {
updateQueue.push(delta);
} else {
const updatedData = update(data, delta, reload, variables);
@ -409,7 +420,6 @@ function makeDataProviderInternal<
if (!client) {
return;
}
if (subscriptionQuery && getDelta && update) {
subscription = client
.subscribe<SubscriptionData>({

View File

@ -1,16 +1,20 @@
export * from './format';
export * from './grid';
export * from './storage';
export * from './validate';
export * from './generic-data-provider';
export * from './get-nodes';
export * from './get-events';
export * from './i18n';
export * from './pagination';
export * from './remove-0x';
export * from './time';
export * from './links';
export * from './remove-pagination-wrapper';
export * from './__generated__/ChainId';
export * from './ag-grid-update';
export * from './apollo-client';
export * from './data-grid';
export * from './format';
export * from './generic-data-provider';
export * from './get-events';
export * from './get-nodes';
export * from './grid';
export * from './i18n';
export * from './is-asset-erc20';
export * from './links';
export * from './local-logger';
export * from './market-expires';
export * from './pagination';
export * from './remove-0x';
export * from './remove-pagination-wrapper';
export * from './storage';
export * from './time';
export * from './validate';

View File

@ -20,7 +20,7 @@ import produce from 'immer';
export const MAX_TRADES = 50;
const getData = (
responseData: TradesQuery
responseData: TradesQuery | null
): ({
cursor: string;
node: TradeFieldsFragment;
@ -30,7 +30,7 @@ const getDelta = (subscriptionData: TradesUpdateSubscription) =>
subscriptionData?.trades || [];
const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getDelta>
) => {
return produce(data, (draft) => {

View File

@ -1,7 +1,6 @@
import { Splash } from '../splash';
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { isNotFoundGraphQLError } from '@vegaprotocol/apollo-client';
interface AsyncRendererProps<T> {
loading: boolean;
@ -26,7 +25,7 @@ export function AsyncRenderer<T = object>({
children,
render,
}: AsyncRendererProps<T>) {
if (error && !isNotFoundGraphQLError(error)) {
if (error) {
return (
<Splash>
{errorMessage

View File

@ -8,6 +8,6 @@ export * from './lib/withdrawal-feedback';
export * from './lib/use-complete-withdraw';
export * from './lib/use-create-withdraw';
export * from './lib/use-verify-withdrawal';
export * from './lib/use-withdrawals';
export * from './lib/withdrawals-provider';
export * from './lib/__generated__/Withdrawal';
export * from './lib/__generated__/Erc20Approval';

View File

@ -1,170 +0,0 @@
import * as Schema from '@vegaprotocol/types';
import { generateWithdrawal } from './test-helpers';
import { updateQuery } from './use-withdrawals';
import type {
WithdrawalsQuery,
WithdrawalEventSubscription,
WithdrawalFieldsFragment,
} from './__generated__/Withdrawal';
describe('updateQuery', () => {
it('updates existing withdrawals', () => {
const withdrawal = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_OPEN,
});
const withdrawalUpdate = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_FINALIZED,
});
const prev = mockQuery([withdrawal]);
const incoming = mockSub([withdrawalUpdate]);
expect(updateQuery(prev, incoming)).toEqual({
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
],
},
},
});
});
it('Adds new withdrawals', () => {
const withdrawal = generateWithdrawal({
id: '1',
amount: '100',
});
const withdrawalUpdate = generateWithdrawal({
id: '2',
amount: '200',
});
const prev = mockQuery([withdrawal]);
const incoming = mockSub([withdrawalUpdate]);
expect(updateQuery(prev, incoming)).toEqual({
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
{
node: withdrawal,
},
],
},
},
});
});
it('creates new party if not present', () => {
const partyId = 'party-id';
const withdrawalUpdate = generateWithdrawal({
id: '2',
});
const incoming = mockSub([withdrawalUpdate]);
expect(
updateQuery({ party: null }, { ...incoming, variables: { partyId } })
).toEqual({
party: {
__typename: 'Party',
id: partyId,
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
],
},
},
});
});
it('Handles updates and inserts simultaneously', () => {
const withdrawal1 = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_OPEN,
});
const withdrawal2 = generateWithdrawal({
id: '2',
});
const withdrawalUpdate = generateWithdrawal({
id: '1',
status: Schema.WithdrawalStatus.STATUS_FINALIZED,
});
const withdrawalNew = generateWithdrawal({
id: '3',
});
const prev = mockQuery([withdrawal1, withdrawal2]);
const incoming = mockSub([withdrawalUpdate, withdrawalNew]);
expect(updateQuery(prev, incoming)).toEqual({
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: [
{
node: withdrawalUpdate,
},
{
node: withdrawalNew,
},
{
node: withdrawal2,
},
],
},
},
});
});
});
const mockQuery = (
withdrawals: WithdrawalFieldsFragment[]
): WithdrawalsQuery => {
return {
party: {
__typename: 'Party',
id: 'party-id',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges: withdrawals.map((w) => ({
node: w,
})),
},
},
};
};
const mockSub = (
withdrawals: WithdrawalFieldsFragment[]
): {
subscriptionData: {
data: WithdrawalEventSubscription;
};
} => {
return {
subscriptionData: {
data: {
busEvents: withdrawals.map((w) => ({
__typename: 'BusEvent',
event: w,
})),
},
},
};
};

View File

@ -1,111 +0,0 @@
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
import { useVegaWallet } from '@vegaprotocol/wallet';
import uniqBy from 'lodash/uniqBy';
import { useEffect } from 'react';
import {
useWithdrawalsQuery,
WithdrawalEventDocument,
} from './__generated__/Withdrawal';
import type {
WithdrawalsQuery,
WithdrawalFieldsFragment,
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables,
} from './__generated__/Withdrawal';
import { removePaginationWrapper } from '@vegaprotocol/react-helpers';
type WithdrawalEdges = { node: WithdrawalFieldsFragment }[];
export const useWithdrawals = () => {
const { pubKey } = useVegaWallet();
const { data, loading, error, subscribeToMore } = useWithdrawalsQuery({
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
useEffect(() => {
if (!pubKey) return;
const unsubscribe = subscribeToMore<
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables
>({
document: WithdrawalEventDocument,
variables: { partyId: pubKey },
updateQuery,
});
return () => {
unsubscribe();
};
}, [pubKey, subscribeToMore]);
return {
data: removePaginationWrapper(
data?.party?.withdrawalsConnection?.edges ?? []
).sort((a, b) => {
if (!b.txHash !== !a.txHash) {
return b.txHash ? -1 : 1;
}
return (
b.txHash ? b.withdrawnTimestamp : b.createdTimestamp
).localeCompare(a.txHash ? a.withdrawnTimestamp : a.createdTimestamp);
}),
loading,
error,
};
};
export const updateQuery: UpdateQueryFn<
WithdrawalsQuery,
WithdrawalEventSubscriptionVariables,
WithdrawalEventSubscription
> = (prev, { subscriptionData, variables }) => {
if (!subscriptionData.data.busEvents?.length) {
return prev;
}
const curr = prev.party?.withdrawalsConnection?.edges || [];
const incoming = subscriptionData.data.busEvents.reduce<WithdrawalEdges>(
(acc, event) => {
if (event.event.__typename === 'Withdrawal') {
acc.push({
node: {
...event.event,
pendingOnForeignChain: false,
},
});
}
return acc;
},
[]
);
const edges = uniqBy([...incoming, ...curr], 'node.id');
// Write new party to cache if not present
if (!prev.party) {
return {
...prev,
party: {
id: variables?.partyId,
__typename: 'Party',
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges,
},
},
} as WithdrawalsQuery;
}
return {
...prev,
party: {
...prev.party,
withdrawalsConnection: {
__typename: 'WithdrawalsConnection',
edges,
},
},
};
};

View File

@ -0,0 +1,56 @@
import uniqBy from 'lodash/uniqBy';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
WithdrawalsDocument,
WithdrawalEventDocument,
} from './__generated__/Withdrawal';
import type {
WithdrawalsQuery,
WithdrawalFieldsFragment,
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables,
} from './__generated__/Withdrawal';
import {
removePaginationWrapper,
getEvents,
} from '@vegaprotocol/react-helpers';
const sortWithdrawals = (data: WithdrawalFieldsFragment[]) =>
data.sort((a, b) => {
if (!b.txHash !== !a.txHash) {
return b.txHash ? -1 : 1;
}
return (b.txHash ? b.withdrawnTimestamp : b.createdTimestamp).localeCompare(
a.txHash ? a.withdrawnTimestamp : a.createdTimestamp
);
});
export const withdrawalProvider = makeDataProvider<
WithdrawalsQuery,
WithdrawalFieldsFragment[],
WithdrawalEventSubscription,
WithdrawalEventSubscription,
WithdrawalEventSubscriptionVariables
>({
query: WithdrawalsDocument,
subscriptionQuery: WithdrawalEventDocument,
getData: (data: WithdrawalsQuery | null) =>
sortWithdrawals(
removePaginationWrapper(data?.party?.withdrawalsConnection?.edges || [])
),
getDelta: (data: WithdrawalEventSubscription) => data,
update: (
data: WithdrawalFieldsFragment[] | null,
delta: WithdrawalEventSubscription
) => {
if (!delta.busEvents?.length) {
return data;
}
const incoming = getEvents<WithdrawalFieldsFragment>(
Schema.BusEventType.Withdrawal,
delta.busEvents
);
return uniqBy([...incoming, ...(data || [])], 'id');
},
});