Compare commits

..
Author SHA1 Message Date
Edd bf519fbfaa fix: typo fix in token readme 2022-12-29 10:05:46 +00:00
Edd c7bc64abfb fix: typo fix in explorer readme 2022-12-29 10:05:35 +00:00
Matthew Russell 7f60485c59 chore: add readmes for token and explorer e2e 2022-12-27 17:26:32 -08:00
465 changed files with 5819 additions and 11464 deletions
@@ -1,3 +1,6 @@
inputs:
passphrase:
description: 'Wallet password'
outputs:
token:
description: 'api-token of wallet'
@@ -12,6 +12,8 @@ on:
options:
- console-lite-e2e
- explorer-e2e
- liquidity-provision-dashboard-e2e
- stats-e2e
- token-e2e
- trading-e2e
tags:
@@ -12,6 +12,6 @@ jobs:
uses: ./.github/workflows/tests-dispatcher.yml
secrets: inherit
with:
project: '[console-lite-e2e, explorer-e2e, token-e2e, trading-e2e]'
project: '[console-lite-e2e, explorer-e2e, liquidity-provision-dashboard-e2e, stats-e2e, token-e2e, trading-e2e]'
tags: --env.grepTags '[ @smoke, @regression, @slow ]'
night-run: true
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
@@ -36,22 +36,23 @@ jobs:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
######
## Setup a Vega wallet for our user
######
- name: Run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
- name: Set up Vegawallet for capsule
- name: Set up Vegawallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
with:
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
@@ -62,6 +63,7 @@ jobs:
run: npx nx run console-lite-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
+12 -5
View File
@@ -44,23 +44,29 @@ jobs:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
#######
## Build and run Vegacapsule network
#######
- name: Build and run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
######
## Setup a Vega wallet for our user
######
- name: Run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
- name: Set up Vegawallet for capsule
- name: Set up Vegawallet for docker
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
with:
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
######
## Run some tests
@@ -75,6 +81,7 @@ jobs:
run: npx nx run explorer-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_NIGHTLY_RUN: ${{ inputs.night-run }}
@@ -0,0 +1,47 @@
name: Cypress - liquidity provision dashboard
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
jobs:
liquidity-provision-dashboard-e2e:
timeout-minutes: 10
if: ${{ inputs.trigger == 'true' }}
runs-on: self-hosted
steps:
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run liquidity-provision-dashboard-e2e:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
+47
View File
@@ -0,0 +1,47 @@
name: Cypress - stats
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
jobs:
stats-e2e:
runs-on: self-hosted
if: ${{ inputs.trigger == 'true' }}
timeout-minutes: 10
steps:
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run stats-e2e:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
+13 -6
View File
@@ -40,23 +40,29 @@ jobs:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
#######
## Build and run Vegacapsule network
#######
- name: Build and run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
######
## Setup a Vega wallet for our user
######
- name: Run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
- name: Set up Vegawallet for capsule
- name: Set up Vegawallet for docker
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
with:
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
######
## Run some tests
@@ -71,6 +77,7 @@ jobs:
run: npx nx run token-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
@@ -90,5 +97,5 @@ jobs:
- uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: logs-token
name: logs
path: /home/runner/.vegacapsule/testnet/logs
+7 -23
View File
@@ -37,7 +37,7 @@ jobs:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
# Install frontend dependencies
- name: Install root dependencies
@@ -48,12 +48,12 @@ jobs:
## Setup a Vega wallet for our user
######
- name: Run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
- name: Set up Vegawallet for capsule
- name: Set up Vegawallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
with:
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
@@ -64,23 +64,7 @@ jobs:
run: npx nx run trading-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
######
## Upload logs
######
- name: Rename files to allow archive
if: ${{ always() }}
run: |
while read -r file; do
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: logs-trading
path: /home/runner/.vegacapsule/testnet/logs
+12
View File
@@ -32,6 +32,18 @@ jobs:
tags: ${{ inputs.tags }}
night-run: ${{ inputs.night-run }}
run-liquidity-e2e:
uses: ./.github/workflows/cypress-liquidity-provision-dashboard-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'liquidity-provision-dashboard-e2e') || contains(inputs.project, 'liquidity-provision-dashboard') }}
run-stats-e2e:
uses: ./.github/workflows/cypress-stats-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'stats-e2e') || contains(inputs.project, 'stats') }}
run-token-e2e:
uses: ./.github/workflows/cypress-token-e2e.yml
secrets: inherit
+4
View File
@@ -22,6 +22,10 @@ The utility dApp for interacting with the Vega token and using its' utility. Thi
The block explorer for the Vega network, showing details of raw chain states and the state of markets on the Vega network.
### [Stats](./apps/stats)
An application for the status of the Vega network. Showing block height and other network activity.
### [Static](./apps/static)
Hosting for static content being shared across apps, for example fonts.
+6 -7
View File
@@ -1,11 +1,10 @@
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
NX_VEGA_ENV=STAGNET3
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_CONFIG_URL=''
NX_VEGA_ENV=CUSTOM
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_ENV=CUSTOM
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_ENV=STAGNET3
CYPRESS_VEGA_WALLET_API_TOKEN=
-11
View File
@@ -1,11 +0,0 @@
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_CONFIG_URL=''
NX_VEGA_ENV=CUSTOM
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_ENV=CUSTOM
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_WALLET_API_TOKEN=
+16
View File
@@ -1,6 +1,7 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
projectId: 'et4snf',
e2e: {
setupNodeEvents(on, config) {
require('@cypress/grep/src/plugin')(config);
@@ -22,6 +23,21 @@ module.exports = defineConfig({
viewportHeight: 900,
},
env: {
ETHEREUM_PROVIDER_URL:
'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
VEGA_PUBLIC_KEY:
'47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278',
VEGA_PUBLIC_KEY2:
'1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4',
TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278',
TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4',
ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158',
ETHERSCAN_URL: 'https://sepolia.etherscan.io',
tsConfig: 'tsconfig.json',
TAGS: 'not @todo and not @ignore and not @manual',
TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123',
ETH_WALLET_MNEMONIC:
'ugly gallery notice network true range brave clarify flat logic someone chunk',
grepTags: '@regression @smoke @slow',
grepFilterSpecs: true,
grepOmitFiltered: true,
@@ -4,8 +4,8 @@ const marketName = 'ACTIVE MARKET';
describe('market selector', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockConsole();
cy.setVegaWallet();
cy.visit(`/trading/${marketId}`);
cy.connectVegaWallet();
cy.wait('@Markets');
});
@@ -67,7 +67,6 @@ describe('market selector', { tags: '@smoke' }, () => {
it('mobile view', () => {
cy.viewport('iphone-xr');
cy.visit(`/trading/${marketId}`);
cy.connectVegaWallet();
cy.get('[role="dialog"]').should('not.exist');
cy.getByTestId('arrow-button').click();
cy.get('[role="dialog"]').should('be.visible');
@@ -30,6 +30,7 @@ describe('Market trade with wallet disconnected', { tags: '@smoke' }, () => {
cy.mockConsole();
cy.visit(`/trading/${marketId}`);
cy.wait('@Market');
console.log('marketId', marketId);
});
it('should not display steps', () => {
cy.getByTestId('trading-connect-wallet')
@@ -50,8 +51,8 @@ describe('Market trade', { tags: '@regression' }, () => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Market', marketQuery(marketOverride));
});
cy.setVegaWallet();
cy.visit(`/trading/${marketId}`);
cy.connectVegaWallet();
cy.wait('@Market');
});
@@ -14,7 +14,6 @@ import {
describe('Portfolio page - wallet', { tags: '@smoke' }, () => {
it('button for wallet connect should work', () => {
cy.mockConsole();
cy.visit('/');
cy.get('[href="/portfolio"]').eq(0).click();
cy.getByTestId('trading-connect-wallet').should('be.visible');
@@ -35,11 +34,11 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
});
cy.setVegaWallet();
});
it('certain tabs should exist', () => {
cy.visit('/portfolio');
cy.connectVegaWallet();
cy.getByTestId('assets').click();
cy.location('pathname').should('eq', '/portfolio/assets');
@@ -58,16 +57,27 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
});
describe('Assets view', () => {
beforeEach(() => {
cy.mockConsole();
cy.setVegaWallet();
before(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
aliasGQLQuery(req, 'Positions', positionsQuery());
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
});
cy.visit('/portfolio/assets');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
cy.get('.ag-center-cols-container .ag-row').should('have.length', 5);
cy.get('[role="gridcell"][col-id="account-asset"] button')
.contains('tEURO')
cy.get(
'.ag-center-cols-container [row-id="ACCOUNT_TYPE_GENERAL-asset-id-null"]'
)
.find('button')
.click();
cy.getByTestId('dialog-title').should(
'have.text',
@@ -89,8 +99,8 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
});
cy.setVegaWallet();
cy.visit('/portfolio/positions');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
@@ -106,8 +116,8 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
aliasGQLQuery(req, 'Orders', ordersQuery());
aliasGQLQuery(req, 'Markets', marketsQuery());
});
cy.setVegaWallet();
cy.visit('/portfolio/orders');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
@@ -124,8 +134,8 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
aliasGQLQuery(req, 'Markets', marketsQuery());
aliasGQLQuery(req, 'Fills', fillsQuery());
});
cy.setVegaWallet();
cy.visit('/portfolio/fills');
cy.connectVegaWallet();
});
it('data should be properly rendered', () => {
@@ -151,8 +161,8 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
});
cy.setVegaWallet();
cy.visit('/portfolio');
cy.connectVegaWallet();
});
it('"No data to display" should be always displayed', () => {
@@ -13,7 +13,6 @@ import {
marketsCandlesQuery,
marketsDataQuery,
marketsQuery,
networkParamsQuery,
ordersQuery,
positionsQuery,
statisticsQuery,
@@ -48,7 +47,6 @@ const mockPage = (req: CyHttpMessages.IncomingHttpRequest) => {
aliasGQLQuery(req, 'SimpleMarkets', marketsQuery());
aliasGQLQuery(req, 'Orders', ordersQuery());
aliasGQLQuery(req, 'Fills', fillsQuery());
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
};
export const addMockConsole = () => {
@@ -8,6 +8,7 @@ import {
useOrderMargin,
useMaximumPositionSize,
useCalculateSlippage,
validateAmount,
} from '@vegaprotocol/deal-ticket';
import { InputError } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from 'bignumber.js';
@@ -22,7 +23,6 @@ import {
addDecimalsFormatNumber,
addDecimal,
formatNumber,
validateAmount,
} from '@vegaprotocol/react-helpers';
import {
useOrderSubmit,
@@ -83,11 +83,10 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
order,
});
const closeOut = useOrderCloseOut({
const estCloseOut = useOrderCloseOut({
order,
market,
});
const estCloseOut = closeOut && formatNumber(closeOut, market.decimalPlaces);
const slippage = useCalculateSlippage({ marketId: market.id, order });
const [slippageValue, setSlippageValue] = useState(
slippage ? parseFloat(slippage) : 0
@@ -1,12 +1,13 @@
import type { ReactNode } from 'react';
import type { FieldErrors } from 'react-hook-form';
import { useMemo } from 'react';
import { DataGrid, t, toDecimal } from '@vegaprotocol/react-helpers';
import { t, toDecimal } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import * as Schema from '@vegaprotocol/types';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import {
MarketDataGrid,
compileGridData,
MarginWarning,
isMarketInAuction,
@@ -216,7 +217,7 @@ export const useOrderValidation = ({
{t('This market is in auction until it reaches')}{' '}
<Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('sufficient liquidity')}</span>
@@ -240,7 +241,7 @@ export const useOrderValidation = ({
{t('This market is in auction due to')}{' '}
<Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('high price volatility')}</span>
@@ -281,7 +282,7 @@ export const useOrderValidation = ({
{t('This market is in auction until it reaches')}{' '}
<Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('sufficient liquidity')}</span>
@@ -307,7 +308,7 @@ export const useOrderValidation = ({
{t('This market is in auction due to')}{' '}
<Tooltip
description={
<DataGrid grid={compileGridData(market, market.data)} />
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('high price volatility')}</span>
@@ -33,17 +33,19 @@ const AccountsManager = () => {
update,
variables,
});
const getRows = useCallback(
async ({ successCallback, startRow, endRow }: IGetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current ? dataRef.current.length : 0;
successCallback(rowsThisBlock, lastRow);
},
[]
);
const getRows = async ({
successCallback,
startRow,
endRow,
}: IGetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current?.length ?? -1;
successCallback(rowsThisBlock, lastRow);
};
const { columnDefs, defaultColDef } = useAccountColumnDefinitions();
console.log(data, loading);
return (
<>
<AsyncRenderer
@@ -13,7 +13,7 @@ import useColumnDefinitions from './use-column-definitions';
const Positions = () => {
const gridRef = useRef<AgGridReact | null>(null);
const { partyId } = useOutletContext<{ partyId: string }>();
const { data, error, loading, getRows } = usePositionsData(partyId, gridRef);
const { data, error, loading } = usePositionsData(partyId, gridRef);
const { columnDefs, defaultColDef } = useColumnDefinitions();
return (
<AsyncRenderer
@@ -29,8 +29,7 @@ const Positions = () => {
columnDefs={columnDefs}
defaultColDef={defaultColDef}
getRowId={getRowId}
rowModelType="infinite"
datasource={{ getRows }}
rowData={data || undefined}
components={{ PriceFlashCell }}
/>
</AsyncRenderer>
@@ -0,0 +1,27 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL subscription operation: CandleLive
// ====================================================
export interface CandleLive_candles {
__typename: "Candle";
/**
* Close price (uint64)
*/
close: string;
}
export interface CandleLive {
/**
* Subscribe to the candles updates
*/
candles: CandleLive_candles;
}
export interface CandleLiveVariables {
marketId: string;
}
@@ -0,0 +1,43 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type CandleLiveSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type CandleLiveSubscription = { __typename?: 'Subscription', candles: { __typename?: 'Candle', close: string } };
export const CandleLiveDocument = gql`
subscription CandleLive($marketId: ID!) {
candles(marketId: $marketId, interval: INTERVAL_I1H) {
close
}
}
`;
/**
* __useCandleLiveSubscription__
*
* To run a query within a React component, call `useCandleLiveSubscription` and pass it any options that fit your needs.
* When your component renders, `useCandleLiveSubscription` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useCandleLiveSubscription({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useCandleLiveSubscription(baseOptions: Apollo.SubscriptionHookOptions<CandleLiveSubscription, CandleLiveSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<CandleLiveSubscription, CandleLiveSubscriptionVariables>(CandleLiveDocument, options);
}
export type CandleLiveSubscriptionHookResult = ReturnType<typeof useCandleLiveSubscription>;
export type CandleLiveSubscriptionResult = Apollo.SubscriptionResult<CandleLiveSubscription>;
-1
View File
@@ -18,4 +18,3 @@ NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
CYPRESS_VEGA_WALLET_API_TOKEN=
CYPRESS_VEGA_URL=http://localhost:3028/query
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_URL=https://n04.d.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_ENV=DEVNET
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
# App flags
NX_EXPLORER_ASSETS=1
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest
# App flags
NX_EXPLORER_ASSETS=1
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
NX_VEGA_ENV=STAGNET3
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
# App flags
NX_EXPLORER_ASSETS=1
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_URL=https://tm.n07.testnet.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://lb.testnet.vega.xyz/tm/websocket
NX_VEGA_URL=https://api.n11.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
# App flags
NX_EXPLORER_ASSETS=1
+3 -8
View File
@@ -21,12 +21,7 @@ The e2e tests run against a locally running instance of the Vega network, manage
- Install the required Vega smart contracts
- Set up DataNodes with a running GraphQL and REST APIs.
1. Refer to the [Vega Capsule readme](https://github.com/vegaprotocol/vegacapsule#readme) for setting up and running Capsule - follow by Pre-start and Quick Start (points 1-2)
2. Bootstrap with auto-installed dependencies including wallet
```bash
vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force
```
Refer to the [Vega Capsule readme](https://github.com/vegaprotocol/vegacapsule#readme) for setting up and running Capsule. You will need [Go 1.19 or later](https://go.dev/doc/install) and [Docker](https://docs.docker.com/get-docker/) installed.
### Troubleshooting
@@ -34,6 +29,6 @@ vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/con
## Vega Wallet Setup
You can then refer to (or run) `frontend-monorepo/vegacapsule/setup-vegawallet.sh`. This will initialise and configure your wallet to have the correct public keys and network config to run against capsule.
Start by [downloading the Vega wallet software here](https://github.com/vegaprotocol/vega/releases).
Go to the .env file in `apps/explorer-e2e` and set the `CYPRESS_VEGA_WALLET_API_TOKEN` environment variable by pasting in your wallets long lived api token
You can then refer to (or run) `vegacapsule/setup-vegawallet.sh`. This will initialise and configure your wallet to have the correct public keys and network config to run against capsule.
@@ -29,7 +29,7 @@
"market.liquidity.maximumLiquidityFeeFactorLevel",
"market.liquidity.minimum.probabilityOfTrading.lpOrders",
"market.liquidity.probabilityOfTrading.tau.scaling",
"market.liquidity.stakeToCcyVolume",
"market.liquidity.stakeToCcySiskas",
"market.liquidity.targetstake.triggering.ratio",
"market.liquidityProvision.minLpStakeQuantumMultiple",
"market.liquidityProvision.shapes.maxSize",
@@ -52,8 +52,7 @@ context('Blocks page', { tags: '@regression' }, function () {
});
});
// Skipping - see https://github.com/vegaprotocol/frontend-monorepo/issues/2494
it.skip('Previous button disabled on first block', function () {
it('Previous button disabled on first block', function () {
cy.get('[data-testid="block-input"]').type('1');
cy.get('[data-testid="go-submit"]').click();
cy.get(previousBlockBtn).find('button').should('be.disabled');
@@ -116,7 +115,7 @@ context('Blocks page', { tags: '@regression' }, function () {
});
function waitForBlocksResponse() {
cy.get('[data-testid="loader"]').should('not.exist', { timeout: 18000 });
cy.contains('Loading...').should('not.exist', { timeout: 18000 });
}
function validateBlocksDisplayed() {
+21 -19
View File
@@ -20,18 +20,19 @@ context('Home Page', function () {
1: 'Height',
2: 'Uptime',
3: 'Total nodes',
4: 'Total staked',
5: 'Backlog',
6: 'Trades / second',
7: 'Orders / block',
8: 'Orders / second',
9: 'Transactions / block',
10: 'Block time',
11: 'Time',
12: 'App',
13: 'Tendermint',
14: 'Up since',
15: 'Chain ID',
4: 'Inactive nodes',
5: 'Total staked',
6: 'Backlog',
7: 'Trades / second',
8: 'Orders / block',
9: 'Orders / second',
10: 'Transactions / block',
11: 'Block time',
12: 'Time',
13: 'App',
14: 'Tendermint',
15: 'Up since',
16: 'Chain ID',
};
cy.get('[data-testid="stats-title"]')
@@ -39,7 +40,7 @@ context('Home Page', function () {
cy.wrap($list).should('have.text', statTitles[index]);
})
.then(($list) => {
cy.wrap($list).should('have.length', 16);
cy.wrap($list).should('have.length', 17);
});
cy.get(statsValue).eq(0).should('have.text', 'CONNECTED');
@@ -49,29 +50,30 @@ context('Home Page', function () {
.invoke('text')
.should('match', /\d+d \d+h \d+m \d+s/i);
cy.get(statsValue).eq(3).should('have.text', '2');
cy.get(statsValue).eq(4).should('have.text', '2');
cy.get(statsValue)
.eq(4)
.eq(5)
.invoke('text')
.should('match', /\d+\.\d\d(?!\d)/i);
cy.get(statsValue).eq(5).should('have.text', '0');
cy.get(statsValue).eq(6).should('have.text', '0');
cy.get(statsValue).eq(7).should('have.text', '0');
cy.get(statsValue).eq(8).should('have.text', '0');
cy.get(statsValue).eq(9).should('not.be.empty');
cy.get(statsValue).eq(9).should('have.text', '0');
cy.get(statsValue).eq(10).should('not.be.empty');
cy.get(statsValue).eq(11).should('not.be.empty');
cy.get(statsValue).eq(12).should('not.be.empty');
if (Cypress.env('NIGHTLY_RUN') != true) {
cy.get(statsValue)
.eq(12)
.eq(13)
.invoke('text')
.should('match', /v\d+\.\d+\.\d+/i);
}
cy.get(statsValue)
.eq(13)
.eq(14)
.invoke('text')
.should('match', /\d+\.\d+\.\d+/i);
cy.get(statsValue).eq(14).should('not.be.empty');
cy.get(statsValue).eq(15).should('not.be.empty');
cy.get(statsValue).eq(16).should('not.be.empty');
});
it('Block height should be updating', function () {
+1 -1
View File
@@ -18,4 +18,4 @@ NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
NX_EXPLORER_MARKETS=1
NX_EXPLORER_ORACLES=1
NX_EXPLORER_TXS_LIST=1
NX_EXPLORER_TXS_LIST=0
-20
View File
@@ -1,20 +0,0 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_ENV=MIRROR
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
# App flags
NX_EXPLORER_ASSETS=1
NX_EXPLORER_GENESIS=1
NX_EXPLORER_GOVERNANCE=1
NX_EXPLORER_NETWORK_PARAMETERS=1
NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
NX_EXPLORER_MARKETS=0
NX_EXPLORER_ORACLES=0
NX_EXPLORER_TXS_LIST=1
+1 -1
View File
@@ -70,7 +70,7 @@
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.66.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.62.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
]
}
},
+19 -2
View File
@@ -1,15 +1,23 @@
import classnames from 'classnames';
import { useState, useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import { useLocation } from 'react-router-dom';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import {
EnvironmentProvider,
NetworkLoader,
useEnvironment,
} from '@vegaprotocol/environment';
import { NetworkInfo } from '@vegaprotocol/network-info';
import { Nav } from './components/nav';
import { Header } from './components/header';
import { Main } from './components/main';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import { ENV } from './config/env';
import type { InMemoryCacheConfig } from '@apollo/client';
function App() {
const { VEGA_ENV } = useEnvironment();
const [menuOpen, setMenuOpen] = useState(false);
const location = useLocation();
@@ -18,9 +26,18 @@ function App() {
setMenuOpen(false);
}, [location]);
useEffect(() => {
Sentry.init({
dsn: ENV.dsn,
integrations: [new BrowserTracing()],
tracesSampleRate: 1,
environment: VEGA_ENV,
});
}, [VEGA_ENV]);
const cacheConfig: InMemoryCacheConfig = {
typePolicies: {
statistics: {
Node: {
keyFields: false,
},
},
@@ -18,6 +18,7 @@ const AssetBalance = ({
showAssetLink = true,
}: AssetBalanceProps) => {
const { data } = useExplorerAssetQuery({
fetchPolicy: 'cache-first',
variables: { id: assetId },
});
@@ -59,8 +59,7 @@ describe('Blocks infinite list', () => {
error={undefined}
/>
);
expect(screen.getByTestId('emptylist')).toBeInTheDocument();
expect(screen.getByText('This chain has 0 blocks')).toBeInTheDocument();
expect(screen.getByText('No items')).toBeInTheDocument();
});
it('error is displayed at item level', () => {
@@ -4,8 +4,6 @@ import InfiniteLoader from 'react-window-infinite-loader';
import { t } from '@vegaprotocol/react-helpers';
import type { BlockMeta } from '../../routes/blocks/tendermint-blockchain-response';
import { BlockData } from './block-data';
import EmptyList from '../empty-list/empty-list';
import { Loader } from '@vegaprotocol/ui-toolkit';
interface BlocksInfiniteListProps {
hasMoreBlocks: boolean;
@@ -33,16 +31,7 @@ export const BlocksInfiniteList = ({
className,
}: BlocksInfiniteListProps) => {
if (!blocks) {
if (!areBlocksLoading) {
return (
<EmptyList
heading={t('This chain has 0 blocks')}
label={t('Check back soon')}
/>
);
} else {
return <Loader />;
}
return <div>No items</div>;
}
// If there are more items to be loaded then add an extra row to hold a loading indicator.
@@ -61,7 +50,7 @@ export const BlocksInfiniteList = ({
if (error) {
content = t(`${error}`);
} else if (!isItemLoaded(index)) {
content = <Loader />;
content = t('Loading...');
} else {
content = <BlockData block={blocks[index]} />;
}
@@ -1,34 +0,0 @@
export type EmptyListProps = {
heading?: string;
label?: string;
};
/**
* Renders the empty state from github ticket #1463
*/
const EmptyList = ({ heading, label }: EmptyListProps) => {
return (
<div
className="empty-list w-full items-center h-full align-center"
data-testid="emptylist"
>
<div className="skeleton-list border-dashed border-neutral-800 rounded p-5 w-full border-[1px] grid gap-4 grid-cols-9 grid-rows-1 place-content-around mb-4">
<div className="bg-neutral-900 mr-5 h-3 col-span-5"></div>
<div className="bg-neutral-900 h-3 col-span-1"></div>
</div>
<div className="mt-4">
{heading ? (
<h1 className="font-alpha text-xl uppercase text-center leading-relaxed">
{heading}
</h1>
) : null}
{label ? (
<p className="font-alpha text-gray-500 text-center">{label}</p>
) : null}
</div>
</div>
);
};
export default EmptyList;
@@ -16,6 +16,7 @@ export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
*/
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
const { data } = useExplorerAssetQuery({
fetchPolicy: 'cache-first',
variables: { id },
});
@@ -29,7 +29,7 @@ const MarketLink = ({ id, ...props }: MarketLinkProps) => {
label = (
<div title={t('Unknown market')}>
<span role="img" aria-label="Unknown market" className="img">
&nbsp;{t('Invalid market')}
</span>
&nbsp;{id}
</div>
@@ -1,3 +1,4 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
@@ -22,14 +22,8 @@ fragment ExplorerDeterministicOrderFields on Order {
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
state
}
}
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } };
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID'];
@@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
}>;
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } };
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order {
@@ -38,14 +38,8 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
state
}
}
`;
@@ -1,6 +1,5 @@
import { t } from '@vegaprotocol/react-helpers';
import type * as Schema from '@vegaprotocol/types';
import type { components } from '../../../../types/explorer';
export interface DeterministicOrderDetailsProps {
id: string;
@@ -20,8 +19,7 @@ export const statusText: Record<Schema.OrderStatus, string> = {
STATUS_STOPPED: t('Stopped'),
};
export const sideText: Record<components['schemas']['vegaSide'], string> = {
SIDE_UNSPECIFIED: t('Unspecified'),
export const sideText: Record<Schema.Side, string> = {
SIDE_BUY: t('Buy'),
SIDE_SELL: t('Sell'),
};
@@ -1,80 +0,0 @@
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import OrderSummary from './order-summary';
import type { OrderSummaryModifier } from './order-summary';
import { render } from '@testing-library/react';
import { ExplorerDeterministicOrderDocument } from '../order-details/__generated__/Order';
const mock = {
request: {
query: ExplorerDeterministicOrderDocument,
variables: {
orderId: '123',
},
},
result: {
data: {
orderByID: {
__typename: 'Order',
id: '123',
type: 'GTC',
status: 'OPEN',
version: '1',
createdAt: 'Tue, Jan 10, 2023 3:35',
expiresAt: 'Tue, Jan 10, 2023 3:35',
updatedAt: 'Tue, Jan 10, 2023 3:35',
rejectionReason: null,
reference: null,
timeInForce: 'GTC',
price: '333',
side: 'SIDE_BUY',
remaining: '100',
size: '100',
party: {
id: '456',
},
market: {
__typename: 'Market',
id: '789',
decimalPlaces: 2,
tradableInstrument: {
instrument: {
name: 'TEST',
},
},
},
},
},
},
};
function renderComponent(
id: string,
mocks?: MockedResponse[],
modifier?: OrderSummaryModifier
) {
return render(
<MockedProvider mocks={mocks}>
<OrderSummary id={id} modifier={modifier} />
</MockedProvider>
);
}
describe('Order Summary component', () => {
it('side, size are present', async () => {
const res = renderComponent(mock.result.data.orderByID.id, [mock]);
expect(await res.findByText('Buy')).toBeInTheDocument();
expect(await res.findByText('100')).toBeInTheDocument();
// Note: Market is not mocked so the PriceInMarket component is not rendering in this test - hence no price formatting
});
it('Cancelled modifier add strikethrough', async () => {
const res = renderComponent(
mock.result.data.orderByID.id,
[mock],
'cancelled'
);
const buy = await res.findByText('Buy');
expect(buy.parentElement).toHaveClass('line-through');
});
});
@@ -1,50 +0,0 @@
import { useExplorerDeterministicOrderQuery } from '../order-details/__generated__/Order';
import PriceInMarket from '../price-in-market/price-in-market';
import { sideText } from '../order-details/lib/order-labels';
// Note: Edited has no style currently
export type OrderSummaryModifier = 'cancelled' | 'edited';
/**
* Provides Tailwind classnames to apply to order
* @param modifier
* @returns string
*/
export function getClassName(modifier?: OrderSummaryModifier) {
if (modifier === 'cancelled') {
return 'line-through';
}
return undefined;
}
export interface OrderSummaryProps {
id: string;
modifier?: OrderSummaryModifier;
}
/**
* This component renders the *current* details for an order, like OrderDetails but much
* more compact. It is equivalent to OrderTxSummary, but operates on an order rather than
* the transaction that creates an order
*/
const OrderSummary = ({ id, modifier }: OrderSummaryProps) => {
const { data, error } = useExplorerDeterministicOrderQuery({
variables: { orderId: id },
});
if (error || !data || (data && !data.orderByID)) {
return <div data-testid="order-summary">-</div>;
}
const order = data.orderByID;
return (
<div data-testid="order-summary" className={getClassName(modifier)}>
<span>{sideText[order.side]}</span>&nbsp;
<span>{order.size}</span>&nbsp;<i>@</i>&nbsp;
<PriceInMarket marketId={order.market.id} price={order.price} />
</div>
);
};
export default OrderSummary;
@@ -1,119 +0,0 @@
import type { MockedResponse } from '@apollo/client/testing';
import type { components } from '../../../types/explorer';
import type { UnknownObject } from '../nested-data-list';
import { MockedProvider } from '@apollo/client/testing';
import OrderTxSummary from './order-tx-summary';
import { render } from '@testing-library/react';
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
type Order = components['schemas']['v1OrderSubmission'];
function renderComponent(order: Order, mocks?: MockedResponse[]) {
return render(
<MockedProvider mocks={mocks}>
<OrderTxSummary order={order} />
</MockedProvider>
);
}
describe('Order TX Summary component', () => {
it('Renders nothing if the order passed is somehow null', () => {
const res = renderComponent({} as UnknownObject as Order);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order passed lacks a side', () => {
const o: Order = {
marketId: '123',
price: '100',
type: 'TYPE_LIMIT',
size: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order passed lacks a price', () => {
const o: Order = {
marketId: '123',
side: 'SIDE_BUY',
type: 'TYPE_LIMIT',
size: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order has an unspecified side', () => {
const o: Order = {
marketId: '123',
side: 'SIDE_UNSPECIFIED',
type: 'TYPE_LIMIT',
size: '10',
price: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order has an unspecified market', () => {
const o: Order = {
side: 'SIDE_BUY',
type: 'TYPE_LIMIT',
size: '10',
price: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('side, size and price in market if all details are present', async () => {
const o: Order = {
marketId: '123',
side: 'SIDE_BUY',
type: 'TYPE_LIMIT',
size: '10',
price: '333',
};
const mock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'TEST',
product: {
__typename: 'Future',
quoteName: 'TEST',
},
},
},
},
},
},
};
const res = renderComponent(o, [mock]);
expect(res.queryByTestId('order-summary')).toBeInTheDocument();
expect(res.getByText('Buy')).toBeInTheDocument();
expect(res.getByText('10')).toBeInTheDocument();
// Initially renders price alone
expect(res.getByText('333')).toBeInTheDocument();
// After fetch renders formatted price and asset quotename
expect(await res.findByText('3.33')).toBeInTheDocument();
expect(await res.findByText('TEST')).toBeInTheDocument();
});
});
@@ -1,41 +0,0 @@
import type { components } from '../../../types/explorer';
import PriceInMarket from '../price-in-market/price-in-market';
import { sideText } from '../order-details/lib/order-labels';
export type OrderSummaryProps = {
order: components['schemas']['v1OrderSubmission'];
};
/**
* Shows a brief, relatively plaintext summary of an order transaction
* showing the price, correctly formatted. Created for the Batch Submission
* list, should be kept compact.
*
* Market name is expected to be listed elsewhere, so is not included
*/
const OrderTxSummary = ({ order }: OrderSummaryProps) => {
// Render nothing if the Order Submission doesn't look right
if (
!order ||
!order.marketId ||
!order.price ||
!order.side ||
order.side === 'SIDE_UNSPECIFIED'
) {
return null;
}
return (
<div data-testid="order-summary">
<span>{sideText[order.side]}</span>&nbsp;
<span>{order.size}</span>&nbsp;<i className="text-xs">@</i>&nbsp;
<PriceInMarket
marketId={order.marketId}
price={order.price}
></PriceInMarket>
</div>
);
};
export default OrderTxSummary;
@@ -15,7 +15,6 @@ export type PriceInMarketProps = {
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
});
let label = price;
@@ -38,9 +37,9 @@ const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
);
} else {
return (
<label>
<div className="inline-block">
<span>{label}</span> <span>{suffix}</span>
</label>
</div>
);
}
};
@@ -1,29 +0,0 @@
import type { BatchInstruction } from '../../../../routes/types/block-explorer-response';
import { TxOrderType } from '../../tx-order-type';
import { MarketLink } from '../../../links';
import OrderSummary from '../../../order-summary/order-summary';
interface BatchAmendProps {
index: number;
submission: BatchInstruction;
}
/**
* Table row for a single amendment in a batch submission
*/
export const BatchAmend = ({ index, submission }: BatchAmendProps) => {
return (
<tr key={`amend-${index}`}>
<td>{index}</td>
<td>
<TxOrderType orderType={'OrderAmendment'} />
</td>
<td>
<OrderSummary id={submission.orderId} modifier="edited" />
</td>
<td>
<MarketLink id={submission.marketId} />
</td>
</tr>
);
};
@@ -1,29 +0,0 @@
import type { BatchCancellationInstruction } from '../../../../routes/types/block-explorer-response';
import { TxOrderType } from '../../tx-order-type';
import { MarketLink } from '../../../links';
import OrderSummary from '../../../order-summary/order-summary';
interface BatchCancelProps {
index: number;
submission: BatchCancellationInstruction;
}
/**
* Table row for a single cancellation in a batch submission
*/
export const BatchCancel = ({ index, submission }: BatchCancelProps) => {
return (
<tr>
<td>{index}</td>
<td>
<TxOrderType orderType={'OrderCancellation'} />
</td>
<td>
<OrderSummary id={submission.orderId} modifier="cancelled" />
</td>
<td>
<MarketLink id={submission.marketId} />
</td>
</tr>
);
};
@@ -1,29 +0,0 @@
import type { BatchInstruction } from '../../../../routes/types/block-explorer-response';
import { TxOrderType } from '../../tx-order-type';
import { MarketLink } from '../../../links';
import OrderTxSummary from '../../../order-summary/order-tx-summary';
interface BatchOrderProps {
index: number;
submission: BatchInstruction;
}
/**
* Table row for a single order in a batch submission
*/
export const BatchOrder = ({ index, submission }: BatchOrderProps) => {
return (
<tr key={`batch-${index}`}>
<td>{index}</td>
<td>
<TxOrderType orderType={'OrderSubmission'} />
</td>
<td>
<OrderTxSummary order={submission} />
</td>
<td>
<MarketLink id={submission.marketId} />
</td>
</tr>
);
};
@@ -31,10 +31,6 @@ export const ChainResponseCode = ({
const icon = isSuccess ? '✅' : '❌';
const label = ErrorCodes.get(code) || 'Unknown response code';
// Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245
const displayError =
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
return (
<div title={`Response code: ${code} - ${label}`}>
<span
@@ -45,8 +41,8 @@ export const ChainResponseCode = ({
{icon}
</span>
{hideLabel ? null : <span>{label}</span>}
{!hideLabel && !!displayError ? (
<span className="ml-1 whitespace-pre">&mdash;&nbsp;{displayError}</span>
{!hideLabel && !!error ? (
<span className="ml-1">&mdash;&nbsp;{error}</span>
) : null}
</div>
);
@@ -14,12 +14,6 @@ interface TxDetailsSharedProps {
blockData: TendermintBlocksResponse | undefined;
}
// Applied to all header cells
const sharedHeaderProps = {
// Ensures that multi line contents still have the header aligned to the first line
className: 'align-top',
};
/**
* These rows are shown for every transaction type, providing a consistent set of rows for the top
* of a transaction details row. The order is relatively arbitrary but felt right - it might need to
@@ -40,27 +34,27 @@ export const TxDetailsShared = ({
return (
<>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{t('Type')}</TableCell>
<TableCell>{txData.type}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
<TableCell>{t('Hash')}</TableCell>
<TableCell>
<code>{txData.hash}</code>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Submitter')}</TableCell>
<TableCell>{t('Submitter')}</TableCell>
<TableCell>{pubKey ? <PartyLink id={pubKey} /> : '-'}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
<TableCell>{t('Block')}</TableCell>
<TableCell>
<BlockLink height={height} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
<TableCell>{t('Time')}</TableCell>
<TableCell>
{time ? (
<div>
@@ -77,7 +71,7 @@ export const TxDetailsShared = ({
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
<TableCell>{t('Response code')}</TableCell>
<TableCell>
<ChainResponseCode code={txData.code} error={txData.error} />
</TableCell>
@@ -1,15 +1,8 @@
import { t } from '@vegaprotocol/react-helpers';
import type {
BatchCancellationInstruction,
BatchInstruction,
BlockExplorerTransactionResult,
} from '../../../routes/types/block-explorer-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableWithTbody, TableRow, TableCell, Table } from '../../table';
import { BatchCancel } from './batch-submission/batch-cancel';
import { BatchAmend } from './batch-submission/batch-amend';
import { BatchOrder } from './batch-submission/batch-order';
import { TableWithTbody, TableRow, TableCell } from '../../table';
interface TxDetailsBatchProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -23,11 +16,6 @@ interface TxDetailsBatchProps {
*
* Design considerations for batch:
* - So far it's very basic details about the size of the batch
*
* Batches are processed in the following order:
* - Cancellations
* - Amends
* - Submissions
*/
export const TxDetailsBatch = ({
txData,
@@ -38,80 +26,47 @@ export const TxDetailsBatch = ({
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const submissions: BatchInstruction[] =
txData.command.batchMarketInstructions.submissions;
const countSubmissions = submissions?.length || 0;
const amendments: BatchInstruction[] =
txData.command.batchMarketInstructions.amendments;
const countAmendments = amendments?.length || 0;
const cancellations: BatchCancellationInstruction[] =
txData.command.batchMarketInstructions.cancellations;
const countCancellations = cancellations.length || 0;
const countSubmissions =
txData.command.batchMarketInstructions.submissions?.length || 0;
const countAmendments =
txData.command.batchMarketInstructions.amendments?.length || 0;
const countCancellations =
txData.command.batchMarketInstructions.cancellations?.length || 0;
const countTotal = countSubmissions + countAmendments + countCancellations;
let index = 0;
return (
<div key={`tx-${index}`}>
<TableWithTbody className="mb-8">
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
<span>{countTotal}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Cancellations')}</span>
</TableCell>
<TableCell>
<span>{countCancellations}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Amendments')}</span>
</TableCell>
<TableCell>
<span>{countAmendments}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Submissions')}</span>
</TableCell>
<TableCell>
<span>{countSubmissions}</span>
</TableCell>
</TableRow>
</TableWithTbody>
<Table className="max-w-5xl min-w-fit">
<thead>
<TableRow modifier="bordered" className="font-mono">
<th align="left">{t('#')}</th>
<th align="left">{t('Type')}</th>
<th align="left">{t('Order')}</th>
<th align="left">{t('Market')}</th>
</TableRow>
</thead>
<tbody>
{cancellations.map((c) => (
<BatchCancel key={`bc-${index}`} submission={c} index={index++} />
))}
{amendments.map((a) => (
<BatchAmend key={`ba-${index}`} submission={a} index={index++} />
))}
{submissions.map((s) => (
<BatchOrder key={`bo-${index}`} submission={s} index={index++} />
))}
</tbody>
</Table>
</div>
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
<span>{countTotal}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Submissions')}</span>
</TableCell>
<TableCell>
<span>{countSubmissions}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Amendments')}</span>
</TableCell>
<TableCell>
<span>{countAmendments}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Cancellations')}</span>
</TableCell>
<TableCell>
<span>{countCancellations}</span>
</TableCell>
</TableRow>
</TableWithTbody>
);
};
@@ -45,7 +45,7 @@ export const TxDetailsWrapper = ({
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
return (
<div key={`txd-${txData.hash}`}>
<>
<section>{child({ txData, pubKey, blockData })}</section>
<details title={t('Decoded transaction')} className="mt-3">
@@ -59,7 +59,7 @@ export const TxDetailsWrapper = ({
<code className="break-all font-mono text-xs">{raw}</code>
</details>
) : null}
</div>
</>
);
};
@@ -14,6 +14,8 @@ export const methodText: Record<
METHOD_NOW: 'Immediate',
METHOD_UNSPECIFIED: 'Unspecified',
METHOD_AT_END_OF_EPOCH: 'End of epoch',
// This will be removed in a future release
METHOD_IN_ANGER: 'Immediate',
};
interface TxDetailsUndelegateProps {
@@ -2,3 +2,4 @@ export { TxList } from './tx-list';
export { TxOrderType } from './tx-order-type';
export { TxsInfiniteList } from './txs-infinite-list';
export { TxsInfiniteListItem } from './txs-infinite-list-item';
export { TxsStatsInfo } from './txs-stats-info';
@@ -137,7 +137,8 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
// This will get unwieldy and should probably produce a different colour of tag
if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
colours =
'text-white dark-text-white bg-vega-pink-dark dark:bg-vega-pink-dark';
} else if (type === 'Proposal' || type === 'Governance Proposal') {
if (command && !!command.proposalSubmission) {
type = getLabelForProposal(command.proposalSubmission);
@@ -46,10 +46,7 @@ describe('Txs infinite list', () => {
error={undefined}
/>
);
expect(screen.getByTestId('emptylist')).toBeInTheDocument();
expect(
screen.getByText('This chain has 0 transactions')
).toBeInTheDocument();
expect(screen.getByText('No items')).toBeInTheDocument();
});
it('error is displayed at item level', () => {
@@ -4,8 +4,6 @@ import InfiniteLoader from 'react-window-infinite-loader';
import { t, useScreenDimensions } from '@vegaprotocol/react-helpers';
import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import EmptyList from '../empty-list/empty-list';
import { Loader } from '@vegaprotocol/ui-toolkit';
interface TxsInfiniteListProps {
hasMoreTxs: boolean;
@@ -31,7 +29,7 @@ const Item = ({ index, style, isLoading, error }: ItemProps) => {
if (error) {
content = t(`Cannot fetch transaction: ${error}`);
} else if (isLoading) {
content = <Loader />;
content = t('Loading...');
} else {
const {
hash,
@@ -70,16 +68,7 @@ export const TxsInfiniteList = ({
const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
if (!txs) {
if (!areTxsLoading) {
return (
<EmptyList
heading={t('This chain has 0 transactions')}
label={t('Check back soon')}
/>
);
} else {
return <Loader />;
}
return <div>No items</div>;
}
// If there are more items to be loaded then add an extra row to hold a loading indicator.
@@ -1,4 +1,5 @@
import { Routes } from '../../routes/route-names';
import { DATA_SOURCES } from '../../config';
import { RenderFetched } from '../render-fetched';
import { TruncatedLink } from '../truncate/truncated-link';
import { TxOrderType } from './tx-order-type';
@@ -7,9 +8,6 @@ import { t, useFetch } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
import isNumber from 'lodash/isNumber';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import { getTxsDataUrl } from '../../hooks/use-txs-data';
import { Loader } from '@vegaprotocol/ui-toolkit';
import EmptyList from '../empty-list/empty-list';
interface TxsPerBlockProps {
blockHeight: string;
@@ -19,11 +17,15 @@ interface TxsPerBlockProps {
const truncateLength = 5;
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
const filters = `filters[block.height]=${blockHeight}`;
const url = getTxsDataUrl({ limit: txCount.toString(), filters });
// TODO after https://github.com/vegaprotocol/vega/pull/6958/files is merged and deployed, use filter
// by block height instead
const {
state: { data, loading, error },
} = useFetch<BlockExplorerTransactions>(url);
} = useFetch<BlockExplorerTransactions>(
`${
DATA_SOURCES.blockExplorerUrl
}/transactions?before=${blockHeight.toString()}.0&limit=${txCount}`
);
return (
<RenderFetched error={error} loading={loading} className="text-body-large">
@@ -86,13 +88,10 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
</tbody>
</Table>
</div>
) : loading ? (
<Loader />
) : (
<EmptyList
heading={t('No transactions in this block')}
label={t('0 transactions')}
/>
<div className="sr-only">
{t(`No transactions in block ${blockHeight}`)}
</div>
)}
</RenderFetched>
);
@@ -0,0 +1,77 @@
import { t } from '@vegaprotocol/react-helpers';
import { useEffect } from 'react';
import { InfoBlock } from '../../components/info-block';
import { Panel } from '../../components/panel';
import { useExplorerStatsQuery } from './__generated__/Explorer-stats';
import type { ExplorerStatsFieldsFragment } from './__generated__/Explorer-stats';
interface StatsMap {
field: keyof ExplorerStatsFieldsFragment;
label: string;
info: string;
}
export const TXS_STATS_MAP: StatsMap[] = [
{
field: 'averageOrdersPerBlock',
label: t('Orders per block'),
info: t(
'Number of new orders processed in the last block. All orders derived from pegged orders and liquidity commitments count as a single order'
),
},
{
field: 'txPerBlock',
label: t('Transactions per block'),
info: t('Number of transactions processed in the last block'),
},
{
field: 'tradesPerSecond',
label: t('Trades per second'),
info: t('Number of trades processed in the last second'),
},
{
field: 'ordersPerSecond',
label: t('Order per second'),
info: t(
'Number of orders processed in the last second. All orders derived from pegged orders and liquidity commitments count as a single order'
),
},
];
interface TxsStatsInfoProps {
className?: string;
}
export const TxsStatsInfo = ({ className }: TxsStatsInfoProps) => {
const { data, startPolling, stopPolling } = useExplorerStatsQuery();
useEffect(() => {
startPolling(1000);
return () => stopPolling();
});
const gridStyles =
'grid grid-rows-2 gap-4 grid-cols-2 xl:gap-8 xl:grid-rows-1 xl:grid-cols-4';
return (
<Panel className={className}>
<section className={gridStyles}>
{TXS_STATS_MAP.map((field) => {
if (!data?.statistics) {
return null;
}
// Workaround for awkward typing
const title = data.statistics[field.field] || '';
return (
<InfoBlock
subtitle={field.label}
tooltipInfo={field.info}
title={title}
/>
);
})}
</section>
</Panel>
);
};
@@ -84,13 +84,13 @@ export const WithdrawalProgress = ({ id, txStatus }: TxsStatsInfoProps) => {
const classes = {
indicatorFailed:
'rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2 border-pink-600 bg-vega-pink-600 text-center text-white font-bold leading-5',
'rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2 border-red-600 bg-red-600 text-center text-white font-bold leading-5',
textFailed:
'absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase text-vega-pink',
'absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase text-vega-red',
indicatorComplete:
'rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2 border-vega-green bg-vega-green text-center text-white leading-5',
'rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2 border-vega-green-dark bg-vega-green-dark text-center text-white leading-5',
textComplete:
'absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase text-vega-green',
'absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase text-vega-green-dark',
indicatorIncomplete:
'rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2 border-gray-300 text-center leading-5',
textIncomplete:
@@ -140,7 +140,7 @@ export function WithdrawalProgressSeparator({
return (
<div
className={`flex-auto border-t-2 transition duration-500 ease-in-out ${
isComplete ? 'border-vega-green' : 'border-gray-300'
isComplete ? 'border-vega-green-dark' : 'border-gray-300'
}`}
></div>
);
@@ -1,44 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import Assets from './index';
import type { MockedResponse } from '@apollo/client/testing';
import { ExplorerAssetDocument } from '../../components/links/asset-link/__generated__/Asset';
function renderComponent(mock: MockedResponse[]) {
return (
<MemoryRouter>
<MockedProvider mocks={mock}>
<Assets />
</MockedProvider>
</MemoryRouter>
);
}
describe('Assets index', () => {
it('Renders loader when loading', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
},
result: {
data: {},
},
};
const res = render(renderComponent([mock]));
expect(await res.findByTestId('loader')).toBeInTheDocument();
});
it('Renders EmptyList when loading completes and there are no results', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
},
result: {
data: {},
},
};
const res = render(renderComponent([mock]));
expect(await res.findByTestId('emptylist')).toBeInTheDocument();
});
});
+7 -16
View File
@@ -2,15 +2,14 @@ import { getNodes, t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { useExplorerAssetsQuery } from './__generated__/Assets';
import type { AssetsFieldsFragment } from './__generated__/Assets';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import EmptyList from '../../components/empty-list/empty-list';
const Assets = () => {
const { data, loading } = useExplorerAssetsQuery();
const { data } = useExplorerAssetsQuery();
useDocumentTitle(['Assets']);
useScrollToLocation();
@@ -18,25 +17,17 @@ const Assets = () => {
const assets = getNodes<AssetsFieldsFragment>(data?.assetsConnection);
if (!assets || assets.length === 0) {
if (!loading) {
return (
<section>
<RouteTitle data-testid="assets-header">{t('Assets')}</RouteTitle>
<EmptyList
heading={t('This chain has no assets')}
label={t('0 assets')}
/>
</section>
);
} else {
return <Loader />;
}
return <section></section>;
}
return (
<section>
<RouteTitle data-testid="assets-header">{t('Assets')}</RouteTitle>
{assets.map((a) => {
if (!a) {
return null;
}
return (
<React.Fragment key={a.id}>
<SubHeading data-testid="asset-header" id={a.id}>
@@ -18,7 +18,6 @@ import { RenderFetched } from '../../../components/render-fetched';
import { t, useFetch } from '@vegaprotocol/react-helpers';
import { NodeLink } from '../../../components/links';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import EmptyList from '../../../components/empty-list/empty-list';
const Block = () => {
const { block } = useParams<{ block: string }>();
@@ -113,12 +112,7 @@ const Block = () => {
blockHeight={blockData.result.block.header.height}
txCount={blockData.result.block.data.txs.length}
/>
) : (
<EmptyList
heading={t('This block is empty')}
label={t('0 transactions')}
/>
)}
) : null}
</>
)}
</>
@@ -1,6 +1,6 @@
import { t, useFetch } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { DATA_SOURCES } from '../../config';
import type { TendermintGenesisResponse } from './tendermint-genesis-response';
import { useDocumentTitle } from '../../hooks/use-document-title';
@@ -9,16 +9,11 @@ const Genesis = () => {
useDocumentTitle(['Genesis']);
const {
state: { data: genesis, loading },
state: { data: genesis },
} = useFetch<TendermintGenesisResponse>(
`${DATA_SOURCES.tendermintUrl}/genesis`
);
if (!genesis?.result.genesis) {
if (loading) {
return <Loader />;
}
return null;
}
if (!genesis?.result.genesis) return null;
return (
<section>
<RouteTitle data-testid="genesis-header">{t('Genesis')}</RouteTitle>
@@ -2,35 +2,19 @@ import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { useExplorerProposalsQuery } from './__generated__/Proposals';
import { useDocumentTitle } from '../../hooks/use-document-title';
import EmptyList from '../../components/empty-list/empty-list';
const Governance = () => {
const { data, loading } = useExplorerProposalsQuery({
const { data } = useExplorerProposalsQuery({
errorPolicy: 'ignore',
});
useDocumentTitle();
if (!data || !data.proposalsConnection || !data.proposalsConnection.edges) {
if (!loading) {
return (
<section>
<RouteTitle data-testid="governance-header">
{t('Governance Proposals')}
</RouteTitle>
<EmptyList
heading={t('This chain has no proposals')}
label={t('0 proposals')}
/>
</section>
);
} else {
return <Loader />;
}
return <section></section>;
}
const proposals = data?.proposalsConnection?.edges.map((e) => {
@@ -6,7 +6,7 @@ const defaultOptions = {} as const;
export type ExplorerMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, tradingMode: Types.MarketTradingMode, state: Types.MarketState, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, name: string, decimals: number, globalRewardPoolAccount?: { __typename?: 'AccountBalance', balance: string } | null } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', balance: string, type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string } } } | null> | null } | null, data?: { __typename?: 'MarketData', markPrice: string, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, midPrice: string, staticMidPrice: string, timestamp: any, openInterest: string, auctionEnd?: string | null, auctionStart?: string | null, indicativePrice: string, indicativeVolume: string, trigger: Types.AuctionTrigger, extensionTrigger: Types.AuctionTrigger, targetStake?: string | null, suppliedStake?: string | null, marketValueProxy: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', auctionExtensionSecs: number, probability: number } }> | null, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null } }> } | null };
export type ExplorerMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, tradingMode: Types.MarketTradingMode, state: Types.MarketState, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, name: string, decimals: number, globalRewardPoolAccount?: { __typename?: 'AccountBalance', balance: string } | null } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', balance: string, type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string } } } | null> | null } | null, data?: { __typename?: 'MarketData', markPrice: string, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, midPrice: string, staticMidPrice: string, timestamp: any, openInterest: string, auctionEnd?: string | null, auctionStart?: string | null, indicativePrice: string, indicativeVolume: string, trigger: Types.AuctionTrigger, extensionTrigger: Types.AuctionTrigger, targetStake?: string | null, suppliedStake?: string | null, marketValueProxy: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', auctionExtensionSecs: number, probability: number } }> | null, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null } }> } | null };
export const ExplorerMarketsDocument = gql`
@@ -1,48 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import Markets from './index';
import type { MockedResponse } from '@apollo/client/testing';
import { ExplorerMarketsDocument } from './__generated__/Markets';
function renderComponent(mock: MockedResponse[]) {
return (
<MemoryRouter>
<MockedProvider mocks={mock}>
<Markets />
</MockedProvider>
</MemoryRouter>
);
}
describe('Markets index', () => {
it('Renders loader when loading', async () => {
const mock = {
request: {
query: ExplorerMarketsDocument,
},
result: {
data: {
marketsConnection: [],
},
},
};
const res = render(renderComponent([mock]));
expect(await res.findByTestId('loader')).toBeInTheDocument();
});
it('Renders EmptyList when loading completes and there are no results', async () => {
const mock = {
request: {
query: ExplorerMarketsDocument,
},
result: {
data: {
marketsConnection: [],
},
},
};
const res = render(renderComponent([mock]));
expect(await res.findByTestId('emptylist')).toBeInTheDocument();
});
});
+12 -20
View File
@@ -1,15 +1,14 @@
import React from 'react';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
import { t } from '@vegaprotocol/react-helpers';
import { useExplorerMarketsQuery } from './__generated__/Markets';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import EmptyList from '../../components/empty-list/empty-list';
const Markets = () => {
const { data, loading } = useExplorerMarketsQuery();
const { data } = useExplorerMarketsQuery();
useScrollToLocation();
useDocumentTitle(['Markets']);
@@ -20,23 +19,16 @@ const Markets = () => {
<section key="markets">
<RouteTitle data-testid="markets-heading">{t('Markets')}</RouteTitle>
{m ? (
m.map((e) => (
<React.Fragment key={e.node.id}>
<SubHeading data-testid="markets-header" id={e.node.id}>
{e.node.tradableInstrument.instrument.name}
</SubHeading>
<SyntaxHighlighter data={e.node} />
</React.Fragment>
))
) : loading ? (
<Loader />
) : (
<EmptyList
heading={t('This chain has no markets')}
label={t('0 markets')}
/>
)}
{m
? m.map((e) => (
<React.Fragment key={e.node.id}>
<SubHeading data-testid="markets-header" id={e.node.id}>
{e.node.tradableInstrument.instrument.name}
</SubHeading>
<SyntaxHighlighter data={e.node} />
</React.Fragment>
))
: null}
</section>
);
};
@@ -1,90 +0,0 @@
fragment ExplorerOracleDataSource on OracleSpec {
dataSourceSpec {
spec {
id
createdAt
updatedAt
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
filters {
key {
name
type
}
conditions {
value
operator
}
}
}
}
}
}
}
}
}
}
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection {
edges {
node {
externalData {
data {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
data {
name
value
}
matchedSpecIds
broadcastAt
}
}
}
}
}
}
query ExplorerOracleSpecs {
oracleSpecsConnection {
edges {
node {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
}
}
@@ -1,27 +0,0 @@
fragment ExplorerOracleForMarketsMarket on Market {
id
tradableInstrument {
instrument {
product {
... on Future {
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForTradingTermination {
id
}
}
}
}
}
}
query ExplorerOracleFormMarkets {
marketsConnection {
edges {
node {
...ExplorerOracleForMarketsMarket
}
}
}
}
@@ -1,136 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } };
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export const ExplorerOracleDataSourceFragmentDoc = gql`
fragment ExplorerOracleDataSource on OracleSpec {
dataSourceSpec {
spec {
id
createdAt
updatedAt
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
filters {
key {
name
type
}
conditions {
value
operator
}
}
}
}
}
}
}
}
}
}
`;
export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection {
edges {
node {
externalData {
data {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
data {
name
value
}
matchedSpecIds
broadcastAt
}
}
}
}
}
}
`;
export const ExplorerOracleSpecsDocument = gql`
query ExplorerOracleSpecs {
oracleSpecsConnection {
edges {
node {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
}
}
${ExplorerOracleDataSourceFragmentDoc}
${ExplorerOracleDataConnectionFragmentDoc}`;
/**
* __useExplorerOracleSpecsQuery__
*
* To run a query within a React component, call `useExplorerOracleSpecsQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerOracleSpecsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerOracleSpecsQuery({
* variables: {
* },
* });
*/
export function useExplorerOracleSpecsQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>(ExplorerOracleSpecsDocument, options);
}
export function useExplorerOracleSpecsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>(ExplorerOracleSpecsDocument, options);
}
export type ExplorerOracleSpecsQueryHookResult = ReturnType<typeof useExplorerOracleSpecsQuery>;
export type ExplorerOracleSpecsLazyQueryHookResult = ReturnType<typeof useExplorerOracleSpecsLazyQuery>;
export type ExplorerOracleSpecsQueryResult = Apollo.QueryResult<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>;
@@ -1,69 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } } };
export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } } } }> } | null };
export const ExplorerOracleForMarketsMarketFragmentDoc = gql`
fragment ExplorerOracleForMarketsMarket on Market {
id
tradableInstrument {
instrument {
product {
... on Future {
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForTradingTermination {
id
}
}
}
}
}
}
`;
export const ExplorerOracleFormMarketsDocument = gql`
query ExplorerOracleFormMarkets {
marketsConnection {
edges {
node {
...ExplorerOracleForMarketsMarket
}
}
}
}
${ExplorerOracleForMarketsMarketFragmentDoc}`;
/**
* __useExplorerOracleFormMarketsQuery__
*
* To run a query within a React component, call `useExplorerOracleFormMarketsQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerOracleFormMarketsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerOracleFormMarketsQuery({
* variables: {
* },
* });
*/
export function useExplorerOracleFormMarketsQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>(ExplorerOracleFormMarketsDocument, options);
}
export function useExplorerOracleFormMarketsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>(ExplorerOracleFormMarketsDocument, options);
}
export type ExplorerOracleFormMarketsQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsQuery>;
export type ExplorerOracleFormMarketsLazyQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsLazyQuery>;
export type ExplorerOracleFormMarketsQueryResult = Apollo.QueryResult<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>;
@@ -1,67 +0,0 @@
import { render } from '@testing-library/react';
import { OracleData } from './oracle-data';
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
function renderComponent(data: ExplorerOracleDataConnectionFragment) {
return <OracleData data={data} />;
}
describe('Oracle Data view', () => {
it('Renders nothing when data is null', () => {
const res = render(
renderComponent(null as unknown as ExplorerOracleDataConnectionFragment)
);
expect(res.container).toBeEmptyDOMElement();
});
it('Renders nothing when dataConnection is empty', () => {
const res = render(
renderComponent({} as ExplorerOracleDataConnectionFragment)
);
expect(res.container).toBeEmptyDOMElement();
});
it('Renders nothing when dataConnection has no edges', () => {
const res = render(
renderComponent({
dataConnection: {
edges: null,
},
} as ExplorerOracleDataConnectionFragment)
);
expect(res.container).toBeEmptyDOMElement();
});
it('Renders nothing when dataConnection edges is empty', () => {
const res = render(
renderComponent({
dataConnection: {
edges: [],
},
} as ExplorerOracleDataConnectionFragment)
);
expect(res.container).toBeEmptyDOMElement();
});
// This stops short of asserting how the data is presented
// because the current view is pretty rudimentary
it('Renders details component when there is data', () => {
const res = render(
renderComponent({
dataConnection: {
edges: [
{
node: {
externalData: {
data: {
broadcastAt: '2022-01-01',
},
},
},
},
],
},
} as ExplorerOracleDataConnectionFragment)
);
expect(res.getByText('Broadcast data')).toBeInTheDocument();
});
});
@@ -1,44 +0,0 @@
import { t } from '@vegaprotocol/react-helpers';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
interface OracleDataTypeProps {
data: ExplorerOracleDataConnectionFragment;
}
/**
* If there is data that has matched this oracle, this view will
* render the data inside a collapsed element so that it can be viewed.
* Currently the data is just rendered as a JSON view, because
* that Does The Job, rather than because it's good.
*/
export function OracleData({ data }: OracleDataTypeProps) {
if (
!data ||
!data.dataConnection ||
!data.dataConnection.edges?.length ||
data.dataConnection.edges.length > 1
) {
return null;
}
return (
<details data-testid="oracle-data">
<summary>{t('Broadcast data')}</summary>
<ul>
{data.dataConnection.edges.map((d) => {
if (!d) {
return null;
}
return (
<li key={d.node.externalData.data.broadcastAt}>
<SyntaxHighlighter data={filter(d, ['__typename'])} />
</li>
);
})}
</ul>
</details>
);
}
@@ -1,32 +0,0 @@
import { render } from '@testing-library/react';
import { OracleDetailsType } from './oracle-details-type';
import type { SourceTypeName } from './oracle-details-type';
function renderComponent(type: SourceTypeName) {
return <OracleDetailsType type={type} />;
}
function renderWrappedComponent(type: SourceTypeName) {
return (
<table>
<tbody>{renderComponent(type)}</tbody>
</table>
);
}
describe('Oracle type view', () => {
it('Renders nothing when type is null', () => {
const res = render(renderComponent(null as unknown as SourceTypeName));
expect(res.container).toBeEmptyDOMElement();
});
it('Renders Internal time for internal sources', () => {
const res = render(renderWrappedComponent('DataSourceDefinitionInternal'));
expect(res.getByText('Internal time')).toBeInTheDocument();
});
it('Renders External data otherwise', () => {
const res = render(renderWrappedComponent('DataSourceDefinitionExternal'));
expect(res.getByText('External data')).toBeInTheDocument();
});
});
@@ -1,28 +0,0 @@
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
export type SourceTypeName = SourceType['__typename'] | undefined;
interface OracleDetailsTypeProps {
type: SourceTypeName;
}
/**
* Renders a a single table row for the Oracle Details view that shows
* if the oracle is using the internal time oracle or external data
*/
export function OracleDetailsType({ type }: OracleDetailsTypeProps) {
if (!type) {
return null;
}
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Type</TableHeader>
<TableCell modifier="bordered">
{type === 'DataSourceDefinitionInternal'
? 'Internal time'
: 'External data'}
</TableCell>
</TableRow>
);
}
@@ -1,162 +0,0 @@
import { render } from '@testing-library/react';
import { getConditionsOrFilters, OracleFilter } from './oracle-filter';
import type { Filter } from './oracle-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import {
ConditionOperator,
DataSourceSpecStatus,
PropertyKeyType,
} from '@vegaprotocol/types';
const mockExternalSpec = {
sourceType: {
__typename: 'DataSourceSpecConfiguration',
filters: [
{
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'test',
},
conditions: [
{
__typename: 'Condition',
value: 'test',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
},
],
},
};
const mockTimeSpec = {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
value: '123',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
};
function renderComponent(data: ExplorerOracleDataSourceFragment) {
return <OracleFilter data={data} />;
}
describe('Oracle Filter view', () => {
it('Renders nothing when data is null', () => {
const res = render(
renderComponent(null as unknown as ExplorerOracleDataSourceFragment)
);
expect(res.container).toBeEmptyDOMElement();
});
it('Renders nothing when data is empty', () => {
const res = render(renderComponent({} as ExplorerOracleDataSourceFragment));
expect(res.container).toBeEmptyDOMElement();
});
it('Renders filters if type is DataSourceSpecConfiguration', () => {
const res = render(
renderComponent({
dataSourceSpec: {
spec: {
id: 'irrelevant-test-data',
createdAt: 'irrelevant-test-data',
status: DataSourceSpecStatus.STATUS_ACTIVE,
data: {
sourceType: mockExternalSpec,
},
},
},
} as ExplorerOracleDataSourceFragment)
);
expect(res.getByText('Filter')).toBeInTheDocument();
// Avoids asserting on how the data is presented because it is very rudimentary
});
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
const res = render(
renderComponent({
dataSourceSpec: {
spec: {
id: 'irrelevant-test-data',
createdAt: 'irrelevant-test-data',
status: DataSourceSpecStatus.STATUS_ACTIVE,
data: {
sourceType: {
__typename: 'DataSourceDefinitionInternal',
sourceType: mockTimeSpec,
},
},
},
},
} as ExplorerOracleDataSourceFragment)
);
expect(res.getByText('Filter')).toBeInTheDocument();
// Avoids asserting on how the data is presented because it is very rudimentary
});
});
describe('getConditionsOrFilter', () => {
it('Returns null if the type is undetermined (not DataSourceSpecConfiguration or DataSourceSpecConfigurationTime', () => {
expect(getConditionsOrFilters({})).toBeNull();
});
it('Returns the conditions object for time specs', () => {
const mock: Filter = {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
__typename: 'Condition',
value: '100',
operator: ConditionOperator.OPERATOR_GREATER_THAN,
},
],
};
const res = getConditionsOrFilters(mock);
// This ugly construction is due to lazy typing on getConditionsOrFilter
if (!res || res.length !== 1 || !res[0] || 'key' in res[0]) {
throw new Error(
'getConditionsOrFilter did not return conditions on a time spec'
);
}
expect(res[0].__typename).toEqual('Condition');
});
it('Returns the filters object for external specs', () => {
const mock: Filter = {
__typename: 'DataSourceSpecConfiguration',
filters: [
{
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'test',
},
conditions: [
{
__typename: 'Condition',
value: 'test',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
},
],
};
const res = getConditionsOrFilters(mock);
// This ugly construction is due to lazy typing on getConditionsOrFilter
if (!res || res.length !== 1 || !res[0] || 'value' in res[0]) {
throw new Error(
'getConditionsOrFilter did not return filters on a external spec'
);
}
expect(res[0].__typename).toEqual('Filter');
});
});
@@ -1,57 +0,0 @@
import { t } from '@vegaprotocol/react-helpers';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
interface OracleFilterProps {
data: ExplorerOracleDataSourceFragment;
}
export type Filter =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType']['sourceType'];
/**
* Given the main Filter view just uses a JSON dump view, this function
* selects the correct filter to dump in to that view. Internal oracles
* (i.e. the Time oracle) have conditions while external data sources
* have filters
*
* @param s A data source
* @returns Object an object containing conditions or filters
*/
export function getConditionsOrFilters(s: Filter) {
if (s.__typename === 'DataSourceSpecConfiguration') {
return s.filters;
} else if (s.__typename === 'DataSourceSpecConfigurationTime') {
return s.conditions;
}
return null;
}
/**
* Shows the conditions that this oracle is using to filter
* data sources.
*
* Renders nothing if there is no data (which will frequently)
* be the case) and if there is data, currently renders a simple
* JSON view.
*/
export function OracleFilter({ data }: OracleFilterProps) {
if (!data?.dataSourceSpec?.spec?.data?.sourceType) {
return null;
}
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
const f = getConditionsOrFilters(s);
if (!f) {
return null;
}
return (
<details>
<summary>{t('Filter')}</summary>
<SyntaxHighlighter data={filter(f, ['__typename'])} />
</details>
);
}
@@ -1,143 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { OracleMarkets } from './oracle-markets';
import { render } from '@testing-library/react';
import { Table } from '../../../components/table';
import { ExplorerOracleFormMarketsDocument } from '../__generated__/OraclesForMarkets';
import { MemoryRouter } from 'react-router-dom';
function renderComponent(id: string, mocks: MockedResponse[]) {
return (
<MemoryRouter>
<MockedProvider mocks={mocks}>
<Table>
<tbody>
<OracleMarkets id={id} />
</tbody>
</Table>
</MockedProvider>
</MemoryRouter>
);
}
describe('Oracle Markets component', () => {
it('Renders a row with the market ID initially', () => {
const res = render(renderComponent('123', []));
expect(res.getByText('Market')).toBeInTheDocument();
expect(res.getByText('123')).toBeInTheDocument();
});
it('Renders that this is a termination source for the right market', async () => {
const mock = {
request: {
query: ExplorerOracleFormMarketsDocument,
},
result: {
data: {
marketsConnection: {
edges: [
{
node: {
__typename: 'Market',
id: '123',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: '456',
},
dataSourceSpecForTradingTermination: {
id: '789',
},
},
},
},
},
},
{
node: {
__typename: 'Market',
id: 'abc',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: 'def',
},
dataSourceSpecForTradingTermination: {
id: 'ghi',
},
},
},
},
},
},
],
},
},
},
};
const res = render(renderComponent('789', [mock]));
expect(await res.findByText('Termination for')).toBeInTheDocument();
expect(await res.findByTestId('m-123')).toBeInTheDocument();
});
it('Renders that this is a settlement source for the right market', async () => {
const mock = {
request: {
query: ExplorerOracleFormMarketsDocument,
},
result: {
data: {
marketsConnection: {
edges: [
{
node: {
__typename: 'Market',
id: '123',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: '789',
},
dataSourceSpecForTradingTermination: {
id: '123',
},
},
},
},
},
},
{
node: {
__typename: 'Market',
id: 'abc',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: 'def',
},
dataSourceSpecForTradingTermination: {
id: 'ghi',
},
},
},
},
},
},
],
},
},
},
};
const res = render(renderComponent('789', [mock]));
expect(await res.findByText('Settlement for')).toBeInTheDocument();
expect(await res.findByTestId('m-123')).toBeInTheDocument();
});
});
@@ -1,62 +0,0 @@
import { getNodes, t } from '@vegaprotocol/react-helpers';
import { MarketLink } from '../../../components/links';
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets';
import { useExplorerOracleFormMarketsQuery } from '../__generated__/OraclesForMarkets';
interface OracleMarketsProps {
id: string;
}
/**
* Slightly misleadlingly names, OracleMarkets lists the market (almost always singular)
* to which an oracle is attached. It also checks what it triggers, by checking on the
* market whether it is attached to the dataSourceSpecForSettlementData or ..TradingTermination
*/
export function OracleMarkets({ id }: OracleMarketsProps) {
const { data } = useExplorerOracleFormMarketsQuery({
fetchPolicy: 'cache-first',
});
const markets = getNodes<ExplorerOracleForMarketsMarketFragment>(
data?.marketsConnection
);
if (markets) {
const m = markets.find((m) => {
const p = m.tradableInstrument.instrument.product;
if (
p.dataSourceSpecForSettlementData.id === id ||
p.dataSourceSpecForTradingTermination.id === id
) {
return true;
}
return false;
});
if (m && m.id) {
const type =
id ===
m.tradableInstrument.instrument.product.dataSourceSpecForSettlementData
.id
? 'Settlement for'
: 'Termination for';
return (
<TableRow modifier="bordered">
<TableHeader scope="row">{type}</TableHeader>
<TableCell modifier="bordered" data-testid={`m-${m.id}`}>
<MarketLink id={m.id} />
</TableCell>
</TableRow>
);
}
}
return (
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Market')}</TableHeader>
<TableCell modifier="bordered">
<span>{id}</span>
</TableCell>
</TableRow>
);
}
@@ -1,79 +0,0 @@
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import type { SourceType } from './oracle';
import { OracleSigners } from './oracle-signers';
function renderComponent(sourceType: SourceType) {
return (
<MemoryRouter>
<OracleSigners sourceType={sourceType} />
</MemoryRouter>
);
}
function renderComponentWrapped(sourceType: SourceType) {
return (
<table>
<tbody>{renderComponent(sourceType)}</tbody>
</table>
);
}
describe('Oracle Signers component', () => {
it('returns empty if there are no signers (null)', () => {
const res = render(renderComponent({} as SourceType));
expect(res.container).toBeEmptyDOMElement();
});
it('returns empty if there are no signers (empty)', () => {
const mock: SourceType = {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
signers: [],
},
};
const res = render(renderComponent(mock));
expect(res.container).toBeEmptyDOMElement();
});
it('Correctly identifies an Ethereum based signer', () => {
const mock: SourceType = {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'ETHAddress',
address: '0x123',
},
},
],
},
};
const res = render(renderComponentWrapped(mock));
expect(res.getByTestId('keytype')).toHaveTextContent('ETH');
});
it('Correctly identifies an Vega based signer', () => {
const mock: SourceType = {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '123',
},
},
],
},
};
const res = render(renderComponentWrapped(mock));
expect(res.getByTestId('keytype')).toHaveTextContent('Vega');
});
});
@@ -1,76 +0,0 @@
import { PartyLink } from '../../../components/links';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
export type Signer = {
__typename?: 'ETHAddress' | 'PubKey' | undefined;
address?: string | null;
key?: string | null;
};
export function getAddressTypeLabel(signer: Signer) {
return signer.__typename === 'ETHAddress' ? 'ETH' : 'Vega';
}
export function getAddress(signer: Signer) {
return signer.address ? signer.address : signer.key ? signer.key : null;
}
export function getAddressLink(signer: Signer) {
const address = getAddress(signer);
if (!address) {
return null;
}
if (signer.__typename === 'ETHAddress') {
return <EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />;
} else if (signer.__typename === 'PubKey') {
return <PartyLink id={address} />;
}
return <span>{address}</span>;
}
interface OracleDetailsSignersProps {
sourceType: SourceType;
}
/**
* Given an Oracle, this component will render either a link to Ethereum
* or the Vega party depending on which type is specified
*/
export function OracleSigners({ sourceType }: OracleDetailsSignersProps) {
if (sourceType.__typename !== 'DataSourceDefinitionExternal') {
return null;
}
const signers = sourceType.sourceType.signers;
if (!signers || signers.length === 0) {
return null;
}
return (
<>
{signers.map((s) => {
return (
<TableRow modifier="bordered" key={getAddress(s.signer)}>
<TableHeader scope="row">Signer</TableHeader>
<TableCell modifier="bordered">
<div>
<span data-testid="keytype">
{getAddressTypeLabel(s.signer)}
</span>
: {getAddressLink(s.signer)}
</div>
</TableCell>
</TableRow>
);
})}
</>
);
}
@@ -1,56 +0,0 @@
import { t } from '@vegaprotocol/react-helpers';
import {
TableRow,
TableCell,
TableWithTbody,
TableHeader,
} from '../../../components/table';
import type {
ExplorerOracleDataConnectionFragment,
ExplorerOracleDataSourceFragment,
} from '../__generated__/Oracles';
import { OracleData } from './oracle-data';
import { OracleFilter } from './oracle-filter';
import { OracleDetailsType } from './oracle-details-type';
import { OracleMarkets } from './oracle-markets';
export type SourceType =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
interface OracleDetailsProps {
id: string;
dataSource: ExplorerOracleDataSourceFragment;
dataConnection: ExplorerOracleDataConnectionFragment;
}
export const OracleDetails = ({
id,
dataSource,
dataConnection,
}: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.dataConnection.edges?.length || 0;
return (
<div>
<TableWithTbody>
<TableRow modifier="bordered">
<TableHeader scope="row">{t('ID')}</TableHeader>
<TableCell modifier="bordered">{id}</TableCell>
</TableRow>
<OracleDetailsType type={sourceType.__typename} />
{
// Disabled until https://github.com/vegaprotocol/vega/issues/7286 is released
/*<OracleSigners sourceType={sourceType} />*/
}
<OracleMarkets id={id} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Broadcasts')}</TableHeader>
<TableCell modifier="bordered">{reportsCount}</TableCell>
</TableRow>
</TableWithTbody>
<OracleFilter data={dataSource} />
<OracleData data={dataConnection} />
</div>
);
};
@@ -1,45 +0,0 @@
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../components/route-title';
import { t } from '@vegaprotocol/react-helpers';
import { useExplorerOracleSpecsQuery } from './__generated__/Oracles';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { OracleDetails } from './components/oracle';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
const Oracles = () => {
const { data, loading } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']);
useScrollToLocation();
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
{loading ? <Loader /> : null}
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
return (
<div id={id} key={id} className="mb-10 cursor-pointer">
<OracleDetails
id={id}
dataSource={o?.node}
dataConnection={o?.node}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</section>
);
};
export default Oracles;
@@ -15,15 +15,9 @@ fragment ExplorerPartyAssetsAccounts on AccountBalance {
balance
market {
id
decimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
}
@@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerPartyAssetsAccountsFragment = { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null };
export type ExplorerPartyAssetsAccountsFragment = { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null };
export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null } } | null> | null } | null } }> } | null };
export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
fragment ExplorerPartyAssetsAccounts on AccountBalance {
@@ -30,15 +30,9 @@ export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
balance
market {
id
decimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
}
@@ -51,7 +51,6 @@ export const PartyAccounts = ({ accounts }: PartyAccountsProps) => {
return (
<TableRow
key={`pa-${account.asset.id}-${account.type}`}
title={account.asset.name}
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
>
@@ -51,12 +51,12 @@ const Party = () => {
const staking = (
<section>
{p?.stakingSummary?.currentStakeAvailable ? (
<div className="mt-4 leading-3">
<p className="mt-4 leading-3">
<strong className="font-semibold">{t('Staking Balance: ')}</strong>
<GovernanceAssetBalance
price={p.stakingSummary.currentStakeAvailable}
/>
</div>
</p>
) : null}
</section>
);
@@ -3,7 +3,6 @@ import BlockPage from './blocks';
import Governance from './governance';
import Home from './home';
import Markets from './markets';
import Oracles from './oracles';
import Party from './parties';
import { Parties } from './parties/home';
import { Party as PartySingle } from './parties/id';
@@ -85,17 +84,6 @@ const marketsRoutes = flags.markets
]
: [];
const oraclesRoutes = flags.oracles
? [
{
path: Routes.ORACLES,
name: 'Oracles',
text: t('Oracles'),
element: <Oracles />,
},
]
: [];
const networkParametersRoutes = flags.networkParameters
? [
{
@@ -166,7 +154,6 @@ const routerConfig = [
...genesisRoutes,
...governanceRoutes,
...marketsRoutes,
...oraclesRoutes,
...networkParametersRoutes,
...validators,
];
@@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../../components/route-title';
import { BlocksRefetch } from '../../../components/blocks';
import { TxsInfiniteList } from '../../../components/txs';
import { TxsInfiniteList, TxsStatsInfo } from '../../../components/txs';
import { useTxsData } from '../../../hooks/use-txs-data';
import { useDocumentTitle } from '../../../hooks/use-document-title';
@@ -16,6 +16,7 @@ export const TxsList = () => {
<section className="md:p-2 lg:p-4 xl:p-6">
<RouteTitle>{t('Transactions')}</RouteTitle>
<BlocksRefetch refetch={refreshTxs} />
<TxsStatsInfo className="!my-12 py-8" />
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
@@ -15,7 +15,7 @@ export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<section className="mb-10" key={txData.hash}>
<section className="mb-10">
<TxDetailsWrapper height={txData.block} txData={txData} pubKey={pubKey} />
</section>
);
@@ -1,7 +1,8 @@
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { DATA_SOURCES } from '../../config';
import { useFetch } from '@vegaprotocol/react-helpers';
import type { TendermintValidatorsResponse } from './tendermint-validator-response';
@@ -27,9 +28,7 @@ const Validators = () => {
<SubHeading data-testid="vega-header">{t('Vega data')}</SubHeading>
<SyntaxHighlighter data-testid="vega-data" data={data} />
</>
) : (
<Loader />
)}
) : null}
{validators ? (
<>
<SubHeading data-testid="tendermint-header">
@@ -37,9 +36,7 @@ const Validators = () => {
</SubHeading>
<SyntaxHighlighter data-testid="tendermint-data" data={validators} />
</>
) : (
<Loader />
)}
) : null}
</section>
);
};

Some files were not shown because too many files have changed in this diff Show More