Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89a7984b72 | ||
|
|
74bf183fad | ||
|
|
674baa1ee3 | ||
|
|
d18a0c7e15 | ||
|
|
76a6005b77 | ||
|
|
79414614c2 | ||
|
|
fde49e5446 | ||
|
|
45283161aa | ||
|
|
375d447fa8 | ||
|
|
1dd97a2bce | ||
|
|
d4a1a9b193 | ||
|
|
376c5241a8 | ||
|
|
8e6d8517cf | ||
|
|
5f9ec222c1 | ||
|
|
8954c41c0a | ||
|
|
4414ab4f47 | ||
|
|
d1d4bacc68 | ||
|
|
f476688c7f | ||
|
|
8845222700 | ||
|
|
e64c464091 | ||
|
|
b2c2d0d7d6 | ||
|
|
1fb61d313c | ||
|
|
ccf5ff632c | ||
|
|
54d6aaf56f | ||
|
|
736b262947 | ||
|
|
6a07127185 | ||
|
|
45f0ba7c0e | ||
|
|
6f4ea68778 | ||
|
|
90dda20891 | ||
|
|
278eb01c2f | ||
|
|
c3157de146 | ||
|
|
d28db485c5 | ||
|
|
caa0076396 |
+2
-1
@@ -51,7 +51,8 @@
|
||||
"ul": ["list"],
|
||||
"ol": ["list"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"no-console": ["error", { "allow": ["warn", "error"] }]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Feature Epic
|
||||
about:
|
||||
A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
||||
title: 'Epic: '
|
||||
labels: feature-epic
|
||||
---
|
||||
|
||||
## Core Feature
|
||||
|
||||
<Name>
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Define high level requirements
|
||||
- [ ] Create basic mockups
|
||||
- [ ] Update "API Requirements" in core spec
|
||||
- [ ] Update "User-Interface Spec" in relevant front end repo
|
||||
- [ ] Create detailed user stories using normal template
|
||||
|
||||
## High Level Requirements
|
||||
|
||||
## Basic Mockups
|
||||
|
||||
## Link to API Requirements in Core spec
|
||||
|
||||
## Link to User Interface Specs
|
||||
|
||||
## Linked User Stories
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
after-release:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -30,18 +30,20 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Wait for publish to complete
|
||||
uses: lewagon/wait-on-check-action@v1.3.1
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name }}
|
||||
check-name: '(CD) publish dist / trading'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
|
||||
- name: resolve ipfs hashes for release
|
||||
run: |
|
||||
echo "Name: ${{ github.event.release.name }}"
|
||||
echo "Description: ${{ github.event.release.body }}"
|
||||
echo "Tag: ${{ github.event.release.tag_name }}"
|
||||
commit="$(git rev-list -n 1 ${{ github.event.release.tag_name }})"
|
||||
echo "Commit: $commit"
|
||||
until docker pull vegaprotocol/trading:$commit; do
|
||||
echo "Image not pushed yet, waiting 60 seconds"
|
||||
sleep 60
|
||||
done
|
||||
docker run --rm vegaprotocol/trading:$commit cat /ipfs-hash > ipfs-hash
|
||||
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash
|
||||
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
|
||||
tar -xzf kubo.tgz
|
||||
export PATH="$PATH:$PWD/kubo"
|
||||
|
||||
@@ -81,6 +81,22 @@ jobs:
|
||||
with:
|
||||
main-branch-name: develop
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
|
||||
python3 tools/ci/check-affected.py --github-ref="${{ github.ref }}" --branch-slug="$branch_slug" --event-name="${{ github.event_name }}"
|
||||
|
||||
- name: Verify script result
|
||||
run: |
|
||||
echo "Check outputs from script"
|
||||
echo "projects: ${{ env.PROJECTS }}"
|
||||
echo "projects-e2e: ${{ env.PROJECTS_E2E }}"
|
||||
echo "preview_governance: ${{ env.PREVIEW_GOVERNANCE }}"
|
||||
echo "preview_trading: ${{ env.PREVIEW_TRADING }}"
|
||||
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
|
||||
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
|
||||
|
||||
- name: Check formatting
|
||||
run: yarn nx format:check
|
||||
|
||||
@@ -96,126 +112,6 @@ jobs:
|
||||
- name: Build affected
|
||||
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
|
||||
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
|
||||
|
||||
echo ">>>> debug"
|
||||
echo "NX_BASE: ${{ env.NX_BASE }}"
|
||||
echo "NX_HEAD: ${{ env.NX_HEAD }}"
|
||||
echo "Affected: ${affected}"
|
||||
echo "Branch slug: ${branch_slug}"
|
||||
echo "Current ref: ${{ github.ref }}"
|
||||
echo ">>>> eof debug"
|
||||
|
||||
projects_array=()
|
||||
|
||||
preview_governance="not deployed"
|
||||
preview_trading="not deployed"
|
||||
preview_explorer="not deployed"
|
||||
preview_tools="not deployed"
|
||||
|
||||
# parse if affected is any of three main applications, if none - use all of them
|
||||
if echo "$affected" | grep -q governance; then
|
||||
echo "Governance is affected"
|
||||
projects_array+=("governance")
|
||||
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
|
||||
fi
|
||||
if echo "$affected" | grep -q trading; then
|
||||
echo "Trading is affected"
|
||||
projects_array+=("trading")
|
||||
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
|
||||
fi
|
||||
if echo "$affected" | grep -q explorer; then
|
||||
echo "Explorer is affected"
|
||||
projects_array+=("explorer")
|
||||
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
|
||||
fi
|
||||
if [[ ${#projects_array[@]} -eq 0 ]]; then
|
||||
projects_array=("governance" "trading" "explorer")
|
||||
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
|
||||
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
|
||||
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
|
||||
fi
|
||||
|
||||
# applications parsed before this loop are applicable for running e2e-tests
|
||||
projects_e2e_array=()
|
||||
for project in "${projects_array[@]}"; do
|
||||
projects_e2e_array+=("${project}-e2e")
|
||||
done
|
||||
# all applications below this loop are not applicable for running e2e-test
|
||||
|
||||
# check if pull request event to deploy tools
|
||||
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
|
||||
if echo "$affected" | grep -q multisig-signer; then
|
||||
echo "Tools are affected"
|
||||
echo "Deploying tools on preview"
|
||||
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
|
||||
|
||||
projects_array+=("multisig-signer")
|
||||
fi
|
||||
# those apps deploy only from develop to mainnet
|
||||
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
if echo "$affected" | grep -q multisig-signer; then
|
||||
echo "Tools are affected"
|
||||
echo "Deploying tools on s3"
|
||||
|
||||
projects_array+=("multisig-signer")
|
||||
fi
|
||||
if echo "$affected" | grep -q static; then
|
||||
echo "static is affected"
|
||||
echo "Deploying static on s3"
|
||||
|
||||
projects_array+=("static")
|
||||
fi
|
||||
if echo "$affected" | grep -q ui-toolkit; then
|
||||
echo "ui-toolkit is affected"
|
||||
echo "Deploying ui-toolkit on s3"
|
||||
|
||||
projects_array+=("ui-toolkit")
|
||||
fi
|
||||
fi
|
||||
|
||||
# if branch starts with release/ and ends with trading / governance or explorer - overwrite the array of affected projects with fixed single application
|
||||
if [[ "${{ github.ref }}" == *release* ]]; then
|
||||
echo ">> This is a relase branch"
|
||||
case "${{ github.ref }}" in
|
||||
*trading)
|
||||
echo ">> Only trading will be deployed"
|
||||
projects_array=(trading)
|
||||
projects_e2e_array=(trading)
|
||||
;;
|
||||
*governance)
|
||||
echo ">> Only governance will be deployed"
|
||||
projects_array=(governance)
|
||||
projects_e2e_array=(governance)
|
||||
;;
|
||||
*explorer)
|
||||
echo ">> Only explorer will be deployed"
|
||||
projects_array=(explorer)
|
||||
projects_e2e_array=(explorer)
|
||||
;;
|
||||
*)
|
||||
echo ">> All apps will be deployed"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "Projects: ${projects_array[@]}"
|
||||
echo "Projects E2E: ${projects_e2e_array[@]}"
|
||||
projects_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_array[@]}")
|
||||
projects_e2e_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_e2e_array[@]}")
|
||||
|
||||
echo PROJECTS_E2E=$projects_e2e_json >> $GITHUB_ENV
|
||||
echo PROJECTS=$projects_json >> $GITHUB_ENV
|
||||
|
||||
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
|
||||
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
|
||||
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
|
||||
echo PREVIEW_TOOLS=$preview_tools >> $GITHUB_ENV
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
projects-e2e: ${{ env.PROJECTS_E2E }}
|
||||
@@ -224,6 +120,15 @@ jobs:
|
||||
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
|
||||
preview_tools: ${{ env.PREVIEW_TOOLS }}
|
||||
|
||||
console-e2e:
|
||||
needs: lint-test-build
|
||||
name: '(CI) console python'
|
||||
uses: ./.github/workflows/console-test-run.yml
|
||||
secrets: inherit
|
||||
if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
cypress:
|
||||
needs: lint-test-build
|
||||
name: '(CI) cypress'
|
||||
|
||||
@@ -1,55 +1,138 @@
|
||||
name: console-test-run
|
||||
name: (CI) Console tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
github-sha:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
console-test:
|
||||
timeout-minutes: 5
|
||||
runs-on: self-hosted-runner
|
||||
run-tests:
|
||||
name: run-tests
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
# check-out frontend-monorepo
|
||||
#----------------------------------------------
|
||||
- name: Checkout console test repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ inputs.github-sha }}
|
||||
#----------------------------------------------
|
||||
# cache node modules
|
||||
#----------------------------------------------
|
||||
- name: Cache node modules
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
# comment out "restore-keys" if you need to rebuild yarn from 0
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cache-node-modules-
|
||||
#----------------------------------------------
|
||||
# setup node
|
||||
#----------------------------------------------
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
#----------------------------------------------
|
||||
# install deps if cache missing
|
||||
#----------------------------------------------
|
||||
- name: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --pure-lockfile
|
||||
#----------------------------------------------
|
||||
# build trading
|
||||
#----------------------------------------------
|
||||
- name: Build affected spec
|
||||
run: |
|
||||
yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading
|
||||
#----------------------------------------------
|
||||
# run trading server
|
||||
#----------------------------------------------
|
||||
- name: Run trading server
|
||||
run: |
|
||||
docker run -d -p 80:4200 -v $PWD/docker/nginx.conf:/etc/nginx/conf.d/default.conf -v $PWD/dist/apps/trading/exported:/usr/share/nginx/html nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
|
||||
sleep 5
|
||||
docker ps
|
||||
#----------------------------------------------
|
||||
# check if container persists between runs
|
||||
#----------------------------------------------
|
||||
- name: Check server
|
||||
run: |
|
||||
docker ps
|
||||
#----------------------------------------------
|
||||
# check-out tests repo
|
||||
#----------------------------------------------
|
||||
- name: Checkout console test repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: vegaprotocol/console-test
|
||||
path: './console-test'
|
||||
|
||||
- name: Set up Python
|
||||
#----------------------------------------------
|
||||
# set-up python
|
||||
#----------------------------------------------
|
||||
- name: Set up python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.11'
|
||||
|
||||
#----------------------------------------------
|
||||
# ----- install & configure poetry -----
|
||||
#----------------------------------------------
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
|
||||
with:
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
virtualenvs-path: console-test/.venv
|
||||
#----------------------------------------------
|
||||
# load cached venv if cache exists
|
||||
#----------------------------------------------
|
||||
- name: Load cached venv
|
||||
id: cached-poetry-dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: console-test/.venv
|
||||
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
||||
#----------------------------------------------
|
||||
# install dependencies if cache does not exist
|
||||
#----------------------------------------------
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install --no-root
|
||||
working-directory: ./console-test
|
||||
|
||||
- name: load Binaries
|
||||
run: |
|
||||
poetry run python -m vega_sim.tools.load_binaries
|
||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
||||
run: poetry install --no-interaction --no-root
|
||||
#----------------------------------------------
|
||||
# install vega binaries
|
||||
#----------------------------------------------
|
||||
- name: Install vega binaries
|
||||
working-directory: ./console-test
|
||||
|
||||
- name: pull console
|
||||
run: |
|
||||
poetry run docker pull ghcr.io/vegaprotocol/frontend/trading:${{ inputs.github-sha }}
|
||||
|
||||
- name: Update container_name in config.py
|
||||
run: |
|
||||
sed -i "s/container_name = \".*\"/container_name = \"vegaprotocol\/frontend\/trading:${{ inputs.github-sha }}\"/g" config.py
|
||||
|
||||
run: poetry run python -m vega_sim.tools.load_binaries --force
|
||||
#----------------------------------------------
|
||||
# install playwright
|
||||
#----------------------------------------------
|
||||
- name: install playwright
|
||||
run: poetry run playwright install
|
||||
working-directory: ./console-test
|
||||
|
||||
- name: run tests
|
||||
run: poetry run pytest --numprocesses auto
|
||||
#----------------------------------------------
|
||||
# run tests
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
working-directory: ./console-test
|
||||
|
||||
run: poetry run pytest --numprocesses auto
|
||||
- name: Check files
|
||||
run: |
|
||||
ls -al .
|
||||
ls -al console-test
|
||||
#----------------------------------------------
|
||||
# upload traces
|
||||
#----------------------------------------------
|
||||
- name: Upload Playwright Trace
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
|
||||
@@ -29,6 +29,12 @@ jobs:
|
||||
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_S3_RELEASE=false >> $GITHUB_ENV
|
||||
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
|
||||
|
||||
- name: Is dev image
|
||||
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
|
||||
run: |
|
||||
echo IS_DEV_IMAGE=true >> $GITHUB_ENV
|
||||
|
||||
- name: Is PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
@@ -36,7 +42,7 @@ jobs:
|
||||
echo IS_PR=true >> $GITHUB_ENV
|
||||
|
||||
- name: Is mainnet release
|
||||
if: ${{ contains(github.ref, 'release/mainnnet') && !contains(github.ref, 'mirror') }}
|
||||
if: ${{ contains(github.ref, 'release/mainnet') && !contains(github.ref, 'mirror') }}
|
||||
run: |
|
||||
echo IS_MAINNET_RELEASE=true >> $GITHUB_ENV
|
||||
|
||||
@@ -75,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Log in to the Container registry (docker hub)
|
||||
uses: docker/login-action@v2
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -97,52 +103,13 @@ jobs:
|
||||
- name: Define dist variables
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
envName=''
|
||||
domain="vega.rocks"
|
||||
bucketName=''
|
||||
python3 tools/ci/define-dist-variables.py --github-ref="${{ github.ref }}" --app="${{ matrix.app }}"
|
||||
|
||||
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
|
||||
# remove prefixing release/ and take the first string limited by - which is supposed to be name of the environment for releasing (format: release/testnet-trading)
|
||||
envName="$(echo ${{ github.ref }} | sed -e "s|refs/heads/release/||" | cut -d '-' -f 1 )"
|
||||
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
envName="stagnet1"
|
||||
if [[ "${{ matrix.app }}" = "multisig-signer" ]]; then
|
||||
envName="mainnet"
|
||||
bucketName="tools.vega.xyz"
|
||||
fi
|
||||
if [[ "${{ matrix.app }}" = "static" ]]; then
|
||||
envName="mainnet"
|
||||
bucketName="static.vega.xyz"
|
||||
fi
|
||||
if [[ "${{ matrix.app }}" = "ui-toolkit" ]]; then
|
||||
envName="mainnet"
|
||||
bucketName="ui.vega.rocks"
|
||||
fi
|
||||
elif [[ "${{ github.ref }}" =~ .*mainnet$ ]]; then
|
||||
envName="mainnet"
|
||||
fi
|
||||
|
||||
if [[ "${envName}" = "mainnet" ]]; then
|
||||
domain="vega.xyz"
|
||||
if [[ -z "${bucketName}" ]]; then
|
||||
bucketName="${{ matrix.app }}.${domain}"
|
||||
fi
|
||||
elif [[ "${envName}" = "testnet" ]]; then
|
||||
domain="fairground.wtf"
|
||||
if [[ -z "${bucketName}" ]]; then
|
||||
bucketName="${{ matrix.app }}.${domain}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${bucketName}" ]]; then
|
||||
bucketName="${{ matrix.app }}.${envName}.${domain}"
|
||||
fi
|
||||
|
||||
echo "bucket name: ${bucketName}"
|
||||
echo "env name: ${envName}"
|
||||
|
||||
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
|
||||
echo ENV_NAME=${envName} >> $GITHUB_ENV
|
||||
- name: Verify script result
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
echo "BUCKET_NAME=${{ env.BUCKET_NAME }}"
|
||||
echo "ENV_NAME=${{ env.ENV_NAME }}"
|
||||
|
||||
- name: Build local dist
|
||||
run: |
|
||||
@@ -157,8 +124,12 @@ jobs:
|
||||
elif [ "${{ matrix.app }}" = "ui-toolkit" ]; then
|
||||
NODE_ENV=production yarn nx run ui-toolkit:build-storybook
|
||||
DIST_LOCATION=dist/storybook/ui-toolkit
|
||||
elif [ "${{ matrix.app }}" = "static" ]; then
|
||||
yarn nx build static || (yarn install && yarn nx build static)
|
||||
else
|
||||
$envCmd yarn nx build ${{ matrix.app }} || (yarn install && $envCmd yarn nx build ${{ matrix.app }})
|
||||
fi
|
||||
if [[ -z "$DIST_LOCATION" ]]; then
|
||||
DIST_LOCATION=dist/apps/${{ matrix.app }}
|
||||
fi
|
||||
mv $DIST_LOCATION dist-result
|
||||
@@ -208,7 +179,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: dockerhub-push
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
||||
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
@@ -218,7 +189,7 @@ jobs:
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
|
||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }}
|
||||
|
||||
- name: Publish dist as docker image (ghcr - retry)
|
||||
uses: docker/build-push-action@v3
|
||||
|
||||
@@ -7,7 +7,11 @@ context('Validator page', { tags: '@smoke' }, function () {
|
||||
it('should be able to see validator tiles', function () {
|
||||
cy.getNodes().then((nodes) => {
|
||||
nodes.forEach((node) => {
|
||||
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
|
||||
if (node.rankingScore.performanceScore > 0) {
|
||||
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
|
||||
} else {
|
||||
cy.get(`[validator-id="${node.id}"]`).should('not.exist');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSubContent,
|
||||
Icon,
|
||||
Button,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { FilterLabel } from './tx-filter-label';
|
||||
@@ -100,15 +99,13 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
|
||||
<DropdownMenu
|
||||
modal={false}
|
||||
trigger={
|
||||
<DropdownMenuTrigger className="ml-0">
|
||||
<Button size="xs" data-testid="filter-trigger">
|
||||
<FilterLabel filters={filters} />
|
||||
</Button>
|
||||
<DropdownMenuTrigger>
|
||||
<FilterLabel filters={filters} />
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{filters.size > 0 ? null : (
|
||||
{filters.size > 1 ? null : (
|
||||
<>
|
||||
<DropdownMenuCheckboxItem
|
||||
onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const TxsListNavigation = ({
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
disabled={!hasMoreTxs || loading}
|
||||
disabled={!hasMoreTxs}
|
||||
onClick={() => {
|
||||
nextPage();
|
||||
}}
|
||||
|
||||
@@ -86,14 +86,18 @@ describe('Txs infinite list item', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
/>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxsInfiniteListItem
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
@@ -14,9 +14,9 @@ const DEFAULT_TRUNCATE_LENGTH = 7;
|
||||
|
||||
export function getIdTruncateLength(screen: Screen): number {
|
||||
if (['xxxl', 'xxl'].includes(screen)) {
|
||||
return 64;
|
||||
} else if (['xl', 'lg', 'md'].includes(screen)) {
|
||||
return 32;
|
||||
} else if (['xl', 'lg', 'md'].includes(screen)) {
|
||||
return 16;
|
||||
}
|
||||
return DEFAULT_TRUNCATE_LENGTH;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,17 @@ import type { BlockExplorerTransactionResult } from '../../routes/types/block-ex
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
const generateHash = (): string =>
|
||||
Array.from(
|
||||
{ length: 64 },
|
||||
() => '0123456789ABCDEF'[Math.floor(Math.random() * 16)]
|
||||
).join('');
|
||||
|
||||
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
return Array.from(Array(number)).map((_) => ({
|
||||
block: '87901',
|
||||
index: 2,
|
||||
hash: '0F8B98DA0923A50786B852D9CA11E051CACC4C733E1DB93D535C7D81DBD10F6F',
|
||||
hash: generateHash(),
|
||||
submitter:
|
||||
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
||||
type: 'Submit Order',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Table, TableRow } from '../table';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
|
||||
import { getTxsDataUrl } from '../../hooks/use-txs-data';
|
||||
import { getTxsDataUrl } from '../../hooks/get-txs-data-url';
|
||||
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import EmptyList from '../empty-list/empty-list';
|
||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
@@ -14,7 +14,7 @@ interface TxsPerBlockProps {
|
||||
|
||||
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
const filters = `filters[block.height]=${blockHeight}`;
|
||||
const url = getTxsDataUrl({ limit: txCount.toString(), filters });
|
||||
const url = getTxsDataUrl({ filters, count: txCount });
|
||||
const {
|
||||
state: { data, loading, error },
|
||||
} = useFetch<BlockExplorerTransactions>(url);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { DATA_SOURCES } from '../config';
|
||||
|
||||
type IGetTxsDataFirstPage = {
|
||||
baseUrl?: string;
|
||||
count?: number;
|
||||
party?: string;
|
||||
filters?: string;
|
||||
};
|
||||
|
||||
interface IGetTxsDataPrevious extends IGetTxsDataFirstPage {
|
||||
before: string;
|
||||
}
|
||||
|
||||
interface IGetTxsDataNext extends IGetTxsDataFirstPage {
|
||||
after: string;
|
||||
}
|
||||
|
||||
type IGetTxsDataUrl =
|
||||
| IGetTxsDataPrevious
|
||||
| IGetTxsDataNext
|
||||
| IGetTxsDataFirstPage;
|
||||
|
||||
export const BE_TXS_PER_REQUEST = 25;
|
||||
|
||||
/**
|
||||
* Properly encodes the filters and parameters for a request to the block explorer
|
||||
* API for transactions. As the API uses a slightly less common format for encoding
|
||||
* filters, some of it is more manual than you might expect.
|
||||
*
|
||||
* @param params An object containing the pagination and filters
|
||||
* @returns string URL to call
|
||||
*/
|
||||
export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
|
||||
const baseUrl =
|
||||
params.baseUrl || `${DATA_SOURCES.blockExplorerUrl}/transactions`;
|
||||
const url = new URL(baseUrl);
|
||||
const count = `${params.count || BE_TXS_PER_REQUEST}`;
|
||||
|
||||
if ('before' in params && params.before?.length > 0) {
|
||||
url.searchParams.append('last', count);
|
||||
url.searchParams.append('before', params.before);
|
||||
} else if ('after' in params && params.after?.length > 0) {
|
||||
url.searchParams.append('first', count);
|
||||
url.searchParams.append('after', params.after);
|
||||
} else {
|
||||
url.searchParams.append('first', count);
|
||||
}
|
||||
|
||||
// Hacky fix for param as array
|
||||
let urlAsString = url.toString();
|
||||
if (params.filters && params.filters?.length > 0) {
|
||||
urlAsString += '&' + params.filters.replaceAll(' ', '%20');
|
||||
}
|
||||
if (params.party && params.party?.length > 0) {
|
||||
urlAsString += `&filters[tx.submitter]=${params.party}`;
|
||||
}
|
||||
|
||||
return urlAsString;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { getTxsDataUrl } from './get-txs-data-url'; // import the function to be tested
|
||||
|
||||
describe('getTxsDataUrl', () => {
|
||||
it('should return the correct URL without filters and party', () => {
|
||||
const params = {
|
||||
count: 10,
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl = 'https://example.com/transactions?first=10';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should return the correct URL with "before" in params', () => {
|
||||
const params = {
|
||||
count: 5,
|
||||
before: '100.1',
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl = 'https://example.com/transactions?last=5&before=100.1';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should return the correct URL with "after" in params', () => {
|
||||
const params = {
|
||||
count: 5,
|
||||
after: '222.1',
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl = 'https://example.com/transactions?first=5&after=222.1';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should return the correct URL with filters and party', () => {
|
||||
const params = {
|
||||
count: 10,
|
||||
filters: 'filters[cmd.type]=Made Up Transaction',
|
||||
party: '1234',
|
||||
baseUrl: 'https://example.com/transactions',
|
||||
};
|
||||
const expectedUrl =
|
||||
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
|
||||
|
||||
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||
});
|
||||
});
|
||||
@@ -1,130 +1,141 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import type { URLSearchParamsInit } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
BlockExplorerTransactionResult,
|
||||
BlockExplorerTransactions,
|
||||
} from '../routes/types/block-explorer-response';
|
||||
import { DATA_SOURCES } from '../config';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import { AllFilterOptions } from '../components/txs/tx-filter';
|
||||
import type { FilterOption } from '../components/txs/tx-filter';
|
||||
import { BE_TXS_PER_REQUEST, getTxsDataUrl } from './get-txs-data-url';
|
||||
|
||||
export interface TxsStateProps {
|
||||
txsData: BlockExplorerTransactionResult[];
|
||||
hasMoreTxs: boolean;
|
||||
cursor: string;
|
||||
previousCursors: string[];
|
||||
hasPreviousPage: boolean;
|
||||
export function getTypeFilters(filters?: Set<FilterOption>) {
|
||||
if (!filters) {
|
||||
return '';
|
||||
} else if (filters.size > 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const forcedSingleFilter = Array.from(filters)[0];
|
||||
return `filters[cmd.type]=${forcedSingleFilter}`;
|
||||
}
|
||||
|
||||
export interface IUseTxsData {
|
||||
limit: number;
|
||||
filters?: string;
|
||||
count?: number;
|
||||
before?: string;
|
||||
after?: string;
|
||||
party?: string;
|
||||
filters?: Set<FilterOption>;
|
||||
}
|
||||
|
||||
interface IGetTxsDataUrl {
|
||||
limit: string;
|
||||
filters?: string;
|
||||
}
|
||||
export const useTxsData = ({
|
||||
count = 25,
|
||||
before,
|
||||
after,
|
||||
filters,
|
||||
party,
|
||||
}: IUseTxsData) => {
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
let hasMoreTxs = true;
|
||||
let txsData: BlockExplorerTransactionResult[] = [];
|
||||
|
||||
export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
|
||||
const url = new URL(`${DATA_SOURCES.blockExplorerUrl}/transactions`);
|
||||
|
||||
if (limit) {
|
||||
url.searchParams.append('limit', limit);
|
||||
}
|
||||
|
||||
// Hacky fix for param as array
|
||||
let urlAsString = url.toString();
|
||||
if (filters) {
|
||||
urlAsString += '&' + filters.replace(' ', '%20');
|
||||
}
|
||||
|
||||
return urlAsString;
|
||||
};
|
||||
|
||||
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
const [
|
||||
{ txsData, hasMoreTxs, cursor, previousCursors, hasPreviousPage },
|
||||
setTxsState,
|
||||
] = useState<TxsStateProps>({
|
||||
txsData: [],
|
||||
hasMoreTxs: false,
|
||||
previousCursors: [],
|
||||
cursor: '',
|
||||
hasPreviousPage: false,
|
||||
const url = getTxsDataUrl({
|
||||
filters: getTypeFilters(filters),
|
||||
count,
|
||||
before,
|
||||
after,
|
||||
party,
|
||||
});
|
||||
|
||||
const url = getTxsDataUrl({ limit: limit.toString(), filters });
|
||||
|
||||
const {
|
||||
state: { data, error, loading },
|
||||
refetch,
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && data && isNumber(data.transactions.length)) {
|
||||
setTxsState((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
txsData: data.transactions,
|
||||
hasMoreTxs: data.transactions.length >= limit,
|
||||
cursor: data?.transactions.at(-1)?.cursor || '',
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [loading, setTxsState, data, limit]);
|
||||
if (!loading && data && isNumber(data.transactions.length)) {
|
||||
hasMoreTxs = data.transactions.length >= count;
|
||||
txsData = data.transactions;
|
||||
}
|
||||
|
||||
const nextPage = useCallback(() => {
|
||||
const c = data?.transactions.at(0)?.cursor;
|
||||
const newPreviousCursors = c ? [...previousCursors, c] : previousCursors;
|
||||
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
hasPreviousPage: true,
|
||||
previousCursors: newPreviousCursors,
|
||||
}));
|
||||
|
||||
return refetch({
|
||||
limit,
|
||||
before: cursor,
|
||||
});
|
||||
}, [data, previousCursors, cursor, limit, refetch]);
|
||||
const after = data?.transactions.at(-1)?.cursor || '';
|
||||
const params: URLSearchParamsInit = { after };
|
||||
if (filters) {
|
||||
params.filters = Array.from(filters).join(',');
|
||||
}
|
||||
setSearchParams(params);
|
||||
}, [filters, data, setSearchParams]);
|
||||
|
||||
const previousPage = useCallback(() => {
|
||||
const previousCursor = [...previousCursors].pop();
|
||||
const newPreviousCursors = previousCursors.slice(0, -1);
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
hasPreviousPage: newPreviousCursors.length > 0,
|
||||
previousCursors: newPreviousCursors,
|
||||
}));
|
||||
return refetch({
|
||||
limit,
|
||||
before: previousCursor,
|
||||
});
|
||||
}, [previousCursors, limit, refetch]);
|
||||
const before = data?.transactions[0]?.cursor || '';
|
||||
const params: URLSearchParamsInit = { before };
|
||||
if (filters && filters.size > 0 && filters.size === 1) {
|
||||
params.filters = Array.from(filters)[0];
|
||||
}
|
||||
setSearchParams(params);
|
||||
}, [filters, data, setSearchParams]);
|
||||
|
||||
const refreshTxs = useCallback(async () => {
|
||||
setTxsState(() => ({
|
||||
txsData: [],
|
||||
cursor: '',
|
||||
previousCursors: [],
|
||||
hasMoreTxs: false,
|
||||
hasPreviousPage: false,
|
||||
}));
|
||||
const params: URLSearchParamsInit = {};
|
||||
if (filters && filters.size > 0 && filters.size === 1) {
|
||||
params.filters = Array.from(filters)[0];
|
||||
}
|
||||
setSearchParams(params);
|
||||
|
||||
refetch({ limit });
|
||||
}, [setTxsState, limit, refetch, filters]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
refetch({ count: BE_TXS_PER_REQUEST });
|
||||
}, [setSearchParams, refetch, filters]);
|
||||
|
||||
const updateFilters = useCallback(
|
||||
(newFilters: Set<FilterOption>) => {
|
||||
const params: URLSearchParamsInit = {};
|
||||
if (newFilters && newFilters.size === 1) {
|
||||
params.filters = Array.from(newFilters)[0];
|
||||
}
|
||||
|
||||
setSearchParams(params);
|
||||
},
|
||||
[setSearchParams]
|
||||
);
|
||||
|
||||
return {
|
||||
updateFilters,
|
||||
txsData,
|
||||
loading,
|
||||
error,
|
||||
hasMoreTxs,
|
||||
hasPreviousPage,
|
||||
previousCursors,
|
||||
cursor,
|
||||
refreshTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a Set of filters based on the URLSearchParams, or
|
||||
* defaults to all.
|
||||
* @param params
|
||||
* @returns Set
|
||||
*/
|
||||
export function getInitialFilters(params: URLSearchParams): Set<FilterOption> {
|
||||
const defaultFilters = new Set(AllFilterOptions);
|
||||
|
||||
const p = params.get('filters');
|
||||
|
||||
if (!p) {
|
||||
return defaultFilters;
|
||||
}
|
||||
|
||||
const filters = new Set<FilterOption>();
|
||||
p.split(',').forEach((f) => {
|
||||
if (AllFilterOptions.includes(f as FilterOption)) {
|
||||
filters.add(f as FilterOption);
|
||||
}
|
||||
});
|
||||
|
||||
if (filters.size === 0) {
|
||||
return defaultFilters;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ describe('Block', () => {
|
||||
(useFetch as jest.Mock).mockReturnValue({
|
||||
state: { data: createBlockResponse(1), loading: false, error: null },
|
||||
});
|
||||
|
||||
render(renderComponent(1));
|
||||
await waitFor(() => screen.getByTestId('block-header'));
|
||||
expect(screen.getByTestId('previous-block-button')).toHaveAttribute(
|
||||
|
||||
@@ -15,9 +15,13 @@ import { PartyBlockAccounts } from './components/party-block-accounts';
|
||||
import { isValidPartyId } from './components/party-id-error';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
const Party = () => {
|
||||
const [params] = useSearchParams();
|
||||
|
||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||
const { party } = useParams<{ party: string }>();
|
||||
|
||||
@@ -27,24 +31,21 @@ const Party = () => {
|
||||
const partyId = toNonHex(party ? party : '');
|
||||
const { isMobile } = useScreenDimensions();
|
||||
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
|
||||
const baseFilters = `filters[tx.submitter]=${partyId}`;
|
||||
const f =
|
||||
filters && filters.size === 1
|
||||
? `${baseFilters}&filters[cmd.type]=${Array.from(filters)[0]}`
|
||||
: baseFilters;
|
||||
|
||||
const {
|
||||
hasMoreTxs,
|
||||
nextPage,
|
||||
refreshTxs,
|
||||
previousPage,
|
||||
error,
|
||||
refreshTxs,
|
||||
loading,
|
||||
txsData,
|
||||
hasPreviousPage,
|
||||
hasMoreTxs,
|
||||
updateFilters,
|
||||
} = useTxsData({
|
||||
limit: 25,
|
||||
filters: f,
|
||||
filters: filters.size === 1 ? filters : undefined,
|
||||
before: params.get('before') || undefined,
|
||||
after: !params.get('before') ? params.get('after') || undefined : undefined,
|
||||
party: partyId,
|
||||
});
|
||||
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
@@ -102,15 +103,21 @@ const Party = () => {
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
hasPreviousPage={true}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
>
|
||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
||||
<TxsFilter
|
||||
filters={filters}
|
||||
setFilters={(f) => {
|
||||
setFilters(f);
|
||||
updateFilters(f as Set<FilterOption>);
|
||||
}}
|
||||
/>
|
||||
</TxsListNavigation>
|
||||
{!error && txsData ? (
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
hasMoreTxs={true}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={nextPage}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { useTxsData, getInitialFilters } from '../../../hooks/use-txs-data';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import { TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||
|
||||
const BE_TXS_PER_REQUEST = 25;
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
export const TxsList = () => {
|
||||
useDocumentTitle(['Transactions']);
|
||||
@@ -27,25 +27,22 @@ export const TxsList = () => {
|
||||
* @returns {JSX.Element} Transaction List and controls
|
||||
*/
|
||||
export const TxsListFiltered = () => {
|
||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||
|
||||
const f =
|
||||
filters && filters.size === 1
|
||||
? `filters[cmd.type]=${Array.from(filters)[0]}`
|
||||
: '';
|
||||
const [params] = useSearchParams();
|
||||
const [filters, setFilters] = useState(getInitialFilters(params));
|
||||
|
||||
const {
|
||||
hasMoreTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
error,
|
||||
refreshTxs,
|
||||
loading,
|
||||
txsData,
|
||||
hasPreviousPage,
|
||||
hasMoreTxs,
|
||||
updateFilters,
|
||||
} = useTxsData({
|
||||
limit: BE_TXS_PER_REQUEST,
|
||||
filters: f,
|
||||
filters: filters.size === 1 ? filters : undefined,
|
||||
before: params.get('before') || undefined,
|
||||
after: !params.get('before') ? params.get('after') || undefined : undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -54,11 +51,17 @@ export const TxsListFiltered = () => {
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
hasPreviousPage={true}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
>
|
||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
||||
<TxsFilter
|
||||
filters={filters}
|
||||
setFilters={(f) => {
|
||||
setFilters(f);
|
||||
updateFilters(f as Set<FilterOption>);
|
||||
}}
|
||||
/>
|
||||
</TxsListNavigation>
|
||||
<TxsInfiniteList
|
||||
hasFilters={filters.size > 0}
|
||||
|
||||
@@ -15,6 +15,9 @@ query ExplorerNodes {
|
||||
stakedByDelegates
|
||||
stakedTotal
|
||||
pendingStake
|
||||
rankingScore {
|
||||
performanceScore
|
||||
}
|
||||
epochData {
|
||||
total
|
||||
offline
|
||||
|
||||
@@ -6,7 +6,7 @@ const defaultOptions = {} as const;
|
||||
export type ExplorerNodesQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerNodesQuery = { __typename?: 'Query', nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, infoUrl: string, avatarUrl?: string | null, pubkey: string, tmPubkey: string, ethereumAddress: string, location: string, status: Types.NodeStatus, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null } } | null> | null } };
|
||||
export type ExplorerNodesQuery = { __typename?: 'Query', nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, infoUrl: string, avatarUrl?: string | null, pubkey: string, tmPubkey: string, ethereumAddress: string, location: string, status: Types.NodeStatus, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, rankingScore: { __typename?: 'RankingScore', performanceScore: string }, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null } } | null> | null } };
|
||||
|
||||
|
||||
export const ExplorerNodesDocument = gql`
|
||||
@@ -27,6 +27,9 @@ export const ExplorerNodesDocument = gql`
|
||||
stakedByDelegates
|
||||
stakedTotal
|
||||
pendingStake
|
||||
rankingScore {
|
||||
performanceScore
|
||||
}
|
||||
epochData {
|
||||
total
|
||||
offline
|
||||
|
||||
@@ -91,7 +91,15 @@ export const ValidatorsPage = () => {
|
||||
const { data: tmData } = useTendermintValidators(5000);
|
||||
const { data, loading, error, refetch } = useExplorerNodesQuery();
|
||||
|
||||
const validators = compact(data?.nodesConnection.edges?.map((e) => e?.node));
|
||||
const validators = compact(
|
||||
data?.nodesConnection.edges
|
||||
?.map((e) => e?.node)
|
||||
.filter(
|
||||
(node) =>
|
||||
node?.rankingScore?.performanceScore &&
|
||||
new BigNumber(node.rankingScore.performanceScore).isGreaterThan(0)
|
||||
)
|
||||
);
|
||||
|
||||
// voting power
|
||||
const powers = compact(tmData?.result.validators).map(
|
||||
|
||||
@@ -218,17 +218,33 @@ describe(
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
// 3002-PROP-021
|
||||
cy.getByTestId('user-voted-yes').should('exist');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('against');
|
||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Tokens against proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Number of voting parties')
|
||||
.should('have.text', '1')
|
||||
.and('be.visible');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
cy.getByTestId('user-voted-no').should('exist');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
|
||||
|
||||
@@ -26,6 +26,8 @@ const votesTable = 'votes-table';
|
||||
const openProposals = 'open-proposals';
|
||||
const proposalVoteProgressForPercentage =
|
||||
'vote-progress-indicator-percentage-for';
|
||||
const majorityVoteReached = 'majority-reached';
|
||||
const minParticipationReached = 'participation-reached';
|
||||
const proposalTimeout = { timeout: 8000 };
|
||||
|
||||
context(
|
||||
@@ -72,7 +74,7 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
// 3001-VOTE-046 3001-VOTE-044 3001-VOTE-074 3001-VOTE-074
|
||||
// 3001-VOTE-020 3001-VOTE-021 3001-VOTE-046 3001-VOTE-044 3001-VOTE-074 3001-VOTE-074
|
||||
it('Able to enact proposal by voting', function () {
|
||||
const proposalTitle = 'Add New proposal with short enactment';
|
||||
const proposalTx = createUpdateNetworkProposalTxBody();
|
||||
@@ -85,10 +87,18 @@ context(
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil(proposalListItem)
|
||||
.last()
|
||||
.within(() => cy.getByTestId(viewProposalButton).click());
|
||||
.within(() => {
|
||||
// 3001-VOTE-019 time to vote is highlighted red
|
||||
cy.getByTestId('vote-details')
|
||||
.find('span')
|
||||
.should('have.class', 'text-vega-pink');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(proposalStatus).should('have.text', 'Open');
|
||||
voteForProposal('for');
|
||||
cy.getByTestId(majorityVoteReached).should('exist');
|
||||
cy.getByTestId(minParticipationReached).should('exist');
|
||||
cy.getByTestId(proposalStatus, proposalTimeout)
|
||||
.should('have.text', 'Passed')
|
||||
.then(() => {
|
||||
@@ -104,6 +114,14 @@ context(
|
||||
cy.getByTestId(proposalVoteProgressForPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil(proposalListItem)
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.getByTestId(majorityVoteReached).should('exist');
|
||||
cy.getByTestId(minParticipationReached).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
// 3001-VOTE-047
|
||||
|
||||
@@ -49,8 +49,8 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
|
||||
// 3001-VOTE-018
|
||||
it('Newly created proposals list - proposals closest to closing date appear higher in list', function () {
|
||||
// 3001-VOTE-005
|
||||
const proposalDays = [364, 50, 2];
|
||||
for (let index = 0; index < proposalDays.length; index++) {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
@@ -120,16 +120,24 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
submitUniqueRawProposal({ proposalTitle: proposalTitle });
|
||||
getProposalFromTitle(proposalTitle).within(() => {
|
||||
// 3001-VOTE-039
|
||||
cy.getByTestId(voteStatus).should(
|
||||
cy.getByTestId('participation-not-reached').should(
|
||||
'have.text',
|
||||
'Participation not reached'
|
||||
'Min. participation not reached'
|
||||
);
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
voteForProposal('for');
|
||||
navigateTo(navigation.proposals);
|
||||
getProposalFromTitle(proposalTitle).within(() => {
|
||||
cy.getByTestId(voteStatus).should('have.text', 'Set to pass');
|
||||
cy.getByTestId(voteStatus).should(
|
||||
'have.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId('user-voted-yes').should('exist');
|
||||
cy.getByTestId('participation-reached').should(
|
||||
'have.text',
|
||||
'Min. participation reached'
|
||||
);
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId(voteBreakDownToggle).click();
|
||||
|
||||
@@ -351,6 +351,12 @@ context(
|
||||
);
|
||||
stakingPageDisassociateAllTokens();
|
||||
});
|
||||
|
||||
it('Able to associate over 1 million tokens', function () {
|
||||
cy.get(ethWalletAssociateButton).click();
|
||||
stakingPageAssociateTokens('2000000', { approve: true });
|
||||
stakingPageDisassociateAllTokens();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const networkUpgradeProposalListItem = 'protocol-upgrade-proposals-list-item';
|
||||
const closedProposals = 'closed-proposals';
|
||||
const closedProposalToggle = 'closed-proposals-toggle-networkUpgrades';
|
||||
const protocolUpgradeTime = 'protocol-upgrade-time';
|
||||
|
||||
context(
|
||||
'Governance Page - verify elements on page',
|
||||
@@ -174,6 +175,7 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
// 3009-NTWU-003 3009-NTWU-004 3009-NTWU-007
|
||||
it('should see details of network upgrade proposal', function () {
|
||||
mockNetworkUpgradeProposal();
|
||||
navigateTo(navigation.proposals);
|
||||
@@ -228,6 +230,7 @@ context(
|
||||
cy.getByTestId(closedProposalToggle).should('not.exist');
|
||||
});
|
||||
|
||||
// 3009-NTWU-001 3009-NTWU-002 3009-NTWU-006 3009-NTWU-009
|
||||
it('should display network upgrade banner with estimate', function () {
|
||||
mockNetworkUpgradeProposal();
|
||||
cy.visit('/');
|
||||
@@ -259,10 +262,18 @@ context(
|
||||
.as('displayedEstimate');
|
||||
cy.get('@displayedEstimate').then((estimateText) => {
|
||||
// Estimated time should automatically update every second
|
||||
cy.getByTestId('protocol-upgrade-time')
|
||||
cy.getByTestId(protocolUpgradeTime)
|
||||
.invoke('text')
|
||||
.should('not.eq', estimateText);
|
||||
});
|
||||
// time estimate on proposal detail
|
||||
cy.getByTestId('protocol-upgrade-proposal').within(() => {
|
||||
cy.get('@displayedEstimate').then((estimateText) => {
|
||||
cy.getByTestId(protocolUpgradeTime)
|
||||
.invoke('text')
|
||||
.should('not.eq', estimateText);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -25,3 +25,5 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
CYPRESS_FAIRGROUND=false
|
||||
LC_ALL="en_US.UTF-8"
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -25,3 +25,5 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
@@ -16,3 +16,6 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -17,3 +17,6 @@ NX_VEGA_REST_URL=https://api.vega.community/api/v2/
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
@@ -16,3 +16,6 @@ NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
|
||||
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
@@ -11,4 +11,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -16,4 +16,7 @@ NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -13,4 +13,7 @@ NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
@@ -0,0 +1 @@
|
||||
export * from './json-diff';
|
||||
@@ -0,0 +1,30 @@
|
||||
import { render, screen, cleanup } from '@testing-library/react';
|
||||
import { JsonDiff } from './json-diff';
|
||||
|
||||
describe('JsonDiff', () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
it('renders without crashing', () => {
|
||||
render(<JsonDiff left={{}} right={{}} />);
|
||||
expect(screen.getByTestId('json-diff')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the correct message when both objects are identical', () => {
|
||||
render(<JsonDiff left={{}} right={{}} />);
|
||||
expect(screen.getByText('Data is identical')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show the "identical" message when both objects are not identical', () => {
|
||||
render(
|
||||
<JsonDiff
|
||||
left={{
|
||||
name: 'test',
|
||||
}}
|
||||
right={{
|
||||
name: 'test2',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByText('Data is identical')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { formatters, create } from 'jsondiffpatch';
|
||||
import 'jsondiffpatch/dist/formatters-styles/html.css';
|
||||
import 'jsondiffpatch/dist/formatters-styles/annotated.css';
|
||||
|
||||
export type JsonValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| JsonValue[]
|
||||
| { [key: string]: JsonValue };
|
||||
|
||||
interface JsonDiffProps {
|
||||
left: JsonValue;
|
||||
right: JsonValue;
|
||||
objectHash?: (obj: unknown) => string | undefined;
|
||||
}
|
||||
|
||||
export const JsonDiff = ({ right, left, objectHash }: JsonDiffProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [html, setHtml] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const delta = create({
|
||||
objectHash,
|
||||
}).diff(left, right);
|
||||
|
||||
if (delta) {
|
||||
const deltaHtml = formatters.html.format(delta, left);
|
||||
|
||||
formatters.html.hideUnchanged();
|
||||
|
||||
setHtml(deltaHtml);
|
||||
} else {
|
||||
setHtml(undefined);
|
||||
}
|
||||
}, [right, left, objectHash]);
|
||||
|
||||
return html ? (
|
||||
<div data-testid="json-diff" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
) : (
|
||||
<p data-testid="json-diff">{t('dataIsIdentical')}</p>
|
||||
);
|
||||
};
|
||||
@@ -18,10 +18,7 @@ import { VegaWallet } from '../vega-wallet';
|
||||
import { useLocation, useMatch } from 'react-router-dom';
|
||||
import { useEffect } from 'react';
|
||||
import { useTelemetryDialog } from '../telemetry-dialog/telemetry-dialog';
|
||||
import {
|
||||
ProtocolUpgradeCountdown,
|
||||
ProtocolUpgradeCountdownMode,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
|
||||
|
||||
export const SettingsLink = () => {
|
||||
const { open, isOpen, close } = useTelemetryDialog();
|
||||
@@ -68,9 +65,7 @@ export const Nav = ({ theme }: Pick<NavigationProps, 'theme'>) => {
|
||||
actions={
|
||||
<>
|
||||
<SettingsLink />
|
||||
<ProtocolUpgradeCountdown
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<ProtocolUpgradeCountdown />
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -853,5 +853,7 @@
|
||||
"consensusNodes": "consensus nodes",
|
||||
"activeNodes": "active nodes",
|
||||
"Estimated time to upgrade": "Estimated time to upgrade",
|
||||
"Upgraded at": "Upgraded at"
|
||||
"Upgraded at": "Upgraded at",
|
||||
"dataIsIdentical": "Data is identical",
|
||||
"updatesToMarket": "Updates to market"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ export const downloadJson = (jsonString: string, proposalTitle: string) => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
+29
-9
@@ -22,6 +22,16 @@ import {
|
||||
} from '../../test-helpers/mocks';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
jest.mock('@vegaprotocol/proposals', () => ({
|
||||
...jest.requireActual('@vegaprotocol/proposals'),
|
||||
useSuccessorMarketProposalDetails: () => ({
|
||||
code: 'PARENT_CODE',
|
||||
parentMarketId: 'PARENT_ID',
|
||||
}),
|
||||
}));
|
||||
|
||||
const renderComponent = (
|
||||
proposal: ProposalQuery['proposal'],
|
||||
@@ -30,20 +40,27 @@ const renderComponent = (
|
||||
) =>
|
||||
render(
|
||||
<AppStateProvider>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={isListItem}
|
||||
networkParams={mockNetworkParams}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
<BrowserRouter>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={isListItem}
|
||||
networkParams={mockNetworkParams}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</BrowserRouter>
|
||||
</AppStateProvider>
|
||||
);
|
||||
|
||||
describe('Proposal header', () => {
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('Renders New market proposal', () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = true;
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
rationale: {
|
||||
@@ -76,6 +93,9 @@ describe('Proposal header', () => {
|
||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||
'tGBP settled future.'
|
||||
);
|
||||
expect(screen.getByTestId('proposal-successor-info')).toHaveTextContent(
|
||||
'PARENT_CODE'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders Update market proposal', () => {
|
||||
|
||||
+25
-1
@@ -11,6 +11,10 @@ import { ProposalInfoLabel } from '../proposal-info-label';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
import { ProposalVotingStatus } from '../proposal-voting-status';
|
||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import Routes from '../../../routes';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
@@ -39,6 +43,9 @@ export const ProposalHeader = ({
|
||||
fallbackTitle = t('NewMarketProposal');
|
||||
details = (
|
||||
<>
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<SuccessorCode proposalId={proposal?.id} />
|
||||
)}
|
||||
<span>
|
||||
{t('Code')}: {change.instrument.code}.
|
||||
</span>{' '}
|
||||
@@ -138,7 +145,7 @@ export const ProposalHeader = ({
|
||||
className="flex items-center gap-2"
|
||||
data-testid={`user-voted-${voteState.toLowerCase()}`}
|
||||
>
|
||||
<div className="text-vega-green">
|
||||
<div className="text-vega-green" data-testid="you-voted-icon">
|
||||
<VegaIcon name={VegaIconNames.VOTE} size={24} />
|
||||
</div>
|
||||
<div>
|
||||
@@ -181,3 +188,20 @@ export const ProposalHeader = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => {
|
||||
const { t } = useTranslation();
|
||||
const successor = useSuccessorMarketProposalDetails(proposalId);
|
||||
|
||||
return successor.parentMarketId || successor.code ? (
|
||||
<span className="block" data-testid="proposal-successor-info">
|
||||
{t('Successor market to')}:{' '}
|
||||
<Link
|
||||
to={`${Routes.PROPOSALS}/${successor.parentMarketId}`}
|
||||
className="hover:underline"
|
||||
>
|
||||
{successor.code || successor.parentMarketId}
|
||||
</Link>
|
||||
</span>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './proposal-market-changes';
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import {
|
||||
ProposalMarketChanges,
|
||||
applyImmutableKeysFromEarlierVersion,
|
||||
} from './proposal-market-changes';
|
||||
import type { JsonValue } from '../../../../components/json-diff';
|
||||
|
||||
describe('applyImmutableKeysFromEarlierVersion', () => {
|
||||
it('returns an empty object if any argument is not an object or null', () => {
|
||||
const earlierVersion: JsonValue = null;
|
||||
const updatedVersion: JsonValue = null;
|
||||
expect(
|
||||
applyImmutableKeysFromEarlierVersion(earlierVersion, updatedVersion)
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it('overrides updatedVersion with values from earlierVersion for immutable keys', () => {
|
||||
const earlierVersion: JsonValue = {
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 3,
|
||||
instrument: {
|
||||
name: 'Instrument1',
|
||||
future: {
|
||||
settlementAsset: 'Asset1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updatedVersion: JsonValue = {
|
||||
decimalPlaces: 3, // should be overridden by 2
|
||||
positionDecimalPlaces: 4, // should be overridden by 3
|
||||
instrument: {
|
||||
name: 'Instrument2', // should be overridden by 'Instrument1'
|
||||
future: {
|
||||
settlementAsset: 'Asset2', // should be overridden by 'Asset1'
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const expected: JsonValue = {
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 3,
|
||||
instrument: {
|
||||
name: 'Instrument1',
|
||||
future: {
|
||||
settlementAsset: 'Asset1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
applyImmutableKeysFromEarlierVersion(earlierVersion, updatedVersion)
|
||||
).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProposalMarketChanges', () => {
|
||||
it('renders correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<ProposalMarketChanges
|
||||
originalProposal={{}}
|
||||
latestEnactedProposal={{}}
|
||||
updatedProposal={{}}
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('proposal-market-changes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('JsonDiff is not visible when showChanges is false', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ProposalMarketChanges
|
||||
originalProposal={{}}
|
||||
latestEnactedProposal={{}}
|
||||
updatedProposal={{}}
|
||||
/>
|
||||
);
|
||||
expect(queryByTestId('json-diff')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('JsonDiff is visible when showChanges is true', async () => {
|
||||
const { getByTestId } = render(
|
||||
<ProposalMarketChanges
|
||||
originalProposal={{}}
|
||||
latestEnactedProposal={{}}
|
||||
updatedProposal={{}}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(getByTestId('proposal-market-changes-toggle'));
|
||||
expect(getByTestId('json-diff')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import set from 'lodash/set';
|
||||
import get from 'lodash/get';
|
||||
import { JsonDiff } from '../../../../components/json-diff';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useState } from 'react';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import type { JsonValue } from '../../../../components/json-diff';
|
||||
|
||||
const immutableKeys = [
|
||||
'decimalPlaces',
|
||||
'positionDecimalPlaces',
|
||||
'instrument.name',
|
||||
'instrument.future.settlementAsset',
|
||||
];
|
||||
|
||||
export const applyImmutableKeysFromEarlierVersion = (
|
||||
earlierVersion: JsonValue,
|
||||
updatedVersion: JsonValue
|
||||
) => {
|
||||
if (
|
||||
typeof earlierVersion !== 'object' ||
|
||||
earlierVersion === null ||
|
||||
typeof updatedVersion !== 'object' ||
|
||||
updatedVersion === null
|
||||
) {
|
||||
// If either version is not an object or is null, return null or throw an error
|
||||
return {};
|
||||
}
|
||||
|
||||
const updatedVersionCopy = cloneDeep(updatedVersion);
|
||||
|
||||
// Overwrite the immutable keys in the updatedVersionCopy with the earlier values
|
||||
immutableKeys.forEach((key) => {
|
||||
set(updatedVersionCopy, key, get(earlierVersion, key));
|
||||
});
|
||||
|
||||
return updatedVersionCopy;
|
||||
};
|
||||
|
||||
interface ProposalMarketChangesProps {
|
||||
originalProposal: JsonValue;
|
||||
latestEnactedProposal: JsonValue | undefined;
|
||||
updatedProposal: JsonValue;
|
||||
}
|
||||
|
||||
export const ProposalMarketChanges = ({
|
||||
originalProposal,
|
||||
latestEnactedProposal,
|
||||
updatedProposal,
|
||||
}: ProposalMarketChangesProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showChanges, setShowChanges] = useState(false);
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-market-changes">
|
||||
<CollapsibleToggle
|
||||
toggleState={showChanges}
|
||||
setToggleState={setShowChanges}
|
||||
dataTestId={'proposal-market-changes-toggle'}
|
||||
>
|
||||
<SubHeading title={t('updatesToMarket')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showChanges && (
|
||||
<div className="mb-6">
|
||||
<JsonDiff
|
||||
left={latestEnactedProposal || originalProposal}
|
||||
right={
|
||||
latestEnactedProposal
|
||||
? applyImmutableKeysFromEarlierVersion(
|
||||
latestEnactedProposal,
|
||||
updatedProposal
|
||||
)
|
||||
: applyImmutableKeysFromEarlierVersion(
|
||||
originalProposal,
|
||||
updatedProposal
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -17,6 +17,7 @@ import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import type { AssetQuery } from '@vegaprotocol/assets';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ProposalMarketChanges } from '../proposal-market-changes';
|
||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
|
||||
export enum ProposalType {
|
||||
@@ -34,6 +35,10 @@ export interface ProposalProps {
|
||||
assetData?: AssetQuery | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
restData: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
originalMarketProposalRestData?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mostRecentlyEnactedAssociatedMarketProposal?: any;
|
||||
}
|
||||
|
||||
export const Proposal = ({
|
||||
@@ -42,6 +47,8 @@ export const Proposal = ({
|
||||
restData,
|
||||
newMarketData,
|
||||
assetData,
|
||||
originalMarketProposalRestData,
|
||||
mostRecentlyEnactedAssociatedMarketProposal,
|
||||
}: ProposalProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -154,6 +161,24 @@ export const Proposal = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateMarket' && (
|
||||
<div className="mb-4">
|
||||
<ProposalMarketChanges
|
||||
originalProposal={
|
||||
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
|
||||
?.changes || {}
|
||||
}
|
||||
latestEnactedProposal={
|
||||
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal
|
||||
?.terms?.updateMarket?.changes || {}
|
||||
}
|
||||
updatedProposal={
|
||||
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(proposal.terms.change.__typename === 'NewAsset' ||
|
||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||
asset && (
|
||||
|
||||
+1
-5
@@ -1,5 +1,5 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
@@ -115,10 +115,6 @@ export const ProposalsListItemDetails = ({
|
||||
|
||||
return (
|
||||
<div className="mt-4 items-start text-sm">
|
||||
<div className="text-vega-green">
|
||||
<VegaIcon size={16} name={VegaIconNames.VOTE} />
|
||||
<VegaIcon size={16} name={VegaIconNames.TICKET} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-vega-light-300 mb-2">
|
||||
{voteDetails && <span data-testid="vote-details">{voteDetails}</span>}
|
||||
{voteDetails && voteStatus && <span>·</span>}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Proposal } from '../components/proposal';
|
||||
@@ -16,7 +16,12 @@ import {
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
|
||||
export const ProposalContainer = () => {
|
||||
const [
|
||||
mostRecentlyEnactedAssociatedMarketProposal,
|
||||
setMostRecentlyEnactedAssociatedMarketProposal,
|
||||
] = useState(undefined);
|
||||
const params = useParams<{ proposalId: string }>();
|
||||
|
||||
const {
|
||||
params: networkParams,
|
||||
loading: networkParamsLoading,
|
||||
@@ -37,9 +42,11 @@ export const ProposalContainer = () => {
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
]);
|
||||
|
||||
const {
|
||||
state: { data: restData },
|
||||
state: { data: restData, loading: restLoading, error: restError },
|
||||
} = useFetch(`${ENV.rest}governance?proposalId=${params.proposalId}`);
|
||||
|
||||
const { data, loading, error, refetch } = useProposalQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
@@ -47,6 +54,35 @@ export const ProposalContainer = () => {
|
||||
skip: !params.proposalId,
|
||||
});
|
||||
|
||||
const {
|
||||
state: {
|
||||
data: originalMarketProposalRestData,
|
||||
loading: originalMarketProposalRestLoading,
|
||||
error: originalMarketProposalRestError,
|
||||
},
|
||||
} = useFetch(
|
||||
`${ENV.rest}governance?proposalId=${
|
||||
data?.proposal?.terms.change.__typename === 'UpdateMarket' &&
|
||||
data?.proposal.terms.change.marketId
|
||||
}`,
|
||||
undefined,
|
||||
true,
|
||||
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
|
||||
);
|
||||
|
||||
const {
|
||||
state: {
|
||||
data: previouslyEnactedMarketProposalsRestData,
|
||||
loading: previouslyEnactedMarketProposalsRestLoading,
|
||||
error: previouslyEnactedMarketProposalsRestError,
|
||||
},
|
||||
} = useFetch(
|
||||
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
|
||||
undefined,
|
||||
true,
|
||||
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
|
||||
);
|
||||
|
||||
const {
|
||||
data: newMarketData,
|
||||
loading: newMarketLoading,
|
||||
@@ -79,6 +115,39 @@ export const ProposalContainer = () => {
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
previouslyEnactedMarketProposalsRestData &&
|
||||
data?.proposal?.terms.change.__typename === 'UpdateMarket'
|
||||
) {
|
||||
const change = data?.proposal?.terms?.change as { marketId: string };
|
||||
|
||||
const filteredProposals =
|
||||
// @ts-ignore rest data is not typed
|
||||
previouslyEnactedMarketProposalsRestData.connection.edges.filter(
|
||||
// @ts-ignore rest data is not typed
|
||||
({ node }) =>
|
||||
node?.proposal?.terms?.updateMarket?.marketId === change.marketId
|
||||
);
|
||||
|
||||
const sortedProposals = filteredProposals.sort(
|
||||
// @ts-ignore rest data is not typed
|
||||
(a, b) =>
|
||||
new Date(a?.node?.terms?.enactmentTimestamp).getTime() -
|
||||
new Date(b?.node?.terms?.enactmentTimestamp).getTime()
|
||||
);
|
||||
|
||||
setMostRecentlyEnactedAssociatedMarketProposal(
|
||||
sortedProposals[sortedProposals.length - 1]
|
||||
);
|
||||
}
|
||||
}, [
|
||||
previouslyEnactedMarketProposalsRestData,
|
||||
params.proposalId,
|
||||
data?.proposal?.terms.change.__typename,
|
||||
data?.proposal?.terms.change,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(refetch, 2000);
|
||||
return () => clearInterval(interval);
|
||||
@@ -87,14 +156,39 @@ export const ProposalContainer = () => {
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={
|
||||
loading || newMarketLoading || assetLoading || networkParamsLoading
|
||||
loading ||
|
||||
newMarketLoading ||
|
||||
assetLoading ||
|
||||
networkParamsLoading ||
|
||||
(restLoading ? (restLoading as boolean) : false) ||
|
||||
(originalMarketProposalRestLoading
|
||||
? (originalMarketProposalRestLoading as boolean)
|
||||
: false) ||
|
||||
(previouslyEnactedMarketProposalsRestLoading
|
||||
? (previouslyEnactedMarketProposalsRestLoading as boolean)
|
||||
: false)
|
||||
}
|
||||
error={
|
||||
error ||
|
||||
newMarketError ||
|
||||
assetError ||
|
||||
restError ||
|
||||
originalMarketProposalRestError ||
|
||||
previouslyEnactedMarketProposalsRestError ||
|
||||
networkParamsError
|
||||
}
|
||||
error={error || newMarketError || assetError || networkParamsError}
|
||||
data={{
|
||||
...data,
|
||||
...networkParams,
|
||||
...(newMarketData ? { newMarketData } : {}),
|
||||
...(assetData ? { assetData } : {}),
|
||||
...(restData ? { restData } : {}),
|
||||
...(originalMarketProposalRestData
|
||||
? { originalMarketProposalRestData }
|
||||
: {}),
|
||||
...(previouslyEnactedMarketProposalsRestData
|
||||
? { previouslyEnactedMarketProposalsRestData }
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
{data?.proposal ? (
|
||||
@@ -104,6 +198,10 @@ export const ProposalContainer = () => {
|
||||
restData={restData}
|
||||
newMarketData={newMarketData}
|
||||
assetData={assetData}
|
||||
originalMarketProposalRestData={originalMarketProposalRestData}
|
||||
mostRecentlyEnactedAssociatedMarketProposal={
|
||||
mostRecentlyEnactedAssociatedMarketProposal
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ProposalNotFound />
|
||||
|
||||
-15
@@ -5,7 +5,6 @@ import {
|
||||
RewardsTable,
|
||||
} from '../shared-rewards-table-assets/shared-rewards-table-assets';
|
||||
import type { EpochIndividualReward } from './generate-epoch-individual-rewards-list';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface EpochIndividualRewardsGridProps {
|
||||
data: EpochIndividualReward;
|
||||
@@ -22,14 +21,11 @@ interface RewardItemProps {
|
||||
const DisplayReward = ({
|
||||
reward,
|
||||
decimals,
|
||||
percentageOfTotal,
|
||||
}: {
|
||||
reward: string;
|
||||
decimals: number;
|
||||
percentageOfTotal?: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (Number(reward) === 0) {
|
||||
return <span className="text-vega-dark-300">-</span>;
|
||||
}
|
||||
@@ -39,23 +35,12 @@ const DisplayReward = ({
|
||||
description={
|
||||
<div className="flex flex-col items-start">
|
||||
<span>{formatNumber(toBigNum(reward, decimals), decimals)}</span>
|
||||
{percentageOfTotal && (
|
||||
<span className="text-vega-dark-300">
|
||||
({percentageOfTotal}% {t('ofTotalDistributed')})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button>
|
||||
<div className="flex flex-col items-start">
|
||||
<span>{formatNumber(toBigNum(reward, decimals), 4)}</span>
|
||||
{percentageOfTotal && (
|
||||
<span className="text-vega-dark-300">
|
||||
({formatNumber(percentageOfTotal, 4).toString()}
|
||||
%)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
-76
@@ -168,17 +168,6 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
expect(result2[1].epoch).toEqual(1);
|
||||
});
|
||||
|
||||
it('correctly calculates the total value of rewards for an asset', () => {
|
||||
const rewards = [reward1, reward4];
|
||||
const result = generateEpochIndividualRewardsList({
|
||||
rewards,
|
||||
epochId: 1,
|
||||
epochRewardSummaries: [],
|
||||
});
|
||||
|
||||
expect(result[0].rewards[0].totalAmount).toEqual('200');
|
||||
});
|
||||
|
||||
it('returns data in the expected shape', () => {
|
||||
// Just sanity checking the whole structure here
|
||||
const rewards = [reward1, reward2, reward3, reward4];
|
||||
@@ -458,69 +447,4 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('correctly calculates the percentage of two or more rewards by referencing the total rewards amount', () => {
|
||||
const result = generateEpochIndividualRewardsList({
|
||||
rewards: [
|
||||
// reward1 is 100 usd, which is 10% of the total rewards amount
|
||||
reward1,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
amount: '200',
|
||||
percentageOfTotal: '0.2',
|
||||
receivedAt: new Date(),
|
||||
asset: { id: 'usd', symbol: 'USD', name: 'USD', decimals: 6 },
|
||||
party: { id: 'blah' },
|
||||
epoch: { id: '1' },
|
||||
},
|
||||
],
|
||||
epochId: 1,
|
||||
epochRewardSummaries: [
|
||||
{
|
||||
__typename: 'EpochRewardSummary',
|
||||
epoch: 1,
|
||||
assetId: 'usd',
|
||||
amount: '1000',
|
||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result[0]).toEqual({
|
||||
epoch: 1,
|
||||
rewards: [
|
||||
{
|
||||
asset: 'USD',
|
||||
decimals: 6,
|
||||
totalAmount: '300',
|
||||
rewardTypes: {
|
||||
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
|
||||
amount: '300',
|
||||
percentageOfTotal: '30',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+1
-16
@@ -59,7 +59,6 @@ export const generateEpochIndividualRewardsList = ({
|
||||
const epochIndividualRewards = rewards.reduce((acc, reward) => {
|
||||
const epochId = reward.epoch.id;
|
||||
const assetName = reward.asset.name;
|
||||
const assetId = reward.asset.id;
|
||||
const assetDecimals = reward.asset.decimals;
|
||||
const rewardType = reward.rewardType;
|
||||
const amount = reward.amount;
|
||||
@@ -77,13 +76,6 @@ export const generateEpochIndividualRewardsList = ({
|
||||
const epoch = acc.get(epochId);
|
||||
|
||||
// matchingTotalReward is the total awarded for all users for the reward type in the epoch of the asset
|
||||
const matchingTotalRewardAmount = epochRewardSummaries.find(
|
||||
(summary) =>
|
||||
summary.epoch === Number(epochId) &&
|
||||
summary.assetId === assetId &&
|
||||
summary.rewardType === rewardType
|
||||
)?.amount;
|
||||
|
||||
let asset = epoch?.rewards.find((r) => r.asset === assetName);
|
||||
|
||||
if (!asset) {
|
||||
@@ -106,14 +98,7 @@ export const generateEpochIndividualRewardsList = ({
|
||||
|
||||
asset.rewardTypes[rewardType] = {
|
||||
amount: newAmount,
|
||||
percentageOfTotal: matchingTotalRewardAmount
|
||||
? new BigNumber(newAmount)
|
||||
.dividedBy(matchingTotalRewardAmount)
|
||||
.multipliedBy(100)
|
||||
.toString()
|
||||
: // this should never be reached, if there's an individual reward there should
|
||||
// always be a reward total from the api too, but set it as a fallback just in case
|
||||
percentageOfTotal,
|
||||
percentageOfTotal: percentageOfTotal,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,6 @@ export const RewardsPage = () => {
|
||||
error: paramsError,
|
||||
} = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]);
|
||||
|
||||
console.log('params', params);
|
||||
|
||||
const payoutDuration = useMemo(() => {
|
||||
if (!params) {
|
||||
return 0;
|
||||
|
||||
@@ -8,9 +8,10 @@ import { TxState } from '../../../hooks/transaction-reducer';
|
||||
import { useTransaction } from '../../../hooks/use-transaction';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { AssociateInfo } from './associate-info';
|
||||
import { removeDecimal, toBigNum } from '@vegaprotocol/utils';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import type { EthereumConfig } from '@vegaprotocol/web3';
|
||||
import { useBalances } from '../../../lib/balances/balances-store';
|
||||
import { MaxUint256 } from '@ethersproject/constants';
|
||||
|
||||
export const WalletAssociate = ({
|
||||
perform,
|
||||
@@ -42,7 +43,7 @@ export const WalletAssociate = ({
|
||||
} = useTransaction(() => {
|
||||
return token.approve(
|
||||
ethereumConfig.staking_bridge_contract.address,
|
||||
removeDecimal('1000000', decimals).toString()
|
||||
MaxUint256.toString()
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Styles required to (effectively) un-override the
|
||||
/* Styles required to (effectively) un-override the
|
||||
* reset styles so that the Proposal description fields
|
||||
* render as you'd expect them to.
|
||||
*
|
||||
@@ -97,3 +97,33 @@
|
||||
.react-markdown-container ul li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.jsondiffpatch-delta,
|
||||
.jsondiffpatch-delta pre {
|
||||
font-family: 'Roboto Mono', monospace !important;
|
||||
}
|
||||
|
||||
.jsondiffpatch-delta pre {
|
||||
padding: 0 0.25em !important;
|
||||
}
|
||||
|
||||
.jsondiffpatch-added .jsondiffpatch-property-name,
|
||||
.jsondiffpatch-added .jsondiffpatch-value pre,
|
||||
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
|
||||
.jsondiffpatch-textdiff-added {
|
||||
background: theme(colors.vega.green[650]) !important;
|
||||
color: theme(colors.white) !important;
|
||||
}
|
||||
|
||||
.jsondiffpatch-deleted .jsondiffpatch-property-name,
|
||||
.jsondiffpatch-deleted pre,
|
||||
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
|
||||
.jsondiffpatch-textdiff-deleted {
|
||||
background: theme(colors.vega.pink[650]) !important;
|
||||
color: theme(colors.white) !important;
|
||||
}
|
||||
|
||||
.jsondiffpatch-moved .jsondiffpatch-moved-destination {
|
||||
background: theme(colors.vega.yellow[350]) !important;
|
||||
color: theme(colors.vega.dark[200]) !important;
|
||||
}
|
||||
|
||||
@@ -535,6 +535,7 @@ function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
|
||||
// unexpected latency
|
||||
const minBefore = subSeconds(new Date(), 5);
|
||||
const maxAfter = addSeconds(new Date(), 5);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(maxAfter);
|
||||
const date = new Date($dateTime.toString());
|
||||
expect(isAfter(date, minBefore) && isBefore(date, maxAfter)).to.equal(
|
||||
|
||||
@@ -363,12 +363,11 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
.first()
|
||||
.find('button svg')
|
||||
.should('exist');
|
||||
|
||||
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
||||
cy.get(rowSelector)
|
||||
.find('[col-id="successorMarketID"]')
|
||||
.find('[col-id="successorMarket"]')
|
||||
.first()
|
||||
.should('have.text', ' - ');
|
||||
.should('have.text', '-');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -68,22 +68,4 @@ describe('home', { tags: '@regression' }, () => {
|
||||
cy.getByTestId('connect').click();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Network switcher', () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('switch to fairground network and check status & incidents link', () => {
|
||||
// 0006-NETW-002
|
||||
// 0006-NETW-003
|
||||
cy.getByTestId('navigation')
|
||||
.find('[data-testid="network-switcher"]')
|
||||
.should('have.text', 'Custom')
|
||||
.click();
|
||||
cy.getByTestId('network-item').contains('Fairground testnet');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { MarketsQuery } from '@vegaprotocol/markets';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-all-markets"] .ag-center-cols-container .ag-row';
|
||||
'[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row';
|
||||
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
||||
|
||||
describe('markets all table', { tags: '@smoke' }, () => {
|
||||
@@ -27,13 +27,14 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
'Description',
|
||||
'Trading mode',
|
||||
'Status',
|
||||
'Successor market',
|
||||
'Best bid',
|
||||
'Best offer',
|
||||
'Mark price',
|
||||
'Settlement asset',
|
||||
'',
|
||||
];
|
||||
cy.getByTestId('tab-all-markets').within(($headers) => {
|
||||
cy.getByTestId('tab-open-markets').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
@@ -43,7 +44,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('markets tab should be rendered properly', () => {
|
||||
cy.get('[data-testid="All markets"]').should(
|
||||
cy.get('[data-testid="Open markets"]').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'active'
|
||||
@@ -165,7 +166,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
'ETHBTC.QM21',
|
||||
'SOLUSD',
|
||||
];
|
||||
cy.get('[data-testid="All markets"]').click({ force: true });
|
||||
cy.get('[data-testid="Open markets"]').click({ force: true });
|
||||
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
|
||||
cy.contains('AAPL.MF21').should('be.visible');
|
||||
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
|
||||
@@ -190,7 +191,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
describe('no open markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const markets: MarketsQuery = {};
|
||||
@@ -203,6 +204,6 @@ describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
|
||||
it.skip('can see no markets message', () => {
|
||||
// 6001-MARK-048
|
||||
cy.getByTestId('tab-all-markets').should('contain.text', 'No markets');
|
||||
cy.getByTestId('tab-open-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
const accordionContent = 'accordion-content';
|
||||
const blockExplorerLink = 'block-explorer-link';
|
||||
@@ -66,20 +67,24 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
// 6002-MDET-201
|
||||
cy.getByTestId(marketTitle).contains('Key details').click();
|
||||
|
||||
validateMarketDataRow(0, 'Name', 'BTCUSD Monthly (30 Jun 2022)');
|
||||
validateMarketDataRow(1, 'Market ID', 'market-0');
|
||||
const rows: [string, string][] = compact([
|
||||
['Name', 'BTCUSD Monthly (30 Jun 2022)'],
|
||||
['Market ID', 'market-0'],
|
||||
Cypress.env('NX_SUCCESSOR_MARKETS') && ['Parent Market ID', 'PARENT-A'],
|
||||
Cypress.env('NX_SUCCESSOR_MARKETS') && [
|
||||
'Insurance Pool Fraction',
|
||||
'0.75',
|
||||
],
|
||||
['Trading Mode', MarketTradingModeMapping.TRADING_MODE_CONTINUOUS],
|
||||
['Market Decimal Places', '5'],
|
||||
['Position Decimal Places', '0'],
|
||||
['Settlement Asset Decimal Places', '5'],
|
||||
]);
|
||||
|
||||
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
||||
validateMarketDataRow(2, 'Parent Market ID', 'PARENT-A');
|
||||
for (const rowNumber in rows) {
|
||||
const [name, value] = rows[rowNumber];
|
||||
validateMarketDataRow(Number(rowNumber), name, value);
|
||||
}
|
||||
validateMarketDataRow(
|
||||
3,
|
||||
'Trading Mode',
|
||||
MarketTradingModeMapping.TRADING_MODE_CONTINUOUS
|
||||
);
|
||||
validateMarketDataRow(4, 'Market Decimal Places', '5');
|
||||
validateMarketDataRow(5, 'Position Decimal Places', '0');
|
||||
validateMarketDataRow(6, 'Settlement Asset Decimal Places', '5');
|
||||
});
|
||||
|
||||
it('instrument displayed', () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
'Description',
|
||||
'Settlement asset',
|
||||
'State',
|
||||
'Parent market',
|
||||
'Voting',
|
||||
'Closing date',
|
||||
'Enactment date',
|
||||
@@ -83,7 +84,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
||||
const dropdownContent = '[data-testid="proposal-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
|
||||
// 6001-MARK-059
|
||||
@@ -99,7 +100,6 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
'VEGA_TOKEN_URL'
|
||||
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
||||
);
|
||||
cy.getByTestId('market-actions-content').click();
|
||||
});
|
||||
|
||||
// 6001-MARK-060
|
||||
@@ -213,11 +213,12 @@ describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
|
||||
// 6001-MARK-061
|
||||
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import { mockConnectWallet } from '@vegaprotocol/cypress';
|
||||
|
||||
describe('Navbar', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearAllLocalStorage();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.wait('@MarketsData');
|
||||
});
|
||||
|
||||
const pages = [
|
||||
{ name: 'Markets', link: '#/markets/all' },
|
||||
{ name: 'Trading', link: '#/markets' },
|
||||
{ name: 'Portfolio', link: '#/portfolio' },
|
||||
];
|
||||
|
||||
describe('desktop view', () => {
|
||||
pages.forEach(({ name, link }) => {
|
||||
it(`${name} should be correctly rendered`, () => {
|
||||
cy.get('nav')
|
||||
.find(`a[data-testid=${name}]:visible`)
|
||||
.then((element) => {
|
||||
cy.wrap(element).click();
|
||||
cy.location('hash').should('contain', link);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Resources dropdown should be correctly rendered', () => {
|
||||
const resourceSelector = 'ul li:contains(Resources)';
|
||||
['Docs', 'Give Feedback'].forEach((text, index) => {
|
||||
cy.get('nav').find(resourceSelector).contains('Resources').click();
|
||||
cy.get('nav')
|
||||
.find(resourceSelector)
|
||||
.find('.navigation-content li')
|
||||
.eq(index)
|
||||
.find('a')
|
||||
.then((element) => {
|
||||
expect(element.attr('target')).to.eq('_blank');
|
||||
expect(element.attr('href')).to.not.be.empty;
|
||||
expect(element.text()).to.eq(text);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Disclaimer should be presented after choosing from menu', () => {
|
||||
cy.get('nav')
|
||||
.find('ul li:contains(Resources)')
|
||||
.contains('Resources')
|
||||
.click();
|
||||
cy.getByTestId('Disclaimer').eq(0).click();
|
||||
cy.location('hash').should('equal', '#/disclaimer');
|
||||
cy.get('p').contains(
|
||||
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mobile view', () => {
|
||||
const viewportHeight = Cypress.config('viewportHeight');
|
||||
const viewportWidth = Cypress.config('viewportWidth');
|
||||
before(() => {
|
||||
// a little hack to keep the viewport size between tests (cypress bug)
|
||||
Cypress.config({
|
||||
viewportWidth: 560,
|
||||
viewportHeight: 890,
|
||||
});
|
||||
cy.viewport(560, 890);
|
||||
});
|
||||
|
||||
describe('wallet drawer', () => {
|
||||
it('wallet drawer should be correctly rendered', () => {
|
||||
mockConnectWallet();
|
||||
cy.connectVegaWallet(true);
|
||||
cy.getByTestId('connect-vega-wallet-mobile').click();
|
||||
cy.getByTestId('wallets-drawer').should('be.visible');
|
||||
cy.getByTestId('wallets-drawer').within((el) => {
|
||||
cy.wrap(el).get('button').contains('Disconnect').click();
|
||||
});
|
||||
cy.getByTestId('wallets-drawer').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('menu drawer', () => {
|
||||
pages.forEach(({ name, link }) => {
|
||||
it(`${name} should be correctly rendered`, () => {
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('menu-drawer').within((el) => {
|
||||
cy.wrap(el).getByTestId(name).click();
|
||||
cy.location('hash').should('contain', link);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Menu drawer should not be visible until opened', () => {
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('be.visible');
|
||||
cy.getByTestId('button-menu-drawer').click();
|
||||
cy.getByTestId('menu-drawer').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
after(() => {
|
||||
// a little hack to keep the viewport size between tests (cypress bug)
|
||||
Cypress.config({
|
||||
viewportWidth,
|
||||
viewportHeight,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -127,7 +127,7 @@ describe(
|
||||
{
|
||||
name: 'Price monitoring bounds',
|
||||
infoText:
|
||||
'Price Monitoring Bounds: Min 162.56291Max 182.96869Reference 172.47489',
|
||||
'Price Monitoring Bounds 1: Min 162.56291Max 182.96869Reference 172.47489',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -171,6 +171,7 @@ describe(
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
const actualDate = text.slice(0, -67);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(actualDate);
|
||||
const actualOhlc = text.slice(-67);
|
||||
assert.isTrue(expectedDateRegex.test(actualDate));
|
||||
|
||||
@@ -28,16 +28,16 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
|
||||
it('must be able to select order direction - long/short', function () {
|
||||
// 7002-SORD-004
|
||||
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleShort).click().next('input').should('be.checked');
|
||||
cy.getByTestId(toggleLong).click().next('input').should('be.checked');
|
||||
});
|
||||
|
||||
it('must be able to select order type - limit/market', function () {
|
||||
// 7002-SORD-005
|
||||
// 7002-SORD-006
|
||||
// 7002-SORD-007
|
||||
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
|
||||
cy.getByTestId(toggleLimit).click().next('input').should('be.checked');
|
||||
cy.getByTestId(toggleMarket).click().next('input').should('be.checked');
|
||||
});
|
||||
|
||||
it('order connect vega wallet button should connect', () => {
|
||||
@@ -51,7 +51,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
.click();
|
||||
cy.wait('@walletReq');
|
||||
cy.getByTestId(placeOrderBtn).should('be.visible');
|
||||
cy.getByTestId(toggleLimit).children('input').should('be.checked');
|
||||
cy.getByTestId(toggleLimit).next('input').should('be.checked');
|
||||
cy.getByTestId(orderPriceField).should('have.value', '101');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ const closePosition = 'close-position';
|
||||
const dialogCloseX = 'dialog-close';
|
||||
const dialogContent = 'dialog-content';
|
||||
const dropDownMenu = 'dropdown-menu';
|
||||
const marketActionsContent = 'market-actions-content';
|
||||
const marketActionsContent = 'position-actions-content';
|
||||
const positions = 'Positions';
|
||||
const tabPositions = 'tab-positions';
|
||||
const toastContent = 'toast-content';
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import { selectAsset } from '../support/helpers';
|
||||
|
||||
const amountField = 'input[name="amount"]';
|
||||
const transferText = 'transfer-intro-text';
|
||||
const errorText = 'input-error-text';
|
||||
const formFieldError = 'input-error-text';
|
||||
const includeTransferFeeRadioBtn = 'include-transfer-fee';
|
||||
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
||||
const manageVegaWallet = 'manage-vega-wallet';
|
||||
const submitTransferBtn = '[type="submit"]';
|
||||
const toAddressField = '[name="toAddress"]';
|
||||
const totalTransferfee = 'total-transfer-fee';
|
||||
const transferAmount = 'transfer-amount';
|
||||
const transferForm = 'transfer-form';
|
||||
const transferFee = 'transfer-fee';
|
||||
const walletTransfer = 'wallet-transfer';
|
||||
|
||||
const ASSET_EURO = 1;
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
|
||||
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Assets');
|
||||
cy.wait('@Accounts');
|
||||
|
||||
cy.mockVegaWalletTransaction();
|
||||
});
|
||||
|
||||
it('transfer fees tooltips', () => {
|
||||
// 1003-TRAN-015
|
||||
// 1003-TRAN-016
|
||||
// 1003-TRAN-017
|
||||
// 1003-TRAN-018
|
||||
// 1003-TRAN-019
|
||||
cy.getByTestId(transferForm);
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
|
||||
/// Check Include Transfer Fee tooltip
|
||||
cy.get('label[for="include-transfer-fee"] div').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Transfer Fee tooltip
|
||||
cy.contains('div', 'Transfer fee').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Amount to be transferred tooltip
|
||||
cy.contains('div', 'Amount to be transferred').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Total amount (with fee) tooltip
|
||||
cy.contains('div', 'Total amount (with fee)').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
|
||||
it('transfer fees', () => {
|
||||
// 1003-TRAN-020
|
||||
// 1003-TRAN-021
|
||||
// 1003-TRAN-022
|
||||
// 1003-TRAN-023
|
||||
cy.getByTestId(transferForm);
|
||||
cy.contains('Enter manually').click();
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
|
||||
cy.getByTestId(transferFee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.01');
|
||||
cy.getByTestId(transferAmount)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.00');
|
||||
cy.getByTestId(totalTransferfee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.01');
|
||||
cy.getByTestId(includeTransferFeeRadioBtn).click();
|
||||
cy.getByTestId(transferFee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.01');
|
||||
cy.getByTestId(transferAmount)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.99');
|
||||
cy.getByTestId(totalTransferfee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.00');
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'transfer form validation',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
});
|
||||
|
||||
it('transfer Text', () => {
|
||||
// 1003-TRAN-003
|
||||
cy.getByTestId(transferText)
|
||||
.should('exist')
|
||||
.get(keyID)
|
||||
.invoke('text')
|
||||
.should('match', /[\w.]{6}…[\w.]{6}/);
|
||||
});
|
||||
|
||||
it('invalid vega key validation', () => {
|
||||
//1003-TRAN-013
|
||||
//1003-TRAN-004
|
||||
cy.getByTestId(transferForm).should('be.visible');
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
||||
cy.contains('label', 'Vega key').should('be.visible');
|
||||
cy.contains('label', 'Asset').should('be.visible');
|
||||
cy.contains('label', 'Amount').should('be.visible');
|
||||
});
|
||||
|
||||
it('empty fields', () => {
|
||||
// 1003-TRAN-012
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||
cy.getByTestId(formFieldError).should('have.length', 3);
|
||||
});
|
||||
|
||||
it('min amount', () => {
|
||||
// 1002-WITH-010
|
||||
// 1003-TRAN-014
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.get(amountField).clear().type('0');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'Value is below minimum'
|
||||
);
|
||||
});
|
||||
|
||||
it('max amount', () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-011
|
||||
// 1003-TRAN-002
|
||||
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
||||
cy.get(amountField).clear().type('1001', { delay: 100 });
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'You cannot transfer more than your available collateral'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -5,205 +5,15 @@ const amountShortName = 'input[name="amount"] + div + span.text-xs';
|
||||
const assetSelection = 'select-asset';
|
||||
const assetBalance = 'asset-balance';
|
||||
const assetOption = 'rich-select-option';
|
||||
const transferText = 'transfer-intro-text';
|
||||
const errorText = 'input-error-text';
|
||||
const formFieldError = 'input-error-text';
|
||||
const includeTransferFeeRadioBtn = 'include-transfer-fee';
|
||||
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
||||
const manageVegaWallet = 'manage-vega-wallet';
|
||||
const openTransferButton = 'open-transfer';
|
||||
const submitTransferBtn = '[type="submit"]';
|
||||
const toAddressField = '[name="toAddress"]';
|
||||
const totalTransferfee = 'total-transfer-fee';
|
||||
const transferAmount = 'transfer-amount';
|
||||
const transferForm = 'transfer-form';
|
||||
const transferFee = 'transfer-fee';
|
||||
const walletTransfer = 'wallet-transfer';
|
||||
|
||||
const ASSET_EURO = 1;
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
|
||||
const collateralTab = 'Collateral';
|
||||
const toastCloseBtn = 'toast-close';
|
||||
const toastContent = 'toast-content';
|
||||
|
||||
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
cy.wait('@Assets');
|
||||
cy.wait('@Accounts');
|
||||
|
||||
cy.mockVegaWalletTransaction();
|
||||
|
||||
// Only click if not already active otherwise sidebar will close
|
||||
cy.get('[data-testid="sidebar-content"]').then(($sidebarContent) => {
|
||||
if ($sidebarContent.find('h2').text() !== 'Transfer') {
|
||||
cy.get('[data-testid="sidebar"] [data-testid="Transfer"]').click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('transfer fees tooltips', () => {
|
||||
// 1003-TRAN-015
|
||||
// 1003-TRAN-016
|
||||
// 1003-TRAN-017
|
||||
// 1003-TRAN-018
|
||||
// 1003-TRAN-019
|
||||
cy.getByTestId(transferForm);
|
||||
cy.contains('Enter manually').click();
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
|
||||
/// Check Include Transfer Fee tooltip
|
||||
cy.get('label[for="include-transfer-fee"] div').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Transfer Fee tooltip
|
||||
cy.contains('div', 'Transfer fee').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Amount to be transferred tooltip
|
||||
cy.contains('div', 'Amount to be transferred').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Total amount (with fee) tooltip
|
||||
cy.contains('div', 'Total amount (with fee)').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
|
||||
it('transfer fees', () => {
|
||||
// 1003-TRAN-020
|
||||
// 1003-TRAN-021
|
||||
// 1003-TRAN-022
|
||||
// 1003-TRAN-023
|
||||
cy.getByTestId(transferForm);
|
||||
cy.contains('Enter manually').click();
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
|
||||
cy.getByTestId(transferFee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.01');
|
||||
cy.getByTestId(transferAmount)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.00');
|
||||
cy.getByTestId(totalTransferfee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.01');
|
||||
cy.getByTestId(includeTransferFeeRadioBtn).click();
|
||||
cy.getByTestId(transferFee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.01');
|
||||
cy.getByTestId(transferAmount)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.99');
|
||||
cy.getByTestId(totalTransferfee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.00');
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'transfer form validation',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
});
|
||||
|
||||
it('transfer Text', () => {
|
||||
// 1003-TRAN-003
|
||||
cy.getByTestId(transferText)
|
||||
.should('exist')
|
||||
.get(keyID)
|
||||
.invoke('text')
|
||||
.should('match', /[\w.]{6}…[\w.]{6}/);
|
||||
});
|
||||
|
||||
it('invalid vega key validation', () => {
|
||||
//1003-TRAN-013
|
||||
//1003-TRAN-004
|
||||
cy.getByTestId(transferForm).should('be.visible');
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
||||
cy.contains('label', 'Vega key').should('be.visible');
|
||||
cy.contains('label', 'Asset').should('be.visible');
|
||||
cy.contains('label', 'Amount').should('be.visible');
|
||||
});
|
||||
|
||||
it('empty fields', () => {
|
||||
// 1003-TRAN-012
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||
cy.getByTestId(formFieldError).should('have.length', 3);
|
||||
});
|
||||
|
||||
it('min amount', () => {
|
||||
// 1002-WITH-010
|
||||
// 1003-TRAN-014
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.get(amountField).clear().type('0');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'Value is below minimum'
|
||||
);
|
||||
});
|
||||
|
||||
it('max amount', () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-011
|
||||
// 1003-TRAN-002
|
||||
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
||||
cy.get(amountField).clear().type('1001', { delay: 100 });
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'You cannot transfer more than your available collateral'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OrderType } from '@vegaprotocol/types';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
|
||||
const orderSizeField = 'order-size';
|
||||
@@ -9,7 +10,9 @@ export const createOrder = (order: OrderSubmission): void => {
|
||||
cy.log('Placing order', order);
|
||||
const { type, side, size, price, timeInForce, expiresAt } = order;
|
||||
|
||||
cy.getByTestId(`order-type-${type}`).click();
|
||||
cy.getByTestId(
|
||||
`order-type-${type === OrderType.TYPE_LIMIT ? 'Limit' : 'Market'}`
|
||||
).click();
|
||||
cy.getByTestId(`order-side-${side}`).click();
|
||||
cy.getByTestId(orderSizeField).clear().type(size);
|
||||
if (price) {
|
||||
|
||||
@@ -6,8 +6,8 @@ export const orderTIFDropDown = 'order-tif';
|
||||
export const placeOrderBtn = 'place-order';
|
||||
export const toggleShort = 'order-side-SIDE_SELL';
|
||||
export const toggleLong = 'order-side-SIDE_BUY';
|
||||
export const toggleLimit = 'order-type-TYPE_LIMIT';
|
||||
export const toggleMarket = 'order-type-TYPE_MARKET';
|
||||
export const toggleLimit = 'order-type-Limit';
|
||||
export const toggleMarket = 'order-type-Market';
|
||||
|
||||
export const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
|
||||
return {
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
successorMarketQuery,
|
||||
parentMarketIdQuery,
|
||||
successorMarketIdsQuery,
|
||||
successorMarketProposalDetailsQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
|
||||
@@ -186,6 +187,11 @@ const mockTradingPage = (
|
||||
aliasGQLQuery(req, 'SuccessorMarket', successorMarketQuery());
|
||||
aliasGQLQuery(req, 'ParentMarketId', parentMarketIdQuery());
|
||||
aliasGQLQuery(req, 'SuccessorMarketIds', successorMarketIdsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'SuccessorMarketProposalDetails',
|
||||
successorMarketProposalDetailsQuery()
|
||||
);
|
||||
};
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
|
||||
+1
-1
@@ -16,6 +16,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -18,6 +18,6 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -16,6 +16,6 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -18,6 +18,6 @@ NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -18,6 +18,6 @@ NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -16,6 +16,6 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -17,6 +17,6 @@ NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -17,6 +17,6 @@ NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
# NX_STOP_ORDERS
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
usePaneLayout,
|
||||
} from '../../components/resizable-grid';
|
||||
import { TradingViews } from './trade-views';
|
||||
import { MarketSuccessorBanner } from '../../components/market-banner';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
interface TradeGridProps {
|
||||
@@ -55,7 +58,11 @@ const MainGrid = memo(
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-left">
|
||||
<Tab id="chart" name={t('Chart')}>
|
||||
<Tab
|
||||
id="chart"
|
||||
name={t('Chart')}
|
||||
menu={<TradingViews.candles.menu />}
|
||||
>
|
||||
<TradingViews.candles.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="depth" name={t('Depth')}>
|
||||
@@ -127,6 +134,13 @@ const MainGrid = memo(
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
{FLAGS.STOP_ORDERS ? (
|
||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.stopOrders.component />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
) : null}
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.fills.component
|
||||
@@ -162,7 +176,12 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div>
|
||||
{FLAGS.SUCCESSOR_MARKETS && <MarketSuccessorBanner market={market} />}
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<>
|
||||
<MarketSuccessorBanner market={market} />
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div className="min-h-0 p-0.5">
|
||||
|
||||
@@ -12,7 +12,10 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { NO_MARKET } from './constants';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import classNames from 'classnames';
|
||||
import { MarketSuccessorBanner } from '../../components/market-banner';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
interface TradePanelsProps {
|
||||
@@ -34,6 +37,7 @@ export const TradePanels = ({
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
|
||||
const renderView = () => {
|
||||
const Component = memo<{
|
||||
marketId: string;
|
||||
@@ -62,12 +66,33 @@ export const TradePanels = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
const viewCfg = TradingViews[view];
|
||||
|
||||
if ('menu' in viewCfg) {
|
||||
const Menu = viewCfg.menu;
|
||||
return (
|
||||
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
<Menu />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[min-content_1fr_min-content]">
|
||||
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||
<div>
|
||||
{FLAGS.SUCCESSOR_MARKETS && <MarketSuccessorBanner market={market} />}
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<>
|
||||
<MarketSuccessorBanner market={market} />
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div>{renderMenu()}</div>
|
||||
<div className="h-full">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
@@ -80,9 +105,12 @@ export const TradePanels = ({
|
||||
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
||||
{Object.keys(TradingViews).map((key) => {
|
||||
const isActive = view === key;
|
||||
const className = classNames('p-4 min-w-[100px] capitalize', {
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
|
||||
});
|
||||
const className = classNames(
|
||||
'py-2 px-4 min-w-[100px] capitalize text-sm',
|
||||
{
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<button
|
||||
data-testid={key}
|
||||
|
||||
@@ -2,7 +2,10 @@ import type { ComponentProps } from 'react';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
import {
|
||||
CandlesChartContainer,
|
||||
CandlesMenu,
|
||||
} from '@vegaprotocol/candles-chart';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { NO_MARKET } from './constants';
|
||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||
@@ -12,6 +15,7 @@ import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import type { OrderContainerProps } from '../../components/orders-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||
|
||||
type MarketDependantView =
|
||||
| typeof CandlesChartContainer
|
||||
@@ -34,6 +38,7 @@ export const TradingViews = {
|
||||
candles: {
|
||||
label: 'Candles',
|
||||
component: requiresMarket(CandlesChartContainer),
|
||||
menu: CandlesMenu,
|
||||
},
|
||||
depth: {
|
||||
label: 'Depth',
|
||||
@@ -74,6 +79,10 @@ export const TradingViews = {
|
||||
label: 'All',
|
||||
component: OrdersContainer,
|
||||
},
|
||||
stopOrders: {
|
||||
label: 'Stop',
|
||||
component: StopOrdersContainer,
|
||||
},
|
||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||
fills: { label: 'Fills', component: FillsContainer },
|
||||
};
|
||||
|
||||
@@ -10,15 +10,18 @@ import type {
|
||||
OracleSpecDataConnectionQuery,
|
||||
MarketsDataQuery,
|
||||
MarketsQuery,
|
||||
SuccessorMarketIdsQuery,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
OracleSpecDataConnectionDocument,
|
||||
MarketsDataDocument,
|
||||
MarketsDocument,
|
||||
SuccessorMarketIdsDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
createMarketFragment,
|
||||
marketsQuery,
|
||||
@@ -46,10 +49,16 @@ jest.mock('@vegaprotocol/markets', () => ({
|
||||
: { data: undefined },
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
FLAGS: { SUCCESSOR_MARKETS: true } as Partial<FeatureFlags>,
|
||||
}));
|
||||
jest.mock('@vegaprotocol/environment', () => {
|
||||
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||
return {
|
||||
...actual,
|
||||
FLAGS: {
|
||||
...actual.FLAGS,
|
||||
SUCCESSOR_MARKETS: true,
|
||||
} as FeatureFlags,
|
||||
};
|
||||
});
|
||||
|
||||
describe('Closed', () => {
|
||||
let originalNow: typeof Date.now;
|
||||
@@ -349,8 +358,25 @@ describe('Closed', () => {
|
||||
state: MarketState.STATE_SETTLED,
|
||||
}),
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: {
|
||||
...createMarketFragment({
|
||||
id: 'successorMarketID',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
}),
|
||||
tradableInstrument: {
|
||||
...createMarketFragment().tradableInstrument,
|
||||
instrument: {
|
||||
...createMarketFragment().tradableInstrument.instrument,
|
||||
id: 'successorAssset',
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
@@ -364,11 +390,36 @@ describe('Closed', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'include-0',
|
||||
successorMarketID: 'successorMarketID',
|
||||
parentMarketID: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[mixedMarketsMock, marketsDataMock, oracleDataMock]}
|
||||
mocks={[
|
||||
mixedMarketsMock,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
successorMarketsMock,
|
||||
]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
@@ -384,5 +435,107 @@ describe('Closed', () => {
|
||||
screen.getByRole('button', { name: 'SuccessorCode' })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('feature flag should hide successors', async () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||
|
||||
const mixedMarkets = [
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: createMarketFragment({
|
||||
id: 'include-0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
}),
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: {
|
||||
...createMarketFragment({
|
||||
id: 'successorMarketID',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
}),
|
||||
tradableInstrument: {
|
||||
...createMarketFragment().tradableInstrument,
|
||||
instrument: {
|
||||
...createMarketFragment().tradableInstrument.instrument,
|
||||
id: 'successorAssset',
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: mixedMarkets,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'include-0',
|
||||
successorMarketID: 'successorMarketID',
|
||||
parentMarketID: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
mixedMarketsMock,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
successorMarketsMock,
|
||||
]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Closed />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'settlementDate',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getAllByRole('columnheader').forEach((element) => {
|
||||
expect(element.getAttribute('col-id')).not.toEqual('successorMarket');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,11 +4,7 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
COL_DEFS,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
@@ -23,15 +19,14 @@ import type {
|
||||
import {
|
||||
MarketActionsDropdown,
|
||||
closedMarketsWithDataProvider,
|
||||
useSuccessorMarket,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { SettlementDateCell } from './settlement-date-cell';
|
||||
import { SettlementPriceCell } from './settlement-price-cell';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
type SettlementAsset =
|
||||
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||
@@ -106,22 +101,6 @@ export const Closed = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const SuccessorMarketRenderer = ({
|
||||
value,
|
||||
}: VegaICellRendererParams<Row, 'id'>) => {
|
||||
const { data } = useSuccessorMarket(value);
|
||||
const onMarketClick = useMarketClickHandler();
|
||||
return data ? (
|
||||
<MarketNameCell
|
||||
value={data.tradableInstrument.instrument.code}
|
||||
data={data}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
) : (
|
||||
' - '
|
||||
);
|
||||
};
|
||||
|
||||
const ClosedMarketsDataGrid = ({
|
||||
rowData,
|
||||
error,
|
||||
@@ -202,8 +181,8 @@ const ClosedMarketsDataGrid = ({
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Successor market'),
|
||||
colId: 'successorMarketID',
|
||||
field: 'id',
|
||||
colId: 'successorMarket',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ export const MarketsPage = () => {
|
||||
<div className="h-full pt-0.5 pb-3 px-1.5">
|
||||
<div className="h-full my-1 border border-default rounded-sm">
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab id="all-markets" name={t('All markets')}>
|
||||
<Tab id="open-markets" name={t('Open markets')}>
|
||||
<Markets />
|
||||
</Tab>
|
||||
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { MarketsContainer } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Markets = () => {
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
return <MarketsContainer onSelect={handleOnSelect} />;
|
||||
return (
|
||||
<MarketsContainer
|
||||
onSelect={handleOnSelect}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Proposed = () => {
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
@@ -13,7 +14,7 @@ export const Proposed = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="h-[400px]">
|
||||
<ProposalsList />
|
||||
<ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />
|
||||
</div>
|
||||
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
|
||||
{t('Propose a new market')}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { MarketNameCell } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketProvider, useSuccessorMarketIds } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import React from 'react';
|
||||
|
||||
export const SuccessorMarketRenderer = ({
|
||||
value,
|
||||
parent,
|
||||
}: {
|
||||
value: string;
|
||||
parent?: boolean;
|
||||
}) => {
|
||||
const successors = useSuccessorMarketIds(value);
|
||||
const onMarketClick = useMarketClickHandler();
|
||||
|
||||
const lookupValue = successors
|
||||
? parent
|
||||
? successors.parentMarketID
|
||||
: successors.successorMarketID
|
||||
: '';
|
||||
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: marketProvider,
|
||||
variables: {
|
||||
marketId: lookupValue || '',
|
||||
},
|
||||
skip: !lookupValue,
|
||||
});
|
||||
|
||||
return data ? (
|
||||
<MarketNameCell
|
||||
value={data.tradableInstrument.instrument.code}
|
||||
data={data}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
};
|
||||
@@ -17,7 +17,7 @@ export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
||||
);
|
||||
return (
|
||||
<header className={headerClasses}>
|
||||
<div className="flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
||||
<div className="hidden lg:flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
||||
{title}
|
||||
</div>
|
||||
<div data-testid="header-summary" className="min-w-0">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const WalletIcon = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
width="26"
|
||||
height="18"
|
||||
viewBox="0 0 26 18"
|
||||
className={className}
|
||||
className={classNames('fill-current', className)}
|
||||
data-testid="wallet-icon"
|
||||
>
|
||||
<path d="M4.77437 17.7499H4.74987C3.6504 17.7368 2.77439 16.8489 2.77439 15.7772V12.8495V12.6343L2.5615 12.6023C1.59672 12.4575 0.849609 11.6116 0.849609 10.6266V7.40064C0.849609 6.39018 1.59509 5.56985 2.56147 5.4249L2.77439 5.39297V5.17767V2.24998C2.77439 1.14102 3.66537 0.25 4.77437 0.25H23.7501C24.8591 0.25 25.7501 1.14098 25.7501 2.24998V15.7499C25.7501 16.8588 24.8591 17.7499 23.7501 17.7499H4.77437ZM4.44917 12.5992H4.19917L4.77441 16.075V16.325H4.77466H23.7502C24.0778 16.325 24.3254 16.0777 24.3254 15.7497V2.24984C24.3254 1.9222 24.0782 1.6746 23.7502 1.6746H4.77441C4.44677 1.6746 4.19917 1.92182 4.19917 2.24984V5.12306V5.37306H4.44917H7.0244C8.51139 5.37306 9.67508 6.56094 9.67508 8.02374V9.94852C9.67508 11.4355 8.4872 12.5992 7.0244 12.5992H4.44917ZM2.84963 6.8253C2.52199 6.8253 2.27439 7.07253 2.27439 7.40054V10.6264C2.27439 10.9541 2.52161 11.2017 2.84962 11.2017L7.02419 11.2019C7.73619 11.2019 8.25009 10.6515 8.25009 9.97598V8.0512C8.25009 7.3392 7.69976 6.8253 7.0242 6.8253H2.84963Z" />
|
||||
|
||||
@@ -11,9 +11,9 @@ export const LayoutWithSidebar = () => {
|
||||
|
||||
const gridClasses = classNames(
|
||||
'h-full relative z-0 grid',
|
||||
'grid-rows-[min-content_1fr]',
|
||||
'grid-cols-[1fr_45px]',
|
||||
'lg:grid-cols-[1fr_350px_45px]'
|
||||
'grid-rows-[min-content_1fr_40px]',
|
||||
'lg:grid-rows-[min-content_1fr]',
|
||||
'lg:grid-cols-[1fr_350px_40px]'
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -40,7 +40,14 @@ export const LayoutWithSidebar = () => {
|
||||
>
|
||||
<SidebarContent />
|
||||
</aside>
|
||||
<div className="col-start-2 lg:col-start-3 bg-vega-clight-800 dark:bg-vega-cdark-800 border-l border-default">
|
||||
<div
|
||||
className={classNames(
|
||||
'bg-vega-clight-800 dark:bg-vega-cdark-800',
|
||||
'border-t lg:border-l lg:border-t-0 border-default',
|
||||
'row-start-3 col-start-1 cols-span-full',
|
||||
'lg:row-start-2 lg:row-span-full lg:col-start-3'
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,10 +12,9 @@ import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
@@ -26,8 +25,6 @@ export const LiquidityContainer = ({
|
||||
marketId: string | undefined;
|
||||
filter?: Filter;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
|
||||
const gridStore = useLiquidityStore((store) => store.gridStore);
|
||||
const updateGridStore = useLiquidityStore((store) => store.updateGridStore);
|
||||
|
||||
@@ -60,7 +57,6 @@ export const LiquidityContainer = ({
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LiquidityTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
|
||||
@@ -42,8 +42,6 @@ export const LiquidityHeader = () => {
|
||||
triggeringRatio,
|
||||
});
|
||||
|
||||
console.log(market);
|
||||
|
||||
return (
|
||||
<Header
|
||||
title={
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './market-successor-banner';
|
||||
export * from './market-successor-proposal-banner';
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import type { SingleExecutionResult } from '@apollo/client';
|
||||
import type { MockedResponse } from '@apollo/react-testing';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner';
|
||||
import type { SuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
import { SuccessorProposalsListDocument } from '@vegaprotocol/proposals';
|
||||
|
||||
const marketProposalMock: MockedResponse<SuccessorProposalsListQuery> = {
|
||||
request: {
|
||||
query: SuccessorProposalsListDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'Proposal',
|
||||
id: 'proposal-1',
|
||||
terms: {
|
||||
__typename: 'ProposalTerms',
|
||||
change: {
|
||||
__typename: 'NewMarket',
|
||||
instrument: {
|
||||
name: 'New proposal of the market successor',
|
||||
},
|
||||
successorConfiguration: {
|
||||
parentMarketId: 'marketId',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('MarketSuccessorProposalBanner', () => {
|
||||
it('should display single proposal', async () => {
|
||||
render(
|
||||
<MockedProvider mocks={[marketProposalMock]}>
|
||||
<MarketSuccessorProposalBanner marketId="marketId" />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('A successors to this market has been proposed')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen
|
||||
.getByRole('link')
|
||||
.getAttribute('href')
|
||||
?.endsWith('/proposals/proposal-1') ?? false
|
||||
).toBe(true);
|
||||
});
|
||||
it('should display plural proposals', async () => {
|
||||
const dualProposalMock = {
|
||||
...marketProposalMock,
|
||||
result: {
|
||||
...marketProposalMock.result,
|
||||
data: {
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
...((
|
||||
marketProposalMock?.result as SingleExecutionResult<SuccessorProposalsListQuery>
|
||||
)?.data?.proposalsConnection?.edges ?? []),
|
||||
{
|
||||
node: {
|
||||
__typename: 'Proposal',
|
||||
id: 'proposal-2',
|
||||
terms: {
|
||||
__typename: 'ProposalTerms',
|
||||
change: {
|
||||
__typename: 'NewMarket',
|
||||
instrument: {
|
||||
name: 'New second proposal of the market successor',
|
||||
},
|
||||
successorConfiguration: {
|
||||
parentMarketId: 'marketId',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<MockedProvider mocks={[dualProposalMock]}>
|
||||
<MarketSuccessorProposalBanner marketId="marketId" />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Successors to this market have been proposed')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen
|
||||
.getAllByRole('link')[0]
|
||||
.getAttribute('href')
|
||||
?.endsWith('/proposals/proposal-1') ?? false
|
||||
).toBe(true);
|
||||
expect(
|
||||
screen
|
||||
.getAllByRole('link')[1]
|
||||
.getAttribute('href')
|
||||
?.endsWith('/proposals/proposal-2') ?? false
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('banner should be hidden because no proposals', () => {
|
||||
const { container } = render(
|
||||
<MockedProvider>
|
||||
<MarketSuccessorProposalBanner marketId="marketId" />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('banner should be hidden because no proposals for the market', () => {
|
||||
const { container } = render(
|
||||
<MockedProvider mocks={[marketProposalMock]}>
|
||||
<MarketSuccessorProposalBanner marketId="otherMarketId" />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('banner should be hidden after user close click', async () => {
|
||||
const { container } = render(
|
||||
<MockedProvider mocks={[marketProposalMock]}>
|
||||
<MarketSuccessorProposalBanner marketId="marketId" />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('A successors to this market has been proposed')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
await act(() => {
|
||||
screen.getByTestId('notification-banner-close').click();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useState } from 'react';
|
||||
import type {
|
||||
SuccessorProposalListFieldsFragment,
|
||||
NewMarketSuccessorFieldsFragment,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useSuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
NotificationBanner,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||
|
||||
export const MarketSuccessorProposalBanner = ({
|
||||
marketId,
|
||||
}: {
|
||||
marketId?: string;
|
||||
}) => {
|
||||
const { data: proposals } = useSuccessorProposalsListQuery({
|
||||
skip: !marketId,
|
||||
});
|
||||
const successors =
|
||||
proposals?.proposalsConnection?.edges
|
||||
?.map((item) => item?.node as SuccessorProposalListFieldsFragment)
|
||||
.filter(
|
||||
(item: SuccessorProposalListFieldsFragment) =>
|
||||
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
||||
?.successorConfiguration?.parentMarketId === marketId
|
||||
) ?? [];
|
||||
const [visible, setVisible] = useState(true);
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
if (visible && successors.length) {
|
||||
return (
|
||||
<NotificationBanner
|
||||
intent={Intent.Primary}
|
||||
onClose={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<div className="uppercase mb-1">
|
||||
{successors.length === 1
|
||||
? t('A successors to this market has been proposed')
|
||||
: t('Successors to this market have been proposed')}
|
||||
</div>
|
||||
<div>
|
||||
{successors.length === 1
|
||||
? t('Check out the terms of the proposal and vote:')
|
||||
: t('Check out the terms of the proposals and vote:')}{' '}
|
||||
{successors.map((item, i) => {
|
||||
const externalLink = tokenLink(
|
||||
TOKEN_PROPOSAL.replace(':id', item.id || '')
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ExternalLink href={externalLink} key={i}>
|
||||
{
|
||||
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
||||
?.instrument.name
|
||||
}
|
||||
</ExternalLink>
|
||||
{i < successors.length - 1 && ', '}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</NotificationBanner>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -4,10 +4,12 @@ import { useParams } from 'react-router-dom';
|
||||
import { MarketSelector } from '../../components/market-selector/market-selector';
|
||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const MarketHeader = () => {
|
||||
const { marketId } = useParams();
|
||||
const { data } = useMarket(marketId);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
@@ -15,6 +17,8 @@ export const MarketHeader = () => {
|
||||
<Header
|
||||
title={
|
||||
<Popover
|
||||
open={open}
|
||||
onChange={setOpen}
|
||||
trigger={
|
||||
<HeaderTitle>
|
||||
{data.tradableInstrument.instrument.code}
|
||||
@@ -23,7 +27,10 @@ export const MarketHeader = () => {
|
||||
}
|
||||
alignOffset={-10}
|
||||
>
|
||||
<MarketSelector currentMarketId={marketId} />
|
||||
<MarketSelector
|
||||
currentMarketId={marketId}
|
||||
onSelect={() => setOpen(false)}
|
||||
/>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -98,6 +98,7 @@ describe('MarketSelectorItem', () => {
|
||||
market={market}
|
||||
currentMarketId={market.id}
|
||||
style={{}}
|
||||
onSelect={jest.fn()}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
|
||||
@@ -17,10 +17,12 @@ export const MarketSelectorItem = ({
|
||||
market,
|
||||
style,
|
||||
currentMarketId,
|
||||
onSelect,
|
||||
}: {
|
||||
market: MarketMaybeWithDataAndCandles;
|
||||
style: CSSProperties;
|
||||
currentMarketId?: string;
|
||||
onSelect: (marketId: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div style={style} role="row">
|
||||
@@ -32,6 +34,7 @@ export const MarketSelectorItem = ({
|
||||
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
||||
market.id === currentMarketId,
|
||||
})}
|
||||
onClick={() => onSelect(market.id)}
|
||||
>
|
||||
<MarketData market={market} />
|
||||
</Link>
|
||||
@@ -80,7 +83,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-2/5" role="gridcell">
|
||||
<h3 className="text-ellipsis whitespace-nowrap overflow-hidden">
|
||||
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
|
||||
{market.tradableInstrument.instrument.code}
|
||||
</h3>
|
||||
{mode && (
|
||||
@@ -90,7 +93,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
className="w-1/5 text-xs lg:text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
title={instrument.product.settlementAsset.symbol}
|
||||
data-testid="market-selector-price"
|
||||
role="gridcell"
|
||||
@@ -98,7 +101,7 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
{price} {instrument.product.settlementAsset.symbol}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
className="w-1/5 text-xs lg:text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
title={t('24h vol')}
|
||||
data-testid="market-selector-volume"
|
||||
role="gridcell"
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('MarketSelector', () => {
|
||||
it('renders only active markets', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(
|
||||
@@ -148,7 +148,7 @@ describe('MarketSelector', () => {
|
||||
it('filters by product type', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('MarketSelector', () => {
|
||||
it('filters by search term', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('MarketSelector', () => {
|
||||
it('filters by asset', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -234,7 +234,7 @@ describe('MarketSelector', () => {
|
||||
it('sorts by gained', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -256,7 +256,7 @@ describe('MarketSelector', () => {
|
||||
it('sorts by lost', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -272,7 +272,7 @@ describe('MarketSelector', () => {
|
||||
it('sorts by new', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export const MarketSelector = ({
|
||||
onSelect,
|
||||
}: {
|
||||
currentMarketId?: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect: (marketId: string) => void;
|
||||
}) => {
|
||||
const [filter, setFilter] = useState<Filter>({
|
||||
searchTerm: '',
|
||||
@@ -48,7 +48,7 @@ export const MarketSelector = ({
|
||||
|
||||
return (
|
||||
<div data-testid="market-selector">
|
||||
<div className="pt-2 px-2 mb-2 w-[320px] lg:w-[584px]">
|
||||
<div className="pt-2 px-2 mb-2">
|
||||
<ProductSelector
|
||||
product={filter.product}
|
||||
onSelect={(product) => {
|
||||
@@ -147,16 +147,17 @@ const MarketList = ({
|
||||
loading: boolean;
|
||||
searchTerm: string;
|
||||
currentMarketId?: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect: (marketId: string) => void;
|
||||
noItems: string;
|
||||
}) => {
|
||||
const itemSize = 45;
|
||||
const listRef = useRef<HTMLDivElement | null>(null);
|
||||
const rect = listRef.current?.getBoundingClientRect();
|
||||
// allow virtualized list to grow until it runs out of space
|
||||
const height = rect
|
||||
const computedHeight = rect
|
||||
? Math.min(data.length * itemSize, window.innerHeight - rect.y)
|
||||
: 400;
|
||||
const height = Math.max(computedHeight, 45);
|
||||
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
@@ -199,7 +200,7 @@ const MarketList = ({
|
||||
|
||||
interface ListItemData {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}
|
||||
|
||||
@@ -216,6 +217,7 @@ const ListItem = ({
|
||||
market={data.data[index]}
|
||||
currentMarketId={data.currentMarketId}
|
||||
style={style}
|
||||
onSelect={data.onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -252,7 +254,11 @@ const List = ({
|
||||
|
||||
if (!data.length) {
|
||||
return (
|
||||
<div style={{ height }} data-testid="no-items">
|
||||
<div
|
||||
style={{ height }}
|
||||
className="flex items-center"
|
||||
data-testid="no-items"
|
||||
>
|
||||
<div className="mx-4 my-2 text-sm">{noItems}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user