chore(trading): prevent node poll if query fails (#3411)

This commit is contained in:
Matthew Russell 2023-04-10 14:48:03 -07:00 committed by GitHub
parent 8553904d81
commit dfa3355a68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 125 additions and 11 deletions

View File

@ -1,6 +1,6 @@
import type { MockedResponse } from '@apollo/react-testing'; import type { MockedResponse } from '@apollo/react-testing';
import { MockedProvider } from '@apollo/react-testing'; import { MockedProvider } from '@apollo/react-testing';
import { render, screen, waitFor } from '@testing-library/react'; import { act, render, screen, waitFor } from '@testing-library/react';
import { RadioGroup } from '@vegaprotocol/ui-toolkit'; import { RadioGroup } from '@vegaprotocol/ui-toolkit';
import type { import type {
BlockTimeSubscription, BlockTimeSubscription,
@ -9,6 +9,7 @@ import type {
import { BlockTimeDocument } from '../../utils/__generated__/Node'; import { BlockTimeDocument } from '../../utils/__generated__/Node';
import { StatisticsDocument } from '../../utils/__generated__/Node'; import { StatisticsDocument } from '../../utils/__generated__/Node';
import type { RowDataProps } from './row-data'; import type { RowDataProps } from './row-data';
import { POLL_INTERVAL } from './row-data';
import { BLOCK_THRESHOLD, RowData } from './row-data'; import { BLOCK_THRESHOLD, RowData } from './row-data';
import type { HeaderEntry } from '@vegaprotocol/apollo-client'; import type { HeaderEntry } from '@vegaprotocol/apollo-client';
import { useHeaderStore } from '@vegaprotocol/apollo-client'; import { useHeaderStore } from '@vegaprotocol/apollo-client';
@ -25,7 +26,7 @@ const statsQueryMock: MockedResponse<StatisticsQuery> = {
result: { result: {
data: { data: {
statistics: { statistics: {
blockHeight: '1234', blockHeight: '1234', // the actual value used in the component is the value from the header store
vegaTime: new Date().toISOString(), vegaTime: new Date().toISOString(),
chainId: 'test-chain-id', chainId: 'test-chain-id',
}, },
@ -251,4 +252,105 @@ describe('RowData', () => {
expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight); expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight);
}); });
it('should poll the query unless an errors is returned', async () => {
jest.useFakeTimers();
const createStatsQueryMock = (
blockHeight: string
): MockedResponse<StatisticsQuery> => {
return {
request: {
query: StatisticsDocument,
},
result: {
data: {
statistics: {
blockHeight,
vegaTime: new Date().toISOString(),
chainId: 'test-chain-id',
},
},
},
};
};
const createFailedStatsQueryMock = (): MockedResponse<StatisticsQuery> => {
return {
request: {
query: StatisticsDocument,
},
result: {
data: undefined,
},
error: new Error('failed'),
};
};
mockHeaders(props.url);
const statsQueryMock1 = createStatsQueryMock('1234');
const statsQueryMock2 = createStatsQueryMock('1235');
const statsQueryMock3 = createFailedStatsQueryMock();
const statsQueryMock4 = createStatsQueryMock('1236');
render(
<MockedProvider
mocks={[
statsQueryMock1,
statsQueryMock2,
statsQueryMock3,
statsQueryMock4,
subMock,
]}
>
<RadioGroup>
{/* Radio group required as radio is being render in isolation */}
<RowData {...props} />
</RadioGroup>
</MockedProvider>
);
expect(screen.getByTestId('block-height-cell')).toHaveTextContent(
'Checking'
);
// statsQueryMock1 should be rendered
await waitFor(() => {
const elem = screen.getByTestId('query-block-height');
expect(elem).toHaveAttribute('data-query-block-height', '1234');
});
await act(async () => {
jest.advanceTimersByTime(POLL_INTERVAL);
});
// statsQueryMock2 should be rendered
await waitFor(() => {
const elem = screen.getByTestId('query-block-height');
expect(elem).toHaveAttribute('data-query-block-height', '1235');
});
await act(async () => {
jest.advanceTimersByTime(POLL_INTERVAL);
});
// statsQueryMock3 should FAIL!
await waitFor(() => {
const elem = screen.getByTestId('query-block-height');
expect(elem).toHaveAttribute('data-query-block-height', 'failed');
});
// run the timer again, but statsQueryMock4's result should not be
// rendered even though its successful, because the poll
// should have been stopped
await act(async () => {
jest.advanceTimersByTime(POLL_INTERVAL);
});
// should still render the result of statsQueryMock3
await waitFor(() => {
const elem = screen.getByTestId('query-block-height');
expect(elem).toHaveAttribute('data-query-block-height', 'failed');
});
jest.useRealTimers();
});
}); });

View File

@ -11,7 +11,7 @@ import {
} from '../../utils/__generated__/Node'; } from '../../utils/__generated__/Node';
import { LayoutCell } from './layout-cell'; import { LayoutCell } from './layout-cell';
const POLL_INTERVAL = 1000; export const POLL_INTERVAL = 1000;
export const BLOCK_THRESHOLD = 3; export const BLOCK_THRESHOLD = 3;
export interface RowDataProps { export interface RowDataProps {
@ -60,18 +60,22 @@ export const RowData = ({
// handle polling // handle polling
useEffect(() => { useEffect(() => {
const handleStartPoll = () => startPolling(POLL_INTERVAL); const handleStartPoll = () => {
if (error) return;
startPolling(POLL_INTERVAL);
};
const handleStopPoll = () => stopPolling(); const handleStopPoll = () => stopPolling();
// start polling on mount, but only if there is no error
if (error) {
handleStopPoll();
} else {
handleStartPoll();
}
window.addEventListener('blur', handleStopPoll); window.addEventListener('blur', handleStopPoll);
window.addEventListener('focus', handleStartPoll); window.addEventListener('focus', handleStartPoll);
handleStartPoll();
if (error) {
stopPolling();
}
return () => { return () => {
window.removeEventListener('blur', handleStopPoll); window.removeEventListener('blur', handleStopPoll);
window.removeEventListener('focus', handleStartPoll); window.removeEventListener('focus', handleStartPoll);
@ -81,6 +85,7 @@ export const RowData = ({
// measure response time // measure response time
useEffect(() => { useEffect(() => {
if (!isValidUrl(url)) return; if (!isValidUrl(url)) return;
if (typeof window.performance.getEntriesByName !== 'function') return; // protection for test environment
// every time we get data measure response speed // every time we get data measure response speed
const requestUrl = new URL(url); const requestUrl = new URL(url);
const requests = window.performance.getEntriesByName(requestUrl.href); const requests = window.performance.getEntriesByName(requestUrl.href);
@ -176,8 +181,15 @@ export const RowData = ({
isLoading={loading} isLoading={loading}
hasError={getHasError()} hasError={getHasError()}
dataTestId="block-height-cell" dataTestId="block-height-cell"
>
<span
data-testid="query-block-height"
data-query-block-height={
error ? 'failed' : data?.statistics.blockHeight
}
> >
{getBlockDisplayValue(headers?.blockHeight, error)} {getBlockDisplayValue(headers?.blockHeight, error)}
</span>
</LayoutCell> </LayoutCell>
<LayoutCell <LayoutCell
label={t('Subscription')} label={t('Subscription')}