Compare commits
164
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
809bdc5e6c | ||
|
|
5ccef2de5e | ||
|
|
4ce2924380 | ||
|
|
f5b345636c | ||
|
|
c533c584da | ||
|
|
c1ebda9274 | ||
|
|
1b2c0e8f8b | ||
|
|
df6b9023f1 | ||
|
|
0702a97b6d | ||
|
|
c552a22af8 | ||
|
|
fbdcd04096 | ||
|
|
89d0723eb3 | ||
|
|
f163a270d1 | ||
|
|
e04314608f | ||
|
|
61c91c3542 | ||
|
|
2a9f3c7568 | ||
|
|
2640b7d77d | ||
|
|
2b229cd1e7 | ||
|
|
d098ee671e | ||
|
|
2c3c685522 | ||
|
|
b96e6ee9ce | ||
|
|
1a6266e2ec | ||
|
|
c70ec7676e | ||
|
|
5e97391d53 | ||
|
|
8eebd814ae | ||
|
|
bd754d1103 | ||
|
|
e362d06b5a | ||
|
|
45a4dd7009 | ||
|
|
ff53e5e841 | ||
|
|
cd0ed05eb8 | ||
|
|
89f41a4500 | ||
|
|
38fdc91444 | ||
|
|
f8f523eb9c | ||
|
|
5a0da4e158 | ||
|
|
4f7b589cbd | ||
|
|
e5bf54ca02 | ||
|
|
866a11fd89 | ||
|
|
b10effa3c7 | ||
|
|
1f1ca510f4 | ||
|
|
06151c0592 | ||
|
|
12a799a2fd | ||
|
|
e91a37684a | ||
|
|
b4a0e74457 | ||
|
|
11ba5e733e | ||
|
|
315c463eec | ||
|
|
62bfbd6822 | ||
|
|
290c2e3d42 | ||
|
|
c67daf885c | ||
|
|
c3944128eb | ||
|
|
c63d2a9bc6 | ||
|
|
e00998f9e3 | ||
|
|
61a5098776 | ||
|
|
9203c81084 | ||
|
|
8864696783 | ||
|
|
99d338f50a | ||
|
|
2b28ff6c18 | ||
|
|
6d3795f1f4 | ||
|
|
c0b84a88d3 | ||
|
|
5b0d799a00 | ||
|
|
9e6ecbc558 | ||
|
|
c3de7f12f0 | ||
|
|
473fdf8466 | ||
|
|
47c0ec01eb | ||
|
|
6410cb54b6 | ||
|
|
b8ff3d3050 | ||
|
|
d474a502a8 | ||
|
|
06e1b3663d | ||
|
|
38ff2c24e4 | ||
|
|
2689bbdcd2 | ||
|
|
c7b4465adf | ||
|
|
3f6913ab39 | ||
|
|
31ffa306eb | ||
|
|
13ab03c459 | ||
|
|
dd2e3a6556 | ||
|
|
4608683bde | ||
|
|
1f96ccea68 | ||
|
|
c055be1146 | ||
|
|
a316eaae9d | ||
|
|
996c4ac4d5 | ||
|
|
560e4a4040 | ||
|
|
d98ce18f9e | ||
|
|
0fd1739ce5 | ||
|
|
cf43566bde | ||
|
|
24a9712e91 | ||
|
|
03f5b5e78b | ||
|
|
9552fbc033 | ||
|
|
74a4a60195 | ||
|
|
ef35999091 | ||
|
|
cba44d7eb1 | ||
|
|
c5b72e5a64 | ||
|
|
863c288e0d | ||
|
|
a97512cc7b | ||
|
|
0884654c1e | ||
|
|
5b4440f767 | ||
|
|
cfbea6e894 | ||
|
|
774a7d697f | ||
|
|
dbb08acba3 | ||
|
|
6db201c1de | ||
|
|
54cd8324ed | ||
|
|
562e2b5cb2 | ||
|
|
ca2647d4d9 | ||
|
|
bbdc64582a | ||
|
|
dabae31bfd | ||
|
|
5b42a6ad35 | ||
|
|
870ae13b4c | ||
|
|
4c32a21f28 | ||
|
|
2ea603e59e | ||
|
|
3e485e3841 | ||
|
|
7766b3f118 | ||
|
|
8db3b01284 | ||
|
|
4fbd2e5eba | ||
|
|
53d3d5ac1c | ||
|
|
35de756120 | ||
|
|
f89cf31982 | ||
|
|
8976a40dc8 | ||
|
|
7669aeb218 | ||
|
|
6e996221b2 | ||
|
|
117c0c1306 | ||
|
|
e56d7bc2d5 | ||
|
|
85ec66a9e2 | ||
|
|
ba14ead8fc | ||
|
|
1211d27011 | ||
|
|
904a1b7805 | ||
|
|
66a8c659b4 | ||
|
|
f1a437500e | ||
|
|
29ced0b460 | ||
|
|
e45265ac71 | ||
|
|
cc3c339cc3 | ||
|
|
6f95989569 | ||
|
|
14f0c5a5fb | ||
|
|
0167a42990 | ||
|
|
1d65c4791c | ||
|
|
8a1fe6e5b1 | ||
|
|
437f7ef807 | ||
|
|
2b3683d4a4 | ||
|
|
6c84153cdb | ||
|
|
ccce5a2848 | ||
|
|
5673620fbd | ||
|
|
41d6940b89 | ||
|
|
84150bec8c | ||
|
|
5a311d14c9 | ||
|
|
0f152a4b30 | ||
|
|
45d8b537a3 | ||
|
|
dc41be5715 | ||
|
|
a85cf66419 | ||
|
|
6e82e917ac | ||
|
|
8dfdccd4aa | ||
|
|
82833cc06a | ||
|
|
aac3799b14 | ||
|
|
0d1c9ff0f1 | ||
|
|
f8eec8a056 | ||
|
|
ee6f26d473 | ||
|
|
de62d2d972 | ||
|
|
955e233c46 | ||
|
|
0bafa4c7d5 | ||
|
|
045529dc38 | ||
|
|
5ca3e4d90f | ||
|
|
9048d41f72 | ||
|
|
d731c4797c | ||
|
|
4a931ca134 | ||
|
|
3398b15d8a | ||
|
|
1e98ecbd21 | ||
|
|
a9c7c2bb46 | ||
|
|
ba2c71de67 |
@@ -1,6 +1,3 @@
|
||||
inputs:
|
||||
passphrase:
|
||||
description: 'Wallet password'
|
||||
outputs:
|
||||
token:
|
||||
description: 'api-token of wallet'
|
||||
|
||||
@@ -12,8 +12,6 @@ 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, liquidity-provision-dashboard-e2e, stats-e2e, token-e2e, trading-e2e]'
|
||||
project: '[console-lite-e2e, explorer-e2e, token-e2e, trading-e2e]'
|
||||
tags: --env.grepTags '[ @smoke, @regression, @slow ]'
|
||||
night-run: true
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
path: |
|
||||
frontend-monorepo/node_modules
|
||||
/home/runner/.cache/Cypress
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
|
||||
|
||||
# Install frontend dependencies
|
||||
- name: Install root dependencies
|
||||
|
||||
@@ -36,23 +36,22 @@ jobs:
|
||||
path: |
|
||||
frontend-monorepo/node_modules
|
||||
/home/runner/.cache/Cypress
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
|
||||
|
||||
# Install frontend dependencies
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: frontend-monorepo
|
||||
|
||||
######
|
||||
## Setup a Vega wallet for our user
|
||||
######
|
||||
|
||||
- name: Set up Vegawallet
|
||||
- name: Run Vegacapsule network
|
||||
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
|
||||
|
||||
- name: Set up Vegawallet for capsule
|
||||
id: setup-vega
|
||||
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
|
||||
with:
|
||||
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
|
||||
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
|
||||
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
|
||||
|
||||
# To make sure that all Cypress binaries are installed properly
|
||||
- name: Install cypress bins
|
||||
@@ -63,7 +62,6 @@ 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 }}
|
||||
|
||||
@@ -44,29 +44,23 @@ jobs:
|
||||
path: |
|
||||
frontend-monorepo/node_modules
|
||||
/home/runner/.cache/Cypress
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
|
||||
|
||||
# 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: Set up Vegawallet for docker
|
||||
- name: Run Vegacapsule network
|
||||
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
|
||||
|
||||
- name: Set up Vegawallet for capsule
|
||||
id: setup-vega
|
||||
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
|
||||
with:
|
||||
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
|
||||
|
||||
######
|
||||
## Run some tests
|
||||
@@ -81,7 +75,6 @@ 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 }}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
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 }}
|
||||
@@ -1,47 +0,0 @@
|
||||
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 }}
|
||||
@@ -40,29 +40,23 @@ jobs:
|
||||
path: |
|
||||
frontend-monorepo/node_modules
|
||||
/home/runner/.cache/Cypress
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
|
||||
|
||||
# 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: Set up Vegawallet for docker
|
||||
- name: Run Vegacapsule network
|
||||
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
|
||||
|
||||
- name: Set up Vegawallet for capsule
|
||||
id: setup-vega
|
||||
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
|
||||
with:
|
||||
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
|
||||
|
||||
######
|
||||
## Run some tests
|
||||
@@ -77,7 +71,6 @@ 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 }}
|
||||
@@ -97,5 +90,5 @@ jobs:
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: logs
|
||||
name: logs-token
|
||||
path: /home/runner/.vegacapsule/testnet/logs
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
path: |
|
||||
frontend-monorepo/node_modules
|
||||
/home/runner/.cache/Cypress
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock') }}
|
||||
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
|
||||
|
||||
# Install frontend dependencies
|
||||
- name: Install root dependencies
|
||||
@@ -48,12 +48,12 @@ jobs:
|
||||
## Setup a Vega wallet for our user
|
||||
######
|
||||
|
||||
- name: Set up Vegawallet
|
||||
- name: Run Vegacapsule network
|
||||
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
|
||||
|
||||
- name: Set up Vegawallet for capsule
|
||||
id: setup-vega
|
||||
uses: ./frontend-monorepo/.github/actions/setup-vegawallet
|
||||
with:
|
||||
recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }}
|
||||
passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
|
||||
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
|
||||
|
||||
# To make sure that all Cypress binaries are installed properly
|
||||
- name: Install cypress bins
|
||||
@@ -64,7 +64,23 @@ 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
|
||||
|
||||
@@ -32,18 +32,6 @@ 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
|
||||
|
||||
@@ -22,10 +22,6 @@ 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.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# 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_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=STAGNET3
|
||||
|
||||
CYPRESS_VEGA_ENV=CUSTOM
|
||||
CYPRESS_VEGA_URL=http://localhost:3028/query
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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=
|
||||
+9
-5
@@ -1,7 +1,11 @@
|
||||
# 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_EXPLORER_URL=https://staging2.explorer.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
|
||||
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
|
||||
CYPRESS_VEGA_ENV=STAGNET3
|
||||
CYPRESS_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
@@ -1,7 +1,6 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
module.exports = defineConfig({
|
||||
projectId: 'et4snf',
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
@@ -23,21 +22,6 @@ 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,6 +67,7 @@ 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,7 +30,6 @@ 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')
|
||||
@@ -51,8 +50,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,6 +14,7 @@ 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');
|
||||
@@ -34,11 +35,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');
|
||||
@@ -57,27 +58,16 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
describe('Assets view', () => {
|
||||
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());
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.mockConsole();
|
||||
cy.setVegaWallet();
|
||||
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(
|
||||
'.ag-center-cols-container [row-id="ACCOUNT_TYPE_GENERAL-asset-id-null"]'
|
||||
)
|
||||
.find('button')
|
||||
cy.get('[role="gridcell"][col-id="account-asset"] button')
|
||||
.contains('tEURO')
|
||||
.click();
|
||||
cy.getByTestId('dialog-title').should(
|
||||
'have.text',
|
||||
@@ -99,8 +89,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', () => {
|
||||
@@ -116,8 +106,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', () => {
|
||||
@@ -134,8 +124,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', () => {
|
||||
@@ -161,8 +151,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,6 +13,7 @@ import {
|
||||
marketsCandlesQuery,
|
||||
marketsDataQuery,
|
||||
marketsQuery,
|
||||
networkParamsQuery,
|
||||
ordersQuery,
|
||||
positionsQuery,
|
||||
statisticsQuery,
|
||||
@@ -47,6 +48,7 @@ const mockPage = (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
aliasGQLQuery(req, 'SimpleMarkets', marketsQuery());
|
||||
aliasGQLQuery(req, 'Orders', ordersQuery());
|
||||
aliasGQLQuery(req, 'Fills', fillsQuery());
|
||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||
};
|
||||
|
||||
export const addMockConsole = () => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { DealTicketManager } from '@vegaprotocol/deal-ticket';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
@@ -50,10 +49,10 @@ export const DealTicketContainer = () => {
|
||||
);
|
||||
|
||||
const container = (
|
||||
<DealTicketManager market={data}>
|
||||
<>
|
||||
{loading ? loader : balance}
|
||||
<DealTicketSteps market={data} />
|
||||
</DealTicketManager>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
useOrderMargin,
|
||||
useMaximumPositionSize,
|
||||
useCalculateSlippage,
|
||||
validateAmount,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import { InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
validateAmount,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useOrderSubmit,
|
||||
@@ -83,10 +83,11 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
order,
|
||||
});
|
||||
|
||||
const estCloseOut = useOrderCloseOut({
|
||||
const closeOut = 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,13 +1,12 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { FieldErrors } from 'react-hook-form';
|
||||
import { useMemo } from 'react';
|
||||
import { t, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { DataGrid, 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,
|
||||
@@ -217,7 +216,7 @@ export const useOrderValidation = ({
|
||||
{t('This market is in auction until it reaches')}{' '}
|
||||
<Tooltip
|
||||
description={
|
||||
<MarketDataGrid grid={compileGridData(market)} />
|
||||
<DataGrid grid={compileGridData(market, market.data)} />
|
||||
}
|
||||
>
|
||||
<span>{t('sufficient liquidity')}</span>
|
||||
@@ -241,7 +240,7 @@ export const useOrderValidation = ({
|
||||
{t('This market is in auction due to')}{' '}
|
||||
<Tooltip
|
||||
description={
|
||||
<MarketDataGrid grid={compileGridData(market)} />
|
||||
<DataGrid grid={compileGridData(market, market.data)} />
|
||||
}
|
||||
>
|
||||
<span>{t('high price volatility')}</span>
|
||||
@@ -282,7 +281,7 @@ export const useOrderValidation = ({
|
||||
{t('This market is in auction until it reaches')}{' '}
|
||||
<Tooltip
|
||||
description={
|
||||
<MarketDataGrid grid={compileGridData(market)} />
|
||||
<DataGrid grid={compileGridData(market, market.data)} />
|
||||
}
|
||||
>
|
||||
<span>{t('sufficient liquidity')}</span>
|
||||
@@ -308,7 +307,7 @@ export const useOrderValidation = ({
|
||||
{t('This market is in auction due to')}{' '}
|
||||
<Tooltip
|
||||
description={
|
||||
<MarketDataGrid grid={compileGridData(market)} />
|
||||
<DataGrid grid={compileGridData(market, market.data)} />
|
||||
}
|
||||
>
|
||||
<span>{t('high price volatility')}</span>
|
||||
|
||||
@@ -33,19 +33,17 @@ const AccountsManager = () => {
|
||||
update,
|
||||
variables,
|
||||
});
|
||||
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 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 { 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 } = usePositionsData(partyId, gridRef);
|
||||
const { data, error, loading, getRows } = usePositionsData(partyId, gridRef);
|
||||
const { columnDefs, defaultColDef } = useColumnDefinitions();
|
||||
return (
|
||||
<AsyncRenderer
|
||||
@@ -29,7 +29,8 @@ const Positions = () => {
|
||||
columnDefs={columnDefs}
|
||||
defaultColDef={defaultColDef}
|
||||
getRowId={getRowId}
|
||||
rowData={data || undefined}
|
||||
rowModelType="infinite"
|
||||
datasource={{ getRows }}
|
||||
components={{ PriceFlashCell }}
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,43 +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 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>;
|
||||
@@ -18,3 +18,4 @@ NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
CYPRESS_VEGA_URL=http://localhost:3028/query
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Vega Explorer E2E tests
|
||||
|
||||
To run the UI automation tests with Vega Capsule, run:
|
||||
|
||||
```bash
|
||||
yarn nx run explorer-e2e:e2e
|
||||
```
|
||||
|
||||
To open Cypress and run in interactive mode, run:
|
||||
|
||||
```bash
|
||||
yarn nx run explorer-e2e:e2e --watch
|
||||
```
|
||||
|
||||
## Vega Capsule Setup
|
||||
|
||||
The e2e tests run against a locally running instance of the Vega network, managed and controlled by [Vega Capsule](https://github.com/vegaprotocol/vegacapsule). Vega Capsule will:
|
||||
|
||||
- Bootstrap and start up a Vega network
|
||||
- Start up [Ganache](https://trufflesuite.com/ganache/) for a local Ethereum network
|
||||
- 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
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- You may need to run `vegacapsule nodes unsafe-reset-all` to get a clean network state
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
@@ -29,7 +29,7 @@
|
||||
"market.liquidity.maximumLiquidityFeeFactorLevel",
|
||||
"market.liquidity.minimum.probabilityOfTrading.lpOrders",
|
||||
"market.liquidity.probabilityOfTrading.tau.scaling",
|
||||
"market.liquidity.stakeToCcySiskas",
|
||||
"market.liquidity.stakeToCcyVolume",
|
||||
"market.liquidity.targetstake.triggering.ratio",
|
||||
"market.liquidityProvision.minLpStakeQuantumMultiple",
|
||||
"market.liquidityProvision.shapes.maxSize",
|
||||
|
||||
@@ -52,7 +52,8 @@ context('Blocks page', { tags: '@regression' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Previous button disabled on first block', function () {
|
||||
// Skipping - see https://github.com/vegaprotocol/frontend-monorepo/issues/2494
|
||||
it.skip('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');
|
||||
@@ -115,7 +116,7 @@ context('Blocks page', { tags: '@regression' }, function () {
|
||||
});
|
||||
|
||||
function waitForBlocksResponse() {
|
||||
cy.contains('Loading...').should('not.exist', { timeout: 18000 });
|
||||
cy.get('[data-testid="loader"]').should('not.exist', { timeout: 18000 });
|
||||
}
|
||||
|
||||
function validateBlocksDisplayed() {
|
||||
|
||||
@@ -20,19 +20,18 @@ context('Home Page', function () {
|
||||
1: 'Height',
|
||||
2: 'Uptime',
|
||||
3: 'Total nodes',
|
||||
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',
|
||||
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',
|
||||
};
|
||||
|
||||
cy.get('[data-testid="stats-title"]')
|
||||
@@ -40,7 +39,7 @@ context('Home Page', function () {
|
||||
cy.wrap($list).should('have.text', statTitles[index]);
|
||||
})
|
||||
.then(($list) => {
|
||||
cy.wrap($list).should('have.length', 17);
|
||||
cy.wrap($list).should('have.length', 16);
|
||||
});
|
||||
|
||||
cy.get(statsValue).eq(0).should('have.text', 'CONNECTED');
|
||||
@@ -50,30 +49,29 @@ 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(5)
|
||||
.eq(4)
|
||||
.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('have.text', '0');
|
||||
cy.get(statsValue).eq(9).should('not.be.empty');
|
||||
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(13)
|
||||
.eq(12)
|
||||
.invoke('text')
|
||||
.should('match', /v\d+\.\d+\.\d+/i);
|
||||
}
|
||||
cy.get(statsValue)
|
||||
.eq(14)
|
||||
.eq(13)
|
||||
.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
@@ -18,4 +18,4 @@ NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=0
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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
|
||||
@@ -70,7 +70,7 @@
|
||||
"executor": "@nrwl/workspace:run-commands",
|
||||
"options": {
|
||||
"commands": [
|
||||
"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"
|
||||
"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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
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,
|
||||
useEnvironment,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { EnvironmentProvider, NetworkLoader } 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();
|
||||
@@ -26,18 +18,9 @@ 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: {
|
||||
Node: {
|
||||
statistics: {
|
||||
keyFields: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,7 +18,6 @@ const AssetBalance = ({
|
||||
showAssetLink = true,
|
||||
}: AssetBalanceProps) => {
|
||||
const { data } = useExplorerAssetQuery({
|
||||
fetchPolicy: 'cache-first',
|
||||
variables: { id: assetId },
|
||||
});
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ describe('Blocks infinite list', () => {
|
||||
error={undefined}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('No items')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('emptylist')).toBeInTheDocument();
|
||||
expect(screen.getByText('This chain has 0 blocks')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('error is displayed at item level', () => {
|
||||
|
||||
@@ -4,6 +4,8 @@ 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;
|
||||
@@ -31,7 +33,16 @@ export const BlocksInfiniteList = ({
|
||||
className,
|
||||
}: BlocksInfiniteListProps) => {
|
||||
if (!blocks) {
|
||||
return <div>No items</div>;
|
||||
if (!areBlocksLoading) {
|
||||
return (
|
||||
<EmptyList
|
||||
heading={t('This chain has 0 blocks')}
|
||||
label={t('Check back soon')}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Loader />;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are more items to be loaded then add an extra row to hold a loading indicator.
|
||||
@@ -50,7 +61,7 @@ export const BlocksInfiniteList = ({
|
||||
if (error) {
|
||||
content = t(`${error}`);
|
||||
} else if (!isItemLoaded(index)) {
|
||||
content = t('Loading...');
|
||||
content = <Loader />;
|
||||
} else {
|
||||
content = <BlockData block={blocks[index]} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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,7 +16,6 @@ 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">
|
||||
⚠️
|
||||
⚠️ {t('Invalid market')}
|
||||
</span>
|
||||
{id}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
@@ -22,8 +22,14 @@ 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, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: 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, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: 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, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: 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, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
|
||||
|
||||
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
fragment ExplorerDeterministicOrderFields on Order {
|
||||
@@ -38,8 +38,14 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
product {
|
||||
... on Future {
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -60,10 +60,15 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '789',
|
||||
state: 'STATUS_ACTIVE',
|
||||
decimalPlaces: '5',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'test',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
|
||||
export interface DeterministicOrderDetailsProps {
|
||||
id: string;
|
||||
@@ -19,7 +20,8 @@ export const statusText: Record<Schema.OrderStatus, string> = {
|
||||
STATUS_STOPPED: t('Stopped'),
|
||||
};
|
||||
|
||||
export const sideText: Record<Schema.Side, string> = {
|
||||
export const sideText: Record<components['schemas']['vegaSide'], string> = {
|
||||
SIDE_UNSPECIFIED: t('Unspecified'),
|
||||
SIDE_BUY: t('Buy'),
|
||||
SIDE_SELL: t('Sell'),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
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',
|
||||
state: 'STATE_ACTIVE',
|
||||
decimalPlaces: 2,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'TEST',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
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>
|
||||
<span>{order.size}</span> <i>@</i>
|
||||
<PriceInMarket marketId={order.market.id} price={order.price} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderSummary;
|
||||
@@ -0,0 +1,119 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
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>
|
||||
<span>{order.size}</span> <i className="text-xs">@</i>
|
||||
<PriceInMarket
|
||||
marketId={order.marketId}
|
||||
price={order.price}
|
||||
></PriceInMarket>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderTxSummary;
|
||||
@@ -15,6 +15,7 @@ export type PriceInMarketProps = {
|
||||
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
||||
const { data } = useExplorerMarketQuery({
|
||||
variables: { id: marketId },
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
let label = price;
|
||||
@@ -37,9 +38,9 @@ const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="inline-block">
|
||||
<label>
|
||||
<span>{label}</span> <span>{suffix}</span>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
+3
-1
@@ -9,6 +9,8 @@ describe('Lib: getBlockTime', () => {
|
||||
it('Returns a known date string', () => {
|
||||
const mockBlockTime = '1669223762';
|
||||
const usRes = getBlockTime(mockBlockTime, 'en-US');
|
||||
expect(usRes).toEqual('11/23/2022, 5:16:02 PM');
|
||||
expect(usRes).toContain('11/23/2022');
|
||||
expect(usRes).toContain('5:16:02');
|
||||
expect(usRes).toContain('PM');
|
||||
});
|
||||
});
|
||||
|
||||
+7
-3
@@ -2,7 +2,7 @@
|
||||
export const ErrorCodes = new Map([
|
||||
[51, 'Transaction failed validation'],
|
||||
[60, 'Transaction could not be decoded'],
|
||||
[70, 'Internal error'],
|
||||
[70, 'Error'],
|
||||
[80, 'Unknown command'],
|
||||
[89, 'Rejected as spam'],
|
||||
[0, 'Success'],
|
||||
@@ -31,6 +31,10 @@ 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
|
||||
@@ -41,8 +45,8 @@ export const ChainResponseCode = ({
|
||||
{icon}
|
||||
</span>
|
||||
{hideLabel ? null : <span>{label}</span>}
|
||||
{!hideLabel && !!error ? (
|
||||
<span className="ml-1">— {error}</span>
|
||||
{!hideLabel && !!displayError ? (
|
||||
<span className="ml-1 whitespace-pre">— {displayError}</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
query ExplorerSettlementAssetForMarket($id: ID!) {
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
... on Future {
|
||||
settlementAsset {
|
||||
decimals
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerSettlementAssetForMarketQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerSettlementAssetForMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
|
||||
|
||||
|
||||
export const ExplorerSettlementAssetForMarketDocument = gql`
|
||||
query ExplorerSettlementAssetForMarket($id: ID!) {
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
... on Future {
|
||||
settlementAsset {
|
||||
decimals
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerSettlementAssetForMarketQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerSettlementAssetForMarketQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerSettlementAssetForMarketQuery` 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 } = useExplorerSettlementAssetForMarketQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerSettlementAssetForMarketQuery(baseOptions: Apollo.QueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
|
||||
}
|
||||
export function useExplorerSettlementAssetForMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
|
||||
}
|
||||
export type ExplorerSettlementAssetForMarketQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketQuery>;
|
||||
export type ExplorerSettlementAssetForMarketLazyQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketLazyQuery>;
|
||||
export type ExplorerSettlementAssetForMarketQueryResult = Apollo.QueryResult<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>;
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import type { LiquidityOrder } from '@vegaprotocol/types';
|
||||
import { PeggedReference } from '@vegaprotocol/types';
|
||||
import { LiquidityProvisionDetailsRow } from './liquidity-provision-details-row';
|
||||
import type { VegaSide } from './liquidity-provision-details-row';
|
||||
|
||||
describe('LiquidityProvisionDetails component', () => {
|
||||
function renderComponent(
|
||||
order: LiquidityOrder,
|
||||
side: VegaSide,
|
||||
normaliseProportionsTo: number,
|
||||
marketId: string
|
||||
) {
|
||||
return render(
|
||||
<MockedProvider>
|
||||
<table>
|
||||
<tbody data-testid="container">
|
||||
<LiquidityProvisionDetailsRow
|
||||
order={order}
|
||||
marketId={marketId}
|
||||
normaliseProportionsTo={normaliseProportionsTo}
|
||||
side={side}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
it('renders null for an order with no proportion', () => {
|
||||
const mockOrder = {
|
||||
offset: '1',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
};
|
||||
|
||||
const res = renderComponent(
|
||||
mockOrder as LiquidityOrder,
|
||||
Side.SIDE_BUY,
|
||||
100,
|
||||
'123'
|
||||
);
|
||||
expect(res.getByTestId('container')).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('renders null for a null order', () => {
|
||||
const res = renderComponent(
|
||||
null as unknown as LiquidityOrder,
|
||||
Side.SIDE_BUY,
|
||||
100,
|
||||
'123'
|
||||
);
|
||||
expect(res.getByTestId('container')).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('renders a row when the order is as expected', () => {
|
||||
const mockOrder = {
|
||||
offset: '1',
|
||||
proportion: 20,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
};
|
||||
|
||||
const res = renderComponent(
|
||||
mockOrder as LiquidityOrder,
|
||||
Side.SIDE_BUY,
|
||||
100,
|
||||
'123'
|
||||
);
|
||||
// Row test ids and keys are based on the side, reference and proportion
|
||||
expect(res.getByTestId('SIDE_BUY-20-1')).toBeInTheDocument();
|
||||
expect(res.getByText('+1')).toBeInTheDocument();
|
||||
expect(res.getByText('Mid')).toBeInTheDocument();
|
||||
expect(res.getByText('20%')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('normalises offsets when normaliseToProportion is not 100', () => {
|
||||
const mockOrder = {
|
||||
offset: '1',
|
||||
proportion: 20,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||
};
|
||||
|
||||
const res = renderComponent(
|
||||
mockOrder as LiquidityOrder,
|
||||
Side.SIDE_SELL,
|
||||
50,
|
||||
'123'
|
||||
);
|
||||
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
|
||||
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
|
||||
expect(res.getByText('-1')).toBeInTheDocument();
|
||||
expect(res.getByText('Best Bid')).toBeInTheDocument();
|
||||
expect(res.getByText('40% (normalised from: 20%)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles a missing offset gracefully (should not happen)', () => {
|
||||
const mockOrder = {
|
||||
proportion: 20,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||
};
|
||||
|
||||
const res = renderComponent(
|
||||
mockOrder as LiquidityOrder,
|
||||
Side.SIDE_SELL,
|
||||
50,
|
||||
'123'
|
||||
);
|
||||
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
|
||||
expect(res.getByTestId('SIDE_SELL-40-')).toBeInTheDocument();
|
||||
expect(res.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles a missing reference gracefully (should not happen)', () => {
|
||||
const mockOrder = {
|
||||
offset: '1',
|
||||
proportion: 20,
|
||||
};
|
||||
|
||||
const res = renderComponent(
|
||||
mockOrder as LiquidityOrder,
|
||||
Side.SIDE_SELL,
|
||||
50,
|
||||
'123'
|
||||
);
|
||||
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
|
||||
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
|
||||
expect(res.getByText('40% (normalised from: 20%)')).toBeInTheDocument();
|
||||
expect(res.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../../types/explorer';
|
||||
import { TableRow } from '../../../../table';
|
||||
import { LiquidityProvisionOffset } from './liquidity-provision-offset';
|
||||
|
||||
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
|
||||
export type VegaSide = components['schemas']['vegaSide'];
|
||||
|
||||
export type LiquidityProvisionOrder =
|
||||
components['schemas']['vegaLiquidityOrder'];
|
||||
|
||||
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
|
||||
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
|
||||
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
|
||||
PEGGED_REFERENCE_MID: t('Mid'),
|
||||
PEGGED_REFERENCE_UNSPECIFIED: '-',
|
||||
};
|
||||
|
||||
export type LiquidityProvisionDetailsRowProps = {
|
||||
order?: LiquidityProvisionOrder;
|
||||
marketId?: string;
|
||||
side: VegaSide;
|
||||
// If this is
|
||||
normaliseProportionsTo: number;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Note: offset is formatted by settlement asset on the market, assuming that is available
|
||||
* Note: Due to the mix of references (MID vs BEST_X), it's not possible to correctly order
|
||||
* the orders by their actual distance from a midpoint. This would require us knowing
|
||||
* the best bid (now or at placement) and the mid. Getting the data for *now* would be
|
||||
* misleading for LP submissions in the past. There is no API for getting <mid />
|
||||
* at the time of a transaction.
|
||||
*/
|
||||
export function LiquidityProvisionDetailsRow({
|
||||
normaliseProportionsTo,
|
||||
order,
|
||||
side,
|
||||
marketId,
|
||||
}: LiquidityProvisionDetailsRowProps) {
|
||||
if (!order || !order.proportion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const proportion =
|
||||
normaliseProportionsTo === 100
|
||||
? order.proportion
|
||||
: Math.round((order.proportion / normaliseProportionsTo) * 100);
|
||||
|
||||
const key = `${side}-${proportion}-${order.offset ? order.offset : ''}`;
|
||||
|
||||
return (
|
||||
<TableRow modifier="bordered" key={key} data-testid={key}>
|
||||
<td className="text-right px-2">
|
||||
{order.offset && marketId ? (
|
||||
<LiquidityProvisionOffset
|
||||
offset={order.offset}
|
||||
side={side}
|
||||
marketId={marketId}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
{order.reference ? LiquidityReferenceLabel[order.reference] : '-'}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
{proportion === order.proportion
|
||||
? `${proportion}%`
|
||||
: `${proportion}% (normalised from: ${order.proportion}%)`}{' '}
|
||||
</td>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { LiquidityProvisionMid } from './liquidity-provision-mid';
|
||||
|
||||
describe('LiquidityProvisionMid component', () => {
|
||||
function renderComponent() {
|
||||
return render(
|
||||
<table>
|
||||
<tbody data-testid="container">
|
||||
<LiquidityProvisionMid />
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
it('renders a basic row that spans the whole table', () => {
|
||||
const res = renderComponent();
|
||||
const display = res.getByTestId('mid-display');
|
||||
expect(res.getByTestId('mid')).toBeInTheDocument();
|
||||
expect(display).toBeInTheDocument();
|
||||
expect(display).toHaveAttribute('colspan', '3');
|
||||
});
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { TableRow } from '../../../../table';
|
||||
|
||||
/**
|
||||
* In a LiquidityProvision table, this row is the midpoint. Above our LP orders on the
|
||||
* buy side, below are LP orders on the sell side. This component simply divides them.
|
||||
*
|
||||
* There is no API that can give us the mid price when the order was created, and even
|
||||
* if there was it isn't clear that would be appropriate for this centre row. So instead
|
||||
* it's a simple divider.
|
||||
*/
|
||||
export function LiquidityProvisionMid() {
|
||||
return (
|
||||
<TableRow modifier="bordered" data-testid="mid">
|
||||
<td data-testid="mid-display" colSpan={3} className="bg-white"></td>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ExplorerSettlementAssetForMarketDocument } from '../__generated__/Explorer-settlement-asset';
|
||||
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
|
||||
import type { VegaSide } from './liquidity-provision-details-row';
|
||||
import {
|
||||
getFormattedOffset,
|
||||
LiquidityProvisionOffset,
|
||||
} from './liquidity-provision-offset';
|
||||
const decimalsMock: ExplorerSettlementAssetForMarketQuery = {
|
||||
market: {
|
||||
id: '123',
|
||||
__typename: 'Market',
|
||||
decimalPlaces: 5,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
settlementAsset: {
|
||||
decimals: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('LiquidityProvisionOffset component', () => {
|
||||
function renderComponent(
|
||||
offset: string,
|
||||
side: VegaSide,
|
||||
marketId: string,
|
||||
mocks: MockedResponse[]
|
||||
) {
|
||||
return render(
|
||||
<MockedProvider mocks={mocks}>
|
||||
<LiquidityProvisionOffset
|
||||
offset={offset}
|
||||
side={side}
|
||||
marketId={marketId}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
it('renders a simple row before market data comes in', () => {
|
||||
const res = renderComponent('1', 'SIDE_BUY', '123', []);
|
||||
expect(res.getByText('+1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('replaces unformatted with formatted if the market data comes in', () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerSettlementAssetForMarketDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
result: {
|
||||
data: decimalsMock,
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = renderComponent('1', 'SIDE_BUY', '123', [mock]);
|
||||
expect(res.getByText('+1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('getFormattedOffset returns the unformatted offset if there is not enough data', () => {
|
||||
const res = getFormattedOffset('1', {});
|
||||
expect(res).toEqual('1');
|
||||
});
|
||||
|
||||
it('getFormattedOffset decimal formats a number if it comes in with market data', () => {
|
||||
const res = getFormattedOffset('1', decimalsMock);
|
||||
expect(res).toEqual('0.00001');
|
||||
});
|
||||
});
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { useExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
|
||||
import type { VegaSide } from './liquidity-provision-details-row';
|
||||
|
||||
export type LiquidityProvisionOffsetProps = {
|
||||
side: VegaSide;
|
||||
offset: string;
|
||||
marketId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Correctly formats an LP's offset according to the market settlement decimal places.
|
||||
* Initially this will appear unformatted, then when the query loads in the proper formatted
|
||||
* value will be displayed
|
||||
*
|
||||
* @see getFormattedOffset
|
||||
*/
|
||||
export function LiquidityProvisionOffset({
|
||||
side,
|
||||
offset,
|
||||
marketId,
|
||||
}: LiquidityProvisionOffsetProps) {
|
||||
const { data } = useExplorerSettlementAssetForMarketQuery({
|
||||
variables: {
|
||||
id: marketId,
|
||||
},
|
||||
});
|
||||
|
||||
// getFormattedOffset handles missing results/loading states
|
||||
const formattedOffset = getFormattedOffset(offset, data);
|
||||
|
||||
const label = side === 'SIDE_BUY' ? '+' : '-';
|
||||
const className = side === 'SIDE_BUY' ? 'text-vega-green' : 'text-vega-pink';
|
||||
return <span className={className}>{`${label}${formattedOffset}`}</span>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the work of formatting the number now we have the settlement decimal places.
|
||||
* If no market data is assigned (i.e. during loading, or if the market doesn't exist)
|
||||
* this function will return the unformatted number
|
||||
*
|
||||
* @see LiquidityProvisionOffset
|
||||
* @param data the result of a ExplorerSettlementAssetForMarketQuery
|
||||
* @param offset the unformatted offset
|
||||
* @returns string the offset of this lp order formatted with the settlement decimal places
|
||||
*/
|
||||
export function getFormattedOffset(
|
||||
offset: string,
|
||||
data?: ExplorerSettlementAssetForMarketQuery
|
||||
) {
|
||||
const decimals =
|
||||
data?.market?.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals;
|
||||
|
||||
if (!decimals) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
return addDecimalsFormatNumber(offset, decimals);
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import type { LiquidityOrder } from '@vegaprotocol/types';
|
||||
import { PeggedReference } from '@vegaprotocol/types';
|
||||
import type { LiquiditySubmission } from '../tx-liquidity-submission';
|
||||
import {
|
||||
LiquidityProvisionDetails,
|
||||
sumProportions,
|
||||
} from './liquidity-provision-details';
|
||||
|
||||
function mockProportion(proportion: number): LiquidityOrder {
|
||||
return {
|
||||
proportion,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
offset: '1',
|
||||
};
|
||||
}
|
||||
describe('sumProportions function', () => {
|
||||
it('returns 0 if the side is undefined', () => {
|
||||
const side: LiquidityOrder[] = undefined as unknown as LiquidityOrder[];
|
||||
const res = sumProportions(side);
|
||||
|
||||
expect(res).toEqual(0);
|
||||
});
|
||||
|
||||
it('returns 0 if the side is empty', () => {
|
||||
const side: LiquidityOrder[] = [];
|
||||
const res = sumProportions(side);
|
||||
|
||||
expect(res).toEqual(0);
|
||||
});
|
||||
|
||||
it('sums 1 item correctly (under 100%)', () => {
|
||||
const side: LiquidityOrder[] = [mockProportion(10)];
|
||||
const res = sumProportions(side);
|
||||
|
||||
expect(res).toEqual(10);
|
||||
});
|
||||
|
||||
it('sums 2 item correctly (exactly 100%)', () => {
|
||||
const side: LiquidityOrder[] = [mockProportion(50), mockProportion(50)];
|
||||
const res = sumProportions(side);
|
||||
|
||||
expect(res).toEqual(100);
|
||||
});
|
||||
|
||||
it('sums 3 item correctly to over 100%', () => {
|
||||
const side: LiquidityOrder[] = [
|
||||
mockProportion(20),
|
||||
mockProportion(40),
|
||||
mockProportion(50),
|
||||
];
|
||||
const res = sumProportions(side);
|
||||
|
||||
expect(res).toEqual(110);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LiquidityProvisionDetails component', () => {
|
||||
function renderComponent(provision: LiquiditySubmission) {
|
||||
return render(
|
||||
<MockedProvider>
|
||||
<LiquidityProvisionDetails provision={provision} />
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
it('handles an LP with no buys or sells by returning empty (should never happen)', () => {
|
||||
const mock: LiquiditySubmission = {};
|
||||
|
||||
const res = renderComponent(mock);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('handles an LP with no sells by just rendering buys', () => {
|
||||
const mock: LiquiditySubmission = {
|
||||
marketId: '123',
|
||||
buys: [
|
||||
{
|
||||
offset: '1',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '2',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = renderComponent(mock);
|
||||
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles an LP with no buys by just rendering sells', () => {
|
||||
const mock: LiquiditySubmission = {
|
||||
marketId: '123',
|
||||
sells: [
|
||||
{
|
||||
offset: '1',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '2',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = renderComponent(mock);
|
||||
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_SELL-50-1')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles an LP with sells by just rendering buys', () => {
|
||||
const mock: LiquiditySubmission = {
|
||||
marketId: '123',
|
||||
buys: [
|
||||
{
|
||||
offset: '1',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '2',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = renderComponent(mock);
|
||||
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles an LP with both sides', () => {
|
||||
const mock: LiquiditySubmission = {
|
||||
marketId: '123',
|
||||
buys: [
|
||||
{
|
||||
offset: '1',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '2',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
sells: [
|
||||
{
|
||||
offset: '4',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '2',
|
||||
proportion: 50,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = renderComponent(mock);
|
||||
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_SELL-50-4')).toBeInTheDocument();
|
||||
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('normalises proportions when they do not total 100%', () => {
|
||||
const mock: LiquiditySubmission = {
|
||||
marketId: '123',
|
||||
buys: [
|
||||
{
|
||||
offset: '1',
|
||||
proportion: 25,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '2',
|
||||
proportion: 30,
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = renderComponent(mock);
|
||||
expect(res.getByText('45% (normalised from: 25%)')).toBeInTheDocument();
|
||||
expect(res.getByText('55% (normalised from: 30%)')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import type { LiquiditySubmission } from '../tx-liquidity-submission';
|
||||
import { TableRow } from '../../../table';
|
||||
import { LiquidityProvisionMid } from './components/liquidity-provision-mid';
|
||||
import { LiquidityProvisionDetailsRow } from './components/liquidity-provision-details-row';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
|
||||
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
|
||||
|
||||
export type LiquidityProvisionOrder =
|
||||
components['schemas']['vegaLiquidityOrder'];
|
||||
|
||||
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
|
||||
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
|
||||
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
|
||||
PEGGED_REFERENCE_MID: t('Mid'),
|
||||
PEGGED_REFERENCE_UNSPECIFIED: '-',
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a side of a liquidity provision order, returns the total
|
||||
* It should be 100%, but it isn't always and if it isn't the proportion
|
||||
* reported for each order should be scaled
|
||||
*
|
||||
* @returns number
|
||||
*/
|
||||
export function sumProportions(
|
||||
side: LiquiditySubmission['buys'] | LiquiditySubmission['sells']
|
||||
): number {
|
||||
if (!side || side.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return side.reduce((total, o) => total + (o.proportion || 0), 0);
|
||||
}
|
||||
|
||||
export type LiquidityProvisionDetailsProps = {
|
||||
provision: LiquiditySubmission;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a table displaying all buys and sells in this LP. It is valid for there
|
||||
* to be no buys or sells.
|
||||
*
|
||||
* It might seem logical to turn proportions in to values based on the total commitment
|
||||
* but based on the current API structure it is awkward, and given that non-LP orders
|
||||
* will change the amount that is actually deployed vs assigned to a level, we decided
|
||||
* not to bother going down that route.
|
||||
*/
|
||||
export function LiquidityProvisionDetails({
|
||||
provision,
|
||||
}: LiquidityProvisionDetailsProps) {
|
||||
if (!provision.buys?.length && !provision.sells?.length) {
|
||||
return null;
|
||||
}
|
||||
// We need to do some additional calcs if these aren't both 100
|
||||
const buyTotal = sumProportions(provision.buys);
|
||||
const sellTotal = sumProportions(provision.sells);
|
||||
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
<TableRow modifier="bordered">
|
||||
<th className="px-2 pb-1">{t('Price offset')}</th>
|
||||
<th className="px-2 pb-1">{t('Price reference')}</th>
|
||||
<th className="px-2 pb-1">{t('Proportion')}</th>
|
||||
</TableRow>
|
||||
</thead>
|
||||
<tbody>
|
||||
{provision.buys?.map((b, i) => (
|
||||
<LiquidityProvisionDetailsRow
|
||||
order={b}
|
||||
marketId={provision.marketId}
|
||||
side={Side.SIDE_BUY}
|
||||
key={`SIDE_BUY-${i}`}
|
||||
normaliseProportionsTo={buyTotal}
|
||||
/>
|
||||
))}
|
||||
<LiquidityProvisionMid />
|
||||
{provision.sells?.map((s, i) => (
|
||||
<LiquidityProvisionDetailsRow
|
||||
order={s}
|
||||
marketId={provision.marketId}
|
||||
side={Side.SIDE_SELL}
|
||||
key={`SIDE_SELL-${i}`}
|
||||
normaliseProportionsTo={sellTotal}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,12 @@ 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
|
||||
@@ -34,27 +40,27 @@ export const TxDetailsShared = ({
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Type')}</TableCell>
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{txData.type}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Hash')}</TableCell>
|
||||
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
|
||||
<TableCell>
|
||||
<code>{txData.hash}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Submitter')}</TableCell>
|
||||
<TableCell {...sharedHeaderProps}>{t('Submitter')}</TableCell>
|
||||
<TableCell>{pubKey ? <PartyLink id={pubKey} /> : '-'}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Block')}</TableCell>
|
||||
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
|
||||
<TableCell>
|
||||
<BlockLink height={height} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Time')}</TableCell>
|
||||
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
|
||||
<TableCell>
|
||||
{time ? (
|
||||
<div>
|
||||
@@ -71,7 +77,7 @@ export const TxDetailsShared = ({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Response code')}</TableCell>
|
||||
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
|
||||
<TableCell>
|
||||
<ChainResponseCode code={txData.code} error={txData.error} />
|
||||
</TableCell>
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type {
|
||||
BatchCancellationInstruction,
|
||||
BatchInstruction,
|
||||
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 } from '../../table';
|
||||
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';
|
||||
|
||||
interface TxDetailsBatchProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -16,6 +23,11 @@ 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,
|
||||
@@ -26,47 +38,80 @@ export const TxDetailsBatch = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
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;
|
||||
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 countTotal = countSubmissions + countAmendments + countCancellations;
|
||||
let index = 0;
|
||||
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>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import { TxDetailsOrder } from './tx-order';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsHeartbeat } from './tx-hearbeat';
|
||||
import { TxDetailsLPAmend } from './tx-lp-amend';
|
||||
import { TxDetailsGeneric } from './tx-generic';
|
||||
import { TxDetailsBatch } from './tx-batch';
|
||||
import { TxDetailsChainEvent } from './tx-chain-event';
|
||||
@@ -17,6 +16,8 @@ import { TxDetailsOrderAmend } from './tx-order-amend';
|
||||
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
|
||||
import { TxDetailsDelegate } from './tx-delegation';
|
||||
import { TxDetailsUndelegate } from './tx-undelegation';
|
||||
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
|
||||
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -45,7 +46,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 +60,7 @@ export const TxDetailsWrapper = ({
|
||||
<code className="break-all font-mono text-xs">{raw}</code>
|
||||
</details>
|
||||
) : null}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -83,8 +84,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsOrderAmend;
|
||||
case 'Validator Heartbeat':
|
||||
return TxDetailsHeartbeat;
|
||||
case 'Amend LiquidityProvision Order':
|
||||
return TxDetailsLPAmend;
|
||||
case 'Batch Market Instructions':
|
||||
return TxDetailsBatch;
|
||||
case 'Chain Event':
|
||||
@@ -93,6 +92,10 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsNodeVote;
|
||||
case 'Withdraw':
|
||||
return TxDetailsWithdrawSubmission;
|
||||
case 'Liquidity Provision Order':
|
||||
return TxDetailsLiquiditySubmission;
|
||||
case 'Amend Liquidity Provision Order':
|
||||
return TxDetailsLiquidityAmendment;
|
||||
case 'Delegate':
|
||||
return TxDetailsDelegate;
|
||||
case 'Undelegate':
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import { MarketLink } from '../../links';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
|
||||
import PriceInMarket from '../../price-in-market/price-in-market';
|
||||
|
||||
export type LiquidityAmendment =
|
||||
components['schemas']['v1LiquidityProvisionAmendment'];
|
||||
|
||||
interface TxDetailsLiquidityAmendmentProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* An existing liquidity order is being amended. This uses
|
||||
* exactly the same details as the creation
|
||||
*/
|
||||
export const TxDetailsLiquidityAmendment = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsLiquidityAmendmentProps) => {
|
||||
if (!txData || !txData.command.liquidityProvisionAmendment) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const amendment: LiquidityAmendment =
|
||||
txData.command.liquidityProvisionAmendment;
|
||||
const marketId: string = amendment.marketId || '-';
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{amendment.commitmentAmount ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Commitment amount')}</TableCell>
|
||||
<TableCell>
|
||||
<PriceInMarket
|
||||
price={amendment.commitmentAmount}
|
||||
marketId={marketId}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
{amendment.fee ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Fee')}</TableCell>
|
||||
<TableCell>{amendment.fee}%</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
<LiquidityProvisionDetails provision={amendment} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import { MarketLink } from '../../links/';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
|
||||
import PriceInMarket from '../../price-in-market/price-in-market';
|
||||
|
||||
export type LiquiditySubmission =
|
||||
components['schemas']['v1LiquidityProvisionSubmission'];
|
||||
|
||||
interface TxDetailsLiquiditySubmissionProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Someone cancelled an order
|
||||
*/
|
||||
export const TxDetailsLiquiditySubmission = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsLiquiditySubmissionProps) => {
|
||||
if (!txData || !txData.command.liquidityProvisionSubmission) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const submission: LiquiditySubmission =
|
||||
txData.command.liquidityProvisionSubmission;
|
||||
const marketId: string = submission.marketId || '-';
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{submission.commitmentAmount ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Commitment amount')}</TableCell>
|
||||
<TableCell>
|
||||
<PriceInMarket
|
||||
price={submission.commitmentAmount}
|
||||
marketId={marketId}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
{submission.fee ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Fee')}</TableCell>
|
||||
<TableCell>{submission.fee}%</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
<LiquidityProvisionDetails provision={submission} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import { MarketLink } from '../../links/';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
|
||||
interface TxDetailsOrderProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies changes to the shape of a users Liquidity Commitment order for
|
||||
* a specific market. So far this only displays the market, which is only
|
||||
* because it's very easy to do so.
|
||||
*/
|
||||
export const TxDetailsLPAmend = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsOrderProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const marketId = txData.command.liquidityProvisionAmendment?.marketId || '';
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
@@ -14,8 +14,6 @@ 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,4 +2,3 @@ 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,8 +137,7 @@ 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 dark:bg-vega-pink-dark';
|
||||
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
|
||||
} else if (type === 'Proposal' || type === 'Governance Proposal') {
|
||||
if (command && !!command.proposalSubmission) {
|
||||
type = getLabelForProposal(command.proposalSubmission);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TxsInfiniteList } from './txs-infinite-list';
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
|
||||
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
return Array.from(Array(number)).map((_) => ({
|
||||
@@ -24,7 +25,7 @@ const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
'b4d0a070f5cc73a7d53b23d6f63f8cb52e937ed65d2469a3af4cc1e80e155fcf',
|
||||
price: '14525946',
|
||||
size: '54',
|
||||
side: 'SIDE_SELL',
|
||||
side: Side.SIDE_SELL,
|
||||
timeInForce: 'TIME_IN_FORCE_GTT',
|
||||
expiresAt: '1664966445481288736',
|
||||
type: 'TYPE_LIMIT',
|
||||
@@ -46,7 +47,10 @@ describe('Txs infinite list', () => {
|
||||
error={undefined}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('No items')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('emptylist')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('This chain has 0 transactions')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('error is displayed at item level', () => {
|
||||
|
||||
@@ -4,6 +4,8 @@ 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;
|
||||
@@ -29,7 +31,7 @@ const Item = ({ index, style, isLoading, error }: ItemProps) => {
|
||||
if (error) {
|
||||
content = t(`Cannot fetch transaction: ${error}`);
|
||||
} else if (isLoading) {
|
||||
content = t('Loading...');
|
||||
content = <Loader />;
|
||||
} else {
|
||||
const {
|
||||
hash,
|
||||
@@ -68,7 +70,16 @@ export const TxsInfiniteList = ({
|
||||
const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
|
||||
|
||||
if (!txs) {
|
||||
return <div>No items</div>;
|
||||
if (!areTxsLoading) {
|
||||
return (
|
||||
<EmptyList
|
||||
heading={t('This chain has 0 transactions')}
|
||||
label={t('Check back soon')}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Loader />;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are more items to be loaded then add an extra row to hold a loading indicator.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
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';
|
||||
@@ -8,6 +7,9 @@ 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;
|
||||
@@ -17,15 +19,11 @@ interface TxsPerBlockProps {
|
||||
const truncateLength = 5;
|
||||
|
||||
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
// TODO after https://github.com/vegaprotocol/vega/pull/6958/files is merged and deployed, use filter
|
||||
// by block height instead
|
||||
const filters = `filters[block.height]=${blockHeight}`;
|
||||
const url = getTxsDataUrl({ limit: txCount.toString(), filters });
|
||||
const {
|
||||
state: { data, loading, error },
|
||||
} = useFetch<BlockExplorerTransactions>(
|
||||
`${
|
||||
DATA_SOURCES.blockExplorerUrl
|
||||
}/transactions?before=${blockHeight.toString()}.0&limit=${txCount}`
|
||||
);
|
||||
} = useFetch<BlockExplorerTransactions>(url);
|
||||
|
||||
return (
|
||||
<RenderFetched error={error} loading={loading} className="text-body-large">
|
||||
@@ -88,10 +86,13 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div className="sr-only">
|
||||
{t(`No transactions in block ${blockHeight}`)}
|
||||
</div>
|
||||
<EmptyList
|
||||
heading={t('No transactions in this block')}
|
||||
label={t('0 transactions')}
|
||||
/>
|
||||
)}
|
||||
</RenderFetched>
|
||||
);
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
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-red-600 bg-red-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-pink-600 bg-vega-pink-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-red',
|
||||
'absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase text-vega-pink',
|
||||
indicatorComplete:
|
||||
'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',
|
||||
'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',
|
||||
textComplete:
|
||||
'absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase text-vega-green-dark',
|
||||
'absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase text-vega-green',
|
||||
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-dark' : 'border-gray-300'
|
||||
isComplete ? 'border-vega-green' : 'border-gray-300'
|
||||
}`}
|
||||
></div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -2,14 +2,15 @@ import { getNodes, t } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { SubHeading } from '../../components/sub-heading';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { Loader, 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 } = useExplorerAssetsQuery();
|
||||
const { data, loading } = useExplorerAssetsQuery();
|
||||
useDocumentTitle(['Assets']);
|
||||
|
||||
useScrollToLocation();
|
||||
@@ -17,17 +18,25 @@ const Assets = () => {
|
||||
const assets = getNodes<AssetsFieldsFragment>(data?.assetsConnection);
|
||||
|
||||
if (!assets || assets.length === 0) {
|
||||
return <section></section>;
|
||||
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>
|
||||
<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,6 +18,7 @@ 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 }>();
|
||||
@@ -112,7 +113,12 @@ const Block = () => {
|
||||
blockHeight={blockData.result.block.header.height}
|
||||
txCount={blockData.result.block.data.txs.length}
|
||||
/>
|
||||
) : null}
|
||||
) : (
|
||||
<EmptyList
|
||||
heading={t('This block is empty')}
|
||||
label={t('0 transactions')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t, useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { Loader, 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,11 +9,16 @@ const Genesis = () => {
|
||||
useDocumentTitle(['Genesis']);
|
||||
|
||||
const {
|
||||
state: { data: genesis },
|
||||
state: { data: genesis, loading },
|
||||
} = useFetch<TendermintGenesisResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/genesis`
|
||||
);
|
||||
if (!genesis?.result.genesis) return null;
|
||||
if (!genesis?.result.genesis) {
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="genesis-header">{t('Genesis')}</RouteTitle>
|
||||
|
||||
@@ -2,19 +2,35 @@ import { t } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { SubHeading } from '../../components/sub-heading';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { Loader, 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 } = useExplorerProposalsQuery({
|
||||
const { data, loading } = useExplorerProposalsQuery({
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
useDocumentTitle();
|
||||
|
||||
if (!data || !data.proposalsConnection || !data.proposalsConnection.edges) {
|
||||
return <section></section>;
|
||||
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 />;
|
||||
}
|
||||
}
|
||||
|
||||
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: 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 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 const ExplorerMarketsDocument = gql`
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { Loader, 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 } = useExplorerMarketsQuery();
|
||||
const { data, loading } = useExplorerMarketsQuery();
|
||||
|
||||
useScrollToLocation();
|
||||
useDocumentTitle(['Markets']);
|
||||
@@ -19,16 +20,23 @@ 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>
|
||||
))
|
||||
: null}
|
||||
{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')}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
fragment ExplorerOracleForMarketsMarket on Market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
... on Future {
|
||||
dataSourceSpecForSettlementData {
|
||||
id
|
||||
}
|
||||
dataSourceSpecForTradingTermination {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerOracleFormMarkets {
|
||||
marketsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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>;
|
||||
@@ -0,0 +1,69 @@
|
||||
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>;
|
||||
@@ -0,0 +1,67 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user