Compare commits

..
Author SHA1 Message Date
asiaznik d3f5c2fc32 fix(proposals): protocol upgrade notification block querying 2023-08-31 13:48:25 +02:00
937 changed files with 17533 additions and 31432 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Related issues 🔗 # Related issues 🔗
Closes #[Issue number here] Issue: #[Issue number here]
# Description # Description
@@ -82,38 +82,3 @@ jobs:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/ https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/ https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
ipfs://${{ env.IPFS_V0 }}/ ipfs://${{ env.IPFS_V0 }}/
- name: Ensure 'Released' label exists
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPO="${{ github.repository }}"
LABEL_EXIST=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$REPO/labels/Released")
if [[ "$LABEL_EXIST" == *"Not Found"* ]]; then
curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-X POST "https://api.github.com/repos/$REPO/labels" \
-d '{"name": "Released", "color": "FFFFFF"}'
fi
- name: Extract issues from release notes
id: extract-issues
run: |
ISSUES=$(echo "${{ github.event.release.body }}" | grep -o -E '#[0-9]+' | tr -d '#' | jq -R . | jq -cs .)
echo "Issues to label: $ISSUES"
echo "::set-output name=issue_numbers::$ISSUES"
- name: Add 'Released' label to issues
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ISSUE_NUMBERS="${{ steps.extract-issues.outputs.issue_numbers }}"
REPO="${{ github.repository }}"
for ISSUE in $(echo "$ISSUE_NUMBERS" | jq -r '.[]'); do
curl -s -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-X POST "https://api.github.com/repos/$REPO/issues/$ISSUE/labels" \
-d '{"labels": ["Released"]}'
done
+57 -130
View File
@@ -5,17 +5,15 @@ on:
branches: branches:
- release/* - release/*
- develop - develop
- main
pull_request: pull_request:
types: types:
- opened - opened
- ready_for_review - ready_for_review
- reopened - reopened
- edited
- synchronize - synchronize
jobs: jobs:
node-modules: node-modules:
# All jobs depend on node_modules, so none should run if the PR is in draft
if: github.event.pull_request.draft == false
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
name: 'Cache yarn modules' name: 'Cache yarn modules'
steps: steps:
@@ -23,6 +21,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Cache node modules - name: Cache node modules
id: cache id: cache
uses: actions/cache@v3 uses: actions/cache@v3
@@ -40,15 +39,23 @@ jobs:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions # https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn cache: yarn
- name: yarn install - name: yarn install
if: steps.cache.outputs.cache-hit != 'true' if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile run: yarn install --pure-lockfile
lint-format: lint-pr-title:
timeout-minutes: 20 needs: node-modules
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
lint-test-build:
timeout-minutes: 60
needs: node-modules needs: node-modules
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
name: '(CI) lint + format check' name: '(CI) lint + unit test + build'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -74,76 +81,6 @@ jobs:
with: with:
main-branch-name: develop main-branch-name: develop
- name: Check formatting
run: yarn nx format:check
- name: Lint affected
run: yarn nx affected:lint --max-warnings=0
- name: Build affected spec
run: yarn nx affected --target=build-spec
test-affected:
timeout-minutes: 30
needs: build-sources
runs-on: ubuntu-22.04
name: 'run unit test of affected apps'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: develop
- name: Test affected
run: yarn nx affected:test
build-sources:
timeout-minutes: 30
needs: lint-format
runs-on: ubuntu-22.04
name: 'Build sources of affected apps'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: develop
# See affected apps # See affected apps
- name: See affected apps - name: See affected apps
run: | run: |
@@ -160,8 +97,21 @@ jobs:
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}" echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}" echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
- name: Check formatting
run: yarn nx format:check
- name: Lint affected
run: yarn nx affected:lint --max-warnings=0
- name: Build affected spec
run: yarn nx affected --target=build-spec
- name: Test affected
run: yarn nx affected:test
- name: Build affected - name: Build affected
run: yarn nx affected:build || (yarn install && yarn nx affected:build) run: yarn nx affected:build || (yarn install && yarn nx affected:build)
outputs: outputs:
projects: ${{ env.PROJECTS }} projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }} projects-e2e: ${{ env.PROJECTS_E2E }}
@@ -170,62 +120,39 @@ jobs:
preview_explorer: ${{ env.PREVIEW_EXPLORER }} preview_explorer: ${{ env.PREVIEW_EXPLORER }}
preview_tools: ${{ env.PREVIEW_TOOLS }} preview_tools: ${{ env.PREVIEW_TOOLS }}
check-e2e-needed:
runs-on: ubuntu-latest
needs: build-sources
name: '(CI) check if e2e needed'
outputs:
run-tests: ${{ steps.check-test.outputs.e2e-needed }}
steps:
- name: Check branch
id: check-test
run: |
if [[ "${{ github.base_ref }}" == "develop" ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "main" ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/"* ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
else
echo "e2e-needed=false" >> $GITHUB_OUTPUT
fi
- name: Print result
run: |
echo "e2e-needed: ${{ steps.check-test.outputs.e2e-needed }}"
cypress:
needs: [build-sources, check-e2e-needed]
name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke'
console-e2e: console-e2e:
needs: [build-sources, check-e2e-needed] needs: lint-test-build
name: '(CI) console python' name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml uses: ./.github/workflows/console-test-run.yml
secrets: inherit secrets: inherit
if: needs.check-e2e-needed.outputs.run-tests == 'true' && contains(needs.build-sources.outputs.projects, 'trading') if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
with: with:
github-sha: ${{ github.event.pull_request.head.sha || github.sha }} github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
cypress:
needs: lint-test-build
name: '(CI) cypress'
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
tags: '@smoke'
publish-dist: publish-dist:
needs: build-sources needs: lint-test-build
name: '(CD) publish dist' name: '(CD) publish dist'
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }} if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }}
uses: ./.github/workflows/publish-dist.yml uses: ./.github/workflows/publish-dist.yml
secrets: inherit secrets: inherit
with: with:
projects: ${{ needs.build-sources.outputs.projects }} projects: ${{ needs.lint-test-build.outputs.projects }}
dist-check: dist-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- publish-dist - publish-dist
- build-sources - lint-test-build
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
timeout-minutes: 60 timeout-minutes: 60
name: '(CD) comment preview links' name: '(CD) comment preview links'
@@ -241,27 +168,27 @@ jobs:
run: | run: |
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url # https://stackoverflow.com/questions/3183444/check-for-valid-link-url
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]' regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
if [[ "${{ needs.build-sources.outputs.preview_governance }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_governance }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_governance }}"; do until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_governance }}"; do
echo "waiting for governance preview: ${{ needs.build-sources.outputs.preview_governance }}" echo "waiting for governance preview: ${{ needs.lint-test-build.outputs.preview_governance }}"
sleep 5 sleep 5
done done
fi fi
if [[ "${{ needs.build-sources.outputs.preview_explorer }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_explorer }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_explorer }}"; do until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_explorer }}"; do
echo "waiting for explorer preview: ${{ needs.build-sources.outputs.preview_explorer }}" echo "waiting for explorer preview: ${{ needs.lint-test-build.outputs.preview_explorer }}"
sleep 5 sleep 5
done done
fi fi
if [[ "${{ needs.build-sources.outputs.preview_trading }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_trading }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_trading }}"; do until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_trading }}"; do
echo "waiting for trading preview: ${{ needs.build-sources.outputs.preview_trading }}" echo "waiting for trading preview: ${{ needs.lint-test-build.outputs.preview_trading }}"
sleep 5 sleep 5
done done
fi fi
if [[ "${{ needs.build-sources.outputs.preview_tools }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_tools }}"; do until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
echo "waiting for tools preview: ${{ needs.build-sources.outputs.preview_tools }}" echo "waiting for tools preview: ${{ needs.lint-test-build.outputs.preview_tools }}"
sleep 5 sleep 5
done done
fi fi
@@ -273,10 +200,10 @@ jobs:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
body: | body: |
Previews Previews
* governance: ${{ needs.build-sources.outputs.preview_governance }} * governance: ${{ needs.lint-test-build.outputs.preview_governance }}
* explorer: ${{ needs.build-sources.outputs.preview_explorer }} * explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
* trading: ${{ needs.build-sources.outputs.preview_trading }} * trading: ${{ needs.lint-test-build.outputs.preview_trading }}
* tools: ${{ needs.build-sources.outputs.preview_tools }} * tools: ${{ needs.lint-test-build.outputs.preview_tools }}
# Report single result at the end, to avoid mess with required checks in PR # Report single result at the end, to avoid mess with required checks in PR
cypress-check: cypress-check:
-29
View File
@@ -1,29 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#managing-caches
name: cleanup caches by a branch
on:
pull_request:
types:
- closed
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
+51 -141
View File
@@ -6,37 +6,23 @@ on:
github-sha: github-sha:
required: true required: true
type: string type: string
workflow_dispatch:
inputs:
console-test-branch:
type: choice
description: 'main: v0.72.14, develop: v0.73.0-preview7'
options:
- main
- develop
jobs: jobs:
create-docker-image: run-tests:
name: Create docker image for console-test name: run-tests
runs-on: ubuntu-22.04 runs-on: 8-cores
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
#---------------------------------------------- #----------------------------------------------
# check-out frontend-monorepo # check-out frontend-monorepo
#---------------------------------------------- #----------------------------------------------
- name: Checkout frontend-monorepo - name: Checkout console test repo
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
ref: ${{ inputs.github-sha || github.sha }} ref: ${{ inputs.github-sha }}
#---------------------------------------------- #----------------------------------------------
# cache node modules # cache node modules
#---------------------------------------------- #----------------------------------------------
- name: setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: yarn
- name: Cache node modules - name: Cache node modules
id: cache id: cache
uses: actions/cache@v3 uses: actions/cache@v3
@@ -47,6 +33,15 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-cache-node-modules- ${{ 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 # install deps if cache missing
#---------------------------------------------- #----------------------------------------------
- name: yarn install - name: yarn install
@@ -55,105 +50,23 @@ jobs:
#---------------------------------------------- #----------------------------------------------
# build trading # build trading
#---------------------------------------------- #----------------------------------------------
- name: Build trading app - name: Build affected spec
run: | run: |
yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading
DIST_LOCATION=dist/apps/trading/exported
mv $DIST_LOCATION dist-result
tree dist-result
#---------------------------------------------- #----------------------------------------------
# export trading app docker image # run trading server
#---------------------------------------------- #----------------------------------------------
- name: Run trading server
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and export to local Docker
id: docker_build
uses: docker/build-push-action@v5
with:
context: .
file: docker/node-outside-docker.Dockerfile
load: true
build-args: |
APP=trading
ENV_NAME=stagnet1
tags: ci/trading:local
outputs: type=docker,dest=/tmp/console-image.tar
- name: Verify docker image created
run: | run: |
echo ${{ steps.docker_build.outputs.digest }} 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
echo ${{ steps.docker_build.outputs.imageid }} sleep 5
docker ps
- name: Upload docker image for console-test usage
uses: actions/upload-artifact@v3
with:
name: console-image
path: /tmp/console-image.tar
console-test-branch:
name: Choose console-test branch to run on
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
console-branch: ${{ steps.output-step.outputs.branch }}
steps:
- name: Workflow dispatch input
id: dispatch-step
if: github.event_name == 'workflow_dispatch'
run: echo "branch=${{ inputs.console-test-branch }}" >> $GITHUB_OUTPUT
- name: Print Workflow dispatch input
if: github.event_name == 'workflow_dispatch'
run: echo ${{ steps.dispatch-step.outputs.branch }}
- name: Workflow_call input
id: workflow_call-step
if: github.event_name != 'workflow_dispatch'
run: |
if [[ "${{ github.base_ref }}" == "main" ]]; then
echo "branch=main" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "develop" && "${{ github.ref_name }}" == "main" ]]; then
echo "branch=main" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/mainnet"* ]]; then
echo "branch=main" >> $GITHUB_OUTPUT
else
echo "branch=develop" >> $GITHUB_OUTPUT
fi
- name: Print Workflow_call input
if: github.event_name != 'workflow_dispatch'
run: echo ${{ steps.workflow_call-step.outputs.branch }}
- name: Set output
id: output-step
run: echo "branch=${{ steps.dispatch-step.outputs.branch || steps.workflow_call-step.outputs.branch }}" >> $GITHUB_OUTPUT
- name: Print final output
run: echo ${{ steps.output-step.outputs.branch }}
run-tests:
name: run-tests
runs-on: 8-cores
needs: [create-docker-image, console-test-branch]
timeout-minutes: 20
steps:
#---------------------------------------------- #----------------------------------------------
# load docker image # check if container persists between runs
#---------------------------------------------- #----------------------------------------------
- name: Download docker image from previous job - name: Check server
uses: actions/download-artifact@v3
with:
name: console-image
path: /tmp
- name: Load Docker image
run: | run: |
docker load --input /tmp/console-image.tar docker ps
docker image ls -a
#---------------------------------------------- #----------------------------------------------
# check-out tests repo # check-out tests repo
#---------------------------------------------- #----------------------------------------------
@@ -161,24 +74,15 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: vegaprotocol/console-test repository: vegaprotocol/console-test
ref: ${{ needs.console-test-branch.outputs.console-branch }} path: './console-test'
- name: Load console test envs
id: console-test-env
uses: falti/dotenv-action@v1.0.4
with:
path: '.env.${{ needs.console-test-branch.outputs.console-branch }}'
export-variables: true
keys-case: upper
log-variables: true
#---------------------------------------------- #----------------------------------------------
# ----- Setup python ----- # set-up python
#---------------------------------------------- #----------------------------------------------
- name: Setup Python - name: Set up python
id: setup-python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: '3.10' python-version: '3.10.11'
#---------------------------------------------- #----------------------------------------------
# ----- install & configure poetry ----- # ----- install & configure poetry -----
#---------------------------------------------- #----------------------------------------------
@@ -187,29 +91,45 @@ jobs:
with: with:
virtualenvs-create: true virtualenvs-create: true
virtualenvs-in-project: true virtualenvs-in-project: true
virtualenvs-path: .venv virtualenvs-path: console-test/.venv
#---------------------------------------------- #----------------------------------------------
# install python dependencies # 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 - name: Install dependencies
working-directory: ./console-test
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root run: poetry install --no-interaction --no-root
#---------------------------------------------- #----------------------------------------------
# install vega binaries # install vega binaries
#---------------------------------------------- #----------------------------------------------
- name: Install vega binaries - name: Install vega binaries
run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }} working-directory: ./console-test
run: poetry run python -m vega_sim.tools.load_binaries --force
#---------------------------------------------- #----------------------------------------------
# install playwright # install playwright
#---------------------------------------------- #----------------------------------------------
- name: install playwright - name: install playwright
run: poetry run playwright install --with-deps chromium run: poetry run playwright install
working-directory: ./console-test
#---------------------------------------------- #----------------------------------------------
# run tests # run tests
#---------------------------------------------- #----------------------------------------------
- name: Run tests - name: Run tests
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15 working-directory: ./console-test
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
ls -al console-test
#---------------------------------------------- #----------------------------------------------
# upload traces # upload traces
#---------------------------------------------- #----------------------------------------------
@@ -220,13 +140,3 @@ jobs:
name: playwright-trace name: playwright-trace
path: ./traces/ path: ./traces/
retention-days: 15 retention-days: 15
#----------------------------------------------
# ----- upload logs -----
#----------------------------------------------
- name: Upload worker logs
uses: actions/upload-artifact@v3
if: always()
with:
name: worker-logs
path: ./logs/
retention-days: 15
+2 -27
View File
@@ -13,35 +13,13 @@ on:
type: string type: string
jobs: jobs:
runner-choice:
runs-on: ubuntu-latest
outputs:
runner: ${{ steps.step.outputs.runner }}
steps:
- name: Check branch
id: step
run: |
if [[ "${{ github.base_ref }}" == "main" ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "develop" && "${{ github.ref_name }}" == "main" ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/mainnet"* ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
else
echo "runner=self-hosted-runner" >> $GITHUB_OUTPUT
fi
- name: Print runner
run: echo ${{ steps.step.outputs.runner }}
e2e: e2e:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
project: ${{ fromJSON(inputs.projects) }} project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }} name: ${{ matrix.project }}
needs: runner-choice runs-on: self-hosted-runner
runs-on: ${{ needs.runner-choice.outputs.runner }}
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
# Checks if skip cache was requested # Checks if skip cache was requested
@@ -59,7 +37,6 @@ jobs:
# Restore node_modules from cache if possible # Restore node_modules from cache if possible
- name: Restore node_modules from cache - name: Restore node_modules from cache
id: cache-node-modules
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: | path: |
@@ -69,7 +46,6 @@ jobs:
# Install frontend dependencies # Install frontend dependencies
- name: Install root dependencies - name: Install root dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
working-directory: frontend-monorepo working-directory: frontend-monorepo
@@ -85,14 +61,13 @@ jobs:
- name: Run Vegacapsule network and Vega wallet - name: Run Vegacapsule network and Vega wallet
id: setup-vega id: setup-vega
uses: ./frontend-monorepo/.github/actions/run-vegacapsule uses: ./frontend-monorepo/.github/actions/run-vegacapsule
timeout-minutes: 10
###### ######
## Run some tests ## Run some tests
###### ######
- name: Run Cypress tests - name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}" run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo working-directory: frontend-monorepo
env: env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }} CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
+11 -11
View File
@@ -2,12 +2,7 @@
name: Verify PR title name: Verify PR title
on: on:
pull_request: workflow_call:
types:
- opened
- edited
- reopened
- synchronize
jobs: jobs:
lint_pr: lint_pr:
@@ -16,16 +11,21 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node - name: Setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: Install dependencies - name: Cache node modules
run: | uses: actions/cache@v3
rm package.json with:
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Check PR title - name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+12 -20
View File
@@ -30,18 +30,12 @@ jobs:
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELEASE=false >> $GITHUB_ENV echo IS_S3_RELEASE=false >> $GITHUB_ENV
echo IS_DEV_IMAGE=false >> $GITHUB_ENV echo IS_DEV_IMAGE=false >> $GITHUB_ENV
echo IS_MAIN_IMAGE=false >> $GITHUB_ENV
- name: Is dev image - name: Is dev image
if: ${{ github.ref_name == 'develop' && github.event_name == 'push' && matrix.app == 'trading' }} if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
run: | run: |
echo IS_DEV_IMAGE=true >> $GITHUB_ENV echo IS_DEV_IMAGE=true >> $GITHUB_ENV
- name: Is main image
if: ${{ github.ref_name == 'main' && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
echo IS_MAIN_IMAGE=true >> $GITHUB_ENV
- name: Is PR - name: Is PR
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
run: | run: |
@@ -63,7 +57,7 @@ jobs:
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release - name: Is S3 Release
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' && github.ref_name != 'main'}} if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' }}
run: | run: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV echo IS_S3_RELEASE=true >> $GITHUB_ENV
@@ -87,7 +81,7 @@ jobs:
- name: Log in to the Container registry (docker hub) - name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2 uses: docker/login-action@v2
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }} if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -161,10 +155,10 @@ jobs:
- name: Sanity check docker image - name: Sanity check docker image
run: | run: |
echo "Check ipfs-hash" echo "Check ipfs-hash"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'cat /ipfs-hash' docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matrix.app }}-ipfs-hash
echo "List html directory" echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'apk add --update tree; tree /usr/share/nginx/html' docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
- name: Publish dist as docker image (ghcr) - name: Publish dist as docker image (ghcr)
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
@@ -185,7 +179,7 @@ jobs:
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
continue-on-error: true continue-on-error: true
id: dockerhub-push id: dockerhub-push
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }} if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with: with:
context: . context: .
file: docker/node-outside-docker.Dockerfile file: docker/node-outside-docker.Dockerfile
@@ -195,7 +189,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }} vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }} 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) - name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
@@ -222,7 +216,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }} vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }} vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
# bucket creation in github.com/vegaprotocol/terraform//frontend # bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3 - name: Publish dist to s3
@@ -335,9 +329,7 @@ jobs:
fi fi
# create commit # create commit
if ! git diff --cached --exit-code; then commit_msg="Automated hash update from ${{ github.ref }}"
commit_msg="Automated hash update from ${{ github.ref }}" git commit -m "$commit_msg"
git commit -m "$commit_msg" git push -u origin "main"
git push -u origin "main"
fi
) )
-1
View File
@@ -52,4 +52,3 @@ cypress.env.json
/apps/**/cypress/reports/ /apps/**/cypress/reports/
/apps/**/cypress/downloads/ /apps/**/cypress/downloads/
/apps/**/fixtures/wallet/node** /apps/**/fixtures/wallet/node**
.nx/
+3
View File
@@ -3,3 +3,6 @@
# Lint commit messages to ensure they follow conventional commit standards # Lint commit messages to ensure they follow conventional commit standards
yarn commitlint --edit "${1}" yarn commitlint --edit "${1}"
# Lint all staged files
yarn lint-staged
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint all staged files
yarn lint-staged
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint all staged files
yarn nx format:check
# Test all projects with changes
yarn nx affected -t test --exclude trading
+1 -1
View File
@@ -1 +1 @@
16.20.2 16.15.1
+1 -1
View File
@@ -11,7 +11,7 @@ recalculate-ipfs:
echo "ipfs hash inside the image" echo "ipfs hash inside the image"
docker run --rm ${TAG} cat /ipfs-hash docker run --rm ${TAG} cat /ipfs-hash
echo "recalculating ipfs hash" echo "recalculating ipfs hash"
docker run --rm ${TAG} ipfs add -rQ /usr/share/nginx/html docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html
.PHONY: eject-ipfs-hash .PHONY: eject-ipfs-hash
unpack: unpack:
+25 -39
View File
@@ -113,47 +113,13 @@ In order to run a container on port 3000:
docker run -p 3000:80 [TAG] docker run -p 3000:80 [TAG]
``` ```
On top of that there are two possible scenarios for running docker image - using nginx server (default) of ipfs daemon.
to run ipfs on port 3000:
```bash
docker run -p 3000:80 [TAG] /run-ipfs.sh
```
to run nginx on port 3000:
```bash
docker run -p 3000:80 [TAG]
```
## Build instructions ## Build instructions
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of Console either for the web, or ready for pinning on IPFS. The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of console either for the web, or ready for pinning on IPFS
### nx build inside the docker
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
At the moment this feature is important only for Console releases.
Each docker build finishes with hash calculation for ` dist`` directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
```bash
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
```
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
### nx build outside the docker ### nx build outside the docker
This Docker image packages a pre-built `dist` folder into an [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image. In this case, the application on docker host machine from source. Packaging prepared dist into [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image involves building the application on docker host machine from source.
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command: As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
@@ -164,21 +130,41 @@ As a prerequisite you need to perform build of `dist` directory and move its con
You can build any of the containers locally with the following command: You can build any of the containers locally with the following command:
```bash ```bash
docker build -f docker/node-outside-docker.Dockerfile . --tag=[TAG] docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
``` ```
### nx build inside the docker
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build) example.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=$(cat .nvmrc) --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
At the moment this feature is important only for `trading` (console) releases.
Each docker build finishes with hash calculation for dist directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
```bash
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
```
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
### Verifying ipfs-hash of existing current application version ### Verifying ipfs-hash of existing current application version
An IPFS CID will be attached to every [release](https://github.com/vegaprotocol/frontend-monorepo/releases). If you are intending to pin an application on IPFS, you can check that your build matches by running the following steps: An IPFS CID will be attached to every [release](https://github.com/vegaprotocol/frontend-monorepo/releases). If you are intending to pin an application on IPFS, you can check that your build matches by running the following steps:
1. Show latest release by running: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading) 1. Show latest release by runnning: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX` 2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
3. Set TAG environment variable to image that you want to validate: `export TAG=vegaprotocol/trading:$RELEASE` 3. Set TAG environment variable to image that you want to validate: `export TAG=vegaprotocol/trading:$RELEASE`
4. Download docker image with the desired release `docker pull $TAG`. 4. Download docker image with the desired release `docker pull $TAG`.
5. Recalculate hash: `make recalculate-ipfs` 5. Recalculate hash: `make recalculate-ipfs`
6. You should see exactly same hash produced by ipfs command as one placed with the release notes: `make show-latest-release` 6. You should see exactly same hash produced by ipfs command as one placed with the release notes: `make show-latest-release`
7. If you want to extract dist from docker image to your local filesystem you can run following command: `make unpack` 7. If you want to extract dist from docker image to your local filesystem you can run following command: `make unpack`
8. Now `dist` directory contains valid application build 8. Now `dist` directory contains valid application build. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
## Config ## Config
-2
View File
@@ -4,8 +4,6 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_ENV=DEVNET NX_VEGA_ENV=DEVNET
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# App flags # App flags
NX_EXPLORER_ASSETS=1 NX_EXPLORER_ASSETS=1
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocke
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# App flags # App flags
NX_EXPLORER_ASSETS=1 NX_EXPLORER_ASSETS=1
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://lb.testnet.vega.xyz/tm/websocket
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET NX_VEGA_ENV=TESTNET
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# App flags # App flags
NX_EXPLORER_ASSETS=1 NX_EXPLORER_ASSETS=1
@@ -36,9 +36,10 @@ context('Market page', { tags: '@regression' }, function () {
it('Able to go to market details page', function () { it('Able to go to market details page', function () {
cy.navigate_to('markets'); cy.navigate_to('markets');
cy.contains('Test market 1').click(); cy.get_element_by_col_id('actions').eq(1).click();
cy.getByTestId(marketHeaders).should('have.text', 'Test market 1'); cy.getByTestId(marketHeaders).should('have.text', 'Test market 1');
cy.validate_element_from_table('Name', 'Test market 1'); cy.validate_element_from_table('Name', 'Test market 1');
cy.validate_element_from_table('Market ID', this.createdMarketId);
cy.validate_element_from_table('Trading Mode', 'Opening auction'); cy.validate_element_from_table('Trading Mode', 'Opening auction');
cy.validate_element_from_table('Market Decimal Places', '5'); cy.validate_element_from_table('Market Decimal Places', '5');
cy.validate_element_from_table('Position Decimal Places', '5'); cy.validate_element_from_table('Position Decimal Places', '5');
@@ -86,13 +87,15 @@ context('Market page', { tags: '@regression' }, function () {
// Liquidity // Liquidity
cy.validate_element_from_table('Target Stake', '0.00 fUSDC'); cy.validate_element_from_table('Target Stake', '0.00 fUSDC');
cy.validate_element_from_table('Supplied Stake', '0.00 fUSDC'); cy.validate_element_from_table('Supplied Stake', '0.00 fUSDC');
cy.validate_element_from_table('Market Value Proxy', '0.00 fUSDC');
// Liquidity price range // Liquidity price range
cy.validate_element_from_table( cy.validate_element_from_table(
'Liquidity Price Range', 'Liquidity Price Range',
'95.00% of mid price' '1,000.00% of mid price'
); );
cy.validate_element_from_table('Lowest Price', '0.00 fUSDC'); cy.validate_element_from_table('Lowest Price', '0.00 fUSDC');
cy.validate_element_from_table('Highest Price', '0.00 fUSDC'); cy.validate_element_from_table('Highest Price', '0.00 fUSDC');
cy.getByTestId('oracle-spec-links') cy.getByTestId('oracle-spec-links')
.should('have.attr', 'href') .should('have.attr', 'href')
.and( .and(
@@ -141,8 +144,11 @@ context('Market page', { tags: '@regression' }, function () {
.as('successorMarketId'); .as('successorMarketId');
cy.contains('Token test market').click(); cy.contains('Token test market').click();
cy.getByTestId(marketHeaders).should('have.text', 'Token test market'); cy.getByTestId(marketHeaders).should('have.text', 'Token test market');
cy.validate_proposal_change_type('Triggering Ratio', 'Added');
cy.validate_element_from_table('Triggering Ratio', '0.7'); cy.validate_element_from_table('Triggering Ratio', '0.7');
cy.validate_proposal_change_type('Time Window', 'Added');
cy.validate_element_from_table('Time Window', '3,600'); cy.validate_element_from_table('Time Window', '3,600');
cy.validate_proposal_change_type('Scaling Factor', 'Added');
cy.validate_element_from_table('Scaling Factor', '10'); cy.validate_element_from_table('Scaling Factor', '10');
cy.getByTestId(successionLineItem) cy.getByTestId(successionLineItem)
@@ -1,9 +1,12 @@
context('Network parameters page', { tags: '@smoke' }, function () { context('Network parameters page', { tags: '@smoke' }, function () {
before('navigate to network parameter page', function () { before('navigate to network parameter page', function () {
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat'); cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
cy.visit('/network-parameters');
}); });
describe('Verify elements on page', function () { describe('Verify elements on page', function () {
beforeEach(() => {
cy.visit('/network-parameters');
});
const networkParametersHeader = '[data-testid="network-param-header"]'; const networkParametersHeader = '[data-testid="network-param-header"]';
const tableRows = '[data-testid="key-value-table-row"]'; const tableRows = '[data-testid="key-value-table-row"]';
@@ -13,7 +16,6 @@ context('Network parameters page', { tags: '@smoke' }, function () {
.and('be.visible'); .and('be.visible');
}); });
// 0006-NETW-021
it('should list each of the network parameters available', function () { it('should list each of the network parameters available', function () {
cy.get_network_parameters().then((network_parameters) => { cy.get_network_parameters().then((network_parameters) => {
const numberOfNetworkParametersInSystem = const numberOfNetworkParametersInSystem =
@@ -196,19 +198,6 @@ context('Network parameters page', { tags: '@smoke' }, function () {
}); });
}); });
// 0006-NETW-022 0006-NETW-023
it('governance assets should be correctly grouped', function () {
cy.getByTestId('governance')
.should('exist')
.parent()
.should('have.attr', 'href', '/network-parameters#governance');
cy.get('[id="governance-proposal-asset"]')
.parent()
.within(() => {
cy.getByTestId('key-value-table-row').should('have.length', 8);
});
});
it('should be able to see network parameters - on mobile', function () { it('should be able to see network parameters - on mobile', function () {
cy.switchToMobile(); cy.switchToMobile();
cy.get_network_parameters().then((network_parameters) => { cy.get_network_parameters().then((network_parameters) => {
@@ -40,7 +40,7 @@ context.skip('Node switcher', { tags: '@regression' }, function () {
const errorTypeTxt = 'Error: invalid url'; const errorTypeTxt = 'Error: invalid url';
const nodeErrorTxt = 'fakeUrl is not a valid url.'; const nodeErrorTxt = 'fakeUrl is not a valid url.';
cy.getByTestId('node-url-custom').click({ force: true }); cy.getByTestId('node-url-custom').click();
cy.getByTestId(customNodeBtn).within(() => { cy.getByTestId(customNodeBtn).within(() => {
cy.get('input').clear().type('fakeUrl'); cy.get('input').clear().type('fakeUrl');
@@ -26,6 +26,7 @@ function getSuccessorTxBody(parentMarketId) {
positionDecimalPlaces: '5', positionDecimalPlaces: '5',
linearSlippageFactor: '0.001', linearSlippageFactor: '0.001',
quadraticSlippageFactor: '0', quadraticSlippageFactor: '0',
lpPriceRange: '10',
instrument: { instrument: {
name: 'Token test market', name: 'Token test market',
code: 'TEST.24h', code: 'TEST.24h',
@@ -129,12 +130,6 @@ function getSuccessorTxBody(parentMarketId) {
parentMarketId: parentMarketId, parentMarketId: parentMarketId,
insurancePoolFraction: '0.75', insurancePoolFraction: '0.75',
}, },
liquiditySlaParameters: {
priceRange: '0.95',
commitmentMinTimeFraction: '0.5',
performanceHysteresisEpochs: 2,
slaCompetitionFactor: '0.75',
},
}, },
}, },
closingTimestamp, closingTimestamp,
-1
View File
@@ -3,7 +3,6 @@ NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/ NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# App flags # App flags
NX_EXPLORER_TXS_LIST=0 NX_EXPLORER_TXS_LIST=0
-1
View File
@@ -8,7 +8,6 @@ NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=/ NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/ NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
-1
View File
@@ -9,7 +9,6 @@ NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/ NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
NX_VEGA_CONSOLE_URL=https://console.vega.xyz NX_VEGA_CONSOLE_URL=https://console.vega.xyz
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_TENDERMINT_URL=https://be.vega.community NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
-1
View File
@@ -9,7 +9,6 @@ NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/ NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
-1
View File
@@ -1,3 +1,2 @@
# .env is stagnet1, so there are no overrides required # .env is stagnet1, so there are no overrides required
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz","STAGNET1":"https://stagnet1.explorer.vega.xyz"}' NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz","STAGNET1":"https://stagnet1.explorer.vega.xyz"}'
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
-1
View File
@@ -9,7 +9,6 @@ NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz 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
-1
View File
@@ -11,7 +11,6 @@ NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_VEGA_GOVERNANCE_URL=https://governance.validators-testnet.vega.rocks NX_VEGA_GOVERNANCE_URL=https://governance.validators-testnet.vega.rocks
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/ NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks/ NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
+1 -2
View File
@@ -5,5 +5,4 @@ NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER= NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/ NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
+1 -1
View File
@@ -77,7 +77,7 @@
"executor": "nx:run-commands", "executor": "nx:run-commands",
"options": { "options": {
"commands": [ "commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.73.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types" "npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.72.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
] ]
} }
}, },
+2 -2
View File
@@ -1,6 +1,6 @@
import { import {
AppFailure,
NetworkLoader, NetworkLoader,
NodeFailure,
NodeGuard, NodeGuard,
NodeSwitcherDialog, NodeSwitcherDialog,
useEnvironment, useEnvironment,
@@ -31,7 +31,7 @@ function App() {
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}> <NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<NodeGuard <NodeGuard
skeleton={<div>{t('Loading')}</div>} skeleton={<div>{t('Loading')}</div>}
failure={<NodeFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />} failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
> >
<Suspense fallback={splashLoading}> <Suspense fallback={splashLoading}>
<RouterProvider router={router} fallbackElement={splashLoading} /> <RouterProvider router={router} fallbackElement={splashLoading} />
@@ -4,7 +4,7 @@ import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid'; import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react'; import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints'; import { BREAKPOINT_MD } from '../../config/breakpoints';
@@ -1,6 +1,5 @@
export type HashProps = { export type HashProps = {
text: string; text: string;
truncate?: boolean;
}; };
/** /**
@@ -8,16 +7,10 @@ export type HashProps = {
* are broken when they need to wrap. This will remove the need * are broken when they need to wrap. This will remove the need
* for a lot of the overflow scrolling that currently exists. * for a lot of the overflow scrolling that currently exists.
*/ */
const Hash = ({ text, truncate = false }: HashProps) => { const Hash = ({ text }: HashProps) => {
const h = truncate ? text.slice(0, 6) : text;
return ( return (
<code <code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
title={text} {text}
className="break-all font-mono"
style={{ wordWrap: 'break-word' }}
>
{h}
</code> </code>
); );
}; };
@@ -3,7 +3,6 @@ query ExplorerMarket($id: ID!) {
id id
decimalPlaces decimalPlaces
positionDecimalPlaces positionDecimalPlaces
state
tradableInstrument { tradableInstrument {
instrument { instrument {
name name
@@ -14,14 +13,9 @@ query ExplorerMarket($id: ID!) {
decimals decimals
} }
} }
... on Perpetual {
quoteName
settlementAsset {
decimals
}
}
} }
} }
} }
state
} }
} }
@@ -8,7 +8,7 @@ export type ExplorerMarketQueryVariables = Types.Exact<{
}>; }>;
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } | { __typename?: 'Perpetual', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } | { __typename?: 'Spot' } } } } | null }; export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
export const ExplorerMarketDocument = gql` export const ExplorerMarketDocument = gql`
@@ -17,7 +17,6 @@ export const ExplorerMarketDocument = gql`
id id
decimalPlaces decimalPlaces
positionDecimalPlaces positionDecimalPlaces
state
tradableInstrument { tradableInstrument {
instrument { instrument {
name name
@@ -28,15 +27,10 @@ export const ExplorerMarketDocument = gql`
decimals decimals
} }
} }
... on Perpetual {
quoteName
settlementAsset {
decimals
}
}
} }
} }
} }
state
} }
} }
`; `;
@@ -61,11 +61,7 @@ describe('Market link component', () => {
instrument: { instrument: {
name: 'test-label', name: 'test-label',
product: { product: {
__typename: 'Future',
quoteName: 'dai', quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -23,7 +23,6 @@ const MarketLink = ({
}: MarketLinkProps) => { }: MarketLinkProps) => {
const { data, error, loading } = useExplorerMarketQuery({ const { data, error, loading } = useExplorerMarketQuery({
variables: { id }, variables: { id },
fetchPolicy: 'cache-first',
}); });
let label = <span>{id}</span>; let label = <span>{id}</span>;
@@ -1,57 +0,0 @@
import OracleLink, { getStatusString } from './oracle-link';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getStatusString', () => {
it("returns 'Unknown' for undefined status", () => {
expect(getStatusString(undefined)).toBe('Unknown');
});
it('returns the correct string for a known status', () => {
expect(getStatusString('STATUS_ACTIVE')).toBe('Active');
expect(getStatusString('STATUS_DEACTIVATED')).toBe('Deactivated');
});
});
describe('OracleLink', () => {
it('renders the truncated Oracle ID', () => {
const id = '123456789';
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} />
</MemoryRouter>
</MockedProvider>
);
const idElement = screen.getByText(id.slice(0, 6));
expect(idElement).toBeInTheDocument();
expect(idElement).toHaveAttribute('title', id);
});
it('renders the Oracle status', () => {
const id = '123';
const status = 'STATUS_ACTIVE';
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} status={status} data-testid="link" />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('link')).toHaveAttribute('data-status', 'Active');
});
it('renders the Oracle data indicator', () => {
const id = '123';
const hasSeenOracleReports = true;
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} hasSeenOracleReports={hasSeenOracleReports} />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('oracle-data-indicator')).toBeInTheDocument();
});
});
@@ -3,93 +3,20 @@ import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react'; import type { ComponentProps } from 'react';
import Hash from '../hash'; import Hash from '../hash';
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import {
DataSourceSpecStatus,
DataSourceSpecStatusMapping,
} from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
/**
* Returns a human-readable string for the given status, or a meaningful
* default if the status is unrecognised
* @param status string status
*/
export function getStatusString(status: string | undefined): string {
if (status && status in DataSourceSpecStatus) {
return DataSourceSpecStatusMapping[status as DataSourceSpecStatus];
}
return t('Unknown');
}
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & { export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
// The Oracle ID
id: string; id: string;
// If available, the oracle status
status?: string;
// If the oracle has corresponding data in the OracleDataConnection
hasSeenOracleReports?: boolean;
}; };
/** const OracleLink = ({ id, ...props }: OracleLinkProps) => {
* Given an Oracle ID, renders a data-dense link to the Oracle page. Data density is achieved by:
* - Colour coding the link based on the Oracle's status
* - Showing a small indicator if the Oracle has matched data
* - Showing a tooltip with the Oracle's status and whether it has matched data
*
* @returns
*/
export const OracleLink = ({
id,
status,
hasSeenOracleReports = false,
...props
}: OracleLinkProps) => {
const bgColour =
status === 'STATUS_ACTIVE'
? 'bg-yellow-100 hover:bg-yellow-200 border-yellow-200 dark:bg-yellow-200 dark:border-yellow-200 dark:text-gray-900 dark:border-yellow-300'
: 'bg-gray-100 hover:bg-gray-200 border-gray-200';
const indicatorColour =
status === 'STATUS_ACTIVE'
? 'bg-yellow-300 hover:bg-yellow-500 dark:bg-yellow-500'
: 'bg-gray-300 hover:bg-gray-500';
const description = (
<div>
<p>
<strong>{`Status: `}</strong>
{getStatusString(status)}
</p>
<p>
<strong>{`Matched data: `}</strong>
{hasSeenOracleReports ? (
<VegaIcon name={VegaIconNames.TICK} />
) : (
<VegaIcon name={VegaIconNames.CROSS} />
)}
</p>
</div>
);
return ( return (
<Tooltip description={description}> <Link
<Link className="underline font-mono"
className={`pl-2 pr-2 font-mono dark:text-black ${bgColour} rounded-sm border-solid border-2 relative`} {...props}
{...props} to={`/${Routes.ORACLES}/${id}`}
to={`/${Routes.ORACLES}/${id}`} >
data-status={getStatusString(status)} <Hash text={id} />
> </Link>
<Hash text={id} truncate={true} />
{hasSeenOracleReports ? (
<strong
data-testid="oracle-data-indicator"
className={`absolute top-0 right-0 w-1 h-full font-thin ${indicatorColour}`}
title="Oracle has matched data"
></strong>
) : null}
</Link>
</Tooltip>
); );
}; };
@@ -1,41 +1,37 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets'; import type { MarketInfoWithData } from '@vegaprotocol/markets';
import { import {
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
PriceMonitoringBoundsInfoPanel, PriceMonitoringBoundsInfoPanel,
SuccessionLineInfoPanel, SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData,
getDataSourceSpecForTradingTermination,
} from '@vegaprotocol/markets'; } from '@vegaprotocol/markets';
import { import {
LiquidityInfoPanel, LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel, LiquidityMonitoringParametersInfoPanel,
InstrumentInfoPanel, InstrumentInfoPanel,
KeyDetailsInfoPanel, KeyDetailsInfoPanel,
LiquidityPriceRangeInfoPanel,
MetadataInfoPanel, MetadataInfoPanel,
OracleInfoPanel, OracleInfoPanel,
RiskFactorsInfoPanel, RiskFactorsInfoPanel,
RiskModelInfoPanel, RiskModelInfoPanel,
RiskParametersInfoPanel,
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
} from '@vegaprotocol/markets'; } from '@vegaprotocol/markets';
import { MarketInfoTable } from '@vegaprotocol/markets'; import { MarketInfoTable } from '@vegaprotocol/markets';
import type { DataSourceFragment } from '@vegaprotocol/markets'; import type { DataSourceDefinition } from '@vegaprotocol/types';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => { export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
if (!market) return null; if (!market) return null;
const { product } = market.tradableInstrument.instrument;
const settlementDataSource = getDataSourceSpecForSettlementData(product);
const terminationDataSource = getDataSourceSpecForTradingTermination(product);
const getSigners = ({ data }: DataSourceFragment) => { const settlementData = market.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
const terminationData = market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') { if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
const signers = const signers = data.sourceType.sourceType.signers || [];
('signers' in data.sourceType.sourceType &&
data.sourceType.sourceType.signers) ||
[];
return signers.map(({ signer }, i) => { return signers.map(({ signer }, i) => {
return ( return (
@@ -47,13 +43,10 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
return []; return [];
}; };
const showTwoOracles = const showTwoOracles = isEqual(
settlementDataSource && getSigners(settlementData),
terminationDataSource && getSigners(terminationData)
isEqual( );
getSigners(settlementDataSource),
getSigners(terminationDataSource)
);
const headerClassName = 'font-alpha calt text-xl mt-4 border-b-2 pb-2'; const headerClassName = 'font-alpha calt text-xl mt-4 border-b-2 pb-2';
@@ -69,8 +62,8 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<MetadataInfoPanel market={market} /> <MetadataInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk model')}</h2> <h2 className={headerClassName}>{t('Risk model')}</h2>
<RiskModelInfoPanel market={market} /> <RiskModelInfoPanel market={market} />
<h2 className={headerClassName}>{t('Margin scaling factors')}</h2> <h2 className={headerClassName}>{t('Risk parameters')}</h2>
<MarginScalingFactorsPanel market={market} /> <RiskParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2> <h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} /> <RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => ( {(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
@@ -96,12 +89,10 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
)} )}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2> <h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} /> <LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity SLA protocol')}</h2>
<LiquiditySLAParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity')}</h2> <h2 className={headerClassName}>{t('Liquidity')}</h2>
<LiquidityInfoPanel market={market} /> <LiquidityInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
{showTwoOracles ? ( {showTwoOracles ? (
<> <>
<h2 className={headerClassName}>{t('Settlement oracle')}</h2> <h2 className={headerClassName}>{t('Settlement oracle')}</h2>
@@ -1,10 +1,10 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { getAsset, type MarketFieldsFragment } from '@vegaprotocol/markets'; import type { MarketFieldsFragment } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import type { ColDef } from 'ag-grid-community'; import type { ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
VegaValueGetterParams, VegaValueGetterParams,
@@ -73,7 +73,8 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
MarketFieldsFragment, MarketFieldsFragment,
'tradableInstrument.instrument.product.settlementAsset.symbol' 'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => { >) => {
const value = data && getAsset(data); const value =
data?.tradableInstrument.instrument.product.settlementAsset;
return value ? ( return value ? (
<ButtonLink <ButtonLink
onClick={(e) => { onClick={(e) => {
@@ -31,9 +31,6 @@ fragment ExplorerDeterministicOrderFields on Order {
... on Future { ... on Future {
quoteName quoteName
} }
... on Perpetual {
quoteName
}
} }
} }
} }
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } | { __typename?: 'Perpetual', quoteName: string } | { __typename?: 'Spot' } } } } }; export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID']; orderId: Types.Scalars['ID'];
@@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
}>; }>;
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } | { __typename?: 'Perpetual', quoteName: string } | { __typename?: 'Spot' } } } } } }; export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql` export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order { fragment ExplorerDeterministicOrderFields on Order {
@@ -47,9 +47,6 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
... on Future { ... on Future {
quoteName quoteName
} }
... on Perpetual {
quoteName
}
} }
} }
} }
@@ -74,9 +74,6 @@ function renderExistingAmend(
product: { product: {
__typename: 'Future', __typename: 'Future',
quoteName: '123', quoteName: '123',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -127,9 +124,6 @@ function renderExistingAmend(
product: { product: {
__typename: 'Future', __typename: 'Future',
quoteName: '123', quoteName: '123',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -156,11 +150,7 @@ function renderExistingAmend(
instrument: { instrument: {
name: 'test-label', name: 'test-label',
product: { product: {
__typename: 'Future',
quoteName: 'dai', quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
}, },
}, },
}, },
@@ -33,8 +33,6 @@ const PriceInMarket = ({
label = addDecimalsFormatNumber(price, data.market.decimalPlaces); label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
} else if ( } else if (
decimalSource === 'SETTLEMENT_ASSET' && decimalSource === 'SETTLEMENT_ASSET' &&
data.market &&
'settlementAsset' in data.market.tradableInstrument.instrument.product &&
data.market?.tradableInstrument.instrument.product.settlementAsset data.market?.tradableInstrument.instrument.product.settlementAsset
) { ) {
label = addDecimalsFormatNumber( label = addDecimalsFormatNumber(
@@ -2,7 +2,7 @@ import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals'; import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
VegaValueFormatterParams, VegaValueFormatterParams,
@@ -33,7 +33,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const { params } = useNetworkParams([ const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority, NetworkParams.governance_proposal_market_requiredMajority,
]); ]);
const tokenLink = useLinks(DApp.Governance); const tokenLink = useLinks(DApp.Token);
const requiredMajorityPercentage = useMemo(() => { const requiredMajorityPercentage = useMemo(() => {
const requiredMajority = const requiredMajority =
params?.governance_proposal_market_requiredMajority ?? 1; params?.governance_proposal_market_requiredMajority ?? 1;
@@ -105,7 +105,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
? new BigNumber(0) ? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted); : yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return ( return (
<div className="flex items-center justify-center h-full pt-2 uppercase"> <div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress <VoteProgress
threshold={requiredMajorityPercentage} threshold={requiredMajorityPercentage}
progress={yesPercentage} progress={yesPercentage}
@@ -22,9 +22,5 @@ export const TimeAgo = ({ date, ...props }: TimeAgoProps) => {
return <>{t('Date unknown')}</>; return <>{t('Date unknown')}</>;
} }
return ( return <span {...props}>{t(`${distanceToNow} ago`)}</span>;
<span {...props} title={date} className="underline decoration-dotted">
{t(`${distanceToNow} ago`)}
</span>
);
}; };
@@ -0,0 +1,6 @@
query ExplorerSettlementAssetForMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
}
}
@@ -0,0 +1,49 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerSettlementAssetForMarketQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerSettlementAssetForMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number } | null };
export const ExplorerSettlementAssetForMarketDocument = gql`
query ExplorerSettlementAssetForMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
}
}
`;
/**
* __useExplorerSettlementAssetForMarketQuery__
*
* To run a query within a React component, call `useExplorerSettlementAssetForMarketQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerSettlementAssetForMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerSettlementAssetForMarketQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerSettlementAssetForMarketQuery(baseOptions: Apollo.QueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
}
export function useExplorerSettlementAssetForMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
}
export type ExplorerSettlementAssetForMarketQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketQuery>;
export type ExplorerSettlementAssetForMarketLazyQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketLazyQuery>;
export type ExplorerSettlementAssetForMarketQueryResult = Apollo.QueryResult<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>;
@@ -0,0 +1,131 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { Side } from '@vegaprotocol/types';
import type { LiquidityOrder } from '@vegaprotocol/types';
import { PeggedReference } from '@vegaprotocol/types';
import { LiquidityProvisionDetailsRow } from './liquidity-provision-details-row';
import type { VegaSide } from './liquidity-provision-details-row';
describe('LiquidityProvisionDetails component', () => {
function renderComponent(
order: LiquidityOrder,
side: VegaSide,
normaliseProportionsTo: number,
marketId: string
) {
return render(
<MockedProvider>
<table>
<tbody data-testid="container">
<LiquidityProvisionDetailsRow
order={order}
marketId={marketId}
normaliseProportionsTo={normaliseProportionsTo}
side={side}
/>
</tbody>
</table>
</MockedProvider>
);
}
it('renders null for an order with no proportion', () => {
const mockOrder = {
offset: '1',
reference: PeggedReference.PEGGED_REFERENCE_MID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
expect(res.getByTestId('container')).toBeEmptyDOMElement();
});
it('renders null for a null order', () => {
const res = renderComponent(
null as unknown as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
expect(res.getByTestId('container')).toBeEmptyDOMElement();
});
it('renders a row when the order is as expected', () => {
const mockOrder = {
offset: '1',
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_MID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
// Row test ids and keys are based on the side, reference and proportion
expect(res.getByTestId('SIDE_BUY-20-1')).toBeInTheDocument();
expect(res.getByText('+1')).toBeInTheDocument();
expect(res.getByText('Mid')).toBeInTheDocument();
expect(res.getByText('20%')).toBeInTheDocument();
});
it('normalises offsets when normaliseToProportion is not 100', () => {
const mockOrder = {
offset: '1',
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('-1')).toBeInTheDocument();
expect(res.getByText('Best Bid')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
});
it('handles a missing offset gracefully (should not happen)', () => {
const mockOrder = {
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-')).toBeInTheDocument();
expect(res.getByText('-')).toBeInTheDocument();
});
it('handles a missing reference gracefully (should not happen)', () => {
const mockOrder = {
offset: '1',
proportion: 20,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
expect(res.getByText('-')).toBeInTheDocument();
});
});
@@ -0,0 +1,73 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../../types/explorer';
import { TableRow } from '../../../../table';
import { LiquidityProvisionOffset } from './liquidity-provision-offset';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export type VegaSide = components['schemas']['vegaSide'];
export type LiquidityProvisionOrder =
components['schemas']['vegaLiquidityOrder'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
export type LiquidityProvisionDetailsRowProps = {
order?: LiquidityProvisionOrder;
marketId?: string;
side: VegaSide;
// If this is
normaliseProportionsTo: number;
};
/**
*
* Note: offset is formatted by settlement asset on the market, assuming that is available
* Note: Due to the mix of references (MID vs BEST_X), it's not possible to correctly order
* the orders by their actual distance from a midpoint. This would require us knowing
* the best bid (now or at placement) and the mid. Getting the data for *now* would be
* misleading for LP submissions in the past. There is no API for getting <mid />
* at the time of a transaction.
*/
export function LiquidityProvisionDetailsRow({
normaliseProportionsTo,
order,
side,
marketId,
}: LiquidityProvisionDetailsRowProps) {
if (!order || !order.proportion) {
return null;
}
const proportion =
normaliseProportionsTo === 100
? order.proportion
: Math.round((order.proportion / normaliseProportionsTo) * 100);
const key = `${side}-${proportion}-${order.offset ? order.offset : ''}`;
return (
<TableRow modifier="bordered" key={key} data-testid={key}>
<td className="text-right px-2">
{order.offset && marketId ? (
<LiquidityProvisionOffset
offset={order.offset}
side={side}
marketId={marketId}
/>
) : (
'-'
)}
</td>
<td className="text-center">
{order.reference ? LiquidityReferenceLabel[order.reference] : '-'}
</td>
<td className="text-center">{proportion}%</td>
</TableRow>
);
}
@@ -0,0 +1,22 @@
import { render } from '@testing-library/react';
import { LiquidityProvisionMid } from './liquidity-provision-mid';
describe('LiquidityProvisionMid component', () => {
function renderComponent() {
return render(
<table>
<tbody data-testid="container">
<LiquidityProvisionMid />
</tbody>
</table>
);
}
it('renders a basic row that spans the whole table', () => {
const res = renderComponent();
const display = res.getByTestId('mid-display');
expect(res.getByTestId('mid')).toBeInTheDocument();
expect(display).toBeInTheDocument();
expect(display).toHaveAttribute('colspan', '3');
});
});
@@ -0,0 +1,17 @@
import { TableRow } from '../../../../table';
/**
* In a LiquidityProvision table, this row is the midpoint. Above our LP orders on the
* buy side, below are LP orders on the sell side. This component simply divides them.
*
* There is no API that can give us the mid price when the order was created, and even
* if there was it isn't clear that would be appropriate for this centre row. So instead
* it's a simple divider.
*/
export function LiquidityProvisionMid() {
return (
<TableRow modifier="bordered" data-testid="mid">
<td data-testid="mid-display" colSpan={3} className="bg-white"></td>
</TableRow>
);
}
@@ -0,0 +1,67 @@
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { ExplorerSettlementAssetForMarketDocument } from '../__generated__/Explorer-settlement-asset';
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import type { VegaSide } from './liquidity-provision-details-row';
import {
getFormattedOffset,
LiquidityProvisionOffset,
} from './liquidity-provision-offset';
const decimalsMock: ExplorerSettlementAssetForMarketQuery = {
market: {
id: '123',
__typename: 'Market',
decimalPlaces: 5,
},
};
describe('LiquidityProvisionOffset component', () => {
function renderComponent(
offset: string,
side: VegaSide,
marketId: string,
mocks: MockedResponse[]
) {
return render(
<MockedProvider mocks={mocks}>
<LiquidityProvisionOffset
offset={offset}
side={side}
marketId={marketId}
/>
</MockedProvider>
);
}
it('renders a simple row before market data comes in', () => {
const res = renderComponent('1', 'SIDE_BUY', '123', []);
expect(res.getByText('+1')).toBeInTheDocument();
});
it('replaces unformatted with formatted if the market data comes in', () => {
const mock = {
request: {
query: ExplorerSettlementAssetForMarketDocument,
variables: {
id: '123',
},
result: {
data: decimalsMock,
},
},
};
const res = renderComponent('1', 'SIDE_BUY', '123', [mock]);
expect(res.getByText('+1')).toBeInTheDocument();
});
it('getFormattedOffset returns the unformatted offset if there is not enough data', () => {
const res = getFormattedOffset('1', {});
expect(res).toEqual('1');
});
it('getFormattedOffset decimal formats a number if it comes in with market data', () => {
const res = getFormattedOffset('1', decimalsMock);
expect(res).toEqual('0.00001');
});
});
@@ -0,0 +1,59 @@
import { useExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import type { VegaSide } from './liquidity-provision-details-row';
export type LiquidityProvisionOffsetProps = {
side: VegaSide;
offset: string;
marketId: string;
};
/**
* Correctly formats an LP's offset according to the market settlement decimal places.
* Initially this will appear unformatted, then when the query loads in the proper formatted
* value will be displayed
*
* @see getFormattedOffset
*/
export function LiquidityProvisionOffset({
side,
offset,
marketId,
}: LiquidityProvisionOffsetProps) {
const { data } = useExplorerSettlementAssetForMarketQuery({
variables: {
id: marketId,
},
});
// getFormattedOffset handles missing results/loading states
const formattedOffset = getFormattedOffset(offset, data);
const label = side === 'SIDE_BUY' ? '+' : '-';
const className = side === 'SIDE_BUY' ? 'text-vega-green' : 'text-vega-pink';
return <span className={className}>{`${label}${formattedOffset}`}</span>;
}
/**
* Does the work of formatting the number now we have the market decimal places.
* If no market data is assigned (i.e. during loading, or if the market doesn't exist)
* this function will return the unformatted number
*
* @see LiquidityProvisionOffset
* @param data the result of a ExplorerSettlementAssetForMarketQuery
* @param offset the unformatted offset
* @returns string the offset of this lp order formatted with the settlement decimal places
*/
export function getFormattedOffset(
offset: string,
data?: ExplorerSettlementAssetForMarketQuery
) {
const decimals = data?.market?.decimalPlaces;
if (!decimals) {
return offset;
}
return addDecimalsFormatNumber(offset, decimals);
}
@@ -0,0 +1,209 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import type { LiquidityOrder } from '@vegaprotocol/types';
import { PeggedReference } from '@vegaprotocol/types';
import type { LiquiditySubmission } from '../tx-liquidity-submission';
import {
LiquidityProvisionDetails,
sumProportions,
} from './liquidity-provision-details';
function mockProportion(proportion: number): LiquidityOrder {
return {
proportion,
reference: PeggedReference.PEGGED_REFERENCE_MID,
offset: '1',
};
}
describe('sumProportions function', () => {
it('returns 0 if the side is undefined', () => {
const side: LiquidityOrder[] = undefined as unknown as LiquidityOrder[];
const res = sumProportions(side);
expect(res).toEqual(0);
});
it('returns 0 if the side is empty', () => {
const side: LiquidityOrder[] = [];
const res = sumProportions(side);
expect(res).toEqual(0);
});
it('sums 1 item correctly (under 100%)', () => {
const side: LiquidityOrder[] = [mockProportion(10)];
const res = sumProportions(side);
expect(res).toEqual(10);
});
it('sums 2 item correctly (exactly 100%)', () => {
const side: LiquidityOrder[] = [mockProportion(50), mockProportion(50)];
const res = sumProportions(side);
expect(res).toEqual(100);
});
it('sums 3 item correctly to over 100%', () => {
const side: LiquidityOrder[] = [
mockProportion(20),
mockProportion(40),
mockProportion(50),
];
const res = sumProportions(side);
expect(res).toEqual(110);
});
});
describe('LiquidityProvisionDetails component', () => {
function renderComponent(provision: LiquiditySubmission) {
return render(
<MockedProvider>
<LiquidityProvisionDetails provision={provision} />
</MockedProvider>
);
}
it('handles an LP with no buys or sells by returning empty (should never happen)', () => {
const mock: LiquiditySubmission = {};
const res = renderComponent(mock);
expect(res.container).toBeEmptyDOMElement();
});
it('handles an LP with no sells by just rendering buys', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
});
it('handles an LP with no buys by just rendering sells', () => {
const mock: LiquiditySubmission = {
marketId: '123',
sells: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
});
it('handles an LP with sells by just rendering buys', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
});
it('handles an LP with both sides', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
sells: [
{
offset: '4',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-4')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
});
it('normalises proportions when they do not total 100%', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 25,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 30,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('45%')).toBeInTheDocument();
expect(res.getByText('55%')).toBeInTheDocument();
});
});
@@ -0,0 +1,94 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import type { LiquiditySubmission } from '../tx-liquidity-submission';
import { TableRow } from '../../../table';
import { LiquidityProvisionMid } from './components/liquidity-provision-mid';
import { LiquidityProvisionDetailsRow } from './components/liquidity-provision-details-row';
import { Side } from '@vegaprotocol/types';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export type LiquidityProvisionOrder =
components['schemas']['vegaLiquidityOrder'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
/**
* Given a side of a liquidity provision order, returns the total
* It should be 100%, but it isn't always and if it isn't the proportion
* reported for each order should be scaled
*
* @returns number
*/
export function sumProportions(
side: LiquiditySubmission['buys'] | LiquiditySubmission['sells']
): number {
if (!side || side.length === 0) {
return 0;
}
return side.reduce((total, o) => total + (o.proportion || 0), 0);
}
export type LiquidityProvisionDetailsProps = {
provision: LiquiditySubmission;
};
/**
* Renders a table displaying all buys and sells in this LP. It is valid for there
* to be no buys or sells.
*
* It might seem logical to turn proportions in to values based on the total commitment
* but based on the current API structure it is awkward, and given that non-LP orders
* will change the amount that is actually deployed vs assigned to a level, we decided
* not to bother going down that route.
*/
export function LiquidityProvisionDetails({
provision,
}: LiquidityProvisionDetailsProps) {
if (!provision.buys?.length && !provision.sells?.length) {
return null;
}
// We need to do some additional calcs if these aren't both 100
const buyTotal = sumProportions(provision.buys);
const sellTotal = sumProportions(provision.sells);
return (
<table>
<thead>
<TableRow modifier="bordered">
<th className="px-2 pb-1">{t('Price offset')}</th>
<th className="px-2 pb-1">{t('Price reference')}</th>
<th className="px-2 pb-1">{t('Proportion')}</th>
</TableRow>
</thead>
<tbody>
{provision.buys?.map((b, i) => (
<LiquidityProvisionDetailsRow
order={b}
marketId={provision.marketId}
side={Side.SIDE_BUY}
key={`SIDE_BUY-${i}`}
normaliseProportionsTo={buyTotal}
/>
))}
<LiquidityProvisionMid />
{provision.sells?.map((s, i) => (
<LiquidityProvisionDetailsRow
order={s}
marketId={provision.marketId}
side={Side.SIDE_SELL}
key={`SIDE_SELL-${i}`}
normaliseProportionsTo={sellTotal}
/>
))}
</tbody>
</table>
);
}
@@ -1,20 +1,11 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import { TableCell, TableRow } from '../../../table'; import { TableCell, TableRow } from '../../../table';
import type { VegaPeggedReference } from '../liquidity-provision/liquidity-provision-details';
import { Side, PeggedReferenceMapping } from '@vegaprotocol/types'; import { Side, PeggedReferenceMapping } from '@vegaprotocol/types';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market'; import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market'; import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
export interface TxDetailsOrderProps { export interface TxDetailsOrderProps {
offset: string; offset: string;
reference: VegaPeggedReference; reference: VegaPeggedReference;
@@ -35,7 +35,7 @@ export const BundleSigners = ({
tx, tx,
id, id,
}: BundleSignersProps) => { }: BundleSignersProps) => {
const tokenLink = useLinks(DApp.Governance); const tokenLink = useLinks(DApp.Token);
const bridgeFunction: BridgeFunction = const bridgeFunction: BridgeFunction =
tx?.changes?.erc20 && 'contractAddress' in tx.changes.erc20 tx?.changes?.erc20 && 'contractAddress' in tx.changes.erc20
@@ -33,14 +33,6 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_HOLDING: 'Holding', ACCOUNT_TYPE_HOLDING: 'Holding',
ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution', ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution',
ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees', ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees',
ACCOUNT_TYPE_NETWORK_TREASURY: 'Network Treasury',
ACCOUNT_TYPE_VESTING_REWARDS: 'Vesting Rewards',
ACCOUNT_TYPE_VESTED_REWARDS: 'Vested Rewards',
ACCOUNT_TYPE_REWARD_AVERAGE_POSITION: 'Reward Average Position',
ACCOUNT_TYPE_REWARD_RELATIVE_RETURN: 'Reward Relative Return',
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility',
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking',
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending Fee Referral Reward',
}; };
interface TransferParticipantsProps { interface TransferParticipantsProps {
@@ -1,50 +1,10 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableWithTbody } from '../../table'; import { TableWithTbody } from '../../table';
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
import { ChainEvent } from './chain-events';
import { BigNumber } from 'ethers';
import type { AbiType } from '../../../lib/encoders/abis/abi-types';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { ChainEvent } from './chain-events';
interface AbiOutput {
type: AbiType;
internalType: AbiType;
name: string;
}
/**
* Decodes the b64/ABIcoded result from an eth cal
* @param data
* @returns
*/
export function decodeEthCallResult(
data: BlockExplorerTransactionResult
): string {
const ethResult = data.command.chainEvent?.contractCall.result;
try {
// Decode the result string: base64 => uint8array
const data = base64.decode(ethResult);
// Parse the escaped ABI in to an object
const abi = JSON.parse(
'[{"inputs":[],"name":"latestAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"}]'
);
// Pull the expected types out of the Oracles ABI
const types: AbiType[] = abi[0].outputs.map((o: AbiOutput) => o.type);
const rawResult = defaultAbiCoder.decode(types, data);
// Finally, convert the resulting BigNumber in to a string
const res = BigNumber.from(rawResult[0]).toString();
return res;
} catch (e) {
return '-';
}
}
interface TxDetailsChainEventProps { interface TxDetailsChainEventProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -16,6 +16,7 @@ import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission'; import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation'; import { TxDetailsDelegate } from './tx-delegation';
import { TxDetailsUndelegate } from './tx-undelegation'; import { TxDetailsUndelegate } from './tx-undelegation';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend'; import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel'; import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission'; import { TxDetailsDataSubmission } from './tx-data-submission';
@@ -27,7 +28,6 @@ import { TxDetailsStateVariable } from './tx-state-variable-proposal';
import { TxProposal } from './tx-proposal'; import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer'; import { TxDetailsTransfer } from './tx-transfer';
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission'; import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
interface TxDetailsWrapperProps { interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -107,7 +107,7 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsWithdrawSubmission; return TxDetailsWithdrawSubmission;
case 'Liquidity Provision Order': case 'Liquidity Provision Order':
return TxDetailsLiquiditySubmission; return TxDetailsLiquiditySubmission;
case 'Amend Liquidity Provision Order': case 'Amend LiquidityProvision Order':
return TxDetailsLiquidityAmendment; return TxDetailsLiquidityAmendment;
case 'Cancel LiquidityProvision Order': case 'Cancel LiquidityProvision Order':
return TxDetailsLiquidityCancellation; return TxDetailsLiquidityCancellation;
@@ -5,6 +5,7 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market'; import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
@@ -39,32 +40,40 @@ export const TxDetailsLiquidityAmendment = ({
: '-'; : '-';
return ( return (
<TableWithTbody className="mb-8" allowWrap={true}> <>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} /> <TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered"> <TxDetailsShared
<TableCell>{t('Market')}</TableCell> txData={txData}
<TableCell> pubKey={pubKey}
<MarketLink id={marketId} /> blockData={blockData}
</TableCell> />
</TableRow>
{amendment.commitmentAmount ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell> <TableCell>{t('Market')}</TableCell>
<TableCell> <TableCell>
<PriceInMarket <MarketLink id={marketId} />
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
) : null} {amendment.commitmentAmount ? (
{amendment.fee ? ( <TableRow modifier="bordered">
<TableRow modifier="bordered"> <TableCell>{t('Commitment amount')}</TableCell>
<TableCell>{t('Fee')}</TableCell> <TableCell>
<TableCell>{fee}%</TableCell> <PriceInMarket
</TableRow> price={amendment.commitmentAmount}
) : null} marketId={marketId}
</TableWithTbody> decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
) : null}
{amendment.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={amendment} />
</>
); );
}; };
@@ -0,0 +1,86 @@
import { render } from '@testing-library/react';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
describe('TxDetailsLiquiditySubmission', () => {
const mockTxData = {
hash: 'test',
command: {
liquidityProvisionSubmission: {
marketId: 'BTC-USD',
commitmentAmount: 100,
fee: '0.01',
},
},
};
const mockPubKey = '123';
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
it('should render the component with correct data', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(getByText('Market')).toBeInTheDocument();
expect(getByText('BTC-USD')).toBeInTheDocument();
expect(getByText('Commitment amount')).toBeInTheDocument();
expect(getByText('100')).toBeInTheDocument();
expect(getByText('Fee')).toBeInTheDocument();
expect(getByText('1%')).toBeInTheDocument();
});
it('should display awaiting message when tx data is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={undefined}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
it('should display awaiting message when liquidityProvisionSubmission is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={{ command: {} } as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
@@ -1,30 +1,31 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links'; import { MarketLink } from '../../links/';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market'; import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
export type LiquiditySubmission = export type LiquiditySubmission =
components['schemas']['v1LiquidityProvisionSubmission']; components['schemas']['v1LiquidityProvisionSubmission'];
interface TxDetailsLiquidityAmendmentProps { interface TxDetailsLiquiditySubmissionProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined; pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined; blockData: TendermintBlocksResponse | undefined;
} }
/** /**
* An existing liquidity order is being created. * Someone cancelled an order
*/ */
export const TxDetailsLiquiditySubmission = ({ export const TxDetailsLiquiditySubmission = ({
txData, txData,
pubKey, pubKey,
blockData, blockData,
}: TxDetailsLiquidityAmendmentProps) => { }: TxDetailsLiquiditySubmissionProps) => {
if (!txData || !txData.command.liquidityProvisionSubmission) { if (!txData || !txData.command.liquidityProvisionSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
@@ -38,38 +39,40 @@ export const TxDetailsLiquiditySubmission = ({
: '-'; : '-';
return ( return (
<TableWithTbody className="mb-8" allowWrap={true}> <>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} /> <TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered"> <TxDetailsShared
<TableCell>{t('Market')}</TableCell> txData={txData}
<TableCell> pubKey={pubKey}
<MarketLink id={marketId} /> blockData={blockData}
</TableCell> />
</TableRow>
{submission.commitmentAmount ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell> <TableCell>{t('Market')}</TableCell>
<TableCell> <TableCell>
<PriceInMarket <MarketLink id={marketId} />
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
) : null} {submission.commitmentAmount ? (
{submission.fee ? ( <TableRow modifier="bordered">
<TableRow modifier="bordered"> <TableCell>{t('Commitment amount')}</TableCell>
<TableCell>{t('Fee')}</TableCell> <TableCell>
<TableCell>{fee}%</TableCell> <PriceInMarket
</TableRow> price={submission.commitmentAmount}
) : null} marketId={marketId}
{submission.reference ? ( decimalSource="SETTLEMENT_ASSET"
<TableRow modifier="bordered"> />
<TableCell>{t('Reference')}</TableCell> </TableCell>
<TableCell>{submission.reference}</TableCell> </TableRow>
</TableRow> ) : null}
) : null} {submission.fee ? (
</TableWithTbody> <TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={submission} />
</>
); );
}; };
@@ -12,7 +12,6 @@ import {
SPECIAL_CASE_NETWORK, SPECIAL_CASE_NETWORK,
SPECIAL_CASE_NETWORK_ID, SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link'; } from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
type Transfer = components['schemas']['commandsv1Transfer']; type Transfer = components['schemas']['commandsv1Transfer'];
@@ -64,16 +63,10 @@ export const TxDetailsTransfer = ({
return ( return (
<> <>
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered" data-testid="type"> <TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell> <TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{getTypeLabelForTransfer(transfer)}</TableCell> <TableCell>{getTypeLabelForTransfer(transfer)}</TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared <TxDetailsShared
txData={txData} txData={txData}
pubKey={pubKey} pubKey={pubKey}
@@ -81,7 +74,7 @@ export const TxDetailsTransfer = ({
hideTypeRow={true} hideTypeRow={true}
/> />
{from ? ( {from ? (
<TableRow modifier="bordered" data-testid="from"> <TableRow modifier="bordered">
<TableCell>{t('From')}</TableCell> <TableCell>{t('From')}</TableCell>
<TableCell> <TableCell>
<PartyLink id={from} /> <PartyLink id={from} />
@@ -89,7 +82,7 @@ export const TxDetailsTransfer = ({
</TableRow> </TableRow>
) : null} ) : null}
{transfer.to ? ( {transfer.to ? (
<TableRow modifier="bordered" data-testid="to"> <TableRow modifier="bordered">
<TableCell>{t('To')}</TableCell> <TableCell>{t('To')}</TableCell>
<TableCell> <TableCell>
<PartyLink id={transfer.to} /> <PartyLink id={transfer.to} />
@@ -97,7 +90,7 @@ export const TxDetailsTransfer = ({
</TableRow> </TableRow>
) : null} ) : null}
{transfer.asset && transfer.amount ? ( {transfer.asset && transfer.amount ? (
<TableRow modifier="bordered" data-testid="amount"> <TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell> <TableCell>{t('Amount')}</TableCell>
<TableCell> <TableCell>
<SizeInAsset assetId={transfer.asset} size={transfer.amount} /> <SizeInAsset assetId={transfer.asset} size={transfer.amount} />
@@ -18,13 +18,11 @@ import { FilterLabel } from './tx-filter-label';
export type FilterOption = export type FilterOption =
| 'Amend LiquidityProvision Order' | 'Amend LiquidityProvision Order'
| 'Amend Order' | 'Amend Order'
| 'Apply Referral Code'
| 'Batch Market Instructions' | 'Batch Market Instructions'
| 'Cancel LiquidityProvision Order' | 'Cancel LiquidityProvision Order'
| 'Cancel Order' | 'Cancel Order'
| 'Cancel Transfer Funds' | 'Cancel Transfer Funds'
| 'Chain Event' | 'Chain Event'
| 'Create Referral Set'
| 'Delegate' | 'Delegate'
| 'Ethereum Key Rotate Submission' | 'Ethereum Key Rotate Submission'
| 'Issue Signatures' | 'Issue Signatures'
@@ -42,7 +40,6 @@ export type FilterOption =
| 'Submit Order' | 'Submit Order'
| 'Transfer Funds' | 'Transfer Funds'
| 'Undelegate' | 'Undelegate'
| 'Update Referral Set'
| 'Validator Heartbeat' | 'Validator Heartbeat'
| 'Vote on Proposal' | 'Vote on Proposal'
| 'Withdraw'; | 'Withdraw';
@@ -104,30 +104,10 @@ export function getLabelForProposal(
} }
} else if (proposal.terms?.updateMarket) { } else if (proposal.terms?.updateMarket) {
return t('Proposal: Update market'); return t('Proposal: Update market');
} else if (proposal.terms?.updateSpotMarket) {
return t('Proposal: Update spot');
} else if (proposal.terms?.updateMarketState) {
const type = proposal.terms.updateMarketState.changes?.updateType;
if (type === 'MARKET_STATE_UPDATE_TYPE_TERMINATE') {
return t('Proposal: Market terminate');
} else if (type === 'MARKET_STATE_UPDATE_TYPE_SUSPEND') {
return t('Proposal: Market suspend');
} else if (type === 'MARKET_STATE_UPDATE_TYPE_RESUME') {
return t('Proposal: Market resume');
}
return t('Proposal: Market state');
} else if (proposal.terms?.updateNetworkParameter) { } else if (proposal.terms?.updateNetworkParameter) {
return t('Proposal: Network parameter'); return t('Proposal: Network parameter');
} else if (proposal.terms?.updateReferralProgram) {
return t('Proposal: Referral program');
} else if (proposal.terms?.updateVolumeDiscountProgram) {
return t('Proposal: Discount program');
} else if (proposal.terms?.newFreeform) { } else if (proposal.terms?.newFreeform) {
return t('Proposal: Freeform'); return t('Proposal: Freeform');
} else if (proposal.terms?.newTransfer) {
return t('Proposal: Transfer');
} else if (proposal.terms?.cancelTransfer) {
return t('Proposal: Transfer cancel');
} else { } else {
return t('Proposal'); return t('Proposal');
} }
@@ -1,15 +1,6 @@
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response'; import { getTypeLabelForTransfer } from './details/tx-transfer';
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
import type { components } from '../../../types/explorer'; import type { components } from '../../../types/explorer';
import {
TxDetailsTransfer,
getTypeLabelForTransfer,
} from './details/tx-transfer';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
type Transfer = components['schemas']['commandsv1Transfer']; type Transfer = components['schemas']['commandsv1Transfer'];
describe('TX: Transfer: getLabelForTransfer', () => { describe('TX: Transfer: getLabelForTransfer', () => {
@@ -65,70 +56,3 @@ describe('TX: Transfer: getLabelForTransfer', () => {
expect(getTypeLabelForTransfer(mock)).toEqual('Transfer'); expect(getTypeLabelForTransfer(mock)).toEqual('Transfer');
}); });
}); });
describe('TxDetailsTransfer', () => {
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
const mockTxData: Partial<BlockExplorerTransactionResult> = {
hash: 'test',
submitter:
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
command: {
nonce: '5188810881378065222',
blockHeight: '14951513',
transfer: {
fromAccountType: 'ACCOUNT_TYPE_GENERAL',
to: '78432a2808f20b18a46ccc6a917bdc4d63c2b9e7007f777bdcab5a9f462c5ba6',
toAccountType: 'ACCOUNT_TYPE_GENERAL',
asset:
'dd20590509d30d20bdbbe64dc1090c1140c7690121a9b9940bc66f62dfa2e599',
amount: '4800000000',
reference: '',
oneOff: {
deliverOn: '0',
},
},
},
signature: {
value:
'610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700',
},
};
it('renders basic transfer details', () => {
const { getByTestId } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsTransfer
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockTxData.command.submitter}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
const id = getByTestId('id');
expect(id.children[0].textContent).toEqual('Transfer ID');
expect(id.children[1].textContent).toEqual(
'51f3bab5eb2637651012507a64d497790a734248792c16e5cf36df8984074fbd'
);
const type = getByTestId('type');
expect(type.children[1].textContent).toEqual('Transfer');
const from = getByTestId('from');
expect(from.children[1].textContent).toEqual(mockTxData.submitter);
const to = getByTestId('to');
expect(to.children[1].textContent).toEqual(mockTxData.command.transfer.to);
});
});
@@ -82,32 +82,6 @@ describe('Txs infinite list item', () => {
expect(screen.getByText('Missing vital data')).toBeInTheDocument(); expect(screen.getByText('Missing vital data')).toBeInTheDocument();
}); });
it('renders data even with missing time', () => {
render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxsInfiniteListItem
type="testType"
submitter="testPubKey"
hash="testTxHash"
block="1"
code={0}
command={{}}
/>
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-time')).toHaveTextContent('-');
});
it('renders data correctly', () => { it('renders data correctly', () => {
render( render(
<MockedProvider> <MockedProvider>
@@ -121,7 +95,6 @@ describe('Txs infinite list item', () => {
block="1" block="1"
code={0} code={0}
command={{}} command={{}}
createdAt="1970-11-01T18:07:15Z"
/> />
</tbody> </tbody>
</table> </table>
@@ -132,6 +105,5 @@ describe('Txs infinite list item', () => {
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey'); expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType'); expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1'); expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-time').textContent).toMatch(/years ago/);
}); });
}); });
@@ -9,7 +9,6 @@ import { PartyLink } from '../links';
import { useScreenDimensions } from '@vegaprotocol/react-helpers'; import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import type { Screen } from '@vegaprotocol/react-helpers'; import type { Screen } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { TimeAgo } from '../time-ago';
const DEFAULT_TRUNCATE_LENGTH = 7; const DEFAULT_TRUNCATE_LENGTH = 7;
@@ -33,7 +32,6 @@ export const TxsInfiniteListItem = ({
type, type,
block, block,
command, command,
createdAt,
}: Partial<BlockExplorerTransactionResult>) => { }: Partial<BlockExplorerTransactionResult>) => {
const { screenSize } = useScreenDimensions(); const { screenSize } = useScreenDimensions();
const idTruncateLength = useMemo( const idTruncateLength = useMemo(
@@ -87,11 +85,6 @@ export const TxsInfiniteListItem = ({
endChars={5} endChars={5}
/> />
</td> </td>
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
<td className="text-sm items-center font-mono" data-testid="tx-time">
{createdAt ? <TimeAgo date={createdAt} /> : '-'}
</td>
)}
</tr> </tr>
); );
}; };
@@ -3,7 +3,6 @@ import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import EmptyList from '../empty-list/empty-list'; import EmptyList from '../empty-list/empty-list';
import { Loader } from '@vegaprotocol/ui-toolkit'; import { Loader } from '@vegaprotocol/ui-toolkit';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
interface TxsInfiniteListProps { interface TxsInfiniteListProps {
hasMoreTxs: boolean; hasMoreTxs: boolean;
@@ -20,16 +19,7 @@ interface ItemProps {
} }
const Item = ({ tx }: ItemProps) => { const Item = ({ tx }: ItemProps) => {
const { const { hash, submitter, type, command, block, code, index: blockIndex } = tx;
hash,
submitter,
type,
command,
block,
code,
createdAt,
index: blockIndex,
} = tx;
return ( return (
<TxsInfiniteListItem <TxsInfiniteListItem
type={type} type={type}
@@ -39,7 +29,6 @@ const Item = ({ tx }: ItemProps) => {
hash={hash} hash={hash}
block={block} block={block}
index={blockIndex} index={blockIndex}
createdAt={createdAt}
/> />
); );
}; };
@@ -50,7 +39,6 @@ export const TxsInfiniteList = ({
className, className,
hasFilters = false, hasFilters = false,
}: TxsInfiniteListProps) => { }: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
if (!txs || txs.length === 0) { if (!txs || txs.length === 0) {
if (!areTxsLoading) { if (!areTxsLoading) {
return ( return (
@@ -78,9 +66,6 @@ export const TxsInfiniteList = ({
<th>{t('Type')}</th> <th>{t('Type')}</th>
<th className="text-left">{t('From')}</th> <th className="text-left">{t('From')}</th>
<th>{t('Block')}</th> <th>{t('Block')}</th>
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
<th>{t('Time')}</th>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -43,7 +43,7 @@ export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
url.searchParams.append('first', count); url.searchParams.append('first', count);
url.searchParams.append('after', params.after); url.searchParams.append('after', params.after);
} else { } else {
url.searchParams.append('last', count); url.searchParams.append('first', count);
} }
// Hacky fix for param as array // Hacky fix for param as array
@@ -6,7 +6,7 @@ describe('getTxsDataUrl', () => {
count: 10, count: 10,
baseUrl: 'https://example.com/transactions', baseUrl: 'https://example.com/transactions',
}; };
const expectedUrl = 'https://example.com/transactions?last=10'; const expectedUrl = 'https://example.com/transactions?first=10';
expect(getTxsDataUrl(params)).toEqual(expectedUrl); expect(getTxsDataUrl(params)).toEqual(expectedUrl);
}); });
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
baseUrl: 'https://example.com/transactions', baseUrl: 'https://example.com/transactions',
}; };
const expectedUrl = const expectedUrl =
'https://example.com/transactions?last=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234'; 'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
expect(getTxsDataUrl(params)).toEqual(expectedUrl); expect(getTxsDataUrl(params)).toEqual(expectedUrl);
}); });
+1 -1
View File
@@ -133,7 +133,7 @@ export const ErrorBoundary = () => {
); );
}; };
export const GHOST = ( const GHOST = (
<svg <svg
width="56" width="56"
height="85" height="85"
@@ -1,2 +1 @@
export * from './network-parameters'; export * from './network-parameters';
export * from './structure-network-params';
@@ -1,105 +1,82 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParametersTable } from './network-parameters'; import { NetworkParametersTable } from './network-parameters';
import { MemoryRouter } from 'react-router-dom';
const renderComponent = (data: NetworkParamsQuery | undefined) => {
return render(
<MemoryRouter>
<NetworkParametersTable data={data} loading={false} />
</MemoryRouter>
);
};
const mockData = {
networkParametersConnection: {
edges: [
{
node: {
key: 'spam.protection.delegation.min.tokens',
value: '3',
},
},
{
node: {
key: 'spam.protection.voting.min.tokens',
value: '1',
},
},
{
node: {
key: 'reward.staking.delegation.minimumValidatorStake',
value: '2',
},
},
{
node: {
key: 'reward.asset',
value:
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
},
},
],
},
};
describe('NetworkParametersTable', () => { describe('NetworkParametersTable', () => {
it('renders headers correctly', () => { it('renders correctly when it has network params', () => {
renderComponent(mockData); const data: NetworkParamsQuery = {
networkParametersConnection: {
const allHeadings = screen.getAllByRole('heading'); edges: [
expect( {
allHeadings.map((h) => { node: {
return { __typename: 'NetworkParameter',
text: h.textContent, key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
level: h.tagName, value: '1',
testId: h.getAttribute('data-testid'), },
}; },
}) {
).toEqual([ node: {
{ __typename: 'NetworkParameter',
text: 'Network Parameters', key: 'market.fee.factors.infrastructureFee',
level: 'H1', value: '0.0005',
testId: 'network-param-header', },
},
],
}, },
{ text: 'Spam', level: 'H1', testId: 'spam' }, };
{ text: 'Protection', level: 'H2', testId: 'spam-protection' }, render(<NetworkParametersTable data={data} loading={false} />);
{ text: 'Delegation', level: 'H3', testId: 'spam-protection-delegation' }, expect(screen.getByTestId('network-param-header')).toHaveTextContent(
{ text: 'Min', level: 'H4', testId: 'spam-protection-delegation-min' }, 'Network Parameters'
{ text: 'Voting', level: 'H3', testId: 'spam-protection-voting' }, );
{ text: 'Min', level: 'H4', testId: 'spam-protection-voting-min' }, const rows = screen.getAllByTestId('key-value-table-row');
{ text: 'Reward', level: 'H1', testId: 'reward' }, expect(rows[0].children[0]).toHaveTextContent(
{ text: 'Staking', level: 'H2', testId: 'reward-staking' }, 'market.fee.factors.infrastructureFee'
{ text: 'Delegation', level: 'H3', testId: 'reward-staking-delegation' }, );
]); expect(rows[1].children[0]).toHaveTextContent(
'market.liquidityProvision.minLpStakeQuantumMultiple'
);
expect(rows[0].children[1]).toHaveTextContent('0.0005');
expect(rows[1].children[1]).toHaveTextContent('1');
}); });
it('renders network params correctly', () => { it('renders the rows in ascending order', () => {
renderComponent(mockData); const data: NetworkParamsQuery = {
networkParametersConnection: {
const delegationMinTokensRow = screen.getByTestId( edges: [
'spam-protection-delegation-min-tokens' {
node: {
__typename: 'NetworkParameter',
key: 'market.fee.factors.infrastructureFee',
value: '0.0005',
},
},
{
node: {
__typename: 'NetworkParameter',
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
value: '1',
},
},
],
},
};
render(<NetworkParametersTable data={data} loading={false} />);
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
'Network Parameters'
); );
expect(delegationMinTokensRow).toHaveTextContent('0.000000000000000003'); const rows = screen.getAllByTestId('key-value-table-row');
expect(rows[0].children[0]).toHaveTextContent(
const votingMinTokensRow = screen.getByTestId( 'market.fee.factors.infrastructureFee'
'spam-protection-voting-min-tokens'
); );
expect(votingMinTokensRow).toHaveTextContent('0.000000000000000001'); expect(rows[1].children[0]).toHaveTextContent(
'market.liquidityProvision.minLpStakeQuantumMultiple'
const minimumValidatorStakeRow = screen.getByTestId(
'reward-staking-delegation-minimumValidatorStake'
);
expect(minimumValidatorStakeRow).toHaveTextContent('2');
const assetRow = screen.getByTestId('reward-asset');
expect(assetRow).toHaveTextContent(
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55'
); );
expect(rows[0].children[1]).toHaveTextContent('0.0005');
expect(rows[1].children[1]).toHaveTextContent('1');
}); });
it('does not render rows when is loading', () => { it('does not render rows when is loading', () => {
renderComponent(undefined); render(<NetworkParametersTable data={undefined} loading={true} />);
expect(screen.getByTestId('network-param-header')).toHaveTextContent( expect(screen.getByTestId('network-param-header')).toHaveTextContent(
'Network Parameters' 'Network Parameters'
); );
@@ -1,8 +1,6 @@
import startCase from 'lodash/startCase';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import { import {
AsyncRenderer, AsyncRenderer,
KeyValueTable,
KeyValueTableRow, KeyValueTableRow,
SyntaxHighlighter, SyntaxHighlighter,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
@@ -14,12 +12,11 @@ import {
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { RouteTitle } from '../../components/route-title'; import { RouteTitle } from '../../components/route-title';
import orderBy from 'lodash/orderBy';
import { useNetworkParamsQuery } from '@vegaprotocol/network-parameters'; import { useNetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { useScrollToLocation } from '../../hooks/scroll-to-location'; import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import { structureNetworkParams } from './structure-network-params';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { GroupedParams } from './structure-network-params';
const PERCENTAGE_PARAMS = [ const PERCENTAGE_PARAMS = [
'governance.proposal.asset.requiredMajority', 'governance.proposal.asset.requiredMajority',
@@ -36,98 +33,30 @@ const PERCENTAGE_PARAMS = [
'governance.proposal.updateNetParam.requiredMajority', 'governance.proposal.updateNetParam.requiredMajority',
'governance.proposal.updateNetParam.requiredParticipation', 'governance.proposal.updateNetParam.requiredParticipation',
'governance.proposal.updateMarket.minProposerEquityLikeShare', 'governance.proposal.updateMarket.minProposerEquityLikeShare',
'governance.proposal.VolumeDiscountProgram.requiredMajority',
'governance.proposal.VolumeDiscountProgram.requiredParticipation',
'governance.proposal.referralProgram.requiredMajority',
'governance.proposal.transfer.requiredMajority',
'governance.proposal.updateAsset.requiredMajority',
'governance.proposal.updateAsset.requiredParticipation',
'governance.proposal.transfer.requiredParticipation',
'governance.proposal.referralProgram.requiredParticipation',
'network.validators.ersatz.rewardFactor',
'network.validators.ersatz.multipleOfTendermintValidators',
'validators.vote.required', 'validators.vote.required',
'referralProgram.maxReferralRewardFactor', ];
'referralProgram.maxReferralDiscountFactor',
'referralProgram.maxReferralRewardProportion',
].map((p) => p.toLowerCase());
const BIG_NUMBER_PARAMS = [ const BIG_NUMBER_PARAMS = [
'spam.protection.delegation.min.tokens', 'spam.protection.delegation.min.tokens',
'validators.delegation.minAmount', 'validators.delegation.minAmount',
'governance.proposal.transfer.maxAmount',
'reward.staking.delegation.minimumValidatorStake', 'reward.staking.delegation.minimumValidatorStake',
'reward.staking.delegation.maxPayoutPerParticipant', 'reward.staking.delegation.maxPayoutPerParticipant',
'reward.staking.delegation.maxPayoutPerEpoch', 'reward.staking.delegation.maxPayoutPerEpoch',
'spam.protection.voting.min.tokens', 'spam.protection.voting.min.tokens',
'spam.protection.proposal.min.tokens',
'governance.proposal.transfer.minVoterBalance',
'governance.proposal.freeform.minProposerBalance', 'governance.proposal.freeform.minProposerBalance',
'governance.proposal.updateNetParam.minVoterBalance', 'governance.proposal.updateNetParam.minVoterBalance',
'governance.proposal.updateMarket.minVoterBalance', 'governance.proposal.updateMarket.minVoterBalance',
'governance.proposal.asset.minVoterBalance', 'governance.proposal.asset.minVoterBalance',
'governance.proposal.updateNetParam.minProposerBalance', 'governance.proposal.updateNetParam.minProposerBalance',
'governance.proposal.freeform.minVoterBalance', 'governance.proposal.freeform.minVoterBalance',
'spam.protection.proposal.min.tokens',
'governance.proposal.updateMarket.minProposerBalance', 'governance.proposal.updateMarket.minProposerBalance',
'governance.proposal.asset.minProposerBalance', 'governance.proposal.asset.minProposerBalance',
'governance.proposal.transfer.minProposerBalance',
'governance.proposal.market.minProposerBalance', 'governance.proposal.market.minProposerBalance',
'governance.proposal.market.minVoterBalance', 'governance.proposal.market.minVoterBalance',
'governance.proposal.updateAsset.minProposerBalance', 'governance.proposal.updateAsset.minProposerBalance',
'governance.proposal.updateAsset.minVoterBalance', 'governance.proposal.updateAsset.minVoterBalance',
'governance.proposal.referralProgram.minProposerBalance', ];
'governance.proposal.referralProgram.minVoterBalance',
'governance.proposal.VolumeDiscountProgram.minProposerBalance',
'governance.proposal.VolumeDiscountProgram.minVoterBalance',
].map((p) => p.toLowerCase());
export const renderGroupedParams = (
group: GroupedParams,
level: number,
parentKeys: string[] = []
) => {
const Header = `h${level}` as keyof JSX.IntrinsicElements;
const headerStyles = classNames('uppercase font-semibold', {
'pt-6 text-3xl underline': level === 1,
'pt-3 text-2xl': level === 2,
'pt-2 text-lg': level === 3,
'pt-2 text-default': level === 4,
});
return Object.entries(group).map(([key, value]) => {
const fullPath = [...parentKeys, key].join('.');
const isLeafNode = typeof value !== 'object';
const id = parentKeys.concat([key]).join('-');
return (
<div key={key}>
{!isLeafNode && (
<div id={id}>
<Link to={`#${id}`}>
<Header className={headerStyles} data-testid={id}>
{startCase(key)}
</Header>
</Link>
</div>
)}
{isLeafNode ? (
typeof value === 'string' ? (
<div data-testid={id}>
<NetworkParameterRow
key={fullPath}
row={{ key: fullPath, value: value }}
/>
</div>
) : null
) : (
<div className="pb-1">
{renderGroupedParams(value, level + 1, [...parentKeys, key])}
</div>
)}
</div>
);
});
};
export const NetworkParameterRow = ({ export const NetworkParameterRow = ({
row: { key, value }, row: { key, value },
@@ -148,15 +77,13 @@ export const NetworkParameterRow = ({
> >
{key} {key}
{isSyntaxRow ? ( {isSyntaxRow ? (
<div className="pb-2"> <SyntaxHighlighter data={JSON.parse(value)} />
<SyntaxHighlighter data={JSON.parse(value)} />
</div>
) : BIG_NUMBER_PARAMS.includes(key.toLowerCase()) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key.toLowerCase()) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : isNaN(Number(value)) ? ( ) : isNaN(Number(value)) ? (
value value
) : BIG_NUMBER_PARAMS.includes(key) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : ( ) : (
formatNumber(Number(value), 4) formatNumber(Number(value), 4)
)} )}
@@ -186,12 +113,17 @@ export const NetworkParametersTable = ({
loading={loading} loading={loading}
error={error} error={error}
render={(data) => { render={(data) => {
const flatParams = removePaginationWrapper( const ascParams = orderBy(
data.networkParametersConnection.edges removePaginationWrapper(data.networkParametersConnection.edges),
(param) => param.key,
'asc'
); );
const groupedParams = structureNetworkParams(flatParams);
return ( return (
<div className="-mt-6">{renderGroupedParams(groupedParams, 1)}</div> <KeyValueTable data-testid="parameters">
{(ascParams || []).map((row) => (
<NetworkParameterRow key={row.key} row={row} />
))}
</KeyValueTable>
); );
}} }}
/> />
@@ -1,109 +0,0 @@
import type { GroupedParams } from './structure-network-params';
import {
structureParams,
sortGroupedParams,
structureNetworkParams,
} from './structure-network-params';
describe('structureParams', () => {
it('should correctly structure params', () => {
const input = [
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
{ key: 'spam.protection.voting.min.tokens', value: '5' },
];
const output: GroupedParams = {
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
voting: {
min: {
tokens: '5',
},
},
},
},
};
expect(structureParams(input)).toEqual(output);
});
it('should handle top-level keys correctly', () => {
const input = [{ key: 'levelOne', value: '10' }];
const output = {
levelOne: '10',
};
expect(structureParams(input)).toEqual(output);
});
});
describe('sortGroupedParams', () => {
it('should correctly sort grouped params', () => {
const input: GroupedParams = {
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
},
},
reward: '50',
};
const output: GroupedParams = {
reward: '50',
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
},
},
};
expect(sortGroupedParams(input)).toEqual(output);
});
it('should handle already sorted keys', () => {
const input = {
a: '10',
b: {
c: '5',
d: '6',
},
};
expect(sortGroupedParams(input)).toEqual(input);
});
});
describe('structureNetworkParams', () => {
it('should structure and sort network params correctly', () => {
const input = [
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
{ key: 'reward.asset', value: '50' },
];
const output: GroupedParams = {
reward: {
asset: '50',
},
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
},
},
};
expect(structureNetworkParams(input)).toEqual(output);
});
it('should return an empty object if no params are provided', () => {
expect(structureNetworkParams([])).toEqual({});
});
});
@@ -1,107 +0,0 @@
/**
* Categorizes and sorts an array of key-value pairs of network params into a nested object structure.
*
* The function takes an array of network params where keys are dot-delimited
* strings representing nested categories (e.g., 'spam.protection.delegation.min.tokens').
*
* Why this is necessary:
* A flat key-value structure wouldn't provide the hierarchical information needed.
* Organizing network parameters like this allows the rendering of nested headers
* and their corresponding network params.
*
* It also ensures that items with the minimum amount of nesting are ordered first. This
* allows us to render these items first, before more deeply nested items are rendered with
* subheaders. This creates a more intuitive UI.
*
* For example, given the input:
* [
* { key: 'spam.protection.delegation.min.tokens', value: '10' },
* { key: 'spam.protection.voting.min.tokens', value: '5' },
* { key: 'reward.staking.delegation.minimumValidatorStake', value: '2' }
* { key: 'reward.asset', value: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55' }
* ]
*
* The output will be:
* {
* spam: {
* protection: {
* delegation: {
* min: {
* tokens: '10'
* }
* },
* voting: {
* min: {
* tokens: '5'
* }
* }
* }
* },
* reward: {
* asset: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
* staking: {
* delegation: {
* minimumValidatorStake: '2'
* }
* }
* }
* }
*
* @param {Array} params - An array of key-value pairs to categorize and sort.
* @returns {GroupedParams} - A nested object that groups the key-value pairs.
*/
export type GroupedParams = {
[key: string]: string | GroupedParams;
};
export const structureParams = (
params: { key: string; value: string }[]
): GroupedParams => {
const grouped: GroupedParams = {};
params.forEach(({ key, value }) => {
const parts = key.split('.');
let node: GroupedParams = grouped;
parts.forEach((part, i) => {
if (typeof node[part] === 'undefined') {
node[part] = i === parts.length - 1 ? value : {};
}
if (typeof node[part] === 'object') {
node = node[part] as GroupedParams;
}
});
});
return grouped;
};
export const sortGroupedParams = (
groupedParams: GroupedParams
): GroupedParams => {
const sorted: GroupedParams = {};
// Sort top-level keys first
Object.entries(groupedParams).forEach(([key, value]) => {
if (typeof value === 'string') {
sorted[key] = value;
}
});
Object.entries(groupedParams).forEach(([key, value]) => {
if (typeof value === 'object') {
sorted[key] = sortGroupedParams(value);
}
});
return sorted;
};
export const structureNetworkParams = (
params: { key: string; value: string }[]
) => {
const grouped = structureParams(params);
return sortGroupedParams(grouped);
};
@@ -1,5 +1,5 @@
fragment ExplorerOracleDataConnection on OracleSpec { fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection(pagination: { first: 30 }) { dataConnection {
edges { edges {
node { node {
externalData { externalData {
@@ -45,51 +45,10 @@ fragment ExplorerOracleDataSource on OracleSpec {
operator operator
} }
} }
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
} }
... on DataSourceDefinitionExternal { ... on DataSourceDefinitionExternal {
sourceType { sourceType {
... on EthCallSpec {
abi
args
method
requiredConfirmations
address
normalisers {
name
expression
}
trigger {
trigger {
... on EthTimeTrigger {
initial
every
until
}
}
}
filters {
key {
name
type
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
... on DataSourceSpecConfiguration { ... on DataSourceSpecConfiguration {
signers { signers {
signer { signer {
@@ -113,23 +72,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
} }
} }
} }
... on EthCallSpec {
abi
address
requiredConfirmations
method
filters {
key {
type
name
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
} }
} }
} }
@@ -139,7 +81,7 @@ fragment ExplorerOracleDataSource on OracleSpec {
} }
query ExplorerOracleSpecs { query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: { first: 30 }) { oracleSpecsConnection(pagination: { first: 50 }) {
pageInfo { pageInfo {
hasNextPage hasNextPage
} }
@@ -1,85 +1,14 @@
fragment ExplorerOraclePerpetual on Perpetual {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForSettlementSchedule {
id
status
}
}
fragment ExplorerOracleFuture on Future {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForTradingTermination {
id
status
}
}
fragment ExplorerOracleForMarketsMarket on Market { fragment ExplorerOracleForMarketsMarket on Market {
id id
state
tradableInstrument { tradableInstrument {
instrument { instrument {
product { product {
... on Future { ... on Future {
...ExplorerOracleFuture dataSourceSpecForSettlementData {
} id
... on Perpetual {
...ExplorerOraclePerpetual
}
}
}
}
}
fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
spec {
id
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
} dataSourceSpecForTradingTermination {
... on DataSourceDefinitionExternal { id
sourceType {
... on EthCallSpec {
address
}
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
}
} }
} }
} }
@@ -95,27 +24,4 @@ query ExplorerOracleFormMarkets {
} }
} }
} }
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: { last: 1 }) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
} }
+5 -63
View File
@@ -5,23 +5,23 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } }; export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } }; export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>; export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null }; export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{ export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID']; id: Types.Scalars['ID'];
}>; }>;
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null }; export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
export const ExplorerOracleDataConnectionFragmentDoc = gql` export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec { fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection(pagination: {first: 30}) { dataConnection {
edges { edges {
node { node {
externalData { externalData {
@@ -68,51 +68,10 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
operator operator
} }
} }
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
} }
... on DataSourceDefinitionExternal { ... on DataSourceDefinitionExternal {
sourceType { sourceType {
... on EthCallSpec {
abi
args
method
requiredConfirmations
address
normalisers {
name
expression
}
trigger {
trigger {
... on EthTimeTrigger {
initial
every
until
}
}
}
filters {
key {
name
type
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
... on DataSourceSpecConfiguration { ... on DataSourceSpecConfiguration {
signers { signers {
signer { signer {
@@ -136,23 +95,6 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
} }
} }
} }
... on EthCallSpec {
abi
address
requiredConfirmations
method
filters {
key {
type
name
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
} }
} }
} }
@@ -163,7 +105,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
${ExplorerOracleDataConnectionFragmentDoc}`; ${ExplorerOracleDataConnectionFragmentDoc}`;
export const ExplorerOracleSpecsDocument = gql` export const ExplorerOracleSpecsDocument = gql`
query ExplorerOracleSpecs { query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: {first: 30}) { oracleSpecsConnection(pagination: {first: 50}) {
pageInfo { pageInfo {
hasNextPage hasNextPage
} }
@@ -3,106 +3,25 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type ExplorerOraclePerpetualFragment = { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } }; export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } } };
export type ExplorerOracleFutureFragment = { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } };
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } };
export type ExplorerOracleDataSourceSpecFragment = { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } };
export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>; export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null }; export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } } } }> } | null };
export const ExplorerOracleFutureFragmentDoc = gql`
fragment ExplorerOracleFuture on Future {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForTradingTermination {
id
status
}
}
`;
export const ExplorerOraclePerpetualFragmentDoc = gql`
fragment ExplorerOraclePerpetual on Perpetual {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForSettlementSchedule {
id
status
}
}
`;
export const ExplorerOracleForMarketsMarketFragmentDoc = gql` export const ExplorerOracleForMarketsMarketFragmentDoc = gql`
fragment ExplorerOracleForMarketsMarket on Market { fragment ExplorerOracleForMarketsMarket on Market {
id id
state
tradableInstrument { tradableInstrument {
instrument { instrument {
product { product {
... on Future { ... on Future {
...ExplorerOracleFuture dataSourceSpecForSettlementData {
} id
... on Perpetual {
...ExplorerOraclePerpetual
}
}
}
}
}
${ExplorerOracleFutureFragmentDoc}
${ExplorerOraclePerpetualFragmentDoc}`;
export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
spec {
id
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
} }
} dataSourceSpecForTradingTermination {
... on DataSourceDefinitionExternal { id
sourceType {
... on EthCallSpec {
address
}
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
}
} }
} }
} }
@@ -119,32 +38,8 @@ export const ExplorerOracleFormMarketsDocument = gql`
} }
} }
} }
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: {last: 1}) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
} }
${ExplorerOracleForMarketsMarketFragmentDoc} ${ExplorerOracleForMarketsMarketFragmentDoc}`;
${ExplorerOracleDataSourceSpecFragmentDoc}`;
/** /**
* __useExplorerOracleFormMarketsQuery__ * __useExplorerOracleFormMarketsQuery__
@@ -51,26 +51,15 @@ describe('Oracle Data view', () => {
{ {
node: { node: {
externalData: { externalData: {
__typename: 'ExternalData',
data: { data: {
__typename: 'Data', broadcastAt: '2022-01-01',
matchedSpecIds: ['123'],
broadcastAt: '2023-01-01T00:00:00Z',
data: [
{
__typename: 'Property',
name: 'Test-name',
value: 'Test-data',
},
],
}, },
}, },
}, },
}, },
], ],
} as ExplorerOracleDataConnectionFragment['dataConnection']) } as DataConnection)
); );
expect(res.getByText('Test-name')).toBeInTheDocument(); expect(res.getByText('Broadcast data')).toBeInTheDocument();
expect(res.getByText('Test-data')).toBeInTheDocument();
}); });
}); });
@@ -1,55 +1,39 @@
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
import { TimeAgo } from '../../../components/time-ago';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
const cellSpacing = 'px-3'; import filter from 'recursive-key-filter';
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
interface OracleDataTypeProps { interface OracleDataTypeProps {
data: ExplorerOracleDataConnectionFragment['dataConnection']; data: ExplorerOracleDataConnectionFragment['dataConnection'];
} }
/**
* If there is data that has matched this oracle, this view will
* render the data inside a collapsed element so that it can be viewed.
* Currently the data is just rendered as a JSON view, because
* that Does The Job, rather than because it's good.
*/
export function OracleData({ data }: OracleDataTypeProps) { export function OracleData({ data }: OracleDataTypeProps) {
if (!data || !data.edges?.length) { if (!data || !data.edges?.length || data.edges.length > 1) {
return null; return null;
} }
return ( return (
<> <details data-testid="oracle-data">
<h2 className="text-3xl font-bold mb-4 display-5 mt-5"> <summary>{t('Broadcast data')}</summary>
{t('Recent data')} <ul>
</h2> {data.edges.map((d) => {
<table> if (!d) {
<thead> return null;
<tr className="text-left"> }
<th className={cellSpacing}>Value</th>
<th className={cellSpacing}>Key</th>
<th className={cellSpacing}>Date</th>
</tr>
</thead>
<tbody>
{data.edges.map((d) => {
if (!d) {
return null;
}
const broadcastAt = d.node.externalData.data.broadcastAt; return (
const node = d.node.externalData.data.data?.at(0); <li key={d.node.externalData.data.broadcastAt}>
if (!node || !node.value || !node.name || !node || !broadcastAt) { <SyntaxHighlighter data={filter(d, ['__typename'])} />
return null; </li>
} );
})}
return ( </ul>
<tr key={d.node.externalData.data.broadcastAt}> </details>
<td className={`${cellSpacing} font-mono`}>{node.value}</td>
<td className={`${cellSpacing} font-mono`}>{node.name}</td>
<td className={cellSpacing}>
<TimeAgo date={broadcastAt} />
</td>
</tr>
);
})}
</tbody>
</table>
</>
); );
} }
@@ -55,13 +55,13 @@ describe('Oracle type view', () => {
const s = mock('prices.external.whatever'); const s = mock('prices.external.whatever');
expect(isInternalSourceType(s)).toEqual(false); expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s)); const res = render(renderWrappedComponent(s));
expect(res.getByText('External Data')).toBeInTheDocument(); expect(res.getByText('External data')).toBeInTheDocument();
}); });
it('Renders External data otherwise', () => { it('Renders External data otherwise', () => {
const s = mock('prices.external.vegaprotocol.builtin.'); const s = mock('prices.external.vegaprotocol.builtin.');
expect(isInternalSourceType(s)).toEqual(false); expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s)); const res = render(renderWrappedComponent(s));
expect(res.getByText('External Data')).toBeInTheDocument(); expect(res.getByText('External data')).toBeInTheDocument();
}); });
}); });
@@ -26,19 +26,6 @@ export function isInternalSourceType(s: SourceType) {
return false; return false;
} }
export function getExternalType(s: SourceType) {
if (s.sourceType.__typename === 'EthCallSpec') {
return 'Ethereum Contract Call';
} else {
return 'External Data';
}
}
export function getTypeString(s: SourceType) {
const isInternal = isInternalSourceType(s);
return isInternal ? 'Internal data' : getExternalType(s);
}
interface OracleDetailsTypeProps { interface OracleDetailsTypeProps {
sourceType: SourceType; sourceType: SourceType;
} }
@@ -52,10 +39,14 @@ export function OracleDetailsType({ sourceType }: OracleDetailsTypeProps) {
return null; return null;
} }
const isInternal = isInternalSourceType(sourceType);
return ( return (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableHeader scope="row">Type</TableHeader> <TableHeader scope="row">Type</TableHeader>
<TableCell modifier="bordered">{getTypeString(sourceType)}</TableCell> <TableCell modifier="bordered">
{isInternal ? 'Internal data' : 'External data'}
</TableCell>
</TableRow> </TableRow>
); );
} }
@@ -1,39 +0,0 @@
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
interface OracleDetailsEthSourceProps {
sourceType: SourceType;
}
/**
* Given an Oracle that sources data from Ethereum, this component will render
* a link to the smart contract and some basic details
*/
export function OracleEthSource({ sourceType }: OracleDetailsEthSourceProps) {
if (
sourceType.__typename !== 'DataSourceDefinitionExternal' ||
sourceType.sourceType.__typename !== 'EthCallSpec'
) {
return null;
}
const address = sourceType.sourceType.address;
if (!address) {
return null;
}
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Ethereum Contract</TableHeader>
<TableCell modifier="bordered">
<EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />
<span className="mx-3">&rArr;</span>
<code>{sourceType.sourceType.method}</code>
</TableCell>
</TableRow>
);
}
@@ -1,17 +1,14 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { OracleFilter } from './oracle-filter'; import { getConditionsOrFilters, OracleFilter } from './oracle-filter';
import type { Filter } from './oracle-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles'; import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import { import {
ConditionOperator, ConditionOperator,
DataSourceSpecStatus, DataSourceSpecStatus,
PropertyKeyType, PropertyKeyType,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import type { Condition } from '@vegaprotocol/types';
type Spec = const mockExternalSpec = {
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
const mockExternalSpec: Spec = {
sourceType: { sourceType: {
__typename: 'DataSourceSpecConfiguration', __typename: 'DataSourceSpecConfiguration',
filters: [ filters: [
@@ -19,12 +16,12 @@ const mockExternalSpec: Spec = {
__typename: 'Filter', __typename: 'Filter',
key: { key: {
type: PropertyKeyType.TYPE_INTEGER, type: PropertyKeyType.TYPE_INTEGER,
name: 'testKey', name: 'test',
}, },
conditions: [ conditions: [
{ {
__typename: 'Condition', __typename: 'Condition',
value: 'testValue', value: 'test',
operator: ConditionOperator.OPERATOR_EQUALS, operator: ConditionOperator.OPERATOR_EQUALS,
}, },
], ],
@@ -33,6 +30,16 @@ const mockExternalSpec: Spec = {
}, },
}; };
const mockTimeSpec = {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
value: '123',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
};
function renderComponent(data: ExplorerOracleDataSourceFragment) { function renderComponent(data: ExplorerOracleDataSourceFragment) {
return <OracleFilter data={data} />; return <OracleFilter data={data} />;
} }
@@ -63,16 +70,11 @@ describe('Oracle Filter view', () => {
}, },
}, },
}, },
dataConnection: { } as ExplorerOracleDataSourceFragment)
edges: [],
},
})
); );
// Renders a comprehensible summary of key = value expect(res.getByText('Filter')).toBeInTheDocument();
expect(res.getByText('testKey')).toBeInTheDocument(); // Avoids asserting on how the data is presented because it is very rudimentary
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByText('testValue')).toBeInTheDocument();
}); });
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => { it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
@@ -86,59 +88,75 @@ describe('Oracle Filter view', () => {
data: { data: {
sourceType: { sourceType: {
__typename: 'DataSourceDefinitionInternal', __typename: 'DataSourceDefinitionInternal',
sourceType: { sourceType: mockTimeSpec,
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
value: '1',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
},
}, },
}, },
}, },
}, },
dataConnection: { } as ExplorerOracleDataSourceFragment)
edges: [],
},
})
); );
expect(res.getByText('Time')).toBeInTheDocument(); expect(res.getByText('Filter')).toBeInTheDocument();
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByTitle('1').textContent).toMatch(/1970/);
// Avoids asserting on how the data is presented because it is very rudimentary // Avoids asserting on how the data is presented because it is very rudimentary
}); });
});
it('DataSourceSpecConfigurationTime handles empty conditions', () => { describe('getConditionsOrFilter', () => {
const res = render( it('Returns null if the type is undetermined (not DataSourceSpecConfiguration or DataSourceSpecConfigurationTime', () => {
renderComponent({ expect(getConditionsOrFilters({})).toBeNull();
dataSourceSpec: { });
spec: {
id: 'irrelevant-test-data', it('Returns the conditions object for time specs', () => {
createdAt: 'irrelevant-test-data', const mock: Filter = {
status: DataSourceSpecStatus.STATUS_ACTIVE, __typename: 'DataSourceSpecConfigurationTime',
data: { conditions: [
sourceType: { {
__typename: 'DataSourceDefinitionInternal', __typename: 'Condition',
sourceType: { value: '100',
__typename: 'DataSourceSpecConfigurationTime', operator: ConditionOperator.OPERATOR_GREATER_THAN,
conditions: [undefined as unknown as Condition], },
}, ],
}, };
}, const res = getConditionsOrFilters(mock);
// This ugly construction is due to lazy typing on getConditionsOrFilter
if (!res || res.length !== 1 || !res[0] || 'key' in res[0]) {
throw new Error(
'getConditionsOrFilter did not return conditions on a time spec'
);
}
expect(res[0].__typename).toEqual('Condition');
});
it('Returns the filters object for external specs', () => {
const mock: Filter = {
__typename: 'DataSourceSpecConfiguration',
filters: [
{
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'test',
}, },
conditions: [
{
__typename: 'Condition',
value: 'test',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
}, },
dataConnection: { ],
edges: [], };
},
})
);
// This should never happen, but for coverage sake we test that it does this const res = getConditionsOrFilters(mock);
const ul = res.getByRole('list'); // This ugly construction is due to lazy typing on getConditionsOrFilter
expect(ul).toBeInTheDocument(); if (!res || res.length !== 1 || !res[0] || 'value' in res[0]) {
expect(ul).toBeEmptyDOMElement(); throw new Error(
'getConditionsOrFilter did not return filters on a external spec'
);
}
expect(res[0].__typename).toEqual('Filter');
}); });
}); });
@@ -1,15 +1,36 @@
import { t } from '@vegaprotocol/i18n';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles'; import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
import { OracleSpecCondition } from './oracle-spec/condition';
import { getCharacterForOperator } from './oracle-spec/operator';
interface OracleFilterProps { interface OracleFilterProps {
data: ExplorerOracleDataSourceFragment; data: ExplorerOracleDataSourceFragment;
} }
export type Filter =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType']['sourceType'];
/**
* Given the main Filter view just uses a JSON dump view, this function
* selects the correct filter to dump in to that view. Internal oracles
* (i.e. the Time oracle) have conditions while external data sources
* have filters
*
* @param s A data source
* @returns Object an object containing conditions or filters
*/
export function getConditionsOrFilters(s: Filter) {
if (s.__typename === 'DataSourceSpecConfiguration') {
return s.filters;
} else if (s.__typename === 'DataSourceSpecConfigurationTime') {
return s.conditions;
}
return null;
}
/** /**
* Shows the conditions that this oracle is using to filter * Shows the conditions that this oracle is using to filter
* data sources, as a list. * data sources.
* *
* Renders nothing if there is no data (which will frequently) * Renders nothing if there is no data (which will frequently)
* be the case) and if there is data, currently renders a simple * be the case) and if there is data, currently renders a simple
@@ -21,53 +42,16 @@ export function OracleFilter({ data }: OracleFilterProps) {
} }
const s = data.dataSourceSpec.spec.data.sourceType.sourceType; const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) { const f = getConditionsOrFilters(s);
return (
<ul>
{s.conditions
.filter((c) => !!c)
.map((c) => {
if (!c) {
return null;
}
return (
<OracleSpecCondition key={c.value} data={c} type={s.__typename} />
);
})}
</ul>
);
} else if (
s.__typename === 'DataSourceSpecConfigurationTimeTrigger' &&
s.triggers
) {
return <OracleSpecInternalTimeTrigger data={s} />;
} else if (
s.__typename === 'EthCallSpec' ||
s.__typename === 'DataSourceSpecConfiguration'
) {
if (s.filters !== null && s.filters && 'filters' in s) {
return (
<ul>
{s.filters.map((f) => {
const prop = <code title={f.key.type}>{f.key.name}</code>;
if (!f.conditions || f.conditions.length === 0) { if (!f) {
return prop; return null;
} else {
return f.conditions.map((c) => {
return (
<li key={`${prop}${c.value}`}>
{prop} {getCharacterForOperator(c.operator)}{' '}
<code>{c.value ? c.value : '-'}</code>
</li>
);
});
}
})}
</ul>
);
}
} }
return null; return (
<details>
<summary>{t('Filter')}</summary>
<SyntaxHighlighter data={filter(f, ['__typename'])} />
</details>
);
} }
@@ -11,7 +11,7 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
<MemoryRouter> <MemoryRouter>
<MockedProvider mocks={mocks}> <MockedProvider mocks={mocks}>
<Table> <Table>
<tbody data-testid="wrapper"> <tbody>
<OracleMarkets id={id} /> <OracleMarkets id={id} />
</tbody> </tbody>
</Table> </Table>
@@ -23,7 +23,8 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
describe('Oracle Markets component', () => { describe('Oracle Markets component', () => {
it('Renders a row with the market ID initially', () => { it('Renders a row with the market ID initially', () => {
const res = render(renderComponent('123', [])); const res = render(renderComponent('123', []));
expect(res.getByTestId('wrapper')).toBeEmptyDOMElement(); expect(res.getByText('Market')).toBeInTheDocument();
expect(res.getByText('123')).toBeInTheDocument();
}); });
it('Renders that this is a termination source for the right market', async () => { it('Renders that this is a termination source for the right market', async () => {
@@ -33,58 +34,21 @@ describe('Oracle Markets component', () => {
}, },
result: { result: {
data: { data: {
oracleSpecsConnection: {
edges: [
{
node: {
dataConnection: {
edges: [
{
node: {
externalData: {
data: {
data: {
name: '123',
value: '456',
},
},
},
},
},
],
},
dataSourceSpec: {
spec: {
id: '789',
state: 'Active',
status: 'Active',
data: {
sourceType: {},
},
},
},
},
},
],
},
marketsConnection: { marketsConnection: {
edges: [ edges: [
{ {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: '123', id: '123',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: '456', id: '456',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: '789', id: '789',
status: 'Active',
}, },
}, },
}, },
@@ -95,18 +59,15 @@ describe('Oracle Markets component', () => {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: 'abc', id: 'abc',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: 'def', id: 'def',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: 'ghi', id: 'ghi',
status: 'Active',
}, },
}, },
}, },
@@ -130,58 +91,21 @@ describe('Oracle Markets component', () => {
}, },
result: { result: {
data: { data: {
oracleSpecsConnection: {
edges: [
{
node: {
dataConnection: {
edges: [
{
node: {
externalData: {
data: {
data: {
name: '123',
value: '456',
},
},
},
},
},
],
},
dataSourceSpec: {
spec: {
id: '789',
state: 'Active',
status: 'Active',
data: {
sourceType: {},
},
},
},
},
},
],
},
marketsConnection: { marketsConnection: {
edges: [ edges: [
{ {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: '123', id: '123',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: '789', id: '789',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: '123', id: '123',
status: 'Active',
}, },
}, },
}, },
@@ -192,18 +116,15 @@ describe('Oracle Markets component', () => {
node: { node: {
__typename: 'Market', __typename: 'Market',
id: 'abc', id: 'abc',
state: 'Active',
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
product: { product: {
__typename: 'Future', __typename: 'Future',
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
id: 'def', id: 'def',
status: 'Active',
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
id: 'ghi', id: 'ghi',
status: 'Active',
}, },
}, },
}, },
@@ -1,4 +1,5 @@
import { getNodes } from '@vegaprotocol/utils'; import { getNodes } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { MarketLink } from '../../../components/links'; import { MarketLink } from '../../../components/links';
import { TableRow, TableCell, TableHeader } from '../../../components/table'; import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets'; import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets';
@@ -9,7 +10,7 @@ interface OracleMarketsProps {
} }
/** /**
* Slightly misleading names, OracleMarkets lists the market (almost always singular) * Slightly misleadlingly names, OracleMarkets lists the market (almost always singular)
* to which an oracle is attached. It also checks what it triggers, by checking on the * to which an oracle is attached. It also checks what it triggers, by checking on the
* market whether it is attached to the dataSourceSpecForSettlementData or ..TradingTermination * market whether it is attached to the dataSourceSpecForSettlementData or ..TradingTermination
*/ */
@@ -23,37 +24,36 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
); );
if (markets) { if (markets) {
const m = markets.filter((market) => { const m = markets.find((m) => {
const p = market.tradableInstrument.instrument.product; const p = m.tradableInstrument.instrument.product;
if ( if (
((p.__typename === 'Future' || p.__typename === 'Perpetual') && p?.dataSourceSpecForSettlementData?.id === id ||
p.dataSourceSpecForSettlementData.id === id) || p?.dataSourceSpecForTradingTermination?.id === id
('dataSourceSpecForTradingTermination' in p &&
p.dataSourceSpecForTradingTermination.id === id) ||
(p.__typename === 'Perpetual' &&
p.dataSourceSpecForSettlementSchedule.id === id)
) { ) {
return true; return true;
} }
return false; return false;
}); });
if (m && m.length > 0) { if (m && m.id) {
return ( return (
<> <TableRow modifier="bordered">
{m.map((market) => ( <TableHeader scope="row">{getLabel(id, m)}</TableHeader>
<TableRow modifier="bordered" key={`m-${market.id}`}> <TableCell modifier="bordered" data-testid={`m-${m.id}`}>
<TableHeader scope="row">{getLabel(id, market)}</TableHeader> <MarketLink id={m.id} />
<TableCell modifier="bordered" data-testid={`m-${market.id}`}> </TableCell>
<MarketLink id={market.id} /> </TableRow>
</TableCell>
</TableRow>
))}
</>
); );
} }
} }
return null; return (
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Market')}</TableHeader>
<TableCell modifier="bordered">
<span>{id}</span>
</TableCell>
</TableRow>
);
} }
export function getLabel( export function getLabel(
@@ -61,32 +61,8 @@ export function getLabel(
m: ExplorerOracleForMarketsMarketFragment | null m: ExplorerOracleForMarketsMarketFragment | null
): string { ): string {
const settlementId = const settlementId =
((m?.tradableInstrument?.instrument?.product?.__typename === 'Future' || m?.tradableInstrument?.instrument?.product?.dataSourceSpecForSettlementData
m?.tradableInstrument?.instrument?.product?.__typename === 'Perpetual') && ?.id || null;
m?.tradableInstrument?.instrument?.product
?.dataSourceSpecForSettlementData?.id) ||
null;
const terminationId = return id === settlementId ? 'Settlement for' : 'Termination for';
(m?.tradableInstrument?.instrument?.product?.__typename === 'Future' &&
m?.tradableInstrument?.instrument?.product
?.dataSourceSpecForTradingTermination?.id) ||
null;
const settlementScheduleId =
(m?.tradableInstrument?.instrument?.product?.__typename === 'Perpetual' &&
m?.tradableInstrument?.instrument?.product
?.dataSourceSpecForSettlementSchedule?.id) ||
null;
switch (id) {
case settlementId:
return 'Settlement for';
case terminationId:
return 'Termination for';
case settlementScheduleId:
return 'Settlement schedule for';
default:
return 'Unknown';
}
} }
@@ -67,9 +67,6 @@ export function OracleSigners({ sourceType }: OracleDetailsSignersProps) {
if (sourceType.__typename !== 'DataSourceDefinitionExternal') { if (sourceType.__typename !== 'DataSourceDefinitionExternal') {
return null; return null;
} }
if (!('signers' in sourceType.sourceType)) {
return null;
}
const signers = sourceType.sourceType.signers; const signers = sourceType.sourceType.signers;
if (!signers || signers.length === 0) { if (!signers || signers.length === 0) {
@@ -1,37 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import fromUnixTime from 'date-fns/fromUnixTime';
import { getCharacterForOperator } from './operator';
import type { Condition } from '@vegaprotocol/types';
export interface OracleSpecCondition {
data: Condition;
type?: string;
}
export function OracleSpecCondition({ data, type }: OracleSpecCondition) {
const c = getCharacterForOperator(data.operator);
const value =
type === 'DataSourceSpecConfigurationTime' && data.value
? fromUnixTime(parseInt(data.value)).toLocaleString()
: data.value;
const typeLabel =
type === 'DataSourceSpecConfigurationTime' ? (
<span>{t('Time')}</span>
) : (
type
);
return (
<li key={`${typeLabel}${c}${value}`}>
{typeLabel} {c}{' '}
{value && (
<span
title={data.value || value}
className="underline decoration-dotted"
>
{value}
</span>
)}
</li>
);
}
@@ -1,44 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { DataSourceSpecConfigurationTimeTrigger } from '@vegaprotocol/types';
import secondsToMinutes from 'date-fns/secondsToMinutes';
import fromUnixTime from 'date-fns/fromUnixTime';
export interface OracleSpecInternalTimeTriggerProps {
data: DataSourceSpecConfigurationTimeTrigger;
}
export function OracleSpecInternalTimeTrigger({
data,
}: OracleSpecInternalTimeTriggerProps) {
return (
<div>
<span>{t('Time')}</span>,&nbsp;
{data.triggers.map((tr) => {
return (
<span>
{tr?.initial ? (
<span title={`${tr.initial}`}>
<strong>{t('starting at')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{fromUnixTime(tr.initial).toLocaleString()}
</em>
</span>
) : (
''
)}
{tr?.every ? (
<span title={`${tr.every} ${t('seconds')}`}>
, <strong>{t('every')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{secondsToMinutes(tr.every)} {t('minutes')}
</em>{' '}
</span>
) : (
''
)}
</span>
);
})}
</div>
);
}
@@ -1,21 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { ConditionOperator } from '@vegaprotocol/types';
export function getCharacterForOperator(
operator: ConditionOperator
): React.ReactElement {
switch (operator) {
case 'OPERATOR_EQUALS':
return <span title={t('equals')}>=</span>;
case 'OPERATOR_GREATER_THAN':
return <span title={t('greater than')}>&gt;</span>;
case 'OPERATOR_GREATER_THAN_OR_EQUAL':
return <span title={t('greater than or equal')}>&ge;</span>;
case 'OPERATOR_LESS_THAN':
return <span title={t('less than')}>&lt;</span>;
case 'OPERATOR_LESS_THAN_OR_EQUAL':
return <span title={t('less than or equal')}>&le;</span>;
}
return <span>{operator}</span>;
}
@@ -14,9 +14,7 @@ import { OracleFilter } from './oracle-filter';
import { OracleDetailsType } from './oracle-details-type'; import { OracleDetailsType } from './oracle-details-type';
import { OracleMarkets } from './oracle-markets'; import { OracleMarkets } from './oracle-markets';
import { OracleSigners } from './oracle-signers'; import { OracleSigners } from './oracle-signers';
import { OracleEthSource } from './oracle-eth-source'; import OracleLink from '../../../components/links/oracle-link/oracle-link';
import Hash from '../../../components/links/hash';
import { getStatusString } from '../../../components/links/oracle-link/oracle-link';
export type SourceType = export type SourceType =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType']; ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
@@ -40,8 +38,10 @@ export const OracleDetails = ({
id, id,
dataSource, dataSource,
dataConnection, dataConnection,
showBroadcasts = false,
}: OracleDetailsProps) => { }: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType; const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.edges?.length || 0;
return ( return (
<div> <div>
@@ -49,27 +49,23 @@ export const OracleDetails = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableHeader scope="row">{t('ID')}</TableHeader> <TableHeader scope="row">{t('ID')}</TableHeader>
<TableCell modifier="bordered"> <TableCell modifier="bordered">
<Hash text={id} /> <OracleLink id={id} />
</TableCell> </TableCell>
</TableRow> </TableRow>
<OracleDetailsType sourceType={sourceType} /> <OracleDetailsType sourceType={sourceType} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Status')}</TableHeader>
<TableCell modifier="bordered">
{getStatusString(dataSource.dataSourceSpec.spec.status)}
</TableCell>
</TableRow>
<OracleSigners sourceType={sourceType} /> <OracleSigners sourceType={sourceType} />
<OracleEthSource sourceType={sourceType} />
<OracleMarkets id={id} /> <OracleMarkets id={id} />
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableHeader scope="row">{t('Filter')}</TableHeader> <TableHeader scope="row">{t('Matched data')}</TableHeader>
<TableCell modifier="bordered"> <TableCell modifier="bordered">
<OracleFilter data={dataSource} /> {showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'}
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableWithTbody> </TableWithTbody>
{dataConnection ? <OracleData data={dataConnection} /> : null} <OracleFilter data={dataSource} />
{showBroadcasts && dataConnection ? (
<OracleData data={dataConnection} />
) : null}
</div> </div>
); );
}; };

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