From dfa3355a6878ac7fb67586f193e70074867235ce Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Mon, 10 Apr 2023 14:48:03 -0700 Subject: [PATCH] chore(trading): prevent node poll if query fails (#3411) --- .../node-switcher/row-data.spec.tsx | 106 +++++++++++++++++- .../src/components/node-switcher/row-data.tsx | 30 +++-- 2 files changed, 125 insertions(+), 11 deletions(-) diff --git a/libs/environment/src/components/node-switcher/row-data.spec.tsx b/libs/environment/src/components/node-switcher/row-data.spec.tsx index 74f732158..b24dee869 100644 --- a/libs/environment/src/components/node-switcher/row-data.spec.tsx +++ b/libs/environment/src/components/node-switcher/row-data.spec.tsx @@ -1,6 +1,6 @@ import type { MockedResponse } 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 type { BlockTimeSubscription, @@ -9,6 +9,7 @@ import type { import { BlockTimeDocument } from '../../utils/__generated__/Node'; import { StatisticsDocument } from '../../utils/__generated__/Node'; import type { RowDataProps } from './row-data'; +import { POLL_INTERVAL } from './row-data'; import { BLOCK_THRESHOLD, RowData } from './row-data'; import type { HeaderEntry } from '@vegaprotocol/apollo-client'; import { useHeaderStore } from '@vegaprotocol/apollo-client'; @@ -25,7 +26,7 @@ const statsQueryMock: MockedResponse = { result: { data: { statistics: { - blockHeight: '1234', + blockHeight: '1234', // the actual value used in the component is the value from the header store vegaTime: new Date().toISOString(), chainId: 'test-chain-id', }, @@ -251,4 +252,105 @@ describe('RowData', () => { expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight); }); + + it('should poll the query unless an errors is returned', async () => { + jest.useFakeTimers(); + const createStatsQueryMock = ( + blockHeight: string + ): MockedResponse => { + return { + request: { + query: StatisticsDocument, + }, + result: { + data: { + statistics: { + blockHeight, + vegaTime: new Date().toISOString(), + chainId: 'test-chain-id', + }, + }, + }, + }; + }; + + const createFailedStatsQueryMock = (): MockedResponse => { + 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( + + + {/* Radio group required as radio is being render in isolation */} + + + + ); + + 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(); + }); }); diff --git a/libs/environment/src/components/node-switcher/row-data.tsx b/libs/environment/src/components/node-switcher/row-data.tsx index 55d69ff5c..bf5c14933 100644 --- a/libs/environment/src/components/node-switcher/row-data.tsx +++ b/libs/environment/src/components/node-switcher/row-data.tsx @@ -11,7 +11,7 @@ import { } from '../../utils/__generated__/Node'; import { LayoutCell } from './layout-cell'; -const POLL_INTERVAL = 1000; +export const POLL_INTERVAL = 1000; export const BLOCK_THRESHOLD = 3; export interface RowDataProps { @@ -60,18 +60,22 @@ export const RowData = ({ // handle polling useEffect(() => { - const handleStartPoll = () => startPolling(POLL_INTERVAL); + const handleStartPoll = () => { + if (error) return; + startPolling(POLL_INTERVAL); + }; 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('focus', handleStartPoll); - handleStartPoll(); - - if (error) { - stopPolling(); - } - return () => { window.removeEventListener('blur', handleStopPoll); window.removeEventListener('focus', handleStartPoll); @@ -81,6 +85,7 @@ export const RowData = ({ // measure response time useEffect(() => { if (!isValidUrl(url)) return; + if (typeof window.performance.getEntriesByName !== 'function') return; // protection for test environment // every time we get data measure response speed const requestUrl = new URL(url); const requests = window.performance.getEntriesByName(requestUrl.href); @@ -177,7 +182,14 @@ export const RowData = ({ hasError={getHasError()} dataTestId="block-height-cell" > - {getBlockDisplayValue(headers?.blockHeight, error)} + + {getBlockDisplayValue(headers?.blockHeight, error)} +