Compare commits

...
Author SHA1 Message Date
Maciek c8ec793b65 chore(trading): make loading masks in tabs consistent along the app (#3155) 2023-03-15 16:15:37 +01:00
Dariusz Majcherczyk 927fe2cb4a test: skip test 2023-03-15 16:11:52 +01:00
dexturr e205c9df8b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-15 16:11:52 +01:00
Maciek 2c2251e953 chore(trading): display latency in block height progressing as warn or error (#3169) 2023-03-15 16:11:52 +01:00
Dariusz Majcherczyk d73080965b test: increase coverage for AC: Connect Ethereum wallet 2023-03-15 11:56:53 +01:00
17 changed files with 523 additions and 242 deletions
+118 -32
View File
@@ -3,9 +3,9 @@
"tranche_id": 54,
"tranche_start": "2023-04-06T00:00:00.000Z",
"tranche_end": "2023-05-06T00:00:00.000Z",
"total_added": "1338",
"total_added": "1584",
"total_removed": "0",
"locked_amount": "1338",
"locked_amount": "1584",
"deposits": [
{
"amount": "111",
@@ -36,6 +36,16 @@
"amount": "111",
"user": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
"tx": "0x99aeaedef27b5fb693485f4e21d434e343b30fa100945a4ff65386de8801c87e"
},
{
"amount": "114",
"user": "0x8D416A61bCccf4E6aF5598302BC4e41f97401652",
"tx": "0x09d7342bca4e0fdc9f5a40e18e402b0328991d0df5da17b834c67b149e270808"
},
{
"amount": "132",
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tx": "0xcb0f255003872ac506798efa97744868e11ceab2f2f03d96da605d8674f783f6"
}
],
"withdrawals": [],
@@ -129,6 +139,36 @@
"total_tokens": "111",
"withdrawn_tokens": "0",
"remaining_tokens": "111"
},
{
"address": "0x8D416A61bCccf4E6aF5598302BC4e41f97401652",
"deposits": [
{
"amount": "114",
"user": "0x8D416A61bCccf4E6aF5598302BC4e41f97401652",
"tranche_id": 54,
"tx": "0x09d7342bca4e0fdc9f5a40e18e402b0328991d0df5da17b834c67b149e270808"
}
],
"withdrawals": [],
"total_tokens": "114",
"withdrawn_tokens": "0",
"remaining_tokens": "114"
},
{
"address": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"deposits": [
{
"amount": "132",
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tranche_id": 54,
"tx": "0xcb0f255003872ac506798efa97744868e11ceab2f2f03d96da605d8674f783f6"
}
],
"withdrawals": [],
"total_tokens": "132",
"withdrawn_tokens": "0",
"remaining_tokens": "132"
}
]
},
@@ -237,7 +277,7 @@
"tranche_end": "2023-04-06T00:00:00.000Z",
"total_added": "14099",
"total_removed": "22.78816420236",
"locked_amount": "9888.9977516427704009",
"locked_amount": "9775.8699107676210698",
"deposits": [
{
"amount": "30",
@@ -2930,7 +2970,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "62861.1571212942085110557",
"locked_amount": "62802.0961944541744214653",
"deposits": [
{
"amount": "86666.297",
@@ -2996,7 +3036,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "1067.9026124338625",
"locked_amount": "1064.48587581400075",
"deposits": [
{
"amount": "2500",
@@ -3117,7 +3157,7 @@
"tranche_end": "2023-09-01T00:00:00.000Z",
"total_added": "17500",
"total_removed": "0",
"locked_amount": "16144.06482739533",
"locked_amount": "16120.4076401469395",
"deposits": [
{
"amount": "12500",
@@ -3384,7 +3424,7 @@
"tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500",
"total_removed": "3992.79801615",
"locked_amount": "28745.160949969305",
"locked_amount": "28693.62674570288625",
"deposits": [
{
"amount": "7500",
@@ -3627,7 +3667,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "62803.8042249150901849635",
"locked_amount": "62744.797183739481485052",
"deposits": [
{
"amount": "129999.45",
@@ -3693,7 +3733,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "29455.15402080162128",
"locked_amount": "29412.49367706747522",
"deposits": [
{
"amount": "10000",
@@ -3886,7 +3926,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "2544.428906646372",
"locked_amount": "2541.021530948757",
"deposits": [
{
"amount": "5000",
@@ -4097,7 +4137,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "4826.8831285567535664111",
"locked_amount": "4769.0027179179875744514",
"deposits": [
{
"amount": "97499.58",
@@ -4130,7 +4170,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508",
"total_removed": "98230.390980249184455396",
"locked_amount": "6597.515910129377760093140184",
"locked_amount": "6518.4033814222176646361931672",
"deposits": [
{
"amount": "135173.4239508",
@@ -4176,7 +4216,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86",
"total_removed": "0",
"locked_amount": "2030.5863763707597222158",
"locked_amount": "2006.2370871562641268176",
"deposits": [
{
"amount": "32499.86",
@@ -4209,7 +4249,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29",
"total_removed": "0",
"locked_amount": "660.9361611648969862",
"locked_amount": "653.010703805487814995",
"deposits": [
{
"amount": "10833.29",
@@ -4242,7 +4282,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "22749.93",
"total_removed": "4720.860935375",
"locked_amount": "2470.7269878599475445005",
"locked_amount": "2441.0998581315436582191",
"deposits": [
{
"amount": "6500",
@@ -4394,7 +4434,7 @@
"tranche_end": "2023-05-01T00:00:00.000Z",
"total_added": "22500",
"total_removed": "5529.83377455",
"locked_amount": "5810.63248158379425",
"locked_amount": "5779.71195902394075",
"deposits": [
{
"amount": "7500",
@@ -4757,7 +4797,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "928642.9598472029154",
"locked_amount": "418510.6360640652716794746",
"locked_amount": "417188.6231006399309246128",
"deposits": [
{
"amount": "1852091.69",
@@ -10200,7 +10240,7 @@
"tranche_start": "2021-09-03T00:00:00.000Z",
"tranche_end": "2022-09-03T00:00:00.000Z",
"total_added": "56798.000000000000000003",
"total_removed": "44482.21518131551",
"total_removed": "48097.21518131551",
"locked_amount": "0",
"deposits": [
{
@@ -20725,6 +20765,21 @@
"user": "0xC6D7208DaDEe4F431bd0f3f11E7d4c91fF51bfb2",
"tx": "0xc1d1e0a03a5ace0a8c61f58c81a407867a2d3195d2249df11d2eb630949d4ea2"
},
{
"amount": "1925",
"user": "0xA260d0f8070b2A85B3C00ADb1f0046f20ca85AAa",
"tx": "0xd2190b942a3b15e97692b9007c71b2cf3b887aaff782a73b23b9dfa7369b9c84"
},
{
"amount": "1690",
"user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f",
"tx": "0x3e3247a9cf87d30ca8436606fe8f0cc01cba11fbd798a5aff4f8a7bbf3247af5"
},
{
"amount": "0",
"user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f",
"tx": "0x11c2f143f1fccec8d746a0760ada1f4abf9fbce7ee76835a8da17bcfc4d2c7f5"
},
{
"amount": "130",
"user": "0x94097462EF7c43D0aC732E7B18f830096D95207C",
@@ -23503,10 +23558,17 @@
"tx": "0x62b105e7c569b195b3a3625869c62e4cd307fbda5b0c1e5931a857e376ef6c09"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "1925",
"user": "0xA260d0f8070b2A85B3C00ADb1f0046f20ca85AAa",
"tranche_id": 11,
"tx": "0xd2190b942a3b15e97692b9007c71b2cf3b887aaff782a73b23b9dfa7369b9c84"
}
],
"total_tokens": "1925",
"withdrawn_tokens": "0",
"remaining_tokens": "1925"
"withdrawn_tokens": "1925",
"remaining_tokens": "0"
},
{
"address": "0xC6D7208DaDEe4F431bd0f3f11E7d4c91fF51bfb2",
@@ -32337,10 +32399,23 @@
"tx": "0x248d34bdf3224997d3f0e2cd55f75e1b13937591a64cff99abf513bada6da087"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "1690",
"user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f",
"tranche_id": 11,
"tx": "0x3e3247a9cf87d30ca8436606fe8f0cc01cba11fbd798a5aff4f8a7bbf3247af5"
},
{
"amount": "0",
"user": "0xb4eE687f019A8e48F7087f4b4f8653208B8cc48f",
"tranche_id": 11,
"tx": "0x11c2f143f1fccec8d746a0760ada1f4abf9fbce7ee76835a8da17bcfc4d2c7f5"
}
],
"total_tokens": "1690",
"withdrawn_tokens": "0",
"remaining_tokens": "1690"
"withdrawn_tokens": "1690",
"remaining_tokens": "0"
},
{
"address": "0xfeDf4b4406B3126744047be05A013570b09DC0cC",
@@ -38221,7 +38296,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "700133.348465855088393",
"locked_amount": "667606.462432296455376981036",
"locked_amount": "665574.988783539092352126032",
"deposits": [
{
"amount": "1998.95815",
@@ -39603,7 +39678,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "803126.94523812819680452",
"locked_amount": "7666977.23699395663856772391215730126493643",
"locked_amount": "7659773.76186853928704096963886072002784536",
"deposits": [
{
"amount": "16249.93",
@@ -45730,7 +45805,7 @@
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "5735873.982235800798313006",
"locked_amount": "1356659.204790641720440640129577783",
"locked_amount": "1350009.002549479199448998084455602",
"deposits": [
{
"amount": "129284.449",
@@ -53074,7 +53149,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "3362269.76579626473241024",
"locked_amount": "219120.853279161821111689561756935",
"locked_amount": "216493.318153589487100687138655917",
"deposits": [
{
"amount": "552496.6455",
@@ -55179,8 +55254,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "37637.6576293990685",
"locked_amount": "105786.04519533285014909089193304",
"total_removed": "37672.6965943910685",
"locked_amount": "105464.16156158729024788626382548",
"deposits": [
{
"amount": "3000",
@@ -61834,6 +61909,11 @@
"user": "0x1010101036EA85caAC4BB82D953f26cAf14a9Ebc",
"tx": "0xd6774c065c0e886d82445f16909e9dec106e3c6714cad682ed4102470403aace"
},
{
"amount": "35.038964992",
"user": "0x046717d8914087EB6890dFdF78ebD0f1343F15F6",
"tx": "0xa90a66881bf15cc988e22cebc0b0fa29e6421ddda8480c13b32dc3ca0a784ea1"
},
{
"amount": "129.867928716",
"user": "0x09996E1a67c371400bD5f52DF841f77B1741eB9d",
@@ -77080,6 +77160,12 @@
}
],
"withdrawals": [
{
"amount": "35.038964992",
"user": "0x046717d8914087EB6890dFdF78ebD0f1343F15F6",
"tranche_id": 5,
"tx": "0xa90a66881bf15cc988e22cebc0b0fa29e6421ddda8480c13b32dc3ca0a784ea1"
},
{
"amount": "84.425145866",
"user": "0x046717d8914087EB6890dFdF78ebD0f1343F15F6",
@@ -77094,8 +77180,8 @@
}
],
"total_tokens": "200",
"withdrawn_tokens": "120.243144342",
"remaining_tokens": "79.756855658"
"withdrawn_tokens": "155.282109334",
"remaining_tokens": "44.717890666"
},
{
"address": "0x8847EBaaf29A18396e49191602f8d8D141b98aa7",
@@ -71,7 +71,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(3, 'Quote Name', 'BTC');
});
it('settlement asset displayed', () => {
// need to check why data are not visible
it.skip('settlement asset displayed', () => {
cy.getByTestId(marketTitle).contains('Settlement asset').click();
cy.window().then((win) => {
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
@@ -0,0 +1,58 @@
import { connectEthereumWallet } from '../support/ethereum-wallet';
const connectEthWalletBtn = 'connect-eth-wallet-btn';
describe('ethereum wallet', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
it('can connect', () => {
// 0004-EWAL-001
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('should see an option to cancel the attempted connection', () => {
// 0004-EWAL-003
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-WalletConnect').click();
cy.get('#walletconnect-qrcode-text').should('exist');
cy.get('#walletconnect-qrcode-close').click();
});
it('able to disconnect eth wallet', () => {
// 0004-EWAL-004
// 0004-EWAL-005
// 0004-EWAL-006
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
cy.getByTestId(connectEthWalletBtn).should('exist');
});
});
@@ -1,7 +1,8 @@
import { mockConnectWallet } from '@vegaprotocol/cypress';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import {
mockConnectWallet,
mockConnectWalletWithUserError,
} from '@vegaprotocol/cypress';
const connectEthWalletBtn = 'connect-eth-wallet-btn';
const connectVegaBtn = 'connect-vega-wallet';
const manageVegaBtn = 'manage-vega-wallet';
const form = 'rest-connector-form';
@@ -127,6 +128,21 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
cy.getByTestId(manageVegaBtn).should('exist');
});
it.only('can not connect', () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
mockConnectWalletWithUserError();
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.getByTestId('dialog-content')
.should('contain.text', 'User error')
.and('contain.text', 'the user rejected the wallet connection');
});
it('can change selected public key and disconnect', () => {
// 0002-WCON-022
// 0002-WCON-023
@@ -164,39 +180,3 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
);
});
});
describe('ethereum wallet', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
it('can connect', () => {
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('able to disconnect eth wallet', () => {
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
cy.getByTestId(connectEthWalletBtn).should('exist');
});
});
@@ -25,7 +25,8 @@ export const DepositsContainer = () => {
<div className="h-full relative">
<DepositsTable
rowData={data || []}
noRowsOverlayComponent={() => null}
suppressLoadingOverlay
suppressNoRowsOverlay
ref={gridRef}
{...bottomPlaceholderProps}
/>
@@ -24,7 +24,8 @@ export const WithdrawalsContainer = () => {
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
noRowsOverlayComponent={() => null}
suppressLoadingOverlay
suppressNoRowsOverlay
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
+33 -15
View File
@@ -1,20 +1,30 @@
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NodeHealth, NodeUrl, HealthIndicator } from './footer';
import { MockedProvider } from '@apollo/client/testing';
import { Intent } from '@vegaprotocol/ui-toolkit';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
useEnvironment: jest
.fn()
.mockImplementation(() => ({ VEGA_URL: 'https://vega-url.wtf' })),
}));
const mockSetNodeSwitcher = jest.fn();
jest.mock('../../stores', () => ({
...jest.requireActual('../../stores'),
useGlobalStore: () => mockSetNodeSwitcher,
}));
describe('NodeHealth', () => {
it('controls the node switcher dialog', async () => {
const mockOnClick = jest.fn();
render(
<NodeHealth
onClick={mockOnClick}
url={'https://api.n99.somenetwork.vega.xyz'}
blockHeight={100}
blockDiff={0}
/>
);
render(<NodeHealth />, { wrapper: MockedProvider });
await waitFor(() => {
expect(screen.getByRole('button')).toBeInTheDocument();
});
await userEvent.click(screen.getByRole('button'));
expect(mockOnClick).toHaveBeenCalled();
expect(mockSetNodeSwitcher).toHaveBeenCalled();
});
});
@@ -31,14 +41,22 @@ describe('NodeUrl', () => {
describe('HealthIndicator', () => {
const cases = [
{ diff: 0, classname: 'bg-vega-green-550', text: 'Operational' },
{ diff: 5, classname: 'bg-warning', text: '5 Blocks behind' },
{ diff: null, classname: 'bg-danger', text: 'Non operational' },
{
intent: Intent.Success,
text: 'Operational',
classname: 'bg-vega-green-550',
},
{
intent: Intent.Warning,
text: '5 Blocks behind',
classname: 'bg-warning',
},
{ intent: Intent.Danger, text: 'Non operational', classname: 'bg-danger' },
];
it.each(cases)(
'renders correct text and indicator color for $diff block difference',
(elem) => {
render(<HealthIndicator blockDiff={elem.diff} />);
render(<HealthIndicator text={elem.text} intent={elem.intent} />);
expect(screen.getByTestId('indicator')).toHaveClass(elem.classname);
expect(screen.getByText(elem.text)).toBeInTheDocument();
}
+21 -56
View File
@@ -1,60 +1,45 @@
import { useCallback } from 'react';
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment';
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import { Indicator, Intent } from '@vegaprotocol/ui-toolkit';
import type { Intent } from '@vegaprotocol/ui-toolkit';
import { Indicator } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { useGlobalStore } from '../../stores';
export const Footer = () => {
const { VEGA_URL } = useEnvironment();
const setNodeSwitcher = useGlobalStore(
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
);
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
return (
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 lg:fixed bottom-0 left-0 border-r bg-white dark:bg-black">
{/* Pull left to align with top nav, due to button padding */}
<div className="-ml-2">
{VEGA_URL && (
<NodeHealth
url={VEGA_URL}
blockHeight={datanodeBlockHeight}
blockDiff={blockDiff}
onClick={() => setNodeSwitcher(true)}
/>
)}
<NodeHealth />
</div>
</footer>
);
};
interface NodeHealthProps {
url: string;
blockHeight: number | undefined;
blockDiff: number | null;
onClick: () => void;
}
export const NodeHealth = ({
url,
blockHeight,
blockDiff,
onClick,
}: NodeHealthProps) => {
return (
export const NodeHealth = () => {
const { VEGA_URL } = useEnvironment();
const setNodeSwitcher = useGlobalStore(
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
);
const { datanodeBlockHeight, text, intent } = useNodeHealth();
const onClick = useCallback(() => {
setNodeSwitcher(true);
}, [setNodeSwitcher]);
return VEGA_URL ? (
<FooterButton onClick={onClick} data-testid="node-health">
<FooterButtonPart>
<HealthIndicator blockDiff={blockDiff} />
<HealthIndicator text={text} intent={intent} />
</FooterButtonPart>
<FooterButtonPart>
<NodeUrl url={url} />
<NodeUrl url={VEGA_URL} />
</FooterButtonPart>
<FooterButtonPart>
<span title={t('Block height')}>{blockHeight}</span>
<span title={t('Block height')}>{datanodeBlockHeight}</span>
</FooterButtonPart>
</FooterButton>
);
) : null;
};
interface NodeUrlProps {
@@ -69,31 +54,11 @@ export const NodeUrl = ({ url }: NodeUrlProps) => {
};
interface HealthIndicatorProps {
blockDiff: number | null;
text: string;
intent: Intent;
}
// How many blocks behind the most advanced block that is
// deemed acceptable for "Good" status
const BLOCK_THRESHOLD = 3;
export const HealthIndicator = ({ blockDiff }: HealthIndicatorProps) => {
const online = useNavigatorOnline();
let intent = Intent.Success;
let text = 'Operational';
if (!online) {
text = t('Offline');
intent = Intent.Danger;
} else if (blockDiff === null) {
// Block height query failed and null was returned
text = t('Non operational');
intent = Intent.Danger;
} else if (blockDiff >= BLOCK_THRESHOLD) {
text = t(`${blockDiff} Blocks behind`);
intent = Intent.Warning;
}
export const HealthIndicator = ({ text, intent }: HealthIndicatorProps) => {
return (
<span title={t('Node health')}>
<Indicator variant={intent} />
+87 -51
View File
@@ -15,51 +15,29 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
}));
describe('AccountManager', () => {
beforeEach(() => {
mockedUseDataProvider
.mockImplementationOnce((args) => {
return {
data: [],
};
})
.mockImplementationOnce((args) => {
return {
data: [
{ asset: { id: 'a1' }, party: { id: 't1' } },
{ asset: { id: 'a2' }, party: { id: 't2' } },
],
};
});
});
it('change partyId should reload data provider', async () => {
const { rerender } = render(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables.partyId
).toEqual('partyOne');
await act(() => {
rerender(
<AccountManager
partyId="partyTwo"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
describe('when rerender', () => {
beforeEach(() => {
mockedUseDataProvider
.mockImplementationOnce((args) => {
return {
data: [],
};
})
.mockImplementationOnce((args) => {
return {
data: [
{ asset: { id: 'a1' }, party: { id: 't1' } },
{ asset: { id: 'a2' }, party: { id: 't2' } },
],
};
});
});
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables.partyId
).toEqual('partyTwo');
});
it('update method should return proper result', async () => {
let rerenderer: (ui: React.ReactElement) => void;
await act(() => {
afterEach(() => {
jest.clearAllMocks();
});
it('change partyId should reload data provider', async () => {
const { rerender } = render(
<AccountManager
partyId="partyOne"
@@ -67,13 +45,67 @@ describe('AccountManager', () => {
isReadOnly={false}
/>
);
rerenderer = rerender;
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables
.partyId
).toEqual('partyOne');
await act(() => {
rerender(
<AccountManager
partyId="partyTwo"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
});
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables
.partyId
).toEqual('partyTwo');
});
await waitFor(() => {
expect(screen.getByText('No accounts')).toBeInTheDocument();
it('update method should return proper result', async () => {
let rerenderer: (ui: React.ReactElement) => void;
await act(() => {
const { rerender } = render(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
rerenderer = rerender;
});
await waitFor(() => {
expect(screen.getByText('No accounts')).toBeInTheDocument();
});
await act(() => {
rerenderer(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
});
const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => {
expect(container).toBeInTheDocument();
});
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
});
});
it('splash loading should be displayed', async () => {
mockedUseDataProvider.mockImplementation((args) => {
return {
loading: true,
data: null,
};
});
await act(() => {
rerenderer(
render(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
@@ -81,11 +113,15 @@ describe('AccountManager', () => {
/>
);
});
const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => {
expect(container).toBeInTheDocument();
expect(
screen.getByText(
(content, element) =>
Boolean(
element?.className.endsWith('flex items-center justify-center')
) && content.startsWith('Loading')
)
).toBeInTheDocument();
});
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
});
});
+2 -1
View File
@@ -60,7 +60,8 @@ export const AccountManager = ({
onClickDeposit={onClickDeposit}
onClickWithdraw={onClickWithdraw}
isReadOnly={isReadOnly}
noRowsOverlayComponent={() => null}
suppressLoadingOverlay
suppressNoRowsOverlay
pinnedAsset={pinnedAsset}
getRowHeight={getRowHeight}
{...bottomPlaceholderProps}
+13 -7
View File
@@ -59,13 +59,19 @@ export function createClient({
const timestamp = r?.headers.get('x-block-timestamp');
if (blockHeight && timestamp) {
const state = useHeaderStore.getState();
useHeaderStore.setState({
...state,
[r.url]: {
blockHeight: Number(blockHeight),
timestamp: new Date(Number(timestamp.slice(0, -6))),
},
});
const urlState = state[r.url];
if (
!urlState?.blockHeight ||
urlState.blockHeight !== blockHeight
) {
useHeaderStore.setState({
...state,
[r.url]: {
blockHeight: Number(blockHeight),
timestamp: new Date(Number(timestamp.slice(0, -6))),
},
});
}
}
return response;
});
+4 -1
View File
@@ -46,7 +46,10 @@ addVegaWalletSubmitProposal();
addVegaWalletSubmitLiquidityProvision();
addImportNodeWallets();
export { mockConnectWallet } from './lib/commands/vega-wallet-connect';
export {
mockConnectWallet,
mockConnectWalletWithUserError,
} from './lib/commands/vega-wallet-connect';
export type { onMessage } from './lib/mock-ws';
export { aliasGQLQuery } from './lib/mock-gql';
export { aliasWalletQuery } from './lib/mock-rest';
@@ -1,4 +1,7 @@
import { aliasWalletConnectQuery } from '../mock-rest';
import {
aliasWalletConnectQuery,
aliasWalletConnectWithUserError,
} from '../mock-rest';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@@ -20,6 +23,12 @@ export const mockConnectWallet = () => {
});
};
export const mockConnectWalletWithUserError = () => {
cy.mockWallet((req) => {
aliasWalletConnectWithUserError(req, Cypress.env('VEGA_WALLET_API_TOKEN'));
});
};
export function addVegaWalletConnect() {
Cypress.Commands.add('connectVegaWallet', (isMobile) => {
mockConnectWallet();
+21
View File
@@ -63,3 +63,24 @@ export const aliasWalletConnectQuery = (
});
}
};
export const aliasWalletConnectWithUserError = (
req: CyHttpMessages.IncomingHttpRequest,
token: string
) => {
if (hasMethod(req, 'client.connect_wallet')) {
req.alias = 'client.connect_wallet';
req.reply({
statusCode: 400,
body: {
jsonrpc: '2.0',
error: {
code: 3001,
data: 'the user rejected the wallet connection',
message: 'User error',
},
id: '0',
},
});
}
};
@@ -5,6 +5,7 @@ import { MockedProvider } from '@apollo/react-testing';
import type { StatisticsQuery } from '../utils/__generated__/Node';
import { StatisticsDocument } from '../utils/__generated__/Node';
import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { Intent } from '@vegaprotocol/ui-toolkit';
const vegaUrl = 'https://foo.bar.com';
@@ -55,9 +56,24 @@ function setup(
describe('useNodeHealth', () => {
it.each([
{ core: 1, node: 1, expected: 0 },
{ core: 1, node: 5, expected: -4 },
{ core: 10, node: 5, expected: 5 },
{
core: 1,
node: 1,
expectedText: 'Operational',
expectedIntent: Intent.Success,
},
{
core: 1,
node: 5,
expectedText: 'Operational',
expectedIntent: Intent.Success,
},
{
core: 10,
node: 5,
expectedText: '5 Blocks behind',
expectedIntent: Intent.Warning,
},
])(
'provides difference core block $core and node block $node',
async (cases) => {
@@ -65,12 +81,12 @@ describe('useNodeHealth', () => {
blockHeight: cases.node,
timestamp: new Date(),
});
expect(result.current.blockDiff).toEqual(null);
expect(result.current.coreBlockHeight).toEqual(undefined);
expect(result.current.text).toEqual('Non operational');
expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(cases.node);
await waitFor(() => {
expect(result.current.blockDiff).toEqual(cases.expected);
expect(result.current.coreBlockHeight).toEqual(cases.core);
expect(result.current.text).toEqual(cases.expectedText);
expect(result.current.intent).toEqual(cases.expectedIntent);
expect(result.current.datanodeBlockHeight).toEqual(cases.node);
});
}
@@ -90,25 +106,64 @@ describe('useNodeHealth', () => {
blockHeight: 1,
timestamp: new Date(),
});
expect(result.current.blockDiff).toEqual(null);
expect(result.current.coreBlockHeight).toEqual(undefined);
expect(result.current.text).toEqual('Non operational');
expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(1);
await waitFor(() => {
expect(result.current.blockDiff).toEqual(null);
expect(result.current.coreBlockHeight).toEqual(undefined);
expect(result.current.text).toEqual('Non operational');
expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(1);
});
});
it('returns 0 if no headers are found (waits until stats query resolves)', async () => {
const { result } = setup(createStatsMock(1), undefined);
expect(result.current.blockDiff).toEqual(null);
expect(result.current.coreBlockHeight).toEqual(undefined);
expect(result.current.text).toEqual('Non operational');
expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(undefined);
await waitFor(() => {
expect(result.current.blockDiff).toEqual(0);
expect(result.current.coreBlockHeight).toEqual(1);
expect(result.current.text).toEqual('Operational');
expect(result.current.intent).toEqual(Intent.Success);
expect(result.current.datanodeBlockHeight).toEqual(undefined);
});
});
it('Warning latency', async () => {
const now = 1678800900087;
const headerTimestamp = now - 4000;
const dateNow = new Date(now);
const dateHeaderTimestamp = new Date(headerTimestamp);
jest.useFakeTimers().setSystemTime(dateNow);
const { result } = setup(createStatsMock(2), {
blockHeight: 2,
timestamp: dateHeaderTimestamp,
});
await waitFor(() => {
expect(result.current.text).toEqual('Warning delay ( >3 sec): 4.05 sec');
expect(result.current.intent).toEqual(Intent.Warning);
expect(result.current.datanodeBlockHeight).toEqual(2);
});
});
it('Erroneous latency', async () => {
const now = 1678800900087;
const headerTimestamp = now - 11000;
const dateNow = new Date(now);
const dateHeaderTimestamp = new Date(headerTimestamp);
jest.useFakeTimers().setSystemTime(dateNow);
const { result } = setup(createStatsMock(2), {
blockHeight: 2,
timestamp: dateHeaderTimestamp,
});
await waitFor(() => {
expect(result.current.text).toEqual(
'Erroneous latency ( >10 sec): 11.05 sec'
);
expect(result.current.intent).toEqual(Intent.Danger);
expect(result.current.datanodeBlockHeight).toEqual(2);
});
jest.useRealTimers();
});
});
+48 -17
View File
@@ -2,30 +2,35 @@ import { useEffect, useMemo } from 'react';
import { useStatisticsQuery } from '../utils/__generated__/Node';
import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { useEnvironment } from './use-environment';
import { fromNanoSeconds } from '@vegaprotocol/utils';
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
const POLL_INTERVAL = 1000;
const BLOCK_THRESHOLD = 3;
const ERROR_LATENCY = 10000;
const WARNING_LATENCY = 3000;
export const useNodeHealth = () => {
const online = useNavigatorOnline();
const url = useEnvironment((store) => store.VEGA_URL);
const headerStore = useHeaderStore();
const headers = url ? headerStore[url] : undefined;
const { data, error, loading, startPolling, stopPolling } =
useStatisticsQuery({
fetchPolicy: 'no-cache',
});
const { data, error, startPolling, stopPolling } = useStatisticsQuery({
fetchPolicy: 'no-cache',
});
const blockDiff = useMemo(() => {
if (!data?.statistics.blockHeight) {
return null;
}
if (!headers) {
if (!headers?.blockHeight) {
return 0;
}
return Number(data.statistics.blockHeight) - headers.blockHeight;
}, [data, headers]);
}, [data?.statistics.blockHeight, headers?.blockHeight]);
useEffect(() => {
if (error) {
@@ -38,17 +43,43 @@ export const useNodeHealth = () => {
}
}, [error, startPolling, stopPolling]);
const blockUpdateMsLatency = headers?.timestamp
? Date.now() - headers.timestamp.getTime()
: 0;
const [text, intent] = useMemo(() => {
let intent = Intent.Success;
let text = 'Operational';
if (!online) {
text = t('Offline');
intent = Intent.Danger;
} else if (blockDiff === null) {
// Block height query failed and null was returned
text = t('Non operational');
intent = Intent.Danger;
} else if (blockUpdateMsLatency > ERROR_LATENCY) {
text = t('Erroneous latency ( >%s sec): %s sec', [
(ERROR_LATENCY / 1000).toString(),
(blockUpdateMsLatency / 1000).toFixed(2),
]);
intent = Intent.Danger;
} else if (blockDiff >= BLOCK_THRESHOLD) {
text = t(`%s Blocks behind`, String(blockDiff));
intent = Intent.Warning;
} else if (blockUpdateMsLatency > WARNING_LATENCY) {
text = t('Warning delay ( >%s sec): %s sec', [
(WARNING_LATENCY / 1000).toString(),
(blockUpdateMsLatency / 1000).toFixed(2),
]);
intent = Intent.Warning;
}
return [text, intent];
}, [online, blockDiff, blockUpdateMsLatency]);
return {
error,
loading,
coreBlockHeight: data?.statistics
? Number(data.statistics.blockHeight)
: undefined,
coreVegaTime: data?.statistics
? fromNanoSeconds(data?.statistics.vegaTime)
: undefined,
datanodeBlockHeight: headers?.blockHeight,
datanodeVegaTime: headers?.timestamp,
blockDiff,
text,
intent,
};
};
+13 -4
View File
@@ -1,7 +1,8 @@
import { useCallback, useRef } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { Position } from '../';
import { usePositionsData, PositionsTable } from '../';
import type { FilterChangedEvent } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import * as Schema from '@vegaprotocol/types';
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
@@ -22,6 +23,7 @@ export const PositionsManager = ({
noBottomPlaceholder,
}: PositionsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0);
const { data, error, loading, reload } = usePositionsData(
partyId,
gridRef,
@@ -67,7 +69,12 @@ export const PositionsManager = ({
gridRef,
setId,
});
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [data]);
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
return (
<div className="h-full relative">
<PositionsTable
@@ -75,8 +82,10 @@ export const PositionsManager = ({
ref={gridRef}
onMarketClick={onMarketClick}
onClose={onClose}
noRowsOverlayComponent={() => null}
suppressLoadingOverlay
suppressNoRowsOverlay
isReadOnly={isReadOnly}
onFilterChanged={onFilterChanged}
{...(noBottomPlaceholder ? null : bottomPlaceholderProps)}
/>
<div className="pointer-events-none absolute inset-0">
@@ -85,7 +94,7 @@ export const PositionsManager = ({
error={error}
data={data}
noDataMessage={t('No positions')}
noDataCondition={(data) => !(data && data.length)}
noDataCondition={(data) => !dataCount}
reload={reload}
/>
</div>