Compare commits

..
13 changed files with 137778 additions and 138014 deletions
+1 -15
View File
@@ -1,17 +1,3 @@
{
"hosts": [
"https://vega-data-graphql.chorus.one/query",
"https://vega.xprv.io/datanode/query",
"http://nala.mainnet.vega.community:3008/query",
"http://commodum.mainnet.vega.community:3008/query",
"http://lovali.mainnet.vega.community:3008/query",
"http://b-harvest.mainnet.vega.community:3008/query",
"http://staking-facilities.mainnet.vega.community:3008/query",
"http://figment.mainnet.vega.community:3008/query",
"http://nodes-guru.mainnet.vega.community:3008/query",
"http://p2p.mainnet.vega.community:3008/query",
"http://rockaway.mainnet.vega.community:3008/query",
"http://greenfield-one.mainnet.vega.community:3008/query",
"http://ryabina.mainnet.vega.community:3008/query"
]
"hosts": ["https://vega.xprv.io/datanode/query"]
}
File diff suppressed because it is too large Load Diff
@@ -190,24 +190,6 @@ describe('capsule', { tags: '@slow' }, () => {
cy.setVegaWallet();
});
it('shows node health', function () {
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId('node-health')
.children()
.first()
.should('contain.text', 'Operational')
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).origin)
.next()
.then(($el) => {
const blockHeight = parseInt($el.text());
// block height will increase over the course of the test run so best
// we can do here is check that its showing something sensible
expect(blockHeight).to.be.greaterThan(0);
});
});
it('can place and receive an order', function () {
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
@@ -304,18 +304,4 @@ describe('home', { tags: '@regression' }, () => {
});
});
});
describe('footer', () => {
it('shows current block height', () => {
cy.visit('/');
cy.getByTestId('node-health')
.children()
.first()
.should('contain.text', 'Operational')
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).origin)
.next()
.should('contain.text', '100'); // all mocked queries have x-block-height header set to 100
});
});
});
+17 -25
View File
@@ -1,35 +1,20 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NodeHealth, NodeUrl, HealthIndicator } from './footer';
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}
/>
);
await userEvent.click(screen.getByRole('button'));
expect(mockOnClick).toHaveBeenCalled();
});
});
import { fireEvent, render, screen } from '@testing-library/react';
import { NodeUrl, NodeHealth } from './footer';
describe('NodeUrl', () => {
it('renders correct part of node url', () => {
it('can open node switcher by clicking the node url', () => {
const mockOpenNodeSwitcher = jest.fn();
const node = 'https://api.n99.somenetwork.vega.xyz';
const expectedText = node.split('.').slice(1).join('.');
render(<NodeUrl url={node} />);
render(<NodeUrl url={node} openNodeSwitcher={mockOpenNodeSwitcher} />);
expect(screen.getByText(expectedText)).toBeInTheDocument();
fireEvent.click(screen.getByText(/n99/));
expect(mockOpenNodeSwitcher).toHaveBeenCalled();
});
});
describe('HealthIndicator', () => {
describe('NodeHealth', () => {
const mockOpenNodeSwitcher = jest.fn();
const cases = [
{ diff: 0, classname: 'bg-vega-green-550', text: 'Operational' },
{ diff: 5, classname: 'bg-warning', text: '5 Blocks behind' },
@@ -38,9 +23,16 @@ describe('HealthIndicator', () => {
it.each(cases)(
'renders correct text and indicator color for $diff block difference',
(elem) => {
render(<HealthIndicator blockDiff={elem.diff} />);
render(
<NodeHealth
blockDiff={elem.diff}
openNodeSwitcher={mockOpenNodeSwitcher}
/>
);
expect(screen.getByTestId('indicator')).toHaveClass(elem.classname);
expect(screen.getByText(elem.text)).toBeInTheDocument();
fireEvent.click(screen.getByText(elem.text));
expect(mockOpenNodeSwitcher).toHaveBeenCalled();
}
);
});
+31 -76
View File
@@ -1,8 +1,6 @@
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment';
import { t, useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { Indicator, Intent } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { ButtonLink, Indicator, Intent } from '@vegaprotocol/ui-toolkit';
import { useGlobalStore } from '../../stores';
export const Footer = () => {
@@ -10,64 +8,45 @@ export const Footer = () => {
const setNodeSwitcher = useGlobalStore(
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
);
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
const { blockDiff } = useNodeHealth();
return (
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300">
{/* 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)}
/>
)}
<footer className="px-4 py-1 text-xs border-t border-default">
<div className="flex justify-between">
<div className="flex gap-2">
{VEGA_URL && (
<>
<NodeHealth
blockDiff={blockDiff}
openNodeSwitcher={() => setNodeSwitcher(true)}
/>
{' | '}
<NodeUrl
url={VEGA_URL}
openNodeSwitcher={() => setNodeSwitcher(true)}
/>
</>
)}
</div>
</div>
</footer>
);
};
interface NodeHealthProps {
url: string;
blockHeight: number | undefined;
blockDiff: number | null;
onClick: () => void;
}
export const NodeHealth = ({
url,
blockHeight,
blockDiff,
onClick,
}: NodeHealthProps) => {
return (
<FooterButton onClick={onClick} data-testid="node-health">
<FooterButtonPart>
<HealthIndicator blockDiff={blockDiff} />
</FooterButtonPart>
<FooterButtonPart>
<NodeUrl url={url} />
</FooterButtonPart>
<FooterButtonPart>
<span title={t('Block height')}>{blockHeight}</span>
</FooterButtonPart>
</FooterButton>
);
};
interface NodeUrlProps {
url: string;
openNodeSwitcher: () => void;
}
export const NodeUrl = ({ url }: NodeUrlProps) => {
export const NodeUrl = ({ url, openNodeSwitcher }: NodeUrlProps) => {
// get base url from api url, api sub domain
const urlObj = new URL(url);
const nodeUrl = urlObj.origin.replace(/^[^.]+\./g, '');
return <span title={t('Connected node')}>{nodeUrl}</span>;
return <ButtonLink onClick={openNodeSwitcher}>{nodeUrl}</ButtonLink>;
};
interface HealthIndicatorProps {
interface NodeHealthProps {
openNodeSwitcher: () => void;
blockDiff: number | null;
}
@@ -75,7 +54,10 @@ interface HealthIndicatorProps {
// deemed acceptable for "Good" status
const BLOCK_THRESHOLD = 3;
export const HealthIndicator = ({ blockDiff }: HealthIndicatorProps) => {
export const NodeHealth = ({
blockDiff,
openNodeSwitcher,
}: NodeHealthProps) => {
const online = useNavigatorOnline();
let intent = Intent.Success;
@@ -94,36 +76,9 @@ export const HealthIndicator = ({ blockDiff }: HealthIndicatorProps) => {
}
return (
<span title={t('Node health')}>
<>
<Indicator variant={intent} />
{text}
</span>
);
};
type FooterButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
const FooterButton = (props: FooterButtonProps) => {
const buttonClasses = classNames(
'px-2 py-0.5 rounded-md',
'enabled:hover:bg-vega-light-150',
'dark:enabled:hover:bg-vega-dark-150'
);
return <button {...props} className={buttonClasses} />;
};
const FooterButtonPart = ({ children }: { children: ReactNode }) => {
return (
<span
className={classNames(
'relative inline-block mr-2 last:mr-0 pr-2 last:pr-0',
'last:after:hidden',
'after:content after:absolute after:right-0 after:top-1/2 after:-translate-y-1/2',
'after:h-3 after:w-1 after:border-r',
'after:border-vega-light-300 dark:after:border-vega-dark-300'
)}
>
{children}
</span>
<ButtonLink onClick={openNodeSwitcher}>{text}</ButtonLink>
</>
);
};
+1 -9
View File
@@ -32,8 +32,7 @@ export const aliasGQLQuery = (
operationName: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?: any,
errors?: Partial<GraphQLError>[],
headers?: Record<string, string>
errors?: Partial<GraphQLError>[]
) => {
if (hasOperationName(req, operationName)) {
req.alias = operationName;
@@ -41,13 +40,6 @@ export const aliasGQLQuery = (
req.reply({
statusCode: 200,
body: { ...(data && { data }), ...(errors && { errors }) },
headers: {
...req.headers,
// basic default block height header response
'x-block-height': '100',
'x-block-timestamp': Date.now().toString() + '0'.repeat(6),
...headers,
},
});
}
}
+5 -12
View File
@@ -4,16 +4,14 @@ import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { useEnvironment } from './use-environment';
import { fromNanoSeconds } from '@vegaprotocol/react-helpers';
const POLL_INTERVAL = 1000;
export const useNodeHealth = () => {
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, loading, stopPolling } = useStatisticsQuery({
pollInterval: 1000,
fetchPolicy: 'no-cache',
});
const blockDiff = useMemo(() => {
if (!data?.statistics.blockHeight) {
@@ -30,13 +28,8 @@ export const useNodeHealth = () => {
useEffect(() => {
if (error) {
stopPolling();
return;
}
if (!('Cypress' in window)) {
startPolling(POLL_INTERVAL);
}
}, [error, startPolling, stopPolling]);
}, [error, stopPolling]);
return {
error,
+6 -5
View File
@@ -1,10 +1,11 @@
import React from 'react';
import {
PriceCell,
VolCell,
Vol,
CumulativeVol,
addDecimalsFormatNumber,
VolumeType,
addDecimal,
} from '@vegaprotocol/react-helpers';
interface OrderbookRowProps {
@@ -41,17 +42,17 @@ export const OrderbookRow = React.memo(
}: OrderbookRowProps) => {
return (
<>
<VolCell
<Vol
testId={`bid-vol-${price}`}
value={bid}
valueFormatted={addDecimalsFormatNumber(bid, positionDecimalPlaces)}
valueFormatted={addDecimal(bid, positionDecimalPlaces)}
relativeValue={relativeBid}
type={VolumeType.bid}
/>
<VolCell
<Vol
testId={`ask-vol-${price}`}
value={ask}
valueFormatted={addDecimalsFormatNumber(ask, positionDecimalPlaces)}
valueFormatted={addDecimal(ask, positionDecimalPlaces)}
relativeValue={relativeAsk}
type={VolumeType.ask}
/>
@@ -1,4 +1,6 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { NumericCell } from './numeric-cell';
describe('NumericCell', () => {
@@ -1,4 +1,6 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { PriceFlashCell } from './price-flash-cell';
describe('<PriceFlashCell />', () => {
@@ -1,49 +0,0 @@
import { render, screen } from '@testing-library/react';
import { VolCell, VolumeType } from './vol-cell';
import * as tailwind from '@vegaprotocol/tailwindcss-config';
describe('VolCell', () => {
const significantPart = '12,345';
const decimalPart = '67';
const props = {
value: 1234567,
valueFormatted: `${significantPart}.${decimalPart}`,
type: VolumeType.ask,
testId: 'cell',
};
it('Displays formatted value', () => {
render(<VolCell {...props} />);
expect(screen.getByTestId(props.testId)).toHaveTextContent(
props.valueFormatted
);
expect(screen.getByText(decimalPart)).toBeInTheDocument();
expect(screen.getByText(decimalPart)).toHaveClass('opacity-60');
});
it('Displays 0', () => {
render(<VolCell {...props} value={0} valueFormatted="0.00" />);
expect(screen.getByTestId(props.testId)).toHaveTextContent('0.00');
});
it('Displays - if value is not a number', () => {
render(<VolCell {...props} value={null} valueFormatted="" />);
expect(screen.getByTestId(props.testId)).toHaveTextContent('-');
});
it('renders bid volume bar', () => {
render(<VolCell {...props} type={VolumeType.bid} />);
expect(screen.getByTestId('vol-bar')).toHaveClass('left-0'); // renders bid bars from the left
expect(screen.getByTestId('vol-bar')).toHaveStyle({
backgroundColor: tailwind.theme.colors.vega.green.DEFAULT,
});
});
it('renders ask volume bar', () => {
render(<VolCell {...props} type={VolumeType.ask} />);
expect(screen.getByTestId('vol-bar')).toHaveClass('right-0'); // renders ask bars from the right
expect(screen.getByTestId('vol-bar')).toHaveStyle({
backgroundColor: tailwind.theme.colors.vega.pink.DEFAULT,
});
});
});
+6 -7
View File
@@ -8,7 +8,7 @@ export enum VolumeType {
bid,
ask,
}
export interface VolCellProps {
export interface VolProps {
value: number | bigint | null | undefined;
valueFormatted: string;
relativeValue?: number;
@@ -17,21 +17,20 @@ export interface VolCellProps {
}
export interface IVolCellProps extends ICellRendererParams {
value: number | bigint | null | undefined;
valueFormatted: Omit<VolCellProps, 'value'>;
valueFormatted: Omit<VolProps, 'value'>;
}
export const BID_COLOR = tailwind.theme.colors.vega.green.DEFAULT;
export const ASK_COLOR = tailwind.theme.colors.vega.pink.DEFAULT;
export const VolCell = React.memo(
({ value, valueFormatted, relativeValue, type, testId }: VolCellProps) => {
export const Vol = React.memo(
({ value, valueFormatted, relativeValue, type, testId }: VolProps) => {
if ((!value && value !== 0) || isNaN(Number(value))) {
return <div data-testid={testId || 'vol'}>-</div>;
return <div data-testid="vol">-</div>;
}
return (
<div className="relative" data-testid={testId || 'vol'}>
<div
data-testid="vol-bar"
className={classNames(
'h-full absolute top-0 opacity-40 dark:opacity-100',
{
@@ -51,4 +50,4 @@ export const VolCell = React.memo(
}
);
VolCell.displayName = 'VolCell';
Vol.displayName = 'Vol';