Compare commits

..
Author SHA1 Message Date
Madalina Raicu ced248345d fix: release script for mainnet 2023-05-09 16:55:36 +01:00
1878 changed files with 189110 additions and 79692 deletions
-1
View File
@@ -1 +0,0 @@
node_modules
+7 -13
View File
@@ -1,7 +1,7 @@
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx", "eslint-plugin-unicorn", "jsx-a11y", "jest"],
"plugins": ["@nrwl/nx", "eslint-plugin-unicorn", "jsx-a11y", "jest"],
"settings": {
"jsx-a11y": {
"components": {
@@ -18,7 +18,7 @@
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"extends": ["plugin:jsx-a11y/strict"],
"rules": {
"@nx/enforce-module-boundaries": [
"@nrwl/nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
@@ -51,13 +51,12 @@
"ul": ["list"],
"ol": ["list"]
}
],
"no-console": ["error", { "allow": ["warn", "error"] }]
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"extends": ["plugin:@nrwl/nx/typescript"],
"rules": {
"@typescript-eslint/ban-ts-comment": [
"error",
@@ -81,19 +80,14 @@
},
{
"files": ["*.spec.ts", "*.spec.tsx"],
"extends": ["plugin:@nx/typescript", "plugin:jest/recommended"],
"extends": ["plugin:@nrwl/nx/typescript", "plugin:jest/recommended"],
"rules": {
"jest/consistent-test-it": [
"error",
{
"fn": "it"
}
]
"jest/consistent-test-it": ["error", { "fn": "it" }]
}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"extends": ["plugin:@nrwl/nx/javascript"],
"rules": {}
}
]
+3 -2
View File
@@ -14,7 +14,8 @@ What we need to achieve and who for
## Tasks
- [ ]
- [ ]
- [ ] What do we need to do first
- [ ] and then what?
- [ ] Etc.
## Additional details / background info
-28
View File
@@ -1,28 +0,0 @@
---
name: Feature Epic
about: A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
title: 'FEATURE EPIC: '
labels: feature-epic
---
## Core Feature
<Name>
## Tasks
- [ ] Define high level requirements
- [ ] Create basic mockups
- [ ] Update "API Requirements" in core spec
- [ ] Update "User-Interface Spec" in relevant front end repo
- [ ] Create detailed user stories using normal template
## High Level Requirements
## Basic Mockups
## Link to API Requirements in Core spec
## Link to User Interface Specs
## Linked User Stories
+4 -7
View File
@@ -22,14 +22,11 @@ So that
## Tasks
- [ ] UX (if needed)
- [ ] Design (if needed)
- [ ] Explore and sketch
- [ ] Team and stakeholder review
- [ ] Specs reviewed and created or adjusted
- [ ] Implementation
- [ ] Testing (unit and/or e2e)
- [ ] Code review
- [ ] QA review
- [ ] Visual Design
- [ ] Team review
- [ ] Etc.
## Sketch
@@ -1,119 +0,0 @@
name: After Release
on:
release:
types: [published]
jobs:
after-release:
runs-on: ubuntu-22.04
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Wait for publish to complete
uses: lewagon/wait-on-check-action@v1.3.1
with:
ref: ${{ github.event.release.tag_name }}
check-name: '(CD) publish dist / trading'
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
- name: resolve ipfs hashes for release
run: |
echo "Name: ${{ github.event.release.name }}"
echo "Description: ${{ github.event.release.body }}"
echo "Tag: ${{ github.event.release.tag_name }}"
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
echo IPFS_V0=$(cat ipfs-hash) >> $GITHUB_ENV
echo IPFS_V1=$(ipfs cid format -v 1 -b base32 $(cat ipfs-hash)) >> $GITHUB_ENV
- name: Edit Release
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: ${{ github.event.release.id }}
body: |
___
# Deployments
* https://explorer.vega.xyz
* https://governance.vega.xyz
# IPFS releases
The IPFS hash of this release of the Trading app is:
CIDv0: ${{ env.IPFS_V0 }}
CIDv1: ${{ env.IPFS_V1 }}
You can always access the latest IPFS release by visiting [console.vega.xyz](https://console.vega.xyz).
You can also access Trading directly from an IPFS gateway.
BEWARE: The Trading interface uses [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. You should always use an IPFS gateway that enforces [origin separation](https://ipfs.github.io/public-gateway-checker/).
Your settings are not remembered across different URLs.
IPFS gateways:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
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
+1 -1
View File
@@ -6,7 +6,7 @@ name: 'Add Issues To Project Board'
types:
- opened
env:
GH_TOKEN: ${{ secrets.PROJECT_MANAGE_ACTION }}
GH_TOKEN: ${{ secrets.GH_NEW_CARD_TO_PROJECT }}
PROJECT_ID: ${{ secrets.FRONT_END_PROJECT_ID }}
ISSUE_ID: ${{ github.event.issue.node_id }}
USER: ${{ github.actor }}
+74 -157
View File
@@ -5,24 +5,21 @@ on:
branches:
- release/*
- develop
- main
pull_request:
types:
- opened
- ready_for_review
- reopened
- edited
- synchronize
jobs:
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
name: 'Cache yarn modules'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Cache node modules
id: cache
uses: actions/cache@v3
@@ -40,21 +37,28 @@ jobs:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: yarn install
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile
lint-format:
timeout-minutes: 20
lint-pr-title:
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
runs-on: ubuntu-22.04
name: '(CI) lint + format check'
name: '(CI) lint + unit test + build'
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
@@ -83,151 +87,88 @@ jobs:
- 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
- name: Build affected
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
# See affected apps
- name: See affected apps
run: |
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
python3 tools/ci/check-affected.py --github-ref="${{ github.ref }}" --branch-slug="$branch_slug" --event-name="${{ github.event_name }}"
echo ">>>> debug"
echo "NX Version: $nx_version"
echo "NX_BASE: ${{ env.NX_BASE }}"
echo "NX_HEAD: ${{ env.NX_HEAD }}"
echo ">>>> eof debug"
- name: Verify script result
run: |
echo "Check outputs from script"
echo "projects: ${{ env.PROJECTS }}"
echo "projects-e2e: ${{ env.PROJECTS_E2E }}"
echo "preview_governance: ${{ env.PREVIEW_GOVERNANCE }}"
echo "preview_trading: ${{ env.PREVIEW_TRADING }}"
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
echo -n "Affected projects: $affected"
branch_slug="$(echo ${{ github.head_ref || github.ref_name }} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
projects_e2e=""
preview_governance="not deployed"
preview_trading="not deployed"
preview_explorer="not deployed"
if [[ -z "$projects_e2e" ]]; then
projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
else
if [[ $affected == *"governance"* ]]; then
projects_e2e+='"governance-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if [[ $affected == *"trading"* ]]; then
projects_e2e+='"trading-e2e" '
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if [[ $affected == *"explorer"* ]]; then
projects_e2e+='"explorer-e2e" '
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
fi
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
- name: Build affected
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
outputs:
projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }}
preview_governance: ${{ env.PREVIEW_GOVERNANCE }}
preview_trading: ${{ env.PREVIEW_TRADING }}
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
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]
needs: lint-test-build
name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke'
console-e2e:
needs: [build-sources, check-e2e-needed]
name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml
secrets: inherit
if: needs.check-e2e-needed.outputs.run-tests == 'true' && contains(needs.build-sources.outputs.projects, 'trading')
with:
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
tags: '@smoke @regression'
publish-dist:
needs: build-sources
needs: lint-test-build
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: ${{ needs.lint-test-build.outputs.projects != '[]' }}
uses: ./.github/workflows/publish-dist.yml
secrets: inherit
with:
projects: ${{ needs.build-sources.outputs.projects }}
projects: ${{ needs.lint-test-build.outputs.projects }}
dist-check:
runs-on: ubuntu-latest
needs:
- publish-dist
- build-sources
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
timeout-minutes: 60
- lint-test-build
if: ${{ github.event_name == 'pull_request' }}
name: '(CD) comment preview links'
steps:
- name: Find Comment
@@ -237,50 +178,26 @@ jobs:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Previews
- name: Wait for deployments
run: |
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
if [[ "${{ needs.build-sources.outputs.preview_governance }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_governance }}"; do
echo "waiting for governance preview: ${{ needs.build-sources.outputs.preview_governance }}"
sleep 5
done
fi
if [[ "${{ needs.build-sources.outputs.preview_explorer }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_explorer }}"; do
echo "waiting for explorer preview: ${{ needs.build-sources.outputs.preview_explorer }}"
sleep 5
done
fi
if [[ "${{ needs.build-sources.outputs.preview_trading }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_trading }}"; do
echo "waiting for trading preview: ${{ needs.build-sources.outputs.preview_trading }}"
sleep 5
done
fi
if [[ "${{ needs.build-sources.outputs.preview_tools }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_tools }}"; do
echo "waiting for tools preview: ${{ needs.build-sources.outputs.preview_tools }}"
sleep 5
done
fi
- name: Create comment
uses: peter-evans/create-or-update-comment@v3
if: ${{ steps.fc.outputs.comment-id == 0 }}
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
Previews
* governance: ${{ needs.build-sources.outputs.preview_governance }}
* explorer: ${{ needs.build-sources.outputs.preview_explorer }}
* trading: ${{ needs.build-sources.outputs.preview_trading }}
* tools: ${{ needs.build-sources.outputs.preview_tools }}
Previews:
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
# Report single result at the end, to avoid mess with required checks in PR
cypress-check:
name: '(CI) cypress - check'
runs-on: ubuntu-latest
needs: cypress
steps:
- run: echo Done!
# Report single result at the end, to avoid mess with required checks in PR
cypress-result:
if: ${{ always() }}
needs: cypress
runs-on: ubuntu-22.04
-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
-232
View File
@@ -1,232 +0,0 @@
name: (CI) Console tests
on:
workflow_call:
inputs:
github-sha:
required: true
type: string
workflow_dispatch:
inputs:
console-test-branch:
type: choice
description: 'main: v0.72.14, develop: v0.73.0-preview7'
options:
- main
- develop
jobs:
create-docker-image:
name: Create docker image for console-test
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
#----------------------------------------------
# check-out frontend-monorepo
#----------------------------------------------
- name: Checkout frontend-monorepo
uses: actions/checkout@v3
with:
ref: ${{ inputs.github-sha || github.sha }}
#----------------------------------------------
# cache node modules
#----------------------------------------------
- name: setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: yarn
- name: Cache node modules
id: cache
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
# comment out "restore-keys" if you need to rebuild yarn from 0
restore-keys: |
${{ runner.os }}-cache-node-modules-
#----------------------------------------------
# install deps if cache missing
#----------------------------------------------
- name: yarn install
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile
#----------------------------------------------
# build trading
#----------------------------------------------
- name: Build trading app
run: |
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
#----------------------------------------------
- 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: |
echo ${{ steps.docker_build.outputs.digest }}
echo ${{ steps.docker_build.outputs.imageid }}
- 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
#----------------------------------------------
- name: Download docker image from previous job
uses: actions/download-artifact@v3
with:
name: console-image
path: /tmp
- name: Load Docker image
run: |
docker load --input /tmp/console-image.tar
docker image ls -a
#----------------------------------------------
# check-out tests repo
#----------------------------------------------
- name: Checkout console test repo
uses: actions/checkout@v3
with:
repository: vegaprotocol/console-test
ref: ${{ needs.console-test-branch.outputs.console-branch }}
- 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 -----
#----------------------------------------------
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
#----------------------------------------------
# ----- install & configure poetry -----
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
#----------------------------------------------
# install python dependencies
#----------------------------------------------
- name: Install dependencies
run: poetry install --no-interaction --no-root
#----------------------------------------------
# install vega binaries
#----------------------------------------------
- name: Install vega binaries
run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }}
#----------------------------------------------
# install playwright
#----------------------------------------------
- name: install playwright
run: poetry run playwright install --with-deps chromium
#----------------------------------------------
# run tests
#----------------------------------------------
- name: Run tests
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
#----------------------------------------------
# upload traces
#----------------------------------------------
- name: Upload Playwright Trace
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-trace
path: ./traces/
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
+1
View File
@@ -33,4 +33,5 @@ jobs:
config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4 -30
View File
@@ -13,36 +13,14 @@ on:
type: string
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:
strategy:
fail-fast: false
matrix:
project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }}
needs: runner-choice
runs-on: ${{ needs.runner-choice.outputs.runner }}
timeout-minutes: 120
runs-on: self-hosted-runner
timeout-minutes: 40
steps:
# Checks if skip cache was requested
- name: Set skip-nx-cache flag
@@ -55,11 +33,9 @@ jobs:
with:
fetch-depth: 0
path: './frontend-monorepo'
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
id: cache-node-modules
uses: actions/cache@v3
with:
path: |
@@ -69,7 +45,6 @@ jobs:
# Install frontend dependencies
- name: Install root dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
@@ -85,14 +60,13 @@ jobs:
- name: Run Vegacapsule network and Vega wallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
timeout-minutes: 10
######
## Run some 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 }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
@@ -124,4 +98,4 @@ jobs:
if: ${{ failure() }}
with:
name: test-report-${{ matrix.project }}
path: frontend-monorepo/apps/${{ matrix.project }}/cypress/reports
path: frontend-monorepo/apps/trading-e2e/cypress/reports
+9 -11
View File
@@ -2,12 +2,7 @@
name: Verify PR title
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
workflow_call:
jobs:
lint_pr:
@@ -20,12 +15,15 @@ jobs:
- name: Setup node
uses: actions/setup-node@v3
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
run: |
rm package.json
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+32
View File
@@ -0,0 +1,32 @@
name: Generate tranches
on:
schedule:
- cron: '0 */6 * * *'
jobs:
master:
name: Generate Queries
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
with:
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- name: Install root dependencies
run: yarn install
- name: Generate queries
run: node ./scripts/get-tranches.js
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: 'chore: update tranches'
commit_options: '--no-verify --signoff'
skip_fetch: true
skip_checkout: true
+57 -229
View File
@@ -19,53 +19,6 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Init variables
run: |
echo IS_PR=false >> $GITHUB_ENV
echo IS_MAINNET_RELEASE=false >> $GITHUB_ENV
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELEASE=false >> $GITHUB_ENV
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
echo IS_MAIN_IMAGE=false >> $GITHUB_ENV
- name: Is dev image
if: ${{ github.ref_name == 'develop' && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
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
if: ${{ github.event_name == 'pull_request' }}
run: |
echo IS_PR=true >> $GITHUB_ENV
- name: Is mainnet release
if: ${{ contains(github.ref, 'release/mainnet') && !contains(github.ref, 'mirror') }}
run: |
echo IS_MAINNET_RELEASE=true >> $GITHUB_ENV
- name: Is testnet release
if: ${{ contains(github.ref, 'release/testnet') }}
run: |
echo IS_TESTNET_RELEASE=true >> $GITHUB_ENV
- name: Is IPFS Release
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( env.IS_MAINNET_RELEASE == 'true' || env.IS_TESTNET_RELEASE == 'true' ) }}
run: |
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' && github.ref_name != 'main'}}
run: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV
- name: Set up QEMU
id: quemu
@@ -77,21 +30,13 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
if: ${{ env.IS_PR == 'true' }}
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup node
uses: actions/setup-node@v3
with:
@@ -106,36 +51,46 @@ jobs:
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
# https://docs.github.com/en/actions/learn-github-actions/contexts
- name: Define dist variables
if: ${{ github.event_name == 'push' }}
- name: Define variables
run: |
python3 tools/ci/define-dist-variables.py --github-ref="${{ github.ref }}" --app="${{ matrix.app }}"
- name: Verify script result
if: ${{ github.event_name == 'push' }}
run: |
echo "BUCKET_NAME=${{ env.BUCKET_NAME }}"
echo "ENV_NAME=${{ env.ENV_NAME }}"
envName=''
dockerfile="dist.Dockerfile"
if [[ "${{ github.event_name }}" = "push" ]]; then
domain="vega.rocks"
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then
domain="vega.community"
if [[ "${{ matrix.app }}" = "trading" ]]; then
dockerfile="ipfs.Dockerfile"
fi
fi
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet1"
fi
bucketName="${{ matrix.app }}.${envName}.${domain}"
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
fi
nodeVersion=$(cat .nvmrc | head -n 1)
echo ENV_NAME=${envName} >> $GITHUB_ENV
echo NODE_VERSION=${nodeVersion} >> $GITHUB_ENV
echo DOCKERFILE=docker/${dockerfile} >> $GITHUB_ENV
- name: Build local dist
if: ${{ env.DOCKERFILE != 'docker/ipfs.Dockerfile' }}
run: |
envCmd=""
flags=""
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
envCmd="yarn env-cmd -f ./apps/${{ matrix.app }}/.env.${{ env.ENV_NAME }}"
if [[ "${{ env.ENV_NAME }}" != "ops-vega" ]]; then
flags="--env=${{ env.ENV_NAME }}"
fi
fi
if [ "${{ matrix.app }}" = "trading" ]; then
$envCmd yarn nx export trading || (yarn install && $envCmd yarn nx export trading)
yarn nx export trading $flags || (yarn install && yarn nx export trading $flags)
DIST_LOCATION=dist/apps/trading/exported
elif [ "${{ matrix.app }}" = "ui-toolkit" ]; then
NODE_ENV=production yarn nx run ui-toolkit:build-storybook
DIST_LOCATION=dist/storybook/ui-toolkit
elif [ "${{ matrix.app }}" = "static" ]; then
yarn nx build static || (yarn install && yarn nx build static)
else
$envCmd yarn nx build ${{ matrix.app }} || (yarn install && $envCmd yarn nx build ${{ matrix.app }})
fi
if [[ -z "$DIST_LOCATION" ]]; then
yarn nx build ${{ matrix.app }} $flags || (yarn install && yarn nx build ${{ matrix.app }} $flags)
DIST_LOCATION=dist/apps/${{ matrix.app }}
fi
mv $DIST_LOCATION dist-result
@@ -143,92 +98,62 @@ jobs:
- name: Build and export to local Docker
id: docker_build
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
uses: docker/build-push-action@v3
with:
context: .
file: docker/node-outside-docker.Dockerfile
file: ${{ env.DOCKERFILE }}
load: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest
if: ${{ env.IS_PR == 'true' }}
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image
if: ${{ github.event_name == 'pull_request' || ( env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' ) }}
run: |
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 /bin/sh -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash
if [[ "${{ env.DOCKERFILE }}" = "docker/ipfs.Dockerfile" ]]; then
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
fi
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 .'
- name: Publish dist as docker image (ghcr)
- name: Copy dist to local filesystem
if: ${{ env.DOCKERFILE == 'docker/ipfs.Dockerfile' && github.event_name == 'push' }}
run: |
docker create --name=dist ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
docker cp dist:/usr/share/nginx/html dist
echo "check local dist files"
tree dist/html
mv dist/html dist-result
- name: Publish dist as docker image
uses: docker/build-push-action@v3
continue-on-error: true
id: ghcr-push
if: ${{ env.IS_PR == 'true' }}
if: ${{ github.event_name == 'pull_request' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
file: ${{ env.DOCKERFILE }}
push: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ env.NODE_VERSION }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
- name: Publish dist as docker image (docker hub)
uses: docker/build-push-action@v3
continue-on-error: true
id: dockerhub-push
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
push: true
build-args: |
APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
- name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3
if: ${{ steps.ghcr-push.outcome == 'failure' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
push: true
build-args: |
APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
- name: Publish dist as docker image (docker hub - retry)
uses: docker/build-push-action@v3
if: ${{ steps.dockerhub-push.outcome == 'failure' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
push: true
build-args: |
APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
# bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3
uses: jakejarvis/s3-sync-action@master
# s3 releases are not happening for trading on mainnet - it's IPFS
if: ${{ env.IS_S3_RELEASE == 'true' }}
if: ${{ github.event_name == 'push' }}
with:
args: --acl private --follow-symlinks --delete
env:
@@ -238,106 +163,9 @@ jobs:
AWS_REGION: 'eu-west-1'
SOURCE_DIR: 'dist-result'
- name: Install aws CLI
if: ${{ env.IS_S3_RELEASE == 'true' }}
uses: unfor19/install-aws-cli-action@master
- name: Perform cache invalidation
if: ${{ env.IS_S3_RELEASE == 'true' }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'eu-west-1'
run: |
echo "Looking for distribution for bucket: ${{ env.BUCKET_NAME }}"
id=$(aws cloudfront list-distributions | jq -Mrc '.DistributionList.Items | .[] | select(.DefaultCacheBehavior.TargetOriginId == "${{ env.BUCKET_NAME }}") | .Id')
echo "Found id is: ${id}"
aws cloudfront create-invalidation --distribution-id $id --paths "/*"
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ env.IS_PR == 'true' }}
if: ${{ github.event_name == 'pull_request' }}
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
- name: Trigger fleek deployment
# release to ipfs happens only on mainnet (represented by main branch) for trading
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
run: |
if [[ "${{ env.IS_MAINNET_RELEASE }}" = "true" ]]; then
# display info about app
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
elif [[ "${{ env.IS_TESTNET_RELEASE }}" = "true" ]]; then
# display info about app
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"79baaeca-1952-4ae7-a256-f668cfc1d68e\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"79baaeca-1952-4ae7-a256-f668cfc1d68e\"){id status}}"}' \
https://api.fleek.co/graphql
fi
- name: Check out ipfs-redirect
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
uses: actions/checkout@v3
with:
repository: 'vegaprotocol/ipfs-redirect'
path: 'ipfs-redirect'
fetch-depth: '0'
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
- name: Update interstitial page to point to the new console
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
env:
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
run: |
# set CID
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
(
cd ipfs-redirect
# configure git
git status
cat .git/config
git config --global user.email "vega-ci-bot@vega.xyz"
git config --global user.name "vega-ci-bot"
# update CID files
if [[ "${{ env.IS_MAINNET_RELEASE }}" = "true" ]]; then
echo $new_hash > cidv0-mainnet.txt
echo $new_cid > cidv1-mainnet.txt
git add cidv0-mainnet.txt cidv1-mainnet.txt
elif [[ "${{ env.IS_TESTNET_RELEASE }}" = "true" ]]; then
echo $new_hash > cidv0-fairground.txt
echo $new_cid > cidv1-fairground.txt
git add cidv0-fairground.txt cidv1-fairground.txt
fi
# create commit
if ! git diff --cached --exit-code; then
commit_msg="Automated hash update from ${{ github.ref }}"
git commit -m "$commit_msg"
git push -u origin "main"
fi
)
-1
View File
@@ -15,7 +15,6 @@ on:
- types
- utils
- i18n
- wallet
jobs:
publish:
-84
View File
@@ -1,84 +0,0 @@
name: 'Rollback console'
on:
workflow_dispatch:
inputs:
version:
description: 'Version that should be set on rollback'
required: true
type: string
jobs:
rollback:
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Retag mainnet
run: |
docker pull vegaprotocol/trading:${{ inputs.version }}
docker tag vegaprotocol/trading:${{ inputs.version }} vegaprotocol/trading:mainnet
docker push vegaprotocol/trading:mainnet
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash
- name: Trigger fleek deployment
run: |
# display info about app
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql
- name: Check out ipfs-redirect
uses: actions/checkout@v3
with:
repository: 'vegaprotocol/ipfs-redirect'
path: 'ipfs-redirect'
fetch-depth: '0'
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
- name: Update console.vega.xyz DNS to redirect to the new console
env:
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
run: |
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
git config --global user.email "vega-ci-bot@vega.xyz"
git config --global user.name "vega-ci-bot"
echo $new_hash > ipfs-redirect/cidv0.txt
echo $new_cid > ipfs-redirect/cidv1.txt
(
cd ipfs-redirect
git status
branch_name="rollback-to-$new_hash"
git checkout -b "$branch_name"
commit_msg="hash rollback to $new_hash"
git add cidv0.txt cidv1.txt
git commit -m "$commit_msg"
git push -u origin "$branch_name" --force-with-lease
pr_url="$(gh pr create --title "${commit_msg}" --body 'automated pull request to update CIDs')"
echo $pr_url
# once auto merge get's enabled on documentation repo let's do follow up
sleep 5
gh pr merge "${pr_url}" --delete-branch --squash --admin
)
+1 -4
View File
@@ -2,7 +2,6 @@
# compiled output
/dist
/dist-result
/tmp
/out-tsc
/tools/executors/**/*.js
@@ -49,6 +48,4 @@ cypress.env.json
.next
#cypress
/apps/**/cypress/reports/
/apps/**/cypress/downloads/
/apps/**/fixtures/wallet/node**
/apps/trading-e2e/cypress/reports/
+3
View File
@@ -3,3 +3,6 @@
# Lint commit messages to ensure they follow conventional commit standards
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
+29
View File
@@ -0,0 +1,29 @@
module.exports = {
stories: [],
addons: [
'@storybook/addon-actions',
'@storybook/addon-viewport',
{
name: '@storybook/addon-docs',
options: {
configureJSX: true,
babelOptions: {},
sourceLoaderOptions: null,
transcludeMarkdown: true,
},
},
'@storybook/addon-controls',
'@storybook/addon-backgrounds',
'@storybook/addon-toolbars',
'@storybook/addon-measure',
'@storybook/addon-outline',
'@storybook/addon-a11y',
],
// uncomment the property below if you want to apply some webpack config globally
// webpackFinal: async (config, { configType }) => {
// // Make whatever fine-grained changes you need that should apply to all storybook configs
// // Return the altered config
// return config;
// },
};
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.base.json",
"exclude": [
"../**/*.spec.js",
"../**/*.test.js",
"../**/*.spec.ts",
"../**/*.test.ts",
"../**/*.spec.tsx",
"../**/*.test.tsx",
"../**/*.spec.jsx",
"../**/*.test.jsx"
],
"include": ["../**/*"]
}
-1
View File
@@ -1,3 +1,2 @@
* @vegaprotocol/frontend
* @vegaprotocol/frontend-qa
*.graphql @vegaprotocol/core
-20
View File
@@ -1,20 +0,0 @@
.PHONY: latest-release
latest-release:
gh release list | head -n 1 | awk '{print $1}'
.PHONY: show-latest-release
show-latest-release:
gh release view `gh release list | head -n 1 | awk '{print $1}'`
.PHONY: recalculate-ipfs
recalculate-ipfs:
echo "ipfs hash inside the image"
docker run --rm ${TAG} cat /ipfs-hash
echo "recalculating ipfs hash"
docker run --rm ${TAG} ipfs add -rQ /usr/share/nginx/html
.PHONY: eject-ipfs-hash
unpack:
docker create --name=dist ${TAG}
docker cp dist:/usr/share/nginx/html dist
docker rm dist
+9 -66
View File
@@ -103,82 +103,25 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work
Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more.
# 🐋 Hosting a console
# Docker & Vegacapsule
To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions).
## Docker
In order to run a container on port 3000:
```bash
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
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
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.
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:
```bash
./docker/prepare-dist.sh
```
The [Dockerfile](./dockerfiles) for running the frontends is pretty basic, merely building the application with the APP arg that is passed in and serving the application from [nginx](./nginx/nginx.conf). The only complexity that exists is that there is a script which allows the passing of run time environment variables to the containers. See configuration below for how to do this.
You can build any of the containers locally with the following command:
```bash
docker build -f docker/node-outside-docker.Dockerfile . --tag=[TAG]
docker build --dockerfile dockerfiles/Dockerfile.cra . --build-arg APP=[YOUR APP] --tag=[TAG]
```
### Verifying ipfs-hash of existing current application version
In order to run a container:
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:
```bash
docker run -p 3000:80 [TAG]
```
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)
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`
4. Download docker image with the desired release `docker pull $TAG`.
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`
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
Images ending with `.dist` are to pack locally created transpiled HTML files into nginx container for non-compatible with yarn architectures like M1 Mac
## Config
+2 -6
View File
@@ -1,3 +1,4 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_URL=http://localhost:3008/graphql
@@ -16,10 +17,5 @@ NX_EXPLORER_NETWORK_PARAMETERS=1
NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
CYPRESS_VEGA_URL=http://localhost:3008/graphql
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_WALLET_API_TOKEN=
CYPRESS_VEGA_URL=http://localhost:3008/graphql
+1 -2
View File
@@ -1,11 +1,10 @@
# App configuration variables
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://n04.d.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_ENV=DEVNET
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# App flags
NX_EXPLORER_ASSETS=1
+1 -1
View File
@@ -1,10 +1,10 @@
# App configuration variables
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
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
NX_EXPLORER_ASSETS=1
+1 -1
View File
@@ -1,10 +1,10 @@
# App configuration variables
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://tm.n07.testnet.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://lb.testnet.vega.xyz/tm/websocket
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET
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
NX_EXPLORER_ASSETS=1
+1 -3
View File
@@ -1,11 +1,10 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
reporter: '../../node_modules/cypress-mochawesome-reporter',
projectId: 'et4snf',
e2e: {
setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config);
return config;
},
@@ -23,7 +22,6 @@ module.exports = defineConfig({
viewportWidth: 1440,
viewportHeight: 900,
testIsolation: false,
experimentalMemoryManagement: true,
},
env: {
environment: 'CUSTOM',
+3 -4
View File
@@ -1,11 +1,10 @@
{
"name": "explorer-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/explorer-e2e/src",
"projectType": "application",
"targets": {
"e2e": {
"executor": "@nx/cypress:cypress",
"executor": "@nrwl/cypress:cypress",
"options": {
"cypressConfig": "apps/explorer-e2e/cypress.config.js",
"devServerTarget": "explorer:serve"
@@ -17,14 +16,14 @@
}
},
"lint": {
"executor": "@nx/linter:eslint",
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/explorer-e2e/**/*.{js,ts}"]
}
},
"build": {
"executor": "nx:run-commands",
"executor": "@nrwl/workspace:run-commands",
"outputs": [],
"options": {
"command": "yarn tsc --project ./apps/explorer-e2e/"
@@ -1,26 +0,0 @@
{
"proposalSubmission": {
"rationale": {
"title": "Test new asset proposal",
"description": "E2E test for proposals"
},
"terms": {
"newAsset": {
"changes": {
"name": "USDT Coin",
"symbol": "USDT",
"decimals": "18",
"quantum": "1",
"erc20": {
"contractAddress": "0xb404c51bbc10dcbe948077f18a4b8e553d160084",
"withdrawThreshold": "10",
"lifetimeLimit": "10"
}
}
},
"closingTimestamp": 1724339572,
"enactmentTimestamp": 1724339572,
"validationTimestamp": 1692799617
}
}
}
@@ -31,7 +31,7 @@ context('Asset page', { tags: '@regression' }, () => {
});
});
it.skip('should open details page when clicked on "View details"', () => {
it('should open details page when clicked on "View details"', () => {
cy.getAssets().then((assets) => {
assets.forEach((asset) => {
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
+10 -160
View File
@@ -1,169 +1,19 @@
import { createSuccessorMarketProposal } from '../support/governance.functions';
context('Market page', { tags: '@regression' }, function () {
//Tests set to skip until market bug for capsule checkpoint is fixed
context.skip('Market page', { tags: '@regression' }, function () {
describe('Verify elements on page', function () {
const marketHeaders = 'markets-heading';
const marketHeaders = '[data-testid="markets-header"]';
const marketNavigation = 'a[href="/markets"]';
before('Create market', function () {
it('Markets page is displayed', function () {
cy.visit('/');
cy.createMarket();
cy.get(marketNavigation).click();
cy.common_validate_blocks_data_displayed(marketHeaders);
});
beforeEach('Get market id', function () {
cy.navigate_to('markets');
cy.get('[col-id="id"]').last().invoke('text').as('createdMarketId');
});
it('Market displayed on market page', function () {
cy.navigate_to('markets');
cy.getByTestId(marketHeaders).should('be.visible');
cy.get(`[row-id="${this.createdMarketId}"]`)
.should('be.visible')
.within(() => {
cy.get_element_by_col_id('code').should('have.text', 'TEST.24h');
cy.get_element_by_col_id('name').should('have.text', 'Test market 1');
cy.get_element_by_col_id('state').should('have.text', 'Pending');
cy.get_element_by_col_id('asset').should('have.text', 'fUSDC');
cy.get_element_by_col_id('id').should(
'have.text',
this.createdMarketId
);
cy.get_element_by_col_id('actions')
.find('a')
.should('have.attr', 'href', `/markets/${this.createdMarketId}`);
});
});
it('Able to go to market details page', function () {
cy.navigate_to('markets');
cy.contains('Test market 1').click();
cy.getByTestId(marketHeaders).should('have.text', '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('Market Decimal Places', '5');
cy.validate_element_from_table('Position Decimal Places', '5');
cy.validate_element_from_table('Settlement Asset Decimal Places', '5');
// Instrument
cy.validate_element_from_table('Market Name', 'Test market 1');
cy.validate_element_from_table('Code', 'TEST.24h');
cy.validate_element_from_table('Product Type', 'Future');
cy.validate_element_from_table('Quote Name', 'fUSDC');
// Settlement Asset
cy.validate_element_from_table('Quote Name', 'fUSDC');
cy.validate_element_from_table('Symbol', 'fUSDC');
cy.validate_element_from_table('Decimals', '5');
cy.validate_element_from_table('Quantum', '0.00');
cy.validate_element_from_table('Status', 'Enabled');
cy.validate_element_from_table('Max faucet amount', '10,000,000.00');
cy.validate_element_from_table(
'Infrastructure fee account balance',
'0.00'
);
cy.validate_element_from_table(
'Global reward pool account balance',
'0.00'
);
// Metadata
cy.validate_element_from_table('Sector', 'tech');
cy.validate_element_from_table('Source', 'docs.vega.xyz');
// Risk model
cy.validate_element_from_table('Tau', '0.0001140771161');
cy.validate_element_from_table('Risk Aversion Parameter', '0.01');
// Risk parameters
// cy.validate_element_from_table('R', '0.016')
cy.validate_element_from_table('Sigma', '0.5');
// Risk factors
cy.validate_element_from_table('Short', '0.0143218738374871');
cy.validate_element_from_table('Long', '0.0141450471498822');
// Price monitoring settings 1
cy.validate_element_from_table('Horizon Secs', '43,200');
cy.validate_element_from_table('Probability', '1');
cy.validate_element_from_table('Auction Extension Secs', '600');
// Liquidity Monitoring
cy.validate_element_from_table('Triggering Ratio', '0.7');
cy.validate_element_from_table('Time Window', '3,600');
cy.validate_element_from_table('Scaling Factor', '10');
// Liquidity
cy.validate_element_from_table('Target Stake', '0.00 fUSDC');
cy.validate_element_from_table('Supplied Stake', '0.00 fUSDC');
// Liquidity price range
cy.validate_element_from_table(
'Liquidity Price Range',
'95.00% of mid price'
);
cy.validate_element_from_table('Lowest Price', '0.00 fUSDC');
cy.validate_element_from_table('Highest Price', '0.00 fUSDC');
cy.getByTestId('oracle-spec-links')
.should('have.attr', 'href')
.and(
'contain',
'/oracles/bf242aa5c9f64fcbb77808aa8582e73711519f4b35264eb797a80f1803590a24'
);
// Able to view Json
cy.contains('View JSON').click();
cy.get('.language-json').should('exist');
cy.getByTestId('icon-cross').click();
});
// Skipping due to resize observer loop limit error
it.skip('Markets page displayed on mobile', function () {
it('Markets page displayed on mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.navigate_to('markets', true);
cy.getByTestId(marketHeaders).should('be.visible');
cy.get(`[row-id="${this.createdMarketId}"]`)
.should('be.visible')
.within(() => {
cy.get_element_by_col_id('code').should('have.text', 'TEST.24h');
cy.get_element_by_col_id('name').should('have.text', 'Test market 1');
cy.get_element_by_col_id('state').should('have.text', 'Pending');
cy.get_element_by_col_id('asset').should('have.text', 'fUSDC');
cy.get_element_by_col_id('id').should(
'have.text',
this.createdMarketId
);
cy.get_element_by_col_id('actions')
.find('a')
.should('have.attr', 'href', `/markets/${this.createdMarketId}`);
});
});
it('Able to go to market details page for successor market', function () {
const successionLineItem = 'succession-line-item';
const successionLineMarketId = 'succession-line-item-market-id';
createSuccessorMarketProposal(this.createdMarketId);
cy.navigate_to('markets');
cy.reload();
cy.contains('Token test market', { timeout: 8000 }).should('be.visible');
cy.get('[row-index="0"]')
.invoke('attr', 'row-id')
.as('successorMarketId');
cy.contains('Token test market').click();
cy.getByTestId(marketHeaders).should('have.text', 'Token test market');
cy.validate_element_from_table('Triggering Ratio', '0.7');
cy.validate_element_from_table('Time Window', '3,600');
cy.validate_element_from_table('Scaling Factor', '10');
cy.getByTestId(successionLineItem)
.first()
.within(() => {
cy.contains('Test market 1');
cy.getByTestId(successionLineMarketId).should(
'have.text',
this.createdMarketId
);
});
cy.getByTestId(successionLineItem)
.eq(1)
.within(() => {
cy.contains('Token test market');
cy.getByTestId(successionLineMarketId).should(
'have.text',
this.successorMarketId
);
});
cy.get(marketNavigation).click();
cy.common_validate_blocks_data_displayed(marketHeaders);
});
});
});
@@ -1,9 +1,12 @@
context('Network parameters page', { tags: '@smoke' }, function () {
before('navigate to network parameter page', function () {
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
cy.visit('/network-parameters');
});
describe('Verify elements on page', function () {
beforeEach(() => {
cy.visit('/network-parameters');
});
const networkParametersHeader = '[data-testid="network-param-header"]';
const tableRows = '[data-testid="key-value-table-row"]';
@@ -13,7 +16,6 @@ context('Network parameters page', { tags: '@smoke' }, function () {
.and('be.visible');
});
// 0006-NETW-021
it('should list each of the network parameters available', function () {
cy.get_network_parameters().then((network_parameters) => {
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 () {
cy.switchToMobile();
cy.get_network_parameters().then((network_parameters) => {
@@ -40,7 +40,7 @@ context.skip('Node switcher', { tags: '@regression' }, function () {
const errorTypeTxt = 'Error: invalid 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.get('input').clear().type('fakeUrl');
@@ -1,68 +0,0 @@
context('Oracle page', { tags: '@smoke' }, () => {
describe('Verify elements on page', () => {
before('create market and navigate to oracle page', () => {
cy.createMarket();
cy.visit('/oracles');
});
it('should see oracle data', () => {
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
cy.getByTestId('oracle-details')
.should('exist')
.eq(0)
.within(() => {
cy.get('tr')
.eq(0)
.within(() => {
cy.get('th').should('have.text', 'ID');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/oracles/');
});
cy.get('tr')
.eq(1)
.within(() => {
cy.get('th').should('have.text', 'Type');
cy.get('td').should('have.text', 'External data');
});
cy.get('tr')
.eq(2)
.within(() => {
cy.get('th').should('have.text', 'Signer');
cy.getByTestId('keytype').should('have.text', 'Vega');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/parties/');
});
cy.get('tr')
.eq(3)
.within(() => {
cy.get('th').should('have.text', 'Settlement for');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/markets/');
});
cy.get('tr')
.eq(4)
.within(() => {
cy.get('th').should('have.text', 'Matched data');
cy.get('td').should('have.text', '❌');
});
cy.get('details')
.eq(0)
.within(() => {
cy.contains('Filter').click();
cy.get('.language-json').should('exist');
});
cy.get('details')
.eq(1)
.within(() => {
cy.contains('JSON').click();
cy.get('.language-json').should('exist');
});
});
});
});
});
@@ -169,6 +169,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
@@ -1,93 +0,0 @@
import { getNewAssetTxBody } from '../support/governance.functions';
context('Proposal page', { tags: '@smoke' }, function () {
describe('Verify elements on page', function () {
const proposalHeading = 'proposals-heading';
const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
before('Create market proposal', function () {
cy.visit('/');
cy.createMarket();
});
it('Able to view proposal', function () {
const proposalTitle = 'Add Lorem Ipsum market';
cy.navigate_to('governanceProposals');
cy.getByTestId(proposalHeading).should('be.visible');
cy.contains(proposalTitle)
.parent()
.parent()
.parent()
.within(() => {
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
cy.getByTestId('vote-progress').should('be.visible');
cy.getByTestId('vote-progress-bar-for')
.invoke('attr', 'style')
.should('eq', 'width: 100%;');
cy.get('[col-id="cDate"]')
.invoke('text')
.should('match', dateTimeRegex);
cy.get('[col-id="eDate"]')
.invoke('text')
.should('match', dateTimeRegex);
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contains', 'https://governance.fairground.wtf/proposals/');
cy.contains('View terms').should('exist').click();
});
cy.getByTestId('dialog-title').should('have.text', proposalTitle);
cy.get('.language-json').should('exist');
cy.getByTestId('icon-cross').click();
});
it.skip('Proposal page displayed on mobile', function () {
const proposalTitle = 'Add Lorem Ipsum market';
cy.common_switch_to_mobile_and_click_toggle();
cy.navigate_to('governanceProposals', true);
cy.getByTestId(proposalHeading).should('be.visible');
cy.get('[row-index="0"]').within(() => {
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
});
});
it('Able to view new asset proposal', function () {
const proposalTitle = 'Test new asset proposal';
const newAssetProposalBody = getNewAssetTxBody();
cy.VegaWalletSubmitProposal(newAssetProposalBody);
cy.visit('/');
cy.navigate_to('governanceProposals');
cy.contains(proposalTitle)
.parent()
.parent()
.parent()
.within(() => {
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
cy.get_element_by_col_id('type').should('have.text', 'NewAsset');
cy.get_element_by_col_id('state').should(
'have.text',
'Waiting for Node Vote'
);
cy.getByTestId('vote-progress').should('be.visible');
cy.getByTestId('vote-progress-bar-against')
.invoke('attr', 'style')
.should('eq', 'width: 100%;');
cy.get('[col-id="cDate"]')
.invoke('text')
.should('match', dateTimeRegex);
cy.get('[col-id="eDate"]')
.invoke('text')
.should('match', dateTimeRegex);
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contains', 'https://governance.fairground.wtf/proposals/');
cy.contains('View terms').should('exist').click();
});
});
});
});
@@ -40,7 +40,7 @@ context.skip('Transactions page', function () {
.first()
.click({ force: true });
} else {
cy.log('Unable to find any transactions on page');
cy.slack('Unable to find any transactions on page');
cy.screenshot();
}
});
@@ -78,7 +78,7 @@ context.skip('Transactions page', function () {
}
});
} else {
cy.log('Unable to find any transactions on page');
cy.slack('Unable to find any transactions on page');
cy.screenshot();
}
});
@@ -7,11 +7,7 @@ context('Validator page', { tags: '@smoke' }, function () {
it('should be able to see validator tiles', function () {
cy.getNodes().then((nodes) => {
nodes.forEach((node) => {
if (node.rankingScore.performanceScore > 0) {
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
} else {
cy.get(`[validator-id="${node.id}"]`).should('not.exist');
}
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
});
});
});
@@ -28,7 +28,7 @@ Cypress.Commands.add('switchToMobile', () => {
Cypress.Commands.add('common_switch_to_mobile_and_click_toggle', function () {
cy.viewport('iphone-x');
cy.visit('/');
cy.get('[data-testid="button-menu-drawer"]').click();
cy.get('[data-testid="open-menu"]').click();
});
Cypress.Commands.add('monitor_clipboard', () => {
@@ -88,49 +88,3 @@ Cypress.Commands.add('convert_number_to_max_four_decimal', (number) => {
maximumFractionDigits: 4,
});
});
Cypress.Commands.add('navigate_to', (page, mobileView = false) => {
const navigation = {
transactions: '/txs',
blocks: '/blocks',
oracles: '/oracles',
validators: '/validators',
parties: '/parties',
assets: '/assets',
markets: '/markets',
governanceProposals: '/governance',
networkParameters: '/network-parameters',
genesisParameters: '/genesis',
};
const topLevelRoutes = ['transactions', 'blocks', 'oracles', 'validators'];
cy.getByTestId('navigation').within(() => {
if (!topLevelRoutes.includes(page) && mobileView === false) {
cy.getByTestId('other').first().click();
}
cy.get(`[href="${navigation[page]}"]`).first().click();
});
cy.url().should('include', navigation[page]);
});
Cypress.Commands.add('get_element_by_col_id', (elementName) => {
cy.get(`[col-id="${elementName}"]`);
});
Cypress.Commands.add(
'validate_element_from_table',
(tableRowName, tableRowValue) => {
cy.contains(tableRowName)
.parentsUntil("[data-testid='key-value-table-row']")
.siblings()
.should('have.text', tableRowValue);
}
);
Cypress.Commands.add(
'validate_proposal_change_type',
(tableRowName, changeType) => {
cy.contains(tableRowName).siblings().should('have.text', changeType);
}
);
@@ -1,186 +0,0 @@
import { addSeconds, millisecondsToSeconds } from 'date-fns';
export function createSuccessorMarketProposal(parentMarketId) {
cy.VegaWalletSubmitProposal(getSuccessorTxBody(parentMarketId));
}
function getSuccessorTxBody(parentMarketId) {
const MIN_CLOSE_SEC = 500;
const MIN_ENACT_SEC = 700;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
return {
proposalSubmission: {
rationale: {
title: 'Test successor market proposal details',
description: 'E2E test for successor market',
},
terms: {
newMarket: {
changes: {
decimalPlaces: '5',
positionDecimalPlaces: '5',
linearSlippageFactor: '0.001',
quadraticSlippageFactor: '0',
instrument: {
name: 'Token test market',
code: 'TEST.24h',
future: {
settlementAsset:
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
quoteName: 'fUSDC',
dataSourceSpecForSettlementData: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'prices.BTC.value',
type: 'TYPE_INTEGER',
numberDecimalPlaces: '0',
},
conditions: [
{
operator: 'OPERATOR_GREATER_THAN',
value: '0',
},
],
},
],
},
},
},
dataSourceSpecForTradingTermination: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'trading.terminated.ETH5',
type: 'TYPE_BOOLEAN',
},
conditions: [
{
operator: 'OPERATOR_EQUALS',
value: 'true',
},
],
},
],
},
},
},
dataSourceSpecBinding: {
settlementDataProperty: 'prices.BTC.value',
tradingTerminationProperty: 'trading.terminated.ETH5',
},
},
},
metadata: [
'sector:food',
'sector:materials',
'source:docs.vega.xyz',
],
priceMonitoringParameters: {
triggers: [
{
horizon: '43200',
probability: '0.9999999',
auctionExtension: '600',
},
],
},
liquidityMonitoringParameters: {
targetStakeParameters: {
timeWindow: '3600',
scalingFactor: 10,
},
triggeringRatio: '0.7',
auctionExtension: '1',
},
logNormal: {
tau: 0.0001140771161,
riskAversionParameter: 0.01,
params: {
mu: 0,
r: 0.016,
sigma: 0.5,
},
},
successor: {
parentMarketId: parentMarketId,
insurancePoolFraction: '0.75',
},
liquiditySlaParameters: {
priceRange: '0.95',
commitmentMinTimeFraction: '0.5',
performanceHysteresisEpochs: 2,
slaCompetitionFactor: '0.75',
},
},
},
closingTimestamp,
enactmentTimestamp,
},
},
};
}
export function getNewAssetTxBody() {
const MIN_CLOSE_SEC = 500;
const MIN_ENACT_SEC = 700;
const MIN_VALID_SEC = 60;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const validationDate = addSeconds(new Date(), MIN_VALID_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
const validationTimestamp = millisecondsToSeconds(validationDate.getTime());
return {
proposalSubmission: {
rationale: {
title: 'Test new asset proposal',
description: 'E2E test for proposals',
},
terms: {
newAsset: {
changes: {
name: 'USDT Coin',
symbol: 'USDT',
decimals: '18',
quantum: '1',
erc20: {
contractAddress: '0xb404c51bbc10dcbe948077f18a4b8e553d160084',
withdrawThreshold: '10',
lifetimeLimit: '10',
},
},
},
closingTimestamp,
enactmentTimestamp,
validationTimestamp,
},
},
};
}
-1
View File
@@ -15,6 +15,5 @@
import '@vegaprotocol/cypress';
import './common.functions.js';
import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep';
registerCypressGrep();
+1 -1
View File
@@ -1,7 +1,7 @@
{
"presets": [
[
"@nx/react/babel",
"@nrwl/react/babel",
{
"runtime": "automatic"
}
+7 -10
View File
@@ -1,21 +1,18 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.rocks
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_TOKEN_URL=https://stagnet1.governance.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.rocks/rest
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases/tag/
NX_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo
# App flags
NX_EXPLORER_ASSETS=1
+2 -4
View File
@@ -1,12 +1,10 @@
# App configuration variables
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# App flags
NX_EXPLORER_TXS_LIST=0
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
+2 -4
View File
@@ -1,4 +1,6 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
@@ -8,7 +10,3 @@ 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_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
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_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
+3 -8
View File
@@ -1,15 +1,10 @@
# App configuration variables
NX_SENTRY_DSN=https://b3a56b03eda842faad731f3ea9dfd1bc@o286262.ingest.sentry.io/6242427
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest
NX_BLOCK_EXPLORER=https://be.vega.community/rest/
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.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_WEBSOCKET_URL=wss://be.vega.community/websocket
-15
View File
@@ -1,15 +0,0 @@
# App configuration variables
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
NX_VEGA_ENV=MAINNET_MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
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_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_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
+1 -3
View File
@@ -1,3 +1 @@
# .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_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# .env is stagnet1, so there are no overrides required
+2 -5
View File
@@ -1,5 +1,7 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV=TESTNET
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
@@ -8,8 +10,3 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.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_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
@@ -7,11 +7,8 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_VEGA_GOVERNANCE_URL=https://governance.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_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_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz/
+1 -2
View File
@@ -5,5 +5,4 @@ NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_EXPLORER_URL=/
+2 -2
View File
@@ -1,6 +1,6 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"],
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__", "__generated___"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+1 -2
View File
@@ -32,7 +32,6 @@ yarn nx serve explorer
Example configurations are provided here:
- [Mainnet](./.env.mainnet)
- [Mainnet-mirror](./.env.mainnet-mirror)
- [Devnet](./.env.devnet)
- [Capsule](./.env.capsule)
- [Testnet](./.env.testnet)
@@ -40,7 +39,7 @@ Example configurations are provided here:
For convenience, you can boot the app injecting one of the configurations above by running:
```bash
yarn env-cmd -f .\apps\explorer\.env.{env} yarn nx run explorer:serve # e.g. stagnet1
yarn nx run explorer:serve --env={env} # e.g. stagnet1
```
There are a few different configuration options offered for this app:
+2 -2
View File
@@ -4,8 +4,8 @@ export default {
displayName: 'explorer',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/explorer',
+9 -16
View File
@@ -1,11 +1,10 @@
{
"name": "explorer",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/explorer/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"executor": "./tools/executors/webpack:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
@@ -39,7 +38,7 @@
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"executor": "./tools/executors/webpack:serve",
"options": {
"port": 3000,
"buildTarget": "explorer:build:development",
@@ -53,36 +52,30 @@
}
},
"lint": {
"executor": "@nx/linter:eslint",
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/explorer/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/explorer"],
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/apps/explorer"],
"options": {
"jestConfig": "apps/explorer/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"generate-types": {
"executor": "nx:run-commands",
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"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"
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.67.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
]
}
},
"build-netlify": {
"executor": "nx:run-commands",
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"cp apps/explorer/netlify.toml netlify.toml",
@@ -91,7 +84,7 @@
}
},
"build-spec": {
"executor": "nx:run-commands",
"executor": "@nrwl/workspace:run-commands",
"outputs": [],
"options": {
"command": "yarn tsc --project ./apps/explorer/tsconfig.spec.json"
+2 -27
View File
@@ -1,19 +1,9 @@
import {
AppFailure,
NetworkLoader,
NodeGuard,
NodeSwitcherDialog,
useEnvironment,
useInitializeEnv,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
import { RouterProvider } from 'react-router-dom';
import { router } from './routes/router-config';
import { t } from '@vegaprotocol/i18n';
import { Suspense } from 'react';
const splashLoading = (
<Splash>
@@ -22,25 +12,10 @@ const splashLoading = (
);
function App() {
const { VEGA_URL } = useEnvironment();
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useNodeSwitcherStore(
(store) => [store.dialogOpen, store.setDialogOpen]
);
return (
<TendermintWebsocketProvider>
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<NodeGuard
skeleton={<div>{t('Loading')}</div>}
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
>
<Suspense fallback={splashLoading}>
<RouterProvider router={router} fallbackElement={splashLoading} />
</Suspense>
</NodeGuard>
<NodeSwitcherDialog
open={nodeSwitcherOpen}
setOpen={setNodeSwitcherOpen}
/>
<RouterProvider router={router} fallbackElement={splashLoading} />
</NetworkLoader>
</TendermintWebsocketProvider>
);
@@ -1,15 +1,15 @@
import { useMemo } from 'react';
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
import { useNavigate } from 'react-router-dom';
import type { RowClickedEvent, ColDef } from 'ag-grid-community';
import type { RowClickedEvent } from 'ag-grid-community';
type AssetsTableProps = {
data: AssetFieldsFragment[] | null;
@@ -31,58 +31,6 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
};
}, []);
const columnDefs = useMemo<ColDef[]>(
() => [
{ headerName: t('Symbol'), field: 'symbol' },
{ headerName: t('Name'), field: 'name' },
{
flex: 2,
headerName: t('ID'),
field: 'id',
hide: window.innerWidth < BREAKPOINT_MD,
},
{
colId: 'type',
headerName: t('Type'),
field: 'source.__typename',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetTypeMapping[value].value : '',
},
{
headerName: t('Status'),
field: 'status',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetStatusMapping[value].value : '',
},
{
colId: 'actions',
headerName: '',
sortable: false,
filter: false,
resizable: false,
wrapText: true,
field: 'id',
cellRenderer: ({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
navigate(value);
}}
>
{t('View details')}
</ButtonLink>
) : (
''
),
},
],
[navigate]
);
return (
<AgGrid
ref={ref}
@@ -98,11 +46,60 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
columnDefs={columnDefs}
suppressCellFocus={true}
onRowClicked={({ data }: RowClickedEvent) => {
navigate(data.id);
}}
/>
>
<AgGridColumn headerName={t('Symbol')} field="symbol" />
<AgGridColumn headerName={t('Name')} field="name" />
<AgGridColumn
flex="2"
headerName={t('ID')}
field="id"
hide={window.innerWidth < BREAKPOINT_MD}
/>
<AgGridColumn
colId="type"
headerName={t('Type')}
field="source.__typename"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetTypeMapping[value].value
}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetStatusMapping[value].value
}
/>
<AgGridColumn
colId="actions"
headerName=""
sortable={false}
filter={false}
resizable={false}
wrapText={true}
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
navigate(value);
}}
>
{t('View details')}
</ButtonLink>
) : (
''
)
}
/>
</AgGrid>
);
};
@@ -1,13 +1,29 @@
import WS from 'jest-websocket-mock';
import { render, screen } from '@testing-library/react';
import useWebSocket from 'react-use-websocket';
import {
render,
screen,
fireEvent,
act,
waitFor,
} from '@testing-library/react';
import { TendermintWebsocketContext } from '../../contexts/websocket/tendermint-websocket-context';
import { BlocksRefetch } from './blocks-refetch';
const BlocksRefetchInWebsocketProvider = ({
callback,
mocketLocation,
}: {
callback: () => null;
mocketLocation: string;
}) => {
return <BlocksRefetch refetch={callback} />;
const contextShape = useWebSocket(mocketLocation);
return (
<TendermintWebsocketContext.Provider value={{ ...contextShape }}>
<BlocksRefetch refetch={callback} />
</TendermintWebsocketContext.Provider>
);
};
describe('Blocks refetch', () => {
@@ -16,8 +32,111 @@ describe('Blocks refetch', () => {
const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation);
render(<BlocksRefetchInWebsocketProvider callback={() => null} />);
render(
<BlocksRefetchInWebsocketProvider
callback={() => null}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
expect(screen.getByTestId('new-blocks')).toHaveTextContent('new blocks');
expect(screen.getByTestId('refresh')).toBeInTheDocument();
mocket.close();
});
it('should initiate callback when the button is clicked', async () => {
const mocketLocation = 'wss:localhost:3003';
const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation);
const callback = jest.fn();
render(
<BlocksRefetchInWebsocketProvider
callback={callback}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
const button = screen.getByTestId('refresh');
act(() => {
fireEvent.click(button);
});
expect(callback.mock.calls.length).toEqual(1);
mocket.close();
});
it('should show new blocks as websocket is correctly updated', async () => {
const mocketLocation = 'wss:localhost:3004';
const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation);
render(
<BlocksRefetchInWebsocketProvider
callback={() => null}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
// Ensuring we send an ID equal to the one the client subscribed with.
await waitFor(() => expect(mocket.messages.length).toEqual(1));
// @ts-ignore id on messages
const id = mocket.messages[0].id;
const newBlockMessage = {
id,
result: {
query: "tm.event = 'NewBlock'",
},
};
expect(screen.getByTestId('new-blocks')).toHaveTextContent('0 new blocks');
act(() => {
mocket.send(newBlockMessage);
});
expect(screen.getByTestId('new-blocks')).toHaveTextContent('1 new blocks');
act(() => {
mocket.send(newBlockMessage);
});
expect(screen.getByTestId('new-blocks')).toHaveTextContent('2 new blocks');
mocket.close();
});
it('will not show new blocks if websocket has wrong ID', async () => {
const mocketLocation = 'wss:localhost:3005';
const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation);
render(
<BlocksRefetchInWebsocketProvider
callback={() => null}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
// Ensuring we send an ID equal to the one the client subscribed with.
await waitFor(() => expect(mocket.messages.length).toEqual(1));
const newBlockMessageBadId = {
id: 'blahblahblah',
result: {
query: "tm.event = 'NewBlock'",
},
};
expect(screen.getByTestId('new-blocks')).toHaveTextContent('0 new blocks');
act(() => {
mocket.send(newBlockMessageBadId);
});
expect(screen.getByTestId('new-blocks')).toHaveTextContent('0 new blocks');
mocket.close();
});
});
@@ -1,19 +1,36 @@
import { useState, useEffect } from 'react';
import { useTendermintWebsocket } from '../../hooks/use-tendermint-websocket';
import { t } from '@vegaprotocol/i18n';
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
interface BlocksRefetchProps {
refetch: () => void;
}
export const BlocksRefetch = ({ refetch }: BlocksRefetchProps) => {
const [blocksToLoad, setBlocksToLoad] = useState<number>(0);
const { messages } = useTendermintWebsocket({
query: "tm.event = 'NewBlock'",
});
useEffect(() => {
if (messages.length > 0) {
setBlocksToLoad((prev) => prev + 1);
}
}, [messages]);
const refresh = () => {
refetch();
setBlocksToLoad(0);
};
return (
<Button onClick={refresh} data-testid="refresh" size="xs">
<Icon name="refresh" className="!align-baseline mr-2" size={3} />
{t('Load new')}
</Button>
<div className="mb-4">
<span data-testid="new-blocks">{blocksToLoad} new blocks - </span>
<ButtonLink onClick={refresh} data-testid="refresh">
{t('refresh to see latest')}
</ButtonLink>
</div>
);
};
@@ -1,21 +1,13 @@
import {
useEnvironment,
useNodeSwitcherStore,
} from '@vegaprotocol/environment';
import { NodeSwitcherDialog, useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { Link, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
import { useMemo, useState } from 'react';
import { ENV } from '../../config/env';
import { Routes } from '../../routes/route-names';
import { Link as RouteLink } from 'react-router-dom';
export const Footer = () => {
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment();
const setNodeSwitcherOpen = useNodeSwitcherStore(
(store) => store.setDialogOpen
);
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo(
() => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
@@ -23,59 +15,56 @@ export const Footer = () => {
);
return (
<footer className="grid grid-cols-[1fr_auto] items-center text-xs md:text-md lg:flex md:col-span-2 px-4 pt-2 pb-3 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<div className="flex justify-between gap-2 align-middle">
{GIT_COMMIT_HASH && (
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
<p data-testid="git-commit-hash">
{t('Version')}:{' '}
<Link
href={
GIT_ORIGIN_URL
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
: undefined
}
target={GIT_ORIGIN_URL ? '_blank' : undefined}
>
{GIT_COMMIT_HASH}
</Link>
</p>
<>
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<div className="flex justify-between gap-2 align-middle">
{GIT_COMMIT_HASH && (
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
<p data-testid="git-commit-hash">
{t('Version')}:{' '}
<Link
href={
GIT_ORIGIN_URL
? `${GIT_ORIGIN_URL}/commit/${GIT_COMMIT_HASH}`
: undefined
}
target={GIT_ORIGIN_URL ? '_blank' : undefined}
>
{GIT_COMMIT_HASH}
</Link>
</p>
</div>
)}
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
{t('Change')}
</Link>
</div>
)}
<div className="content-center flex pr-4 md:border-r border-neutral-700 dark:border-neutral-300">
<span className="pr-2">{VEGA_URL && <NodeUrl url={VEGA_URL} />}</span>
<Link
className="ml-2 underline-offset-4"
onClick={() => setNodeSwitcherOpen(true)}
>
{t('Change')}
</Link>
</div>
{ENV.addresses.feedback ? (
<div className="flex pl-2 content-center">
<ExternalLink href={ENV.addresses.feedback}>
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
</ExternalLink>
</div>
) : null}
</div>
<div className="pl-2 align-center lg:align-right lg:flex lg:justify-end gap-2 align-middle lg:max-w-xs lg:ml-auto">
<RouteLink
to={`/${Routes.DISCLAIMER}`}
className="underline underline-offset-4"
>
Disclaimer
</RouteLink>
</div>
</footer>
</div>
</footer>
<NodeSwitcherDialog
open={nodeSwitcherOpen}
setOpen={setNodeSwitcherOpen}
/>
</>
);
};
export const NodeUrl = ({ url }: { url: string }) => {
const NodeUrl = ({ url }: { url: string }) => {
// get base url from api url, api sub domain
const urlObj = new URL(url);
const nodeUrl = urlObj.origin.replace(/^[^.]+\./g, '');
return <span className="cursor-default">{nodeUrl}</span>;
return (
<Link href={'https://' + nodeUrl} target="_blank">
{nodeUrl}
</Link>
);
};
@@ -3,7 +3,6 @@ import { Header } from './header';
import { MemoryRouter } from 'react-router-dom';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
NetworkSwitcher: () => (
<div data-testid="network-switcher">NetworkSwitcher</div>
),
@@ -95,10 +95,7 @@ export const Header = () => {
{mainItems.map(routeToNavigationItem)}
{groupedItems && groupedItems.length > 0 && (
<NavigationItem>
<NavigationTrigger
isActive={Boolean(isOnOther)}
data-testid="other"
>
<NavigationTrigger isActive={Boolean(isOnOther)}>
{t('Other')}
</NavigationTrigger>
<NavigationContent>
@@ -1,4 +1,5 @@
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import React from 'react';
export interface InfoBlockProps {
title: string;
@@ -13,12 +13,6 @@ query ExplorerMarket($id: ID!) {
decimals
}
}
... on Perpetual {
quoteName
settlementAsset {
decimals
}
}
}
}
}
@@ -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`
@@ -27,12 +27,6 @@ export const ExplorerMarketDocument = gql`
decimals
}
}
... on Perpetual {
quoteName
settlementAsset {
decimals
}
}
}
}
}
@@ -61,7 +61,6 @@ describe('Market link component', () => {
instrument: {
name: 'test-label',
product: {
__typename: 'Future',
quoteName: 'dai',
},
},
@@ -1,98 +1,33 @@
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import PartyLink from './party-link';
import { MockedProvider } from '@apollo/client/testing';
import { ExplorerNodeNamesDocument } from '../../../routes/validators/__generated__/NodeNames';
import { act } from 'react-dom/test-utils';
const zeroes =
'0000000000000000000000000000000000000000000000000000000000000000';
const mocks = [
{
request: {
query: ExplorerNodeNamesDocument,
},
result: {
data: {
nodesConnection: {
edges: [
{
node: {
id: '1',
name: 'Validator Node',
pubkey:
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e',
tmPubkey: 'tmPubkey1',
ethereumAddress: '0x123456789',
},
},
{
node: {
id: '2',
name: 'Node 2',
pubkey: 'pubkey2',
tmPubkey: 'tmPubkey2',
ethereumAddress: '0xabcdef123',
},
},
],
},
},
},
},
];
describe('PartyLink', () => {
it('renders Network for 000.000 party', () => {
const screen = render(
<MockedProvider>
<PartyLink id={zeroes} />
</MockedProvider>
);
const zeroes =
'0000000000000000000000000000000000000000000000000000000000000000';
const screen = render(<PartyLink id={zeroes} />);
expect(screen.getByText('Network')).toBeInTheDocument();
});
it('renders Network for network party', () => {
const screen = render(
<MockedProvider>
<PartyLink id="network" />
</MockedProvider>
);
const screen = render(<PartyLink id="network" />);
expect(screen.getByText('Network')).toBeInTheDocument();
});
it('renders ID with no link for invalid party', () => {
const screen = render(
<MockedProvider>
<PartyLink id="this-party-is-not-valid" />
</MockedProvider>
);
const screen = render(<PartyLink id="this-party-is-not-valid" />);
expect(screen.getByTestId('invalid-party')).toBeInTheDocument();
});
it('if the key is a validator, render their name instead', async () => {
const screen = render(
<MockedProvider mocks={mocks}>
<MemoryRouter>
<PartyLink id="13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e" />
</MemoryRouter>
</MockedProvider>
);
// Wait for hook to update with mock data
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
await expect(screen.getByText('Validator Node')).toBeInTheDocument();
});
it('links a valid party to the party page', () => {
const aValidParty =
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e';
const screen = render(
<MockedProvider>
<MemoryRouter>
<PartyLink id={aValidParty} />
</MemoryRouter>
</MockedProvider>
<MemoryRouter>
<PartyLink id={aValidParty} />
</MemoryRouter>
);
const el = screen.getByText(aValidParty);
@@ -1,44 +1,22 @@
import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import { useMemo, type ComponentProps } from 'react';
import type { ComponentProps } from 'react';
import Hash from '../hash';
import { t } from '@vegaprotocol/i18n';
import { isValidPartyId } from '../../../routes/parties/id/components/party-id-error';
import { Icon, truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { useExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames';
import type { ExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames';
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
export const SPECIAL_CASE_NETWORK_ID =
'0000000000000000000000000000000000000000000000000000000000000000';
export const SPECIAL_CASE_NETWORK = 'network';
export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
if (!data || data?.nodesConnection?.edges?.length === 0) {
return id;
}
const validator = data.nodesConnection.edges?.find((e) => {
return e?.node.pubkey === id;
});
if (validator) {
return validator.node.name;
}
return id;
}
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
truncate?: boolean;
};
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
const { data } = useExplorerNodeNamesQuery();
const name = useMemo(() => getNameForParty(id, data), [data, id]);
const useName = name !== id;
// Some transactions will involve the 'network' party, which is alias for '000...000'
// The party page does not handle this nicely, so in this case we render the word 'Network'
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
@@ -60,20 +38,13 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
}
return (
<span className="whitespace-nowrap">
{useName && <Icon size={4} name="cube" className="mr-2" />}
<Link
className="underline font-mono"
{...props}
to={`/${Routes.PARTIES}/${id}`}
>
{useName ? (
name
) : (
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
)}
</Link>
</span>
<Link
className="underline font-mono"
{...props}
to={`/${Routes.PARTIES}/${id}`}
>
<Hash text={truncate ? truncateMiddle(id) : id} />
</Link>
);
};
@@ -1,41 +1,35 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
import { LiquidityInfoPanel } from '@vegaprotocol/market-info';
import { LiquidityMonitoringParametersInfoPanel } from '@vegaprotocol/market-info';
import {
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
PriceMonitoringBoundsInfoPanel,
SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData,
getDataSourceSpecForTradingTermination,
} from '@vegaprotocol/markets';
import {
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
InstrumentInfoPanel,
KeyDetailsInfoPanel,
LiquidityPriceRangeInfoPanel,
MetadataInfoPanel,
OracleInfoPanel,
RiskFactorsInfoPanel,
RiskModelInfoPanel,
RiskParametersInfoPanel,
SettlementAssetInfoPanel,
} from '@vegaprotocol/markets';
import { MarketInfoTable } from '@vegaprotocol/markets';
import type { DataSourceFragment } from '@vegaprotocol/markets';
} from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info';
import type { DataSourceDefinition } from '@vegaprotocol/types';
import isEqual from 'lodash/isEqual';
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
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;
const terminationData =
market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data;
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
const signers =
('signers' in data.sourceType.sourceType &&
data.sourceType.sourceType.signers) ||
[];
const signers = data.sourceType.sourceType.signers || [];
return signers.map(({ signer }, i) => {
return (
@@ -47,76 +41,133 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
return [];
};
const showTwoOracles =
settlementDataSource &&
terminationDataSource &&
isEqual(
getSigners(settlementDataSource),
getSigners(terminationDataSource)
);
const oraclePanels = isEqual(
getSigners(settlementData),
getSigners(terminationData)
)
? [
{
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="termination"
/>
),
},
]
: [
{
title: t('Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
];
const headerClassName = 'font-alpha calt text-xl mt-4 border-b-2 pb-2';
return (
<div>
<h2 className={headerClassName}>{t('Key details')}</h2>
<KeyDetailsInfoPanel market={market} />
<h2 className={headerClassName}>{t('Instrument')}</h2>
<InstrumentInfoPanel market={market} />
<h2 className={headerClassName}>{t('Settlement asset')}</h2>
<SettlementAssetInfoPanel market={market} />
<h2 className={headerClassName}>{t('Metadata')}</h2>
<MetadataInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk model')}</h2>
<RiskModelInfoPanel market={market} />
<h2 className={headerClassName}>{t('Margin scaling factors')}</h2>
<MarginScalingFactorsPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
const panels = [
{
title: t('Key details'),
content: <KeyDetailsInfoPanel noBorder={false} market={market} />,
},
{
title: t('Instrument'),
content: <InstrumentInfoPanel noBorder={false} market={market} />,
},
{
title: t('Settlement asset'),
content: <SettlementAssetInfoPanel market={market} noBorder={false} />,
},
{
title: t('Metadata'),
content: <MetadataInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk model'),
content: <RiskModelInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk parameters'),
content: <RiskParametersInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk factors'),
content: <RiskFactorsInfoPanel noBorder={false} market={market} />,
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => ({
title: t(`Price monitoring trigger ${i + 1}`),
content: <MarketInfoTable noBorder={false} data={trigger} />,
})
),
...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({
title: t(`Price monitoring bound ${i + 1}`),
content: (
<>
<h2 className={headerClassName}>
{t('Price monitoring bounds %s', [(i + 1).toString()])}
</h2>
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={i + 1}
<MarketInfoTable
noBorder={false}
data={{
maxValidPrice: trigger.maxValidPrice,
minValidPrice: trigger.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
/>
<MarketInfoTable
noBorder={false}
data={{ referencePrice: trigger.referencePrice }}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
/>
</>
),
})),
{
title: t('Liquidity monitoring parameters'),
content: (
<LiquidityMonitoringParametersInfoPanel
noBorder={false}
market={market}
/>
),
},
{
title: t('Liquidity'),
content: <LiquidityInfoPanel market={market} noBorder={false} />,
},
{
title: t('Liquidity price range'),
content: (
<LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
),
},
...oraclePanels,
];
return (
<>
{panels.map((p) => (
<div key={p.title} className="mb-3">
<h2 className="font-alpha calt text-xl">{p.title}</h2>
{p.content}
</div>
))}
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring settings %s', [(i + 1).toString()])}
</h2>
<MarketInfoTable data={trigger} key={i} />
</>
)
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<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>
<LiquidityInfoPanel market={market} />
{showTwoOracles ? (
<>
<h2 className={headerClassName}>{t('Settlement oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
<h2 className={headerClassName}>{t('Termination oracle')}</h2>
<OracleInfoPanel market={market} type="termination" />
</>
) : (
<>
<h2 className={headerClassName}>{t('Oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
</>
)}
<h2 className={`${headerClassName} mb-4`}>{t('Succession line')}</h2>
<SuccessionLineInfoPanel market={market} />
</div>
</>
);
};
@@ -1,10 +1,9 @@
import { useMemo } from 'react';
import { getAsset, type MarketFieldsFragment } from '@vegaprotocol/markets';
import type { MarketFieldsFragment } from '@vegaprotocol/market-list';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueGetterParams,
@@ -40,78 +39,6 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
};
}, []);
const columnDefs = useMemo<ColDef[]>(
() => [
{
colId: 'code',
headerName: t('Code'),
field: 'tradableInstrument.instrument.code',
},
{
colId: 'name',
headerName: t('Name'),
field: 'tradableInstrument.instrument.name',
},
{
headerName: t('Status'),
field: 'state',
hide: window.innerWidth <= BREAKPOINT_MD,
valueGetter: ({
data,
}: VegaValueGetterParams<MarketFieldsFragment>) => {
return data?.state ? MarketStateMapping[data?.state] : '-';
},
},
{
colId: 'asset',
headerName: t('Settlement asset'),
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
hide: window.innerWidth <= BREAKPOINT_MD,
cellRenderer: ({
data,
}: VegaICellRendererParams<
MarketFieldsFragment,
'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => {
const value = data && getAsset(data);
return value ? (
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value.id, e.target as HTMLElement);
}}
>
{value.symbol}
</ButtonLink>
) : (
''
);
},
},
{
flex: 2,
headerName: t('Market ID'),
field: 'id',
hide: window.innerWidth <= BREAKPOINT_MD,
},
{
colId: 'actions',
headerName: '',
field: 'id',
cellRenderer: ({
value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
value ? (
<Link className="underline" to={value}>
{t('View details')}
</Link>
) : (
''
),
},
],
[openAssetDetailsDialog]
);
return (
<AgGrid
ref={gridRef}
@@ -127,13 +54,79 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
columnDefs={columnDefs}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
navigate(data.id);
}
}}
/>
>
<AgGridColumn
colId="code"
headerName={t('Code')}
field="tradableInstrument.instrument.code"
/>
<AgGridColumn
colId="name"
headerName={t('Name')}
field="tradableInstrument.instrument.name"
/>
<AgGridColumn
headerName={t('Status')}
field="state"
hide={window.innerWidth <= BREAKPOINT_MD}
valueGetter={({
data,
}: VegaValueGetterParams<MarketFieldsFragment, 'state'>) => {
return data?.state ? MarketStateMapping[data?.state] : '-';
}}
/>
<AgGridColumn
colId="asset"
headerName={t('Settlement asset')}
field="tradableInstrument.instrument.product.settlementAsset"
hide={window.innerWidth <= BREAKPOINT_MD}
cellRenderer={({
value,
}: VegaICellRendererParams<
MarketFieldsFragment,
'tradableInstrument.instrument.product.settlementAsset'
>) =>
value ? (
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value.id, e.target as HTMLElement);
}}
>
{value.symbol}
</ButtonLink>
) : (
''
)
}
/>
<AgGridColumn
flex={2}
headerName={t('Market ID')}
field="id"
hide={window.innerWidth <= BREAKPOINT_MD}
/>
<AgGridColumn
colId="actions"
headerName=""
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
value ? (
<Link className="underline" to={value}>
{t('View details')}
</Link>
) : (
''
)
}
/>
</AgGrid>
);
};
@@ -13,10 +13,6 @@ fragment ExplorerDeterministicOrderFields on Order {
remaining
size
rejectionReason
peggedOrder {
reference
offset
}
party {
id
}
@@ -31,9 +27,6 @@ fragment ExplorerDeterministicOrderFields on Order {
... on Future {
quoteName
}
... on Perpetual {
quoteName
}
}
}
}
@@ -1,25 +0,0 @@
fragment ExplorerStopOrderFields on StopOrder {
id
status
createdAt
trigger {
... on StopOrderPrice {
price
}
... on StopOrderTrailingPercentOffset {
trailingPercentOffset
}
}
createdAt
ocoLinkId
triggerDirection
order {
id
}
}
query ExplorerStopOrder($stopOrderId: ID!) {
stopOrder(id: $stopOrderId) {
...ExplorerStopOrderFields
}
}
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, 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, 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<{
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, 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`
fragment ExplorerDeterministicOrderFields on Order {
@@ -29,10 +29,6 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
remaining
size
rejectionReason
peggedOrder {
reference
offset
}
party {
id
}
@@ -47,9 +43,6 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
... on Future {
quoteName
}
... on Perpetual {
quoteName
}
}
}
}
@@ -1,70 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerStopOrderFieldsFragment = { __typename?: 'StopOrder', id: string, status: Types.StopOrderStatus, createdAt: any, ocoLinkId?: string | null, triggerDirection: Types.StopOrderTriggerDirection, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, order?: { __typename?: 'Order', id: string } | null };
export type ExplorerStopOrderQueryVariables = Types.Exact<{
stopOrderId: Types.Scalars['ID'];
}>;
export type ExplorerStopOrderQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, status: Types.StopOrderStatus, createdAt: any, ocoLinkId?: string | null, triggerDirection: Types.StopOrderTriggerDirection, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, order?: { __typename?: 'Order', id: string } | null } | null };
export const ExplorerStopOrderFieldsFragmentDoc = gql`
fragment ExplorerStopOrderFields on StopOrder {
id
status
createdAt
trigger {
... on StopOrderPrice {
price
}
... on StopOrderTrailingPercentOffset {
trailingPercentOffset
}
}
createdAt
ocoLinkId
triggerDirection
order {
id
}
}
`;
export const ExplorerStopOrderDocument = gql`
query ExplorerStopOrder($stopOrderId: ID!) {
stopOrder(id: $stopOrderId) {
...ExplorerStopOrderFields
}
}
${ExplorerStopOrderFieldsFragmentDoc}`;
/**
* __useExplorerStopOrderQuery__
*
* To run a query within a React component, call `useExplorerStopOrderQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerStopOrderQuery` 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 } = useExplorerStopOrderQuery({
* variables: {
* stopOrderId: // value for 'stopOrderId'
* },
* });
*/
export function useExplorerStopOrderQuery(baseOptions: Apollo.QueryHookOptions<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>(ExplorerStopOrderDocument, options);
}
export function useExplorerStopOrderLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>(ExplorerStopOrderDocument, options);
}
export type ExplorerStopOrderQueryHookResult = ReturnType<typeof useExplorerStopOrderQuery>;
export type ExplorerStopOrderLazyQueryHookResult = ReturnType<typeof useExplorerStopOrderLazyQuery>;
export type ExplorerStopOrderQueryResult = Apollo.QueryResult<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>;
@@ -12,7 +12,7 @@ type Amend = components['schemas']['v1OrderAmendment'];
function renderAmendOrderDetails(
id: string,
version: number | undefined,
version: number,
amend: Amend,
mocks: MockedResponse[]
) {
@@ -25,11 +25,7 @@ function renderAmendOrderDetails(
);
}
function renderExistingAmend(
id: string,
version: number | undefined,
amend: Amend
) {
function renderExistingAmend(id: string, version: number, amend: Amend) {
const mocks = [
{
request: {
@@ -53,7 +49,6 @@ function renderExistingAmend(
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
price: '200',
side: 'BUY',
peggedOrder: null,
remaining: '99',
rejectionReason: 'rejection',
reference: '123',
@@ -82,56 +77,6 @@ function renderExistingAmend(
},
},
},
{
request: {
query: ExplorerDeterministicOrderDocument,
variables: {
orderId: '123',
},
},
result: {
data: {
orderByID: {
__typename: 'Order',
id: '123',
type: 'GTT',
status: Schema.OrderStatus.STATUS_ACTIVE,
version: 100,
createdAt: '123',
updatedAt: '456',
expiresAt: '789',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
peggedOrder: null,
price: '200',
side: 'BUY',
remaining: '99',
rejectionReason: 'rejection',
reference: '123',
size: '200',
party: {
__typename: 'Party',
id: '234',
},
market: {
__typename: 'Market',
id: 'amend-to-order-latest-version',
state: 'STATUS_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: '5',
tradableInstrument: {
instrument: {
name: 'amend-to-order-latest-version-test',
product: {
__typename: 'Future',
quoteName: '123',
},
},
},
},
},
},
},
},
{
request: {
query: ExplorerMarketDocument,
@@ -150,7 +95,6 @@ function renderExistingAmend(
instrument: {
name: 'test-label',
product: {
__typename: 'Future',
quoteName: 'dai',
},
},
@@ -213,15 +157,4 @@ describe('Amend order details', () => {
expect(await res.findByText('New price')).toBeInTheDocument();
expect(await res.findByText('-7879')).toBeInTheDocument();
});
it('Fetches latest version when version is not specified', async () => {
const amend: Amend = {
price: '-7879',
};
const res = renderExistingAmend('123', undefined, amend);
expect(
await res.findByText('amend-to-order-latest-version')
).toBeInTheDocument();
});
});
@@ -12,7 +12,7 @@ import { wrapperClasses } from './deterministic-order-details';
export interface AmendOrderDetailsProps {
id: string;
amend: components['schemas']['v1OrderAmendment'];
// Version to fetch. Latest is provided by default
// Version to fetch, with 0 being 'latest' and 1 being 'first'. Defaults to 0
version?: number;
}
@@ -34,11 +34,13 @@ export function getSideDeltaColour(delta: string): string {
* @param param0
* @returns
*/
const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
const variables = version ? { orderId: id, version } : { orderId: id };
const AmendOrderDetails = ({
id,
version = 0,
amend,
}: AmendOrderDetailsProps) => {
const { data, error } = useExplorerDeterministicOrderQuery({
variables,
variables: { orderId: id, version },
});
if (error || (data && !data.orderByID)) {
@@ -5,7 +5,6 @@ import PriceInMarket from '../price-in-market/price-in-market';
import { Time } from '../time';
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market';
import { TxOrderPeggedReference } from '../txs/details/order/tx-order-peg';
export interface DeterministicOrderDetailsProps {
id: string;
@@ -69,35 +68,25 @@ const DeterministicOrderDetails = ({
<span className="mx-5 text-base">@</span>
<PriceInMarket price={o.price} marketId={o.market.id} />
</h2>
<p className="text-gray-200">
<p className="text-gray-500 mb-4">
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
</p>
{o.peggedOrder ? (
<p className="text-gray-200">
{t('Price peg')}:{' '}
<TxOrderPeggedReference
side={o.side}
reference={o.peggedOrder.reference}
offset={o.peggedOrder.offset}
marketId={o.market.id}
/>
</p>
) : null}
{o.reference ? (
<p className="text-gray-500 mt-4">
<p className="text-gray-500 mb-4">
<span>{t('Reference')}</span>: {o.reference}
</p>
) : null}
<div className="grid md:grid-cols-4 gap-x-6 mt-4">
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Status')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
{statusText[o.status]}
</h5>
</div>
<div className="grid md:grid-cols-4 gap-x-6">
{version !== 0 ? null : (
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Status')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
{statusText[o.status]}
</h5>
</div>
)}
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
@@ -106,6 +95,17 @@ const DeterministicOrderDetails = ({
</h5>
</div>
{version !== 0 ? null : (
<div className="">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Remaining')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
<SizeInMarket size={o.remaining} marketId={o.market.id} />
</h5>
</div>
)}
<div className="">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Version')}
@@ -31,7 +31,6 @@ const mock = {
side: 'SIDE_BUY',
remaining: '100',
size: '100',
peggedOrder: null,
party: {
id: '456',
},
@@ -33,7 +33,7 @@ describe('Order TX Summary component', () => {
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders "Market Price" if the order passed lacks a price', () => {
it('Renders nothing if the order passed lacks a price', () => {
const o: Order = {
marketId: '123',
side: 'SIDE_BUY',
@@ -41,7 +41,7 @@ describe('Order TX Summary component', () => {
size: '10',
};
const res = renderComponent(o);
expect(res.queryByText('Market Price')).toBeInTheDocument();
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order has an unspecified side', () => {
@@ -3,7 +3,6 @@ import type { components } from '../../../types/explorer';
import PriceInMarket from '../price-in-market/price-in-market';
import { sideText } from '../order-details/lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market';
import { t } from '@vegaprotocol/i18n';
export type OrderSummaryProps = {
order: components['schemas']['v1OrderSubmission'];
@@ -21,6 +20,7 @@ const OrderTxSummary = ({ order }: OrderSummaryProps) => {
if (
!order ||
!order.marketId ||
!order.price ||
!order.side ||
order.side === 'SIDE_UNSPECIFIED'
) {
@@ -36,14 +36,10 @@ const OrderTxSummary = ({ order }: OrderSummaryProps) => {
'-'
)}
&nbsp;<i className="text-xs">@</i>&nbsp;
{order.price ? (
<PriceInMarket
marketId={order.marketId}
price={order.price}
></PriceInMarket>
) : (
t('Market Price')
)}
<PriceInMarket
marketId={order.marketId}
price={order.price}
></PriceInMarket>
</div>
);
};
@@ -33,8 +33,6 @@ const PriceInMarket = ({
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
} else if (
decimalSource === 'SETTLEMENT_ASSET' &&
data.market &&
'settlementAsset' in data.market.tradableInstrument.instrument.product &&
data.market?.tradableInstrument.instrument.product.settlementAsset
) {
label = addDecimalsFormatNumber(
@@ -1,14 +1,15 @@
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { RowClickedEvent, ColDef } from 'ag-grid-community';
import type { RowClickedEvent } from 'ag-grid-community';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
@@ -33,7 +34,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const { params } = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
]);
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const requiredMajorityPercentage = useMemo(() => {
const requiredMajority =
params?.governance_proposal_market_requiredMajority ?? 1;
@@ -63,128 +64,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
title: '',
content: null,
});
const columnDefs = useMemo<ColDef[]>(
() => [
{
colId: 'title',
headerName: t('Title'),
field: 'rationale.title',
flex: 2,
wrapText: true,
},
{
colId: 'type',
maxWidth: 180,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Type'),
field: 'terms.change.__typename',
},
{
maxWidth: 100,
headerName: t('State'),
field: 'state',
valueFormatter: ({
value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
return value ? ProposalStateMapping[value] : '-';
},
},
{
colId: 'voting',
maxWidth: 100,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Voting'),
cellRenderer: ({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) {
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
const noTokens = new BigNumber(data.votes.no.totalTokens);
const totalTokensVoted = yesTokens.plus(noTokens);
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="flex items-center justify-center h-full pt-2 uppercase">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</div>
);
}
return '-';
},
},
{
colId: 'cDate',
maxWidth: 150,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Closing date'),
field: 'terms.closingDatetime',
valueFormatter: ({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.closingDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
},
},
{
colId: 'eDate',
maxWidth: 150,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Enactment date'),
field: 'terms.enactmentDatetime',
valueFormatter: ({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.enactmentDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
},
},
{
colId: 'actions',
minWidth: window.innerWidth > BREAKPOINT_MD ? 221 : 80,
maxWidth: 221,
sortable: false,
filter: false,
resizable: false,
cellRenderer: ({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data?.id || '')
);
const openDialog = () => {
if (!data) return;
setDialog({
open: true,
title: data.rationale.title,
content: data.terms,
});
};
return (
<div className="pb-1">
<button className="underline max-md:hidden" onClick={openDialog}>
{t('View terms')}
</button>{' '}
<ExternalLink className="max-md:hidden" href={proposalPage}>
{t('Open in Governance')}
</ExternalLink>
<ExternalLink className="md:hidden" href={proposalPage}>
{t('Open')}
</ExternalLink>
</div>
);
},
},
],
[requiredMajorityPercentage, tokenLink]
);
return (
<>
<AgGrid
@@ -203,7 +83,6 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
columnDefs={columnDefs}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if (
@@ -215,7 +94,128 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
window.open(proposalPage, '_blank');
}
}}
/>
>
<AgGridColumn
colId="title"
headerName={t('Title')}
field="rationale.title"
flex={2}
wrapText={true}
/>
<AgGridColumn
colId="type"
maxWidth={180}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Type')}
field="terms.change.__typename"
/>
<AgGridColumn
maxWidth={100}
headerName={t('State')}
field="state"
valueFormatter={({
value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
return value ? ProposalStateMapping[value] : '-';
}}
/>
<AgGridColumn
colId="voting"
maxWidth={100}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Voting')}
cellRenderer={({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) {
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
const noTokens = new BigNumber(data.votes.no.totalTokens);
const totalTokensVoted = yesTokens.plus(noTokens);
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</div>
);
}
return '-';
}}
/>
<AgGridColumn
colId="cDate"
maxWidth={150}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Closing date')}
field="terms.closingDatetime"
valueFormatter={({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.closingDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
}}
/>
<AgGridColumn
colId="eDate"
maxWidth={150}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Enactment date')}
field="terms.enactmentDatetime"
valueFormatter={({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.enactmentDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
}}
/>
<AgGridColumn
colId="actions"
minWidth={window.innerWidth > BREAKPOINT_MD ? 221 : 80}
maxWidth={221}
sortable={false}
filter={false}
resizable={false}
cellRenderer={({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data?.id || '')
);
const openDialog = () => {
if (!data) return;
setDialog({
open: true,
title: data.rationale.title,
content: data.terms,
});
};
return (
<div className="pb-1">
<button
className="underline max-md:hidden"
onClick={openDialog}
>
{t('View terms')}
</button>{' '}
<ExternalLink className="max-md:hidden" href={proposalPage}>
{t('Open in Governance')}
</ExternalLink>
<ExternalLink className="md:hidden" href={proposalPage}>
{t('Open')}
</ExternalLink>
</div>
);
}}
/>
</AgGrid>
<JsonViewerDialog
open={dialog.open}
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
@@ -1,24 +1,19 @@
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
export const ErrorCodes = new Map([
[51, 'Transaction failed validation'],
[60, 'Transaction could not be decoded'],
[70, 'Error'],
[71, 'Partial success/error'],
[80, 'Unknown command'],
[89, 'Rejected as spam'],
[0, 'Success'],
]);
export const successCodes = new Set([0, 71]);
export const successCodes = new Set([0]);
interface ChainResponseCodeProps {
code: number;
hideLabel?: boolean;
error?: string;
hideIfOk?: boolean;
small?: boolean;
}
/**
@@ -30,22 +25,10 @@ export const ChainResponseCode = ({
code,
hideLabel = false,
error,
hideIfOk = false,
small = false,
}: ChainResponseCodeProps) => {
if (hideIfOk && code === 0) {
return null;
}
const isSuccess = successCodes.has(code);
const size = small ? 3 : 4;
const successColour =
code === 71 ? '!fill-vega-orange' : '!fill-vega-green-600';
const icon = isSuccess ? (
<Icon size={size} name="tick-circle" className={`${successColour}`} />
) : (
<Icon size={size} name="cross" className="!fill-vega-pink-500" />
);
const icon = isSuccess ? '✅' : '❌';
const label = ErrorCodes.get(code) || 'Unknown response code';
// Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245
@@ -53,28 +36,18 @@ export const ChainResponseCode = ({
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
return (
<Tooltip
description={
<span>
Response code: {code} - {label}
</span>
}
>
<div className="mt-[-1px] inline-block">
<span
className="mr-2"
aria-label={isSuccess ? 'Success' : 'Warning'}
role="img"
>
{icon}
</span>
{hideLabel ? null : <span>{label}</span>}
{!hideLabel && !!displayError ? (
<span className="ml-1 whitespace-pre">
&mdash;&nbsp;{displayError}
</span>
) : null}
</div>
</Tooltip>
<div title={`Response code: ${code} - ${label}`} className="inline-block">
<span
className="mr-2"
aria-label={isSuccess ? 'Success' : 'Warning'}
role="img"
>
{icon}
</span>
{hideLabel ? null : <span>{label}</span>}
{!hideLabel && !!displayError ? (
<span className="ml-1 whitespace-pre">&mdash;&nbsp;{displayError}</span>
) : null}
</div>
);
};
@@ -1,139 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import { formatNumberPercentage } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import PriceInMarket from '../../../price-in-market/price-in-market';
import StopOrderTriggerSummary from './stop-order-trigger';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import fromUnixTime from 'date-fns/fromUnixTime';
const wrapperClasses =
'flex-1 max-w-xs items-center border border-vega-light-200 dark:border-vega-dark-150 rounded-md pv-2 ph-5 mb-5';
export type StopOrderType = 'RisesAbove' | 'FallsBelow' | 'OCO';
type V1OrderSetup = components['schemas']['v1StopOrderSetup'];
interface StopOrderSetupProps extends V1OrderSetup {
type: StopOrderType;
deterministicId: string;
}
export function getExpiryTypeLabel(
expiryStrategy: V1OrderSetup['expiryStrategy']
): string {
switch (expiryStrategy) {
case 'EXPIRY_STRATEGY_CANCELS':
return t('Cancels');
case 'EXPIRY_STRATEGY_SUBMIT':
return t('Submit');
}
return expiryStrategy || t('Unknown');
}
export interface ExpiryTriggerProps {
trailingPercentOffset?: string;
price?: string;
marketId?: string;
}
export function ExpiryTrigger({
trailingPercentOffset,
price,
marketId,
}: ExpiryTriggerProps) {
if (price && marketId) {
return <PriceInMarket price={price} marketId={marketId} />;
}
if (trailingPercentOffset) {
return (
<span>
{formatNumberPercentage(new BigNumber(trailingPercentOffset))}{' '}
(trailing)
</span>
);
}
return null;
}
export function getMovePrefix(
type: StopOrderType,
trailingPercentOffset?: string
): string {
if (type === 'RisesAbove') {
if (trailingPercentOffset) {
return '+';
} else {
return '>';
}
} else {
if (trailingPercentOffset) {
return '-';
} else {
return '<';
}
}
}
export const TypeLabel = {
RisesAbove: t('Rises above ↗'),
FallsBelow: t('Falls below ↘'),
OCO: '',
};
/**
*/
export const StopOrderSetup = ({
type,
price,
orderSubmission,
expiresAt,
expiryStrategy,
trailingPercentOffset,
deterministicId,
}: StopOrderSetupProps) => {
let d = 'Unknown';
try {
d = expiresAt
? fromUnixTime(parseInt(expiresAt) / 1000000000).toLocaleString()
: t('Unknown');
} catch (e) {
d = t('Unknown');
}
return (
<div className={wrapperClasses}>
<div className="mb-12 lg:mb-0">
<div className="bg-slate-100 text-slate-900 px-6 py-2 md:px-6 flex">
<div className="flex-1">
<strong className="font-bold mb-1">{TypeLabel[type]} </strong>
<p className=" font-xs mb-0">
{getMovePrefix(type, trailingPercentOffset)}
<ExpiryTrigger
trailingPercentOffset={trailingPercentOffset}
price={price}
marketId={orderSubmission?.marketId}
/>
</p>
</div>
{expiresAt && expiryStrategy ? (
<div className="flex-1">
<strong className="font-bold mb-1">{t('Expiry Type')}</strong>
<p className=" font-xs mb-0">
<Tooltip description={<span>{d}</span>}>
<span>{getExpiryTypeLabel(expiryStrategy)}</span>
</Tooltip>
</p>
</div>
) : null}
</div>
<StopOrderTriggerSummary
id={deterministicId}
orderSubmission={orderSubmission}
/>
</div>
</div>
);
};
@@ -1,108 +0,0 @@
import { StopOrderStatus } from '@vegaprotocol/types';
import { useExplorerStopOrderQuery } from '../../../order-details/__generated__/StopOrder';
import type { ExplorerStopOrderQuery } from '../../../order-details/__generated__/StopOrder';
import { t } from '@vegaprotocol/i18n';
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
import OrderTxSummary from '../../../order-summary/order-tx-summary';
import type { components } from '../../../../../types/explorer';
import type { ApolloError } from '@apollo/client';
export const StatusLabel: Record<StopOrderStatus, string> = {
[StopOrderStatus.STATUS_CANCELLED]: t('Cancelled'),
[StopOrderStatus.STATUS_EXPIRED]: t('Expired'),
[StopOrderStatus.STATUS_PENDING]: t('Pending'),
[StopOrderStatus.STATUS_REJECTED]: t('Rejected'),
[StopOrderStatus.STATUS_STOPPED]: t('Stopped'),
[StopOrderStatus.STATUS_TRIGGERED]: t('Triggered'),
[StopOrderStatus.STATUS_UNSPECIFIED]: t('Status unknown'),
};
export const StatusIcon: Record<StopOrderStatus, IconName> = {
[StopOrderStatus.STATUS_CANCELLED]: 'disable',
[StopOrderStatus.STATUS_EXPIRED]: 'outdated',
[StopOrderStatus.STATUS_PENDING]: 'circle',
[StopOrderStatus.STATUS_REJECTED]: 'cross',
[StopOrderStatus.STATUS_STOPPED]: 'stop',
[StopOrderStatus.STATUS_TRIGGERED]: 'tick',
[StopOrderStatus.STATUS_UNSPECIFIED]: 'help',
};
export const StatusMidColor: Record<StopOrderStatus, string> = {
[StopOrderStatus.STATUS_CANCELLED]: 'bg-red-100 text-red-900',
[StopOrderStatus.STATUS_EXPIRED]: 'bg-red-100 text-red-900',
[StopOrderStatus.STATUS_PENDING]: 'bg-yellow-100 text-yellow-900',
[StopOrderStatus.STATUS_REJECTED]: 'bg-red-100 text-red-900',
[StopOrderStatus.STATUS_STOPPED]: 'bg-red-100 text-red-900',
[StopOrderStatus.STATUS_TRIGGERED]: 'bg-green-100 text-green-900',
[StopOrderStatus.STATUS_UNSPECIFIED]: 'bg-yellow-100 text-yellow-900',
};
export const StatusBottomColor: Record<StopOrderStatus, string> = {
[StopOrderStatus.STATUS_CANCELLED]: 'bg-red-50 text-red-900 line-through',
[StopOrderStatus.STATUS_EXPIRED]: 'bg-red-50 text-red-900 line-through',
[StopOrderStatus.STATUS_PENDING]: 'bg-yellow-50 text-yellow-900',
[StopOrderStatus.STATUS_REJECTED]: 'bg-red-50 text-red-900 line-through',
[StopOrderStatus.STATUS_STOPPED]: 'bg-red-50 text-red-900 line-through',
[StopOrderStatus.STATUS_TRIGGERED]: 'bg-green-50 text-green-900',
[StopOrderStatus.STATUS_UNSPECIFIED]:
'bg-yellow-50 text-yellow-900 line-through',
};
export function getStopOrderTriggerStatus(
data?: ExplorerStopOrderQuery,
error?: ApolloError
) {
if (data && data.stopOrder) {
return data.stopOrder.status;
}
return StopOrderStatus.STATUS_UNSPECIFIED;
}
export interface StopOrderTriggerSummaryProps {
id: string;
orderSubmission?: components['schemas']['v1OrderSubmission'];
}
/**
*/
const StopOrderTriggerSummary = ({
id,
orderSubmission,
}: StopOrderTriggerSummaryProps) => {
const { data, error } = useExplorerStopOrderQuery({
variables: { stopOrderId: id },
});
const status = getStopOrderTriggerStatus(data, error);
return (
<>
<div
className={`${StatusMidColor[status]} px-3 py-2 md:px-6 flex space-x-4`}
>
<p className="m-0 p-0 align-top">
<Icon
size={6}
name={StatusIcon[status]}
className="inline-block mr-2"
/>
<span className="align-top">{StatusLabel[status]}</span>
</p>
</div>
<div
className={`${StatusBottomColor[status]} px-3 py-2 md:px-6 flex space-x-4`}
>
{orderSubmission && (
<p className="text-vega-grey-400 strike">
<OrderTxSummary order={orderSubmission} />
</p>
)}
</div>
</>
);
};
export default StopOrderTriggerSummary;
@@ -1,68 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import SizeInMarket from '../../../size-in-market/size-in-market';
export interface TxDetailsOrderIcebergDetailsProps {
iceberg: components['schemas']['v1IcebergOpts'];
size: components['schemas']['v1OrderSubmission']['size'];
marketId?: string;
}
/**
.iceberg .sub {
vertical-align: bottom;
color: #954F01;
}
.iceberg .sup {
vertical-align: top;
color: #515E1E;
}
*/
/**
* Visualises the iceberg details of an order, showing the minimum visible size,
* the full size and the peak size.
*/
export const TxOrderIcebergDetails = ({
iceberg,
size,
marketId,
}: TxDetailsOrderIcebergDetailsProps) => {
return (
<div
className="inline-block iceberg bg-vega-blue-300 border-vega-blue-350 border-[1px] text-[70%] font-mono py-[2px] pl-2 pr-3 height-[1em] leading-[1em] cursor-pointer"
style={{ clipPath: 'polygon(10% 0%, 100% 0%, 90% 100%, 0% 100%)' }}
>
<Tooltip description={t('Iceberg: Minimum visible size')}>
<span className="align-bottom text-vega-orange-650">
{marketId ? (
<SizeInMarket
size={iceberg.minimumVisibleSize}
marketId={marketId}
/>
) : (
iceberg.minimumVisibleSize
)}
</span>
</Tooltip>
<Tooltip description={t('Iceberg: Total size')}>
<span className="text-sm text-vega-blue-600 mx-3">
{marketId ? <SizeInMarket size={size} marketId={marketId} /> : size}
</span>
</Tooltip>
<Tooltip description={t('Iceberg: Visible peak')}>
<span className="align-top text-vega-yellow-600">
{marketId ? (
<SizeInMarket size={iceberg.peakSize} marketId={marketId} />
) : (
iceberg.peakSize
)}
</span>
</Tooltip>
</div>
);
};
@@ -1,113 +0,0 @@
import { render } from '@testing-library/react';
import type { TxDetailsOrderProps } from './tx-order-peg';
import { TxOrderPeggedReference, getMarketDecimals } from './tx-order-peg';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import { PeggedReference, Side } from '@vegaprotocol/types';
// Mock the useExplorerMarketQuery hook
jest.mock('../../../links/market-link/__generated__/Market', () => ({
useExplorerMarketQuery: jest.fn().mockReturnValue({
data: {
market: { decimalPlaces: 0 },
},
loading: false,
}),
}));
describe('getSettlementAsset', () => {
it('should return the decimal places if data is defined', () => {
const data = {
market: {
__typename: 'Market',
id: '123',
decimalPlaces: 8,
},
};
const result = getMarketDecimals(data as Partial<ExplorerMarketQuery>);
expect(result).toEqual(8);
});
it('should return 0 if data is undefined', () => {
const result = getMarketDecimals(undefined);
expect(result).toEqual(0);
});
});
describe('TxOrderPeggedReference', () => {
beforeEach(() => {
// Mock the useExplorerMarketQuery hook return value
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
data: {
settlementAsset: 'some-settlement-asset',
},
loading: false,
});
});
afterEach(() => {
jest.resetAllMocks();
});
it('should render the offset and reference correctly', () => {
const props: TxDetailsOrderProps = {
side: Side.SIDE_BUY,
offset: '10',
reference: PeggedReference.PEGGED_REFERENCE_MID,
marketId: 'some-market-id',
};
const { getByTestId } = render(<TxOrderPeggedReference {...props} />);
expect(getByTestId('pegged-reference')).toHaveTextContent('Mid + 10');
});
it('should return null if the reference is "PEGGED_REFERENCE_UNSPECIFIED"', () => {
const props: TxDetailsOrderProps = {
side: Side.SIDE_BUY,
offset: '10',
reference: 'PEGGED_REFERENCE_UNSPECIFIED',
marketId: 'some-market-id',
};
const { container } = render(<TxOrderPeggedReference {...props} />);
expect(container.firstChild).toBeNull();
});
it('should render the offset without formatting initially, then render the formatted version', () => {
const props: TxDetailsOrderProps = {
side: Side.SIDE_BUY,
offset: '10',
reference: PeggedReference.PEGGED_REFERENCE_BEST_ASK,
marketId: 'some-market-id',
};
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
data: null,
loading: true,
});
const screen = render(<TxOrderPeggedReference {...props} />);
expect(screen.getByTestId('pegged-reference')).toHaveTextContent(
'Ask + 10'
);
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
data: {
market: {
decimalPlaces: 10,
},
},
loading: false,
});
screen.rerender(<TxOrderPeggedReference {...props} />);
expect(screen.getByTestId('pegged-reference')).toHaveTextContent(
'Ask + 0.000000001'
);
});
});
@@ -1,72 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { TableCell, TableRow } from '../../../table';
import type { VegaPeggedReference } from '../liquidity-provision/liquidity-provision-details';
import { Side, PeggedReferenceMapping } from '@vegaprotocol/types';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
export interface TxDetailsOrderProps {
offset: string;
reference: VegaPeggedReference;
marketId: string;
side: Side;
}
export function getMarketDecimals(
data: ExplorerMarketQuery | undefined
): number {
return data?.market?.decimalPlaces || 0;
}
/**
* Summarises an order's peg
*/
export const TxOrderPeggedReferenceRow = ({
offset,
reference,
marketId,
side,
}: TxDetailsOrderProps) => {
return (
<TableRow modifier="bordered">
<TableCell>{t('Pegged order')}</TableCell>
<TableCell>
<TxOrderPeggedReference
side={side}
offset={offset}
reference={reference}
marketId={marketId}
/>
</TableCell>
</TableRow>
);
};
export const TxOrderPeggedReference = ({
offset,
reference,
marketId,
side,
}: TxDetailsOrderProps) => {
const { data, loading } = useExplorerMarketQuery({
variables: { id: marketId },
});
const direction = side === Side.SIDE_BUY ? '+' : '-';
const decimalPlaces = getMarketDecimals(data);
if (reference === 'PEGGED_REFERENCE_UNSPECIFIED') {
return null;
}
return (
<span data-testid="pegged-reference">
{PeggedReferenceMapping[reference]}&nbsp;
{direction}&nbsp;
{!loading && data
? addDecimalsFormatNumber(offset, decimalPlaces)
: offset}
</span>
);
};
@@ -35,7 +35,7 @@ export const BundleSigners = ({
tx,
id,
}: BundleSignersProps) => {
const tokenLink = useLinks(DApp.Governance);
const tokenLink = useLinks(DApp.Token);
const bridgeFunction: BridgeFunction =
tx?.changes?.erc20 && 'contractAddress' in tx.changes.erc20
@@ -26,11 +26,6 @@ export const sharedHeaderProps = {
className: 'align-top',
};
const Labels: Record<BlockExplorerTransactionResult['type'], string> = {
'Stop Orders Submission': 'Stop Order',
'Stop Orders Cancellation': 'Cancel Stop Order',
};
/**
* These rows are shown for every transaction type, providing a consistent set of rows for the top
* of a transaction details row. The order is relatively arbitrary but felt right - it might need to
@@ -49,14 +44,12 @@ export const TxDetailsShared = ({
const time: string = blockData?.result.block.header.time || '';
const height: string = blockData?.result.block.header.height || txData.block;
const type = Labels[txData.type] || txData.type;
return (
<>
{hideTypeRow === false ? (
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{type}</TableCell>
<TableCell>{txData.type}</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
@@ -6,34 +6,10 @@ import {
SPECIAL_CASE_NETWORK_ID,
} from '../../../../links/party-link/party-link';
import SizeInAsset from '../../../../size-in-asset/size-in-asset';
import { AccountTypeMapping } from '@vegaprotocol/types';
import { AccountType } from '@vegaprotocol/types';
import { headerClasses, wrapperClasses } from '../transfer-details';
import type { components } from '../../../../../../types/explorer';
type Transfer = components['schemas']['commandsv1Transfer'];
type AccountTypes = components['schemas']['vegaAccountType'];
const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_UNSPECIFIED: 'Unspecified',
ACCOUNT_TYPE_INSURANCE: 'Insurance',
ACCOUNT_TYPE_SETTLEMENT: 'Settlement',
ACCOUNT_TYPE_MARGIN: 'Margin',
ACCOUNT_TYPE_GENERAL: 'General',
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: 'Infrastructure',
ACCOUNT_TYPE_FEES_LIQUIDITY: 'Liquidity',
ACCOUNT_TYPE_FEES_MAKER: 'Maker',
ACCOUNT_TYPE_BOND: 'Bond',
ACCOUNT_TYPE_EXTERNAL: 'External',
ACCOUNT_TYPE_GLOBAL_INSURANCE: 'Global Insurance',
ACCOUNT_TYPE_GLOBAL_REWARD: 'Global Reward',
ACCOUNT_TYPE_PENDING_TRANSFERS: 'Pending Transfers',
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: 'Maker Paid Fees',
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: 'Maker Received Fees',
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'LP Received Fees',
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Market Proposers',
ACCOUNT_TYPE_HOLDING: 'Holding',
ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution',
ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees',
};
import type { Transfer } from '../transfer-details';
interface TransferParticipantsProps {
transfer: Transfer;
@@ -54,22 +30,22 @@ export function TransferParticipants({
}: TransferParticipantsProps) {
// This mapping is required as the global account types require a type to be set, while
// the underlying protobufs allow for every field to be undefined.
const fromAcct: AccountTypes =
const fromAcct =
transfer.fromAccountType &&
transfer.fromAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
? transfer.fromAccountType
: 'ACCOUNT_TYPE_GENERAL';
const fromAccountTypeLabel: string = transfer.fromAccountType
? AccountType[fromAcct]
? AccountType[transfer.fromAccountType]
: AccountType.ACCOUNT_TYPE_GENERAL;
const fromAccountTypeLabel = transfer.fromAccountType
? AccountTypeMapping[fromAcct]
: 'Unknown';
const toAcct: AccountTypes =
const toAcct =
transfer.toAccountType &&
transfer.toAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
? transfer.toAccountType
: 'ACCOUNT_TYPE_GENERAL';
? AccountType[transfer.toAccountType]
: AccountType.ACCOUNT_TYPE_GENERAL;
const toAccountTypeLabel = transfer.fromAccountType
? AccountType[toAcct]
? AccountTypeMapping[toAcct]
: 'Unknown';
return (
@@ -27,9 +27,9 @@ export function TransferRepeat({ recurring }: TransferRepeatProps) {
<div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Active epochs')}</h2>
<div className="relative block rounded-lg py-6 text-center p-6">
<div>
<p>
<EpochOverview id={recurring.startEpoch} />
</div>
</p>
<p className="leading-10 my-2">
<IconForEpoch
start={recurring.startEpoch}
@@ -37,13 +37,13 @@ export function TransferRepeat({ recurring }: TransferRepeatProps) {
current={data?.epoch.id}
/>
</p>
<div>
<p>
{recurring.endEpoch ? (
<EpochOverview id={recurring.endEpoch} />
) : (
<span>{t('Forever')}</span>
)}
</div>
</p>
</div>
</div>
);
@@ -8,7 +8,7 @@ import { DispatchMetricLabels } from '@vegaprotocol/types';
export type Metric = components['schemas']['vegaDispatchMetric'];
export type Strategy = components['schemas']['vegaDispatchStrategy'];
const metricLabels: Record<Metric, string> = {
const metricLabels = {
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
...DispatchMetricLabels,
};
@@ -3,7 +3,7 @@ import { TransferRepeat } from './blocks/transfer-repeat';
import { TransferRewards } from './blocks/transfer-rewards';
import { TransferParticipants } from './blocks/transfer-participants';
export type Recurring = components['schemas']['commandsv1RecurringTransfer'];
export type Recurring = components['schemas']['v1RecurringTransfer'];
export type Metric = components['schemas']['vegaDispatchMetric'];
export const wrapperClasses =
@@ -11,7 +11,6 @@ import { TxDetailsBatch } from './tx-batch';
import { TxDetailsChainEvent } from './tx-chain-event';
import { TxDetailsNodeVote } from './tx-node-vote';
import { TxDetailsOrderCancel } from './tx-order-cancel';
import { TxDetailsStopOrderCancel } from './tx-stop-order-cancel';
import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation';
@@ -27,7 +26,6 @@ import { TxDetailsNodeAnnounce } from './tx-node-announce';
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer';
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -87,8 +85,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsProtocolUpgrade;
case 'Cancel Order':
return TxDetailsOrderCancel;
case 'Stop Orders Cancellation':
return TxDetailsStopOrderCancel;
case 'Amend Order':
return TxDetailsOrderAmend;
case 'Validator Heartbeat':
@@ -117,8 +113,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsUndelegate;
case 'State Variable Proposal':
return TxDetailsStateVariable;
case 'Stop Orders Submission':
return TxDetailsStopOrderSubmission;
case 'Transfer Funds':
return TxDetailsTransfer;
default:

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