Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17bc4541e9 | ||
|
|
2bd8044a6c | ||
|
|
b3a82fd6f0 |
@@ -3,7 +3,3 @@ apps/**/node_modules/*
|
||||
tmp/*
|
||||
.dockerignore
|
||||
dockerfiles
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: Release
|
||||
about: A template to outline the steps needed to for a successful release of our frontend apps
|
||||
title: 'Release [add dapp version]-core-[add core version]'
|
||||
labels:
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] Review [link to core release](xxx)
|
||||
- [ ] Tag frontend-monorepo
|
||||
- [ ] Create release and generate release notes
|
||||
- [ ] Run `@smoke` tests
|
||||
- [ ] Run `@regression` tests
|
||||
- [ ] Run `@slow` tests
|
||||
- [ ] Explorative testing of key flows
|
||||
- [ ] Set `release/[network]` to tagged commit
|
||||
- [ ] Verify builds (on Netlify and Fleek) are successful
|
||||
- [ ] Verify build has been deployed
|
||||
- [ ] Smoke testing on deployed app
|
||||
@@ -1,74 +0,0 @@
|
||||
name: After Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
after-release:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- 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: resolve ipfs hashes for release
|
||||
run: |
|
||||
echo "Tag name: ${{ github.event.release.tag_name }}"
|
||||
echo "Name: ${{ github.event.release.name }}"
|
||||
echo "Description: ${{ github.event.release.body }}"
|
||||
until docker pull vegaprotocol/trading:${{ github.event.release.tag_name }}; do
|
||||
echo "Image not pushed yet, waiting 60 seconds"
|
||||
sleep 60
|
||||
done
|
||||
docker run --rm vegaprotocol/trading:${{ github.event.release.tag_name }} 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
|
||||
Tye 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 [vega.trading](https://vega.trading).
|
||||
|
||||
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 }}/
|
||||
@@ -13,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
add_issue:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Add issue to project board'
|
||||
run: |
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
name: 'Check if branch is shorter than 52 chars'
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
branch-naming-rules:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# echo "branches that are longer than 51 chars can't be parsed by kubernetes to create previews. Each app has prefix of it's name like: 'governance-' (12 chars), what leaves 51 max branch length"
|
||||
# current parsable length: $( git rev-parse --abbrev-ref HEAD | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | wc -c)
|
||||
- uses: deepakputhraya/action-branch-name@master
|
||||
with:
|
||||
# regex: '([a-z])+\/([a-z])+' # Regex the branch should match. This example enforces grouping
|
||||
# allowed_prefixes: 'feature,stable,fix' # All branches should start with the given prefix
|
||||
# ignore: master,develop # Ignore exactly matching branch names from convention
|
||||
min_length: 1 # Min length of the branch name
|
||||
max_length: 51 # Max length of the branch name
|
||||
@@ -1,211 +0,0 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release/*
|
||||
- develop
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- ready_for_review
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
jobs:
|
||||
node-modules:
|
||||
runs-on: ubuntu-22.04
|
||||
name: 'Cache yarn modules'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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-
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
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-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 + unit test + build'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- 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: Check formatting
|
||||
run: yarn nx format:check
|
||||
|
||||
- name: Lint affected
|
||||
run: yarn nx affected:lint --max-warnings=0
|
||||
|
||||
- name: Build affected spec
|
||||
run: yarn nx affected --target=build-spec
|
||||
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
|
||||
- name: Build affected
|
||||
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
echo ">>>> debug"
|
||||
echo "NX_BASE: ${{ env.NX_BASE }}"
|
||||
echo "NX_HEAD: ${{ env.NX_HEAD }}"
|
||||
echo ">>>> eof debug"
|
||||
|
||||
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
|
||||
|
||||
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
|
||||
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
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
projects-e2e: ${{ env.PROJECTS_E2E }}
|
||||
preview_governance: ${{ env.PREVIEW_GOVERNANCE }}
|
||||
preview_trading: ${{ env.PREVIEW_TRADING }}
|
||||
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
|
||||
|
||||
cypress:
|
||||
needs: lint-test-build
|
||||
name: '(CI) cypress'
|
||||
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
|
||||
tags: '@smoke @regression'
|
||||
|
||||
publish-dist:
|
||||
needs: lint-test-build
|
||||
name: '(CD) publish dist'
|
||||
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
|
||||
uses: ./.github/workflows/publish-dist.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.lint-test-build.outputs.projects }}
|
||||
|
||||
dist-check:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- publish-dist
|
||||
- lint-test-build
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
name: '(CD) comment preview links'
|
||||
steps:
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: Previews
|
||||
|
||||
- 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.lint-test-build.outputs.preview_governance }}
|
||||
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
|
||||
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
|
||||
|
||||
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
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.cypress.result }}"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -13,17 +13,11 @@ on:
|
||||
jobs:
|
||||
cypress-run:
|
||||
name: Run Cypress Trading tests -- live environment
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
@@ -33,4 +27,5 @@ jobs:
|
||||
config: baseUrl=${{ github.event.inputs.url }}
|
||||
env: grepTags=@live
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
type: choice
|
||||
options:
|
||||
- explorer-e2e
|
||||
- governance-e2e
|
||||
- token-e2e
|
||||
- trading-e2e
|
||||
tags:
|
||||
description: 'Test tags to run'
|
||||
|
||||
@@ -10,5 +10,5 @@ jobs:
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
|
||||
projects: '["explorer-e2e","token-e2e","trading-e2e"]'
|
||||
tags: '@smoke @regression @slow'
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Cypress tests - PR
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
jobs:
|
||||
pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout frontend mono repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
- name: Remove package.json & yarn.lock to avoid installing everything
|
||||
run: rm package.json yarn.lock
|
||||
|
||||
- name: Install nx
|
||||
run: yarn add nx
|
||||
|
||||
# Check SHAs
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v2
|
||||
with:
|
||||
main-branch-name: ${{ github.base_ref || github.ref_name }}
|
||||
set-environment-variables-for-job: true
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
affected=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)
|
||||
echo -n "Affected projects: $affected"
|
||||
projects=""
|
||||
if [[ $affected == *"token"* ]]; then projects+='"token-e2e" '; fi
|
||||
if [[ $affected == *"trading"* ]]; then projects+='"trading-e2e" '; fi
|
||||
if [[ $affected == *"explorer"* ]]; then projects+='"explorer-e2e" '; fi
|
||||
if [[ -z "$projects" ]]; then projects+='"token-e2e" "trading-e2e" "explorer-e2e" '; fi
|
||||
projects=${projects%?}
|
||||
projects=[${projects// /,}]
|
||||
echo PROJECTS=$projects >> $GITHUB_ENV
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
|
||||
run:
|
||||
needs: pr
|
||||
if: ${{ needs.pr.outputs.projects != '[]' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.pr.outputs.projects }}
|
||||
tags: '@smoke @regression'
|
||||
|
||||
# Report single result at the end, to avoid mess with required checks in PR
|
||||
result:
|
||||
if: ${{ always() }}
|
||||
needs: run
|
||||
runs-on: ubuntu-latest
|
||||
name: Cypress result
|
||||
steps:
|
||||
- run: |
|
||||
result="${{ needs.run.result }}"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,4 +1,3 @@
|
||||
name: (CI) Cypress Run
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -18,9 +17,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project: ${{ fromJSON(inputs.projects) }}
|
||||
name: ${{ matrix.project }}
|
||||
runs-on: self-hosted-runner
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
# Checks if skip cache was requested
|
||||
- name: Set skip-nx-cache flag
|
||||
@@ -66,11 +64,12 @@ jobs:
|
||||
######
|
||||
|
||||
- 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
|
||||
working-directory: frontend-monorepo
|
||||
env:
|
||||
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
|
||||
CYPRESS_grepTags: ${{ inputs.tags }}
|
||||
|
||||
######
|
||||
## Upload logs
|
||||
@@ -84,18 +83,8 @@ jobs:
|
||||
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
|
||||
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
|
||||
|
||||
- name: Print logs files
|
||||
if: ${{ always() }}
|
||||
run: ls -alsh /home/runner/.vegacapsule/testnet/logs/
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() }}
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: logs-${{ matrix.project }}
|
||||
path: /home/runner/.vegacapsule/testnet/logs
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: test-report-${{ matrix.project }}
|
||||
path: frontend-monorepo/apps/${{ matrix.project }}/cypress/reports
|
||||
|
||||
@@ -8,25 +8,22 @@ on:
|
||||
jobs:
|
||||
master:
|
||||
name: Generate Queries
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
fetch-depth: 0
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
run: yarn install
|
||||
- name: Generate queries
|
||||
run: node ./scripts/get-queries.js
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: queries
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: Verify PR title
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint_pr:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- 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
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Verify PR title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review, reopened, edited, synchronize]
|
||||
jobs:
|
||||
lint_pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
- name: Install commitlint cli and config
|
||||
run: npm install @commitlint/cli @commitlint/config-conventional
|
||||
- name: Create config
|
||||
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
|
||||
- name: Check PR title
|
||||
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
master:
|
||||
name: Generate Queries
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
node-version: 16.14.0
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Generate queries
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
name: (CD) Publish docker + s3
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
projects:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish-dist:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
app: ${{ fromJSON(inputs.projects) }}
|
||||
name: ${{ matrix.app }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
id: quemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.qemu.outputs.platforms }}
|
||||
|
||||
- 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
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
with:
|
||||
# registry: registry.hub.docker.com
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
# https://docs.github.com/en/actions/learn-github-actions/contexts
|
||||
- name: Define variables
|
||||
run: |
|
||||
envName=''
|
||||
domain="vega.rocks"
|
||||
if [[ "${{ github.event_name }}" = "push" ]]; then
|
||||
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
|
||||
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
|
||||
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
envName="stagnet1"
|
||||
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ ${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }} = "true" ]]; then
|
||||
envName="mainnet"
|
||||
elif [[ "${{ github.ref }}" =~ .*main$ ]]; then
|
||||
envName="mainnet"
|
||||
fi
|
||||
if [[ "${envName}" = "mainnet" ]]; then
|
||||
domain="vega.xyz"
|
||||
fi
|
||||
bucketName="${{ matrix.app }}.${envName}.${domain}"
|
||||
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
|
||||
fi
|
||||
echo ENV_NAME=${envName} >> $GITHUB_ENV
|
||||
|
||||
- name: Build local dist
|
||||
run: |
|
||||
flags=""
|
||||
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
|
||||
if [[ "${{ env.ENV_NAME }}" != "ops-vega" ]]; then
|
||||
flags="--env=${{ env.ENV_NAME }}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${{ matrix.app }}" = "trading" ]; then
|
||||
yarn nx export trading $flags || (yarn install && yarn nx export trading $flags)
|
||||
DIST_LOCATION=dist/apps/trading/exported
|
||||
else
|
||||
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
|
||||
tree dist-result
|
||||
|
||||
- name: Build and export to local Docker
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
load: true
|
||||
build-args: |
|
||||
APP=${{ matrix.app }}
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
|
||||
|
||||
- name: Image digest
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
|
||||
- name: Sanity check docker image
|
||||
run: |
|
||||
echo "Check ipfs-hash"
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local cat /ipfs-hash > ${{ matrix.app }}-ipfs-hash
|
||||
echo "List html directory"
|
||||
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local sh -c 'apk add --update tree; tree /usr/share/nginx/html'
|
||||
|
||||
- name: Publish dist as docker image (ghcr)
|
||||
uses: docker/build-push-action@v3
|
||||
if: ${{ github.event_name == 'pull_request' || (matrix.app == 'trading' && github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') ) }}
|
||||
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)
|
||||
uses: docker/build-push-action@v3
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
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.ref_name }}
|
||||
vegaprotocol/${{ matrix.app }}:mainnet
|
||||
|
||||
# bucket creation in github.com/vegaprotocol/terraform//frontend
|
||||
- name: Publish dist to s3
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') }}
|
||||
with:
|
||||
args: --acl private --follow-symlinks --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: ${{ env.BUCKET_NAME }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: 'eu-west-1'
|
||||
SOURCE_DIR: 'dist-result'
|
||||
|
||||
- name: Add preview label
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
labels: ${{ matrix.app }}-preview
|
||||
number: ${{ github.event.number }}
|
||||
|
||||
- name: Trigger fleek deployment
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
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: Update vega.trading DNS to redirect to the new console
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
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 ${{ matrix.app }}-ipfs-hash)
|
||||
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
|
||||
|
||||
# Generate console URL
|
||||
new_console_url_type=ipfs
|
||||
# new_console_url_type=ipns
|
||||
new_console_url_domain=cf-ipfs.com
|
||||
# new_console_url_domain=dweb.link
|
||||
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
|
||||
echo "new_console_url=${new_console_url}"
|
||||
|
||||
# Update record in DNSimple
|
||||
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
|
||||
dnsimple_account_id=84895
|
||||
dnsimple_zone_name=vega.trading
|
||||
dnsimple_record_id=44409591
|
||||
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
|
||||
|
||||
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X PATCH \
|
||||
-d "{
|
||||
\"content\": \"${new_console_url}\"
|
||||
}" \
|
||||
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
|
||||
@@ -0,0 +1,126 @@
|
||||
name: Publish docker containers
|
||||
|
||||
'on':
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-*'
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Publish tag to Docker Hub & GitHub Registry'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
tag:
|
||||
description: 'Git Tag to build and publish'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
apps:
|
||||
description: 'Applications to build and publish'
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- '["explorer", "token", "trading"]'
|
||||
- '["explorer"]'
|
||||
- '["token"]'
|
||||
- '["trading"]'
|
||||
archs:
|
||||
description: 'Architecture to build and publish'
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- linux/amd64, linux/arm64
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
jobs:
|
||||
master:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
app: ${{ fromJson(inputs.apps || '["explorer", "token", "trading"]') }}
|
||||
name: Build the ${{ matrix.app }} image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Set up QEMU
|
||||
id: quemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.qemu.outputs.platforms }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: ${{ inputs.publish || startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Determine Docker Image tag
|
||||
id: tags
|
||||
run: |
|
||||
hash=$(git rev-parse HEAD|cut -b1-8)
|
||||
versionTag=${{ inputs.tag || startsWith(github.ref, 'refs/tags/') && github.ref_name || '${hash}' }}
|
||||
echo ::set-output name=version::${versionTag}
|
||||
echo ::set-output name=npmVersion::$(cat dockerfiles/${{ matrix.app =='trading' && 'Dockerfile.next' || 'Dockerfile.cra' }} | grep FROM | head -n 1 | awk '{print $2}' | cut -d ':' -f 2 | cut -d '-' -f 1 )
|
||||
|
||||
- name: Print config
|
||||
run: |
|
||||
git rev-parse --verify HEAD
|
||||
git status
|
||||
echo "inputs.tag=${{ inputs.tag }}"
|
||||
echo "inputs.publish=${{ inputs.publish }}"
|
||||
echo "inputs.apps=${{ inputs.apps }}"
|
||||
echo "inputs.archs=${{ inputs.archs }}"
|
||||
echo "steps.tags.outputs.version=${{ steps.tags.outputs.version }}"
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ steps.tags.outputs.npmVersion }}
|
||||
|
||||
- name: Build frontend dists
|
||||
run: |
|
||||
yarn --verbose --pure-lockfile
|
||||
yarn nx ${{ matrix.app =='trading' && 'export' || 'build' }} ${{ matrix.app }} --pure-lockfile
|
||||
|
||||
- name: Build and export to local Docker
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: dockerfiles/${{ matrix.app =='trading' && 'Dockerfile.next' || 'Dockerfile.cra' }}.dist
|
||||
build-args: APP=${{ matrix.app }}
|
||||
load: true
|
||||
tags: vegaprotocol/${{ matrix.app }}:local
|
||||
|
||||
- name: Sanity check docker image
|
||||
run: |
|
||||
docker run --rm vegaprotocol/${{ matrix.app }}:local cat .env
|
||||
docker run --rm vegaprotocol/${{ matrix.app }}:local ls -lah
|
||||
|
||||
- name: Build and push to DockerHub
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: ${{ inputs.publish || startsWith(github.ref, 'refs/tags/') }}
|
||||
file: dockerfiles/${{ matrix.app =='trading' && 'Dockerfile.next' || 'Dockerfile.cra' }}.dist
|
||||
build-args: APP=${{ matrix.app }}
|
||||
platforms: ${{ inputs.archs || 'linux/amd64, linux/arm64' }}
|
||||
tags: |
|
||||
vegaprotocol/${{ matrix.app }}:latest
|
||||
vegaprotocol/${{ matrix.app }}:${{ steps.tags.outputs.version }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Publish libs to npm
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
project:
|
||||
description: 'Monorepo project to publish'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- announcements
|
||||
- ui-toolkit
|
||||
- react-helpers
|
||||
- tailwindcss-config
|
||||
- types
|
||||
- utils
|
||||
- i18n
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Build & Publish - Tag
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: 'read'
|
||||
actions: 'read'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build project
|
||||
run: yarn nx build ${{inputs.project}}
|
||||
|
||||
- name: Publish project to @vegaprotocol
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: dist/libs/${{inputs.project}}/package.json
|
||||
access: public
|
||||
@@ -1,77 +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: Update vega.trading DNS to redirect to the new console
|
||||
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)
|
||||
|
||||
# Generate console URL
|
||||
new_console_url_type=ipfs
|
||||
# new_console_url_type=ipns
|
||||
new_console_url_domain=cf-ipfs.com
|
||||
# new_console_url_domain=dweb.link
|
||||
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
|
||||
echo "new_console_url=${new_console_url}"
|
||||
|
||||
# Update record in DNSimple
|
||||
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
|
||||
dnsimple_account_id=84895
|
||||
dnsimple_zone_name=vega.trading
|
||||
dnsimple_record_id=44409591
|
||||
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
|
||||
|
||||
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X PATCH \
|
||||
-d "{
|
||||
\"content\": \"${new_console_url}\"
|
||||
}" \
|
||||
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Unit tests & build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
pull_request:
|
||||
jobs:
|
||||
pr:
|
||||
name: Test and lint - PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: 'read'
|
||||
actions: 'read'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v2
|
||||
with:
|
||||
main-branch-name: ${{ github.base_ref }}
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
- name: Restore node_modules from cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Check formatting
|
||||
run: yarn nx format:check
|
||||
- name: Lint affected
|
||||
run: yarn nx affected:lint --max-warnings=0
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
- name: Build affected
|
||||
run: yarn nx affected:build
|
||||
- name: Build affected spec
|
||||
run: yarn nx affected --target=build-spec
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/dist-result
|
||||
/tmp
|
||||
/out-tsc
|
||||
/tools/executors/**/*.js
|
||||
@@ -47,6 +46,3 @@ cypress.env.json
|
||||
|
||||
# Next.js
|
||||
.next
|
||||
|
||||
#cypress
|
||||
/apps/**/cypress/reports/
|
||||
|
||||
@@ -7,4 +7,5 @@ __generated___
|
||||
|
||||
apps/static/src/assets/devnet-tranches.json
|
||||
apps/static/src/assets/mainnet-tranches.json
|
||||
apps/static/src/assets/stagnet3-tranches.json
|
||||
apps/static/src/assets/testnet-tranches.json
|
||||
|
||||
Vendored
+19
-1
@@ -1,2 +1,20 @@
|
||||
@Library('vega-shared-library') _
|
||||
runApprobation ignoreFailure: false, frontendBranch: env.BRANCH_NAME, type: 'frontend'
|
||||
|
||||
def commitHash = 'UNKNOWN'
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
options {
|
||||
skipDefaultCheckout true
|
||||
parallelsAlwaysFailFast()
|
||||
}
|
||||
stages {
|
||||
stage('approbation') {
|
||||
steps {
|
||||
sh 'printenv'
|
||||
checkout scm
|
||||
runApprobation ignoreFailure: false, frontendBranch: env.BRANCH_NAME, type: 'frontend'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 -r /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
|
||||
@@ -72,7 +72,7 @@ Run `nx serve my-app` for a dev server. Navigate to the port specified in `app/<
|
||||
In order to generate the schemas for your GraphQL queries, you can run `GRAPHQL_SCHEMA_PATH=[YOUR SCHEMA FILE / API URL HERE] nx run types:generate`.
|
||||
|
||||
```bash
|
||||
export GRAPHQL_SCHEMA_PATH=https://api.n07.testnet.vega.xyz/graphql
|
||||
export GRAPHQL_SCHEMA_PATH=https://api.n11.testnet.vega.xyz/graphql
|
||||
yarn nx run types:generate
|
||||
```
|
||||
|
||||
@@ -103,68 +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:
|
||||
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 --dockerfile dockerfiles/Dockerfile.cra . --build-arg APP=[YOUR APP] --tag=[TAG]
|
||||
```
|
||||
|
||||
In order to run a container:
|
||||
|
||||
```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 outside the docker
|
||||
|
||||
Packaging prepared dist into [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image involves building the application on docker host machine from source.
|
||||
|
||||
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
|
||||
|
||||
```bash
|
||||
./docker/prepare-dist.sh
|
||||
```
|
||||
|
||||
You can build any of the containers locally with the following command:
|
||||
|
||||
```bash
|
||||
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
|
||||
```
|
||||
|
||||
### nx build inside the docker
|
||||
|
||||
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build) example.
|
||||
|
||||
```bash
|
||||
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=$(cat .nvmrc) --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
|
||||
```
|
||||
|
||||
### Computing ipfs-hash of the build
|
||||
|
||||
At the moment this feature is important only for `trading` (console) releases.
|
||||
|
||||
Each docker build finishes with hash calculation for dist directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
|
||||
|
||||
```bash
|
||||
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
|
||||
```
|
||||
|
||||
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
|
||||
|
||||
### Verifying ipfs-hash of existing current application version
|
||||
|
||||
An IPFS CID will be attached to every [release](https://github.com/vegaprotocol/frontend-monorepo/releases). If you are intending to pin an application on IPFS, you can check that your build matches by running the following steps:
|
||||
|
||||
1. Show latest release by runnning: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
|
||||
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
|
||||
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. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
|
||||
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
|
||||
|
||||
@@ -204,7 +161,7 @@ In order to setup and run vegawallet for e2e capsule tests, in a separate termin
|
||||
bash setup-vegawallet.sh
|
||||
```
|
||||
|
||||
3. copy generated `api-token` and paste the token into `CYPRESS_VEGA_WALLET_API_TOKEN` environment variable in either `apps/governance-e2e/.env` or `apps/explorer-e2e/.env` depending on which project needs testing.
|
||||
3. copy generated `api-token` and paste the token into `CYPRESS_VEGA_WALLET_API_TOKEN` environment variable in either `apps/token-e2e/.env` or `apps/explorer-e2e/.env` depending on which project needs testing.
|
||||
|
||||
Note: The script is only needed if capsule was built for first time or fresh. To run existing wallet service for capsule:
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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
|
||||
NX_VEGA_URL=http://localhost:3028/query
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_VEGA_CONFIG_URL=
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/capsule-network.json
|
||||
|
||||
CYPRESS_VEGA_TENDERMINT_URL=http://localhost:26617
|
||||
|
||||
@@ -18,4 +18,4 @@ NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
CYPRESS_VEGA_URL=http://localhost:3008/graphql
|
||||
CYPRESS_VEGA_URL=http://localhost:3028/query
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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_URL=https://api.vega.xyz/query
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=0
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
@@ -2,7 +2,7 @@
|
||||
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_URL=https://api.n11.testnet.vega.xyz/graphql
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
|
||||
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
@@ -22,11 +21,10 @@ module.exports = defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
viewportWidth: 1440,
|
||||
viewportHeight: 900,
|
||||
testIsolation: false,
|
||||
},
|
||||
env: {
|
||||
environment: 'CUSTOM',
|
||||
networkQueryUrl: 'http://localhost:3008/graphql',
|
||||
networkQueryUrl: 'http://localhost:3028/query',
|
||||
ethUrl: 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
|
||||
commitHash: 'dev',
|
||||
tsConfig: 'tsconfig.json',
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
"governance.proposal.updateMarket.minVoterBalance",
|
||||
"governance.proposal.updateNetParam.minProposerBalance",
|
||||
"governance.proposal.updateNetParam.minVoterBalance",
|
||||
"governance.proposal.updateAsset.minProposerBalance",
|
||||
"governance.proposal.updateAsset.minVoterBalance",
|
||||
"reward.staking.delegation.maxPayoutPerEpoch",
|
||||
"reward.staking.delegation.maxPayoutPerParticipant",
|
||||
"reward.staking.delegation.minimumValidatorStake",
|
||||
@@ -21,6 +19,10 @@
|
||||
"validators.delegation.minAmount"
|
||||
],
|
||||
"fiveDecimal": [
|
||||
"governance.proposal.updateAsset.minProposerBalance",
|
||||
"governance.proposal.updateAsset.minVoterBalance",
|
||||
"governance.proposal.updateAsset.requiredParticipation",
|
||||
"governance.proposal.updateMarket.minProposerEquityLikeShare",
|
||||
"market.fee.factors.infrastructureFee",
|
||||
"market.fee.factors.makerFee",
|
||||
"market.liquidity.bondPenaltyParameter",
|
||||
@@ -75,8 +77,6 @@
|
||||
"governance.proposal.updateMarket.requiredParticipationLP",
|
||||
"governance.proposal.updateNetParam.requiredMajority",
|
||||
"governance.proposal.updateNetParam.requiredParticipation",
|
||||
"governance.proposal.updateMarket.minProposerEquityLikeShare",
|
||||
"governance.proposal.updateAsset.requiredParticipation",
|
||||
"validators.vote.required"
|
||||
],
|
||||
"duration": [
|
||||
|
||||
@@ -1,48 +1,150 @@
|
||||
context('Asset page', { tags: '@regression' }, () => {
|
||||
const columns = ['symbol', 'name', 'id', 'type', 'status', 'actions'];
|
||||
const hiddenOnMobile = ['id', 'type', 'status'];
|
||||
describe('Verify elements on page', () => {
|
||||
before('Navigate to assets page', () => {
|
||||
cy.visit('/assets');
|
||||
context('Asset page', { tags: '@regression' }, function () {
|
||||
before('gather system asset information', function () {
|
||||
cy.get_asset_information().as('assetsInfo');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
const assetsNavigation = 'a[href="/assets"]';
|
||||
const assetHeader = '[data-testid="asset-header"]';
|
||||
const jsonSection = '.language-json';
|
||||
|
||||
before('Navigate to assets page', function () {
|
||||
cy.visit('/');
|
||||
cy.get(assetsNavigation).click();
|
||||
|
||||
// Check we have enough enough assets
|
||||
const assetNames = Object.keys(this.assetsInfo);
|
||||
assert.isAtLeast(
|
||||
assetNames.length,
|
||||
5,
|
||||
'Ensuring we have at least 5 assets to test'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to see full assets list', () => {
|
||||
cy.getAssets().then((assets) => {
|
||||
assets.forEach((asset) => {
|
||||
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
|
||||
});
|
||||
});
|
||||
columns.forEach((col) => {
|
||||
cy.get(`[col-id="${col}"]`).should('be.visible');
|
||||
it('should be able to see assets page sections', function () {
|
||||
const assetNames = Object.keys(this.assetsInfo);
|
||||
assetNames.forEach((assetName) => {
|
||||
cy.get(assetHeader)
|
||||
.contains(assetName)
|
||||
.should('be.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see assets page displayed in mobile', () => {
|
||||
cy.switchToMobile();
|
||||
it('should be able to see all asset details displayed in JSON', function () {
|
||||
const assetNames = Object.keys(this.assetsInfo);
|
||||
assetNames.forEach((assetName) => {
|
||||
cy.get(assetHeader)
|
||||
.contains(assetName)
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection)
|
||||
.invoke('text')
|
||||
.convert_string_json_to_js_object()
|
||||
.then((assetsListedInJson) => {
|
||||
const assetInfo = this.assetsInfo[assetName];
|
||||
|
||||
hiddenOnMobile.forEach((col) => {
|
||||
cy.get(`[col-id="${col}"]`).should('have.length', 0);
|
||||
});
|
||||
assert.equal(assetsListedInJson.name, assetInfo.node.name);
|
||||
assert.equal(assetsListedInJson.id, assetInfo.node.id);
|
||||
assert.equal(
|
||||
assetsListedInJson.decimals,
|
||||
assetInfo.node.decimals
|
||||
);
|
||||
assert.equal(assetsListedInJson.symbol, assetInfo.node.symbol);
|
||||
assert.equal(
|
||||
assetsListedInJson.source.__typename,
|
||||
assetInfo.node.source.__typename
|
||||
);
|
||||
|
||||
cy.getAssets().then((assets) => {
|
||||
assets.forEach((asset) => {
|
||||
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
|
||||
});
|
||||
if (assetInfo.node.source.__typename == 'ERC20') {
|
||||
assert.equal(
|
||||
assetsListedInJson.source.contractAddress,
|
||||
assetInfo.node.source.contractAddress
|
||||
);
|
||||
}
|
||||
|
||||
if (assetInfo.node.source.__typename == 'BuiltinAsset') {
|
||||
assert.equal(
|
||||
assetsListedInJson.source.maxFaucetAmountMint,
|
||||
assetInfo.node.source.maxFaucetAmountMint
|
||||
);
|
||||
}
|
||||
|
||||
let knownAssetTypes = ['BuiltinAsset', 'ERC20'];
|
||||
assert.include(
|
||||
knownAssetTypes,
|
||||
assetInfo.node.source.__typename,
|
||||
`Checking that current asset type of ${assetInfo.node.source.__typename} /
|
||||
is one of: ${knownAssetTypes}: /
|
||||
If fail then we need to add extra tests for un-encountered asset types`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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`)
|
||||
.eq(0)
|
||||
.should('contain.text', 'View details');
|
||||
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
|
||||
.eq(0)
|
||||
.click();
|
||||
cy.getByTestId('asset-header').should('have.text', asset.name);
|
||||
cy.go('back');
|
||||
it('should be able to switch assets between light and dark mode', function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
const jsonFields = '.hljs';
|
||||
const sideMenuBackground = '.absolute';
|
||||
|
||||
// Engage dark mode if not allready set
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.then((background_color) => {
|
||||
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
|
||||
cy.get(themeSwitcher).click();
|
||||
});
|
||||
|
||||
// Engage white mode
|
||||
cy.get(themeSwitcher).click();
|
||||
|
||||
// White Mode
|
||||
cy.get(assetsNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSideMenuBackgroundColor);
|
||||
|
||||
// Dark Mode
|
||||
cy.get(themeSwitcher).click();
|
||||
cy.get(assetsNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSideMenuBackgroundColor);
|
||||
});
|
||||
|
||||
it('should be able to see assets page displayed in mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(assetsNavigation).click();
|
||||
|
||||
const assetNames = Object.keys(this.assetsInfo);
|
||||
assetNames.forEach((assetName) => {
|
||||
cy.get(assetHeader)
|
||||
.contains(assetName)
|
||||
.should('be.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
context('Blocks page', { tags: '@regression' }, function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
beforeEach(() => {
|
||||
cy.visit('/blocks');
|
||||
});
|
||||
const blockNavigation = 'a[href="/blocks"]';
|
||||
const blockHeight = '[data-testid="block-height"]';
|
||||
const blockTime = '[data-testid="block-time"]';
|
||||
const blockHeader = '[data-testid="block-header"]';
|
||||
const previousBlockBtn = '[data-testid="previous-block"]';
|
||||
const infiniteScrollWrapper = '[data-testid="infinite-scroll-wrapper"]';
|
||||
|
||||
beforeEach('navigate to blocks page', function () {
|
||||
cy.get(blockNavigation).click();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed', function () {
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(blockNavigation).click();
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Block validator page is displayed', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get('[data-testid="block-validator"]').should('not.be.empty');
|
||||
cy.get(blockTime).should('not.be.empty');
|
||||
//TODO: Add assertion for transactions when txs are added
|
||||
@@ -29,7 +35,7 @@ context('Blocks page', { tags: '@regression' }, function () {
|
||||
|
||||
it('Navigate to previous block', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get(blockHeader)
|
||||
.invoke('text')
|
||||
.then(($blockHeaderTxt) => {
|
||||
|
||||
@@ -6,39 +6,75 @@ context('Home Page', function () {
|
||||
describe('Stats page', { tags: '@smoke' }, function () {
|
||||
const statsValue = '[data-testid="stats-value"]';
|
||||
|
||||
it('Should show connected environment', function () {
|
||||
const deployedEnv = Cypress.env('environment').toUpperCase();
|
||||
cy.get('[data-testid="stats-environment"]').should(
|
||||
'have.text',
|
||||
`/ ${deployedEnv}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should show connected environment stats', function () {
|
||||
const statTitles = {
|
||||
0: 'Status',
|
||||
1: 'Epoch',
|
||||
2: 'Block height',
|
||||
3: 'Uptime',
|
||||
4: 'Total nodes',
|
||||
5: 'Total staked',
|
||||
6: 'Backlog',
|
||||
7: 'Trades / second',
|
||||
8: 'Orders / block',
|
||||
9: 'Orders / second',
|
||||
10: 'Transactions / block',
|
||||
11: 'Block time',
|
||||
12: 'Time',
|
||||
13: 'App',
|
||||
14: 'Tendermint',
|
||||
15: 'Up since',
|
||||
16: 'Chain ID',
|
||||
1: 'Height',
|
||||
2: 'Uptime',
|
||||
3: 'Total nodes',
|
||||
4: 'Total staked',
|
||||
5: 'Backlog',
|
||||
6: 'Trades / second',
|
||||
7: 'Orders / block',
|
||||
8: 'Orders / second',
|
||||
9: 'Transactions / block',
|
||||
10: 'Block time',
|
||||
11: 'Time',
|
||||
12: 'App',
|
||||
13: 'Tendermint',
|
||||
14: 'Up since',
|
||||
15: 'Chain ID',
|
||||
};
|
||||
|
||||
cy.get('[data-testid="stats-title"]')
|
||||
.each(($list, index) => {
|
||||
cy.wrap($list).should('contain.text', statTitles[index]);
|
||||
cy.wrap($list).should('have.text', statTitles[index]);
|
||||
})
|
||||
.then(($list) => {
|
||||
cy.wrap($list).should('have.length', 17);
|
||||
cy.wrap($list).should('have.length', 16);
|
||||
});
|
||||
|
||||
cy.get(statsValue).eq(0).should('have.text', 'CONNECTED');
|
||||
cy.get(statsValue).eq(1).should('not.be.empty');
|
||||
cy.get(statsValue)
|
||||
.eq(2)
|
||||
.invoke('text')
|
||||
.should('match', /\d+d \d+h \d+m \d+s/i);
|
||||
cy.get(statsValue).eq(3).should('have.text', '2');
|
||||
cy.get(statsValue)
|
||||
.eq(4)
|
||||
.invoke('text')
|
||||
.should('match', /\d+\.\d\d(?!\d)/i);
|
||||
cy.get(statsValue).eq(5).should('have.text', '0');
|
||||
cy.get(statsValue).eq(6).should('have.text', '0');
|
||||
cy.get(statsValue).eq(7).should('have.text', '0');
|
||||
cy.get(statsValue).eq(8).should('have.text', '0');
|
||||
cy.get(statsValue).eq(9).should('not.be.empty');
|
||||
cy.get(statsValue).eq(10).should('not.be.empty');
|
||||
cy.get(statsValue).eq(11).should('not.be.empty');
|
||||
cy.get(statsValue)
|
||||
.eq(12)
|
||||
.invoke('text')
|
||||
.should('match', /v\d+\.\d+\.\d+/i);
|
||||
cy.get(statsValue)
|
||||
.eq(13)
|
||||
.invoke('text')
|
||||
.should('match', /\d+\.\d+\.\d+/i);
|
||||
cy.get(statsValue).eq(14).should('not.be.empty');
|
||||
cy.get(statsValue).eq(15).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('Block height should be updating', function () {
|
||||
cy.get(statsValue)
|
||||
.eq(2)
|
||||
.eq(1)
|
||||
.invoke('text')
|
||||
.then((blockHeightTxt) => {
|
||||
cy.get(statsValue)
|
||||
@@ -50,4 +86,75 @@ context('Home Page', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Git info', function () {
|
||||
it('git info is rendered on the footer of the page', function () {
|
||||
cy.getByTestId('git-info').within(() => {
|
||||
cy.getByTestId('git-network-data').within(() => {
|
||||
cy.contains('Reading network data from').should('be.visible');
|
||||
cy.get('span').should('have.text', Cypress.env('networkQueryUrl'));
|
||||
cy.getByTestId('link').should('be.visible');
|
||||
});
|
||||
|
||||
cy.getByTestId('git-eth-data').within(() => {
|
||||
cy.contains('Reading Ethereum data from').should('be.visible');
|
||||
cy.get('span').should('have.text', Cypress.env('ethUrl'));
|
||||
});
|
||||
|
||||
cy.getByTestId('git-commit-hash').within(() => {
|
||||
cy.contains('Version/commit hash:').should('be.visible');
|
||||
cy.getByTestId('link').should('have.text', Cypress.env('commitHash'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search bar', function () {
|
||||
it('Successful search for specific id by block id', function () {
|
||||
const blockId = '973624';
|
||||
search(blockId);
|
||||
cy.url().should('include', blockId);
|
||||
});
|
||||
|
||||
it('Successful search for specific id by tx hash', function () {
|
||||
const txHash =
|
||||
'9ED3718AA8308E7E08EC588EE7AADAF49711D2138860D8914B4D81A2054D9FB8';
|
||||
search(txHash);
|
||||
cy.url().should('include', txHash);
|
||||
});
|
||||
|
||||
it('Successful search for specific id by tx id', function () {
|
||||
const txId =
|
||||
'0x61DCCEBB955087F50D0B85382DAE138EDA9631BF1A4F92E563D528904AA38898';
|
||||
search(txId);
|
||||
cy.url().should('include', txId);
|
||||
});
|
||||
|
||||
it('Error message displayed when invalid search by wrong string length', function () {
|
||||
search('9ED3718AA8308E7E08EC588EE7AADAF497D2138860D8914B4D81A2054D9FB8');
|
||||
validateSearchError("Something doesn't look right");
|
||||
});
|
||||
|
||||
it('Error message displayed when invalid search by invalid hash', function () {
|
||||
search(
|
||||
'9ED3718AA8308E7E08ECht8EE753DAF49711D2138860D8914B4D81A2054D9FB8'
|
||||
);
|
||||
validateSearchError('Transaction is not hexadecimal');
|
||||
});
|
||||
|
||||
it('Error message displayed when searching empty field', function () {
|
||||
cy.get('[data-testid="search"]').clear();
|
||||
cy.get('[data-testid="search-button"]').click();
|
||||
validateSearchError('Search required');
|
||||
});
|
||||
|
||||
function search(searchTxt) {
|
||||
cy.get('[data-testid="search"]').clear().type(searchTxt);
|
||||
cy.get('[data-testid="search-button"]').click();
|
||||
}
|
||||
|
||||
function validateSearchError(expectedError) {
|
||||
cy.get('[data-testid="search-error"]').should('have.text', expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,14 +2,17 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
||||
});
|
||||
describe('Verify elements on page', function () {
|
||||
beforeEach(() => {
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
const networkParametersNavigation = 'a[href="/network-parameters"]';
|
||||
const networkParametersHeader = '[data-testid="network-param-header"]';
|
||||
const tableRows = '[data-testid="key-value-table-row"]';
|
||||
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.visit('/');
|
||||
cy.get(networkParametersNavigation).click();
|
||||
});
|
||||
|
||||
it('should show network parameter heading at top of page', function () {
|
||||
cy.get(networkParametersHeader)
|
||||
.should('have.text', 'Network Parameters')
|
||||
@@ -138,7 +141,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () {
|
||||
it('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () {
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
network_parameters = Object.entries(network_parameters);
|
||||
network_parameters.forEach((network_parameter) => {
|
||||
@@ -198,8 +201,55 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to switch network parameter page - between light and dark mode', function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
const jsonFields = '.hljs';
|
||||
const sideMenuBackground = '.absolute';
|
||||
|
||||
// Engage dark mode if not already set
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.then((background_color) => {
|
||||
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
|
||||
cy.get(themeSwitcher).click();
|
||||
});
|
||||
|
||||
// Engage white mode
|
||||
cy.get(themeSwitcher).click();
|
||||
|
||||
// White Mode
|
||||
cy.get(networkParametersNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSideMenuBackgroundColor);
|
||||
|
||||
// Dark Mode
|
||||
cy.get(themeSwitcher).click();
|
||||
cy.get(networkParametersNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSideMenuBackgroundColor);
|
||||
});
|
||||
|
||||
it('should be able to see network parameters - on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(networkParametersNavigation).click();
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
network_parameters = Object.entries(network_parameters);
|
||||
network_parameters.forEach((network_parameter) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ const customNodeBtn = 'custom-node';
|
||||
context.skip('Node switcher', { tags: '@regression' }, function () {
|
||||
beforeEach('visit home page', function () {
|
||||
cy.intercept('GET', 'https://static.vega.xyz/assets/capsule-network.json', {
|
||||
hosts: ['http://localhost:3008/graphql'],
|
||||
hosts: ['http://localhost:3028/query'],
|
||||
}).as('nodeData');
|
||||
cy.visit('/');
|
||||
cy.wait('@nodeData');
|
||||
|
||||
@@ -123,7 +123,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
|
||||
.convert_string_json_to_js_object()
|
||||
.get_party_accounts_data_from_js_object()
|
||||
.then((accountsListedInJson) => {
|
||||
cy.getAssets().then((assetsInfo) => {
|
||||
cy.get_asset_information().then((assetsInfo) => {
|
||||
const assetInfo =
|
||||
assetsInfo[accountsListedInJson[assetInTest].asset.name];
|
||||
|
||||
@@ -205,7 +205,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_asset_decimals', (assetID) => {
|
||||
cy.getAssets().then((assetsInfo) => {
|
||||
cy.get_asset_information().then((assetsInfo) => {
|
||||
const assetDecimals = assetsInfo[assetData[assetID].name].decimals;
|
||||
let decimals = '';
|
||||
for (let i = 0; i < assetDecimals; i++) decimals += '0';
|
||||
@@ -219,7 +219,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
|
||||
balance type asset {id symbol decimals}}}}}}}}';
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `http://localhost:3008/graphql`,
|
||||
url: `http://localhost:3028/query`,
|
||||
body: {
|
||||
query: mutation,
|
||||
},
|
||||
|
||||
@@ -1,15 +1,303 @@
|
||||
context('Validator page', { tags: '@smoke' }, function () {
|
||||
const validatorMenuHeading = 'a[href="/validators"]';
|
||||
const tendermintDataHeader = '[data-testid="tendermint-header"]';
|
||||
const vegaDataHeader = '[data-testid="vega-header"]';
|
||||
const jsonSection = '.language-json';
|
||||
|
||||
before('Visit validators page and obtain data', function () {
|
||||
cy.visit('/validators');
|
||||
cy.visit('/');
|
||||
cy.get(validatorMenuHeading).click();
|
||||
cy.get_validators().as('validators');
|
||||
cy.get_nodes().as('nodes');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
it('should be able to see validator tiles', function () {
|
||||
cy.getNodes().then((nodes) => {
|
||||
nodes.forEach((node) => {
|
||||
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
|
||||
before('Ensure at least two validators are present', function () {
|
||||
assert.isAtLeast(
|
||||
this.validators.length,
|
||||
2,
|
||||
'Ensuring at least two validators exist'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to see validator page sections', function () {
|
||||
cy.get(vegaDataHeader)
|
||||
.contains('Vega data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see relevant validator information in tendermint section', function () {
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection)
|
||||
.invoke('text')
|
||||
.convert_string_json_to_js_object()
|
||||
.then((validatorsInJson) => {
|
||||
this.validators.forEach((validator, index) => {
|
||||
const validatorInJson =
|
||||
validatorsInJson.result.validators[index];
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.address,
|
||||
validator.address,
|
||||
`Checking that validator address shown in json matches system data`
|
||||
);
|
||||
cy.contains(validator.address).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.pub_key.type,
|
||||
validator.pub_key.type,
|
||||
`Checking that validator public key type shown in json matches system data`
|
||||
);
|
||||
cy.contains(validator.pub_key.type).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.pub_key.value,
|
||||
validator.pub_key.value,
|
||||
`Checking that validator public key value shown in json matches system data`
|
||||
);
|
||||
cy.contains(validator.pub_key.value).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.voting_power,
|
||||
validator.voting_power,
|
||||
`Checking that validator voting power in json matches system data`
|
||||
);
|
||||
cy.contains(validator.voting_power).should('be.visible');
|
||||
|
||||
// Proposer priority can change frequently mid test
|
||||
// Therefore only checking the field name is present.
|
||||
cy.contains('proposer_priority').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test disabled 2022/11/15 during the 0.62.1 upgrade. The JSON structure changed, and
|
||||
// this test failed. Rather than fix it, it will be replaced when the validator view displays
|
||||
// something useful rather than just dumping out the JSON.
|
||||
xit('should be able to see relevant node information in vega data section', function () {
|
||||
cy.get(vegaDataHeader)
|
||||
.contains('Vega data')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection)
|
||||
.invoke('text')
|
||||
.convert_string_json_to_js_object()
|
||||
.then((nodesInJson) => {
|
||||
this.nodes.forEach((node, index) => {
|
||||
const nodeInJson = nodesInJson.edges[index].node;
|
||||
|
||||
// Vegacapsule shows no info or null for following fields:
|
||||
// name, infoURL, avatarUrl, location, epoch data
|
||||
// Therefore, these values remain unchecked.
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.__typename,
|
||||
node.__typename,
|
||||
`Checking that node __typename shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.__typename).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.id,
|
||||
node.id,
|
||||
`Checking that node id shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.id).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.pubkey,
|
||||
node.pubkey,
|
||||
`Checking that node pubkey shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.pubkey).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.tmPubkey,
|
||||
node.tmPubkey,
|
||||
`Checking that node tmPubkey shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.tmPubkey).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.ethereumAddress,
|
||||
node.ethereumAddress,
|
||||
`Checking that node ethereumAddress shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.ethereumAddress).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.stakedByOperator,
|
||||
node.stakedByOperator,
|
||||
`Checking that node stakedByOperator value shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.stakedByOperator).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.stakedByDelegates,
|
||||
node.stakedByDelegates,
|
||||
`Checking that node stakedByDelegates value shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.stakedByDelegates).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.stakedTotal,
|
||||
node.stakedTotal,
|
||||
`Checking that node stakedTotal shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.stakedTotal).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.pendingStake,
|
||||
node.pendingStake,
|
||||
`Checking that node pendingStake shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.pendingStake).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.status,
|
||||
node.status,
|
||||
`Checking that node status shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.status).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see validator page displayed on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(validatorMenuHeading).click();
|
||||
cy.get(vegaDataHeader)
|
||||
.contains('Vega data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.next()
|
||||
.within(() => {
|
||||
this.validators.forEach((validator) => {
|
||||
cy.contains(validator.address).should('be.visible');
|
||||
cy.contains(validator.pub_key.type).should('be.visible');
|
||||
cy.contains(validator.pub_key.value).should('be.visible');
|
||||
cy.contains(validator.voting_power).should('be.visible');
|
||||
// Proposer priority can change frequently mid test
|
||||
// Therefore only checking the field name is present.
|
||||
cy.contains('proposer_priority').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to switch validator page between light and dark mode', function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
const jsonFields = '.hljs';
|
||||
const sideMenuBackground = '.absolute';
|
||||
|
||||
// Engage dark mode if not allready set
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.then((background_color) => {
|
||||
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
|
||||
cy.get(themeSwitcher).click();
|
||||
});
|
||||
|
||||
// Engage white mode
|
||||
cy.get(themeSwitcher).click();
|
||||
|
||||
// White Mode
|
||||
cy.get(validatorMenuHeading)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSideMenuBackgroundColor);
|
||||
|
||||
// Dark Mode
|
||||
cy.get(themeSwitcher).click();
|
||||
cy.get(validatorMenuHeading)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSideMenuBackgroundColor);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_validators', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `http://localhost:26617/validators`,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
.its(`body.result.validators`)
|
||||
.then(function (response) {
|
||||
let validators = [];
|
||||
response.forEach((account, index) => {
|
||||
validators[index] = account;
|
||||
});
|
||||
return validators;
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_nodes', () => {
|
||||
const mutation =
|
||||
'{nodesConnection { edges { node { id name infoUrl avatarUrl pubkey tmPubkey ethereumAddress \
|
||||
location stakedByOperator stakedByDelegates stakedTotal pendingStake \
|
||||
epochData { total offline online __typename } status name __typename}}}}';
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `http://localhost:3028/query`,
|
||||
body: {
|
||||
query: mutation,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
.its(`body.data.nodesConnection.edges`)
|
||||
.then(function (response) {
|
||||
let nodes = [];
|
||||
response.forEach((node) => {
|
||||
nodes.push(node);
|
||||
});
|
||||
return nodes;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,10 +21,6 @@ Cypress.Commands.add(
|
||||
}
|
||||
);
|
||||
|
||||
Cypress.Commands.add('switchToMobile', () => {
|
||||
cy.viewport('iphone-x');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('common_switch_to_mobile_and_click_toggle', function () {
|
||||
cy.viewport('iphone-x');
|
||||
cy.visit('/');
|
||||
|
||||
@@ -15,6 +15,5 @@
|
||||
|
||||
import '@vegaprotocol/cypress';
|
||||
import './common.functions.js';
|
||||
import 'cypress-mochawesome-reporter/register';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
registerCypressGrep();
|
||||
|
||||
+9
-16
@@ -1,20 +1,13 @@
|
||||
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.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://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.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
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://stagnet1.governance.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo
|
||||
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
NX_VEGA_EXPLORER_URL=/
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=0
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
# 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_TENDERMINT_URL=https://n04.d.vega.xyz/tm
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://dev.governance.vega.xyz
|
||||
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_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
|
||||
@@ -1,11 +1,10 @@
|
||||
# App configuration variables
|
||||
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_TENDERMINT_URL=https://be.explorer.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_BLOCK_EXPLORER=https://be.vega.community/rest/
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/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_GOVERNANCE_URL=https://token.vega.xyz
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=https://tm.be.mainnet-mirror.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_ENV=MIRROR
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
NX_EXPLORER_GENESIS=1
|
||||
NX_EXPLORER_GOVERNANCE=1
|
||||
NX_EXPLORER_NETWORK_PARAMETERS=1
|
||||
NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
NX_EXPLORER_MARKETS=0
|
||||
NX_EXPLORER_ORACLES=0
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
@@ -0,0 +1,12 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=SANDBOX
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
|
||||
@@ -1,2 +1,14 @@
|
||||
# .env is stagnet1, so there are no overrides required
|
||||
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz","STAGNET3":"https://stagnet3.explorer.vega.xyz"}'
|
||||
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.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
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.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
|
||||
@@ -1,12 +1,13 @@
|
||||
# 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_TENDERMINT_WEBSOCKET_URL=wss://tm.be.testnet.vega.xyz/websocket/
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_NETWORKS={}
|
||||
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_GOVERNANCE_URL=https://token.fairground.wtf
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
|
||||
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
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_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz/
|
||||
@@ -2,7 +2,6 @@
|
||||
NX_TENDERMINT_URL=http://localhost:26607/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
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=/
|
||||
@@ -35,11 +35,12 @@ Example configurations are provided here:
|
||||
- [Devnet](./.env.devnet)
|
||||
- [Capsule](./.env.capsule)
|
||||
- [Testnet](./.env.testnet)
|
||||
- [Stagnet3](./.env.stagnet3)
|
||||
|
||||
For convenience, you can boot the app injecting one of the configurations above by running:
|
||||
|
||||
```bash
|
||||
yarn nx run explorer:serve --env={env} # e.g. stagnet1
|
||||
yarn nx run explorer:serve --env={env} # e.g. stagnet3
|
||||
```
|
||||
|
||||
There are a few different configuration options offered for this app:
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -1,51 +1,62 @@
|
||||
import {
|
||||
AppFailure,
|
||||
NetworkLoader,
|
||||
NodeGuard,
|
||||
NodeSwitcherDialog,
|
||||
useEnvironment,
|
||||
useInitializeEnv,
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import classnames from 'classnames';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
||||
import { Nav } from './components/nav';
|
||||
import { Header } from './components/header';
|
||||
import { Main } from './components/main';
|
||||
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';
|
||||
|
||||
const splashLoading = (
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
);
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import { Footer } from './components/footer/footer';
|
||||
|
||||
function App() {
|
||||
const { VEGA_URL } = useEnvironment();
|
||||
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useNodeSwitcherStore(
|
||||
(store) => [store.dialogOpen, store.setDialogOpen]
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
setMenuOpen(false);
|
||||
}, [location]);
|
||||
|
||||
const cacheConfig: InMemoryCacheConfig = {
|
||||
typePolicies: {
|
||||
statistics: {
|
||||
keyFields: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const layoutClasses = classnames(
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)_auto] md:grid-cols-[300px_1fr]',
|
||||
'min-h-[100vh] mx-auto my-0',
|
||||
'border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r',
|
||||
'bg-white dark:bg-black',
|
||||
'antialiased text-black dark:text-white',
|
||||
{
|
||||
'h-[100vh] min-h-auto overflow-hidden': menuOpen,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<TendermintWebsocketProvider>
|
||||
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
>
|
||||
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||
</NodeGuard>
|
||||
<NodeSwitcherDialog
|
||||
open={nodeSwitcherOpen}
|
||||
setOpen={setNodeSwitcherOpen}
|
||||
/>
|
||||
<NetworkLoader cache={cacheConfig}>
|
||||
<div className={layoutClasses}>
|
||||
<Header menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
|
||||
<Nav menuOpen={menuOpen} />
|
||||
<Main />
|
||||
<Footer />
|
||||
</div>
|
||||
</NetworkLoader>
|
||||
</TendermintWebsocketProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
useInitializeEnv();
|
||||
return <App />;
|
||||
return (
|
||||
<EnvironmentProvider>
|
||||
<App />
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||
import { AssetLink } from '../links';
|
||||
import { useExplorerAssetQuery } from '../links/asset-link/__generated__/Asset';
|
||||
|
||||
export type AssetBalanceProps = {
|
||||
assetId: string;
|
||||
price: string;
|
||||
showAssetLink?: boolean;
|
||||
showAssetSymbol?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -17,20 +16,21 @@ const AssetBalance = ({
|
||||
assetId,
|
||||
price,
|
||||
showAssetLink = true,
|
||||
showAssetSymbol = false,
|
||||
}: AssetBalanceProps) => {
|
||||
const { data: asset, loading } = useAssetDataProvider(assetId);
|
||||
const { data } = useExplorerAssetQuery({
|
||||
variables: { id: assetId },
|
||||
});
|
||||
|
||||
const label =
|
||||
!loading && asset && asset.decimals
|
||||
? addDecimalsFormatNumber(price, asset.decimals)
|
||||
data && data.asset?.decimals
|
||||
? addDecimalsFormatNumber(price, data.asset.decimals)
|
||||
: price;
|
||||
|
||||
return (
|
||||
<div className="inline-block">
|
||||
<span>{label}</span>{' '}
|
||||
{showAssetLink && asset?.id ? (
|
||||
<AssetLink showAssetSymbol={showAssetSymbol} assetId={assetId} />
|
||||
{showAssetLink && data?.asset?.id ? (
|
||||
<AssetLink id={data.asset.id} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerGovernanceAssetQuery } from './__generated__/Governance-asset';
|
||||
import AssetBalance from './asset-balance';
|
||||
|
||||
@@ -13,17 +13,11 @@ const DEFAULT_DECIMALS = 18;
|
||||
* the governance asset first, which is set by a network parameter
|
||||
*/
|
||||
const GovernanceAssetBalance = ({ price }: GovernanceAssetBalanceProps) => {
|
||||
const { data, loading } = useExplorerGovernanceAssetQuery();
|
||||
const { data } = useExplorerGovernanceAssetQuery();
|
||||
|
||||
if (!loading && data && data.networkParameter?.value) {
|
||||
if (data && data.networkParameter?.value) {
|
||||
const governanceAssetId = data.networkParameter.value;
|
||||
return (
|
||||
<AssetBalance
|
||||
price={price}
|
||||
showAssetSymbol={true}
|
||||
assetId={governanceAssetId}
|
||||
/>
|
||||
);
|
||||
return <AssetBalance price={price} assetId={governanceAssetId} />;
|
||||
} else {
|
||||
return (
|
||||
<div className="inline-block">
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { assetsList } from '../../mocks/assets';
|
||||
import { AssetsTable } from './assets-table';
|
||||
|
||||
describe('AssetsTable', () => {
|
||||
it('shows loading message on first render', async () => {
|
||||
const res = render(
|
||||
<MemoryRouter>
|
||||
<AssetsTable data={null} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(await res.findByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows no data message if no assets found', async () => {
|
||||
const res = render(
|
||||
<MemoryRouter>
|
||||
<AssetsTable data={[]} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(
|
||||
await res.findByText('This chain has no assets')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a table/list with all the assets', async () => {
|
||||
const res = render(
|
||||
<MemoryRouter>
|
||||
<AssetsTable data={assetsList} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
await waitFor(() => {
|
||||
const rowA1 = res.container.querySelector('[row-id="123"]');
|
||||
expect(rowA1).toBeInTheDocument();
|
||||
|
||||
const rowA2 = res.container.querySelector('[row-id="456"]');
|
||||
expect(rowA2).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,105 +0,0 @@
|
||||
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 { 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 } from 'ag-grid-community';
|
||||
|
||||
type AssetsTableProps = {
|
||||
data: AssetFieldsFragment[] | null;
|
||||
};
|
||||
export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef<AgGridReact>(null);
|
||||
const showColumnsOnDesktop = () => {
|
||||
ref.current?.columnApi.setColumnsVisible(
|
||||
['id', 'type', 'status'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
window.addEventListener('resize', showColumnsOnDesktop);
|
||||
return () => {
|
||||
window.removeEventListener('resize', showColumnsOnDesktop);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: AssetFieldsFragment }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no assets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -2,11 +2,11 @@ import React from 'react';
|
||||
import type { BlockMeta } from '../../routes/blocks/tendermint-blockchain-response';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { TimeAgo } from '../time-ago';
|
||||
import { TableWithTbody, TableRow, TableCell } from '../table';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface BlockProps {
|
||||
block: BlockMeta;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import type { TendermintBlockchainResponse } from '../../routes/blocks/tendermint-blockchain-response';
|
||||
import { BlockData } from './block-data';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import InfiniteLoader from 'react-window-infinite-loader';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockMeta } from '../../routes/blocks/tendermint-blockchain-response';
|
||||
import { BlockData } from './block-data';
|
||||
import EmptyList from '../empty-list/empty-list';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTendermintWebsocket } from '../../hooks/use-tendermint-websocket';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface BlocksRefetchProps {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
Icon,
|
||||
SyntaxHighlighter,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
type JsonViewerDialogProps = {
|
||||
title: string;
|
||||
content: unknown;
|
||||
open: boolean;
|
||||
onChange: (isOpen: boolean) => void;
|
||||
trigger?: HTMLElement;
|
||||
};
|
||||
export const JsonViewerDialog = ({
|
||||
title,
|
||||
content,
|
||||
open,
|
||||
onChange,
|
||||
trigger,
|
||||
}: JsonViewerDialogProps) => {
|
||||
return (
|
||||
<Dialog
|
||||
size="medium"
|
||||
title={title}
|
||||
icon={<Icon name="info-sign"></Icon>}
|
||||
open={open}
|
||||
onChange={(isOpen) => onChange(isOpen)}
|
||||
onCloseAutoFocus={(e) => {
|
||||
/**
|
||||
* This mimics radix's default behaviour that focuses the dialog's
|
||||
* trigger after closing itself
|
||||
*/
|
||||
if (trigger) {
|
||||
e.preventDefault();
|
||||
trigger.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="pr-8 mb-8 max-h-[70vh] overflow-y-scroll">
|
||||
<SyntaxHighlighter size="smaller" data={content} />
|
||||
</div>
|
||||
<div className="w-1/4">
|
||||
<Button
|
||||
data-testid="close-asset-details-dialog"
|
||||
fill={true}
|
||||
size="sm"
|
||||
onClick={() => onChange(false)}
|
||||
>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -19,12 +19,12 @@ const EmptyList = ({ heading, label }: EmptyListProps) => {
|
||||
|
||||
<div className="mt-4">
|
||||
{heading ? (
|
||||
<h1 className="font-alpha calt text-xl uppercase text-center leading-relaxed">
|
||||
<h1 className="font-alpha text-xl uppercase text-center leading-relaxed">
|
||||
{heading}
|
||||
</h1>
|
||||
) : null}
|
||||
{label ? (
|
||||
<p className="font-alpha calt text-gray-500 text-center">{label}</p>
|
||||
<p className="font-alpha text-gray-500 text-center">{label}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
query ExplorerEpoch($id: ID!) {
|
||||
epoch(id: $id) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
firstBlock
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerFutureEpoch {
|
||||
networkParameter(key: "validators.epoch.length") {
|
||||
value
|
||||
}
|
||||
|
||||
epoch {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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 ExplorerEpochQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, firstBlock: string, lastBlock?: string | null } } };
|
||||
|
||||
export type ExplorerFutureEpochQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerFutureEpochQuery = { __typename?: 'Query', networkParameter?: { __typename?: 'NetworkParameter', value: string } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null } } };
|
||||
|
||||
|
||||
export const ExplorerEpochDocument = gql`
|
||||
query ExplorerEpoch($id: ID!) {
|
||||
epoch(id: $id) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
firstBlock
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerEpochQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerEpochQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerEpochQuery` 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 } = useExplorerEpochQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerEpochQuery(baseOptions: Apollo.QueryHookOptions<ExplorerEpochQuery, ExplorerEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerEpochQuery, ExplorerEpochQueryVariables>(ExplorerEpochDocument, options);
|
||||
}
|
||||
export function useExplorerEpochLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerEpochQuery, ExplorerEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerEpochQuery, ExplorerEpochQueryVariables>(ExplorerEpochDocument, options);
|
||||
}
|
||||
export type ExplorerEpochQueryHookResult = ReturnType<typeof useExplorerEpochQuery>;
|
||||
export type ExplorerEpochLazyQueryHookResult = ReturnType<typeof useExplorerEpochLazyQuery>;
|
||||
export type ExplorerEpochQueryResult = Apollo.QueryResult<ExplorerEpochQuery, ExplorerEpochQueryVariables>;
|
||||
export const ExplorerFutureEpochDocument = gql`
|
||||
query ExplorerFutureEpoch {
|
||||
networkParameter(key: "validators.epoch.length") {
|
||||
value
|
||||
}
|
||||
epoch {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerFutureEpochQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerFutureEpochQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerFutureEpochQuery` 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 } = useExplorerFutureEpochQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerFutureEpochQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>(ExplorerFutureEpochDocument, options);
|
||||
}
|
||||
export function useExplorerFutureEpochLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>(ExplorerFutureEpochDocument, options);
|
||||
}
|
||||
export type ExplorerFutureEpochQueryHookResult = ReturnType<typeof useExplorerFutureEpochQuery>;
|
||||
export type ExplorerFutureEpochLazyQueryHookResult = ReturnType<typeof useExplorerFutureEpochLazyQuery>;
|
||||
export type ExplorerFutureEpochQueryResult = Apollo.QueryResult<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>;
|
||||
@@ -1,56 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { IconForEpoch } from './epoch';
|
||||
|
||||
const THE_PAST = 'Monday, 17 February 2022 11:44:09';
|
||||
const THE_FUTURE = 'Monday, 17 February 3023 11:44:09';
|
||||
|
||||
describe('IconForEpoch', () => {
|
||||
it('Handles malformed dates', () => {
|
||||
const start = 'This is n0t a d4te';
|
||||
const end = '📅';
|
||||
const screen = render(<IconForEpoch start={start} end={end} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'calendar icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to a calendar icon', () => {
|
||||
const start = null as unknown as string;
|
||||
const end = null as unknown as string;
|
||||
const screen = render(<IconForEpoch start={start} end={end} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'calendar icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('if start and end are both in the future, stick with calendar', () => {
|
||||
const screen = render(<IconForEpoch start={THE_FUTURE} end={THE_FUTURE} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'calendar icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('if start is in the past and end is in the future, this is currently active', () => {
|
||||
const screen = render(<IconForEpoch start={THE_PAST} end={THE_FUTURE} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'circle icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('if start and end are in the paste, this is done', () => {
|
||||
const screen = render(<IconForEpoch start={THE_PAST} end={THE_PAST} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'tick-circle icon'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,125 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import EpochMissingOverview, { calculateEpochData } from './epoch-missing';
|
||||
import { getSecondsFromInterval } from '@vegaprotocol/utils';
|
||||
const START_DATE_PAST = 'Monday, 17 February 2022 11:44:09';
|
||||
|
||||
describe('getSecondsFromInterval', () => {
|
||||
it('returns 0 for bad data', () => {
|
||||
expect(getSecondsFromInterval(null as unknown as string)).toEqual(0);
|
||||
expect(getSecondsFromInterval('')).toEqual(0);
|
||||
expect(getSecondsFromInterval('🧙')).toEqual(0);
|
||||
expect(getSecondsFromInterval(2 as unknown as string)).toEqual(0);
|
||||
});
|
||||
|
||||
it('parses out months from a capital M', () => {
|
||||
expect(getSecondsFromInterval('2M')).toEqual(5184000);
|
||||
});
|
||||
|
||||
it('parses out days from a capital D', () => {
|
||||
expect(getSecondsFromInterval('1D')).toEqual(86400);
|
||||
});
|
||||
|
||||
it('parses out hours from a lower case h', () => {
|
||||
expect(getSecondsFromInterval('11h')).toEqual(39600);
|
||||
});
|
||||
|
||||
it('parses out minutes from a lower case m', () => {
|
||||
expect(getSecondsFromInterval('10m')).toEqual(600);
|
||||
});
|
||||
|
||||
it('parses out seconds from a lower case s', () => {
|
||||
expect(getSecondsFromInterval('99s')).toEqual(99);
|
||||
});
|
||||
|
||||
it('parses complex examples', () => {
|
||||
expect(getSecondsFromInterval('24h')).toEqual(86400);
|
||||
expect(getSecondsFromInterval('1h30m')).toEqual(5400);
|
||||
expect(getSecondsFromInterval('1D1h30m1s')).toEqual(91801);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateEpochData', () => {
|
||||
it('Handles bad data', () => {
|
||||
const currentEpochId = null as unknown as string;
|
||||
const missingEpochId = null as unknown as string;
|
||||
const epochStart = null as unknown as string;
|
||||
const epochLength = null as unknown as string;
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
expect(res).toHaveProperty('label', 'Missing data');
|
||||
expect(res).toHaveProperty('isInFuture', false);
|
||||
});
|
||||
|
||||
it('Calculates that a bigger epoch number is in the future from basic data', () => {
|
||||
const currentEpochId = '10';
|
||||
const missingEpochId = '20';
|
||||
const epochStart = '';
|
||||
const epochLength = '';
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
expect(res).toHaveProperty('isInFuture', true);
|
||||
});
|
||||
|
||||
it('If it has an epoch length and a start time, it provides an estimate', () => {
|
||||
const currentEpochId = '10';
|
||||
const missingEpochId = '20';
|
||||
const epochStart = START_DATE_PAST;
|
||||
const epochLength = '1s';
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
// 'Estimate: 17/02/2022, 11:44:19 - in less than a minute')
|
||||
expect(res).toHaveProperty('label');
|
||||
expect(res.label).toMatch(/^Estimate/);
|
||||
expect(res.label).toMatch(/in less than a minute$/);
|
||||
});
|
||||
|
||||
it('Provide decent string for past', () => {
|
||||
const currentEpochId = '20';
|
||||
const missingEpochId = '10';
|
||||
const epochStart = START_DATE_PAST;
|
||||
const epochLength = '1s';
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
// 'Estimate: 17/02/2022, 11:44:19 - in less than a minute')
|
||||
expect(res).toHaveProperty('label');
|
||||
expect(res.label).toMatch(/^Estimate/);
|
||||
expect(res.label).toMatch(/less than a minute ago$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EpochMissingOverview', () => {
|
||||
function renderComponent(missingEpochId: string) {
|
||||
return render(
|
||||
<MockedProvider>
|
||||
<EpochMissingOverview missingEpochId={missingEpochId} />
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
it('renders a - if no id is provided', () => {
|
||||
const n = null as unknown as string;
|
||||
const screen = renderComponent(n);
|
||||
expect(screen.getByTestId('empty')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
import { useExplorerFutureEpochQuery } from './__generated__/Epoch';
|
||||
|
||||
import addSeconds from 'date-fns/addSeconds';
|
||||
import formatDistance from 'date-fns/formatDistance';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import isFuture from 'date-fns/isFuture';
|
||||
import { getSecondsFromInterval, isValidDate } from '@vegaprotocol/utils';
|
||||
|
||||
export type EpochMissingOverviewProps = {
|
||||
missingEpochId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a set of details for an epoch that has no representation in the
|
||||
* data node. This is primarily for one of two reasons:
|
||||
*
|
||||
* 1. The epoch hasn't happened yet
|
||||
* 2. The epoch happened before a snapshot, and thus the details don't exist
|
||||
*
|
||||
* This component is used when the API has responded with no data for an epoch
|
||||
* by ID, so we already know that we can't display start time/block etc.
|
||||
*
|
||||
* We can detect 1 if the epoch is a higher number than the current epoch
|
||||
* We can detect 2 if the epoch is in the past, but we still get no response.
|
||||
*/
|
||||
const EpochMissingOverview = ({
|
||||
missingEpochId,
|
||||
}: EpochMissingOverviewProps) => {
|
||||
const { data, error, loading } = useExplorerFutureEpochQuery();
|
||||
|
||||
// This should not happen, but it's easily handled
|
||||
if (!missingEpochId) {
|
||||
return <span data-testid="empty">-</span>;
|
||||
}
|
||||
|
||||
// No data should also not happen - we've requested the current epoch. This
|
||||
// could happen at chain restart, but shouldn't. If it does, fallback.
|
||||
if (!data || loading || error) {
|
||||
return <span data-testid="empty">{missingEpochId}</span>;
|
||||
}
|
||||
|
||||
// If we have enough information to predict a future or past block time, let's do it
|
||||
if (
|
||||
!missingEpochId ||
|
||||
!data.epoch.id ||
|
||||
!data.epoch.timestamps.start ||
|
||||
!data?.networkParameter?.value
|
||||
) {
|
||||
return <span data-testid="empty">{missingEpochId}</span>;
|
||||
}
|
||||
|
||||
const { label, isInFuture } = calculateEpochData(
|
||||
data.epoch.id,
|
||||
missingEpochId,
|
||||
data.epoch.timestamps.start,
|
||||
data.networkParameter.value
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip description={<p className="text-xs m-2">{label}</p>}>
|
||||
<p>
|
||||
{isInFuture ? (
|
||||
<Icon name="calendar" className="mr-1" />
|
||||
) : (
|
||||
<Icon name="outdated" className="mr-1" />
|
||||
)}
|
||||
{missingEpochId}
|
||||
</p>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export function calculateEpochData(
|
||||
currentEpochId: string,
|
||||
missingEpochId: string,
|
||||
epochStart: string,
|
||||
epochLength: string
|
||||
) {
|
||||
// Blank string will be return 0 seconds from getSecondsFromInterval
|
||||
const epochLengthInSeconds = getSecondsFromInterval(epochLength);
|
||||
|
||||
if (!epochStart || !epochLength) {
|
||||
// Let's just take a guess
|
||||
return {
|
||||
label: 'Missing data',
|
||||
isInFuture: parseInt(missingEpochId) > parseInt(currentEpochId),
|
||||
};
|
||||
}
|
||||
|
||||
const startFrom = new Date(epochStart);
|
||||
|
||||
const diff = parseInt(missingEpochId) - parseInt(currentEpochId);
|
||||
const futureDate = addSeconds(startFrom, diff * epochLengthInSeconds);
|
||||
|
||||
const label =
|
||||
isValidDate(futureDate) && isValidDate(startFrom)
|
||||
? `Estimate: ${futureDate.toLocaleString()} - ${formatDistance(
|
||||
futureDate,
|
||||
startFrom,
|
||||
{ addSuffix: true }
|
||||
)}`
|
||||
: 'Missing data';
|
||||
|
||||
return {
|
||||
label,
|
||||
isInFuture: isFuture(futureDate),
|
||||
};
|
||||
}
|
||||
|
||||
export default EpochMissingOverview;
|
||||
@@ -1,127 +0,0 @@
|
||||
import { useExplorerEpochQuery } from './__generated__/Epoch';
|
||||
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { BlockLink } from '../links';
|
||||
import { Time } from '../time';
|
||||
import { TimeAgo } from '../time-ago';
|
||||
import EpochMissingOverview from './epoch-missing';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import isPast from 'date-fns/isPast';
|
||||
|
||||
const borderClass =
|
||||
'border-solid border-2 border-vega-dark-200 border-collapse';
|
||||
|
||||
export type EpochOverviewProps = {
|
||||
id?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays detailed information about an epoch, given an ID. This
|
||||
* works for past epochs and current epochs - future epochs, and a
|
||||
* few other situations (see epoch-missing) will not return us
|
||||
* enough information to render this.
|
||||
*
|
||||
* The details are hidden in a tooltip, behind the epoch number
|
||||
*/
|
||||
const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
const { data, error, loading } = useExplorerEpochQuery({
|
||||
variables: { id: id || '' },
|
||||
});
|
||||
|
||||
const ti = data?.epoch.timestamps;
|
||||
if (
|
||||
error?.message &&
|
||||
error.message.includes('no resource corresponding to this id')
|
||||
) {
|
||||
return <EpochMissingOverview missingEpochId={id} />;
|
||||
}
|
||||
|
||||
if (!ti || loading || error) {
|
||||
return <span>{id}</span>;
|
||||
}
|
||||
|
||||
const description = (
|
||||
<table className="text-xs m-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Block')}</th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('Start')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.firstBlock ? <BlockLink height={ti.firstBlock} /> : '-'}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
<Time date={ti.start} />
|
||||
<br />
|
||||
<TimeAgo date={ti.start} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('End')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.lastBlock ? (
|
||||
<BlockLink height={ti.lastBlock} />
|
||||
) : (
|
||||
t('In progress')
|
||||
)}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.end ? (
|
||||
<>
|
||||
<Time date={ti.end} />
|
||||
<br />
|
||||
<TimeAgo date={ti.end} />
|
||||
</>
|
||||
) : (
|
||||
<span>{t('-')}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip description={description}>
|
||||
<p>
|
||||
<IconForEpoch start={ti.start} end={ti.end} />
|
||||
{id}
|
||||
</p>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export type IconForEpochProps = {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Chooses an icon to display next to the epoch number, representing
|
||||
* when the epoch is relative to now (i.e. not yet started, started,
|
||||
* finished)
|
||||
*/
|
||||
export function IconForEpoch({ start, end }: IconForEpochProps) {
|
||||
const startHasPassed = start ? isPast(new Date(start)) : false;
|
||||
const endHasPassed = end ? isPast(new Date(end)) : false;
|
||||
|
||||
let i: IconProps['name'] = 'calendar';
|
||||
|
||||
if (!startHasPassed && !endHasPassed) {
|
||||
i = 'calendar';
|
||||
} else if (startHasPassed && !endHasPassed) {
|
||||
i = 'circle';
|
||||
} else if (startHasPassed && endHasPassed) {
|
||||
i = 'tick-circle';
|
||||
}
|
||||
|
||||
return <Icon name={i} className="mr-2" />;
|
||||
}
|
||||
|
||||
export default EpochOverview;
|
||||
@@ -1,30 +1,15 @@
|
||||
import {
|
||||
useEnvironment,
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMemo } from 'react';
|
||||
import { ENV } from '../../config/env';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Footer = () => {
|
||||
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL } = useEnvironment();
|
||||
const setNodeSwitcherOpen = useNodeSwitcherStore(
|
||||
(store) => store.setDialogOpen
|
||||
);
|
||||
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const showFullFeedbackLabel = useMemo(
|
||||
() => ['md', 'lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
|
||||
const { VEGA_URL, GIT_COMMIT_HASH, GIT_ORIGIN_URL, setNodeSwitcherOpen } =
|
||||
useEnvironment();
|
||||
return (
|
||||
<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">
|
||||
<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-neutral-700 dark:border-neutral-300">
|
||||
<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">
|
||||
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
{GIT_COMMIT_HASH && (
|
||||
<p data-testid="git-commit-hash">
|
||||
{t('Version')}:{' '}
|
||||
<Link
|
||||
@@ -38,23 +23,15 @@ export const Footer = () => {
|
||||
{GIT_COMMIT_HASH}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
<div className="flex pl-2 content-center">
|
||||
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
|
||||
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
|
||||
<Link className="ml-2" onClick={setNodeSwitcherOpen}>
|
||||
{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>
|
||||
</footer>
|
||||
);
|
||||
@@ -64,5 +41,9 @@ 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>
|
||||
),
|
||||
@@ -14,18 +13,19 @@ jest.mock('../search', () => ({
|
||||
}));
|
||||
|
||||
const renderComponent = () => (
|
||||
<MemoryRouter initialEntries={['/txs']}>
|
||||
<Header />
|
||||
<MemoryRouter>
|
||||
<Header menuOpen={false} setMenuOpen={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
describe('Header', () => {
|
||||
it('should render navigation', () => {
|
||||
it('should render heading', () => {
|
||||
render(renderComponent());
|
||||
|
||||
expect(screen.getByTestId('navigation')).toHaveTextContent('Explorer');
|
||||
expect(screen.getByTestId('explorer-header')).toHaveTextContent(
|
||||
'Vega Explorer'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render search', () => {
|
||||
render(renderComponent());
|
||||
|
||||
|
||||
@@ -1,111 +1,47 @@
|
||||
import { matchPath, useLocation, useMatch } from 'react-router-dom';
|
||||
import {
|
||||
ThemeSwitcher,
|
||||
Navigation,
|
||||
NavigationList,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
NavigationBreakpoint,
|
||||
NavigationTrigger,
|
||||
NavigationContent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import { isNavigable } from '../../routes/router-config';
|
||||
import { routerConfig } from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ThemeSwitcher, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Search } from '../search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
|
||||
const routeToNavigationItem = (r: Navigable) => (
|
||||
<NavigationItem key={r.handle.name}>
|
||||
<NavigationLink to={r.path}>{r.handle.text}</NavigationLink>
|
||||
</NavigationItem>
|
||||
);
|
||||
|
||||
export const Header = () => {
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const pages = routerConfig[0].children || [];
|
||||
const mainItems = compact(
|
||||
[Routes.TX, Routes.BLOCKS, Routes.ORACLES, Routes.VALIDATORS].map((n) =>
|
||||
pages.find((r) => r.path === n)
|
||||
)
|
||||
).filter(isNavigable);
|
||||
|
||||
const groupedItems = compact(
|
||||
[
|
||||
Routes.PARTIES,
|
||||
Routes.ASSETS,
|
||||
Routes.MARKETS,
|
||||
Routes.GOVERNANCE,
|
||||
Routes.NETWORK_PARAMETERS,
|
||||
Routes.GENESIS,
|
||||
].map((n) => pages.find((r) => r.path === n))
|
||||
).filter(isNavigable);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
/**
|
||||
* Because the grouped items are displayed in a sub menu under an "Other" item
|
||||
* we need to determine whether any underlying item is active to highlight the
|
||||
* trigger in the same fashion as any other top-level `NavigationLink`.
|
||||
* This function checks whether the current location pathname is one of the
|
||||
* underlying NavigationLinks.
|
||||
*/
|
||||
const isOnOther = useMemo(() => {
|
||||
for (const path of groupedItems.map((r) => r.path)) {
|
||||
const matched = matchPath(`${path}/*`, pathname);
|
||||
if (matched) return true;
|
||||
}
|
||||
return false;
|
||||
}, [groupedItems, pathname]);
|
||||
interface ThemeToggleProps {
|
||||
menuOpen: boolean;
|
||||
setMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const Header = ({ menuOpen, setMenuOpen }: ThemeToggleProps) => {
|
||||
const headerClasses = classnames(
|
||||
'md:col-span-2',
|
||||
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
|
||||
'p-4 gap-2 md:gap-4',
|
||||
'border-b border-neutral-700 dark:border-neutral-300 bg-black',
|
||||
'dark text-white'
|
||||
);
|
||||
return (
|
||||
<Navigation
|
||||
appName="Explorer"
|
||||
theme="system"
|
||||
breakpoints={[490, 900]}
|
||||
actions={
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
{!isHome && <Search />}
|
||||
</>
|
||||
}
|
||||
onResize={(width, el) => {
|
||||
if (width < 1157) {
|
||||
// switch to magnifying glass trigger when width < 1157
|
||||
el.classList.remove('nav-search-full');
|
||||
el.classList.add('nav-search-compact');
|
||||
} else {
|
||||
el.classList.remove('nav-search-compact');
|
||||
el.classList.add('nav-search-full');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<NavigationList hide={[NavigationBreakpoint.Small]}>
|
||||
<NavigationItem>
|
||||
<NetworkSwitcher />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Small, NavigationBreakpoint.Narrow]}
|
||||
<header className={headerClasses}>
|
||||
<div className="flex h-full items-center sm:items-stretch gap-4">
|
||||
<Link to={Routes.HOME}>
|
||||
<h1
|
||||
className="text-white text-3xl font-alpha uppercase calt mb-0"
|
||||
data-testid="explorer-header"
|
||||
>
|
||||
{t('Vega Explorer')}
|
||||
</h1>
|
||||
</Link>
|
||||
<NetworkSwitcher />
|
||||
</div>
|
||||
<button
|
||||
data-testid="open-menu"
|
||||
className="md:hidden text-white"
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
>
|
||||
{mainItems.map(routeToNavigationItem)}
|
||||
{groupedItems && groupedItems.length > 0 && (
|
||||
<NavigationItem>
|
||||
<NavigationTrigger isActive={Boolean(isOnOther)}>
|
||||
{t('Other')}
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList>
|
||||
{groupedItems.map(routeToNavigationItem)}
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
)}
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
<Icon name={menuOpen ? 'cross' : 'menu'} />
|
||||
</button>
|
||||
<Search />
|
||||
<ThemeSwitcher className="-my-4" />
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,14 +11,14 @@ export const InfoBlock = ({ title, subtitle, tooltipInfo }: InfoBlockProps) => {
|
||||
return (
|
||||
<div className="flex flex-col text-center ">
|
||||
<h3 className="text-4xl">{title}</h3>
|
||||
<p className="text-vega-dark-100 dark:text-vega-light-200">
|
||||
<p className="text-zinc-800 dark:text-zinc-300">
|
||||
{subtitle}
|
||||
{tooltipInfo ? (
|
||||
<Tooltip description={tooltipInfo} align="center">
|
||||
<span>
|
||||
<Icon
|
||||
name="info-sign"
|
||||
className="ml-2 text-vega-light-300 dark:text-vega-dark-300"
|
||||
className="ml-2 text-zinc-400 dark:text-zinc-600"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -44,12 +44,12 @@ export const InfoPanel = ({
|
||||
text={id}
|
||||
startChars={visibleChars}
|
||||
endChars={visibleChars}
|
||||
className="text-vega-dark-100 dark:text-vega-light-200"
|
||||
className="text-black dark:text-zinc-200"
|
||||
/>
|
||||
) : (
|
||||
<p
|
||||
title={id}
|
||||
className="text-vega-dark-100 dark:text-vega-light-200 truncate ..."
|
||||
className="text-black dark:text-zinc-200 truncate ..."
|
||||
>
|
||||
{id}
|
||||
</p>
|
||||
@@ -70,7 +70,7 @@ export const InfoPanel = ({
|
||||
</div>
|
||||
{copy && (
|
||||
<CopyWithTooltip text={id}>
|
||||
<button className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-sm py-2 px-3">
|
||||
<button className="bg-zinc-100 dark:bg-zinc-900 rounded-sm py-2 px-3">
|
||||
<Icon name="duplicate" />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { HTMLInputTypeAttribute, SyntheticEvent } from 'react';
|
||||
import { Input, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface JumpToProps {
|
||||
label: string;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
query ExplorerAsset($id: ID!) {
|
||||
asset(id: $id) {
|
||||
id
|
||||
name
|
||||
status
|
||||
decimals
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerAssetQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerAssetQuery = { __typename?: 'Query', asset?: { __typename?: 'Asset', id: string, name: string, status: Types.AssetStatus, decimals: number } | null };
|
||||
|
||||
|
||||
export const ExplorerAssetDocument = gql`
|
||||
query ExplorerAsset($id: ID!) {
|
||||
asset(id: $id) {
|
||||
id
|
||||
name
|
||||
status
|
||||
decimals
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerAssetQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerAssetQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerAssetQuery` 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 } = useExplorerAssetQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerAssetQuery(baseOptions: Apollo.QueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
|
||||
}
|
||||
export function useExplorerAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
|
||||
}
|
||||
export type ExplorerAssetQueryHookResult = ReturnType<typeof useExplorerAssetQuery>;
|
||||
export type ExplorerAssetLazyQueryHookResult = ReturnType<typeof useExplorerAssetLazyQuery>;
|
||||
export type ExplorerAssetQueryResult = Apollo.QueryResult<ExplorerAssetQuery, ExplorerAssetQueryVariables>;
|
||||
@@ -1,37 +1,63 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { AssetLink } from './asset-link';
|
||||
import { mockAssetA1 } from '../../../mocks/assets';
|
||||
import { render } from '@testing-library/react';
|
||||
import AssetLink from './asset-link';
|
||||
import { ExplorerAssetDocument } from './__generated__/Asset';
|
||||
|
||||
function renderComponent(id: string, mock: MockedResponse[]) {
|
||||
return (
|
||||
<MockedProvider mocks={mock} addTypename={false}>
|
||||
<MockedProvider mocks={mock}>
|
||||
<MemoryRouter>
|
||||
<AssetLink assetId={id} />
|
||||
<AssetLink id={id} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('AssetLink', () => {
|
||||
it('renders the asset id when not found and makes the button disabled', async () => {
|
||||
describe('Asset link component', () => {
|
||||
it('Renders the ID at first', () => {
|
||||
const res = render(renderComponent('123', []));
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
expect(await res.findByTestId('asset-link')).toBeDisabled();
|
||||
await waitFor(async () => {
|
||||
expect(await res.queryByText('A ONE')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the asset name when found and make the button enabled', async () => {
|
||||
const res = render(renderComponent('123', [mockAssetA1]));
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
it('Renders the asset name when the query returns a result', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerAssetDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
asset: {
|
||||
id: '123',
|
||||
name: 'test-label',
|
||||
status: 'irrelevant-test-data',
|
||||
decimals: 18,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await res.findByText('A ONE')).toBeInTheDocument();
|
||||
expect(await res.findByTestId('asset-link')).not.toBeDisabled();
|
||||
});
|
||||
const res = render(renderComponent('123', [mock]));
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
expect(await res.findByText('test-label')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Leaves the asset id when the asset is not found', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerAssetDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
error: new Error('No such asset'),
|
||||
};
|
||||
|
||||
const res = render(renderComponent('123', [mock]));
|
||||
expect(await res.findByText('123')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,54 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { useExplorerAssetQuery } from './__generated__/Asset';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
useAssetDataProvider,
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
|
||||
export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
|
||||
assetId: string;
|
||||
asDialog?: boolean;
|
||||
showAssetSymbol?: boolean;
|
||||
export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an asset ID, it will fetch the asset name and show that,
|
||||
* with a link to the assets modal. If the name does not come back
|
||||
* with a link to the assets list. If the name does not come back
|
||||
* it will use the ID instead.
|
||||
*/
|
||||
export const AssetLink = ({
|
||||
assetId,
|
||||
asDialog,
|
||||
showAssetSymbol = false,
|
||||
...props
|
||||
}: AssetLinkProps) => {
|
||||
const { data: asset } = useAssetDataProvider(assetId);
|
||||
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
|
||||
const { data } = useExplorerAssetQuery({
|
||||
variables: { id },
|
||||
});
|
||||
|
||||
const open = useAssetDetailsDialogStore((state) => state.open);
|
||||
const navigate = useNavigate();
|
||||
const label = asset
|
||||
? showAssetSymbol
|
||||
? asset?.symbol
|
||||
: asset?.name
|
||||
: assetId;
|
||||
let label: string = id;
|
||||
|
||||
if (data?.asset?.name) {
|
||||
label = data.asset.name;
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonLink
|
||||
data-testid="asset-link"
|
||||
disabled={!asset}
|
||||
onClick={(e) => {
|
||||
if (asDialog) {
|
||||
open(assetId, e.target as HTMLElement);
|
||||
} else {
|
||||
navigate(`/${Routes.ASSETS}/${asset?.id}`);
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
|
||||
<Hash text={label} />
|
||||
</ButtonLink>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssetLink;
|
||||
|
||||
@@ -2,4 +2,4 @@ export { default as BlockLink } from './block-link/block-link';
|
||||
export { default as PartyLink } from './party-link/party-link';
|
||||
export { default as NodeLink } from './node-link/node-link';
|
||||
export { default as MarketLink } from './market-link/market-link';
|
||||
export * from './asset-link/asset-link';
|
||||
export { default as AssetLink } from './asset-link/asset-link';
|
||||
|
||||
@@ -2,7 +2,6 @@ query ExplorerMarket($id: ID!) {
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
+1
-2
@@ -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 } } } } } | null };
|
||||
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: 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`
|
||||
@@ -16,7 +16,6 @@ export const ExplorerMarketDocument = gql`
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
@@ -55,7 +55,6 @@ describe('Market link component', () => {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useExplorerMarketQuery } from './__generated__/Market';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import Hash from '../hash';
|
||||
|
||||
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
@@ -48,7 +48,7 @@ const MarketLink = ({
|
||||
<Link
|
||||
className="underline"
|
||||
{...props}
|
||||
to={`/${Routes.MARKETS}/${id}`}
|
||||
to={`/${Routes.MARKETS}#${id}`}
|
||||
title={id}
|
||||
>
|
||||
{label}
|
||||
@@ -56,7 +56,7 @@ const MarketLink = ({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.MARKETS}/${id}`}>
|
||||
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
|
||||
<Hash text={id} />
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import PartyLink from './party-link';
|
||||
|
||||
describe('PartyLink', () => {
|
||||
it('renders Network for 000.000 party', () => {
|
||||
const zeroes =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const screen = render(<PartyLink id={zeroes} />);
|
||||
expect(screen.getByText('Network')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Network for network party', () => {
|
||||
const screen = render(<PartyLink id="network" />);
|
||||
expect(screen.getByText('Network')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders ID with no link for invalid party', () => {
|
||||
const screen = render(<PartyLink id="this-party-is-not-valid" />);
|
||||
expect(screen.getByTestId('invalid-party')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('links a valid party to the party page', () => {
|
||||
const aValidParty =
|
||||
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e';
|
||||
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<PartyLink id={aValidParty} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const el = screen.getByText(aValidParty);
|
||||
expect(el).toBeInTheDocument();
|
||||
// The text should be a link that points to the party's page
|
||||
expect(el.parentElement?.tagName).toEqual('A');
|
||||
expect(el.parentElement?.getAttribute('href')).toContain(aValidParty);
|
||||
});
|
||||
});
|
||||
@@ -3,47 +3,19 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
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 { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const SPECIAL_CASE_NETWORK_ID =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
export const SPECIAL_CASE_NETWORK = 'network';
|
||||
|
||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
truncate?: boolean;
|
||||
};
|
||||
|
||||
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
// 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) {
|
||||
return (
|
||||
<span className="font-mono" data-testid="network">
|
||||
{t('Network')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// If the party doesn't look correct, there's no point in linking to id. Just render
|
||||
// the ID as it was given to us
|
||||
if (!isValidPartyId(id)) {
|
||||
return (
|
||||
<span className="font-mono" data-testid="invalid-party">
|
||||
{id}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const PartyLink = ({ id, ...props }: PartyLinkProps) => {
|
||||
return (
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
{...props}
|
||||
to={`/${Routes.PARTIES}/${id}`}
|
||||
>
|
||||
<Hash text={truncate ? truncateMiddle(id) : id} />
|
||||
<Hash text={id} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,14 +4,13 @@ import { ENV } from '../../../config/env';
|
||||
import Hash from '../hash';
|
||||
export type ProposalLinkProps = {
|
||||
id: string;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a proposal ID, generates an external link over to
|
||||
* the Governance page for more information
|
||||
*/
|
||||
const ProposalLink = ({ id, text }: ProposalLinkProps) => {
|
||||
const ProposalLink = ({ id }: ProposalLinkProps) => {
|
||||
const { data } = useExplorerProposalQuery({
|
||||
variables: { id },
|
||||
});
|
||||
@@ -21,7 +20,7 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
|
||||
|
||||
return (
|
||||
<ExternalLink href={`${base}/proposals/${id}`}>
|
||||
{text ? text : <Hash text={label} />}
|
||||
<Hash text={label} />
|
||||
</ExternalLink>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { AppRouter } from '../../routes';
|
||||
|
||||
export const Main = () => {
|
||||
return (
|
||||
<main className="p-4 overflow-scroll">
|
||||
<AppRouter />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@@ -1,171 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { MarketInfoWithData } 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 { DataSourceDefinition } from '@vegaprotocol/types';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
if (!market) return null;
|
||||
|
||||
const settlementData = market.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
|
||||
const terminationData = market.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
|
||||
|
||||
const getSigners = (data: DataSourceDefinition) => {
|
||||
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
|
||||
const signers = data.sourceType.sourceType.signers || [];
|
||||
|
||||
return signers.map(({ signer }, i) => {
|
||||
return (
|
||||
(signer.__typename === 'ETHAddress' && signer.address) ||
|
||||
(signer.__typename === 'PubKey' && signer.key)
|
||||
);
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
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 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: (
|
||||
<>
|
||||
<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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
import type { MarketFieldsFragment } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { useRef, useLayoutEffect } from 'react';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
import { MarketStateMapping } from '@vegaprotocol/types';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
type MarketsTableProps = {
|
||||
data: MarketFieldsFragment[] | null;
|
||||
};
|
||||
export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
const openAssetDetailsDialog = useAssetDetailsDialogStore(
|
||||
(state) => state.open
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
const showColumnsOnDesktop = () => {
|
||||
gridRef.current?.columnApi.setColumnsVisible(
|
||||
['id', 'state', 'asset'],
|
||||
window.innerWidth > BREAKPOINT_MD
|
||||
);
|
||||
};
|
||||
window.addEventListener('resize', showColumnsOnDesktop);
|
||||
return () => {
|
||||
window.removeEventListener('resize', showColumnsOnDesktop);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no markets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface NavProps {
|
||||
menuOpen: boolean;
|
||||
}
|
||||
|
||||
export const Nav = ({ menuOpen }: NavProps) => {
|
||||
return (
|
||||
<nav className="relative">
|
||||
<div
|
||||
className={classnames(
|
||||
'absolute top-0 z-50 md:static',
|
||||
'w-full p-4 md:border-r border-neutral-700 dark:border-neutral-300',
|
||||
'bg-white dark:bg-black',
|
||||
'transition-[right]',
|
||||
{
|
||||
'right-0 h-[100vh]': menuOpen,
|
||||
'right-[200vw] h-full': !menuOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{routerConfig.map((r) => (
|
||||
<NavLink
|
||||
key={r.name}
|
||||
to={r.path}
|
||||
className={({ isActive }) =>
|
||||
classnames(
|
||||
'block mb-2 px-2',
|
||||
'text-lg hover:bg-vega-pink dark:hover:bg-vega-yellow hover:text-white dark:hover:text-black',
|
||||
{
|
||||
'bg-vega-pink text-white dark:bg-vega-yellow dark:text-black':
|
||||
isActive,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
{r.text}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -83,7 +83,7 @@ const NestedDataListItem = ({
|
||||
});
|
||||
|
||||
const titleClasses = classNames({
|
||||
'text-xl pl-4 border-l-4 font-alpha calt': hasChildren,
|
||||
'text-xl pl-4 border-l-4 font-alpha': hasChildren,
|
||||
'text-base font-medium whitespace-nowrap': !hasChildren,
|
||||
});
|
||||
|
||||
@@ -117,7 +117,7 @@ const NestedDataListItem = ({
|
||||
)}
|
||||
</h4>
|
||||
{!hasChildren && (
|
||||
<code className="text-vega-light-100 mb-2 last:mb-0 dark:text-vega-dark-100 break-all">
|
||||
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
|
||||
{JSON.stringify(value, null, ' ')}
|
||||
</code>
|
||||
)}
|
||||
|
||||
@@ -19,7 +19,6 @@ fragment ExplorerDeterministicOrderFields on Order {
|
||||
market {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
|
||||
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
|
||||
|
||||
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
|
||||
orderId: Types.Scalars['ID'];
|
||||
@@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
|
||||
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
|
||||
|
||||
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
fragment ExplorerDeterministicOrderFields on Order {
|
||||
@@ -35,7 +35,6 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
market {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
@@ -61,7 +61,6 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
__typename: 'Market',
|
||||
id: '789',
|
||||
state: 'STATUS_ACTIVE',
|
||||
positionDecimalPlaces: 2,
|
||||
decimalPlaces: '5',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
@@ -89,7 +88,6 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
market: {
|
||||
id: '789',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerDeterministicOrderQuery } from './__generated__/Order';
|
||||
import { MarketLink } from '../links';
|
||||
import PriceInMarket from '../price-in-market/price-in-market';
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerDeterministicOrderQuery } from './__generated__/Order';
|
||||
import { MarketLink } from '../links';
|
||||
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';
|
||||
|
||||
export interface DeterministicOrderDetailsProps {
|
||||
id: string;
|
||||
// Version to fetch, with 0 being 'latest' and 1 being 'first'. Defaults to 0
|
||||
version?: number | null;
|
||||
version?: number;
|
||||
}
|
||||
|
||||
export const wrapperClasses =
|
||||
'grid lg:grid-cols-1 flex items-center max-w-xl border border-vega-light-200 dark:border-vega-dark-150 rounded-md pv-2 ph-5 mb-5';
|
||||
'grid lg:grid-cols-1 flex items-center max-w-xl border border-zinc-200 dark:border-zinc-800 rounded-md pv-2 ph-5 mb-5';
|
||||
|
||||
/**
|
||||
* This component renders the *current* details for an order
|
||||
@@ -28,7 +27,7 @@ export const wrapperClasses =
|
||||
*/
|
||||
const DeterministicOrderDetails = ({
|
||||
id,
|
||||
version = null,
|
||||
version = 0,
|
||||
}: DeterministicOrderDetailsProps) => {
|
||||
const { data, error } = useExplorerDeterministicOrderQuery({
|
||||
variables: { orderId: id, version },
|
||||
@@ -42,7 +41,7 @@ const DeterministicOrderDetails = ({
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
{t('Order not found')}
|
||||
</h2>
|
||||
<p className="text-vega-light-400 mb-12">
|
||||
<p className="text-gray-500 mb-12">
|
||||
{t('No order created from this transaction')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -91,7 +90,7 @@ const DeterministicOrderDetails = ({
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<SizeInMarket size={o.size} marketId={o.market.id} />
|
||||
{o.size}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
@@ -101,7 +100,7 @@ const DeterministicOrderDetails = ({
|
||||
{t('Remaining')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<SizeInMarket size={o.remaining} marketId={o.market.id} />
|
||||
{o.remaining}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user