Compare commits

...

7 Commits

Author SHA1 Message Date
asiaznik
bb98dcfae8
fix(environment): pick best node
chore: added response time comparison to the node picker
2024-02-06 14:16:59 +01:00
m.ray
ecd2865bd0
fix(trading): disable chart settings temporarily in default env (#5750) 2024-02-05 17:03:34 +00:00
m.ray
adf0e26df6
chore(trading): clear session storage and update readme.md in main (#5747) 2024-02-05 16:23:03 +00:00
m.ray
a7f95aa953
chore(trading): disable trading view temporarily (#5742) 2024-02-05 11:27:20 +00:00
m.ray
3583f22067
fix(trading): active rewards filter (#5733) 2024-02-02 16:04:41 +00:00
Bartłomiej Głownia
2ac5471710
feat(accounts): remove include transfer fee checkbox (#5732) 2024-02-02 15:59:35 +00:00
m.ray
98ce1d0a25
fix(trading): closed markets filter (#5691)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-31 14:27:43 +00:00
18 changed files with 186 additions and 258 deletions

View File

@ -4,7 +4,7 @@ The front-end monorepo provides a toolkit for building apps that interact with V
This repository is managed using [Nx](https://nx.dev). This repository is managed using [Nx](https://nx.dev).
# 🔎 Applications in this repo ## 🔎 Applications in this repo
### [Block explorer](./apps/explorer) ### [Block explorer](./apps/explorer)
@ -30,7 +30,7 @@ Hosting for static content being shared across apps, for example fonts.
The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract. The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract.
# 🧱 Libraries in this repo ## 🧱 Libraries in this repo
### [UI toolkit](./libs/ui-toolkit) ### [UI toolkit](./libs/ui-toolkit)
@ -53,7 +53,7 @@ A utility library for connecting to the Ethereum network and interacting with Ve
Generic react helpers that can be used across multiple applications, along with other utilities. Generic react helpers that can be used across multiple applications, along with other utilities.
# 💻 Develop ## 💻 Develop
### Set up ### Set up
@ -103,7 +103,7 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more. Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
# 🐋 Hosting a console ## 🐋 Hosting a console
To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions). To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions).
@ -226,6 +226,6 @@ Note: The script is only needed if capsule was built for first time or fresh. To
vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet
``` ```
# 📑 License ## 📑 License
[MIT](./LICENSE) [MIT](./LICENSE)

View File

@ -29,5 +29,5 @@ NX_REFERRALS=true
NX_TENDERMINT_URL=https://be.vega.community NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/ # NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg= # NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=

View File

@ -29,5 +29,5 @@ NX_REFERRALS=true
NX_TENDERMINT_URL=https://be.vega.community NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/ # NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg= # NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=

View File

@ -309,49 +309,29 @@ export const ActiveRewardCard = ({
].includes(m.state) ].includes(m.state)
); );
const assetInSettledMarket =
allMarkets &&
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
return (
m?.state &&
[
MarketState.STATE_TRADING_TERMINATED,
MarketState.STATE_SETTLED,
MarketState.STATE_CANCELLED,
MarketState.STATE_CLOSED,
].includes(m.state)
);
}
return false;
});
if (marketSettled) { if (marketSettled) {
return null; return null;
} }
// Gray out the cards that are related to suspended markets const assetInActiveMarket =
const suspended = transferNode.markets?.some( allMarkets &&
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
return m?.state && MarketState.STATE_ACTIVE === m.state;
}
return false;
});
const marketSuspended = transferNode.markets?.some(
(m) => (m) =>
m?.state === MarketState.STATE_SUSPENDED || m?.state === MarketState.STATE_SUSPENDED ||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
); );
const assetInSuspendedMarket =
allMarkets &&
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
return (
m?.state === MarketState.STATE_SUSPENDED ||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
);
}
return false;
});
// Gray out the cards that are related to suspended markets // Gray out the cards that are related to suspended markets
// Or settlement assets in markets that are not active and eligible for rewards
const { gradientClassName, mainClassName } = const { gradientClassName, mainClassName } =
suspended || assetInSuspendedMarket || assetInSettledMarket marketSuspended || !assetInActiveMarket
? { ? {
gradientClassName: 'from-vega-cdark-500 to-vega-clight-400', gradientClassName: 'from-vega-cdark-500 to-vega-clight-400',
mainClassName: 'from-vega-cdark-400 dark:from-vega-cdark-600 to-20%', mainClassName: 'from-vega-cdark-400 dark:from-vega-cdark-600 to-20%',
@ -437,12 +417,12 @@ export const ActiveRewardCard = ({
<span> <span>
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '} {DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '}
<Tooltip <Tooltip
underline={suspended} underline={marketSuspended}
description={ description={
(suspended || assetInSuspendedMarket) && (marketSuspended || !assetInActiveMarket) &&
(specificMarkets (specificMarkets
? t('Eligible market(s) currently suspended') ? t('Eligible market(s) currently suspended')
: assetInSuspendedMarket : !assetInActiveMarket
? t('Currently no markets eligible for reward') ? t('Currently no markets eligible for reward')
: '') : '')
} }

View File

@ -81,6 +81,7 @@ export const Settings = () => {
intent={Intent.Primary} intent={Intent.Primary}
onClick={() => { onClick={() => {
localStorage.clear(); localStorage.clear();
sessionStorage.clear();
window.location.reload(); window.location.reload();
}} }}
> >

View File

@ -96,11 +96,6 @@ describe('TransferForm', () => {
}); });
it.each([ it.each([
{
targetText: 'Include transfer fee',
tooltipText:
'The fee will be taken from the amount you are transferring.',
},
{ {
targetText: 'Transfer fee', targetText: 'Transfer fee',
tooltipText: /transfer\.fee\.factor/, tooltipText: /transfer\.fee\.factor/,
@ -276,9 +271,6 @@ describe('TransferForm', () => {
const amountInput = screen.getByLabelText('Amount'); const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
await userEvent.clear(amountInput); await userEvent.clear(amountInput);
await userEvent.type(amountInput, '50'); await userEvent.type(amountInput, '50');
@ -288,10 +280,7 @@ describe('TransferForm', () => {
await userEvent.click(screen.getByRole('button', { name: 'Use max' })); await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('100.00'); expect(amountInput).toHaveValue('100.00');
// If transfering from a vested account 'include fees' checkbox should // If transfering from a vested account fees should be 0
// be disabled and fees should be 0
expect(checkbox).not.toBeChecked();
expect(checkbox).toBeDisabled();
const expectedFee = '0'; const expectedFee = '0';
const total = new BigNumber(amount).plus(expectedFee).toFixed(); const total = new BigNumber(amount).plus(expectedFee).toFixed();
@ -396,78 +385,7 @@ describe('TransferForm', () => {
}); });
}); });
}); });
describe('IncludeFeesCheckbox', () => { describe('IncludeFeesCheckbox', () => {
it('validates fields and submits when checkbox is checked', async () => {
const mockSubmit = jest.fn();
renderComponent({ ...props, submitTransfer: mockSubmit });
// check current pubkey not shown
const keySelect = screen.getByLabelText<HTMLSelectElement>('To Vega key');
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
pubKeyOptions
);
await submit();
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
// Select a pubkey
await userEvent.selectOptions(
screen.getByLabelText('To Vega key'),
props.pubKeys[1]
);
// Select asset
await selectAsset(asset);
await userEvent.selectOptions(
screen.getByLabelText('From account'),
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
);
const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
// 1003-TRAN-022
expect(checkbox).not.toBeChecked();
await userEvent.clear(amountInput);
await userEvent.type(amountInput, amount);
await userEvent.click(checkbox);
expect(checkbox).toBeChecked();
const expectedFee = new BigNumber(amount)
.times(props.feeFactor)
.toFixed();
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
// 1003-TRAN-020
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
expectedAmount
);
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
amount
);
await submit();
await waitFor(() => {
// 1003-TRAN-023
expect(mockSubmit).toHaveBeenCalledTimes(1);
expect(mockSubmit).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(expectedAmount, asset.decimals),
oneOff: {},
});
});
});
it('validates fields when checkbox is not checked', async () => { it('validates fields when checkbox is not checked', async () => {
renderComponent(props); renderComponent(props);
@ -497,11 +415,8 @@ describe('TransferForm', () => {
); );
const amountInput = screen.getByLabelText('Amount'); const amountInput = screen.getByLabelText('Amount');
const checkbox = screen.getByTestId('include-transfer-fee');
expect(checkbox).not.toBeChecked();
await userEvent.type(amountInput, amount); await userEvent.type(amountInput, amount);
expect(checkbox).not.toBeChecked();
const expectedFee = new BigNumber(amount) const expectedFee = new BigNumber(amount)
.times(props.feeFactor) .times(props.feeFactor)
.toFixed(); .toFixed();

View File

@ -15,7 +15,6 @@ import {
TradingRichSelect, TradingRichSelect,
TradingSelect, TradingSelect,
Tooltip, Tooltip,
TradingCheckbox,
TradingButton, TradingButton,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet'; import type { Transfer } from '@vegaprotocol/wallet';
@ -135,32 +134,17 @@ export const TransferForm = ({
const accountBalance = const accountBalance =
account && addDecimal(account.balance, account.asset.decimals); account && addDecimal(account.balance, account.asset.decimals);
const [includeFee, setIncludeFee] = useState(false);
// Max amount given selected asset and from account // Max amount given selected asset and from account
const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0); const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0);
const transferAmount = useMemo(() => { const fee = useMemo(
if (!amount) return undefined; () => feeFactor && new BigNumber(feeFactor).times(amount).toString(),
if (includeFee && feeFactor) { [amount, feeFactor]
return new BigNumber(1).minus(feeFactor).times(amount).toString();
}
return amount;
}, [amount, includeFee, feeFactor]);
const fee = useMemo(() => {
if (!transferAmount) return undefined;
if (includeFee) {
return new BigNumber(amount).minus(transferAmount).toString();
}
return (
feeFactor && new BigNumber(feeFactor).times(transferAmount).toString()
); );
}, [amount, includeFee, transferAmount, feeFactor]);
const onSubmit = useCallback( const onSubmit = useCallback(
(fields: FormFields) => { (fields: FormFields) => {
if (!transferAmount) { if (!amount) {
throw new Error('Submitted transfer with no amount selected'); throw new Error('Submitted transfer with no amount selected');
} }
@ -173,7 +157,7 @@ export const TransferForm = ({
const transfer = normalizeTransfer( const transfer = normalizeTransfer(
fields.toVegaKey, fields.toVegaKey,
transferAmount, amount,
type, type,
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
{ {
@ -183,7 +167,7 @@ export const TransferForm = ({
); );
submitTransfer(transfer); submitTransfer(transfer);
}, },
[submitTransfer, transferAmount, assets] [submitTransfer, amount, assets]
); );
// reset for placeholder workaround https://github.com/radix-ui/primitives/issues/1569 // reset for placeholder workaround https://github.com/radix-ui/primitives/issues/1569
@ -279,7 +263,6 @@ export const TransferForm = ({
) { ) {
setValue('toVegaKey', pubKey); setValue('toVegaKey', pubKey);
setToVegaKeyMode('select'); setToVegaKeyMode('select');
setIncludeFee(false);
} }
}} }}
> >
@ -449,27 +432,9 @@ export const TransferForm = ({
</TradingInputError> </TradingInputError>
)} )}
</TradingFormGroup> </TradingFormGroup>
<div className="mb-4"> {amount && fee && (
<Tooltip
description={t(
`The fee will be taken from the amount you are transferring.`
)}
>
<div>
<TradingCheckbox
name="include-transfer-fee"
disabled={!transferAmount || fromVested}
label={t('Include transfer fee')}
checked={includeFee}
onCheckedChange={() => setIncludeFee((x) => !x)}
/>
</div>
</Tooltip>
</div>
{transferAmount && fee && (
<TransferFee <TransferFee
amount={transferAmount} amount={amount}
transferAmount={transferAmount}
feeFactor={feeFactor} feeFactor={feeFactor}
fee={fromVested ? '0' : fee} fee={fromVested ? '0' : fee}
decimals={asset?.decimals} decimals={asset?.decimals}
@ -484,29 +449,22 @@ export const TransferForm = ({
export const TransferFee = ({ export const TransferFee = ({
amount, amount,
transferAmount,
feeFactor, feeFactor,
fee, fee,
decimals, decimals,
}: { }: {
amount: string; amount: string;
transferAmount: string;
feeFactor: string | null; feeFactor: string | null;
fee?: string; fee?: string;
decimals?: number; decimals?: number;
}) => { }) => {
const t = useT(); const t = useT();
if (!feeFactor || !amount || !transferAmount || !fee) return null; if (!feeFactor || !amount || !fee) return null;
if ( if (isNaN(Number(feeFactor)) || isNaN(Number(amount)) || isNaN(Number(fee))) {
isNaN(Number(feeFactor)) ||
isNaN(Number(amount)) ||
isNaN(Number(transferAmount)) ||
isNaN(Number(fee))
) {
return null; return null;
} }
const totalValue = new BigNumber(transferAmount).plus(fee).toString(); const totalValue = new BigNumber(amount).plus(fee).toString();
return ( return (
<div className="mb-4 flex flex-col gap-2 text-xs"> <div className="mb-4 flex flex-col gap-2 text-xs">

View File

@ -23,7 +23,6 @@ import {
SUBSCRIPTION_TIMEOUT, SUBSCRIPTION_TIMEOUT,
useNodeBasicStatus, useNodeBasicStatus,
useNodeSubscriptionStatus, useNodeSubscriptionStatus,
useResponseTime,
} from './row-data'; } from './row-data';
import { BLOCK_THRESHOLD, RowData } from './row-data'; import { BLOCK_THRESHOLD, RowData } from './row-data';
import { CUSTOM_NODE_KEY } from '../../types'; import { CUSTOM_NODE_KEY } from '../../types';
@ -162,19 +161,6 @@ describe('useNodeBasicStatus', () => {
}); });
}); });
describe('useResponseTime', () => {
it('returns response time when url is valid', () => {
const { result } = renderHook(() =>
useResponseTime('https://localhost:1234')
);
expect(result.current.responseTime).toBe(50);
});
it('does not return response time when url is invalid', () => {
const { result } = renderHook(() => useResponseTime('nope'));
expect(result.current.responseTime).toBeUndefined();
});
});
describe('RowData', () => { describe('RowData', () => {
const props = { const props = {
id: '0', id: '0',

View File

@ -1,4 +1,3 @@
import { isValidUrl } from '@vegaprotocol/utils';
import { TradingRadio } from '@vegaprotocol/ui-toolkit'; import { TradingRadio } from '@vegaprotocol/ui-toolkit';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { CUSTOM_NODE_KEY } from '../../types'; import { CUSTOM_NODE_KEY } from '../../types';
@ -8,6 +7,7 @@ import {
} from '../../utils/__generated__/NodeCheck'; } from '../../utils/__generated__/NodeCheck';
import { LayoutCell } from './layout-cell'; import { LayoutCell } from './layout-cell';
import { useT } from '../../use-t'; import { useT } from '../../use-t';
import { useResponseTime } from '../../utils/time';
export const POLL_INTERVAL = 1000; export const POLL_INTERVAL = 1000;
export const SUBSCRIPTION_TIMEOUT = 3000; export const SUBSCRIPTION_TIMEOUT = 3000;
@ -108,20 +108,6 @@ export const useNodeBasicStatus = () => {
}; };
}; };
export const useResponseTime = (url: string, trigger?: unknown) => {
const [responseTime, setResponseTime] = useState<number>();
useEffect(() => {
if (!isValidUrl(url)) return;
if (typeof window.performance.getEntriesByName !== 'function') return; // protection for test environment
const requestUrl = new URL(url);
const requests = window.performance.getEntriesByName(requestUrl.href);
const { duration } =
(requests.length && requests[requests.length - 1]) || {};
setResponseTime(duration);
}, [url, trigger]);
return { responseTime };
};
export const RowData = ({ export const RowData = ({
id, id,
url, url,

View File

@ -10,6 +10,7 @@ import {
getUserEnabledFeatureFlags, getUserEnabledFeatureFlags,
setUserEnabledFeatureFlag, setUserEnabledFeatureFlag,
} from './use-environment'; } from './use-environment';
import { canMeasureResponseTime, measureResponseTime } from '../utils/time';
const noop = () => { const noop = () => {
/* no op*/ /* no op*/
@ -17,6 +18,10 @@ const noop = () => {
jest.mock('@vegaprotocol/apollo-client'); jest.mock('@vegaprotocol/apollo-client');
jest.mock('zustand'); jest.mock('zustand');
jest.mock('../utils/time');
const mockCanMeasureResponseTime = canMeasureResponseTime as jest.Mock;
const mockMeasureResponseTime = measureResponseTime as jest.Mock;
const mockCreateClient = createClient as jest.Mock; const mockCreateClient = createClient as jest.Mock;
const createDefaultMockClient = () => { const createDefaultMockClient = () => {
@ -155,6 +160,14 @@ describe('useEnvironment', () => {
const fastNode = 'https://api.n01.foo.vega.xyz'; const fastNode = 'https://api.n01.foo.vega.xyz';
const fastWait = 1000; const fastWait = 1000;
const nodes = [slowNode, fastNode]; const nodes = [slowNode, fastNode];
mockCanMeasureResponseTime.mockImplementation(() => true);
mockMeasureResponseTime.mockImplementation((url: string) => {
if (url === slowNode) return slowWait;
if (url === fastNode) return fastWait;
return Infinity;
});
// @ts-ignore: typscript doesn't recognise the mock implementation // @ts-ignore: typscript doesn't recognise the mock implementation
global.fetch.mockImplementation(setupFetch({ hosts: nodes })); global.fetch.mockImplementation(setupFetch({ hosts: nodes }));
@ -168,7 +181,7 @@ describe('useEnvironment', () => {
statistics: { statistics: {
chainId: 'chain-id', chainId: 'chain-id',
blockHeight: '100', blockHeight: '100',
vegaTime: new Date().toISOString(), vegaTime: new Date(1).toISOString(),
}, },
}, },
}); });
@ -196,7 +209,8 @@ describe('useEnvironment', () => {
expect(result.current.nodes).toEqual(nodes); expect(result.current.nodes).toEqual(nodes);
}); });
jest.runAllTimers(); jest.advanceTimersByTime(2000);
// jest.runAllTimers();
await waitFor(() => { await waitFor(() => {
expect(result.current.status).toEqual('success'); expect(result.current.status).toEqual('success');

View File

@ -19,6 +19,9 @@ import { compileErrors } from '../utils/compile-errors';
import { envSchema } from '../utils/validate-environment'; import { envSchema } from '../utils/validate-environment';
import { tomlConfigSchema } from '../utils/validate-configuration'; import { tomlConfigSchema } from '../utils/validate-configuration';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import orderBy from 'lodash/orderBy';
import first from 'lodash/first';
import { canMeasureResponseTime, measureResponseTime } from '../utils/time';
type Client = ReturnType<typeof createClient>; type Client = ReturnType<typeof createClient>;
type ClientCollection = { type ClientCollection = {
@ -38,8 +41,17 @@ export type EnvStore = Env & Actions;
const VERSION = 1; const VERSION = 1;
export const STORAGE_KEY = `vega_url_${VERSION}`; export const STORAGE_KEY = `vega_url_${VERSION}`;
const QUERY_TIMEOUT = 3000;
const SUBSCRIPTION_TIMEOUT = 3000; const SUBSCRIPTION_TIMEOUT = 3000;
const raceAgainst = (timeout: number): Promise<false> =>
new Promise((resolve) => {
setTimeout(() => {
resolve(false);
}, timeout);
});
/** /**
* Fetch and validate a vega node configuration * Fetch and validate a vega node configuration
*/ */
@ -64,53 +76,88 @@ const fetchConfig = async (url?: string) => {
const findNode = async (clients: ClientCollection): Promise<string | null> => { const findNode = async (clients: ClientCollection): Promise<string | null> => {
const tests = Object.entries(clients).map((args) => testNode(...args)); const tests = Object.entries(clients).map((args) => testNode(...args));
try { try {
const url = await Promise.any(tests); const nodes = await Promise.all(tests);
return url; const responsiveNodes = nodes
} catch { .filter(([, q, s]) => q && s)
.map(([url, q]) => {
return {
url,
...q,
};
});
// more recent and faster at the top
const ordered = orderBy(
responsiveNodes,
[(n) => n.blockHeight, (n) => n.vegaTime, (n) => n.responseTime],
['desc', 'desc', 'asc']
);
const best = first(ordered);
return best ? best.url : null;
} catch (err) {
// All tests rejected, no suitable node found // All tests rejected, no suitable node found
return null; return null;
} }
}; };
type Maybe<T> = T | false;
type QueryTestResult = {
blockHeight: number;
vegaTime: Date;
responseTime: number;
};
type SubscriptionTestResult = true;
type NodeTestResult = [
/** url */
string,
Maybe<QueryTestResult>,
Maybe<SubscriptionTestResult>
];
/** /**
* Test a node for suitability for connection * Test a node for suitability for connection
*/ */
const testNode = async ( const testNode = async (
url: string, url: string,
client: Client client: Client
): Promise<string | null> => { ): Promise<NodeTestResult> => {
const results = await Promise.all([ const results = await Promise.all([
// these promises will only resolve with true/false testQuery(client, url),
testQuery(client),
testSubscription(client), testSubscription(client),
]); ]);
if (results[0] && results[1]) { return [url, ...results];
return url;
}
const message = `Tests failed for node: ${url}`;
console.warn(message);
// throwing here will mean this tests is ignored and a different
// node that hopefully does resolve will fulfill the Promise.any
throw new Error(message);
}; };
/** /**
* Run a test query on a client * Run a test query on a client
*/ */
const testQuery = async (client: Client) => { const testQuery = (
try { client: Client,
const result = await client.query<NodeCheckQuery>({ url: string
): Promise<Maybe<QueryTestResult>> => {
const test: Promise<Maybe<QueryTestResult>> = new Promise((resolve) =>
client
.query<NodeCheckQuery>({
query: NodeCheckDocument, query: NodeCheckDocument,
}); })
if (!result || result.error) { .then((result) => {
return false; if (result && !result.error) {
} const res = {
return true; blockHeight: Number(result.data.statistics.blockHeight),
} catch (err) { vegaTime: new Date(result.data.statistics.vegaTime),
return false; // only after a request has been sent we can retrieve the response time
responseTime: canMeasureResponseTime(url)
? measureResponseTime(url) || Infinity
: Infinity,
} as QueryTestResult;
resolve(res);
} else {
resolve(false);
} }
})
.catch(() => resolve(false))
);
return Promise.race([test, raceAgainst(QUERY_TIMEOUT)]);
}; };
/** /**
@ -118,7 +165,9 @@ const testQuery = async (client: Client) => {
* that takes longer than SUBSCRIPTION_TIMEOUT ms to respond * that takes longer than SUBSCRIPTION_TIMEOUT ms to respond
* is deemed a failure * is deemed a failure
*/ */
const testSubscription = (client: Client) => { const testSubscription = (
client: Client
): Promise<Maybe<SubscriptionTestResult>> => {
return new Promise((resolve) => { return new Promise((resolve) => {
const sub = client const sub = client
.subscribe<NodeCheckTimeUpdateSubscription>({ .subscribe<NodeCheckTimeUpdateSubscription>({

View File

@ -0,0 +1,22 @@
import { renderHook } from '@testing-library/react';
import { useResponseTime } from './time';
const mockResponseTime = 50;
global.performance.getEntriesByName = jest.fn().mockReturnValue([
{
duration: mockResponseTime,
},
]);
describe('useResponseTime', () => {
it('returns response time when url is valid', () => {
const { result } = renderHook(() =>
useResponseTime('https://localhost:1234')
);
expect(result.current.responseTime).toBe(50);
});
it('does not return response time when url is invalid', () => {
const { result } = renderHook(() => useResponseTime('nope'));
expect(result.current.responseTime).toBeUndefined();
});
});

View File

@ -0,0 +1,25 @@
import { isValidUrl } from '@vegaprotocol/utils';
import { useEffect, useState } from 'react';
export const useResponseTime = (url: string, trigger?: unknown) => {
const [responseTime, setResponseTime] = useState<number>();
useEffect(() => {
if (!canMeasureResponseTime(url)) return;
const duration = measureResponseTime(url);
setResponseTime(duration);
}, [url, trigger]);
return { responseTime };
};
export const canMeasureResponseTime = (url: string) => {
if (!isValidUrl(url)) return false;
if (typeof window.performance.getEntriesByName !== 'function') return false;
return true;
};
export const measureResponseTime = (url: string) => {
const requestUrl = new URL(url);
const requests = window.performance.getEntriesByName(requestUrl.href);
const { duration } = (requests.length && requests[requests.length - 1]) || {};
return duration;
};

View File

@ -21,7 +21,6 @@
"Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.": "Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.", "Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.": "Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.",
"Enter manually": "Enter manually", "Enter manually": "Enter manually",
"From account": "From account", "From account": "From account",
"Include transfer fee": "Include transfer fee",
"initial level": "initial level", "initial level": "initial level",
"maintenance level": "maintenance level", "maintenance level": "maintenance level",
"Margin health": "Margin health", "Margin health": "Margin health",
@ -33,7 +32,6 @@
"release level": "release level", "release level": "release level",
"search level": "search level", "search level": "search level",
"Select from wallet": "Select from wallet", "Select from wallet": "Select from wallet",
"The fee will be taken from the amount you are transferring.": "The fee will be taken from the amount you are transferring.",
"The total amount of each asset on this key. Includes used and available collateral.": "The total amount of each asset on this key. Includes used and available collateral.", "The total amount of each asset on this key. Includes used and available collateral.": "The total amount of each asset on this key. Includes used and available collateral.",
"The total amount taken from your account. The amount to be transferred plus the fee.": "The total amount taken from your account. The amount to be transferred plus the fee.", "The total amount taken from your account. The amount to be transferred plus the fee.": "The total amount taken from your account. The amount to be transferred plus the fee.",
"The total amount to be transferred (without the fee)": "The total amount to be transferred (without the fee)", "The total amount to be transferred (without the fee)": "The total amount to be transferred (without the fee)",

View File

@ -300,7 +300,7 @@
"Unknown": "Unknown", "Unknown": "Unknown",
"Unknown settlement date": "Unknown settlement date", "Unknown settlement date": "Unknown settlement date",
"Vega Reward pot": "Vega Reward pot", "Vega Reward pot": "Vega Reward pot",
"Vega Wallet <0>full featured<0>": "Vega Wallet <0>full featured<0>", "Vega Wallet <0>full featured</0>": "Vega Wallet <0>full featured</0>",
"Vega chart": "Vega chart", "Vega chart": "Vega chart",
"Vesting": "Vesting", "Vesting": "Vesting",
"Vesting multiplier": "Vesting multiplier", "Vesting multiplier": "Vesting multiplier",

View File

@ -113,6 +113,8 @@ export const filterAndSortClosedMarkets = (markets: MarketMaybeWithData[]) => {
return [ return [
MarketState.STATE_SETTLED, MarketState.STATE_SETTLED,
MarketState.STATE_TRADING_TERMINATED, MarketState.STATE_TRADING_TERMINATED,
MarketState.STATE_CLOSED,
MarketState.STATE_CANCELLED,
].includes(m.data?.marketState || m.state); ].includes(m.data?.marketState || m.state);
}); });
}; };

View File

@ -11,7 +11,9 @@
"echo $NX_VEGA_URL", "echo $NX_VEGA_URL",
"echo $NX_TENDERMINT_URL", "echo $NX_TENDERMINT_URL",
"echo $NX_TENDERMINT_WEBSOCKET_URL", "echo $NX_TENDERMINT_WEBSOCKET_URL",
"echo $NX_ETHEREUM_PROVIDER_URL" "echo $NX_ETHEREUM_PROVIDER_URL",
"echo $NX_CHARTING_LIBRARY_PATH",
"echo $NX_CHARTING_LIBRARY_HASH"
] ]
} }
} }

View File

@ -40,22 +40,12 @@
## Transfer ## Transfer
- **Must** can select include transfer fee (<a name="1003-TRAN-015" href="#1003-TRAN-015">1003-TRAN-015</a>)
- **Must** display tooltip for "Include transfer fee" when hovered over.(<a name="1003-TRAN-016" href="#1003-TRAN-016">1003-TRAN-016</a>)
- **Must** display tooltip for "Transfer fee when hovered over.(<a name="1003-TRAN-017" href="#1003-TRAN-017">1003-TRAN-017</a>) - **Must** display tooltip for "Transfer fee when hovered over.(<a name="1003-TRAN-017" href="#1003-TRAN-017">1003-TRAN-017</a>)
- **Must** display tooltip for "Amount to be transferred" when hovered over.(<a name="1003-TRAN-018" href="#1003-TRAN-018">1003-TRAN-018</a>) - **Must** display tooltip for "Amount to be transferred" when hovered over.(<a name="1003-TRAN-018" href="#1003-TRAN-018">1003-TRAN-018</a>)
- **Must** display tooltip for "Total amount (with fee)" when hovered over.(<a name="1003-TRAN-019" href="#1003-TRAN-019">1003-TRAN-019</a>) - **Must** display tooltip for "Total amount (with fee)" when hovered over.(<a name="1003-TRAN-019" href="#1003-TRAN-019">1003-TRAN-019</a>)
- **Must** amount to be transferred and transfer fee update correctly when include transfer fee is selected (<a name="1003-TRAN-020" href="#1003-TRAN-020">1003-TRAN-020</a>)
- **Must** total amount with fee is correct with and without "Include transfer fee" selected (<a name="1003-TRAN-021" href="#1003-TRAN-021">1003-TRAN-021</a>)
- **Must** i cannot select include transfer fee unless amount is entered (<a name="1003-TRAN-022" href="#1003-TRAN-022">1003-TRAN-022</a>)
- **Must** With all fields entered correctly, clicking "confirm transfer" button will start transaction(<a name="1003-TRAN-023" href="#1003-TRAN-023">1003-TRAN-023</a>) - **Must** With all fields entered correctly, clicking "confirm transfer" button will start transaction(<a name="1003-TRAN-023" href="#1003-TRAN-023">1003-TRAN-023</a>)
### Transfer page ### Transfer page