fix(environment): missing block height in node switcher (#5210)
This commit is contained in:
parent
7aee2a3a7b
commit
607ad06971
libs/environment/src/components/node-switcher
@ -1,6 +1,12 @@
|
|||||||
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 { act, render, screen, waitFor } from '@testing-library/react';
|
import {
|
||||||
|
act,
|
||||||
|
render,
|
||||||
|
renderHook,
|
||||||
|
screen,
|
||||||
|
waitFor,
|
||||||
|
} from '@testing-library/react';
|
||||||
import { RadioGroup } from '@vegaprotocol/ui-toolkit';
|
import { RadioGroup } from '@vegaprotocol/ui-toolkit';
|
||||||
import type {
|
import type {
|
||||||
NodeCheckTimeUpdateSubscription,
|
NodeCheckTimeUpdateSubscription,
|
||||||
@ -11,30 +17,33 @@ import {
|
|||||||
NodeCheckTimeUpdateDocument,
|
NodeCheckTimeUpdateDocument,
|
||||||
} from '../../utils/__generated__/NodeCheck';
|
} from '../../utils/__generated__/NodeCheck';
|
||||||
import type { RowDataProps } from './row-data';
|
import type { RowDataProps } from './row-data';
|
||||||
import { POLL_INTERVAL } from './row-data';
|
import {
|
||||||
|
POLL_INTERVAL,
|
||||||
|
Result,
|
||||||
|
SUBSCRIPTION_TIMEOUT,
|
||||||
|
useNodeBasicStatus,
|
||||||
|
useNodeSubscriptionStatus,
|
||||||
|
useResponseTime,
|
||||||
|
} 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 { useHeaderStore } from '@vegaprotocol/apollo-client';
|
|
||||||
import { CUSTOM_NODE_KEY } from '../../types';
|
import { CUSTOM_NODE_KEY } from '../../types';
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/apollo-client', () => ({
|
const mockStatsQuery = (
|
||||||
useHeaderStore: jest.fn().mockReturnValue({}),
|
blockHeight = '1234'
|
||||||
}));
|
): MockedResponse<NodeCheckQuery> => ({
|
||||||
|
|
||||||
const statsQueryMock: MockedResponse<NodeCheckQuery> = {
|
|
||||||
request: {
|
request: {
|
||||||
query: NodeCheckDocument,
|
query: NodeCheckDocument,
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
statistics: {
|
statistics: {
|
||||||
blockHeight: '1234', // the actual value used in the component is the value from the header store
|
blockHeight,
|
||||||
vegaTime: new Date().toISOString(),
|
vegaTime: new Date().toISOString(),
|
||||||
chainId: 'test-chain-id',
|
chainId: 'test-chain-id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
const subMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
|
const subMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
|
||||||
request: {
|
request: {
|
||||||
@ -59,18 +68,6 @@ global.performance.getEntriesByName = jest.fn().mockReturnValue([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const mockHeaders = (
|
|
||||||
url: string,
|
|
||||||
headers: Partial<HeaderEntry> = {
|
|
||||||
blockHeight: 100,
|
|
||||||
timestamp: new Date(),
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
(useHeaderStore as unknown as jest.Mock).mockReturnValue({
|
|
||||||
[url]: headers,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderComponent = (
|
const renderComponent = (
|
||||||
props: RowDataProps,
|
props: RowDataProps,
|
||||||
queryMock: MockedResponse<NodeCheckQuery>,
|
queryMock: MockedResponse<NodeCheckQuery>,
|
||||||
@ -86,6 +83,98 @@ const renderComponent = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
describe('useNodeSubscriptionStatus', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
const mockWrapper =
|
||||||
|
(withData = false) =>
|
||||||
|
({ children }: { children: React.ReactNode }) =>
|
||||||
|
(
|
||||||
|
<MockedProvider mocks={withData ? [subMock, subMock, subMock] : []}>
|
||||||
|
{children}
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
it('results initially as loading', async () => {
|
||||||
|
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||||
|
wrapper: mockWrapper(true),
|
||||||
|
});
|
||||||
|
expect(result.current.status).toBe(Result.Loading);
|
||||||
|
});
|
||||||
|
it('results as successful when data received', async () => {
|
||||||
|
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||||
|
wrapper: mockWrapper(true),
|
||||||
|
});
|
||||||
|
expect(result.current.status).toBe(Result.Loading);
|
||||||
|
await act(() => {
|
||||||
|
jest.advanceTimersByTime(SUBSCRIPTION_TIMEOUT);
|
||||||
|
});
|
||||||
|
expect(result.current.status).toBe(Result.Successful);
|
||||||
|
});
|
||||||
|
it('result as failed when no data received', async () => {
|
||||||
|
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||||
|
wrapper: mockWrapper(false),
|
||||||
|
});
|
||||||
|
expect(result.current.status).toBe(Result.Loading);
|
||||||
|
await act(() => {
|
||||||
|
jest.advanceTimersByTime(SUBSCRIPTION_TIMEOUT);
|
||||||
|
});
|
||||||
|
expect(result.current.status).toBe(Result.Failed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useNodeBasicStatus', () => {
|
||||||
|
const mockWrapper =
|
||||||
|
(withData = false) =>
|
||||||
|
({ children }: { children: React.ReactNode }) =>
|
||||||
|
(
|
||||||
|
<MockedProvider mocks={withData ? [mockStatsQuery('1234')] : []}>
|
||||||
|
{children}
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
it('results initially as loading', async () => {
|
||||||
|
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||||
|
wrapper: mockWrapper(true),
|
||||||
|
});
|
||||||
|
expect(result.current.status).toBe(Result.Loading);
|
||||||
|
expect(result.current.currentBlockHeight).toBeNaN();
|
||||||
|
});
|
||||||
|
it('results as successful when data received', async () => {
|
||||||
|
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||||
|
wrapper: mockWrapper(true),
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.status).toBe(Result.Successful);
|
||||||
|
expect(result.current.currentBlockHeight).toBe(1234);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('result as failed when no data received', async () => {
|
||||||
|
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||||
|
wrapper: mockWrapper(false),
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.status).toBe(Result.Failed);
|
||||||
|
expect(result.current.currentBlockHeight).toBeNaN();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
@ -94,9 +183,13 @@ describe('RowData', () => {
|
|||||||
onBlockHeight: jest.fn(),
|
onBlockHeight: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('radio button enabled after stats query successful', async () => {
|
it('radio button enabled after stats query successful', async () => {
|
||||||
mockHeaders(props.url);
|
render(renderComponent(props, mockStatsQuery('100'), subMock));
|
||||||
render(renderComponent(props, statsQueryMock, subMock));
|
|
||||||
|
|
||||||
// radio should be enabled until query resolves
|
// radio should be enabled until query resolves
|
||||||
expect(
|
expect(
|
||||||
@ -127,8 +220,6 @@ describe('RowData', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('radio button still enabled if query fails', async () => {
|
it('radio button still enabled if query fails', async () => {
|
||||||
mockHeaders(props.url, {});
|
|
||||||
|
|
||||||
const failedQueryMock: MockedResponse<NodeCheckQuery> = {
|
const failedQueryMock: MockedResponse<NodeCheckQuery> = {
|
||||||
request: {
|
request: {
|
||||||
query: NodeCheckDocument,
|
query: NodeCheckDocument,
|
||||||
@ -178,12 +269,11 @@ describe('RowData', () => {
|
|||||||
|
|
||||||
it('highlights rows with a slow block height', async () => {
|
it('highlights rows with a slow block height', async () => {
|
||||||
const blockHeight = 100;
|
const blockHeight = 100;
|
||||||
mockHeaders(props.url, { blockHeight });
|
|
||||||
|
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
renderComponent(
|
renderComponent(
|
||||||
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD },
|
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD },
|
||||||
statsQueryMock,
|
mockStatsQuery(String(blockHeight)),
|
||||||
subMock
|
subMock
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -201,7 +291,7 @@ describe('RowData', () => {
|
|||||||
rerender(
|
rerender(
|
||||||
renderComponent(
|
renderComponent(
|
||||||
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD + 1 },
|
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD + 1 },
|
||||||
statsQueryMock,
|
mockStatsQuery(String(blockHeight)),
|
||||||
subMock
|
subMock
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -216,7 +306,7 @@ describe('RowData', () => {
|
|||||||
...props,
|
...props,
|
||||||
id: CUSTOM_NODE_KEY,
|
id: CUSTOM_NODE_KEY,
|
||||||
},
|
},
|
||||||
statsQueryMock,
|
mockStatsQuery('1234'),
|
||||||
subMock
|
subMock
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -230,17 +320,18 @@ describe('RowData', () => {
|
|||||||
it('updates highest block after new header received', async () => {
|
it('updates highest block after new header received', async () => {
|
||||||
const mockOnBlockHeight = jest.fn();
|
const mockOnBlockHeight = jest.fn();
|
||||||
const blockHeight = 200;
|
const blockHeight = 200;
|
||||||
mockHeaders(props.url, { blockHeight });
|
|
||||||
render(
|
render(
|
||||||
renderComponent(
|
renderComponent(
|
||||||
{ ...props, onBlockHeight: mockOnBlockHeight },
|
{ ...props, onBlockHeight: mockOnBlockHeight },
|
||||||
statsQueryMock,
|
mockStatsQuery(String(blockHeight)),
|
||||||
subMock
|
subMock
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight);
|
expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should poll the query unless an errors is returned', async () => {
|
it('should poll the query unless an errors is returned', async () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@ -275,7 +366,6 @@ describe('RowData', () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
mockHeaders(props.url);
|
|
||||||
const statsQueryMock1 = createStatsQueryMock('1234');
|
const statsQueryMock1 = createStatsQueryMock('1234');
|
||||||
const statsQueryMock2 = createStatsQueryMock('1235');
|
const statsQueryMock2 = createStatsQueryMock('1235');
|
||||||
const statsQueryMock3 = createFailedStatsQueryMock();
|
const statsQueryMock3 = createFailedStatsQueryMock();
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import type { ApolloError } from '@apollo/client';
|
|
||||||
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
|
||||||
import { isValidUrl } from '@vegaprotocol/utils';
|
import { isValidUrl } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { TradingRadio } from '@vegaprotocol/ui-toolkit';
|
import { TradingRadio } from '@vegaprotocol/ui-toolkit';
|
||||||
@ -12,6 +10,7 @@ import {
|
|||||||
import { LayoutCell } from './layout-cell';
|
import { LayoutCell } from './layout-cell';
|
||||||
|
|
||||||
export const POLL_INTERVAL = 1000;
|
export const POLL_INTERVAL = 1000;
|
||||||
|
export const SUBSCRIPTION_TIMEOUT = 3000;
|
||||||
export const BLOCK_THRESHOLD = 3;
|
export const BLOCK_THRESHOLD = 3;
|
||||||
|
|
||||||
export interface RowDataProps {
|
export interface RowDataProps {
|
||||||
@ -21,15 +20,39 @@ export interface RowDataProps {
|
|||||||
onBlockHeight: (blockHeight: number) => void;
|
onBlockHeight: (blockHeight: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RowData = ({
|
export enum Result {
|
||||||
id,
|
Successful,
|
||||||
url,
|
Failed,
|
||||||
highestBlock,
|
Loading,
|
||||||
onBlockHeight,
|
}
|
||||||
}: RowDataProps) => {
|
|
||||||
const [subFailed, setSubFailed] = useState(false);
|
export const useNodeSubscriptionStatus = () => {
|
||||||
const [time, setTime] = useState<number>();
|
const [status, setStatus] = useState<Result>(Result.Loading);
|
||||||
// no use of data here as we need the data nodes reference to block height
|
const { data, error } = useNodeCheckTimeUpdateSubscription();
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) {
|
||||||
|
setStatus(Result.Failed);
|
||||||
|
}
|
||||||
|
if (data?.busEvents && data.busEvents.length > 0) {
|
||||||
|
setStatus(Result.Successful);
|
||||||
|
}
|
||||||
|
// set as failed when no data received after SUBSCRIPTION_TIMEOUT ms
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (!data || error) {
|
||||||
|
setStatus(Result.Failed);
|
||||||
|
}
|
||||||
|
}, SUBSCRIPTION_TIMEOUT);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [data, error]);
|
||||||
|
|
||||||
|
return { status };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useNodeBasicStatus = () => {
|
||||||
|
const [status, setStatus] = useState<Result>(Result.Loading);
|
||||||
|
|
||||||
const { data, error, loading, startPolling, stopPolling } = useNodeCheckQuery(
|
const { data, error, loading, startPolling, stopPolling } = useNodeCheckQuery(
|
||||||
{
|
{
|
||||||
pollInterval: POLL_INTERVAL,
|
pollInterval: POLL_INTERVAL,
|
||||||
@ -38,28 +61,7 @@ export const RowData = ({
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const headerStore = useHeaderStore();
|
|
||||||
const headers = headerStore[url];
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: subData,
|
|
||||||
error: subError,
|
|
||||||
loading: subLoading,
|
|
||||||
} = useNodeCheckTimeUpdateSubscription();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
if (!subData) {
|
|
||||||
setSubFailed(true);
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
}, [subData]);
|
|
||||||
|
|
||||||
// handle polling
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleStartPoll = () => {
|
const handleStartPoll = () => {
|
||||||
if (error) return;
|
if (error) return;
|
||||||
@ -83,56 +85,57 @@ export const RowData = ({
|
|||||||
};
|
};
|
||||||
}, [startPolling, stopPolling, error]);
|
}, [startPolling, stopPolling, error]);
|
||||||
|
|
||||||
// measure response time
|
const currentBlockHeight = parseInt(
|
||||||
|
data?.statistics.blockHeight || 'NONE',
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loading) {
|
||||||
|
setStatus(Result.Loading);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!error && !isNaN(currentBlockHeight)) {
|
||||||
|
setStatus(Result.Successful);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStatus(Result.Failed);
|
||||||
|
}, [currentBlockHeight, error, loading]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
currentBlockHeight,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResponseTime = (url: string, trigger?: unknown) => {
|
||||||
|
const [responseTime, setResponseTime] = useState<number>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isValidUrl(url)) return;
|
if (!isValidUrl(url)) return;
|
||||||
if (typeof window.performance.getEntriesByName !== 'function') return; // protection for test environment
|
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 requestUrl = new URL(url);
|
||||||
const requests = window.performance.getEntriesByName(requestUrl.href);
|
const requests = window.performance.getEntriesByName(requestUrl.href);
|
||||||
const { duration } =
|
const { duration } =
|
||||||
(requests.length && requests[requests.length - 1]) || {};
|
(requests.length && requests[requests.length - 1]) || {};
|
||||||
setTime(duration);
|
setResponseTime(duration);
|
||||||
}, [url, data]);
|
}, [url, trigger]);
|
||||||
|
return { responseTime };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RowData = ({
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
highestBlock,
|
||||||
|
onBlockHeight,
|
||||||
|
}: RowDataProps) => {
|
||||||
|
const { status: subStatus } = useNodeSubscriptionStatus();
|
||||||
|
const { status, currentBlockHeight } = useNodeBasicStatus();
|
||||||
|
const { responseTime } = useResponseTime(url, currentBlockHeight); // measure response time (ms) every time we get data (block height)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (headers?.blockHeight) {
|
if (!isNaN(currentBlockHeight)) {
|
||||||
onBlockHeight(headers.blockHeight);
|
onBlockHeight(currentBlockHeight);
|
||||||
}
|
}
|
||||||
}, [headers?.blockHeight, onBlockHeight]);
|
}, [currentBlockHeight, onBlockHeight]);
|
||||||
|
|
||||||
const getHasError = () => {
|
|
||||||
// the stats query errored
|
|
||||||
if (error) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we are still awaiting a header entry its not an error
|
|
||||||
// we are still waiting for the query to resolve
|
|
||||||
if (!headers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// highlight this node as 'error' if its more than BLOCK_THRESHOLD blocks behind the most
|
|
||||||
// advanced node
|
|
||||||
if (
|
|
||||||
highestBlock !== null &&
|
|
||||||
headers.blockHeight < highestBlock - BLOCK_THRESHOLD
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSubFailed = (
|
|
||||||
subError: ApolloError | undefined,
|
|
||||||
subFailed: boolean
|
|
||||||
) => {
|
|
||||||
if (subError) return true;
|
|
||||||
if (subFailed) return true;
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -143,72 +146,58 @@ export const RowData = ({
|
|||||||
)}
|
)}
|
||||||
<LayoutCell
|
<LayoutCell
|
||||||
label={t('Response time')}
|
label={t('Response time')}
|
||||||
isLoading={!error && loading}
|
isLoading={status === Result.Loading}
|
||||||
hasError={Boolean(error)}
|
hasError={status === Result.Failed}
|
||||||
dataTestId="response-time-cell"
|
dataTestId="response-time-cell"
|
||||||
>
|
>
|
||||||
{getResponseTimeDisplayValue(time, error)}
|
{display(status, formatResponseTime(responseTime))}
|
||||||
</LayoutCell>
|
</LayoutCell>
|
||||||
<LayoutCell
|
<LayoutCell
|
||||||
label={t('Block')}
|
label={t('Block')}
|
||||||
isLoading={loading}
|
isLoading={status === Result.Loading}
|
||||||
hasError={getHasError()}
|
hasError={
|
||||||
|
status === Result.Failed ||
|
||||||
|
(highestBlock != null &&
|
||||||
|
!isNaN(currentBlockHeight) &&
|
||||||
|
currentBlockHeight < highestBlock - BLOCK_THRESHOLD)
|
||||||
|
}
|
||||||
dataTestId="block-height-cell"
|
dataTestId="block-height-cell"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-testid="query-block-height"
|
data-testid="query-block-height"
|
||||||
data-query-block-height={
|
data-query-block-height={
|
||||||
error ? 'failed' : data?.statistics.blockHeight
|
status === Result.Failed ? 'failed' : currentBlockHeight
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{getBlockDisplayValue(headers?.blockHeight, error)}
|
{display(status, currentBlockHeight)}
|
||||||
</span>
|
</span>
|
||||||
</LayoutCell>
|
</LayoutCell>
|
||||||
<LayoutCell
|
<LayoutCell
|
||||||
label={t('Subscription')}
|
label={t('Subscription')}
|
||||||
isLoading={subFailed ? false : subLoading}
|
isLoading={subStatus === Result.Loading}
|
||||||
hasError={getSubFailed(subError, subFailed)}
|
hasError={subStatus === Result.Failed}
|
||||||
dataTestId="subscription-cell"
|
dataTestId="subscription-cell"
|
||||||
>
|
>
|
||||||
{getSubscriptionDisplayValue(subFailed, subData?.busEvents, subError)}
|
{display(subStatus, t('Yes'), t('No'))}
|
||||||
</LayoutCell>
|
</LayoutCell>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getResponseTimeDisplayValue = (
|
const formatResponseTime = (time: number | undefined) =>
|
||||||
responseTime?: number,
|
time != null ? `${Number(time).toFixed(2)}ms` : '-';
|
||||||
error?: ApolloError
|
|
||||||
) => {
|
|
||||||
if (error) {
|
|
||||||
return t('n/a');
|
|
||||||
}
|
|
||||||
if (typeof responseTime === 'number') {
|
|
||||||
return `${Number(responseTime).toFixed(2)}ms`;
|
|
||||||
}
|
|
||||||
return '-';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBlockDisplayValue = (block?: number, error?: ApolloError) => {
|
const display = (
|
||||||
if (error) {
|
status: Result,
|
||||||
return t('n/a');
|
yes: string | number | undefined,
|
||||||
}
|
no = t('n/a')
|
||||||
if (block) {
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
return '-';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSubscriptionDisplayValue = (
|
|
||||||
subFailed: boolean,
|
|
||||||
events?: { id: string }[] | null,
|
|
||||||
error?: ApolloError
|
|
||||||
) => {
|
) => {
|
||||||
if (subFailed || error) {
|
switch (status) {
|
||||||
return t('No');
|
case Result.Successful:
|
||||||
}
|
return yes;
|
||||||
if (events?.length) {
|
case Result.Failed:
|
||||||
return t('Yes');
|
return no;
|
||||||
}
|
default:
|
||||||
return '-';
|
return '-';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user