Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3b240ef48 | ||
|
|
24f21dbd2e | ||
|
|
0473821c17 | ||
|
|
24347eaf18 | ||
|
|
5145c2a558 | ||
|
|
db19ee80ac | ||
|
|
45623f33c5 | ||
|
|
08e204fe31 | ||
|
|
81f7b0595e | ||
|
|
4ed0e3a056 | ||
|
|
47a84b4dac | ||
|
|
c540d4b17f | ||
|
|
f1fa3250a4 | ||
|
|
707c00def2 | ||
|
|
78aff54360 | ||
|
|
6160fd7638 | ||
|
|
c345623a06 | ||
|
|
e9173796f5 | ||
|
|
2a0811863f | ||
|
|
4f610bbd1b | ||
|
|
104b2d145d | ||
|
|
f75787562c | ||
|
|
678c9090c7 |
@@ -21,7 +21,6 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Cache node modules
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
@@ -39,7 +38,6 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
|
||||
- name: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --pure-lockfile
|
||||
@@ -51,11 +49,11 @@ jobs:
|
||||
uses: ./.github/workflows/lint-pr.yml
|
||||
secrets: inherit
|
||||
|
||||
lint-test-build:
|
||||
timeout-minutes: 60
|
||||
lint-format:
|
||||
timeout-minutes: 20
|
||||
needs: node-modules
|
||||
runs-on: ubuntu-22.04
|
||||
name: '(CI) lint + unit test + build'
|
||||
name: '(CI) lint + format check'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -81,6 +79,76 @@ jobs:
|
||||
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
|
||||
|
||||
test-affected:
|
||||
timeout-minutes: 30
|
||||
needs: build-sources
|
||||
runs-on: ubuntu-22.04
|
||||
name: 'run unit test of affected apps'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v3
|
||||
with:
|
||||
main-branch-name: develop
|
||||
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
|
||||
build-sources:
|
||||
timeout-minutes: 30
|
||||
needs: lint-format
|
||||
runs-on: ubuntu-22.04
|
||||
name: 'Build sources of affected apps'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v3
|
||||
with:
|
||||
main-branch-name: develop
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
@@ -97,21 +165,8 @@ jobs:
|
||||
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
|
||||
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
|
||||
|
||||
- name: Check formatting
|
||||
run: yarn nx format:check
|
||||
|
||||
- name: Lint affected
|
||||
run: yarn nx affected:lint --max-warnings=0
|
||||
|
||||
- name: Build affected spec
|
||||
run: yarn nx affected --target=build-spec
|
||||
|
||||
- name: Test affected
|
||||
run: yarn nx affected:test
|
||||
|
||||
- name: Build affected
|
||||
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
projects-e2e: ${{ env.PROJECTS_E2E }}
|
||||
@@ -121,38 +176,38 @@ jobs:
|
||||
preview_tools: ${{ env.PREVIEW_TOOLS }}
|
||||
|
||||
console-e2e:
|
||||
needs: lint-test-build
|
||||
needs: build-sources
|
||||
name: '(CI) console python'
|
||||
uses: ./.github/workflows/console-test-run.yml
|
||||
secrets: inherit
|
||||
if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
|
||||
if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
cypress:
|
||||
needs: lint-test-build
|
||||
needs: build-sources
|
||||
name: '(CI) cypress'
|
||||
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
|
||||
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
|
||||
projects: ${{ needs.build-sources.outputs.projects-e2e }}
|
||||
tags: '@smoke'
|
||||
|
||||
publish-dist:
|
||||
needs: lint-test-build
|
||||
needs: build-sources
|
||||
name: '(CD) publish dist'
|
||||
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }}
|
||||
uses: ./.github/workflows/publish-dist.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
projects: ${{ needs.lint-test-build.outputs.projects }}
|
||||
projects: ${{ needs.build-sources.outputs.projects }}
|
||||
|
||||
dist-check:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- publish-dist
|
||||
- lint-test-build
|
||||
- build-sources
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
|
||||
timeout-minutes: 60
|
||||
name: '(CD) comment preview links'
|
||||
@@ -168,27 +223,27 @@ jobs:
|
||||
run: |
|
||||
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
|
||||
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_governance }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_governance }}"; do
|
||||
echo "waiting for governance preview: ${{ needs.lint-test-build.outputs.preview_governance }}"
|
||||
if [[ "${{ needs.build-sources.outputs.preview_governance }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_governance }}"; do
|
||||
echo "waiting for governance preview: ${{ needs.build-sources.outputs.preview_governance }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_explorer }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_explorer }}"; do
|
||||
echo "waiting for explorer preview: ${{ needs.lint-test-build.outputs.preview_explorer }}"
|
||||
if [[ "${{ needs.build-sources.outputs.preview_explorer }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_explorer }}"; do
|
||||
echo "waiting for explorer preview: ${{ needs.build-sources.outputs.preview_explorer }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_trading }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_trading }}"; do
|
||||
echo "waiting for trading preview: ${{ needs.lint-test-build.outputs.preview_trading }}"
|
||||
if [[ "${{ needs.build-sources.outputs.preview_trading }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_trading }}"; do
|
||||
echo "waiting for trading preview: ${{ needs.build-sources.outputs.preview_trading }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
|
||||
echo "waiting for tools preview: ${{ needs.lint-test-build.outputs.preview_tools }}"
|
||||
if [[ "${{ needs.build-sources.outputs.preview_tools }}" =~ $regex ]]; then
|
||||
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_tools }}"; do
|
||||
echo "waiting for tools preview: ${{ needs.build-sources.outputs.preview_tools }}"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
@@ -200,10 +255,10 @@ jobs:
|
||||
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 }}
|
||||
* tools: ${{ needs.lint-test-build.outputs.preview_tools }}
|
||||
* governance: ${{ needs.build-sources.outputs.preview_governance }}
|
||||
* explorer: ${{ needs.build-sources.outputs.preview_explorer }}
|
||||
* trading: ${{ needs.build-sources.outputs.preview_trading }}
|
||||
* tools: ${{ needs.build-sources.outputs.preview_tools }}
|
||||
|
||||
# Report single result at the end, to avoid mess with required checks in PR
|
||||
cypress-check:
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
jobs:
|
||||
run-tests:
|
||||
name: run-tests
|
||||
runs-on: 8-cores
|
||||
runs-on: console-test
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
#----------------------------------------------
|
||||
@@ -33,15 +33,6 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cache-node-modules-
|
||||
#----------------------------------------------
|
||||
# setup node
|
||||
#----------------------------------------------
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
#----------------------------------------------
|
||||
# install deps if cache missing
|
||||
#----------------------------------------------
|
||||
- name: yarn install
|
||||
@@ -76,32 +67,6 @@ jobs:
|
||||
repository: vegaprotocol/console-test
|
||||
path: './console-test'
|
||||
#----------------------------------------------
|
||||
# set-up python
|
||||
#----------------------------------------------
|
||||
- name: Set up python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10.11'
|
||||
#----------------------------------------------
|
||||
# ----- install & configure poetry -----
|
||||
#----------------------------------------------
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
virtualenvs-path: console-test/.venv
|
||||
#----------------------------------------------
|
||||
# load cached venv if cache exists
|
||||
#----------------------------------------------
|
||||
- name: Load cached venv
|
||||
id: cached-poetry-dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: console-test/.venv
|
||||
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
||||
#----------------------------------------------
|
||||
# install dependencies if cache does not exist
|
||||
#----------------------------------------------
|
||||
- name: Install dependencies
|
||||
@@ -125,7 +90,7 @@ jobs:
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
working-directory: ./console-test
|
||||
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
|
||||
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
|
||||
- name: Check files
|
||||
run: |
|
||||
ls -al .
|
||||
|
||||
@@ -37,6 +37,7 @@ jobs:
|
||||
|
||||
# Restore node_modules from cache if possible
|
||||
- name: Restore node_modules from cache
|
||||
id: cache-node-modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@@ -46,6 +47,7 @@ jobs:
|
||||
|
||||
# Install frontend dependencies
|
||||
- name: Install root dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: frontend-monorepo
|
||||
|
||||
@@ -67,7 +69,7 @@ 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 }} --browser chrome --env.grepTags="${{ inputs.tags }}"
|
||||
working-directory: frontend-monorepo
|
||||
env:
|
||||
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
|
||||
|
||||
@@ -11,7 +11,7 @@ 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
|
||||
docker run --rm ${TAG} ipfs add -rQ /usr/share/nginx/html
|
||||
|
||||
.PHONY: eject-ipfs-hash
|
||||
unpack:
|
||||
|
||||
@@ -115,11 +115,31 @@ 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
|
||||
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of Console either for the web, or ready for pinning on IPFS.
|
||||
|
||||
### nx build inside the docker
|
||||
|
||||
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical.
|
||||
|
||||
```bash
|
||||
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
|
||||
```
|
||||
|
||||
### Computing ipfs-hash of the build
|
||||
|
||||
At the moment this feature is important only for Console releases.
|
||||
|
||||
Each docker build finishes with hash calculation for ` dist`` directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
|
||||
|
||||
```bash
|
||||
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
|
||||
```
|
||||
|
||||
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
|
||||
|
||||
### nx build outside the docker
|
||||
|
||||
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.
|
||||
This Docker image packages a pre-built `dist` folder into an [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image. In this case, the application on docker host machine from source.
|
||||
|
||||
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
|
||||
|
||||
@@ -133,38 +153,18 @@ You can build any of the containers locally with the following command:
|
||||
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)
|
||||
1. Show latest release by running: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
|
||||
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
|
||||
3. Set TAG environment variable to image that you want to validate: `export TAG=vegaprotocol/trading:$RELEASE`
|
||||
4. Download docker image with the desired release `docker pull $TAG`.
|
||||
5. Recalculate hash: `make recalculate-ipfs`
|
||||
6. You should see exactly same hash produced by ipfs command as one placed with the release notes: `make show-latest-release`
|
||||
7. If you want to extract dist from docker image to your local filesystem you can run following command: `make unpack`
|
||||
8. Now `dist` directory contains valid application build. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
|
||||
8. Now `dist` directory contains valid application build
|
||||
|
||||
## Config
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
describe('Verify elements on page', function () {
|
||||
beforeEach(() => {
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
|
||||
const networkParametersHeader = '[data-testid="network-param-header"]';
|
||||
const tableRows = '[data-testid="key-value-table-row"]';
|
||||
|
||||
@@ -16,6 +13,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
.and('be.visible');
|
||||
});
|
||||
|
||||
// 0006-NETW-021
|
||||
it('should list each of the network parameters available', function () {
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
const numberOfNetworkParametersInSystem =
|
||||
@@ -198,6 +196,19 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
// 0006-NETW-022 0006-NETW-023
|
||||
it('governance assets should be correctly grouped', function () {
|
||||
cy.getByTestId('governance')
|
||||
.should('exist')
|
||||
.parent()
|
||||
.should('have.attr', 'href', '/network-parameters#governance');
|
||||
cy.get('[id="governance-proposal-asset"]')
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.getByTestId('key-value-table-row').should('have.length', 8);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see network parameters - on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './network-parameters';
|
||||
export * from './structure-network-params';
|
||||
|
||||
@@ -1,82 +1,105 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import { NetworkParametersTable } from './network-parameters';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const renderComponent = (data: NetworkParamsQuery | undefined) => {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<NetworkParametersTable data={data} loading={false} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const mockData = {
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'spam.protection.delegation.min.tokens',
|
||||
value: '3',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
key: 'spam.protection.voting.min.tokens',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
key: 'reward.staking.delegation.minimumValidatorStake',
|
||||
value: '2',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
key: 'reward.asset',
|
||||
value:
|
||||
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
describe('NetworkParametersTable', () => {
|
||||
it('renders correctly when it has network params', () => {
|
||||
const data: NetworkParamsQuery = {
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.fee.factors.infrastructureFee',
|
||||
value: '0.0005',
|
||||
},
|
||||
},
|
||||
],
|
||||
it('renders headers correctly', () => {
|
||||
renderComponent(mockData);
|
||||
|
||||
const allHeadings = screen.getAllByRole('heading');
|
||||
expect(
|
||||
allHeadings.map((h) => {
|
||||
return {
|
||||
text: h.textContent,
|
||||
level: h.tagName,
|
||||
testId: h.getAttribute('data-testid'),
|
||||
};
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
text: 'Network Parameters',
|
||||
level: 'H1',
|
||||
testId: 'network-param-header',
|
||||
},
|
||||
};
|
||||
render(<NetworkParametersTable data={data} loading={false} />);
|
||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
||||
'Network Parameters'
|
||||
);
|
||||
const rows = screen.getAllByTestId('key-value-table-row');
|
||||
expect(rows[0].children[0]).toHaveTextContent(
|
||||
'market.fee.factors.infrastructureFee'
|
||||
);
|
||||
expect(rows[1].children[0]).toHaveTextContent(
|
||||
'market.liquidityProvision.minLpStakeQuantumMultiple'
|
||||
);
|
||||
expect(rows[0].children[1]).toHaveTextContent('0.0005');
|
||||
expect(rows[1].children[1]).toHaveTextContent('1');
|
||||
{ text: 'Spam', level: 'H1', testId: 'spam' },
|
||||
{ text: 'Protection', level: 'H2', testId: 'spam-protection' },
|
||||
{ text: 'Delegation', level: 'H3', testId: 'spam-protection-delegation' },
|
||||
{ text: 'Min', level: 'H4', testId: 'spam-protection-delegation-min' },
|
||||
{ text: 'Voting', level: 'H3', testId: 'spam-protection-voting' },
|
||||
{ text: 'Min', level: 'H4', testId: 'spam-protection-voting-min' },
|
||||
{ text: 'Reward', level: 'H1', testId: 'reward' },
|
||||
{ text: 'Staking', level: 'H2', testId: 'reward-staking' },
|
||||
{ text: 'Delegation', level: 'H3', testId: 'reward-staking-delegation' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders the rows in ascending order', () => {
|
||||
const data: NetworkParamsQuery = {
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.fee.factors.infrastructureFee',
|
||||
value: '0.0005',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
render(<NetworkParametersTable data={data} loading={false} />);
|
||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
||||
'Network Parameters'
|
||||
it('renders network params correctly', () => {
|
||||
renderComponent(mockData);
|
||||
|
||||
const delegationMinTokensRow = screen.getByTestId(
|
||||
'spam-protection-delegation-min-tokens'
|
||||
);
|
||||
const rows = screen.getAllByTestId('key-value-table-row');
|
||||
expect(rows[0].children[0]).toHaveTextContent(
|
||||
'market.fee.factors.infrastructureFee'
|
||||
expect(delegationMinTokensRow).toHaveTextContent('0.000000000000000003');
|
||||
|
||||
const votingMinTokensRow = screen.getByTestId(
|
||||
'spam-protection-voting-min-tokens'
|
||||
);
|
||||
expect(rows[1].children[0]).toHaveTextContent(
|
||||
'market.liquidityProvision.minLpStakeQuantumMultiple'
|
||||
expect(votingMinTokensRow).toHaveTextContent('0.000000000000000001');
|
||||
|
||||
const minimumValidatorStakeRow = screen.getByTestId(
|
||||
'reward-staking-delegation-minimumValidatorStake'
|
||||
);
|
||||
expect(minimumValidatorStakeRow).toHaveTextContent('2');
|
||||
|
||||
const assetRow = screen.getByTestId('reward-asset');
|
||||
expect(assetRow).toHaveTextContent(
|
||||
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55'
|
||||
);
|
||||
expect(rows[0].children[1]).toHaveTextContent('0.0005');
|
||||
expect(rows[1].children[1]).toHaveTextContent('1');
|
||||
});
|
||||
|
||||
it('does not render rows when is loading', () => {
|
||||
render(<NetworkParametersTable data={undefined} loading={true} />);
|
||||
renderComponent(undefined);
|
||||
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
|
||||
'Network Parameters'
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import startCase from 'lodash/startCase';
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
SyntaxHighlighter,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
@@ -12,11 +14,12 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { useNetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { structureNetworkParams } from './structure-network-params';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import type { GroupedParams } from './structure-network-params';
|
||||
|
||||
const PERCENTAGE_PARAMS = [
|
||||
'governance.proposal.asset.requiredMajority',
|
||||
@@ -58,6 +61,54 @@ const BIG_NUMBER_PARAMS = [
|
||||
'governance.proposal.updateAsset.minVoterBalance',
|
||||
];
|
||||
|
||||
export const renderGroupedParams = (
|
||||
group: GroupedParams,
|
||||
level: number,
|
||||
parentKeys: string[] = []
|
||||
) => {
|
||||
const Header = `h${level}` as keyof JSX.IntrinsicElements;
|
||||
const headerStyles = classNames('uppercase font-semibold', {
|
||||
'pt-6 text-3xl underline': level === 1,
|
||||
'pt-3 text-2xl': level === 2,
|
||||
'pt-2 text-lg': level === 3,
|
||||
'pt-2 text-default': level === 4,
|
||||
});
|
||||
|
||||
return Object.entries(group).map(([key, value]) => {
|
||||
const fullPath = [...parentKeys, key].join('.');
|
||||
const isLeafNode = typeof value !== 'object';
|
||||
const id = parentKeys.concat([key]).join('-');
|
||||
|
||||
return (
|
||||
<div key={key}>
|
||||
{!isLeafNode && (
|
||||
<div id={id}>
|
||||
<Link to={`#${id}`}>
|
||||
<Header className={headerStyles} data-testid={id}>
|
||||
{startCase(key)}
|
||||
</Header>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{isLeafNode ? (
|
||||
typeof value === 'string' ? (
|
||||
<div data-testid={id}>
|
||||
<NetworkParameterRow
|
||||
key={fullPath}
|
||||
row={{ key: fullPath, value: value }}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
) : (
|
||||
<div className="pb-1">
|
||||
{renderGroupedParams(value, level + 1, [...parentKeys, key])}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const NetworkParameterRow = ({
|
||||
row: { key, value },
|
||||
}: {
|
||||
@@ -77,7 +128,9 @@ export const NetworkParameterRow = ({
|
||||
>
|
||||
{key}
|
||||
{isSyntaxRow ? (
|
||||
<SyntaxHighlighter data={JSON.parse(value)} />
|
||||
<div className="pb-2">
|
||||
<SyntaxHighlighter data={JSON.parse(value)} />
|
||||
</div>
|
||||
) : isNaN(Number(value)) ? (
|
||||
value
|
||||
) : BIG_NUMBER_PARAMS.includes(key) ? (
|
||||
@@ -113,17 +166,12 @@ export const NetworkParametersTable = ({
|
||||
loading={loading}
|
||||
error={error}
|
||||
render={(data) => {
|
||||
const ascParams = orderBy(
|
||||
removePaginationWrapper(data.networkParametersConnection.edges),
|
||||
(param) => param.key,
|
||||
'asc'
|
||||
const flatParams = removePaginationWrapper(
|
||||
data.networkParametersConnection.edges
|
||||
);
|
||||
const groupedParams = structureNetworkParams(flatParams);
|
||||
return (
|
||||
<KeyValueTable data-testid="parameters">
|
||||
{(ascParams || []).map((row) => (
|
||||
<NetworkParameterRow key={row.key} row={row} />
|
||||
))}
|
||||
</KeyValueTable>
|
||||
<div className="-mt-6">{renderGroupedParams(groupedParams, 1)}</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import type { GroupedParams } from './structure-network-params';
|
||||
import {
|
||||
structureParams,
|
||||
sortGroupedParams,
|
||||
structureNetworkParams,
|
||||
} from './structure-network-params';
|
||||
|
||||
describe('structureParams', () => {
|
||||
it('should correctly structure params', () => {
|
||||
const input = [
|
||||
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
|
||||
{ key: 'spam.protection.voting.min.tokens', value: '5' },
|
||||
];
|
||||
const output: GroupedParams = {
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
voting: {
|
||||
min: {
|
||||
tokens: '5',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(structureParams(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should handle top-level keys correctly', () => {
|
||||
const input = [{ key: 'levelOne', value: '10' }];
|
||||
const output = {
|
||||
levelOne: '10',
|
||||
};
|
||||
expect(structureParams(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortGroupedParams', () => {
|
||||
it('should correctly sort grouped params', () => {
|
||||
const input: GroupedParams = {
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
reward: '50',
|
||||
};
|
||||
const output: GroupedParams = {
|
||||
reward: '50',
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(sortGroupedParams(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should handle already sorted keys', () => {
|
||||
const input = {
|
||||
a: '10',
|
||||
b: {
|
||||
c: '5',
|
||||
d: '6',
|
||||
},
|
||||
};
|
||||
expect(sortGroupedParams(input)).toEqual(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('structureNetworkParams', () => {
|
||||
it('should structure and sort network params correctly', () => {
|
||||
const input = [
|
||||
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
|
||||
{ key: 'reward.asset', value: '50' },
|
||||
];
|
||||
const output: GroupedParams = {
|
||||
reward: {
|
||||
asset: '50',
|
||||
},
|
||||
spam: {
|
||||
protection: {
|
||||
delegation: {
|
||||
min: {
|
||||
tokens: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(structureNetworkParams(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should return an empty object if no params are provided', () => {
|
||||
expect(structureNetworkParams([])).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Categorizes and sorts an array of key-value pairs of network params into a nested object structure.
|
||||
*
|
||||
* The function takes an array of network params where keys are dot-delimited
|
||||
* strings representing nested categories (e.g., 'spam.protection.delegation.min.tokens').
|
||||
*
|
||||
* Why this is necessary:
|
||||
* A flat key-value structure wouldn't provide the hierarchical information needed.
|
||||
* Organizing network parameters like this allows the rendering of nested headers
|
||||
* and their corresponding network params.
|
||||
*
|
||||
* It also ensures that items with the minimum amount of nesting are ordered first. This
|
||||
* allows us to render these items first, before more deeply nested items are rendered with
|
||||
* subheaders. This creates a more intuitive UI.
|
||||
*
|
||||
* For example, given the input:
|
||||
* [
|
||||
* { key: 'spam.protection.delegation.min.tokens', value: '10' },
|
||||
* { key: 'spam.protection.voting.min.tokens', value: '5' },
|
||||
* { key: 'reward.staking.delegation.minimumValidatorStake', value: '2' }
|
||||
* { key: 'reward.asset', value: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55' }
|
||||
* ]
|
||||
*
|
||||
* The output will be:
|
||||
* {
|
||||
* spam: {
|
||||
* protection: {
|
||||
* delegation: {
|
||||
* min: {
|
||||
* tokens: '10'
|
||||
* }
|
||||
* },
|
||||
* voting: {
|
||||
* min: {
|
||||
* tokens: '5'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* },
|
||||
* reward: {
|
||||
* asset: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
|
||||
* staking: {
|
||||
* delegation: {
|
||||
* minimumValidatorStake: '2'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param {Array} params - An array of key-value pairs to categorize and sort.
|
||||
* @returns {GroupedParams} - A nested object that groups the key-value pairs.
|
||||
*/
|
||||
|
||||
export type GroupedParams = {
|
||||
[key: string]: string | GroupedParams;
|
||||
};
|
||||
|
||||
export const structureParams = (
|
||||
params: { key: string; value: string }[]
|
||||
): GroupedParams => {
|
||||
const grouped: GroupedParams = {};
|
||||
|
||||
params.forEach(({ key, value }) => {
|
||||
const parts = key.split('.');
|
||||
let node: GroupedParams = grouped;
|
||||
|
||||
parts.forEach((part, i) => {
|
||||
if (typeof node[part] === 'undefined') {
|
||||
node[part] = i === parts.length - 1 ? value : {};
|
||||
}
|
||||
|
||||
if (typeof node[part] === 'object') {
|
||||
node = node[part] as GroupedParams;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return grouped;
|
||||
};
|
||||
|
||||
export const sortGroupedParams = (
|
||||
groupedParams: GroupedParams
|
||||
): GroupedParams => {
|
||||
const sorted: GroupedParams = {};
|
||||
|
||||
// Sort top-level keys first
|
||||
Object.entries(groupedParams).forEach(([key, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
sorted[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
Object.entries(groupedParams).forEach(([key, value]) => {
|
||||
if (typeof value === 'object') {
|
||||
sorted[key] = sortGroupedParams(value);
|
||||
}
|
||||
});
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const structureNetworkParams = (
|
||||
params: { key: string; value: string }[]
|
||||
) => {
|
||||
const grouped = structureParams(params);
|
||||
return sortGroupedParams(grouped);
|
||||
};
|
||||
@@ -34,19 +34,20 @@ import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
||||
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
|
||||
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const proposalVoteProgressForPercentage =
|
||||
'vote-progress-indicator-percentage-for';
|
||||
const proposalVoteProgressAgainstPercentage =
|
||||
'vote-progress-indicator-percentage-against';
|
||||
const proposalVoteProgressForTokens = 'vote-progress-indicator-tokens-for';
|
||||
const proposalVoteProgressAgainstTokens =
|
||||
'vote-progress-indicator-tokens-against';
|
||||
const participationNotMet = 'token-participation-not-met';
|
||||
const voteStatus = 'vote-status';
|
||||
const voteMajorityNotMet = 'token-majority-not-met';
|
||||
const numberOfVotesFor = 'num-votes-for';
|
||||
const votesForPercentage = 'votes-for-percentage';
|
||||
const numberOfVotesAgainst = 'num-votes-against';
|
||||
const votesAgainstPercentage = 'votes-against-percentage';
|
||||
const totalVotedNumber = 'total-voted';
|
||||
const totalVotedPercentage = 'total-voted-percentage';
|
||||
const changeVoteButton = 'change-vote-button';
|
||||
const proposalDetailsTitle = 'proposal-title';
|
||||
const proposalDetailsDescription = 'proposal-description';
|
||||
const openProposals = 'open-proposals';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const voteBreakdownToggle = 'vote-breakdown-toggle';
|
||||
const proposalTermsToggle = 'proposal-json-toggle';
|
||||
const marketDataToggle = 'proposal-market-data-toggle';
|
||||
|
||||
@@ -150,29 +151,32 @@ describe(
|
||||
// 3001-VOTE-037
|
||||
// 3001-VOTE-040
|
||||
// 3001-VOTE-067
|
||||
// 3001-VOTE-023
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
});
|
||||
cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
|
||||
'be.visible'
|
||||
cy.getByTestId(participationNotMet).should(
|
||||
'have.text',
|
||||
'0.000000000000000000000015% participation threshold not met'
|
||||
);
|
||||
cy.getByTestId(voteMajorityNotMet).should(
|
||||
'have.text',
|
||||
'66% majority threshold not met'
|
||||
);
|
||||
cy.getByTestId(voteStatus).should(
|
||||
'have.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Expected to pass')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-062
|
||||
// 3001-VOTE-040
|
||||
// 3001-VOTE-070
|
||||
getProposalInformationFromTable('Token majority met')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-068
|
||||
getProposalInformationFromTable('Token participation met')
|
||||
.contains('👎')
|
||||
.should('be.visible');
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||
cy.getByTestId(numberOfVotesAgainst).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||
});
|
||||
|
||||
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
|
||||
@@ -197,43 +201,16 @@ describe(
|
||||
.contains(votedDate)
|
||||
.should('be.visible');
|
||||
});
|
||||
cy.getByTestId(proposalVoteProgressForPercentage) // 3001-VOTE-072
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
||||
.contains('0.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressForTokens)
|
||||
.contains('1.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstTokens)
|
||||
.contains('0.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Tokens against proposal')
|
||||
.should('have.text', '0.00')
|
||||
.and('be.visible');
|
||||
// 3001-VOTE-061
|
||||
getProposalInformationFromTable('Participation required')
|
||||
.contains('0.00%')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-066
|
||||
getProposalInformationFromTable('Majority Required') // 3001-VOTE-073
|
||||
.contains(`${(66).toFixed(2)}%`)
|
||||
.should('be.visible');
|
||||
getProposalInformationFromTable('Number of voting parties')
|
||||
.should('have.text', '1')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(votesForPercentage) // 3001-VOTE-072
|
||||
.should('have.text', '100%');
|
||||
cy.getByTestId(votesAgainstPercentage).should('have.text', '0%');
|
||||
cy.getByTestId('token-majority-progress')
|
||||
.should('have.attr', 'style')
|
||||
.and('eq', 'width: 100%;'); // 3001-VOTE-024
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('for');
|
||||
// 3001-VOTE-064
|
||||
cy.getByTestId('user-voted-yes').should('exist');
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
@@ -244,16 +221,7 @@ describe(
|
||||
});
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('against');
|
||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Tokens against proposal')
|
||||
.should('have.text', (1).toFixed(2))
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Number of voting parties')
|
||||
.should('have.text', '1')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(votesAgainstPercentage).should('have.text', '100%');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
@@ -265,6 +233,7 @@ describe(
|
||||
|
||||
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
|
||||
it('Newly created proposal details - ability to increase associated tokens - by voting again after association', function () {
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
@@ -275,69 +244,28 @@ describe(
|
||||
voteForProposal('for');
|
||||
// 3001-VOTE-079
|
||||
cy.contains('You voted: For').should('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressForTokens)
|
||||
.contains('1')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Total Supply')
|
||||
.invoke('text')
|
||||
.then((totalSupply) => {
|
||||
const tokensRequiredToAchieveResult = (
|
||||
(Number(totalSupply.replace(/,/g, '')) * 0.001) /
|
||||
100
|
||||
).toFixed(2);
|
||||
ethereumWalletConnect();
|
||||
ensureSpecifiedUnstakedTokensAreAssociated(
|
||||
tokensRequiredToAchieveResult
|
||||
);
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
});
|
||||
cy.getByTestId(proposalVoteProgressForPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstPercentage)
|
||||
.contains('0.00%')
|
||||
.and('be.visible');
|
||||
// 3001-VOTE-065
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('for');
|
||||
cy.getByTestId(proposalVoteProgressForTokens)
|
||||
.contains(tokensRequiredToAchieveResult)
|
||||
.and('be.visible');
|
||||
cy.getByTestId(proposalVoteProgressAgainstTokens)
|
||||
.contains('0.00')
|
||||
.and('be.visible');
|
||||
cy.getByTestId(voteBreakdownToggle).click();
|
||||
getProposalInformationFromTable('Total tokens voted percentage')
|
||||
.should('have.text', '0.00%')
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.should('have.text', tokensRequiredToAchieveResult)
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Tokens against proposal')
|
||||
.should('have.text', '0.00')
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Number of voting parties')
|
||||
.should('have.text', '1')
|
||||
.and('be.visible');
|
||||
getProposalInformationFromTable('Expected to pass')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
// 3001-VOTE-062
|
||||
getProposalInformationFromTable('Token majority met')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
getProposalInformationFromTable('Token participation met')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
getProposalInformationFromTable('Tokens for proposal')
|
||||
.contains(tokensRequiredToAchieveResult)
|
||||
.and('be.visible');
|
||||
});
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
|
||||
ethereumWalletConnect();
|
||||
stakingPageAssociateTokens('1000000', { approve: true });
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
});
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
|
||||
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
|
||||
// 3001-VOTE-065
|
||||
cy.getByTestId(changeVoteButton).should('be.visible').click();
|
||||
voteForProposal('for');
|
||||
cy.getByTestId(numberOfVotesFor).should('have.text', '1.0M');
|
||||
cy.getByTestId(totalVotedNumber).should('have.text', '1.0M');
|
||||
cy.getByTestId(totalVotedPercentage).should('have.text', '(1.54%)');
|
||||
});
|
||||
|
||||
it('Able to vote for proposal twice by switching public key', function () {
|
||||
@@ -360,10 +288,6 @@ describe(
|
||||
voteForProposal('against');
|
||||
cy.contains('You voted: Against').should('be.visible');
|
||||
switchVegaWalletPubKey();
|
||||
cy.getByTestId(proposalVoteProgressForTokens).should(
|
||||
'contain.text',
|
||||
'1.00'
|
||||
);
|
||||
// Checking vote status for different public keys is displayed correctly
|
||||
cy.contains('You voted: For').should('be.visible');
|
||||
});
|
||||
|
||||
@@ -22,12 +22,10 @@ const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const closedProposals = 'closed-proposals';
|
||||
const proposalStatus = 'proposal-status';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const votesTable = 'votes-table';
|
||||
const votesTable = 'user-vote';
|
||||
const openProposals = 'open-proposals';
|
||||
const proposalVoteProgressForPercentage =
|
||||
'vote-progress-indicator-percentage-for';
|
||||
const majorityVoteReached = 'majority-reached';
|
||||
const minParticipationReached = 'participation-reached';
|
||||
const majorityVoteReached = 'token-majority-met';
|
||||
const minParticipationReached = 'token-participation-met';
|
||||
const proposalTimeout = { timeout: 8000 };
|
||||
|
||||
context(
|
||||
@@ -70,7 +68,6 @@ context(
|
||||
cy.getByTestId('proposal-type').should('have.text', 'New market');
|
||||
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
|
||||
cy.getByTestId(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -92,7 +89,7 @@ context(
|
||||
// 3001-VOTE-019 time to vote is highlighted red
|
||||
cy.getByTestId('vote-details')
|
||||
.find('span')
|
||||
.should('have.class', 'text-vega-pink');
|
||||
.should('have.class', 'text-vega-orange');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
});
|
||||
@@ -109,12 +106,9 @@ context(
|
||||
);
|
||||
});
|
||||
cy.getByTestId(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
});
|
||||
cy.getByTestId(proposalVoteProgressForPercentage)
|
||||
.contains('100.00%')
|
||||
.and('be.visible');
|
||||
cy.getByTestId('votes-for-percentage').should('have.text', '100%');
|
||||
navigateTo(navigation.proposals);
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil(proposalListItem)
|
||||
|
||||
@@ -298,9 +298,6 @@ context(
|
||||
getProposalFromTitle(proposalTitle).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
cy.contains('Vote breakdown').should('be.visible', {
|
||||
timeout: 10000,
|
||||
});
|
||||
cy.getByTestId(voteButtons).should('not.exist');
|
||||
cy.getByTestId('min-proposal-requirements').should(
|
||||
'have.text',
|
||||
|
||||
@@ -52,8 +52,6 @@ const enactmentDeadlineError = 'enactment-before-voting-deadline';
|
||||
const proposalDownloadBtn = 'proposal-download-json';
|
||||
const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const proposalJsonToggle = 'proposal-json-toggle';
|
||||
const proposalJsonSection = 'proposal-json';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
@@ -421,27 +419,28 @@ context(
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
cy.getByTestId('lp-majority-not-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold not met'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
|
||||
cy.getByTestId('token-majority-not-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold not met'
|
||||
);
|
||||
voteForProposal('for');
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
cy.getByTestId('lp-majority-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold met'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
cy.getByTestId('token-majority-met').should(
|
||||
'have.text',
|
||||
'66% majority threshold met'
|
||||
);
|
||||
cy.getByTestId('vote-status').should(
|
||||
'have.text',
|
||||
'Currently expected to pass by token vote'
|
||||
);
|
||||
cy.getByTestId('vote-breakdown-toggle').click();
|
||||
getProposalInformationFromTable('Expected to pass')
|
||||
.contains('👍 by token vote')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
enterRawProposalBody,
|
||||
generateFreeFormProposalTitle,
|
||||
getProposalFromTitle,
|
||||
getProposalInformationFromTable,
|
||||
goToMakeNewProposal,
|
||||
governanceProposalType,
|
||||
submitUniqueRawProposal,
|
||||
@@ -30,7 +29,9 @@ const proposalType = 'proposal-type';
|
||||
const proposalStatus = 'proposal-status';
|
||||
const proposalClosingDate = 'vote-details';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const voteBreakDownToggle = 'vote-breakdown-toggle';
|
||||
const voteMajorityNotMet = 'token-majority-not-met';
|
||||
const voteMajorityMet = 'token-majority-met';
|
||||
const votesForPercentage = 'votes-for-percentage';
|
||||
|
||||
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
@@ -121,10 +122,15 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
submitUniqueRawProposal({ proposalTitle: proposalTitle });
|
||||
getProposalFromTitle(proposalTitle).within(() => {
|
||||
// 3001-VOTE-039
|
||||
cy.getByTestId('participation-not-reached').should(
|
||||
cy.getByTestId(voteMajorityNotMet).should(
|
||||
'have.text',
|
||||
'Min. participation not reached'
|
||||
'66% majority threshold not met'
|
||||
);
|
||||
cy.getByTestId('token-participation-not-met').should(
|
||||
'have.text',
|
||||
'0.000000000000000000000015% participation threshold not met'
|
||||
);
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '0%');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
voteForProposal('for');
|
||||
@@ -134,16 +140,15 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
'have.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId('user-voted-yes').should('exist');
|
||||
cy.getByTestId('participation-reached').should(
|
||||
cy.getByTestId(voteMajorityMet).should(
|
||||
'have.text',
|
||||
'Min. participation reached'
|
||||
'66% majority threshold met'
|
||||
);
|
||||
cy.getByTestId(votesForPercentage).should('have.text', '100%');
|
||||
cy.getByTestId('token-participation-met').should(
|
||||
'have.text',
|
||||
'0.000000000000000000000015% participation threshold met'
|
||||
);
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId(voteBreakDownToggle).click();
|
||||
getProposalInformationFromTable('Token participation met')
|
||||
.contains('👍')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -183,7 +183,7 @@ context(
|
||||
.first()
|
||||
.find('[data-testid="view-proposal-btn"]')
|
||||
.click();
|
||||
cy.url().should('contain', '/protocol-upgrades/v1');
|
||||
cy.url().should('contain', '/protocol-upgrades/v1/2015942');
|
||||
cy.getByTestId('protocol-upgrade-proposal').within(() => {
|
||||
cy.get('h1').should('have.text', 'Vega Release v1');
|
||||
cy.getByTestId('protocol-upgrade-block-height').should(
|
||||
@@ -243,7 +243,7 @@ context(
|
||||
);
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/proposals/protocol-upgrade/v1');
|
||||
.and('contain', '/proposals/protocol-upgrade/v1/2015942');
|
||||
});
|
||||
|
||||
// estimate does not display possibly due to mocks or Cypress unless the proposal is clicked on several times
|
||||
|
||||
@@ -146,7 +146,7 @@ export function getProposalInformationFromTable(heading: string) {
|
||||
}
|
||||
|
||||
export function voteForProposal(vote: string) {
|
||||
cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
|
||||
cy.get(voteButtons).should('be.visible', { timeout: 10000 });
|
||||
cy.get(voteButtons).contains(vote).click();
|
||||
cy.get(dialogTitle, proposalTimeout).should(
|
||||
'have.text',
|
||||
|
||||
@@ -219,7 +219,9 @@ export function ensureSpecifiedUnstakedTokensAreAssociated(
|
||||
.eq(1)
|
||||
.invoke('text')
|
||||
.then((unstakedBalance) => {
|
||||
if (parseFloat(unstakedBalance) != parseFloat(tokenAmount)) {
|
||||
const tokenFloat = parseFloat(tokenAmount);
|
||||
const unstakedFloat = parseFloat(unstakedBalance.replace(/,/g, ''));
|
||||
if (tokenFloat != unstakedFloat) {
|
||||
vegaWalletTeardown();
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
|
||||
'0.00',
|
||||
|
||||
@@ -184,7 +184,7 @@ const Web3Container = ({
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<footer className="p-4 border-t border-neutral-700">
|
||||
<footer className="p-4 border-t border-neutral-700 break-all">
|
||||
<NetworkInfo />
|
||||
</footer>
|
||||
</AppLayout>
|
||||
@@ -308,7 +308,7 @@ const AppContainer = () => {
|
||||
<Router>
|
||||
<ScrollToTop />
|
||||
<AppStateProvider>
|
||||
<div className="min-h-full text-white grid">
|
||||
<div className="min-h-full text-white">
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={
|
||||
|
||||
@@ -201,6 +201,8 @@
|
||||
"STATE_WAITING_FOR_NODE_VOTE": "Waiting for node vote",
|
||||
"UpdateNetworkParameter": "Network parameter",
|
||||
"NewFreeform": "Freeform",
|
||||
"setToPass": "Set to pass",
|
||||
"setToFail": "Set to fail",
|
||||
"tokenVotes": "Token votes",
|
||||
"liquidityVotes": "Liquidity votes",
|
||||
"castYourVote": "Cast your vote",
|
||||
@@ -209,13 +211,23 @@
|
||||
"against": "Against",
|
||||
"majorityRequired": "Majority Required",
|
||||
"participation": "Participation",
|
||||
"met": "Met",
|
||||
"notMet": "Not Met",
|
||||
"majorityThreshold": "majority threshold",
|
||||
"participationThreshold": "participation threshold",
|
||||
"met": "met",
|
||||
"notMet": "not met",
|
||||
"governanceRequired": "Required",
|
||||
"daysLeft": "{{daysLeft}} left to vote.",
|
||||
"toVote": "to vote",
|
||||
"voteFor": "Vote for",
|
||||
"voteAgainst": "Vote against",
|
||||
"tokenVote": "Token vote",
|
||||
"tokenVotesFor": "Token votes for",
|
||||
"tokenVotesAgainst": "Token votes against",
|
||||
"totalTokensVoted": "Total tokens voted",
|
||||
"liquidityProviderVote": "Liquidity provider vote",
|
||||
"liquidityProviderVotesFor": "LP votes for",
|
||||
"liquidityProviderVotesAgainst": "LP votes against",
|
||||
"totalLiquidityProviderTokensVoted": "Total LP tokens voted",
|
||||
"votingThresholdInfo": "If the token vote passes the participation threshold it will be the deciding vote. If not, the outcome will be determined by liquidity providers on this market.",
|
||||
"noGovernanceTokens": "You need some VEGA tokens to participate in governance",
|
||||
"youVoted": "You voted",
|
||||
@@ -462,7 +474,7 @@
|
||||
"rewardsColLiquidityProvisionHeader": "LIQUIDITY PROVISION",
|
||||
"rewardsColLiquidityProvisionTooltip": "Liquidity provision rewards are distributed based on how much you have earned in liquidity fees, funded by a liquidity reward pool for that market",
|
||||
"rewardsColMarketCreationHeader": "MARKET CREATION",
|
||||
"rewardsColMarketCreationTooltip": "Market creation rewards are paid out to the creator of any market that exceeds a set threshold of cumulative volume in a given epoch, currently [rewards.marketCreationQuantumMultiple]",
|
||||
"rewardsColMarketCreationTooltip": "Market creation rewards are paid out to the creator of any market that exceeds a set threshold of cumulative volume in a given epoch, currently {{marketCreationQuantumMultiple}}",
|
||||
"rewardsColTotalHeader": "TOTAL",
|
||||
"ofTotalDistributed": "of total distributed",
|
||||
"checkBackSoon": "Check back soon",
|
||||
|
||||
@@ -31,10 +31,6 @@ import {
|
||||
orderByDate,
|
||||
orderByUpgradeBlockHeight,
|
||||
} from '../proposals/components/proposals-list/proposals-list';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
|
||||
const nodesToShow = 6;
|
||||
@@ -47,57 +43,34 @@ const HomeProposals = ({
|
||||
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
params: networkParams,
|
||||
loading: networkParamsLoading,
|
||||
error: networkParamsError,
|
||||
} = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
]);
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={networkParamsLoading}
|
||||
error={networkParamsError}
|
||||
data={networkParams}
|
||||
>
|
||||
<section className="mb-16" data-testid="home-proposals">
|
||||
<Heading title={t('vegaGovernance')} />
|
||||
<h3 className="mb-6">{t('homeProposalsIntro')}</h3>
|
||||
<div className="mb-8">
|
||||
<ExternalLink href={ExternalLinks.GOVERNANCE_PAGE}>
|
||||
{t(`readMoreGovernance`)}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<section className="mb-16" data-testid="home-proposals">
|
||||
<Heading title={t('vegaGovernance')} />
|
||||
<h3 className="mb-6">{t('homeProposalsIntro')}</h3>
|
||||
<div className="mb-8">
|
||||
<ExternalLink href={ExternalLinks.GOVERNANCE_PAGE}>
|
||||
{t(`readMoreGovernance`)}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
|
||||
<SubHeading title={t('latestProposals')} />
|
||||
<ul data-testid="home-proposal-list" className="grid gap-6">
|
||||
{protocolUpgradeProposals.map((proposal, index) => (
|
||||
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
|
||||
))}
|
||||
<SubHeading title={t('latestProposals')} />
|
||||
<ul data-testid="home-proposal-list" className="grid gap-6">
|
||||
{protocolUpgradeProposals.map((proposal, index) => (
|
||||
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
|
||||
))}
|
||||
|
||||
{proposals.map((proposal) => (
|
||||
<ProposalsListItem
|
||||
key={proposal.id}
|
||||
proposal={proposal}
|
||||
networkParams={networkParams}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{proposals.map((proposal) => (
|
||||
<ProposalsListItem key={proposal.id} proposal={proposal} />
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-6">
|
||||
<Link to={`${Routes.PROPOSALS}`}>
|
||||
<Button size="md">{t('homeProposalsButtonText')}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</AsyncRenderer>
|
||||
<div className="mt-6">
|
||||
<Link to={`${Routes.PROPOSALS}`}>
|
||||
<Button size="md">{t('homeProposalsButtonText')}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -306,8 +279,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
trimmedActiveNodes={trimmedActiveNodes}
|
||||
/>
|
||||
|
||||
<section className="grid grid-cols-2 gap-12 mb-16">
|
||||
<div data-testid="home-rewards">
|
||||
<section className="flex justify-between flex-wrap gap-12 mb-16">
|
||||
<div className="min-w-[360px] flex-1" data-testid="home-rewards">
|
||||
<Heading title={t('Rewards')} marginTop={false} />
|
||||
<h3 className="mb-6">{t('homeRewardsIntro')}</h3>
|
||||
<div className="flex items-center mb-8 gap-4">
|
||||
@@ -317,7 +290,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-testid="home-vega-token">
|
||||
<div className="min-w-[360px] flex-1" data-testid="home-vega-token">
|
||||
<Heading title={t('vegaToken')} marginTop={false} />
|
||||
<h3 className="mb-6">{t('homeVegaTokenIntro')}</h3>
|
||||
<div className="flex items-center mb-8 gap-4">
|
||||
|
||||
-2
@@ -16,7 +16,6 @@ import { ProposalHeader } from './proposal-header';
|
||||
import {
|
||||
lastWeek,
|
||||
nextWeek,
|
||||
mockNetworkParams,
|
||||
mockWalletContext,
|
||||
createUserVoteQueryMock,
|
||||
} from '../../test-helpers/mocks';
|
||||
@@ -48,7 +47,6 @@ const renderComponent = (
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={isListItem}
|
||||
networkParams={mockNetworkParams}
|
||||
voteState={voteState}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
|
||||
+4
-7
@@ -8,22 +8,19 @@ import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
||||
import { CurrentProposalState } from '../current-proposal-state';
|
||||
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||
import { ProposalVotingStatus } from '../proposal-voting-status';
|
||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import Routes from '../../../routes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { VoteState } from '../vote-details/use-user-vote';
|
||||
import { VoteBreakdown } from '../vote-breakdown';
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
networkParams,
|
||||
isListItem = true,
|
||||
voteState,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
networkParams: Partial<NetworkParamsResult>;
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
@@ -146,7 +143,7 @@ export const ProposalHeader = ({
|
||||
className="flex items-center gap-2"
|
||||
data-testid={`user-voted-${voteState.toLowerCase()}`}
|
||||
>
|
||||
<div className="text-vega-green" data-testid="you-voted-icon">
|
||||
<div data-testid="you-voted-icon">
|
||||
<VegaIcon name={VegaIconNames.VOTE} size={24} />
|
||||
</div>
|
||||
<div>
|
||||
@@ -162,7 +159,7 @@ export const ProposalHeader = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-testid="proposal-title">
|
||||
<div data-testid="proposal-title" className="break-all">
|
||||
{isListItem ? (
|
||||
<header>
|
||||
<SubHeading
|
||||
@@ -185,7 +182,7 @@ export const ProposalHeader = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProposalVotingStatus proposal={proposal} networkParams={networkParams} />
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { ProposalVotesTable } from './proposal-votes-table';
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { ProposalVotesTable } from './proposal-votes-table';
|
||||
import { ProposalType } from '../proposal/proposal';
|
||||
import {
|
||||
generateNoVotes,
|
||||
generateProposal,
|
||||
generateYesVotes,
|
||||
} from '../../test-helpers/generate-proposals';
|
||||
|
||||
const defaultProposal = generateProposal();
|
||||
const defaultProposalType = ProposalType.PROPOSAL_NETWORK_PARAMETER;
|
||||
const updateMarketProposal = generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
marketId: '12345',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(10),
|
||||
no: generateNoVotes(0),
|
||||
},
|
||||
});
|
||||
const updateMarketProposalType = ProposalType.PROPOSAL_UPDATE_MARKET;
|
||||
|
||||
const renderComponent = (
|
||||
proposal = defaultProposal,
|
||||
proposalType = defaultProposalType
|
||||
) =>
|
||||
render(
|
||||
<MockedProvider>
|
||||
<AppStateProvider>
|
||||
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
||||
</AppStateProvider>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
describe('Proposal Votes Table', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = renderComponent();
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show vote breakdown fields, excluding custom update market fields', () => {
|
||||
renderComponent();
|
||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||
expect(screen.getByText('Expected to pass')).toBeInTheDocument();
|
||||
expect(screen.getByText('Token majority met')).toBeInTheDocument();
|
||||
expect(screen.getByText('Token participation met')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tokens for proposal')).toBeInTheDocument();
|
||||
expect(screen.getByText('Total Supply')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tokens against proposal')).toBeInTheDocument();
|
||||
expect(screen.getByText('Participation required')).toBeInTheDocument();
|
||||
expect(screen.getByText('Majority Required')).toBeInTheDocument();
|
||||
expect(screen.getByText('Number of voting parties')).toBeInTheDocument();
|
||||
expect(screen.getByText('Total tokens voted')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Total tokens voted percentage')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Number of votes for')).toBeInTheDocument();
|
||||
expect(screen.getByText('Number of votes against')).toBeInTheDocument();
|
||||
expect(screen.getByText('Yes percentage')).toBeInTheDocument();
|
||||
expect(screen.getByText('No percentage')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Liquidity majority met')).toBeNull();
|
||||
expect(screen.queryByText('Liquidity participation met')).toBeNull();
|
||||
expect(screen.queryByText('Liquidity shares for proposal')).toBeNull();
|
||||
});
|
||||
|
||||
it('displays different breakdown fields for update market proposal', () => {
|
||||
renderComponent(updateMarketProposal, updateMarketProposalType);
|
||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||
expect(screen.getByText('Liquidity majority met')).toBeInTheDocument();
|
||||
expect(screen.getByText('Liquidity participation met')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Liquidity shares for proposal')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('Number of voting parties')).toBeNull();
|
||||
expect(screen.queryByText('Total tokens voted')).toBeNull();
|
||||
expect(screen.queryByText('Total tokens voted percentage')).toBeNull();
|
||||
expect(screen.queryByText('Number of votes for')).toBeNull();
|
||||
expect(screen.queryByText('Number of votes against')).toBeNull();
|
||||
expect(screen.queryByText('Yes percentage')).toBeNull();
|
||||
expect(screen.queryByText('No percentage')).toBeNull();
|
||||
});
|
||||
|
||||
it('displays if an update market proposal will pass by token vote', () => {
|
||||
renderComponent(updateMarketProposal, updateMarketProposalType);
|
||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays if an update market proposal will pass by LP vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
marketId: '12345',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: {
|
||||
...generateYesVotes(0, 1, '10'),
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0, 1, '0'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
updateMarketProposalType
|
||||
);
|
||||
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
Thumbs,
|
||||
RoundedWrapper,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { ProposalType } from '../proposal/proposal';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
interface ProposalVotesTableProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
proposalType: ProposalType | null;
|
||||
}
|
||||
|
||||
export const ProposalVotesTable = ({
|
||||
proposal,
|
||||
proposalType,
|
||||
}: ProposalVotesTableProps) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
appState: { totalSupply },
|
||||
} = useAppState();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const {
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
totalTokensPercentage,
|
||||
participationMet,
|
||||
participationLPMet,
|
||||
totalTokensVoted,
|
||||
noPercentage,
|
||||
yesPercentage,
|
||||
noTokens,
|
||||
yesTokens,
|
||||
yesEquityLikeShareWeight,
|
||||
yesVotes,
|
||||
noVotes,
|
||||
totalVotes,
|
||||
requiredMajorityPercentage,
|
||||
requiredParticipation,
|
||||
majorityMet,
|
||||
majorityLPMet,
|
||||
} = useVoteInformation({ proposal });
|
||||
|
||||
const isUpdateMarket = proposalType === ProposalType.PROPOSAL_UPDATE_MARKET;
|
||||
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
|
||||
const updateMarketVotePassMethod = willPassByTokenVote
|
||||
? t('byTokenVote')
|
||||
: t('byLiquidityVote');
|
||||
|
||||
return (
|
||||
<>
|
||||
<CollapsibleToggle
|
||||
toggleState={showDetails}
|
||||
setToggleState={setShowDetails}
|
||||
dataTestId="vote-breakdown-toggle"
|
||||
>
|
||||
<SubHeading title={t('voteBreakdown')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showDetails && (
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable
|
||||
data-testid="proposal-votes-table"
|
||||
numerical={true}
|
||||
headingLevel={4}
|
||||
>
|
||||
<KeyValueTableRow>
|
||||
{t('expectedToPass')}
|
||||
{isUpdateMarket ? (
|
||||
updateMarketWillPass ? (
|
||||
<Thumbs up={true} text={updateMarketVotePassMethod} />
|
||||
) : (
|
||||
<Thumbs up={false} />
|
||||
)
|
||||
) : willPassByTokenVote ? (
|
||||
<Thumbs up={true} />
|
||||
) : (
|
||||
<Thumbs up={false} />
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('majorityMet')}
|
||||
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
||||
</KeyValueTableRow>
|
||||
{isUpdateMarket && (
|
||||
<KeyValueTableRow>
|
||||
{t('majorityLPMet')}
|
||||
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
<KeyValueTableRow>
|
||||
{t('participationMet')}
|
||||
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
||||
</KeyValueTableRow>
|
||||
{isUpdateMarket && (
|
||||
<KeyValueTableRow>
|
||||
{t('participationLPMet')}
|
||||
{participationLPMet ? (
|
||||
<Thumbs up={true} />
|
||||
) : (
|
||||
<Thumbs up={false} />
|
||||
)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
<KeyValueTableRow>
|
||||
{t('tokenForProposal')}
|
||||
{formatNumber(yesTokens, 2)}
|
||||
</KeyValueTableRow>
|
||||
{isUpdateMarket && (
|
||||
<KeyValueTableRow>
|
||||
{t('tokenLPForProposal')}
|
||||
{formatNumber(yesEquityLikeShareWeight, 2)}
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
<KeyValueTableRow>
|
||||
{t('totalSupply')}
|
||||
{formatNumber(totalSupply, 2)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('tokensAgainstProposal')}
|
||||
{formatNumber(noTokens, 2)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('participationRequired')}
|
||||
{formatNumberPercentage(requiredParticipation)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('majorityRequired')}
|
||||
{formatNumberPercentage(requiredMajorityPercentage)}
|
||||
</KeyValueTableRow>
|
||||
{!isUpdateMarket && (
|
||||
<>
|
||||
<KeyValueTableRow>
|
||||
{t('numberOfVotingParties')}
|
||||
{formatNumber(totalVotes, 0)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('totalTokensVotes')}
|
||||
{formatNumber(totalTokensVoted, 2)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('totalTokenVotedPercentage')}
|
||||
{formatNumberPercentage(totalTokensPercentage, 2)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('numberOfForVotes')}
|
||||
{formatNumber(yesVotes, 0)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('numberOfAgainstVotes')}
|
||||
{formatNumber(noVotes, 0)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
{t('yesPercentage')}
|
||||
{formatNumberPercentage(yesPercentage, 2)}
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
{t('noPercentage')}
|
||||
{formatNumberPercentage(noPercentage, 2)}
|
||||
</KeyValueTableRow>
|
||||
</>
|
||||
)}
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './proposal-voting-status';
|
||||
-153
@@ -1,153 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
lastWeek,
|
||||
mockWalletContext,
|
||||
networkParamsQueryMock,
|
||||
nextWeek,
|
||||
mockNetworkParams,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { ProposalVotingStatus } from './proposal-voting-status';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import {
|
||||
generateNoVotes,
|
||||
generateProposal,
|
||||
generateYesVotes,
|
||||
} from '../../test-helpers/generate-proposals';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { BigNumber } from '../../../../lib/bignumber';
|
||||
import type { AppState } from '../../../../contexts/app-state/app-state-context';
|
||||
|
||||
const mockTotalSupply = new BigNumber(100);
|
||||
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
const mockAppState: AppState = {
|
||||
totalAssociated: new BigNumber('50063005'),
|
||||
decimals: 18,
|
||||
totalSupply: mockTotalSupply,
|
||||
vegaWalletManageOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
};
|
||||
|
||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
useAppState: () => ({
|
||||
appState: mockAppState,
|
||||
}),
|
||||
}));
|
||||
|
||||
const renderComponent = (
|
||||
proposal: ProposalQuery['proposal'],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
|
||||
) =>
|
||||
render(
|
||||
<Router>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposalVotingStatus
|
||||
proposal={proposal}
|
||||
networkParams={mockNetworkParams}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
describe('ProposalVotingStatus', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('Renders majority reached', () => {
|
||||
const yesVotes = 100;
|
||||
const noVotes = 0;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('majority-reached')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders majority not reached', () => {
|
||||
const yesVotes = 20;
|
||||
const noVotes = 80;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('majority-not-reached')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders participation reached', () => {
|
||||
const yesVotes = 1000;
|
||||
const noVotes = 0;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('participation-reached')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders participation not reached', () => {
|
||||
const yesVotes = 0;
|
||||
const noVotes = 0;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('participation-not-reached')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
-174
@@ -1,174 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
import { BigNumber } from '../../../../lib/bignumber';
|
||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
const statusClasses = (reached: boolean) =>
|
||||
classNames('flex items-center gap-2 px-4 py-2 rounded-md', {
|
||||
'bg-vega-green-700': reached,
|
||||
'bg-vega-red-700': !reached,
|
||||
});
|
||||
|
||||
const MajorityStatus = ({
|
||||
reached,
|
||||
requiredMajority,
|
||||
}: {
|
||||
reached: boolean;
|
||||
requiredMajority: string | null | undefined;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={statusClasses(reached)}
|
||||
data-testid="proposal-majority-status"
|
||||
>
|
||||
{reached ? <Icon name="tick" /> : <Icon name="cross" />}
|
||||
{reached ? (
|
||||
<div data-testid="majority-reached">
|
||||
{requiredMajority ? (
|
||||
<>
|
||||
{new BigNumber(requiredMajority).times(100).toString()}%{' '}
|
||||
{t('majorityVotedForProposal')}
|
||||
</>
|
||||
) : (
|
||||
t('requiredMajorityVotedForProposal')
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div data-testid="majority-not-reached">
|
||||
{requiredMajority ? (
|
||||
<>
|
||||
{new BigNumber(requiredMajority).times(100).toString()}%{' '}
|
||||
{t('majorityNotVotedForProposal')}
|
||||
</>
|
||||
) : (
|
||||
t('requiredMajorityNotVotedForProposal')
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ParticipationStatus = ({ reached }: { reached: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={statusClasses(reached)}>
|
||||
{reached ? (
|
||||
<>
|
||||
<Icon name="tick" />
|
||||
<div data-testid="participation-reached">
|
||||
{t('minParticipationReached')}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon name="cross" />
|
||||
<div data-testid="participation-not-reached">
|
||||
{t('minParticipationNotReached')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProposalVotingStatus = ({
|
||||
proposal,
|
||||
networkParams,
|
||||
}: {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
networkParams: Partial<NetworkParamsResult>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { majorityMet, majorityLPMet, participationMet, participationLPMet } =
|
||||
useVoteInformation({
|
||||
proposal,
|
||||
});
|
||||
|
||||
if (!proposal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
|
||||
|
||||
let requiredVotingMajority = null;
|
||||
let requiredVotingMajorityLP = null;
|
||||
|
||||
if (networkParams) {
|
||||
switch (proposal.terms.change.__typename) {
|
||||
case 'NewMarket':
|
||||
requiredVotingMajority =
|
||||
networkParams.governance_proposal_market_requiredMajority;
|
||||
break;
|
||||
case 'UpdateMarket':
|
||||
requiredVotingMajority =
|
||||
networkParams.governance_proposal_updateMarket_requiredMajority;
|
||||
requiredVotingMajorityLP =
|
||||
networkParams.governance_proposal_updateMarket_requiredMajorityLP;
|
||||
break;
|
||||
case 'NewAsset':
|
||||
requiredVotingMajority =
|
||||
networkParams.governance_proposal_asset_requiredMajority;
|
||||
break;
|
||||
case 'UpdateAsset':
|
||||
requiredVotingMajority =
|
||||
networkParams.governance_proposal_updateAsset_requiredMajority;
|
||||
break;
|
||||
case 'UpdateNetworkParameter':
|
||||
requiredVotingMajority =
|
||||
networkParams.governance_proposal_updateNetParam_requiredMajority;
|
||||
break;
|
||||
case 'NewFreeform':
|
||||
requiredVotingMajority =
|
||||
networkParams.governance_proposal_freeform_requiredMajority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUpdateMarket) {
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<p>{t('Token vote')}</p>
|
||||
<div
|
||||
className="grid grid-cols-2 gap-4 items-center"
|
||||
data-testid="token-vote-statuses"
|
||||
>
|
||||
<MajorityStatus
|
||||
reached={majorityMet}
|
||||
requiredMajority={requiredVotingMajority}
|
||||
/>{' '}
|
||||
<ParticipationStatus reached={participationMet} />
|
||||
</div>
|
||||
|
||||
<p className="mt-4">{t('Liquidity provider vote')}</p>
|
||||
<div
|
||||
className="grid grid-cols-2 gap-4 items-center"
|
||||
data-testid="lp-vote-statuses"
|
||||
>
|
||||
<MajorityStatus
|
||||
reached={majorityLPMet}
|
||||
requiredMajority={requiredVotingMajorityLP}
|
||||
/>{' '}
|
||||
<ParticipationStatus reached={participationLPMet} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4 items-center mb-6">
|
||||
<MajorityStatus
|
||||
reached={majorityMet}
|
||||
requiredMajority={requiredVotingMajority}
|
||||
/>{' '}
|
||||
<ParticipationStatus reached={participationMet} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -34,12 +34,6 @@ jest.mock('../proposal-change-table', () => ({
|
||||
jest.mock('../proposal-json', () => ({
|
||||
ProposalJson: () => <div data-testid="proposal-json"></div>,
|
||||
}));
|
||||
jest.mock('../proposal-votes-table', () => ({
|
||||
ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>,
|
||||
}));
|
||||
jest.mock('../vote-details', () => ({
|
||||
VoteDetails: () => <div data-testid="proposal-vote-details"></div>,
|
||||
}));
|
||||
jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
@@ -104,8 +98,6 @@ it('renders each section', async () => {
|
||||
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
||||
import { ProposalDescription } from '../proposal-description';
|
||||
import { ProposalChangeTable } from '../proposal-change-table';
|
||||
import { ProposalJson } from '../proposal-json';
|
||||
import { ProposalVotesTable } from '../proposal-votes-table';
|
||||
import { ProposalAssetDetails } from '../proposal-asset-details';
|
||||
import { VoteDetails } from '../vote-details';
|
||||
import { UserVote } from '../vote-details';
|
||||
import { ListAsset } from '../list-asset';
|
||||
import Routes from '../../../routes';
|
||||
import { ProposalMarketData } from '../proposal-market-data';
|
||||
@@ -22,14 +21,6 @@ import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
import { useVoteSubmit } from '@vegaprotocol/proposals';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
|
||||
export enum ProposalType {
|
||||
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
|
||||
PROPOSAL_UPDATE_MARKET = 'PROPOSAL_UPDATE_MARKET',
|
||||
PROPOSAL_NEW_ASSET = 'PROPOSAL_NEW_ASSET',
|
||||
PROPOSAL_UPDATE_ASSET = 'PROPOSAL_UPDATE_ASSET',
|
||||
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
|
||||
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
|
||||
}
|
||||
export interface ProposalProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
networkParams: Partial<NetworkParamsResult>;
|
||||
@@ -80,39 +71,32 @@ export const Proposal = ({
|
||||
}
|
||||
|
||||
let minVoterBalance = null;
|
||||
let proposalType = null;
|
||||
|
||||
if (networkParams) {
|
||||
switch (proposal.terms.change.__typename) {
|
||||
case 'NewMarket':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_market_minVoterBalance;
|
||||
proposalType = ProposalType.PROPOSAL_NEW_MARKET;
|
||||
break;
|
||||
case 'UpdateMarket':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateMarket_minVoterBalance;
|
||||
proposalType = ProposalType.PROPOSAL_UPDATE_MARKET;
|
||||
break;
|
||||
case 'NewAsset':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_asset_minVoterBalance;
|
||||
proposalType = ProposalType.PROPOSAL_NEW_ASSET;
|
||||
break;
|
||||
case 'UpdateAsset':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateAsset_minVoterBalance;
|
||||
proposalType = ProposalType.PROPOSAL_UPDATE_ASSET;
|
||||
break;
|
||||
case 'UpdateNetworkParameter':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_updateNetParam_minVoterBalance;
|
||||
proposalType = ProposalType.PROPOSAL_NETWORK_PARAMETER;
|
||||
break;
|
||||
case 'NewFreeform':
|
||||
minVoterBalance =
|
||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||
proposalType = ProposalType.PROPOSAL_FREEFORM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -140,91 +124,81 @@ export const Proposal = ({
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
isListItem={false}
|
||||
networkParams={networkParams}
|
||||
voteState={voteState}
|
||||
/>
|
||||
|
||||
<div id="details">
|
||||
<div className="my-10">
|
||||
<ProposalChangeTable proposal={proposal} />
|
||||
</div>
|
||||
|
||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||
proposal.id ? (
|
||||
<ListAsset
|
||||
assetId={proposal.id}
|
||||
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
|
||||
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalDescription description={proposal.rationale.description} />
|
||||
</div>
|
||||
|
||||
{newMarketData && (
|
||||
<div className="mb-4">
|
||||
<ProposalMarketData
|
||||
marketData={newMarketData}
|
||||
parentMarketData={parentMarketData ? parentMarketData : undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateMarket' && (
|
||||
<div className="mb-4">
|
||||
<ProposalMarketChanges
|
||||
originalProposal={
|
||||
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
|
||||
?.changes || {}
|
||||
}
|
||||
latestEnactedProposal={
|
||||
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal
|
||||
?.terms?.updateMarket?.changes || {}
|
||||
}
|
||||
updatedProposal={
|
||||
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(proposal.terms.change.__typename === 'NewAsset' ||
|
||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||
asset && (
|
||||
<div className="mb-4">
|
||||
<ProposalAssetDetails asset={asset} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<ProposalJson proposal={restData?.data?.proposal} />
|
||||
</div>
|
||||
<div className="my-10">
|
||||
<ProposalChangeTable proposal={proposal} />
|
||||
</div>
|
||||
|
||||
<div id="voting">
|
||||
<div className="mb-10">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<VoteDetails
|
||||
proposal={proposal}
|
||||
proposalType={proposalType}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={
|
||||
networkParams?.spam_protection_voting_min_tokens
|
||||
}
|
||||
submit={submit}
|
||||
dialog={Dialog}
|
||||
transaction={transaction}
|
||||
voteState={voteState}
|
||||
voteDatetime={voteDatetime}
|
||||
/>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||
proposal.id ? (
|
||||
<ListAsset
|
||||
assetId={proposal.id}
|
||||
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
|
||||
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalDescription description={proposal.rationale.description} />
|
||||
</div>
|
||||
|
||||
{newMarketData && (
|
||||
<div className="mb-4">
|
||||
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
||||
<ProposalMarketData
|
||||
marketData={newMarketData}
|
||||
parentMarketData={parentMarketData ? parentMarketData : undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{proposal.terms.change.__typename === 'UpdateMarket' && (
|
||||
<div className="mb-4">
|
||||
<ProposalMarketChanges
|
||||
originalProposal={
|
||||
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
|
||||
?.changes || {}
|
||||
}
|
||||
latestEnactedProposal={
|
||||
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal?.terms
|
||||
?.updateMarket?.changes || {}
|
||||
}
|
||||
updatedProposal={
|
||||
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(proposal.terms.change.__typename === 'NewAsset' ||
|
||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||
asset && (
|
||||
<div className="mb-4">
|
||||
<ProposalAssetDetails asset={asset} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-10">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<UserVote
|
||||
proposal={proposal}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={
|
||||
networkParams?.spam_protection_voting_min_tokens
|
||||
}
|
||||
submit={submit}
|
||||
dialog={Dialog}
|
||||
transaction={transaction}
|
||||
voteState={voteState}
|
||||
voteDatetime={voteDatetime}
|
||||
/>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<ProposalJson proposal={restData?.data?.proposal} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
+1
-120
@@ -6,11 +6,7 @@ import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { format } from 'date-fns';
|
||||
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
|
||||
import {
|
||||
generateNoVotes,
|
||||
generateProposal,
|
||||
generateYesVotes,
|
||||
} from '../../test-helpers/generate-proposals';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
import {
|
||||
@@ -93,84 +89,6 @@ describe('Proposals list item details', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by LP vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: '1000',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to pass by LP vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by token vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(1000, 1000),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to pass by token vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to fail', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to fail'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Open - 5 minutes left to vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
@@ -213,43 +131,6 @@ describe('Proposals list item details', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Open - majority not reached', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
no: generateNoVotes(1, 1000000000000000000),
|
||||
yes: generateYesVotes(1, 1000000000000000000),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to fail'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Open - will pass', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(3000, 1000000000000000000),
|
||||
no: generateNoVotes(0),
|
||||
},
|
||||
terms: {
|
||||
closingDatetime: nextWeek.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to pass'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Rejected', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
|
||||
+1
-30
@@ -11,7 +11,6 @@ import {
|
||||
import Routes from '../../../routes';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
|
||||
export const ProposalsListItemDetails = ({
|
||||
proposal,
|
||||
@@ -20,18 +19,10 @@ export const ProposalsListItemDetails = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const state = proposal?.state;
|
||||
const { willPassByTokenVote, willPassByLPVote } = useVoteInformation({
|
||||
proposal,
|
||||
});
|
||||
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
|
||||
const updateMarketVotePassMethod = willPassByTokenVote
|
||||
? t('byTokenVote')
|
||||
: t('byLPVote');
|
||||
const nowToEnactmentInHours = differenceInHours(
|
||||
new Date(proposal?.terms.closingDatetime),
|
||||
new Date()
|
||||
);
|
||||
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
|
||||
|
||||
let voteDetails: ReactNode;
|
||||
let voteStatus: ReactNode;
|
||||
@@ -78,31 +69,11 @@ export const ProposalsListItemDetails = ({
|
||||
}
|
||||
case ProposalState.STATE_OPEN: {
|
||||
voteDetails = (
|
||||
<span className={nowToEnactmentInHours < 6 ? 'text-vega-pink' : ''}>
|
||||
<span className={nowToEnactmentInHours < 6 ? 'text-vega-orange' : ''}>
|
||||
{formatDistanceToNowStrict(new Date(proposal?.terms.closingDatetime))}{' '}
|
||||
{t('left to vote')}
|
||||
</span>
|
||||
);
|
||||
voteStatus =
|
||||
(isUpdateMarket &&
|
||||
(updateMarketWillPass ? (
|
||||
<>
|
||||
{t('currentlySetTo')} {t('pass')} {updateMarketVotePassMethod}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('currentlySetTo')} {t('fail')}
|
||||
</>
|
||||
))) ||
|
||||
(willPassByTokenVote ? (
|
||||
<>
|
||||
{t('currentlySetTo')} {t('pass')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('currentlySetTo')} {t('fail')}
|
||||
</>
|
||||
));
|
||||
break;
|
||||
}
|
||||
case ProposalState.STATE_REJECTED: {
|
||||
|
||||
+3
-12
@@ -4,28 +4,19 @@ import { ProposalsListItemDetails } from './proposals-list-item-details';
|
||||
import { useUserVote } from '../vote-details/use-user-vote';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
|
||||
|
||||
interface ProposalsListItemProps {
|
||||
proposal?: ProposalFieldsFragment | ProposalQuery['proposal'] | null;
|
||||
networkParams: Partial<NetworkParamsResult> | null;
|
||||
}
|
||||
|
||||
export const ProposalsListItem = ({
|
||||
proposal,
|
||||
networkParams,
|
||||
}: ProposalsListItemProps) => {
|
||||
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
|
||||
const { voteState } = useUserVote(proposal?.id);
|
||||
if (!proposal || !proposal.id || !networkParams) return null;
|
||||
if (!proposal || !proposal.id) return null;
|
||||
|
||||
return (
|
||||
<li id={proposal.id} data-testid="proposals-list-item">
|
||||
<RoundedWrapper paddingBottom={true} heightFull={true}>
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
networkParams={networkParams}
|
||||
voteState={voteState}
|
||||
/>
|
||||
<ProposalHeader proposal={proposal} voteState={voteState} />
|
||||
<ProposalsListItemDetails proposal={proposal} />
|
||||
</RoundedWrapper>
|
||||
</li>
|
||||
|
||||
+150
-181
@@ -8,7 +8,6 @@ import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-
|
||||
import { ProposalsListFilter } from '../proposals-list-filter';
|
||||
import Routes from '../../../routes';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
Button,
|
||||
Toggle,
|
||||
VegaIcon,
|
||||
@@ -20,10 +19,6 @@ import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
||||
@@ -60,7 +55,10 @@ export const orderByUpgradeBlockHeight = (
|
||||
) =>
|
||||
orderBy(
|
||||
arr,
|
||||
[(p) => p?.upgradeBlockHeight, (p) => p.vegaReleaseTag],
|
||||
[
|
||||
(p) => (p?.upgradeBlockHeight ? parseInt(p.upgradeBlockHeight, 10) : 0),
|
||||
(p) => p.vegaReleaseTag,
|
||||
],
|
||||
['desc', 'desc']
|
||||
);
|
||||
|
||||
@@ -75,20 +73,6 @@ export const ProposalsList = ({
|
||||
lastBlockHeight,
|
||||
}: ProposalsListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
params: networkParams,
|
||||
loading: networkParamsLoading,
|
||||
error: networkParamsError,
|
||||
} = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
]);
|
||||
|
||||
const [filterString, setFilterString] = useState('');
|
||||
const [closedProposalsView, setClosedProposalsView] =
|
||||
useState<ClosedProposalsViewOptions>(
|
||||
@@ -153,181 +137,166 @@ export const ProposalsList = ({
|
||||
p?.party?.id?.toString().includes(filterString);
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={networkParamsLoading}
|
||||
error={networkParamsError}
|
||||
data={networkParams}
|
||||
>
|
||||
<div data-testid="proposals-list">
|
||||
<div className="grid xs:grid-cols-2 items-center">
|
||||
<Heading
|
||||
centerContent={false}
|
||||
marginBottom={false}
|
||||
title={t('pageTitleProposals')}
|
||||
/>
|
||||
<div data-testid="proposals-list">
|
||||
<div className="grid xs:grid-cols-2 items-center">
|
||||
<Heading
|
||||
centerContent={false}
|
||||
marginBottom={false}
|
||||
title={t('pageTitleProposals')}
|
||||
/>
|
||||
|
||||
{DocsLinks && (
|
||||
<div
|
||||
className="xs:justify-self-end"
|
||||
data-testid="new-proposal-link"
|
||||
>
|
||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
|
||||
<Button variant="primary" size="sm">
|
||||
<div className="flex items-center gap-1">
|
||||
{t('NewProposal')}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
|
||||
</div>
|
||||
</Button>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mb-8">
|
||||
{t(
|
||||
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network. Network upgrades are proposed and approved by validators.`
|
||||
)}{' '}
|
||||
<ExternalLink
|
||||
data-testid="proposal-documentation-link"
|
||||
href={ExternalLinks.GOVERNANCE_PAGE}
|
||||
className="text-white"
|
||||
>
|
||||
{t(`Find out more about Vega governance`)}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
|
||||
{proposals.length > 0 && (
|
||||
<ProposalsListFilter
|
||||
filterString={filterString}
|
||||
setFilterString={(value) => {
|
||||
setFilterString(value);
|
||||
if (value.length > 0) {
|
||||
// If the filter is engaged, ensure the user is viewing governance proposals,
|
||||
// as network upgrades do not have IDs to filter by and will be excluded.
|
||||
setClosedProposalsView(
|
||||
ClosedProposalsViewOptions.NetworkGovernance
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{DocsLinks && (
|
||||
<div className="xs:justify-self-end" data-testid="new-proposal-link">
|
||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
|
||||
<Button variant="primary" size="sm">
|
||||
<div className="flex items-center gap-1">
|
||||
{t('NewProposal')}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
|
||||
</div>
|
||||
</Button>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
|
||||
<SubHeading title={t('openProposals')} />
|
||||
<p className="mb-8">
|
||||
{t(
|
||||
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network. Network upgrades are proposed and approved by validators.`
|
||||
)}{' '}
|
||||
<ExternalLink
|
||||
data-testid="proposal-documentation-link"
|
||||
href={ExternalLinks.GOVERNANCE_PAGE}
|
||||
className="text-white"
|
||||
>
|
||||
{t(`Find out more about Vega governance`)}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
|
||||
{sortedProposals.open.length > 0 ||
|
||||
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
||||
<ul data-testid="open-proposals">
|
||||
{filterString.length < 1 &&
|
||||
sortedProtocolUpgradeProposals.open.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
proposal={proposal}
|
||||
/>
|
||||
))}
|
||||
{proposals.length > 0 && (
|
||||
<ProposalsListFilter
|
||||
filterString={filterString}
|
||||
setFilterString={(value) => {
|
||||
setFilterString(value);
|
||||
if (value.length > 0) {
|
||||
// If the filter is engaged, ensure the user is viewing governance proposals,
|
||||
// as network upgrades do not have IDs to filter by and will be excluded.
|
||||
setClosedProposalsView(
|
||||
ClosedProposalsViewOptions.NetworkGovernance
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
|
||||
<ProposalsListItem
|
||||
key={proposal?.id}
|
||||
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
|
||||
<SubHeading title={t('openProposals')} />
|
||||
|
||||
{sortedProposals.open.length > 0 ||
|
||||
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
||||
<ul data-testid="open-proposals">
|
||||
{filterString.length < 1 &&
|
||||
sortedProtocolUpgradeProposals.open.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
proposal={proposal}
|
||||
networkParams={networkParams}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="mb-0" data-testid="no-open-proposals">
|
||||
{t('noOpenProposals')}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="relative">
|
||||
<SubHeading title={t('closedProposals')} />
|
||||
{sortedProposals.closed.length > 0 ||
|
||||
sortedProtocolUpgradeProposals.closed.length > 0 ? (
|
||||
<>
|
||||
{
|
||||
// We need both the closed proposals and closed protocol upgrade
|
||||
// proposals to be present for there to be a toggle. It also gets
|
||||
// hidden if the user has filtered the list, as the upgrade proposals
|
||||
// do not have the necessary fields for filtering.
|
||||
sortedProposals.closed.length > 0 &&
|
||||
sortedProtocolUpgradeProposals.closed.length > 0 &&
|
||||
filterString.length < 1 && (
|
||||
<div
|
||||
className="grid w-full justify-end xl:-mt-12 pb-6"
|
||||
data-testid="toggle-closed-proposals"
|
||||
>
|
||||
<div className="w-[440px]">
|
||||
<Toggle
|
||||
name="closed-proposals-toggle"
|
||||
toggles={[
|
||||
{
|
||||
label: t(
|
||||
ClosedProposalsViewOptions.NetworkGovernance
|
||||
),
|
||||
value:
|
||||
ClosedProposalsViewOptions.NetworkGovernance,
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
ClosedProposalsViewOptions.NetworkUpgrades
|
||||
),
|
||||
value: ClosedProposalsViewOptions.NetworkUpgrades,
|
||||
},
|
||||
]}
|
||||
checkedValue={closedProposalsView}
|
||||
onChange={(e) =>
|
||||
setClosedProposalsView(
|
||||
e.target.value as ClosedProposalsViewOptions
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
|
||||
<ProposalsListItem key={proposal?.id} proposal={proposal} />
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="mb-0" data-testid="no-open-proposals">
|
||||
{t('noOpenProposals')}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="relative">
|
||||
<SubHeading title={t('closedProposals')} />
|
||||
{sortedProposals.closed.length > 0 ||
|
||||
sortedProtocolUpgradeProposals.closed.length > 0 ? (
|
||||
<>
|
||||
{
|
||||
// We need both the closed proposals and closed protocol upgrade
|
||||
// proposals to be present for there to be a toggle. It also gets
|
||||
// hidden if the user has filtered the list, as the upgrade proposals
|
||||
// do not have the necessary fields for filtering.
|
||||
sortedProposals.closed.length > 0 &&
|
||||
sortedProtocolUpgradeProposals.closed.length > 0 &&
|
||||
filterString.length < 1 && (
|
||||
<div
|
||||
className="flex justify-end xl:-mt-12 pb-6"
|
||||
data-testid="toggle-closed-proposals"
|
||||
>
|
||||
<div className="w-full max-w-[420px]">
|
||||
<Toggle
|
||||
name="closed-proposals-toggle"
|
||||
toggles={[
|
||||
{
|
||||
label: t(
|
||||
ClosedProposalsViewOptions.NetworkGovernance
|
||||
),
|
||||
value: ClosedProposalsViewOptions.NetworkGovernance,
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
ClosedProposalsViewOptions.NetworkUpgrades
|
||||
),
|
||||
value: ClosedProposalsViewOptions.NetworkUpgrades,
|
||||
},
|
||||
]}
|
||||
checkedValue={closedProposalsView}
|
||||
onChange={(e) =>
|
||||
setClosedProposalsView(
|
||||
e.target.value as ClosedProposalsViewOptions
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<ul data-testid="closed-proposals">
|
||||
{closedProposalsView ===
|
||||
ClosedProposalsViewOptions.NetworkUpgrades && (
|
||||
<div data-testid="closed-upgrade-proposals">
|
||||
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
<ul data-testid="closed-proposals">
|
||||
{closedProposalsView ===
|
||||
ClosedProposalsViewOptions.NetworkUpgrades && (
|
||||
<div data-testid="closed-upgrade-proposals">
|
||||
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
proposal={proposal}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{closedProposalsView ===
|
||||
ClosedProposalsViewOptions.NetworkGovernance && (
|
||||
<div data-testid="closed-governance-proposals">
|
||||
{sortedProposals.closed
|
||||
.filter(filterPredicate)
|
||||
.map((proposal) => (
|
||||
<ProposalsListItem
|
||||
key={proposal?.id}
|
||||
proposal={proposal}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<p className="mb-0" data-testid="no-closed-proposals">
|
||||
{t('noClosedProposals')}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{closedProposalsView ===
|
||||
ClosedProposalsViewOptions.NetworkGovernance && (
|
||||
<div data-testid="closed-governance-proposals">
|
||||
{sortedProposals.closed
|
||||
.filter(filterPredicate)
|
||||
.map((proposal) => (
|
||||
<ProposalsListItem
|
||||
key={proposal?.id}
|
||||
proposal={proposal}
|
||||
networkParams={networkParams}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<p className="mb-0" data-testid="no-closed-proposals">
|
||||
{t('noClosedProposals')}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
|
||||
{t('seeRejectedProposals')}
|
||||
</Link>
|
||||
</div>
|
||||
</AsyncRenderer>
|
||||
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
|
||||
{t('seeRejectedProposals')}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-29
@@ -1,15 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Heading } from '../../../../components/heading';
|
||||
import { ProposalsListItem } from '../proposals-list-item';
|
||||
import { ProposalsListFilter } from '../proposals-list-filter';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<ProposalQuery['proposal'] | ProposalFieldsFragment>;
|
||||
@@ -17,19 +12,6 @@ interface ProposalsListProps {
|
||||
|
||||
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
params: networkParams,
|
||||
loading: networkParamsLoading,
|
||||
error: networkParamsError,
|
||||
} = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
]);
|
||||
const [filterString, setFilterString] = useState('');
|
||||
|
||||
const filterPredicate = (
|
||||
@@ -39,11 +21,7 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
p?.party?.id?.toString().includes(filterString);
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={networkParamsLoading}
|
||||
error={networkParamsError}
|
||||
data={networkParams}
|
||||
>
|
||||
<>
|
||||
<Heading title={t('pageTitleRejectedProposals')} />
|
||||
<ProposalsListFilter
|
||||
filterString={filterString}
|
||||
@@ -53,11 +31,7 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
{proposals.length > 0 ? (
|
||||
<ul data-testid="rejected-proposals">
|
||||
{proposals.filter(filterPredicate).map((proposal) => (
|
||||
<ProposalsListItem
|
||||
key={proposal?.id}
|
||||
proposal={proposal}
|
||||
networkParams={networkParams}
|
||||
/>
|
||||
<ProposalsListItem key={proposal?.id} proposal={proposal} />
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
@@ -66,6 +40,6 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
</AsyncRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ export const ProtocolUpgradeProposalsListItem = ({
|
||||
<Link
|
||||
to={`${Routes.PROTOCOL_UPGRADES}/${stripFullStops(
|
||||
proposal.vegaReleaseTag
|
||||
)}`}
|
||||
)}/${proposal.upgradeBlockHeight}`}
|
||||
>
|
||||
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './vote-breakdown';
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
lastWeek,
|
||||
mockWalletContext,
|
||||
networkParamsQueryMock,
|
||||
nextWeek,
|
||||
} from '../../test-helpers/mocks';
|
||||
import { VoteBreakdown } from './vote-breakdown';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import {
|
||||
generateNoVotes,
|
||||
generateProposal,
|
||||
generateYesVotes,
|
||||
} from '../../test-helpers/generate-proposals';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { BigNumber } from '../../../../lib/bignumber';
|
||||
import type { AppState } from '../../../../contexts/app-state/app-state-context';
|
||||
|
||||
const mockTotalSupply = new BigNumber(100);
|
||||
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
|
||||
const fixedTokenValue = 1000000000000000000;
|
||||
|
||||
const mockAppState: AppState = {
|
||||
totalAssociated: new BigNumber('50063005'),
|
||||
decimals: 18,
|
||||
totalSupply: mockTotalSupply,
|
||||
vegaWalletManageOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
};
|
||||
|
||||
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||
useAppState: () => ({
|
||||
appState: mockAppState,
|
||||
}),
|
||||
}));
|
||||
|
||||
const renderComponent = (
|
||||
proposal: ProposalQuery['proposal'],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
|
||||
) =>
|
||||
render(
|
||||
<Router>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
describe('VoteBreakdown', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('Renders majority reached', () => {
|
||||
const yesVotes = 100;
|
||||
const noVotes = 0;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('token-majority-met')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders majority not reached', () => {
|
||||
const yesVotes = 20;
|
||||
const noVotes = 80;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('token-majority-not-met')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders participation reached', () => {
|
||||
const yesVotes = 1000;
|
||||
const noVotes = 0;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('token-participation-met')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders participation not reached', () => {
|
||||
const yesVotes = 0;
|
||||
const noVotes = 0;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('token-participation-not-met')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by LP vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: '1000',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to pass by liquidity vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to pass by token vote', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(1000, fixedTokenValue),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0, fixedTokenValue),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to pass by token vote'
|
||||
);
|
||||
});
|
||||
|
||||
it('Renders proposal state: Update market proposal - Currently expected to fail', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: '0',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||
'Currently expected to fail'
|
||||
);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - token majority', () => {
|
||||
const yesVotes = 80;
|
||||
const noVotes = 20;
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const element = screen.getByTestId('token-majority-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
|
||||
expect(style.width).toBe(`${yesVotes}%`);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - token participation', () => {
|
||||
const yesVotes = 40;
|
||||
const noVotes = 20;
|
||||
const totalVotes = yesVotes + noVotes;
|
||||
const totalSupplyValue = mockTotalSupply.toNumber();
|
||||
const expectedProgress = (totalVotes / totalSupplyValue) * 100; // Here it should be 60%
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
closingDatetime: lastWeek.toString(),
|
||||
enactmentDatetime: nextWeek.toString(),
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: generateYesVotes(yesVotes, fixedTokenValue),
|
||||
no: generateNoVotes(noVotes, fixedTokenValue),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const element = screen.getByTestId('token-participation-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
|
||||
expect(style.width).toBe(`${expectedProgress}%`);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - LP majority', () => {
|
||||
const yesVotesLP = 800;
|
||||
const noVotesLP = 200;
|
||||
const expectedProgress = (yesVotesLP / (yesVotesLP + noVotesLP)) * 100; // 80%
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: `${yesVotesLP}`,
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: `${noVotesLP}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const element = screen.getByTestId('lp-majority-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
expect(style.width).toBe(`${expectedProgress}%`);
|
||||
});
|
||||
|
||||
it('Progress bar displays status - LP participation', () => {
|
||||
const yesVotesLP = 400;
|
||||
const noVotesLP = 600;
|
||||
const totalVotesLP = yesVotesLP + noVotesLP;
|
||||
const totalLPSupply = 1000;
|
||||
const expectedProgress = (totalVotesLP / totalLPSupply) * 100; // 100%
|
||||
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
state: ProposalState.STATE_PASSED,
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'UpdateMarket',
|
||||
},
|
||||
},
|
||||
votes: {
|
||||
__typename: 'ProposalVotes',
|
||||
yes: {
|
||||
...generateYesVotes(0),
|
||||
totalEquityLikeShareWeight: `${yesVotesLP}`,
|
||||
},
|
||||
no: {
|
||||
...generateNoVotes(0),
|
||||
totalEquityLikeShareWeight: `${noVotesLP}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const element = screen.getByTestId('lp-participation-progress');
|
||||
const style = window.getComputedStyle(element);
|
||||
expect(style.width).toBe(`${expectedProgress}%`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,399 @@
|
||||
import classNames from 'classnames';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
interface VoteBreakdownProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}
|
||||
|
||||
interface VoteProgressProps {
|
||||
percentageFor: BigNumber;
|
||||
colourfulBg?: boolean;
|
||||
testId?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const VoteProgress = ({
|
||||
percentageFor,
|
||||
colourfulBg,
|
||||
testId,
|
||||
children,
|
||||
}: VoteProgressProps) => {
|
||||
const containerClasses = classNames(
|
||||
'relative h-10 rounded-md border border-vega-dark-300 overflow-hidden',
|
||||
colourfulBg ? 'bg-vega-pink' : 'bg-vega-dark-400'
|
||||
);
|
||||
|
||||
const progressClasses = classNames(
|
||||
'absolute h-full top-0 left-0',
|
||||
colourfulBg ? 'bg-vega-green' : 'bg-white'
|
||||
);
|
||||
|
||||
const textClasses = classNames(
|
||||
'absolute top-0 left-0 w-full h-full flex items-center justify-start px-3 text-black'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
<div
|
||||
className={progressClasses}
|
||||
style={{ width: `${percentageFor}%` }}
|
||||
data-testid={testId}
|
||||
></div>
|
||||
<div className={textClasses}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface StatusProps {
|
||||
reached: boolean;
|
||||
threshold: BigNumber;
|
||||
text: string;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div data-testid={testId}>
|
||||
{reached ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon name="tick" size={4} />
|
||||
<span>
|
||||
{threshold.toString()}% {text} {t('met')}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon name="cross" size={4} />
|
||||
<span>
|
||||
{threshold.toString()}% {text} {t('not met')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
|
||||
const {
|
||||
totalTokensPercentage,
|
||||
participationMet,
|
||||
totalTokensVoted,
|
||||
totalLPTokensPercentage,
|
||||
noPercentage,
|
||||
noLPPercentage,
|
||||
yesPercentage,
|
||||
yesLPPercentage,
|
||||
yesTokens,
|
||||
noTokens,
|
||||
yesEquityLikeShareWeight,
|
||||
noEquityLikeShareWeight,
|
||||
totalEquityLikeShareWeight,
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
requiredParticipation,
|
||||
requiredParticipationLP,
|
||||
participationLPMet,
|
||||
majorityMet,
|
||||
majorityLPMet,
|
||||
willPassByTokenVote,
|
||||
willPassByLPVote,
|
||||
} = useVoteInformation({ proposal });
|
||||
|
||||
const { t } = useTranslation();
|
||||
const defaultDP = 2;
|
||||
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||
const isUpdateMarket = proposal?.terms?.change?.__typename === 'UpdateMarket';
|
||||
const participationThresholdProgress = BigNumber.min(
|
||||
totalTokensPercentage.dividedBy(requiredParticipation).multipliedBy(100),
|
||||
new BigNumber(100)
|
||||
);
|
||||
const lpParticipationThresholdProgress =
|
||||
requiredParticipationLP &&
|
||||
BigNumber.min(
|
||||
totalLPTokensPercentage
|
||||
.dividedBy(requiredParticipationLP)
|
||||
.multipliedBy(100),
|
||||
new BigNumber(100)
|
||||
);
|
||||
const willPass = willPassByTokenVote || willPassByLPVote;
|
||||
const updateMarketVotePassMethod = willPassByTokenVote
|
||||
? t('byTokenVote')
|
||||
: t('byLiquidityVote');
|
||||
|
||||
const sectionWrapperClasses = classNames(
|
||||
'flex justify-between flex-wrap gap-6'
|
||||
);
|
||||
const sectionClasses = classNames('min-w-[300px] flex-1 flex-grow');
|
||||
const headingClasses = classNames('mb-2 text-vega-dark-400');
|
||||
const progressDetailsClasses = classNames(
|
||||
'flex justify-between flex-wrap mt-2 text-sm'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mb-6">
|
||||
{isProposalOpen && (
|
||||
<div
|
||||
data-testid="vote-status"
|
||||
className="flex items-center gap-1 mb-2 text-bold"
|
||||
>
|
||||
<span>
|
||||
{willPass ? (
|
||||
<Icon name="tick" size={5} className="text-vega-green" />
|
||||
) : (
|
||||
<Icon name="cross" size={5} className="text-vega-pink" />
|
||||
)}
|
||||
</span>
|
||||
<span>{t('currentlySetTo')} </span>
|
||||
{willPass ? (
|
||||
<span>
|
||||
<span className="text-vega-green">{t('pass')}</span>
|
||||
{isUpdateMarket && <span> {updateMarketVotePassMethod}</span>}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-vega-pink">{t('fail')}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUpdateMarket && (
|
||||
<div className="mb-4">
|
||||
<h3 className={headingClasses}>{t('liquidityProviderVote')}</h3>
|
||||
<div className={sectionWrapperClasses}>
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-majority-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={yesLPPercentage}
|
||||
colourfulBg={true}
|
||||
testId="lp-majority-progress"
|
||||
>
|
||||
<Status
|
||||
reached={majorityLPMet}
|
||||
threshold={requiredMajorityLPPercentage}
|
||||
text={t('majorityThreshold')}
|
||||
testId={
|
||||
majorityLPMet ? 'lp-majority-met' : 'lp-majority-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className={progressDetailsClasses}>
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesFor')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
yesEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
{yesEquityLikeShareWeight
|
||||
.dividedBy(toBigNum(10 ** 6, 0))
|
||||
.toFixed(1)}
|
||||
M
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{yesLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{yesLPPercentage.toFixed(0)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('liquidityProviderVotesAgainst')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
noEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
{noEquityLikeShareWeight
|
||||
.dividedBy(toBigNum(10 ** 6, 0))
|
||||
.toFixed(1)}
|
||||
M
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={
|
||||
<span>{noLPPercentage.toFixed(defaultDP)}%</span>
|
||||
}
|
||||
>
|
||||
<button>{noLPPercentage.toFixed(0)}%</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="lp-participation-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={
|
||||
lpParticipationThresholdProgress || new BigNumber(0)
|
||||
}
|
||||
testId="lp-participation-progress"
|
||||
>
|
||||
<Status
|
||||
reached={participationLPMet}
|
||||
threshold={requiredParticipationLP || new BigNumber(1)}
|
||||
text={t('participationThreshold')}
|
||||
testId={
|
||||
participationLPMet
|
||||
? 'lp-participation-met'
|
||||
: 'lp-participation-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className="flex mt-2 text-sm">
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('totalLiquidityProviderTokensVoted')}:</span>
|
||||
<Tooltip
|
||||
description={formatNumber(
|
||||
totalEquityLikeShareWeight,
|
||||
defaultDP
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
{totalEquityLikeShareWeight
|
||||
.dividedBy(toBigNum(10 ** 6, 0))
|
||||
.toFixed(1)}
|
||||
M
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
({totalEquityLikeShareWeight.toFixed(defaultDP)}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUpdateMarket && <h3 className={headingClasses}>{t('tokenVote')}</h3>}
|
||||
<div className={sectionWrapperClasses}>
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="token-majority-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={yesPercentage}
|
||||
colourfulBg={true}
|
||||
testId="token-majority-progress"
|
||||
>
|
||||
<Status
|
||||
reached={majorityMet}
|
||||
threshold={requiredMajorityPercentage}
|
||||
text={t('majorityThreshold')}
|
||||
testId={
|
||||
majorityMet ? 'token-majority-met' : 'token-majority-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className={progressDetailsClasses}>
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('tokenVotesFor')}:</span>
|
||||
<Tooltip description={formatNumber(yesTokens, defaultDP)}>
|
||||
<button data-testid="num-votes-for">
|
||||
{yesTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={<span>{yesPercentage.toFixed(defaultDP)}%</span>}
|
||||
>
|
||||
<button data-testid="votes-for-percentage">
|
||||
{yesPercentage.toFixed(0)}%
|
||||
</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('tokenVotesAgainst')}:</span>
|
||||
<Tooltip description={formatNumber(noTokens, defaultDP)}>
|
||||
<button data-testid="num-votes-against">
|
||||
{noTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span>
|
||||
(
|
||||
<Tooltip
|
||||
description={<span>{noPercentage.toFixed(defaultDP)}%</span>}
|
||||
>
|
||||
<button data-testid="votes-against-percentage">
|
||||
{noPercentage.toFixed(0)}%
|
||||
</button>
|
||||
</Tooltip>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={sectionClasses}
|
||||
data-testid="token-participation-breakdown"
|
||||
>
|
||||
<VoteProgress
|
||||
percentageFor={participationThresholdProgress}
|
||||
testId="token-participation-progress"
|
||||
>
|
||||
<Status
|
||||
reached={participationMet}
|
||||
threshold={requiredParticipation}
|
||||
text={t('participationThreshold')}
|
||||
testId={
|
||||
participationMet
|
||||
? 'token-participation-met'
|
||||
: 'token-participation-not-met'
|
||||
}
|
||||
/>
|
||||
</VoteProgress>
|
||||
|
||||
<div className="flex mt-2 text-sm">
|
||||
<div className="flex items-center gap-1">
|
||||
<span>{t('totalTokensVoted')}:</span>
|
||||
<Tooltip description={formatNumber(totalTokensVoted, defaultDP)}>
|
||||
<button data-testid="total-voted">
|
||||
{totalTokensVoted.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
|
||||
</button>
|
||||
</Tooltip>
|
||||
<span data-testid="total-voted-percentage">
|
||||
({totalTokensPercentage.toFixed(defaultDP)}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
export { VoteDetails } from './vote-details';
|
||||
export { UserVote } from './user-vote';
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { ConnectToVega } from '../../../../components/connect-to-vega';
|
||||
import { VoteButtonsContainer } from './vote-buttons';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { VoteState } from './use-user-vote';
|
||||
|
||||
interface UserVoteProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
minVoterBalance: string | null | undefined;
|
||||
spamProtectionMinTokens: string | null | undefined;
|
||||
transaction: VegaTxState | null;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
}
|
||||
|
||||
export const UserVote = ({
|
||||
proposal,
|
||||
minVoterBalance,
|
||||
spamProtectionMinTokens,
|
||||
submit,
|
||||
transaction,
|
||||
dialog,
|
||||
voteState,
|
||||
voteDatetime,
|
||||
}: UserVoteProps) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section data-testid="user-vote">
|
||||
{proposal?.state === ProposalState.STATE_OPEN ? (
|
||||
<SubHeading title={t('castYourVote')} />
|
||||
) : (
|
||||
<SubHeading title={t('yourVote')} />
|
||||
)}
|
||||
|
||||
{pubKey ? (
|
||||
proposal && (
|
||||
<VoteButtonsContainer
|
||||
voteState={voteState}
|
||||
voteDatetime={voteDatetime}
|
||||
proposalState={proposal.state}
|
||||
proposalId={proposal.id ?? ''}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={spamProtectionMinTokens}
|
||||
className="flex"
|
||||
submit={submit}
|
||||
transaction={transaction}
|
||||
dialog={dialog}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div className="pb-2">
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon name={'info-sign'} />
|
||||
<div>{t('connectAVegaWalletToVote')}</div>
|
||||
</div>
|
||||
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
|
||||
{t('findOutMoreAboutHowToVote')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<ConnectToVega />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -190,14 +190,15 @@ export const VoteButtons = ({
|
||||
(voteState === VoteState.Yes || voteState === VoteState.No) && (
|
||||
<p data-testid="you-voted">
|
||||
<span>{t('youVoted')}:</span>{' '}
|
||||
<span className="text-white font-bold">
|
||||
<span className="mx-1 text-white font-bold uppercase">
|
||||
{t(`voteState_${voteState}`)}
|
||||
</span>{' '}
|
||||
{voteDatetime ? (
|
||||
<span>{format(voteDatetime, DATE_FORMAT_LONG)}. </span>
|
||||
<span>on {format(voteDatetime, DATE_FORMAT_LONG)}. </span>
|
||||
) : null}
|
||||
{proposalVotable ? (
|
||||
<ButtonLink
|
||||
className="text-white"
|
||||
data-testid="change-vote-button"
|
||||
onClick={() => {
|
||||
setChangeVote(true);
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { RoundedWrapper, Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { VoteProgress } from '@vegaprotocol/proposals';
|
||||
import { formatNumber } from '../../../../lib/format-number';
|
||||
import { ConnectToVega } from '../../../../components/connect-to-vega';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
import { CurrentProposalStatus } from '../current-proposal-status';
|
||||
import { VoteButtonsContainer } from './vote-buttons';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { ProposalType } from '../proposal/proposal';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { VoteState } from './use-user-vote';
|
||||
|
||||
interface VoteDetailsProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
minVoterBalance: string | null | undefined;
|
||||
spamProtectionMinTokens: string | null | undefined;
|
||||
proposalType: ProposalType | null;
|
||||
transaction: VegaTxState | null;
|
||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||
dialog: (props: DialogProps) => JSX.Element;
|
||||
voteState: VoteState | null;
|
||||
voteDatetime: Date | null;
|
||||
}
|
||||
|
||||
export const VoteDetails = ({
|
||||
proposal,
|
||||
minVoterBalance,
|
||||
spamProtectionMinTokens,
|
||||
proposalType,
|
||||
submit,
|
||||
transaction,
|
||||
dialog,
|
||||
voteState,
|
||||
voteDatetime,
|
||||
}: VoteDetailsProps) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const {
|
||||
totalTokensPercentage,
|
||||
participationMet,
|
||||
totalTokensVoted,
|
||||
totalLPTokensPercentage,
|
||||
noPercentage,
|
||||
noLPPercentage,
|
||||
yesPercentage,
|
||||
yesLPPercentage,
|
||||
yesTokens,
|
||||
noTokens,
|
||||
requiredMajorityPercentage,
|
||||
requiredMajorityLPPercentage,
|
||||
requiredParticipation,
|
||||
requiredParticipationLP,
|
||||
participationLPMet,
|
||||
} = useVoteInformation({ proposal });
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const defaultDecimals = 2;
|
||||
const daysLeft = t('daysLeft', {
|
||||
daysLeft: formatDistanceToNow(new Date(proposal?.terms.closingDatetime)),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
||||
<section>
|
||||
<SubHeading title={t('liquidityVotes')} />
|
||||
<p data-testid="liquidity-votes-status">
|
||||
<span>
|
||||
<CurrentProposalStatus proposal={proposal} />
|
||||
</span>
|
||||
{'. '}
|
||||
{proposal?.state === ProposalState.STATE_OPEN ? daysLeft : null}
|
||||
</p>
|
||||
<table className="w-full mb-8">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-vega-green w-[18%] text-left">
|
||||
{t('for')}
|
||||
</th>
|
||||
<th>
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityLPPercentage}
|
||||
progress={yesLPPercentage}
|
||||
/>
|
||||
</th>
|
||||
<th className="text-danger w-[18%] text-right">
|
||||
{t('against')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
className="text-left"
|
||||
data-testid="vote-progress-indicator-percentage-for"
|
||||
>
|
||||
{yesLPPercentage.toFixed(defaultDecimals)}%
|
||||
</td>
|
||||
<td className="text-center text-white">
|
||||
{t('majorityRequired')}{' '}
|
||||
{requiredMajorityLPPercentage.toFixed(defaultDecimals)}%
|
||||
</td>
|
||||
<td
|
||||
className="text-right"
|
||||
data-testid="vote-progress-indicator-percentage-against"
|
||||
>
|
||||
{noLPPercentage.toFixed(defaultDecimals)}%
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p className="mb-6">
|
||||
{t('participation')}
|
||||
{': '}
|
||||
{participationLPMet ? (
|
||||
<span className="text-vega-green mx-4">{t('met')}</span>
|
||||
) : (
|
||||
<span className="text-danger mx-4">{t('notMet')}</span>
|
||||
)}{' '}
|
||||
{formatNumber(totalLPTokensPercentage, defaultDecimals)}%
|
||||
<span className="ml-4">
|
||||
{requiredParticipationLP && (
|
||||
<>
|
||||
({formatNumber(requiredParticipationLP, defaultDecimals)}%{' '}
|
||||
{t('governanceRequired')})
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
<section data-testid="votes-table">
|
||||
<SubHeading title={t('tokenVotes')} />
|
||||
<p data-testid="token-votes-status">
|
||||
<span>
|
||||
<CurrentProposalStatus proposal={proposal} />
|
||||
</span>
|
||||
{'. '}
|
||||
{proposal?.state === ProposalState.STATE_OPEN ? daysLeft : null}
|
||||
</p>
|
||||
<table className="w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-vega-green w-[18%] text-left">{t('for')}</th>
|
||||
<th>
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityPercentage}
|
||||
progress={yesPercentage}
|
||||
/>
|
||||
</th>
|
||||
<th className="text-danger w-[18%] text-right">{t('against')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
className="text-left"
|
||||
data-testid="vote-progress-indicator-percentage-for"
|
||||
>
|
||||
{yesPercentage.toFixed(defaultDecimals)}%
|
||||
</td>
|
||||
<td className="text-center text-white">
|
||||
{t('majorityRequired')}{' '}
|
||||
{requiredMajorityPercentage.toFixed(defaultDecimals)}%
|
||||
</td>
|
||||
<td
|
||||
className="text-right"
|
||||
data-testid="vote-progress-indicator-percentage-against"
|
||||
>
|
||||
{noPercentage.toFixed(defaultDecimals)}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-testid="vote-progress-indicator-tokens-for">
|
||||
{' '}
|
||||
{formatNumber(yesTokens, defaultDecimals)}{' '}
|
||||
</td>
|
||||
<td></td>
|
||||
<td
|
||||
data-testid="vote-progress-indicator-tokens-against"
|
||||
className="text-right"
|
||||
>
|
||||
{formatNumber(noTokens, defaultDecimals)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="mb-6">
|
||||
{t('participation')}
|
||||
{': '}
|
||||
{participationMet ? (
|
||||
<span className="text-vega-green mx-4">{t('met')}</span>
|
||||
) : (
|
||||
<span className="text-danger mx-4">{t('notMet')}</span>
|
||||
)}{' '}
|
||||
{formatNumber(totalTokensVoted, defaultDecimals)}{' '}
|
||||
{formatNumber(totalTokensPercentage, defaultDecimals)}%
|
||||
<span className="ml-4">
|
||||
({formatNumber(requiredParticipation, defaultDecimals)}%{' '}
|
||||
{t('governanceRequired')})
|
||||
</span>
|
||||
</p>
|
||||
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
||||
<p>{t('votingThresholdInfo')}</p>
|
||||
)}
|
||||
|
||||
<section className="mt-10">
|
||||
{proposal?.state === ProposalState.STATE_OPEN ? (
|
||||
<SubHeading title={t('castYourVote')} />
|
||||
) : (
|
||||
<SubHeading title={t('yourVote')} />
|
||||
)}
|
||||
|
||||
{pubKey ? (
|
||||
proposal && (
|
||||
<VoteButtonsContainer
|
||||
voteState={voteState}
|
||||
voteDatetime={voteDatetime}
|
||||
proposalState={proposal.state}
|
||||
proposalId={proposal.id ?? ''}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={spamProtectionMinTokens}
|
||||
className="flex"
|
||||
submit={submit}
|
||||
transaction={transaction}
|
||||
dialog={dialog}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon name={'info-sign'} />
|
||||
<div>{t('connectAVegaWalletToVote')}</div>
|
||||
</div>
|
||||
<ExternalLink href="https://blog.vega.xyz/how-to-vote-on-vega-2195d1e52ec5">
|
||||
{t('findOutMoreAboutHowToVote')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
<ConnectToVega />
|
||||
</RoundedWrapper>
|
||||
)}
|
||||
</section>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+4
-1
@@ -43,7 +43,10 @@ describe('EpochIndividualRewardsTable', () => {
|
||||
it('should render correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<AppStateProvider>
|
||||
<EpochIndividualRewardsTable data={mockData} />
|
||||
<EpochIndividualRewardsTable
|
||||
data={mockData}
|
||||
marketCreationQuantumMultiple={'1000'}
|
||||
/>
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(getByTestId('epoch-individual-rewards-table')).toBeInTheDocument();
|
||||
|
||||
+3
@@ -8,6 +8,7 @@ import type { EpochIndividualReward } from './generate-epoch-individual-rewards-
|
||||
|
||||
interface EpochIndividualRewardsGridProps {
|
||||
data: EpochIndividualReward;
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}
|
||||
|
||||
interface RewardItemProps {
|
||||
@@ -69,9 +70,11 @@ const RewardItem = ({
|
||||
|
||||
export const EpochIndividualRewardsTable = ({
|
||||
data,
|
||||
marketCreationQuantumMultiple,
|
||||
}: EpochIndividualRewardsGridProps) => {
|
||||
return (
|
||||
<RewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
dataTestId="epoch-individual-rewards-table"
|
||||
epoch={Number(data.epoch)}
|
||||
>
|
||||
|
||||
+5
@@ -9,6 +9,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||
|
||||
const EPOCHS_PAGE_SIZE = 10;
|
||||
|
||||
@@ -26,6 +27,9 @@ export const EpochIndividualRewards = ({
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { delegationsPagination } = ENV;
|
||||
const { param: marketCreationQuantumMultiple } = useNetworkParam(
|
||||
'rewards_marketCreationQuantumMultiple'
|
||||
);
|
||||
|
||||
const { data, loading, error, refetch } = useRewardsQuery({
|
||||
notifyOnNetworkStatusChange: true,
|
||||
@@ -103,6 +107,7 @@ export const EpochIndividualRewards = ({
|
||||
{epochIndividualRewardSummaries.map(
|
||||
(epochIndividualRewardSummary) => (
|
||||
<EpochIndividualRewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
data={epochIndividualRewardSummary}
|
||||
/>
|
||||
)
|
||||
|
||||
+4
-1
@@ -66,7 +66,10 @@ describe('EpochTotalRewardsTable', () => {
|
||||
it('should render correctly', () => {
|
||||
const { getByTestId } = render(
|
||||
<AppStateProvider>
|
||||
<EpochTotalRewardsTable data={mockData} />
|
||||
<EpochTotalRewardsTable
|
||||
data={mockData}
|
||||
marketCreationQuantumMultiple={'1000'}
|
||||
/>
|
||||
</AppStateProvider>
|
||||
);
|
||||
expect(getByTestId('epoch-total-rewards-table')).toBeInTheDocument();
|
||||
|
||||
+7
-1
@@ -8,6 +8,7 @@ import type { EpochTotalSummary } from './generate-epoch-total-rewards-list';
|
||||
|
||||
interface EpochTotalRewardsGridProps {
|
||||
data: EpochTotalSummary;
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}
|
||||
|
||||
interface RewardItemProps {
|
||||
@@ -52,9 +53,14 @@ const RewardItem = ({
|
||||
|
||||
export const EpochTotalRewardsTable = ({
|
||||
data,
|
||||
marketCreationQuantumMultiple,
|
||||
}: EpochTotalRewardsGridProps) => {
|
||||
return (
|
||||
<RewardsTable dataTestId="epoch-total-rewards-table" epoch={data.epoch}>
|
||||
<RewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
dataTestId="epoch-total-rewards-table"
|
||||
epoch={data.epoch}
|
||||
>
|
||||
{Array.from(data.assetRewards.values()).map(
|
||||
({ name, rewards, totalAmount, decimals }, i) => (
|
||||
<div className="contents" key={i}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useEpochAssetsRewardsQuery } from '../home/__generated__/Rewards';
|
||||
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
|
||||
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||
|
||||
const EPOCHS_PAGE_SIZE = 10;
|
||||
|
||||
@@ -18,6 +19,9 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
|
||||
const epochId = Number(currentEpoch.id) - 1;
|
||||
const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
|
||||
const { t } = useTranslation();
|
||||
const { param: marketCreationQuantumMultiple } = useNetworkParam(
|
||||
'rewards_marketCreationQuantumMultiple'
|
||||
);
|
||||
const [page, setPage] = useState(1);
|
||||
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
|
||||
notifyOnNetworkStatusChange: true,
|
||||
@@ -70,7 +74,11 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
|
||||
>
|
||||
{Array.from(epochTotalRewardSummaries.values()).map(
|
||||
(epochTotalSummary, index) => (
|
||||
<EpochTotalRewardsTable data={epochTotalSummary} key={index} />
|
||||
<EpochTotalRewardsTable
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
data={epochTotalSummary}
|
||||
key={index}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Pagination
|
||||
|
||||
@@ -133,24 +133,26 @@ export const RewardsPage = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-[360px]">
|
||||
<Toggle
|
||||
name="epoch-reward-view-toggle"
|
||||
toggles={[
|
||||
{
|
||||
label: t('totalDistributed'),
|
||||
value: 'total',
|
||||
},
|
||||
{
|
||||
label: t('earnedByMe'),
|
||||
value: 'individual',
|
||||
},
|
||||
]}
|
||||
checkedValue={toggleRewardsView}
|
||||
onChange={(e) =>
|
||||
setToggleRewardsView(e.target.value as RewardsView)
|
||||
}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<div className="w-full max-w-[360px]">
|
||||
<Toggle
|
||||
name="epoch-reward-view-toggle"
|
||||
toggles={[
|
||||
{
|
||||
label: t('totalDistributed'),
|
||||
value: 'total',
|
||||
},
|
||||
{
|
||||
label: t('earnedByMe'),
|
||||
value: 'individual',
|
||||
},
|
||||
]}
|
||||
checkedValue={toggleRewardsView}
|
||||
onChange={(e) =>
|
||||
setToggleRewardsView(e.target.value as RewardsView)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+13
-3
@@ -77,7 +77,11 @@ const ColumnHeader = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const ColumnHeaders = () => {
|
||||
const ColumnHeaders = ({
|
||||
marketCreationQuantumMultiple,
|
||||
}: {
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="contents">
|
||||
@@ -89,7 +93,9 @@ const ColumnHeaders = () => {
|
||||
<ColumnHeader
|
||||
key={columnTitle}
|
||||
title={t(columnTitle)}
|
||||
tooltipContent={t(description)}
|
||||
tooltipContent={t(description, {
|
||||
marketCreationQuantumMultiple,
|
||||
})}
|
||||
className={headerGridItemStyles()}
|
||||
/>
|
||||
))}
|
||||
@@ -105,6 +111,7 @@ export interface RewardTableProps {
|
||||
dataTestId: string;
|
||||
epoch: number;
|
||||
children: ReactNode;
|
||||
marketCreationQuantumMultiple: string | null;
|
||||
}
|
||||
|
||||
// Rewards table children will be the row items. Make sure they contain
|
||||
@@ -113,12 +120,15 @@ export const RewardsTable = ({
|
||||
dataTestId,
|
||||
epoch,
|
||||
children,
|
||||
marketCreationQuantumMultiple,
|
||||
}: RewardTableProps) => (
|
||||
<div data-testid={dataTestId} className="mb-12">
|
||||
<SubHeading title={`EPOCH ${epoch}`} />
|
||||
|
||||
<div className={gridStyles}>
|
||||
<ColumnHeaders />
|
||||
<ColumnHeaders
|
||||
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -279,7 +279,7 @@ const routerConfig = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag`,
|
||||
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag/:proposalBlockHeight`,
|
||||
element: <LazyProtocolUpgradeProposal />,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -100,8 +100,8 @@ const Home = ({ name }: RouteChildProps) => {
|
||||
</Link>
|
||||
</p>
|
||||
</HomeSection>
|
||||
<div className="flex gap-12">
|
||||
<div className="flex-1">
|
||||
<div className="flex justify-between flex-wrap gap-x-12 gap-y-4">
|
||||
<div className="flex-1 min-w-[360px]">
|
||||
<HomeSection>
|
||||
<SubHeading title={t('Staking')} />
|
||||
<p>
|
||||
@@ -118,7 +118,7 @@ const Home = ({ name }: RouteChildProps) => {
|
||||
</p>
|
||||
</HomeSection>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 min-w-[360px]">
|
||||
<HomeSection>
|
||||
<SubHeading title={t('Governance')} />
|
||||
<p>
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-2xl text-white uppercase mb-4;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
NX_APP_VERSION=v0.21.0-core-0.72.14
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
@@ -39,7 +39,11 @@ export const MarketsPage = () => {
|
||||
id="proposed-markets"
|
||||
name={t('Proposed markets')}
|
||||
menu={
|
||||
<TradingAnchorButton size="extra-small" href={externalLink}>
|
||||
<TradingAnchorButton
|
||||
size="extra-small"
|
||||
data-testid="propose-new-market"
|
||||
href={externalLink}
|
||||
>
|
||||
{t('Propose a new market')}
|
||||
</TradingAnchorButton>
|
||||
}
|
||||
|
||||
@@ -4,22 +4,37 @@ export default function Document() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
{/*
|
||||
meta tags
|
||||
- next advised against using _document for this, so they exist in our
|
||||
- single page index.page.tsx
|
||||
*/}
|
||||
|
||||
{/* icons */}
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
content="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
|
||||
{/* fonts */}
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
|
||||
{/* styles */}
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/preloader.css" media="all" />
|
||||
|
||||
{/* scripts */}
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
</Head>
|
||||
<Html>
|
||||
|
||||
@@ -10,23 +10,16 @@ export default function Index() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charSet="utf-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Vega Protocol - VEGA Console" />
|
||||
<meta name="description" content="Vega Protocol - Console" />
|
||||
<meta name="og:type" content="website" />
|
||||
<meta name="og:url" content="https://console.vega.xyz/" />
|
||||
<meta name="og:title" content="Vega Protocol - Console" />
|
||||
<meta name="og:site_name" content="Vega Protocol - Console" />
|
||||
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
|
||||
|
||||
<meta
|
||||
name="twitter:card"
|
||||
content="https://static.vega.xyz/favicon.ico"
|
||||
@@ -39,26 +32,6 @@ export default function Index() {
|
||||
/>
|
||||
<meta name="twitter:image:alt" content="VEGA logo" />
|
||||
<meta name="twitter:site" content="@vegaprotocol" />
|
||||
|
||||
<meta name="description" content="Vega Protocol - Console" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
content="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
/>
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/preloader.css" media="all" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
</Head>
|
||||
<ClientRouter />
|
||||
</>
|
||||
|
||||
@@ -7,7 +7,7 @@ envCmd=""
|
||||
|
||||
if [[ ! -z "${ENV_NAME}" ]]; then
|
||||
if [[ "${ENV_NAME}" != "ops-vega" ]]; then
|
||||
envCmd="envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
|
||||
envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,12 +6,11 @@ WORKDIR /app
|
||||
ARG APP
|
||||
ARG ENV_NAME=""
|
||||
RUN apk add --update --no-cache \
|
||||
python3==3.10.11-r0 \
|
||||
make==4.3-r0 \
|
||||
gcc==11.2.1_git20220219-r2 \
|
||||
g++==11.2.1_git20220219-r2
|
||||
COPY . ./
|
||||
RUN yarn --network-timeout 100000 --pure-lockfile
|
||||
RUN yarn --pure-lockfile
|
||||
# work around for different build process in trading
|
||||
RUN sh docker/docker-build.sh
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/announcements",
|
||||
"version": "0.0.2",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
"version": "0.0.3"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,13 @@
|
||||
"tsConfig": "libs/announcements/tsconfig.lib.json",
|
||||
"project": "libs/announcements/package.json",
|
||||
"entryFile": "libs/announcements/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"external": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"@vegaprotocol/react-helpers",
|
||||
"@vegaprotocol/ui-toolkit"
|
||||
],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
|
||||
@@ -78,8 +78,18 @@ const triggerPriceWarningMessage = 'stop-order-warning-message-trigger-price';
|
||||
const triggerTrailingPercentOffsetErrorMessage =
|
||||
'stop-order-error-message-trigger-trailing-percent-offset';
|
||||
|
||||
const numberOfActiveOrdersLimit = 'stop-order-warning-limit';
|
||||
|
||||
const ocoPostfix = (id: string, postfix = true) => (postfix ? `${id}-oco` : id);
|
||||
|
||||
const mockDataProvider = jest.fn((...args) => ({
|
||||
data: Array(0),
|
||||
}));
|
||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
...jest.requireActual('@vegaprotocol/data-provider'),
|
||||
useDataProvider: jest.fn((...args) => mockDataProvider(...args)),
|
||||
}));
|
||||
|
||||
describe('StopOrder', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
@@ -460,4 +470,26 @@ describe('StopOrder', () => {
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
});
|
||||
|
||||
it('shows limit of active stop orders number', async () => {
|
||||
mockDataProvider.mockReturnValue({
|
||||
data: Array(4),
|
||||
});
|
||||
render(generateJsx());
|
||||
expect(mockDataProvider.mock.lastCall?.[0].skip).toBe(true);
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||
expect(mockDataProvider.mock.lastCall?.[0].skip).toBe(false);
|
||||
expect(screen.getByTestId(numberOfActiveOrdersLimit)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('counts oco as two orders', async () => {
|
||||
mockDataProvider.mockReturnValue({
|
||||
data: Array(3),
|
||||
});
|
||||
render(generateJsx());
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||
expect(screen.queryByTestId(numberOfActiveOrdersLimit)).toBeNull();
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
expect(screen.getByTestId(numberOfActiveOrdersLimit)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
TradingButton as Button,
|
||||
Pill,
|
||||
Intent,
|
||||
Notification,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
@@ -53,6 +54,8 @@ import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
import { validateExpiration } from '../../utils';
|
||||
import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants';
|
||||
import { KeyValue } from './key-value';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { stopOrdersProvider } from '@vegaprotocol/orders';
|
||||
|
||||
export interface StopOrderProps {
|
||||
market: Market;
|
||||
@@ -60,6 +63,8 @@ export interface StopOrderProps {
|
||||
submit: (order: StopOrdersSubmission) => void;
|
||||
}
|
||||
|
||||
const MAX_NUMBER_OF_ACTIVE_STOP_ORDERS = 4;
|
||||
const POLLING_TIME = 2000;
|
||||
const trailingPercentOffsetStep = '0.1';
|
||||
|
||||
const getDefaultValues = (
|
||||
@@ -802,6 +807,27 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const triggerTrailingPercentOffset = watch('triggerTrailingPercentOffset');
|
||||
const triggerType = watch('triggerType');
|
||||
|
||||
const { data: activeStopOrders, reload } = useDataProvider({
|
||||
dataProvider: stopOrdersProvider,
|
||||
variables: {
|
||||
filter: {
|
||||
parties: pubKey ? [pubKey] : [],
|
||||
markets: [market.id],
|
||||
liveOnly: true,
|
||||
},
|
||||
},
|
||||
skip: !(pubKey && (formState.isDirty || formState.submitCount)),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
reload();
|
||||
}, POLLING_TIME);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [reload]);
|
||||
|
||||
useEffect(() => {
|
||||
const storedSize = storedFormValues?.[dealTicketType]?.size;
|
||||
if (storedSize && size !== storedSize) {
|
||||
@@ -864,7 +890,6 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
{errors.type.message}
|
||||
</InputError>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
name="side"
|
||||
control={control}
|
||||
@@ -1095,6 +1120,20 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
</>
|
||||
)}
|
||||
<NoWalletWarning isReadOnly={isReadOnly} />
|
||||
{(activeStopOrders?.length ?? 0) + (oco ? 2 : 1) >
|
||||
MAX_NUMBER_OF_ACTIVE_STOP_ORDERS ? (
|
||||
<div className="mb-2">
|
||||
<Notification
|
||||
intent={Intent.Warning}
|
||||
testId={'stop-order-warning-limit'}
|
||||
message={t(
|
||||
'There is a limit of %s active stop orders per market. Orders submitted above the limit will be immediately rejected.',
|
||||
[MAX_NUMBER_OF_ACTIVE_STOP_ORDERS.toString()]
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<SubmitButton
|
||||
assetUnit={assetUnit}
|
||||
market={market}
|
||||
|
||||
@@ -127,7 +127,7 @@ export const TOKEN_GOVERNANCE = '/proposals';
|
||||
export const TOKEN_PROPOSALS = '/proposals';
|
||||
export const TOKEN_PROPOSAL = '/proposals/:id';
|
||||
export const TOKEN_PROTOCOL_UPGRADE_PROPOSAL =
|
||||
'/proposals/protocol-upgrade/:tag';
|
||||
'/proposals/protocol-upgrade/:tag/:blockHeight';
|
||||
export const TOKEN_VALIDATOR = '/validators/:id';
|
||||
|
||||
/**
|
||||
@@ -135,12 +135,12 @@ export const TOKEN_VALIDATOR = '/validators/:id';
|
||||
*/
|
||||
export const useProtocolUpgradeProposalLink = () => {
|
||||
const governance = useLinks(DApp.Token);
|
||||
return (releaseTag: string) =>
|
||||
return (releaseTag: string, blockHeight: string) =>
|
||||
governance(
|
||||
TOKEN_PROTOCOL_UPGRADE_PROPOSAL.replace(
|
||||
':tag',
|
||||
stripFullStops(releaseTag)
|
||||
)
|
||||
).replace(':blockHeight', blockHeight)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@vegaprotocol/i18n",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"format": ["esm", "cjs"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/i18n",
|
||||
"main": "libs/i18n/src/index.ts",
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
export const NetworkParams = {
|
||||
blockchains_ethereumConfig: 'blockchains_ethereumConfig',
|
||||
reward_asset: 'reward_asset',
|
||||
rewards_marketCreationQuantumMultiple:
|
||||
'rewards_marketCreationQuantumMultiple',
|
||||
reward_staking_delegation_payoutDelay:
|
||||
'reward_staking_delegation_payoutDelay',
|
||||
governance_proposal_market_minVoterBalance:
|
||||
|
||||
@@ -144,8 +144,8 @@ fragment StopOrderFields on StopOrder {
|
||||
}
|
||||
}
|
||||
|
||||
query StopOrders($partyId: ID!) {
|
||||
stopOrders(filter: { parties: [$partyId] }) {
|
||||
query StopOrders($filter: StopOrderFilter) {
|
||||
stopOrders(filter: $filter) {
|
||||
edges {
|
||||
node {
|
||||
...StopOrderFields
|
||||
|
||||
+5
-5
@@ -37,7 +37,7 @@ export type OrderSubmissionFieldsFragment = { __typename?: 'OrderSubmission', ma
|
||||
export type StopOrderFieldsFragment = { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, order?: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, icebergOrder?: { __typename: 'IcebergOrder', peakSize: string, minimumVisibleSize: string, reservedRemaining: string } | null } | null, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } };
|
||||
|
||||
export type StopOrdersQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
filter?: Types.InputMaybe<Types.StopOrderFilter>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -283,8 +283,8 @@ export function useOrdersUpdateSubscription(baseOptions: Apollo.SubscriptionHook
|
||||
export type OrdersUpdateSubscriptionHookResult = ReturnType<typeof useOrdersUpdateSubscription>;
|
||||
export type OrdersUpdateSubscriptionResult = Apollo.SubscriptionResult<OrdersUpdateSubscription>;
|
||||
export const StopOrdersDocument = gql`
|
||||
query StopOrders($partyId: ID!) {
|
||||
stopOrders(filter: {parties: [$partyId]}) {
|
||||
query StopOrders($filter: StopOrderFilter) {
|
||||
stopOrders(filter: $filter) {
|
||||
edges {
|
||||
node {
|
||||
...StopOrderFields
|
||||
@@ -306,11 +306,11 @@ export const StopOrdersDocument = gql`
|
||||
* @example
|
||||
* const { data, loading, error } = useStopOrdersQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* filter: // value for 'filter'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useStopOrdersQuery(baseOptions: Apollo.QueryHookOptions<StopOrdersQuery, StopOrdersQueryVariables>) {
|
||||
export function useStopOrdersQuery(baseOptions?: Apollo.QueryHookOptions<StopOrdersQuery, StopOrdersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<StopOrdersQuery, StopOrdersQueryVariables>(StopOrdersDocument, options);
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './__generated__/Orders';
|
||||
export * from './order-data-provider';
|
||||
export * from './stop-orders-data-provider';
|
||||
|
||||
@@ -12,9 +12,10 @@ import {
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
Button,
|
||||
Dialog,
|
||||
Icon,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { Order } from '../order-data-provider';
|
||||
@@ -37,7 +38,7 @@ export const OrderEditDialog = ({
|
||||
order,
|
||||
onSubmit,
|
||||
}: OrderEditDialogProps) => {
|
||||
const headerClassName = 'text-lg font-bold text-black dark:text-white';
|
||||
const headerClassName = 'text-xs font-bold text-black dark:text-white';
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
@@ -57,13 +58,13 @@ export const OrderEditDialog = ({
|
||||
open={isOpen}
|
||||
onChange={onChange}
|
||||
title={t('Edit order')}
|
||||
icon={<Icon name="edit" />}
|
||||
icon={<VegaIcon name={VegaIconNames.EDIT} />}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
{order.market && (
|
||||
<div className="md:col-span-2">
|
||||
<p className={headerClassName}>{t(`Market`)}</p>
|
||||
<p>{t(`${order.market.tradableInstrument.instrument.name}`)}</p>
|
||||
<p>{order.market.tradableInstrument.instrument.code}</p>
|
||||
</div>
|
||||
)}
|
||||
{order.type === Schema.OrderType.TYPE_LIMIT && order.market && (
|
||||
@@ -149,9 +150,7 @@ export const OrderEditDialog = ({
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
<Button variant="primary" size="md" type="submit">
|
||||
{t('Update')}
|
||||
</Button>
|
||||
<TradingButton type="submit">{t('Update')}</TradingButton>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -271,8 +271,8 @@ export const OrderListTable = memo<
|
||||
{
|
||||
colId: 'amend',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 90 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 90 : COL_DEFS.actions.minWidth,
|
||||
minWidth: showAllActions ? 80 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 80 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: Order }) => {
|
||||
if (!data) return null;
|
||||
|
||||
@@ -284,6 +284,7 @@ export const OrderListTable = memo<
|
||||
<ButtonLink
|
||||
data-testid="edit"
|
||||
onClick={() => onEdit(data)}
|
||||
title={t('Edit order')}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EDIT} size={16} />
|
||||
</ButtonLink>
|
||||
@@ -291,6 +292,7 @@ export const OrderListTable = memo<
|
||||
<ButtonLink
|
||||
data-testid="cancel"
|
||||
onClick={() => onCancel(data)}
|
||||
title={t('Cancel order')}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={16} />
|
||||
</ButtonLink>
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { StopOrder } from '../order-data-provider/stop-orders-data-provider
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { stopOrdersWithMarketProvider } from '../order-data-provider/stop-orders-data-provider';
|
||||
import { OrderViewDialog } from '../order-list/order-view-dialog';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import type { Order, StopOrdersQueryVariables } from '../order-data-provider';
|
||||
|
||||
export interface StopOrdersManagerProps {
|
||||
partyId: string;
|
||||
@@ -26,7 +26,11 @@ export const StopOrdersManager = ({
|
||||
}: StopOrdersManagerProps) => {
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const [viewOrder, setViewOrder] = useState<Order | null>(null);
|
||||
const variables = { partyId };
|
||||
const variables: StopOrdersQueryVariables = {
|
||||
filter: {
|
||||
parties: [partyId],
|
||||
},
|
||||
};
|
||||
|
||||
const { data, error, reload } = useDataProvider({
|
||||
dataProvider: stopOrdersWithMarketProvider,
|
||||
|
||||
@@ -463,6 +463,7 @@ export const PositionsTable = ({
|
||||
<ButtonLink
|
||||
data-testid="close-position"
|
||||
onClick={() => data && onClose(data)}
|
||||
title={t('Close position')}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={16} />
|
||||
</ButtonLink>
|
||||
|
||||
@@ -73,7 +73,7 @@ export const ProtocolUpgradeCountdown = ({
|
||||
|
||||
return (
|
||||
<a
|
||||
href={detailsLink(data.vegaReleaseTag)}
|
||||
href={detailsLink(data.vegaReleaseTag, data.upgradeBlockHeight)}
|
||||
target="_blank"
|
||||
rel="noreferrer nofollow noopener"
|
||||
>
|
||||
|
||||
@@ -77,8 +77,8 @@ export const ProtocolUpgradeInProgressNotification = () => {
|
||||
{t(
|
||||
'Trading and other network activity has stopped until the upgrade is complete.'
|
||||
)}{' '}
|
||||
{vegaReleaseTag && (
|
||||
<ExternalLink href={detailsLink(vegaReleaseTag)}>
|
||||
{vegaReleaseTag && upgradeBlockHeight && (
|
||||
<ExternalLink href={detailsLink(vegaReleaseTag, upgradeBlockHeight)}>
|
||||
{t('View details')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const ProtocolUpgradeProposalNotification = ({
|
||||
{t(
|
||||
'Trading activity will be interrupted, manage your risk appropriately.'
|
||||
)}{' '}
|
||||
<ExternalLink href={detailsLink(vegaReleaseTag)}>
|
||||
<ExternalLink href={detailsLink(vegaReleaseTag, upgradeBlockHeight)}>
|
||||
{t('View details')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/react-helpers",
|
||||
"version": "0.2.5",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
"version": "0.2.6"
|
||||
}
|
||||
|
||||
@@ -9,12 +9,17 @@
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"outputPath": "dist/libs/react-helpers",
|
||||
"tsConfig": "libs/react-helpers/tsconfig.lib.json",
|
||||
"project": "libs/react-helpers/package.json",
|
||||
"entryFile": "libs/react-helpers/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"external": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"@vegaprotocol/tailwindcss-config",
|
||||
"@vegaprotocol/utils"
|
||||
],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"format": ["esm", "cjs"],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/tailwindcss-config",
|
||||
"version": "0.0.4"
|
||||
"version": "0.0.5"
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"outputPath": "dist/libs/tailwindcss-config",
|
||||
"main": "libs/tailwindcss-config/src/index.js",
|
||||
"tsConfig": "libs/tailwindcss-config/tsconfig.lib.json",
|
||||
"assets": ["libs/tailwindcss-config/*.md"],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies"
|
||||
"assets": ["libs/tailwindcss-config/*.md"]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/types",
|
||||
"version": "0.0.5"
|
||||
"version": "0.0.6"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"tsConfig": "libs/types/tsconfig.lib.json",
|
||||
"project": "libs/types/package.json",
|
||||
"entryFile": "libs/types/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"format": ["esm", "cjs"],
|
||||
@@ -23,8 +22,7 @@
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/ui-toolkit",
|
||||
"version": "0.12.8",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
"version": "0.12.9"
|
||||
}
|
||||
|
||||
@@ -9,12 +9,19 @@
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"outputPath": "dist/libs/ui-toolkit",
|
||||
"tsConfig": "libs/ui-toolkit/tsconfig.lib.json",
|
||||
"project": "libs/ui-toolkit/package.json",
|
||||
"entryFile": "libs/ui-toolkit/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"external": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"@vegaprotocol/i18n",
|
||||
"@vegaprotocol/react-helpers",
|
||||
"@vegaprotocol/tailwindcss-config",
|
||||
"@vegaprotocol/utils"
|
||||
],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"format": ["esm", "cjs"],
|
||||
|
||||
@@ -7,7 +7,7 @@ export type { IconName } from '@blueprintjs/icons';
|
||||
export interface IconProps {
|
||||
name: IconName;
|
||||
className?: string;
|
||||
size?: 2 | 3 | 4 | 6 | 8 | 10 | 12 | 14 | 16;
|
||||
size?: 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 14 | 16;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export const Icon = ({ size = 4, name, className, ariaLabel }: IconProps) => {
|
||||
'w-2 h-2': size === 2,
|
||||
'w-3 h-3': size === 3,
|
||||
'w-4 h-4': size === 4,
|
||||
'w-5 h-5': size === 5,
|
||||
'w-6 h-6': size === 6,
|
||||
'w-8 h-8': size === 8,
|
||||
'w-10 h-10': size === 10,
|
||||
|
||||
@@ -132,6 +132,7 @@ export const TradingAnchorButton = forwardRef<
|
||||
children,
|
||||
className,
|
||||
subLabel,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
@@ -139,6 +140,7 @@ export const TradingAnchorButton = forwardRef<
|
||||
ref={ref}
|
||||
href={href}
|
||||
className={getClassName({ size, subLabel, intent }, className)}
|
||||
{...props}
|
||||
>
|
||||
<Content icon={icon} subLabel={subLabel} children={children} />
|
||||
</a>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Ref, SelectHTMLAttributes } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon, VegaIcon, VegaIconNames } from '..';
|
||||
import { VegaIcon, VegaIconNames } from '..';
|
||||
import { defaultSelectElement } from '../../utils/shared';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
|
||||
@@ -65,8 +65,8 @@ export const TradingRichSelect = forwardRef<
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<SelectPrimitive.Value placeholder={placeholder} />
|
||||
<SelectPrimitive.Icon className={classNames('absolute right-4')}>
|
||||
<Icon name="chevron-down" />
|
||||
<SelectPrimitive.Icon className={classNames('absolute right-2')}>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
<SelectPrimitive.Portal container={containerRef.current}>
|
||||
@@ -85,11 +85,11 @@ export const TradingRichSelect = forwardRef<
|
||||
align={'center'}
|
||||
>
|
||||
<SelectPrimitive.ScrollUpButton className="absolute z-20 flex items-center justify-center w-full h-6 py-1 bg-gradient-to-t from-transparent to-neutral-50 dark:to-neutral-900">
|
||||
<Icon name="chevron-up" />
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_UP} />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
<SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>
|
||||
<SelectPrimitive.ScrollDownButton className="absolute bottom-0 z-20 flex items-center justify-center w-full h-6 py-1 bg-gradient-to-b from-transparent to-neutral-50 dark:to-neutral-900">
|
||||
<Icon name="chevron-down" />
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
@@ -122,7 +122,7 @@ export const TradingOption = forwardRef<
|
||||
>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
<SelectPrimitive.ItemIndicator className="absolute right-4 top-[50%] translate-y-[-50%]">
|
||||
<Icon name="tick" />
|
||||
<VegaIcon name={VegaIconNames.TICK} />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@vegaprotocol/utils",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.10",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/wallet",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
"version": "0.0.2"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,17 @@
|
||||
"tsConfig": "libs/wallet/tsconfig.lib.json",
|
||||
"project": "libs/wallet/package.json",
|
||||
"entryFile": "libs/wallet/src/index.ts",
|
||||
"external": ["react", "react-dom", "react/jsx-runtime"],
|
||||
"external": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"@vegaprotocol/i18n",
|
||||
"@vegaprotocol/react-helpers",
|
||||
"@vegaprotocol/types",
|
||||
"@vegaprotocol/ui-toolkit",
|
||||
"@vegaprotocol/utils",
|
||||
"@vegaprotocol/wallet-client"
|
||||
],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"format": ["esm", "cjs"],
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
- **Must** be able to click on current node to open node switcher dialog (<a name="0006-NETW-012" href="#0006-NETW-012">0006-NETW-012</a>)
|
||||
- In the node dialog
|
||||
|
||||
- **Must** must see all nodes provided by the [network config](https://github.com/vegaprotocol/networks) (<a name="0006-NETW-013" href="#0006-NETW-013">0006-NETW-013</a>)
|
||||
- For each node
|
||||
- **Must** see the response time of the node (<a name="0006-NETW-014" href="#0006-NETW-014">0006-NETW-014</a>)
|
||||
@@ -34,3 +35,9 @@
|
||||
- **Must** be able to select 'other' to input a node address and connect to it (<a name="0006-NETW-018" href="#0006-NETW-018">0006-NETW-018</a>)
|
||||
- **Must** have disabled connect button if 'other' is selected but no url has been entered (<a name="0006-NETW-019" href="#0006-NETW-019">0006-NETW-019</a>)
|
||||
- **Must** have disabled connect button if selected node is the current node (<a name="0006-NETW-020" href="#0006-NETW-020">0006-NETW-020</a>)
|
||||
|
||||
## Block Explorer
|
||||
|
||||
- **Must** show all possible network parameters (<a name="0006-NETW-021" href="#0006-NETW-021">0006-NETW-021</a>)
|
||||
- **Must** I can see network parameters grouped by type (<a name="0006-NETW-022" href="#0006-NETW-022">0006-NETW-022</a>)
|
||||
- **Must** Network parameter headings act as linkable anchors i.e. a third party website can link directly to a "section" of the network parameter page (<a name="0006-NETW-023" href="#0006-NETW-023">0006-NETW-022</a>)
|
||||
|
||||
@@ -162,6 +162,8 @@ When looking to vote on the proposal, I...
|
||||
- **must** trigger a transaction that needs to be confirmed in users wallet (<a name="3001-VOTE-013" href="#3001-VOTE-013">3001-VOTE-013</a>)
|
||||
- **must** see that I need to confirm the transaction in my wallet to continue (<a name="3001-VOTE-014" href="#3001-VOTE-014">3001-VOTE-014</a>)
|
||||
- **must** see option to change my vote (vote again in same or different direction) (<a name="3001-VOTE-090" href="#3001-VOTE-090">3001-VOTE-090</a>)
|
||||
- **must** see two vote status bars, one showing majority voting yes/no, and the other showing participation progress. The maximum value for the progress bar should be the threshold for that proposal type. (<a name="3001-VOTE-023" href="#3001-VOTE-023">3001-VOTE-023</a>)
|
||||
- **must** see when the participation bar reaches 100% (i.e. the network param threshold is met) the indicator text on top of it reads "[network param]% participation threshold met". Otherwise it says "[network param]% participation threshold not met". (<a name="3001-VOTE-024" href="#3001-VOTE-024">3001-VOTE-024</a>)
|
||||
|
||||
For open market change proposals, all of the above and:
|
||||
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Run on Github by commits to master, this script:
|
||||
* 1. Gets all projects with a fleek configuration file
|
||||
* 2. Gets the last commit for the relevant site id
|
||||
* 3. Runs nx:affected for that commit ID and checks if the site has changed
|
||||
* 4. Calls deploy if it has
|
||||
*
|
||||
* This would probably be best as an NX task, but the circular nature of getting
|
||||
* the fleek ID for the project, then checking if it was affected didn't fit within the
|
||||
* build-only-affected cycle, and as each fleek deploy will have been triggered by
|
||||
* a different commit, it seemed best to do this outwith nx. Feel free to re-implement
|
||||
* this if the assumptions are wrong.
|
||||
*
|
||||
* It has also been written to skip external dependencies.
|
||||
*/
|
||||
const { existsSync, readdirSync, readFileSync } = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
/**
|
||||
* Parses the last commit hash out of the Fleek API response
|
||||
* @param {String} siteId
|
||||
* @returns string Last commit that triggered a build
|
||||
*/
|
||||
function getFleekLastBuildCommit(siteId) {
|
||||
const curl = `curl 'https://api.fleek.co/graphql' --silent -X POST -H 'Accept: */*' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Authorization: ${process.env['FLEEK_API_KEY']}' --data-raw '{"query":"{getSiteById(siteId: \\"${siteId}\\"){publishedDeploy{repository{commit}}}}","variables":null}'`;
|
||||
const fleekRes = execSync(curl);
|
||||
|
||||
const res = JSON.parse(fleekRes.toString());
|
||||
let commit = res.data.getSiteById.publishedDeploy.repository.commit;
|
||||
|
||||
return commit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a Fleek build of the latest code via GraphQL
|
||||
* @param {String} siteId
|
||||
* @returns
|
||||
*/
|
||||
function triggerDeploy(siteId) {
|
||||
const curl = `curl 'https://api.fleek.co/graphql' --silent -X POST -H 'Accept: */*' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Authorization: ${process.env['FLEEK_API_KEY']}' --data-raw '{"query":"mutation {triggerDeploy(commit: \\"HEAD\\", siteId: \\"${siteId}\\"){status}}","variables":null}'`;
|
||||
const fleekRes = execSync(curl);
|
||||
|
||||
JSON.parse(fleekRes.toString());
|
||||
|
||||
// Will have thrown if it failed
|
||||
return true;
|
||||
}
|
||||
|
||||
// The folder containing NX projects
|
||||
const projectPath = './apps/';
|
||||
// The Fleek project file, the existence indicates a deployed app
|
||||
const fleekFile = '.fleek.json';
|
||||
// Some simple stats for the end
|
||||
let fleekProjects = 0;
|
||||
let deployedProjects = 0;
|
||||
|
||||
// Fleek CLI requires this variable to be set
|
||||
if (!process.env['FLEEK_API_KEY']) {
|
||||
console.error('Error: FLEEK_API_KEY must be set');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
readdirSync(projectPath).forEach((proj) => {
|
||||
try {
|
||||
const config = `${projectPath}${proj}/${fleekFile}`;
|
||||
if (!existsSync(config)) {
|
||||
// No fleek file, skip it
|
||||
return;
|
||||
}
|
||||
|
||||
fleekProjects++;
|
||||
|
||||
console.group(proj);
|
||||
|
||||
// The UID for the site according to the config
|
||||
let siteId;
|
||||
|
||||
try {
|
||||
const fleekConfig = JSON.parse(readFileSync(config));
|
||||
siteId = fleekConfig.site.id;
|
||||
|
||||
console.log(`Fleek site ID: ${siteId}`);
|
||||
} catch (e) {
|
||||
console.error(`Failed to read Fleek site id for ${proj}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// The last commit that triggered a build
|
||||
let baseCommit;
|
||||
|
||||
try {
|
||||
baseCommit = getFleekLastBuildCommit(siteId);
|
||||
|
||||
console.log(`Last deploy: ${baseCommit}`);
|
||||
} catch (e) {
|
||||
console.error(`Failed to fetch last deploy for ${proj}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now run nx affected
|
||||
let isAffected;
|
||||
|
||||
try {
|
||||
const affectedSinceCommit = execSync(
|
||||
`yarn nx print-affected --base=${baseCommit} --head=HEAD --select=projects`
|
||||
);
|
||||
|
||||
// Detect if this project name is in output, taking care not to match names that are
|
||||
// included in other projects - `trading`, `trading-e2e` is a current example
|
||||
isAffected = affectedSinceCommit
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.includes(proj);
|
||||
} catch (e) {
|
||||
console.error(`Failed to run nx:affected for ${baseCommit}:master`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAffected) {
|
||||
console.log(`Triggering deploy for: ${siteId}`);
|
||||
deployedProjects++;
|
||||
|
||||
try {
|
||||
triggerDeploy(siteId);
|
||||
} catch (e) {
|
||||
console.error(`Failed to trigger deploy for ${proj}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log(`Has not changed since last build, skipping...`);
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Fleek projects: ${fleekProjects}`);
|
||||
console.log(`Deploys triggered: ${deployedProjects}`);
|
||||
|
||||
process.exit(0);
|
||||
Reference in New Issue
Block a user