fix(1758): data node being down (#1871)

* fix: timeout data node requests for node switcher

* chore: generate apollo client library

* chore: migrate console lite to use new apollo client package

* chore: migrate explorer across

* chore: remove completely unused file

* chore: migrate stats

* chore: migrate trading

* chore: migrate multisigner app

* chore: migrate token over

* chore: final migrations

* test: adjust tests for new behaviour

* fix: build script

* Update libs/apollo-client/src/lib/apollo-client.ts

* chore: fix conflicts

* fix: cache

* test: setup mocks before each test

* style: lint

* style: lint

* chore: resolve conflicts

* test: fix tests
This commit is contained in:
Dexter Edwards 2022-11-10 13:08:12 +00:00 committed by GitHub
parent d0d06bd4ab
commit dccc750154
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 475 additions and 672 deletions

View File

@ -1,6 +1,5 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { createClient } from './lib/apollo-client';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
@ -16,6 +15,7 @@ import Header from './components/header';
import { Main } from './components/main';
import LocalContext from './context/local-context';
import useLocalValues from './hooks/use-local-values';
import type { InMemoryCacheConfig } from '@apollo/client';
function App() {
const [theme, toggleTheme] = useThemeSwitcher();
@ -30,10 +30,34 @@ function App() {
setMenuOpen(false);
}, [location, setMenuOpen]);
const cacheConfig: InMemoryCacheConfig = {
typePolicies: {
Market: {
merge: true,
},
Party: {
merge: true,
},
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
Instrument: {
keyFields: false,
},
},
};
return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}>
<NetworkLoader createClient={createClient}>
<NetworkLoader cache={cacheConfig}>
<VegaWalletProvider>
<LocalContext.Provider value={localValues}>
<AppLoader>

View File

@ -1,88 +0,0 @@
import {
ApolloClient,
from,
HttpLink,
InMemoryCache,
split,
} from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient as createWSClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
export function createClient(base?: string) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const urlHTTP = new URL(base);
const urlWS = new URL(base);
// Replace http with ws, preserving if its a secure connection eg. https => wss
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
const cache = new InMemoryCache({
typePolicies: {
Market: {
merge: true,
},
Party: {
merge: true,
},
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
Instrument: {
keyFields: false,
},
},
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: urlHTTP.href,
credentials: 'same-origin',
});
const wsLink = new GraphQLWsLink(
createWSClient({
url: urlWS.href,
})
);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
});
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, splitLink]),
cache,
});
}

View File

@ -9,12 +9,12 @@ import {
useEnvironment,
} from '@vegaprotocol/environment';
import { NetworkInfo } from '@vegaprotocol/network-info';
import { createClient } from './lib/apollo-client';
import { Nav } from './components/nav';
import { Header } from './components/header';
import { Main } from './components/main';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import { ENV } from './config/env';
import type { InMemoryCacheConfig } from '@apollo/client';
function App() {
const { VEGA_ENV } = useEnvironment();
@ -36,10 +36,18 @@ function App() {
});
}, [VEGA_ENV]);
const cacheConfig: InMemoryCacheConfig = {
typePolicies: {
Node: {
keyFields: false,
},
},
};
return (
<ThemeContext.Provider value={theme}>
<TendermintWebsocketProvider>
<NetworkLoader createClient={createClient}>
<NetworkLoader cache={cacheConfig}>
<div
className={`${
menuOpen && 'h-[100vh] overflow-hidden'

View File

@ -1,52 +0,0 @@
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
export function createClient(base?: string) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const urlHTTP = new URL(base);
const urlWS = new URL(base);
// Replace http with ws, preserving if its a secure connection eg. https => wss
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
const cache = new InMemoryCache({
typePolicies: {
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
},
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: urlHTTP.href,
credentials: 'same-origin',
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
});
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, httpLink]),
cache,
});
}

View File

@ -1,7 +1,4 @@
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { useRoutes } from 'react-router-dom';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import { createClient } from './lib/apollo-client';
import '../styles.scss';
import { Navbar } from './components/navbar';
@ -12,16 +9,10 @@ const AppRouter = () => useRoutes(routerConfig);
export function App() {
return (
<EnvironmentProvider>
<ThemeContext.Provider value="light">
<NetworkLoader createClient={createClient}>
<div className="max-h-full min-h-full bg-white">
<Navbar />
<AppRouter />
</div>
</NetworkLoader>
</ThemeContext.Provider>
</EnvironmentProvider>
<div className="max-h-full min-h-full bg-white">
<Navbar />
<AppRouter />
</div>
);
}

View File

@ -1,88 +0,0 @@
import {
ApolloClient,
from,
HttpLink,
InMemoryCache,
split,
} from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient as createWSClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
export function createClient(base?: string) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const urlHTTP = new URL(base);
const urlWS = new URL(base);
// Replace http with ws, preserving if its a secure connection eg. https => wss
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
const cache = new InMemoryCache({
typePolicies: {
Market: {
merge: true,
},
Party: {
merge: true,
},
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
Instrument: {
keyFields: false,
},
},
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: urlHTTP.href,
credentials: 'same-origin',
});
const wsLink = new GraphQLWsLink(
createWSClient({
url: urlWS.href,
})
);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
});
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, splitLink]),
cache,
});
}

View File

@ -1,16 +1,47 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import App from './app/app';
import type { InMemoryCacheConfig } from '@apollo/client';
const rootElement = document.getElementById('root');
const root = rootElement && createRoot(rootElement);
const cache: InMemoryCacheConfig = {
typePolicies: {
Market: {
merge: true,
},
Party: {
merge: true,
},
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
Instrument: {
keyFields: false,
},
},
};
root?.render(
<StrictMode>
<BrowserRouter>
<App />
<EnvironmentProvider>
<ThemeContext.Provider value="light">
<NetworkLoader cache={cache}>
<App />
</NetworkLoader>
</ThemeContext.Provider>
</EnvironmentProvider>
</BrowserRouter>
</StrictMode>
);

View File

@ -11,7 +11,6 @@ import { AsyncRenderer, Button, Lozenge } from '@vegaprotocol/ui-toolkit';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { useEthereumConfig, Web3Provider } from '@vegaprotocol/web3';
import { ThemeContext, useThemeSwitcher, t } from '@vegaprotocol/react-helpers';
import { createClient } from './lib/apollo-client';
import { ENV } from './config/env';
import { ContractsProvider } from './config/contracts/contracts-provider';
import {
@ -24,6 +23,7 @@ import { createConnectors } from './lib/web3-connectors';
import { Web3Connector } from './components/web3-connector';
import { EthWalletContainer } from './components/eth-wallet-container';
import { useWeb3React } from '@web3-react/core';
import type { InMemoryCacheConfig } from '@apollo/client';
const pageWrapperClasses = classnames(
'min-h-screen w-screen',
@ -96,10 +96,26 @@ function App() {
}
const Wrapper = () => {
const cache: InMemoryCacheConfig = {
typePolicies: {
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
},
};
return (
<EnvironmentProvider>
<NetworkLoader createClient={createClient}>
<App />
<NetworkLoader cache={cache}>
<ContractsProvider>
<App />
</ContractsProvider>
</NetworkLoader>
</EnvironmentProvider>
);

View File

@ -1,54 +0,0 @@
import * as Sentry from '@sentry/react';
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
export function createClient(base?: string) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const urlHTTP = new URL(base);
const urlWS = new URL(base);
// Replace http with ws, preserving if its a secure connection eg. https => wss
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
const cache = new InMemoryCache({
typePolicies: {
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
},
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: urlHTTP.href,
credentials: 'same-origin',
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
Sentry.captureException(graphQLErrors);
});
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, httpLink]),
cache,
});
}

View File

@ -3,14 +3,13 @@ import { Header } from './components/header';
import { StatsManager } from '@vegaprotocol/network-stats';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { createClient } from './lib/apollo-client';
function App() {
const [theme, toggleTheme] = useThemeSwitcher();
return (
<ThemeContext.Provider value={theme}>
<NetworkLoader createClient={createClient}>
<NetworkLoader>
<div className="w-screen min-h-screen grid pb-6 bg-white text-neutral-900 dark:bg-black dark:text-neutral-100">
<div className="layout-grid w-screen justify-self-center">
<Header theme={theme} toggleTheme={toggleTheme} />

View File

@ -1,52 +0,0 @@
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
export function createClient(base?: string) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const urlHTTP = new URL(base);
const urlWS = new URL(base);
// Replace http with ws, preserving if its a secure connection eg. https => wss
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
const cache = new InMemoryCache({
typePolicies: {
Query: {},
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Node: {
keyFields: false,
},
},
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: urlHTTP.href,
credentials: 'same-origin',
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
});
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, httpLink]),
cache,
});
}

View File

@ -26,9 +26,148 @@ import {
EnvironmentProvider,
NetworkLoader,
} from '@vegaprotocol/environment';
import { createClient } from './lib/apollo-client';
import { createConnectors } from './lib/web3-connectors';
import { ENV } from './config/env';
import type {
FieldFunctionOptions,
InMemoryCacheConfig,
Reference,
} from '@apollo/client';
import sortBy from 'lodash/sortBy';
import uniqBy from 'lodash/uniqBy';
import { deterministicShuffle } from './lib/deterministic-shuffle';
import { addDecimal } from '@vegaprotocol/react-helpers';
const formatUintToNumber = (amount: string, decimals = 18) =>
addDecimal(amount, decimals).toString();
const createReadField = (fieldName: string) => ({
[`${fieldName}Formatted`]: {
read(_: string, options: FieldFunctionOptions) {
const amount = options.readField(fieldName) as string;
return amount ? formatUintToNumber(amount) : '0';
},
},
});
// Create seed in memory. Validator list order will remain the same
// until the page is refreshed.
const VALIDATOR_RANDOMISER_SEED = (
Math.floor(Math.random() * 1000) + 1
).toString();
const cache: InMemoryCacheConfig = {
typePolicies: {
Query: {
fields: {
nodes: {
// Merge function to make the validator list random but remain consistent
// as the user navigates around the site. If the user refreshes the list
// will be randomised.
merge: (existing = [], incoming) => {
// uniqBy will take the first of any matches
const uniq = uniqBy([...incoming, ...existing], 'id');
// sort result so that the input is consistent
const sorted = sortBy(uniq, 'id');
// randomise based on seed string
const random = deterministicShuffle(
VALIDATOR_RANDOMISER_SEED,
sorted
);
return random;
},
},
},
},
Account: {
keyFields: false,
fields: {
balanceFormatted: {
read(_: string, options: FieldFunctionOptions) {
const balance = options.readField('balance');
const asset = options.readField('asset');
const decimals = options.readField('decimals', asset as Reference);
if (typeof balance !== 'string') return '0';
if (typeof decimals !== 'number') return '0';
return balance && decimals
? formatUintToNumber(balance, decimals)
: '0';
},
},
},
},
Delegation: {
keyFields: false,
// Only get full updates
merge(_, incoming) {
return incoming;
},
fields: {
...createReadField('amount'),
},
},
Reward: {
keyFields: false,
fields: {
...createReadField('amount'),
},
},
RewardPerAssetDetail: {
keyFields: false,
fields: {
...createReadField('totalAmount'),
},
},
Node: {
keyFields: false,
fields: {
...createReadField('pendingStake'),
...createReadField('stakedByOperator'),
...createReadField('stakedByDelegates'),
...createReadField('stakedTotal'),
},
},
NodeData: {
merge: (existing = {}, incoming) => {
return { ...existing, ...incoming };
},
fields: {
...createReadField('stakedTotal'),
},
},
Party: {
fields: {
stake: {
merge(existing, incoming) {
return {
...existing,
...incoming,
};
},
read(stake) {
if (stake) {
return {
...stake,
currentStakeAvailableFormatted: formatUintToNumber(
stake.currentStakeAvailable
),
};
}
return stake;
},
},
},
},
Withdrawal: {
fields: {
pendingOnForeignChain: {
read: (isPending = false) => isPending,
},
},
},
},
};
const Web3Container = ({
chainId,
@ -129,7 +268,7 @@ const AppContainer = () => {
function App() {
return (
<EnvironmentProvider>
<NetworkLoader createClient={createClient}>
<NetworkLoader cache={cache}>
<AppContainer />
</NetworkLoader>
</EnvironmentProvider>

View File

@ -1,150 +0,0 @@
import type { FieldFunctionOptions, Reference } from '@apollo/client';
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import { addDecimal } from '@vegaprotocol/react-helpers';
import sortBy from 'lodash/sortBy';
import uniqBy from 'lodash/uniqBy';
import { deterministicShuffle } from './deterministic-shuffle';
// Create seed in memory. Validator list order will remain the same
// until the page is refreshed.
const VALIDATOR_RANDOMISER_SEED = (
Math.floor(Math.random() * 1000) + 1
).toString();
export function createClient(base?: string) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const formatUintToNumber = (amount: string, decimals = 18) =>
addDecimal(amount, decimals).toString();
const createReadField = (fieldName: string) => ({
[`${fieldName}Formatted`]: {
read(_: string, options: FieldFunctionOptions) {
const amount = options.readField(fieldName) as string;
return amount ? formatUintToNumber(amount) : '0';
},
},
});
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
nodes: {
// Merge function to make the validator list random but remain consistent
// as the user navigates around the site. If the user refreshes the list
// will be randomised.
merge: (existing = [], incoming) => {
// uniqBy will take the first of any matches
const uniq = uniqBy([...incoming, ...existing], 'id');
// sort result so that the input is consistent
const sorted = sortBy(uniq, 'id');
// randomise based on seed string
const random = deterministicShuffle(
VALIDATOR_RANDOMISER_SEED,
sorted
);
return random;
},
},
},
},
Account: {
keyFields: false,
fields: {
balanceFormatted: {
read(_: string, options: FieldFunctionOptions) {
const balance = options.readField('balance');
const asset = options.readField('asset');
const decimals = options.readField(
'decimals',
asset as Reference
);
if (typeof balance !== 'string') return '0';
if (typeof decimals !== 'number') return '0';
return balance && decimals
? formatUintToNumber(balance, decimals)
: '0';
},
},
},
},
Delegation: {
keyFields: false,
// Only get full updates
merge(_, incoming) {
return incoming;
},
fields: {
...createReadField('amount'),
},
},
Reward: {
keyFields: false,
fields: {
...createReadField('amount'),
},
},
RewardPerAssetDetail: {
keyFields: false,
fields: {
...createReadField('totalAmount'),
},
},
Node: {
keyFields: false,
fields: {
...createReadField('pendingStake'),
...createReadField('stakedByOperator'),
...createReadField('stakedByDelegates'),
...createReadField('stakedTotal'),
},
},
NodeData: {
merge: (existing = {}, incoming) => {
return { ...existing, ...incoming };
},
fields: {
...createReadField('stakedTotal'),
},
},
Withdrawal: {
fields: {
pendingOnForeignChain: {
read: (isPending = false) => isPending,
},
},
},
},
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: base,
credentials: 'same-origin',
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
// eslint-disable-next-line no-console
console.log(graphQLErrors);
// eslint-disable-next-line no-console
console.log(networkError);
});
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, httpLink]),
cache,
});
}

View File

@ -219,7 +219,7 @@ describe('market states', { tags: '@smoke' }, function () {
states.forEach((marketState) => {
describe(marketState, function () {
before(function () {
beforeEach(function () {
cy.mockTradingPage(marketState);
cy.mockGQLSubscription();
cy.visit('/#/markets/market-0');

View File

@ -1,8 +1,9 @@
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import { useEagerConnect } from '@vegaprotocol/wallet';
import { NetworkLoader } from '@vegaprotocol/environment';
import { Connectors } from '../../lib/vega-connectors';
import { createClient } from '../../lib/apollo-client';
import type { InMemoryCacheConfig } from '@apollo/client';
interface AppLoaderProps {
children: ReactNode;
@ -15,6 +16,55 @@ interface AppLoaderProps {
export function AppLoader({ children }: AppLoaderProps) {
// Get keys from vega wallet immediately
useEagerConnect(Connectors);
return <NetworkLoader createClient={createClient}>{children}</NetworkLoader>;
const cache: InMemoryCacheConfig = useMemo(
() => ({
typePolicies: {
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Instrument: {
keyFields: false,
},
TradableInstrument: {
keyFields: ['instrument'],
},
Product: {
keyFields: ['settlementAsset', ['id']],
},
MarketData: {
keyFields: ['market', ['id']],
},
Node: {
keyFields: false,
},
Withdrawal: {
fields: {
pendingOnForeignChain: {
read: (isPending = false) => isPending,
},
},
},
ERC20: {
keyFields: ['contractAddress'],
},
PositionUpdate: {
keyFields: false,
},
AccountUpdate: {
keyFields: false,
},
Party: {
keyFields: false,
},
Fees: {
keyFields: false,
},
},
}),
[]
);
return <NetworkLoader cache={cache}>{children}</NetworkLoader>;
}

View File

@ -1,117 +0,0 @@
import {
ApolloClient,
ApolloLink,
split,
from,
HttpLink,
InMemoryCache,
} from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import { createClient as createWSClient } from 'graphql-ws';
export function createClient(base?: string) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
const urlHTTP = new URL(base);
const urlWS = new URL(base);
// Replace http with ws, preserving if its a secure connection eg. https => wss
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
const cache = new InMemoryCache({
typePolicies: {
Account: {
keyFields: false,
fields: {
balanceFormatted: {},
},
},
Instrument: {
keyFields: false,
},
TradableInstrument: {
keyFields: ['instrument'],
},
Product: {
keyFields: ['settlementAsset', ['id']],
},
MarketData: {
keyFields: ['market', ['id']],
},
Node: {
keyFields: false,
},
Withdrawal: {
fields: {
pendingOnForeignChain: {
read: (isPending = false) => isPending,
},
},
},
ERC20: {
keyFields: ['contractAddress'],
},
PositionUpdate: {
keyFields: false,
},
AccountUpdate: {
keyFields: false,
},
Party: {
keyFields: false,
},
Fees: {
keyFields: false,
},
},
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 10000,
jitter: true,
},
});
const httpLink = new HttpLink({
uri: urlHTTP.href,
credentials: 'same-origin',
});
const wsLink = process.browser
? new GraphQLWsLink(
createWSClient({
url: urlWS.href,
})
)
: new ApolloLink((operation, forward) => forward(operation));
const splitLink = process.browser
? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
)
: httpLink;
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
});
return new ApolloClient({
connectToDevTools: process.env['NODE_ENV'] === 'development',
link: from([errorLink, retryLink, splitLink]),
cache,
});
}

View File

@ -0,0 +1,3 @@
{
"presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
}

View File

@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,11 @@
# apollo-client
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test apollo-client` to execute the unit tests via [Jest](https://jestjs.io).
## Running lint
Run `nx lint apollo-client` to execute the lint via [ESLint](https://eslint.org/).

View File

@ -0,0 +1,15 @@
/* eslint-disable */
export default {
displayName: 'apollo-client',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/apollo-client',
};

View File

@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/apollo-client",
"version": "0.0.1"
}

View File

@ -0,0 +1,43 @@
{
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/apollo-client/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/apollo-client",
"tsConfig": "libs/apollo-client/tsconfig.lib.json",
"project": "libs/apollo-client/package.json",
"entryFile": "libs/apollo-client/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/apollo-client/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/apollo-client/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/libs/apollo-client"],
"options": {
"jestConfig": "libs/apollo-client/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}

View File

@ -0,0 +1 @@
export * from './lib/apollo-client';

View File

@ -1,3 +1,4 @@
import type { InMemoryCacheConfig } from '@apollo/client';
import {
ApolloClient,
from,
@ -11,10 +12,11 @@ import { getMainDefinition } from '@apollo/client/utilities';
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';
const isBrowser = typeof window !== 'undefined';
export default function createClient(base?: string) {
export function createClient(base?: string, cacheConfig?: InMemoryCacheConfig) {
if (!base) {
throw new Error('Base must be passed into createClient!');
}
@ -22,7 +24,7 @@ export default function createClient(base?: string) {
const urlWS = new URL(base);
// Replace http with ws, preserving if its a secure connection eg. https => wss
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
const timeoutLink = new ApolloLinkTimeout(10000);
const retryLink = new RetryLink({
delay: {
initial: 300,
@ -64,7 +66,7 @@ export default function createClient(base?: string) {
});
return new ApolloClient({
link: from([errorLink, retryLink, splitLink]),
cache: new InMemoryCache(),
link: from([errorLink, timeoutLink, retryLink, splitLink]),
cache: new InMemoryCache(cacheConfig),
});
}

View File

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": []
},
"include": ["**/*.ts"],
"exclude": ["jest.config.ts", "**/*.spec.ts"]
}

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts"
]
}

View File

@ -3,8 +3,11 @@ import { ApolloProvider } from '@apollo/client';
import { useEnvironment } from '../../hooks';
import { render, screen } from '@testing-library/react';
import { NetworkLoader } from './network-loader';
import { createClient } from '@vegaprotocol/apollo-client';
import { createMockClient } from 'mock-apollo-client';
jest.mock('@apollo/client');
jest.mock('@vegaprotocol/apollo-client');
jest.mock('../../hooks');
// @ts-ignore Typescript doesn't recognise mocked instances
@ -15,15 +18,6 @@ ApolloProvider.mockImplementation(({ children }: { children: ReactNode }) => {
const SKELETON_TEXT = 'LOADING';
const SUCCESS_TEXT = 'LOADED';
const createClient = jest.fn();
beforeEach(() => {
createClient.mockReset();
createClient.mockImplementation(() => {
return jest.fn();
});
});
describe('Network loader', () => {
it('renders a skeleton when there is no vega url in the environment', () => {
// @ts-ignore Typescript doesn't recognise mocked instances
@ -32,9 +26,7 @@ describe('Network loader', () => {
}));
render(
<NetworkLoader skeleton={SKELETON_TEXT} createClient={createClient}>
{SUCCESS_TEXT}
</NetworkLoader>
<NetworkLoader skeleton={SKELETON_TEXT}>{SUCCESS_TEXT}</NetworkLoader>
);
expect(screen.getByText(SKELETON_TEXT)).toBeInTheDocument();
@ -42,20 +34,19 @@ describe('Network loader', () => {
expect(createClient).not.toHaveBeenCalled();
});
it('renders the child components wrapped in an apollo provider when the environment has a vega url', () => {
it('renders the child components wrapped in an apollo provider when the environment has a vega url', async () => {
// @ts-ignore -- ts does not seem to infer this type correctly
createClient.mockReturnValueOnce(createMockClient());
// @ts-ignore Typescript doesn't recognise mocked instances
useEnvironment.mockImplementation(() => ({
VEGA_URL: 'http://vega.node',
}));
render(
<NetworkLoader skeleton={SKELETON_TEXT} createClient={createClient}>
{SUCCESS_TEXT}
</NetworkLoader>
<NetworkLoader skeleton={SKELETON_TEXT}>{SUCCESS_TEXT}</NetworkLoader>
);
expect(() => screen.getByText(SKELETON_TEXT)).toThrow();
expect(screen.getByText(SUCCESS_TEXT)).toBeInTheDocument();
expect(createClient).toHaveBeenCalledWith('http://vega.node');
expect(createClient).toHaveBeenCalledWith('http://vega.node', undefined);
expect(await screen.findByText(SUCCESS_TEXT)).toBeInTheDocument();
});
});

View File

@ -1,28 +1,29 @@
import { useMemo } from 'react';
import type { ReactNode } from 'react';
import type { ApolloClient } from '@apollo/client';
import type { InMemoryCacheConfig } from '@apollo/client';
import { ApolloProvider } from '@apollo/client';
import { useEnvironment } from '../../hooks';
import { createClient } from '@vegaprotocol/apollo-client';
type NetworkLoaderProps<T> = {
type NetworkLoaderProps = {
children?: ReactNode;
skeleton?: ReactNode;
createClient: (url: string) => ApolloClient<T>;
cache?: InMemoryCacheConfig;
};
export function NetworkLoader<T>({
export function NetworkLoader({
skeleton,
children,
createClient,
}: NetworkLoaderProps<T>) {
cache,
}: NetworkLoaderProps) {
const { VEGA_URL } = useEnvironment();
const client = useMemo(() => {
if (VEGA_URL) {
return createClient(VEGA_URL);
return createClient(VEGA_URL, cache);
}
return undefined;
}, [VEGA_URL, createClient]);
}, [VEGA_URL, cache]);
if (!client) {
return (

View File

@ -2,10 +2,10 @@ import type { ReactNode } from 'react';
import { ApolloProvider } from '@apollo/client';
import { t } from '@vegaprotocol/react-helpers';
import type { NodeData } from '../../types';
import type createClient from '../../utils/apollo-client';
import { LayoutRow } from './layout-row';
import { LayoutCell } from './layout-cell';
import { NodeBlockHeight } from './node-block-height';
import type { createClient } from '@vegaprotocol/apollo-client';
type NodeStatsContentProps = {
data?: NodeData;

View File

@ -2,11 +2,11 @@
// workaround based on: https://github.com/facebook/react/issues/11565
import type { ComponentProps, ReactNode } from 'react';
import { renderHook } from '@testing-library/react';
import createClient from '../utils/apollo-client';
import { createClient } from '@vegaprotocol/apollo-client';
import { useEnvironment, EnvironmentProvider } from './use-environment';
import { Networks } from '../types';
import createMockClient from './mocks/apollo-client';
jest.mock('../utils/apollo-client');
jest.mock('@vegaprotocol/apollo-client');
jest.mock('react-dom', () => ({
...jest.requireActual('react-dom'),

View File

@ -2,14 +2,14 @@
// workaround based on: https://github.com/facebook/react/issues/11565
import type { ComponentProps, ReactNode } from 'react';
import { renderHook, waitFor, act } from '@testing-library/react';
import createClient from '../utils/apollo-client';
import { createClient } from '@vegaprotocol/apollo-client';
import { useEnvironment, EnvironmentProvider } from './use-environment';
import { Networks, ErrorType } from '../types';
import type { MockRequestConfig } from './mocks/apollo-client';
import createMockClient from './mocks/apollo-client';
import { getErrorByType } from '../utils/validate-node';
jest.mock('../utils/apollo-client');
jest.mock('@vegaprotocol/apollo-client');
jest.mock('react-dom', () => ({
...jest.requireActual('react-dom'),

View File

@ -1,13 +1,13 @@
import { renderHook, act } from '@testing-library/react';
import { ApolloClient } from '@apollo/client';
import createClient from '../utils/apollo-client';
import { createClient } from '@vegaprotocol/apollo-client';
import { useNodes } from './use-nodes';
import createMockClient, {
getMockStatisticsResult,
} from './mocks/apollo-client';
import { waitFor } from '@testing-library/react';
jest.mock('../utils/apollo-client');
jest.mock('@vegaprotocol/apollo-client');
const MOCK_DURATION = 1073;

View File

@ -1,9 +1,9 @@
import type { Dispatch } from 'react';
import { useState, useEffect, useReducer } from 'react';
import { produce } from 'immer';
import type createClient from '../utils/apollo-client';
import { initializeNode } from '../utils/initialize-node';
import type { NodeData, Configuration } from '../types';
import type { createClient } from '@vegaprotocol/apollo-client';
type StatisticsPayload = {
block: NodeData['block']['value'];

View File

@ -1,9 +1,9 @@
import createClient from './apollo-client';
import { StatisticsDocument, BlockTimeDocument } from './__generated__/Node';
import type {
StatisticsQuery,
BlockTimeSubscription,
} from './__generated__/Node';
import { createClient } from '@vegaprotocol/apollo-client';
type Callbacks = {
onStatsSuccess: (data: StatisticsQuery) => void;

View File

@ -44,6 +44,7 @@
"allotment": "^1.14.5",
"alpha-lyrae": "vegaprotocol/alpha-lyrae",
"apollo": "^2.33.9",
"apollo-link-timeout": "^4.0.0",
"bignumber.js": "^9.0.2",
"buffer": "^6.0.3",
"classnames": "^2.3.1",

View File

@ -17,6 +17,7 @@
"resolveJsonModule": true,
"paths": {
"@vegaprotocol/accounts": ["libs/accounts/src/index.ts"],
"@vegaprotocol/apollo-client": ["libs/apollo-client/src/index.ts"],
"@vegaprotocol/assets": ["libs/assets/src/index.ts"],
"@vegaprotocol/candles-chart": ["libs/candles-chart/src/index.ts"],
"@vegaprotocol/cypress": ["libs/cypress/src/index.ts"],

View File

@ -2,6 +2,7 @@
"version": 2,
"projects": {
"accounts": "libs/accounts",
"apollo-client": "libs/apollo-client",
"assets": "libs/assets",
"candles-chart": "libs/candles-chart",
"console-lite": "apps/console-lite",

View File

@ -7985,6 +7985,11 @@ apollo-link-http@^1.5.5:
apollo-link-http-common "^0.2.16"
tslib "^1.9.3"
apollo-link-timeout@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/apollo-link-timeout/-/apollo-link-timeout-4.0.0.tgz#3e255bcced6a6babdcc080b1919dd958c036e235"
integrity sha512-2tZsNvmbsAHunWSsGi+URLMQSDoSU0NRDJeYicX/eB7J94QXydgvZOG4FCsgU5hY0dhUrPrLCotcpJjvOOfSlA==
apollo-link@^1.2.14, apollo-link@^1.2.3:
version "1.2.14"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9"