Compare commits

..
242 changed files with 2453 additions and 3205 deletions
+42 -97
View File
@@ -21,6 +21,7 @@ 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
@@ -38,6 +39,7 @@ 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
@@ -49,11 +51,11 @@ jobs:
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
lint-format:
timeout-minutes: 20
lint-test-build:
timeout-minutes: 60
needs: node-modules
runs-on: ubuntu-22.04
name: '(CI) lint + format check'
name: '(CI) lint + unit test + build'
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -79,76 +81,6 @@ 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: |
@@ -165,8 +97,21 @@ 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 }}
@@ -176,38 +121,38 @@ jobs:
preview_tools: ${{ env.PREVIEW_TOOLS }}
console-e2e:
needs: build-sources
needs: lint-test-build
name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml
secrets: inherit
if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
with:
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
cypress:
needs: build-sources
needs: lint-test-build
name: '(CI) cypress'
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }}
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.build-sources.outputs.projects-e2e }}
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
tags: '@smoke'
publish-dist:
needs: build-sources
needs: lint-test-build
name: '(CD) publish dist'
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }}
uses: ./.github/workflows/publish-dist.yml
secrets: inherit
with:
projects: ${{ needs.build-sources.outputs.projects }}
projects: ${{ needs.lint-test-build.outputs.projects }}
dist-check:
runs-on: ubuntu-latest
needs:
- publish-dist
- build-sources
- lint-test-build
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'
@@ -223,27 +168,27 @@ jobs:
run: |
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
if [[ "${{ needs.build-sources.outputs.preview_governance }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_governance }}"; do
echo "waiting for governance preview: ${{ needs.build-sources.outputs.preview_governance }}"
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 }}"
sleep 5
done
fi
if [[ "${{ needs.build-sources.outputs.preview_explorer }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_explorer }}"; do
echo "waiting for explorer preview: ${{ needs.build-sources.outputs.preview_explorer }}"
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 }}"
sleep 5
done
fi
if [[ "${{ needs.build-sources.outputs.preview_trading }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_trading }}"; do
echo "waiting for trading preview: ${{ needs.build-sources.outputs.preview_trading }}"
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 }}"
sleep 5
done
fi
if [[ "${{ needs.build-sources.outputs.preview_tools }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_tools }}"; do
echo "waiting for tools preview: ${{ needs.build-sources.outputs.preview_tools }}"
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 }}"
sleep 5
done
fi
@@ -255,10 +200,10 @@ jobs:
issue-number: ${{ github.event.pull_request.number }}
body: |
Previews
* governance: ${{ needs.build-sources.outputs.preview_governance }}
* explorer: ${{ needs.build-sources.outputs.preview_explorer }}
* trading: ${{ needs.build-sources.outputs.preview_trading }}
* tools: ${{ needs.build-sources.outputs.preview_tools }}
* 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 }}
# Report single result at the end, to avoid mess with required checks in PR
cypress-check:
+37 -2
View File
@@ -10,7 +10,7 @@ on:
jobs:
run-tests:
name: run-tests
runs-on: console-test
runs-on: 8-cores
timeout-minutes: 20
steps:
#----------------------------------------------
@@ -33,6 +33,15 @@ 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
@@ -67,6 +76,32 @@ 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
@@ -90,7 +125,7 @@ jobs:
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
+1 -3
View File
@@ -37,7 +37,6 @@ jobs:
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
id: cache-node-modules
uses: actions/cache@v3
with:
path: |
@@ -47,7 +46,6 @@ jobs:
# Install frontend dependencies
- name: Install root dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
@@ -69,7 +67,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 }}
+3
View File
@@ -3,3 +3,6 @@
# Lint commit messages to ensure they follow conventional commit standards
yarn commitlint --edit "${1}"
# Lint all staged files
yarn lint-staged
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint all staged files
yarn lint-staged
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint all staged files
yarn nx format:check
# Test all projects with changes
yarn nx affected -t test --exclude trading
+1 -1
View File
@@ -1 +1 @@
16.20.2
16.15.1
+1 -1
View File
@@ -1,2 +1,2 @@
* @vegaprotocol/frontend @vegaprotocol/frontend-qa
* @vegaprotocol/frontend
*.graphql @vegaprotocol/core
+1 -1
View File
@@ -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 -rQ /usr/share/nginx/html
docker run --rm ${TAG} ipfs add -r /usr/share/nginx/html
.PHONY: eject-ipfs-hash
unpack:
+24 -24
View File
@@ -115,31 +115,11 @@ docker run -p 3000:80 [TAG]
## Build instructions
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of Console either for the web, or ready for pinning on IPFS.
### nx build inside the docker
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
At the moment this feature is important only for Console releases.
Each docker build finishes with hash calculation for ` dist`` directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
```bash
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
```
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of console either for the web, or ready for pinning on IPFS
### nx build outside the docker
This Docker image packages a pre-built `dist` folder into an [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image. In this case, the application on docker host machine from source.
Packaging prepared dist into [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image involves building the application on docker host machine from source.
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
@@ -153,18 +133,38 @@ 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 running: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
1. Show latest release by runnning: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
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
8. Now `dist` directory contains valid application build. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
## Config
@@ -1,9 +1,12 @@
context('Network parameters page', { tags: '@smoke' }, function () {
before('navigate to network parameter page', function () {
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
cy.visit('/network-parameters');
});
describe('Verify elements on page', function () {
beforeEach(() => {
cy.visit('/network-parameters');
});
const networkParametersHeader = '[data-testid="network-param-header"]';
const tableRows = '[data-testid="key-value-table-row"]';
@@ -13,7 +16,6 @@ context('Network parameters page', { tags: '@smoke' }, function () {
.and('be.visible');
});
// 0006-NETW-021
it('should list each of the network parameters available', function () {
cy.get_network_parameters().then((network_parameters) => {
const numberOfNetworkParametersInSystem =
@@ -196,19 +198,6 @@ context('Network parameters page', { tags: '@smoke' }, function () {
});
});
// 0006-NETW-022 0006-NETW-023
it('governance assets should be correctly grouped', function () {
cy.getByTestId('governance')
.should('exist')
.parent()
.should('have.attr', 'href', '/network-parameters#governance');
cy.get('[id="governance-proposal-asset"]')
.parent()
.within(() => {
cy.getByTestId('key-value-table-row').should('have.length', 8);
});
});
it('should be able to see network parameters - on mobile', function () {
cy.switchToMobile();
cy.get_network_parameters().then((network_parameters) => {
@@ -1,68 +0,0 @@
context('Oracle page', { tags: '@smoke' }, () => {
describe('Verify elements on page', () => {
before('create market and navigate to oracle page', () => {
cy.createMarket();
cy.visit('/oracles');
});
it('should see oracle data', () => {
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
cy.getByTestId('oracle-details')
.should('exist')
.eq(0)
.within(() => {
cy.get('tr')
.eq(0)
.within(() => {
cy.get('th').should('have.text', 'ID');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/oracles/');
});
cy.get('tr')
.eq(1)
.within(() => {
cy.get('th').should('have.text', 'Type');
cy.get('td').should('have.text', 'External data');
});
cy.get('tr')
.eq(2)
.within(() => {
cy.get('th').should('have.text', 'Signer');
cy.getByTestId('keytype').should('have.text', 'Vega');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/parties/');
});
cy.get('tr')
.eq(3)
.within(() => {
cy.get('th').should('have.text', 'Settlement for');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/markets/');
});
cy.get('tr')
.eq(4)
.within(() => {
cy.get('th').should('have.text', 'Matched data');
cy.get('td').should('have.text', '❌');
});
cy.get('details')
.eq(0)
.within(() => {
cy.contains('Filter').click();
cy.get('.language-json').should('exist');
});
cy.get('details')
.eq(1)
.within(() => {
cy.contains('JSON').click();
cy.get('.language-json').should('exist');
});
});
});
});
});
@@ -1,2 +1 @@
export * from './network-parameters';
export * from './structure-network-params';
@@ -1,105 +1,82 @@
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 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',
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',
},
},
],
},
{ 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' },
]);
};
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');
});
it('renders network params correctly', () => {
renderComponent(mockData);
const delegationMinTokensRow = screen.getByTestId(
'spam-protection-delegation-min-tokens'
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'
);
expect(delegationMinTokensRow).toHaveTextContent('0.000000000000000003');
const votingMinTokensRow = screen.getByTestId(
'spam-protection-voting-min-tokens'
const rows = screen.getAllByTestId('key-value-table-row');
expect(rows[0].children[0]).toHaveTextContent(
'market.fee.factors.infrastructureFee'
);
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[1].children[0]).toHaveTextContent(
'market.liquidityProvision.minLpStakeQuantumMultiple'
);
expect(rows[0].children[1]).toHaveTextContent('0.0005');
expect(rows[1].children[1]).toHaveTextContent('1');
});
it('does not render rows when is loading', () => {
renderComponent(undefined);
render(<NetworkParametersTable data={undefined} loading={true} />);
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
'Network Parameters'
);
@@ -1,8 +1,6 @@
import startCase from 'lodash/startCase';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import {
AsyncRenderer,
KeyValueTable,
KeyValueTableRow,
SyntaxHighlighter,
} from '@vegaprotocol/ui-toolkit';
@@ -14,12 +12,11 @@ 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',
@@ -61,54 +58,6 @@ 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 },
}: {
@@ -128,9 +77,7 @@ export const NetworkParameterRow = ({
>
{key}
{isSyntaxRow ? (
<div className="pb-2">
<SyntaxHighlighter data={JSON.parse(value)} />
</div>
<SyntaxHighlighter data={JSON.parse(value)} />
) : isNaN(Number(value)) ? (
value
) : BIG_NUMBER_PARAMS.includes(key) ? (
@@ -166,12 +113,17 @@ export const NetworkParametersTable = ({
loading={loading}
error={error}
render={(data) => {
const flatParams = removePaginationWrapper(
data.networkParametersConnection.edges
const ascParams = orderBy(
removePaginationWrapper(data.networkParametersConnection.edges),
(param) => param.key,
'asc'
);
const groupedParams = structureNetworkParams(flatParams);
return (
<div className="-mt-6">{renderGroupedParams(groupedParams, 1)}</div>
<KeyValueTable data-testid="parameters">
{(ascParams || []).map((row) => (
<NetworkParameterRow key={row.key} row={row} />
))}
</KeyValueTable>
);
}}
/>
@@ -1,109 +0,0 @@
import type { GroupedParams } from './structure-network-params';
import {
structureParams,
sortGroupedParams,
structureNetworkParams,
} from './structure-network-params';
describe('structureParams', () => {
it('should correctly structure params', () => {
const input = [
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
{ key: 'spam.protection.voting.min.tokens', value: '5' },
];
const output: GroupedParams = {
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
voting: {
min: {
tokens: '5',
},
},
},
},
};
expect(structureParams(input)).toEqual(output);
});
it('should handle top-level keys correctly', () => {
const input = [{ key: 'levelOne', value: '10' }];
const output = {
levelOne: '10',
};
expect(structureParams(input)).toEqual(output);
});
});
describe('sortGroupedParams', () => {
it('should correctly sort grouped params', () => {
const input: GroupedParams = {
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
},
},
reward: '50',
};
const output: GroupedParams = {
reward: '50',
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
},
},
};
expect(sortGroupedParams(input)).toEqual(output);
});
it('should handle already sorted keys', () => {
const input = {
a: '10',
b: {
c: '5',
d: '6',
},
};
expect(sortGroupedParams(input)).toEqual(input);
});
});
describe('structureNetworkParams', () => {
it('should structure and sort network params correctly', () => {
const input = [
{ key: 'spam.protection.delegation.min.tokens', value: '10' },
{ key: 'reward.asset', value: '50' },
];
const output: GroupedParams = {
reward: {
asset: '50',
},
spam: {
protection: {
delegation: {
min: {
tokens: '10',
},
},
},
},
};
expect(structureNetworkParams(input)).toEqual(output);
});
it('should return an empty object if no params are provided', () => {
expect(structureNetworkParams([])).toEqual({});
});
});
@@ -1,107 +0,0 @@
/**
* Categorizes and sorts an array of key-value pairs of network params into a nested object structure.
*
* The function takes an array of network params where keys are dot-delimited
* strings representing nested categories (e.g., 'spam.protection.delegation.min.tokens').
*
* Why this is necessary:
* A flat key-value structure wouldn't provide the hierarchical information needed.
* Organizing network parameters like this allows the rendering of nested headers
* and their corresponding network params.
*
* It also ensures that items with the minimum amount of nesting are ordered first. This
* allows us to render these items first, before more deeply nested items are rendered with
* subheaders. This creates a more intuitive UI.
*
* For example, given the input:
* [
* { key: 'spam.protection.delegation.min.tokens', value: '10' },
* { key: 'spam.protection.voting.min.tokens', value: '5' },
* { key: 'reward.staking.delegation.minimumValidatorStake', value: '2' }
* { key: 'reward.asset', value: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55' }
* ]
*
* The output will be:
* {
* spam: {
* protection: {
* delegation: {
* min: {
* tokens: '10'
* }
* },
* voting: {
* min: {
* tokens: '5'
* }
* }
* }
* },
* reward: {
* asset: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
* staking: {
* delegation: {
* minimumValidatorStake: '2'
* }
* }
* }
* }
*
* @param {Array} params - An array of key-value pairs to categorize and sort.
* @returns {GroupedParams} - A nested object that groups the key-value pairs.
*/
export type GroupedParams = {
[key: string]: string | GroupedParams;
};
export const structureParams = (
params: { key: string; value: string }[]
): GroupedParams => {
const grouped: GroupedParams = {};
params.forEach(({ key, value }) => {
const parts = key.split('.');
let node: GroupedParams = grouped;
parts.forEach((part, i) => {
if (typeof node[part] === 'undefined') {
node[part] = i === parts.length - 1 ? value : {};
}
if (typeof node[part] === 'object') {
node = node[part] as GroupedParams;
}
});
});
return grouped;
};
export const sortGroupedParams = (
groupedParams: GroupedParams
): GroupedParams => {
const sorted: GroupedParams = {};
// Sort top-level keys first
Object.entries(groupedParams).forEach(([key, value]) => {
if (typeof value === 'string') {
sorted[key] = value;
}
});
Object.entries(groupedParams).forEach(([key, value]) => {
if (typeof value === 'object') {
sorted[key] = sortGroupedParams(value);
}
});
return sorted;
};
export const structureNetworkParams = (
params: { key: string; value: string }[]
) => {
const grouped = structureParams(params);
return sortGroupedParams(grouped);
};
@@ -38,12 +38,7 @@ const Oracles = () => {
const dataConnection = o?.node.dataConnection;
return (
<div
id={id}
key={id}
className="mb-10"
data-testid="oracle-details"
>
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id}
dataSource={o?.node}
@@ -34,20 +34,19 @@ import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
const proposalListItem = '[data-testid="proposals-list-item"]';
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 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 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';
@@ -151,32 +150,29 @@ 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.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.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
'be.visible'
);
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
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
cy.getByTestId(numberOfVotesAgainst).should('have.text', '0.0M');
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
getProposalInformationFromTable('Token participation met')
.contains('👎')
.should('be.visible');
});
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
@@ -201,16 +197,43 @@ describe(
.contains(votedDate)
.should('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(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(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(() => {
@@ -221,7 +244,16 @@ describe(
});
cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('against');
cy.getByTestId(votesAgainstPercentage).should('have.text', '100%');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('100.00%')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Tokens against proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
getProposalInformationFromTable('Number of voting parties')
.should('have.text', '1')
.and('be.visible');
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
@@ -233,7 +265,6 @@ 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) => {
@@ -244,28 +275,69 @@ describe(
voteForProposal('for');
// 3001-VOTE-079
cy.contains('You voted: For').should('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%)');
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');
});
});
it('Able to vote for proposal twice by switching public key', function () {
@@ -288,6 +360,10 @@ 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,10 +22,12 @@ const proposalListItem = '[data-testid="proposals-list-item"]';
const closedProposals = 'closed-proposals';
const proposalStatus = 'proposal-status';
const viewProposalButton = 'view-proposal-btn';
const votesTable = 'user-vote';
const votesTable = 'votes-table';
const openProposals = 'open-proposals';
const majorityVoteReached = 'token-majority-met';
const minParticipationReached = 'token-participation-met';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
const majorityVoteReached = 'majority-reached';
const minParticipationReached = 'participation-reached';
const proposalTimeout = { timeout: 8000 };
context(
@@ -68,6 +70,7 @@ 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');
});
});
@@ -89,7 +92,7 @@ context(
// 3001-VOTE-019 time to vote is highlighted red
cy.getByTestId('vote-details')
.find('span')
.should('have.class', 'text-vega-orange');
.should('have.class', 'text-vega-pink');
cy.getByTestId(viewProposalButton).click();
});
});
@@ -106,9 +109,12 @@ context(
);
});
cy.getByTestId(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
cy.getByTestId('votes-for-percentage').should('have.text', '100%');
cy.getByTestId(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
navigateTo(navigation.proposals);
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
@@ -298,6 +298,9 @@ 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,6 +52,8 @@ 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');
@@ -419,28 +421,27 @@ context(
cy.getByTestId(viewProposalBtn).click();
});
});
cy.getByTestId('lp-majority-not-met').should(
'have.text',
'66% majority threshold not met'
cy.getByTestId(liquidityVoteStatus).should(
'contain.text',
'Currently expected to fail'
);
cy.getByTestId('token-majority-not-met').should(
'have.text',
'66% majority threshold not met'
cy.getByTestId(tokenVoteStatus).should(
'contain.text',
'Currently expected to fail'
);
voteForProposal('for');
cy.getByTestId('lp-majority-met').should(
'have.text',
'66% majority threshold met'
cy.getByTestId(liquidityVoteStatus).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(tokenVoteStatus).should(
'contain.text',
'Currently expected to pass'
);
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,6 +11,7 @@ import {
enterRawProposalBody,
generateFreeFormProposalTitle,
getProposalFromTitle,
getProposalInformationFromTable,
goToMakeNewProposal,
governanceProposalType,
submitUniqueRawProposal,
@@ -29,9 +30,7 @@ const proposalType = 'proposal-type';
const proposalStatus = 'proposal-status';
const proposalClosingDate = 'vote-details';
const viewProposalButton = 'view-proposal-btn';
const voteMajorityNotMet = 'token-majority-not-met';
const voteMajorityMet = 'token-majority-met';
const votesForPercentage = 'votes-for-percentage';
const voteBreakDownToggle = 'vote-breakdown-toggle';
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
before('connect wallets and set approval limit', function () {
@@ -122,15 +121,10 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
submitUniqueRawProposal({ proposalTitle: proposalTitle });
getProposalFromTitle(proposalTitle).within(() => {
// 3001-VOTE-039
cy.getByTestId(voteMajorityNotMet).should(
cy.getByTestId('participation-not-reached').should(
'have.text',
'66% majority threshold not met'
'Min. participation not reached'
);
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');
@@ -140,15 +134,16 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
'have.text',
'Currently expected to pass'
);
cy.getByTestId(voteMajorityMet).should(
cy.getByTestId('user-voted-yes').should('exist');
cy.getByTestId('participation-reached').should(
'have.text',
'66% majority threshold met'
);
cy.getByTestId(votesForPercentage).should('have.text', '100%');
cy.getByTestId('token-participation-met').should(
'have.text',
'0.000000000000000000000015% participation threshold met'
'Min. participation reached'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(voteBreakDownToggle).click();
getProposalInformationFromTable('Token participation met')
.contains('👍')
.should('be.visible');
});
});
@@ -37,39 +37,11 @@ context(
});
// 3002-PROP-023 3004-PMAC-002 3005-PASN-002 3006-PASC-002 3007-PNEC-002 3008-PFRO-003
it('new proposal page should have button for link to more information on proposals', function () {
cy.getByTestId('new-proposal-link').click();
cy.url().should('include', '/proposals/propose/raw');
cy.contains('To see Explorer data on proposals visit').within(() => {
cy.getByTestId('external-link').should(
'have.attr',
'href',
'https://explorer.fairground.wtf/governance'
);
});
cy.contains(
'1. Sense check your proposal with the community on the forum:'
).within(() => {
cy.getByTestId('external-link').should(
'have.attr',
'href',
'https://community.vega.xyz/c/governance/25'
);
});
cy.contains(
'2. Use the appropriate proposal template in the docs:'
).within(() => {
cy.getByTestId('external-link').should(
'have.attr',
'href',
'https://docs.vega.xyz/mainnet/tutorials/proposals'
);
});
cy.contains('Connect your wallet to submit a proposal').should(
'be.visible'
);
cy.getByTestId('connect-to-vega-wallet-btn').should('exist');
navigateTo(navigation.proposals);
it('should have button for link to more information on proposals', function () {
const proposalsUrl = 'https://docs.vega.xyz/mainnet/tutorials/proposals';
cy.getByTestId('new-proposal-link')
.find('a')
.should('have.attr', 'href', proposalsUrl);
});
it('should be able to see a working link for - find out more about Vega governance', function () {
@@ -211,7 +183,7 @@ context(
.first()
.find('[data-testid="view-proposal-btn"]')
.click();
cy.url().should('contain', '/protocol-upgrades/v1/2015942');
cy.url().should('contain', '/protocol-upgrades/v1');
cy.getByTestId('protocol-upgrade-proposal').within(() => {
cy.get('h1').should('have.text', 'Vega Release v1');
cy.getByTestId('protocol-upgrade-block-height').should(
@@ -271,7 +243,7 @@ context(
);
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contain', '/proposals/protocol-upgrade/v1/2015942');
.and('contain', '/proposals/protocol-upgrade/v1');
});
// 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.get(voteButtons).should('be.visible', { timeout: 10000 });
cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
cy.get(voteButtons).contains(vote).click();
cy.get(dialogTitle, proposalTimeout).should(
'have.text',
@@ -219,9 +219,7 @@ export function ensureSpecifiedUnstakedTokensAreAssociated(
.eq(1)
.invoke('text')
.then((unstakedBalance) => {
const tokenFloat = parseFloat(tokenAmount);
const unstakedFloat = parseFloat(unstakedBalance.replace(/,/g, ''));
if (tokenFloat != unstakedFloat) {
if (parseFloat(unstakedBalance) != parseFloat(tokenAmount)) {
vegaWalletTeardown();
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
'0.00',
+2 -2
View File
@@ -184,7 +184,7 @@ const Web3Container = ({
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 border-t border-neutral-700 break-all">
<footer className="p-4 border-t border-neutral-700">
<NetworkInfo />
</footer>
</AppLayout>
@@ -308,7 +308,7 @@ const AppContainer = () => {
<Router>
<ScrollToTop />
<AppStateProvider>
<div className="min-h-full text-white">
<div className="min-h-full text-white grid">
<NodeGuard
skeleton={<div>{t('Loading')}</div>}
failure={
@@ -54,7 +54,7 @@ export const WalletCardRow = ({
}) => {
const ref = React.useRef<HTMLDivElement | null>(null);
useAnimateValue(ref, value);
const [integers, decimalsPlaces, separator] = useNumberParts(value, decimals);
const [integers, decimalsPlaces] = useNumberParts(value, decimals);
return (
<div
@@ -75,10 +75,7 @@ export const WalletCardRow = ({
className="font-mono flex-1 text-right"
data-testid="associated-amount"
>
<span>
{integers}
{separator}
</span>
<span>{integers}.</span>
<span>{decimalsPlaces}</span>
</span>
)}
@@ -113,10 +110,7 @@ export const WalletCardAsset = ({
border,
subheading,
}: WalletCardAssetProps) => {
const [integers, decimalsPlaces, separator] = useNumberParts(
balance,
decimals
);
const [integers, decimalsPlaces] = useNumberParts(balance, decimals);
return (
<div className="flex flex-nowrap mt-2 mb-4">
@@ -138,10 +132,7 @@ export const WalletCardAsset = ({
</div>
</div>
<div className="px-2 basis-full font-mono" data-testid="currency-value">
<span>
{integers}
{separator}
</span>
<span>{integers}.</span>
<span className="text-neutral-400">{decimalsPlaces}</span>
</div>
</div>
@@ -474,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 {{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 [rewards.marketCreationQuantumMultiple]",
"rewardsColTotalHeader": "TOTAL",
"ofTotalDistributed": "of total distributed",
"checkBackSoon": "Check back soon",
@@ -708,7 +708,7 @@
"NewAssetProposal": "New asset proposal",
"UpdateAssetProposal": "Update asset proposal",
"NewFreeformProposal": "New freeform proposal",
"NewRawProposal": "New proposal",
"NewRawProposal": "New raw proposal",
"MinProposalRequirements": "You must have at least {{value}} VEGA associated to make a proposal",
"MinProposalVoteRequirements": "You must have at least {{value}} VEGA associated to vote on this proposal",
"totalSupply": "Total Supply",
@@ -870,9 +870,5 @@
"Upgraded at": "Upgraded at",
"dataIsIdentical": "Data is identical",
"updatesToMarket": "Updates to market",
"viewAsParty": "View as party",
"HowToPropose": "How to make a proposal",
"HowToProposeRawStep1": "1. Sense check your proposal with the community on the forum:",
"HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:",
"HowToProposeRawStep3": "3. Submit on-chain below"
"viewAsParty": "View as party"
}
+3 -3
View File
@@ -279,8 +279,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
trimmedActiveNodes={trimmedActiveNodes}
/>
<section className="flex justify-between flex-wrap gap-12 mb-16">
<div className="min-w-[360px] flex-1" data-testid="home-rewards">
<section className="grid grid-cols-2 gap-12 mb-16">
<div 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">
@@ -290,7 +290,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
</div>
</div>
<div className="min-w-[360px] flex-1" data-testid="home-vega-token">
<div 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">
@@ -159,7 +159,7 @@ export const ProposalHeader = ({
</div>
</div>
<div data-testid="proposal-title" className="break-all">
<div data-testid="proposal-title">
{isListItem ? (
<header>
<SubHeading
@@ -7,13 +7,18 @@ import { ProposalsListItem } from '../proposals-list-item';
import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import Routes from '../../../routes';
import { Button, Toggle } from '@vegaprotocol/ui-toolkit';
import {
Button,
Toggle,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { ExternalLinks } from '@vegaprotocol/environment';
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
interface ProposalsListProps {
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
@@ -50,10 +55,7 @@ export const orderByUpgradeBlockHeight = (
) =>
orderBy(
arr,
[
(p) => (p?.upgradeBlockHeight ? parseInt(p.upgradeBlockHeight, 10) : 0),
(p) => p.vegaReleaseTag,
],
[(p) => p?.upgradeBlockHeight, (p) => p.vegaReleaseTag],
['desc', 'desc']
);
@@ -140,13 +142,18 @@ export const ProposalsList = ({
title={t('pageTitleProposals')}
/>
<div className="xs:justify-self-end" data-testid="new-proposal-link">
<Link to={`${Routes.PROPOSALS}/propose/raw`}>
<Button variant="primary" size="sm">
<div className="flex items-center gap-1">{t('NewProposal')}</div>
</Button>
</Link>
</div>
{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">
@@ -217,10 +224,10 @@ export const ProposalsList = ({
sortedProtocolUpgradeProposals.closed.length > 0 &&
filterString.length < 1 && (
<div
className="flex justify-end xl:-mt-12 pb-6"
className="grid w-full justify-end xl:-mt-12 pb-6"
data-testid="toggle-closed-proposals"
>
<div className="w-full max-w-[420px]">
<div className="w-[440px]">
<Toggle
name="closed-proposals-toggle"
toggles={[
@@ -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>
@@ -130,10 +130,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
? 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 sectionWrapperClasses = classNames('grid sm:grid-cols-2 gap-6');
const headingClasses = classNames('mb-2 text-vega-dark-400');
const progressDetailsClasses = classNames(
'flex justify-between flex-wrap mt-2 text-sm'
@@ -169,10 +166,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="mb-4">
<h3 className={headingClasses}>{t('liquidityProviderVote')}</h3>
<div className={sectionWrapperClasses}>
<section
className={sectionClasses}
data-testid="lp-majority-breakdown"
>
<section data-testid="lp-majority-breakdown">
<VoteProgress
percentageFor={yesLPPercentage}
colourfulBg={true}
@@ -247,10 +241,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
</div>
</section>
<section
className={sectionClasses}
data-testid="lp-participation-breakdown"
>
<section data-testid="lp-participation-breakdown">
<VoteProgress
percentageFor={
lpParticipationThresholdProgress || new BigNumber(0)
@@ -297,10 +288,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
{isUpdateMarket && <h3 className={headingClasses}>{t('tokenVote')}</h3>}
<div className={sectionWrapperClasses}>
<section
className={sectionClasses}
data-testid="token-majority-breakdown"
>
<section data-testid="token-majority-breakdown">
<VoteProgress
percentageFor={yesPercentage}
colourfulBg={true}
@@ -320,7 +308,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="flex items-center gap-1">
<span>{t('tokenVotesFor')}:</span>
<Tooltip description={formatNumber(yesTokens, defaultDP)}>
<button data-testid="num-votes-for">
<button>
{yesTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
</button>
</Tooltip>
@@ -329,9 +317,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<Tooltip
description={<span>{yesPercentage.toFixed(defaultDP)}%</span>}
>
<button data-testid="votes-for-percentage">
{yesPercentage.toFixed(0)}%
</button>
<button>{yesPercentage.toFixed(0)}%</button>
</Tooltip>
)
</span>
@@ -340,7 +326,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="flex items-center gap-1">
<span>{t('tokenVotesAgainst')}:</span>
<Tooltip description={formatNumber(noTokens, defaultDP)}>
<button data-testid="num-votes-against">
<button>
{noTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
</button>
</Tooltip>
@@ -349,9 +335,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<Tooltip
description={<span>{noPercentage.toFixed(defaultDP)}%</span>}
>
<button data-testid="votes-against-percentage">
{noPercentage.toFixed(0)}%
</button>
<button>{noPercentage.toFixed(0)}%</button>
</Tooltip>
)
</span>
@@ -359,10 +343,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
</div>
</section>
<section
className={sectionClasses}
data-testid="token-participation-breakdown"
>
<section data-testid="token-participation-breakdown">
<VoteProgress
percentageFor={participationThresholdProgress}
testId="token-participation-progress"
@@ -383,13 +364,11 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="flex items-center gap-1">
<span>{t('totalTokensVoted')}:</span>
<Tooltip description={formatNumber(totalTokensVoted, defaultDP)}>
<button data-testid="total-voted">
<button>
{totalTokensVoted.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
</button>
</Tooltip>
<span data-testid="total-voted-percentage">
({totalTokensPercentage.toFixed(defaultDP)}%)
</span>
<span>({totalTokensPercentage.toFixed(defaultDP)}%)</span>
</div>
</div>
</section>
@@ -1,17 +1,12 @@
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import {
useEnvironment,
DocsLinks,
ExternalLinks,
} from '@vegaprotocol/environment';
import { Heading, SubHeading } from '../../../../components/heading';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { Heading } from '../../../../components/heading';
import {
AsyncRenderer,
ExternalLink,
FormGroup,
InputError,
RoundedWrapper,
TextArea,
} from '@vegaprotocol/ui-toolkit';
import { validateJson } from '@vegaprotocol/utils';
@@ -84,6 +79,15 @@ export const ProposeRaw = () => {
spamProtectionMin={params.spam_protection_proposal_min_tokens}
/>
{DocsLinks && (
<p className="text-sm" data-testid="proposal-docs-link">
<span className="mr-1">{t('ProposalTermsText')}</span>
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank">
{DocsLinks.PROPOSALS_GUIDE}
</ExternalLink>
</p>
)}
{VEGA_EXPLORER_URL && (
<p className="text-sm">
{t('MoreProposalsInfo')}{' '}
@@ -94,44 +98,12 @@ export const ProposeRaw = () => {
</p>
)}
<section data-testid="how-to" className="my-10">
<RoundedWrapper paddingBottom={true}>
<SubHeading title={t('HowToPropose')} />
<ul>
<li className="p-1">
{t('HowToProposeRawStep1')}{' '}
{ExternalLinks && (
<span data-testid="proposal-docs-link">
<ExternalLink
href={ExternalLinks.PROPOSALS_FORUM}
target="_blank"
>
{ExternalLinks.PROPOSALS_FORUM}
</ExternalLink>
</span>
)}
</li>
<li className="p-1">
{t('HowToProposeRawStep2')}{' '}
{DocsLinks && (
<span data-testid="proposal-docs-link">
<ExternalLink
href={DocsLinks.PROPOSALS_GUIDE}
target="_blank"
>
{DocsLinks.PROPOSALS_GUIDE}
</ExternalLink>
</span>
)}
</li>
<li className="p-1">{t('HowToProposeRawStep3')}</li>
</ul>
</RoundedWrapper>
</section>
<div data-testid="raw-proposal-form">
<form onSubmit={handleSubmit(onSubmit)}>
<FormGroup label="Valid JSON required" labelFor="proposal-data">
<FormGroup
label="Make a proposal by submitting JSON"
labelFor="proposal-data"
>
<TextArea
id="proposal-data"
className="min-h-[200px]"
@@ -43,10 +43,7 @@ describe('EpochIndividualRewardsTable', () => {
it('should render correctly', () => {
const { getByTestId } = render(
<AppStateProvider>
<EpochIndividualRewardsTable
data={mockData}
marketCreationQuantumMultiple={'1000'}
/>
<EpochIndividualRewardsTable data={mockData} />
</AppStateProvider>
);
expect(getByTestId('epoch-individual-rewards-table')).toBeInTheDocument();
@@ -8,7 +8,6 @@ import type { EpochIndividualReward } from './generate-epoch-individual-rewards-
interface EpochIndividualRewardsGridProps {
data: EpochIndividualReward;
marketCreationQuantumMultiple: string | null;
}
interface RewardItemProps {
@@ -70,11 +69,9 @@ const RewardItem = ({
export const EpochIndividualRewardsTable = ({
data,
marketCreationQuantumMultiple,
}: EpochIndividualRewardsGridProps) => {
return (
<RewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
dataTestId="epoch-individual-rewards-table"
epoch={Number(data.epoch)}
>
@@ -9,7 +9,6 @@ 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;
@@ -27,9 +26,6 @@ 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,
@@ -107,7 +103,6 @@ export const EpochIndividualRewards = ({
{epochIndividualRewardSummaries.map(
(epochIndividualRewardSummary) => (
<EpochIndividualRewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
data={epochIndividualRewardSummary}
/>
)
@@ -66,10 +66,7 @@ describe('EpochTotalRewardsTable', () => {
it('should render correctly', () => {
const { getByTestId } = render(
<AppStateProvider>
<EpochTotalRewardsTable
data={mockData}
marketCreationQuantumMultiple={'1000'}
/>
<EpochTotalRewardsTable data={mockData} />
</AppStateProvider>
);
expect(getByTestId('epoch-total-rewards-table')).toBeInTheDocument();
@@ -8,7 +8,6 @@ import type { EpochTotalSummary } from './generate-epoch-total-rewards-list';
interface EpochTotalRewardsGridProps {
data: EpochTotalSummary;
marketCreationQuantumMultiple: string | null;
}
interface RewardItemProps {
@@ -53,14 +52,9 @@ const RewardItem = ({
export const EpochTotalRewardsTable = ({
data,
marketCreationQuantumMultiple,
}: EpochTotalRewardsGridProps) => {
return (
<RewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
dataTestId="epoch-total-rewards-table"
epoch={data.epoch}
>
<RewardsTable 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,7 +6,6 @@ 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;
@@ -19,9 +18,6 @@ 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,
@@ -74,11 +70,7 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
>
{Array.from(epochTotalRewardSummaries.values()).map(
(epochTotalSummary, index) => (
<EpochTotalRewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
data={epochTotalSummary}
key={index}
/>
<EpochTotalRewardsTable data={epochTotalSummary} key={index} />
)
)}
<Pagination
@@ -133,26 +133,24 @@ export const RewardsPage = () => {
</p>
</div>
<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 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>
</section>
@@ -77,11 +77,7 @@ const ColumnHeader = ({
</div>
);
const ColumnHeaders = ({
marketCreationQuantumMultiple,
}: {
marketCreationQuantumMultiple: string | null;
}) => {
const ColumnHeaders = () => {
const { t } = useTranslation();
return (
<div className="contents">
@@ -93,9 +89,7 @@ const ColumnHeaders = ({
<ColumnHeader
key={columnTitle}
title={t(columnTitle)}
tooltipContent={t(description, {
marketCreationQuantumMultiple,
})}
tooltipContent={t(description)}
className={headerGridItemStyles()}
/>
))}
@@ -111,7 +105,6 @@ export interface RewardTableProps {
dataTestId: string;
epoch: number;
children: ReactNode;
marketCreationQuantumMultiple: string | null;
}
// Rewards table children will be the row items. Make sure they contain
@@ -120,15 +113,12 @@ export const RewardsTable = ({
dataTestId,
epoch,
children,
marketCreationQuantumMultiple,
}: RewardTableProps) => (
<div data-testid={dataTestId} className="mb-12">
<SubHeading title={`EPOCH ${epoch}`} />
<div className={gridStyles}>
<ColumnHeaders
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
/>
<ColumnHeaders />
{children}
</div>
</div>
+1 -1
View File
@@ -279,7 +279,7 @@ const routerConfig = [
],
},
{
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag/:proposalBlockHeight`,
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag`,
element: <LazyProtocolUpgradeProposal />,
},
{
+3 -3
View File
@@ -100,8 +100,8 @@ const Home = ({ name }: RouteChildProps) => {
</Link>
</p>
</HomeSection>
<div className="flex justify-between flex-wrap gap-x-12 gap-y-4">
<div className="flex-1 min-w-[360px]">
<div className="flex gap-12">
<div className="flex-1">
<HomeSection>
<SubHeading title={t('Staking')} />
<p>
@@ -118,7 +118,7 @@ const Home = ({ name }: RouteChildProps) => {
</p>
</HomeSection>
</div>
<div className="flex-1 min-w-[360px]">
<div className="flex-1">
<HomeSection>
<SubHeading title={t('Governance')} />
<p>
-6
View File
@@ -6,12 +6,6 @@
@tailwind utilities;
@layer base {
*,
*::before,
*::after {
box-sizing: border-box;
}
h1 {
@apply text-2xl text-white uppercase mb-4;
}
@@ -0,0 +1,117 @@
const orderbookTab = 'Orderbook';
const orderbookTable = 'tab-orderbook';
const askPrice = 'price-9894185';
const bidPrice = 'price-9889001';
const askVolume = 'ask-vol-9894185';
const bidVolume = 'bid-vol-9889001';
const askCumulative = 'cumulative-vol-9894185';
const bidCumulative = 'cumulative-vol-9889001';
const midPrice = 'last-traded-4612690000';
const priceResolution = 'resolution';
const dealTicketPrice = 'order-price';
const dealTicketSize = 'order-size';
const resPrice = 'price-990';
describe('order book', { tags: '@smoke' }, () => {
before(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
beforeEach(() => {
cy.mockTradingPage();
});
it('show order book', () => {
// 6003-ORDB-001
// 6003-ORDB-002
cy.getByTestId(orderbookTab).click();
cy.getByTestId(orderbookTable).should('be.visible');
cy.getByTestId(orderbookTable).should('not.be.empty');
});
it('show orders prices', () => {
// 6003-ORDB-003
cy.getByTestId(askPrice).should('have.text', '98.94185');
cy.getByTestId(bidPrice).should('have.text', '98.89001');
});
it('show prices volumes', () => {
// 6003-ORDB-004
cy.getByTestId(askVolume).should('have.text', '1');
cy.getByTestId(bidVolume).should('have.text', '1');
});
it('show prices cumulative volumes', () => {
// 6003-ORDB-005
cy.getByTestId(askCumulative).should('have.text', '38');
cy.getByTestId(bidCumulative).should('have.text', '7');
});
it('show mid price', () => {
// 6003-ORDB-006
cy.getByTestId(midPrice).should('have.text', '46,126.90');
});
it('sort prices descending', () => {
// 6003-ORDB-007
const prices: number[] = [];
cy.getByTestId(orderbookTable).within(() => {
cy.get('[data-testid*=price]')
.each(($el) => {
prices.push(Number($el.text()));
})
.then(() => {
expect(prices).to.deep.equal(prices.sort((a, b) => b - a));
});
});
});
it('copy price to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(askPrice).click();
cy.getByTestId(dealTicketPrice).should('have.value', '98.94185');
});
it('copy size to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(bidCumulative).click();
cy.getByTestId(dealTicketSize).should('have.value', '7');
});
it('copy size to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(bidVolume).click();
cy.getByTestId(dealTicketSize).should('have.value', '1');
});
it('change price resolution', () => {
// 6003-ORDB-008
const resolutions = [
'0.00000',
'0.0000',
'0.000',
'0.00',
'0.0',
'0',
'10',
'100',
'1,000',
'10,000',
];
cy.getByTestId(priceResolution).click();
cy.get('[role="menu"]')
.find('[role="menuitem"]')
.each(($el, index) => {
expect($el.text()).to.equal(resolutions[index]);
});
cy.get('[role="menuitem"]').eq(4).click();
cy.getByTestId(resPrice).should('have.text', '99.0');
cy.getByTestId(askPrice).should('not.exist');
cy.getByTestId(bidPrice).should('not.exist');
});
});
@@ -24,9 +24,6 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.getByTestId('deal-ticket-fee-margin-required').within(() => {
cy.get('button').click();
});
});
describe('limit order', () => {
+4 -4
View File
@@ -16,12 +16,12 @@ 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.21.0-core-0.72.14
NX_APP_VERSION=v0.20.21-core-0.71.6
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
NX_SUCCESSOR_MARKETS=false
NX_STOP_ORDERS=false
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
+3 -3
View File
@@ -19,9 +19,9 @@ NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-ma
NX_APP_VERSION=v0.20.19-core-0.71.6
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
NX_SUCCESSOR_MARKETS=false
NX_STOP_ORDERS=false
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
+1 -1
View File
@@ -19,6 +19,6 @@ NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fa
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
+3 -3
View File
@@ -19,9 +19,9 @@ NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fa
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
NX_SUCCESSOR_MARKETS=false
NX_STOP_ORDERS=false
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
@@ -1,53 +1,27 @@
import React, { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
LocalStoragePersistTabs as Tabs,
Tab,
TradingAnchorButton,
} from '@vegaprotocol/ui-toolkit';
import { LocalStoragePersistTabs as Tabs, Tab } from '@vegaprotocol/ui-toolkit';
import { Markets } from './markets';
import { Proposed } from './proposed';
import { usePageTitleStore } from '../../stores';
import { Closed } from './closed';
import {
DApp,
TOKEN_NEW_MARKET_PROPOSAL,
useLinks,
} from '@vegaprotocol/environment';
export const MarketsPage = () => {
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
}));
const tokenLink = useLinks(DApp.Token);
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
useEffect(() => {
updateTitle(titlefy(['Markets']));
}, [updateTitle]);
return (
<div className="h-full pt-0.5 pb-3 px-1.5">
<div className="h-full my-1 border rounded-sm border-default">
<div className="h-full my-1 border border-default rounded-sm">
<Tabs storageKey="console-markets">
<Tab id="open-markets" name={t('Open markets')}>
<Markets />
</Tab>
<Tab
id="proposed-markets"
name={t('Proposed markets')}
menu={
<TradingAnchorButton
size="extra-small"
data-testid="propose-new-market"
href={externalLink}
>
{t('Propose a new market')}
</TradingAnchorButton>
}
>
<Tab id="proposed-markets" name={t('Proposed markets')}>
<Proposed />
</Tab>
<Tab id="closed-markets" name={t('Closed markets')}>
+19 -1
View File
@@ -1,6 +1,24 @@
import { t } from '@vegaprotocol/i18n';
import {
DApp,
TOKEN_NEW_MARKET_PROPOSAL,
useLinks,
} from '@vegaprotocol/environment';
import { ProposalsList } from '@vegaprotocol/proposals';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { SuccessorMarketRenderer } from './successor-market-cell';
export const Proposed = () => {
return <ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />;
const tokenLink = useLinks(DApp.Token);
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
return (
<>
<div className="h-[400px]">
<ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />
</div>
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
{t('Propose a new market')}
</ExternalLink>
</>
);
};
@@ -4,7 +4,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import compact from 'lodash/compact';
import uniqBy from 'lodash/uniqBy';
import type { ChangeEvent } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
import * as Schema from '@vegaprotocol/types';
@@ -12,13 +12,12 @@ import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import {
AsyncRenderer,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Splash,
Toggle,
TradingButton,
TradingDropdown,
TradingDropdownContent,
TradingDropdownItem,
TradingDropdownTrigger,
} from '@vegaprotocol/ui-toolkit';
import { AccountTypeMapping } from '@vegaprotocol/types';
import { PriceChart } from 'pennant';
@@ -151,7 +150,6 @@ const AccountHistoryManager = ({
)
: null;
}, [accounts, marketFilterCb]);
const resolveMarket = useCallback(
(m: Market) => {
setMarket(m);
@@ -176,114 +174,111 @@ const AccountHistoryManager = ({
}),
[pubKey, asset, accountType, range, market?.id]
);
const { data } = useAccountHistoryQuery({
variables,
skip: !asset || !pubKey,
});
const accountTypeMenu = useMemo(() => {
return (
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{accountType
? `${
AccountTypeMapping[
accountType as keyof typeof Schema.AccountType
]
} Account`
: t('Select account type')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{[
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
Schema.AccountType.ACCOUNT_TYPE_BOND,
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
].map((type) => (
<DropdownMenuItem
key={type}
onClick={() => setAccountType(type as Schema.AccountType)}
>
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}, [accountType]);
const assetsMenu = useMemo(() => {
return (
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{asset ? asset.symbol : t('Select asset')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{assets.map((a) => (
<DropdownMenuItem key={a.id} onClick={() => setAssetId(a.id)}>
{a.symbol}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}, [asset, assets, setAssetId]);
const marketsMenu = useMemo(() => {
return accountType === Schema.AccountType.ACCOUNT_TYPE_MARGIN &&
markets?.length ? (
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{market
? market.tradableInstrument.instrument.code
: t('Select market')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{market && (
<DropdownMenuItem key="0" onClick={() => setMarket(null)}>
{t('All markets')}
</DropdownMenuItem>
)}
{markets?.map((m) => (
<DropdownMenuItem key={m.id} onClick={() => resolveMarket(m)}>
{m.tradableInstrument.instrument.code}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
) : null;
}, [markets, market, accountType, resolveMarket]);
useEffect(() => {
if (
accountType !== Schema.AccountType.ACCOUNT_TYPE_MARGIN ||
market?.tradableInstrument.instrument.product.settlementAsset.id !==
asset?.id
) {
setMarket(null);
}
}, [accountType, asset?.id, market]);
return (
<div className="flex flex-col h-full gap-2">
<div className="flex flex-wrap justify-between px-1 pt-2 gap-2">
<div className="flex items-center gap-1 shrink-0">
<TradingDropdown
trigger={
<TradingDropdownTrigger>
<TradingButton size="small">
{accountType
? `${
AccountTypeMapping[
accountType as keyof typeof Schema.AccountType
]
} Account`
: t('Select account type')}
</TradingButton>
</TradingDropdownTrigger>
}
>
<TradingDropdownContent>
{[
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
Schema.AccountType.ACCOUNT_TYPE_BOND,
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
].map((type) => (
<TradingDropdownItem
key={type}
onClick={() => {
setAccountType(type as Schema.AccountType);
// if not a margin account clear any market selection
if (type !== Schema.AccountType.ACCOUNT_TYPE_MARGIN) {
setMarket(null);
}
}}
>
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
</TradingDropdownItem>
))}
</TradingDropdownContent>
</TradingDropdown>
<TradingDropdown
trigger={
<TradingDropdownTrigger>
<TradingButton size="small">
{asset ? asset.symbol : t('Select asset')}
</TradingButton>
</TradingDropdownTrigger>
}
>
<TradingDropdownContent>
{assets.map((a) => (
<TradingDropdownItem
key={a.id}
onClick={() => {
setAssetId(a.id);
// if the selected asset is different to the selected market clear the market
if (
a.id !==
market?.tradableInstrument.instrument.product
.settlementAsset.id
) {
setMarket(null);
}
}}
>
{a.symbol}
</TradingDropdownItem>
))}
</TradingDropdownContent>
</TradingDropdown>
<TradingDropdown
trigger={
<TradingDropdownTrigger>
<TradingButton size="small">
{market
? market.tradableInstrument.instrument.code
: t('Select market')}
</TradingButton>
</TradingDropdownTrigger>
}
>
<TradingDropdownContent>
{market && (
<TradingDropdownItem key="0" onClick={() => setMarket(null)}>
{t('All markets')}
</TradingDropdownItem>
)}
{markets?.map((m) => (
<TradingDropdownItem
key={m.id}
onClick={() => resolveMarket(m)}
>
{m.tradableInstrument.instrument.code}
</TradingDropdownItem>
))}
</TradingDropdownContent>
</TradingDropdown>
<div className="h-full w-full flex flex-col gap-8">
<div className="w-full flex flex-col-reverse lg:flex-row items-start lg:items-center justify-between gap-4 px-2">
<div className="flex items-center gap-4 shrink-0">
<>
{accountTypeMenu}
{assetsMenu}
{marketsMenu}
</>
</div>
<div className="justify-items-end">
<div className="pt-1 justify-items-end">
<Toggle
id="account-history-date-range"
name="account-history-date-range"
@@ -292,19 +287,16 @@ const AccountHistoryManager = ({
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setRange(e.target.value as keyof typeof DateRange)
}
size="sm"
/>
</div>
</div>
<div className="flex-1">
<div className="h-5/6 px-4">
{asset && (
<div className="h-full">
<AccountHistoryChart
data={data}
accountType={accountType}
asset={asset}
/>
</div>
<AccountHistoryChart
data={data}
accountType={accountType}
asset={asset}
/>
)}
</div>
</div>
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const AccountsMenu = () => {
@@ -8,6 +8,7 @@ export const AccountsMenu = () => {
return (
<>
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={() => setView({ type: ViewType.Transfer })}
@@ -15,6 +16,7 @@ export const AccountsMenu = () => {
{t('Transfer')}
</TradingButton>
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
>
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const DepositsMenu = () => {
@@ -7,6 +7,7 @@ export const DepositsMenu = () => {
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
data-testid="deposit-button"
@@ -1,12 +1,13 @@
import { t } from '@vegaprotocol/i18n';
import {
TradingDropdown,
TradingDropdownCheckboxItem,
TradingDropdownContent,
TradingDropdownItemIndicator,
TradingDropdownTrigger,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItemIndicator,
DropdownMenuTrigger,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { MarketSelectorButton } from './market-selector-button';
type Assets = Array<{ id: string; symbol: string }>;
@@ -24,19 +25,17 @@ export const AssetDropdown = ({
}
return (
<TradingDropdown
<DropdownMenu
trigger={
<TradingDropdownTrigger data-testid="asset-trigger">
<MarketSelectorButton>
{triggerText({ assets, checkedAssets })}
</MarketSelectorButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger data-testid="asset-trigger">
<TriggerText assets={assets} checkedAssets={checkedAssets} />
</DropdownMenuTrigger>
}
>
<TradingDropdownContent>
<DropdownMenuContent>
{assets?.map((a) => {
return (
<TradingDropdownCheckboxItem
<DropdownMenuCheckboxItem
key={a.id}
checked={checkedAssets.includes(a.id)}
onCheckedChange={(checked) => {
@@ -47,16 +46,16 @@ export const AssetDropdown = ({
data-testid={`asset-id-${a.id}`}
>
{a.symbol}
<TradingDropdownItemIndicator />
</TradingDropdownCheckboxItem>
<DropdownMenuItemIndicator />
</DropdownMenuCheckboxItem>
);
})}
</TradingDropdownContent>
</TradingDropdown>
</DropdownMenuContent>
</DropdownMenu>
);
};
const triggerText = ({
const TriggerText = ({
assets,
checkedAssets,
}: {
@@ -73,5 +72,9 @@ const triggerText = ({
text = t(`${checkedAssets.length} Assets`);
}
return text;
return (
<span className="flex justify-between items-center">
{text} <VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
</span>
);
};
@@ -1,3 +1,2 @@
export * from './market-selector';
export * from './market-selector-item';
export * from './market-selector-button';
@@ -1,23 +0,0 @@
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ButtonHTMLAttributes } from 'react';
import { forwardRef } from 'react';
export const MarketSelectorButton = forwardRef<
HTMLButtonElement,
ButtonHTMLAttributes<HTMLButtonElement>
>((props, ref) => (
<button
{...props}
className={classNames(
'flex items-center justify-between px-2 border rounded gap-1',
'border-vega-clight-600 dark:border-vega-cdark-600 bg-vega-clight-700 dark:bg-vega-cdark-700',
'text-secondary data-[state=open]:text-vega-clight-50 dark:data-[state=open]:text-vega-cdark-50'
)}
ref={ref}
>
{props.children}
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
</button>
));
MarketSelectorButton.displayName = 'MarketSelectorButton';
@@ -31,7 +31,7 @@ export const MarketSelectorItem = ({
<div style={style} role="row">
<Link
to={`/markets/${market.id}`}
className={classNames('h-full flex items-center gap-2 mx-2 px-2', {
className={classNames('h-full flex items-center gap-2 px-4', {
'hover:bg-vega-clight-700 dark:hover:bg-vega-cdark-700':
market.id !== currentMarketId,
'bg-vega-clight-600 dark:bg-vega-cdark-600':
@@ -94,7 +94,7 @@ const MarketData = ({
return (
<>
<div className="w-2/5" role="gridcell">
<h3 className="overflow-hidden text-sm text-ellipsis lg:text-base whitespace-nowrap">
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
{market.tradableInstrument.instrument.code}{' '}
{allProducts && productType && (
<MarketProductPill productType={productType} />
@@ -107,7 +107,7 @@ const MarketData = ({
)}
</div>
<div
className="w-1/5 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis"
className="w-1/5 text-xs lg:text-sm whitespace-nowrap text-ellipsis overflow-hidden"
title={instrument.product.settlementAsset.symbol}
data-testid="market-selector-price"
role="gridcell"
@@ -115,14 +115,14 @@ const MarketData = ({
{price} {instrument.product.settlementAsset.symbol}
</div>
<div
className="w-1/5 overflow-hidden text-xs text-right lg:text-sm whitespace-nowrap text-ellipsis"
className="w-1/5 text-xs lg:text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
title={t('24h vol')}
data-testid="market-selector-volume"
role="gridcell"
>
{volume}
</div>
<div className="flex justify-end w-1/5" role="gridcell">
<div className="w-1/5 flex justify-end" role="gridcell">
{oneDayCandles && (
<Sparkline
width={64}
@@ -262,7 +262,9 @@ describe('MarketSelector', () => {
await userEvent.click(screen.getByTestId('sort-trigger'));
const options = screen.getAllByTestId(/sort-item/);
expect(options.map((o) => o.textContent?.trim())).toEqual(
Object.entries(Sort).map(([key]) => SortTypeMapping[key as SortType])
Object.entries(Sort)
.filter(([key]) => key !== Sort.None)
.map(([key]) => SortTypeMapping[key as SortType])
);
await userEvent.click(screen.getByTestId('sort-item-Gained'));
expect(
@@ -40,7 +40,7 @@ export const MarketSelector = ({
const [filter, setFilter] = useState<Filter>({
searchTerm: '',
product: Product.All,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
const allProducts = filter.product === Product.All;
@@ -52,8 +52,8 @@ export const MarketSelector = ({
}, [reload]);
return (
<div data-testid="market-selector" className="md:w-[580px]">
<div className="px-2 pt-2 mb-2">
<div data-testid="market-selector">
<div className="pt-2 px-2 mb-2">
<ProductSelector
product={filter.product}
onSelect={(product) => {
@@ -106,6 +106,9 @@ export const MarketSelector = ({
currentSort={filter.sort}
onSelect={(sort) => {
setFilter((curr) => {
if (curr.sort === sort) {
return { ...curr, sort: Sort.None };
}
return {
...curr,
sort,
@@ -291,9 +294,9 @@ const List = ({
const Skeleton = () => {
return (
<div className="px-2 mb-2">
<div className="p-4 rounded-lg bg-vega-light-100 dark:bg-vega-dark-100">
<div className="w-full h-3 mb-2 bg-vega-light-200 dark:bg-vega-dark-200" />
<div className="mb-2 px-2">
<div className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4">
<div className="w-full h-3 bg-vega-light-200 dark:bg-vega-dark-200 mb-2" />
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
</div>
</div>
@@ -33,14 +33,11 @@ export const ProductSelector = ({
return (
<div className="flex mb-2">
{Object.keys(Product).map((t) => {
const classes = classNames(
'text-sm px-3 py-1.5 rounded hover:text-vega-clight-50 dark:hover:text-vega-cdark-50',
{
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
t === product,
'text-secondary': t !== product,
}
);
const classes = classNames('px-3 py-1.5 rounded', {
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
t === product,
'text-secondary': t !== product,
});
return (
<button
key={t}
@@ -56,7 +53,7 @@ export const ProductSelector = ({
})}
<Link
to={Routes.MARKETS}
className="flex items-center ml-auto text-sm gap-2"
className="flex items-center gap-2 ml-auto"
title={t('See all markets')}
>
<span className="underline underline-offset-4">{t('Browse')}</span>
@@ -1,16 +1,17 @@
import { t } from '@vegaprotocol/i18n';
import {
TradingDropdown,
TradingDropdownContent,
TradingDropdownItemIndicator,
TradingDropdownRadioGroup,
TradingDropdownRadioItem,
TradingDropdownTrigger,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItemIndicator,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { MarketSelectorButton } from './market-selector-button';
export const Sort = {
None: 'None',
Gained: 'Gained',
Lost: 'Lost',
New: 'New',
@@ -22,15 +23,17 @@ export type SortType = keyof typeof Sort;
export const SortTypeMapping: {
[key in SortType]: string;
} = {
[Sort.TopTraded]: 'Top traded',
[Sort.None]: 'None',
[Sort.Gained]: 'Top gaining',
[Sort.Lost]: 'Top losing',
[Sort.New]: 'New markets',
[Sort.TopTraded]: 'Top traded',
};
const SortIconMapping: {
[key in SortType]: VegaIconNames;
} = {
[Sort.None]: null as unknown as VegaIconNames, // not shown in list
[Sort.Gained]: VegaIconNames.TREND_UP,
[Sort.Lost]: VegaIconNames.TREND_DOWN,
[Sort.New]: VegaIconNames.STAR,
@@ -45,38 +48,43 @@ export const SortDropdown = ({
onSelect: (sort: SortType) => void;
}) => {
return (
<TradingDropdown
<DropdownMenu
trigger={
<TradingDropdownTrigger data-testid="sort-trigger">
<MarketSelectorButton>
{SortTypeMapping[currentSort]}
</MarketSelectorButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger data-testid="sort-trigger">
<span className="flex justify-between items-center">
{currentSort === SortTypeMapping.None
? t('Sort')
: SortTypeMapping[currentSort]}{' '}
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
</span>
</DropdownMenuTrigger>
}
>
<TradingDropdownContent>
<TradingDropdownRadioGroup
<DropdownMenuContent>
<DropdownMenuRadioGroup
value={currentSort}
onValueChange={(value) => onSelect(value as SortType)}
>
{Object.keys(Sort).map((key) => {
return (
<TradingDropdownRadioItem
inset
key={key}
value={key}
data-testid={`sort-item-${key}`}
>
<span className="flex gap-2">
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
{SortTypeMapping[key as SortType]}
</span>
<TradingDropdownItemIndicator />
</TradingDropdownRadioItem>
);
})}
</TradingDropdownRadioGroup>
</TradingDropdownContent>
</TradingDropdown>
{Object.keys(Sort)
.filter((s) => s !== Sort.None)
.map((key) => {
return (
<DropdownMenuRadioItem
inset
key={key}
value={key}
data-testid={`sort-item-${key}`}
>
<span className="flex gap-2">
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
{SortTypeMapping[key as SortType]}
</span>
<DropdownMenuItemIndicator />
</DropdownMenuRadioItem>
);
})}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
};
@@ -12,10 +12,7 @@ import { useMarketList } from '@vegaprotocol/markets';
import type { Filter } from '../../components/market-selector';
import { subDays } from 'date-fns';
jest.mock('@vegaprotocol/markets', () => ({
...jest.requireActual('@vegaprotocol/markets'),
useMarketList: jest.fn(),
}));
jest.mock('@vegaprotocol/markets');
const mockUseMarketList = useMarketList as jest.Mock;
describe('useMarketSelectorList', () => {
@@ -23,7 +20,7 @@ describe('useMarketSelectorList', () => {
const defaultArgs: Filter = {
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
};
return renderHook((args) => useMarketSelectorList(args), {
@@ -112,21 +109,21 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Spot as 'Future',
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[1]]);
rerender({
searchTerm: '',
product: Product.Perpetual as 'Future',
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[2]]);
rerender({
searchTerm: '',
product: Product.All,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual(markets);
@@ -192,7 +189,7 @@ describe('useMarketSelectorList', () => {
const { result, rerender } = setup({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-0'],
});
expect(result.current.markets).toEqual([markets[0], markets[1]]);
@@ -200,7 +197,7 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-0', 'asset-1'],
});
@@ -213,7 +210,7 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-0', 'asset-1', 'asset-2'],
});
@@ -223,7 +220,7 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-invalid'],
});
@@ -278,28 +275,28 @@ describe('useMarketSelectorList', () => {
const { result, rerender } = setup({
searchTerm: 'abc',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[0]]);
rerender({
searchTerm: 'def',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[1], markets[2]]);
rerender({
searchTerm: 'defg',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[2]]);
rerender({
searchTerm: 'zzz',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([]);
@@ -308,14 +305,14 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: 'aaa',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[0]]);
rerender({
searchTerm: 'ggg',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([
@@ -325,15 +322,11 @@ describe('useMarketSelectorList', () => {
]);
});
it('sorts by top traded by default', () => {
it('sorts by state and volume by default', () => {
const markets = [
createMarketFragment({
id: 'market-0',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
state: MarketState.STATE_PENDING,
// @ts-ignore candles not on fragment
candles: [
{
@@ -344,10 +337,16 @@ describe('useMarketSelectorList', () => {
createMarketFragment({
id: 'market-1',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
// @ts-ignore candles not on fragment
candles: [
{
volume: '200',
},
],
}),
createMarketFragment({
id: 'market-2',
state: MarketState.STATE_ACTIVE,
// @ts-ignore candles not on fragment
candles: [
{
@@ -356,30 +355,12 @@ describe('useMarketSelectorList', () => {
],
}),
createMarketFragment({
id: 'market-2',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
// @ts-ignore candles not on fragment
candles: [
{
volume: '300',
},
],
}),
createMarketFragment({
state: MarketState.STATE_PENDING,
id: 'market-3',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
// @ts-ignore candles not on fragment
candles: [
{
volume: '400',
volume: '100',
},
],
}),
@@ -394,15 +375,14 @@ describe('useMarketSelectorList', () => {
const { result } = setup({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([
markets[3],
markets[1],
markets[2],
markets[0],
markets[1],
markets[3],
]);
});
@@ -1,7 +1,11 @@
import { useMemo } from 'react';
import orderBy from 'lodash/orderBy';
import { MarketState } from '@vegaprotocol/types';
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
import {
calcCandleVolume,
calcTradedFactor,
useMarketList,
} from '@vegaprotocol/markets';
import { priceChangePercentage } from '@vegaprotocol/utils';
import type { Filter } from '../../components/market-selector/market-selector';
import { Sort } from './sort-dropdown';
@@ -56,6 +60,22 @@ export const useMarketSelectorList = ({
return false;
});
if (sort === Sort.None) {
// Sort by market state primarily and AtoZ secondarily
return orderBy(
markets,
[
(m) => MARKET_TEMPLATE.indexOf(m.state),
(m) => {
if (!m.candles?.length) return 0;
const vol = calcCandleVolume(m.candles);
return Number(vol || 0);
},
],
['asc', 'desc']
);
}
if (sort === Sort.Gained || sort === Sort.Lost) {
const dir = sort === Sort.Gained ? 'desc' : 'asc';
return orderBy(
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { usePositionsStore } from '../positions-container';
export const PositionsMenu = () => {
@@ -7,6 +7,7 @@ export const PositionsMenu = () => {
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={toggle}
@@ -24,8 +24,8 @@ export const Settings = () => {
>
<Switch
name="settings-telemetry-switch"
onCheckedChange={(isOn) => setIsApproved(isOn ? 'true' : 'false')}
checked={isApproved === 'true'}
onCheckedChange={(isOn) => setIsApproved(isOn)}
checked={isApproved}
/>
</SettingsGroup>
<SettingsGroup label={t('Toast location')}>
@@ -0,0 +1 @@
export * from './vega-wallet-container';
@@ -0,0 +1,27 @@
import { render, screen } from '@testing-library/react';
import { VegaWalletContainer } from './vega-wallet-container';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import type { PartialDeep } from 'type-fest';
const generateJsx = (context: PartialDeep<VegaWalletContextShape>) => {
return (
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
<VegaWalletContainer>
<div data-testid="child" />
</VegaWalletContainer>
</VegaWalletContext.Provider>
);
};
describe('VegaWalletContainer', () => {
it('doesnt render children if not connected', () => {
render(generateJsx({ pubKey: null }));
expect(screen.queryByTestId('child')).not.toBeInTheDocument();
});
it('renders children if connected', () => {
render(generateJsx({ pubKey: '0x123' }));
expect(screen.getByTestId('child')).toBeInTheDocument();
});
});
@@ -0,0 +1,35 @@
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/i18n';
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
interface VegaWalletContainerProps {
children: ReactNode;
}
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const { pubKey } = useVegaWallet();
if (!pubKey) {
return (
<Splash>
<div className="text-center">
<p className="mb-4" data-testid="connect-vega-wallet-text">
{t('Connect your Vega wallet')}
</p>
<Button
onClick={openVegaWalletDialog}
data-testid="vega-wallet-connect"
>
{t('Connect')}
</Button>
</div>
</Splash>
);
}
return <>{children}</>;
};
@@ -22,7 +22,7 @@ describe('GetStarted', () => {
};
const checkTicks = (elements: Element[]) => {
elements.forEach((item, i) => {
if (i + 1 < mockStep - 1) {
if (i + 1 < mockStep) {
expect(item.querySelector('[data-testid="icon-tick"]')).toBeTruthy();
}
});
@@ -57,9 +57,19 @@ describe('GetStarted', () => {
'get'
);
navigatorGetter.mockReturnValue('Chrome');
mockStep = 1;
const { rerender, container } = renderComponent();
expect(screen.queryByTestId('icon-tick')).not.toBeInTheDocument();
expect(screen.getByTestId('get-wallet-button')).toBeInTheDocument();
mockStep = 2;
const { rerender, container } = renderComponent();
rerender(
<MemoryRouter>
<VegaWalletContext.Provider value={{} as VegaWalletContextShape}>
<GetStarted />
</VegaWalletContext.Provider>
</MemoryRouter>
);
checkTicks(screen.getAllByRole('listitem'));
expect(screen.getByRole('button', { name: 'Connect' })).toBeInTheDocument();
@@ -83,9 +93,7 @@ describe('GetStarted', () => {
</MemoryRouter>
);
checkTicks(screen.getAllByRole('listitem'));
expect(
screen.getByRole('button', { name: 'Ready to trade' })
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
mockStep = 5;
rerender(
@@ -7,7 +7,11 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import {
GetWalletButton,
useVegaWallet,
useVegaWalletDialogStore,
} from '@vegaprotocol/wallet';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useNavigate } from 'react-router-dom';
@@ -19,19 +23,19 @@ import { Links, Routes } from '../../pages/client-router';
import { useGlobalStore } from '../../stores';
import { useSidebar, ViewType } from '../sidebar';
import * as constants from '../constants';
import { useOnboardingStore } from './welcome-dialog';
interface Props {
lead?: string;
}
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
const { CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL } = useEnvironment();
const navigate = useNavigate();
const [, setOnboardingViewed] = useLocalStorage(
constants.ONBOARDING_VIEWED_KEY
);
const dismiss = useOnboardingStore((store) => store.dismiss);
const update = useGlobalStore((store) => store.update);
const marketId = useGlobalStore((store) => store.marketId);
const link = marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]();
const openVegaWalletDialog = useVegaWalletDialogStore(
@@ -42,17 +46,25 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
let onClickHandle = () => {
openVegaWalletDialog();
};
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
if (step === OnboardingStep.ONBOARDING_WALLET_STEP) {
return (
<GetWalletButton
className="justify-between"
chromeExtensionUrl={CHROME_EXTENSION_URL}
mozillaExtensionUrl={MOZILLA_EXTENSION_URL}
/>
);
} else if (step === OnboardingStep.ONBOARDING_CONNECT_STEP) {
buttonText = t('Connect');
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
buttonText = t('Deposit');
onClickHandle = () => {
navigate(link);
setView({ type: ViewType.Deposit });
dismiss();
update({ onBoardingDismissed: true });
};
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
buttonText = t('Ready to trade');
buttonText = t('Dismiss');
onClickHandle = () => {
navigate(link);
setView({ type: ViewType.Order });
@@ -103,18 +115,23 @@ export const GetStarted = ({ lead }: Props) => {
<ul className="list-none">
<Step
step={1}
text={t('Get a Vega wallet')}
complete={currentStep > OnboardingStep.ONBOARDING_WALLET_STEP}
/>
<Step
step={2}
text={t('Connect')}
complete={Boolean(
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
)}
/>
<Step
step={2}
step={3}
text={t('Deposit funds')}
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
/>
<Step
step={3}
step={4}
text={t('Open a position')}
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
/>
@@ -2,35 +2,29 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TelemetryApproval } from './telemetry-approval';
describe('TelemetryApproval', () => {
it('click on buttons should be properly handled', async () => {
const mockSetTelemetryValue = jest.fn();
render(
<TelemetryApproval
telemetryValue="false"
setTelemetryValue={mockSetTelemetryValue}
/>
);
expect(
screen.getByRole('button', { name: 'No thanks' })
).toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: 'No thanks' }));
expect(mockSetTelemetryValue).toHaveBeenCalledWith('false');
expect(screen.getByText('Share data')).toBeInTheDocument();
await userEvent.click(screen.getByText('Share data'));
expect(mockSetTelemetryValue).toHaveBeenCalledWith('true');
});
jest.mock('@vegaprotocol/logger', () => ({
SentryInit: () => undefined,
SentryClose: () => undefined,
}));
it('confirm button should have proper text', async () => {
const mockSetTelemetryValue = jest.fn();
render(
<TelemetryApproval
telemetryValue="true"
setTelemetryValue={mockSetTelemetryValue}
/>
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
}));
describe('TelemetryApproval', () => {
it('click on checkbox should be properly handled', async () => {
const helpText = 'My help text';
render(<TelemetryApproval helpText={helpText} />);
expect(screen.getByRole('checkbox')).toHaveAttribute(
'data-state',
'unchecked'
);
expect(screen.getByText('Continue sharing data')).toBeInTheDocument();
await userEvent.click(screen.getByText('Continue sharing data'));
expect(mockSetTelemetryValue).toHaveBeenCalledWith('true');
await userEvent.click(screen.getByRole('checkbox'));
expect(screen.getByRole('checkbox')).toHaveAttribute(
'data-state',
'checked'
);
expect(screen.getByText('Share usage data')).toBeInTheDocument();
expect(screen.getByText(helpText)).toBeInTheDocument();
});
});
@@ -1,69 +1,21 @@
import {
Intent,
TradingButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { TradingCheckbox } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
interface Props {
telemetryValue: string;
setTelemetryValue: (value: string) => void;
}
export const TelemetryApproval = ({
telemetryValue,
setTelemetryValue,
}: Props) => {
export const TelemetryApproval = ({ helpText }: { helpText: string }) => {
const [isApproved, setIsApproved] = useTelemetryApproval();
return (
<div className="flex flex-col">
<div className="flex flex-col py-3">
<div className="mr-4" role="form">
<p className="mb-4">
{t(
'Help us identify bugs and improve Vega Governance by sharing anonymous usage data.'
)}
</p>
<div className="flex items-start mb-2 gap-3">
<VegaIcon name={VegaIconNames.EYE_OFF} size={18} />
<div className="flex flex-col gap-1">
<h6 className="font-semibold">{t('Anonymous')}</h6>
<p className="text-muted">
{t('Your identity is always anonymous on Vega')}
</p>
</div>
</div>
<div className="flex items-start mb-4 gap-3">
<VegaIcon name={VegaIconNames.COG} size={18} />
<div className="flex flex-col gap-1">
<h6 className="font-semibold">{t('Optional')}</h6>
<p className="text-muted">
{t('You can opt out any time via settings')}
</p>
</div>
</div>
<div className="flex flex-col items-center justify-around gap-2">
<TradingButton
onClick={() => setTelemetryValue('false')}
size="small"
intent={Intent.None}
data-testid="do-not-share-data-button"
fill
>
{t('No thanks')}
</TradingButton>
<TradingButton
onClick={() => setTelemetryValue('true')}
intent={Intent.Info}
data-testid="share-data-button"
size="small"
fill
>
{telemetryValue === 'true'
? t('Continue sharing data')
: t('Share data')}
</TradingButton>
</div>
<TradingCheckbox
label={<span className="text-lg pl-1">{t('Share usage data')}</span>}
checked={isApproved}
name="telemetry-approval"
onCheckedChange={() => setIsApproved(!isApproved)}
/>
</div>
<div className="text-sm text-vega-light-300 dark:text-vega-dark-300 ml-6">
<span>{helpText}</span>
</div>
</div>
);
@@ -7,6 +7,8 @@ import {
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { depositsProvider } from '@vegaprotocol/deposits';
import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
import { positionsDataProvider } from '@vegaprotocol/positions';
@@ -21,7 +23,7 @@ let mockContext: Partial<VegaWalletContextShape> = { pubKey: 'test-pubkey' };
describe('useGetOnboardingStep', () => {
beforeEach(() => {
jest.clearAllMocks();
mockData = [];
mockData = [{ id: 'item-id' }];
mockContext = { pubKey: 'test-pubkey' };
globalThis.window.vega = {} as Vega;
});
@@ -40,6 +42,13 @@ describe('useGetOnboardingStep', () => {
expect(result.current).toEqual(OnboardingStep.ONBOARDING_UNKNOWN_STEP);
});
it('should return properly ONBOARDING_WALLET_STEP', () => {
// @ts-ignore test only purpose
globalThis.window.vega = undefined;
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
expect(result.current).toEqual(OnboardingStep.ONBOARDING_WALLET_STEP);
});
it('should return properly ONBOARDING_CONNECT_STEP', () => {
mockContext = { pubKey: null };
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
@@ -47,6 +56,15 @@ describe('useGetOnboardingStep', () => {
});
it('should return properly ONBOARDING_DEPOSIT_STEP', async () => {
(useDataProvider as jest.Mock).mockImplementation((args) => {
if (
args.dataProvider === depositsProvider ||
args.dataProvider === aggregatedAccountsDataProvider
) {
return { data: [] };
}
return { data: mockData };
});
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
await expect(result.current).toEqual(
OnboardingStep.ONBOARDING_DEPOSIT_STEP
@@ -54,7 +72,6 @@ describe('useGetOnboardingStep', () => {
});
it('should return properly ONBOARDING_ORDER_STEP', async () => {
mockData = [{ id: 'item-id' }];
(useDataProvider as jest.Mock).mockImplementation((args) => {
if (
args.dataProvider === ordersWithMarketProvider ||
@@ -69,7 +86,6 @@ describe('useGetOnboardingStep', () => {
});
it('should return properly ONBOARDING_COMPLETE_STEP', async () => {
mockData = [{ id: 'item-id' }];
(useDataProvider as jest.Mock).mockImplementation(() => {
return { data: mockData };
});
@@ -1,4 +1,4 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import { isBrowserWalletInstalled, useVegaWallet } from '@vegaprotocol/wallet';
import { depositsProvider } from '@vegaprotocol/deposits';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
@@ -63,21 +63,20 @@ export const useGetOnboardingStep = () => {
collateralData === null ||
positionsData === null)
);
let step = OnboardingStep.ONBOARDING_UNKNOWN_STEP;
if (isLoading) {
return step;
return OnboardingStep.ONBOARDING_UNKNOWN_STEP;
}
if (!isBrowserWalletInstalled()) {
return OnboardingStep.ONBOARDING_WALLET_STEP;
}
if (!pubKey) {
step = OnboardingStep.ONBOARDING_CONNECT_STEP;
return OnboardingStep.ONBOARDING_CONNECT_STEP;
}
if (pubKey) {
step = OnboardingStep.ONBOARDING_DEPOSIT_STEP;
if (!deposits && !collaterals) {
return OnboardingStep.ONBOARDING_DEPOSIT_STEP;
}
if (pubKey && (deposits || collaterals)) {
step = OnboardingStep.ONBOARDING_ORDER_STEP;
if (!orders && !positions) {
return OnboardingStep.ONBOARDING_ORDER_STEP;
}
if (pubKey && (orders || positions)) {
step = OnboardingStep.ONBOARDING_COMPLETE_STEP;
}
return step;
return OnboardingStep.ONBOARDING_COMPLETE_STEP;
};
@@ -5,17 +5,17 @@ import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import type { ReactNode } from 'react';
import { useOnboardingStore } from './welcome-dialog';
import { useGlobalStore } from '../../stores';
export const WelcomeDialogContent = () => {
const { VEGA_ENV } = useEnvironment();
const dismiss = useOnboardingStore((store) => store.dismiss);
const update = useGlobalStore((store) => store.update);
const navigate = useNavigate();
const browseMarkets = () => {
const link = Links[Routes.MARKETS]();
navigate(link);
dismiss();
update({ onBoardingDismissed: true });
};
const lead =
VEGA_ENV === Networks.MAINNET
@@ -1,9 +1,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { Dialog, Intent, useToasts } from '@vegaprotocol/ui-toolkit';
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useEnvironment } from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { WelcomeDialogContent } from './welcome-dialog-content';
@@ -14,41 +12,15 @@ import {
OnboardingStep,
} from './use-get-onboarding-step';
import * as constants from '../constants';
import { TelemetryApproval } from './telemetry-approval';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
import { useCallback } from 'react';
const ONBOARDING_STORAGE_KEY = 'vega_onboarding_dismiss_store';
export const useOnboardingStore = create<{
dismissed: boolean;
dismiss: () => void;
}>()(
persist(
(set) => ({
dismissed: false,
dismiss: () => set(() => ({ dismissed: true })),
}),
{
name: ONBOARDING_STORAGE_KEY,
}
)
);
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
export const WelcomeDialog = () => {
const { VEGA_ENV } = useEnvironment();
const navigate = useNavigate();
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
useTelemetryApproval();
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
const dismiss = useOnboardingStore((store) => store.dismiss);
const dismissed = useOnboardingStore((store) => store.dismissed);
const update = useGlobalStore((store) => store.update);
const dismissed = useGlobalStore((store) => store.onBoardingDismissed);
const currentStep = useGetOnboardingStep();
const isTelemetryPopupNeeded =
isTelemetryNeeded &&
(onBoardingViewed === 'true' ||
currentStep > OnboardingStep.ONBOARDING_ORDER_STEP);
const navigate = useNavigate();
const isOnboardingDialogNeeded =
onBoardingViewed !== 'true' &&
currentStep &&
@@ -57,58 +29,12 @@ export const WelcomeDialog = () => {
const marketId = useGlobalStore((store) => store.marketId);
const onClose = () => {
if (isTelemetryPopupNeeded) {
closeTelemetry();
} else {
const link = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.HOME]();
navigate(link);
dismiss();
}
const link = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.HOME]();
navigate(link);
update({ onBoardingDismissed: true });
};
const [setToast, hasToast, removeToast] = useToasts((store) => [
store.setToast,
store.hasToast,
store.remove,
]);
const onApprovalClose = useCallback(() => {
closeTelemetry();
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
}, [removeToast, closeTelemetry]);
const setTelemetryApprovalAndClose = useCallback(
(value: string) => {
setTelemetryValue(value);
onApprovalClose();
},
[setTelemetryValue, onApprovalClose]
);
if (isTelemetryPopupNeeded) {
const toast: Toast = {
id: TELEMETRY_APPROVAL_TOAST_ID,
intent: Intent.Primary,
content: (
<>
<h3 className="mb-1 text-sm uppercase">
{t('Improve vega console')}
</h3>
<TelemetryApproval
telemetryValue={telemetryValue}
setTelemetryValue={setTelemetryApprovalAndClose}
/>
</>
),
onClose: onApprovalClose,
};
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
setToast(toast);
}
return;
}
const title = (
<span className="font-alpha calt" data-testid="welcome-title">
{t('Console')}{' '}
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const WithdrawalsMenu = () => {
@@ -7,6 +7,7 @@ export const WithdrawalsMenu = () => {
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Withdraw })}
data-testid="withdraw-dialog-button"
@@ -1,40 +1,19 @@
import { renderHook, act, waitFor } from '@testing-library/react';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
import {
STORAGE_KEY,
STORAGE_SECOND_KEY,
useTelemetryApproval,
} from './use-telemetry-approval';
import { Networks } from '@vegaprotocol/environment';
import { STORAGE_KEY, useTelemetryApproval } from './use-telemetry-approval';
const mockSetValue = jest.fn();
let mockStorageHookApprovalResult: [string | null, jest.Mock] = [
null,
mockSetValue,
];
const mockSetSecondValue = jest.fn();
let mockStorageHookViewedResult: [string | null, jest.Mock] = [
null,
mockSetSecondValue,
];
const mockRemoveValue = jest.fn();
jest.mock('@vegaprotocol/logger');
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useLocalStorage: jest.fn((key: string) => {
if (key === 'vega_telemetry_approval') {
return mockStorageHookApprovalResult;
}
return mockStorageHookViewedResult;
}),
useLocalStorage: jest
.fn()
.mockImplementation(() => [false, mockSetValue, mockRemoveValue]),
}));
let mockVegaEnv = 'test';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
useEnvironment: jest.fn(() => ({
VEGA_ENV: mockVegaEnv,
SENTRY_DSN: 'sentry-dsn',
})),
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
}));
describe('useTelemetryApproval', () => {
@@ -42,71 +21,32 @@ describe('useTelemetryApproval', () => {
jest.clearAllMocks();
});
it('when empty hook should return proper array', () => {
it('hook should return proper array', () => {
const { result } = renderHook(() => useTelemetryApproval());
expect(result.current[0]).toEqual('');
expect(result.current[0]).toEqual(false);
expect(result.current[1]).toEqual(expect.any(Function));
expect(result.current[2]).toEqual(true);
expect(result.current[3]).toEqual(expect.any(Function));
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_SECOND_KEY);
expect(mockSetValue).toHaveBeenCalledWith('true');
expect(mockSetSecondValue).not.toHaveBeenCalledWith('true');
});
it('when approval not empty but viewed is empty should return proper array', () => {
mockStorageHookApprovalResult = ['false', mockSetValue];
const { result } = renderHook(() => useTelemetryApproval());
expect(result.current[0]).toEqual('false');
expect(result.current[1]).toEqual(expect.any(Function));
expect(result.current[2]).toEqual(true);
expect(result.current[3]).toEqual(expect.any(Function));
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_SECOND_KEY);
expect(mockSetValue).not.toHaveBeenCalled();
expect(mockSetSecondValue).not.toHaveBeenCalled();
});
it('when NOT empty hook should return proper array', () => {
mockStorageHookApprovalResult = ['false', mockSetValue];
mockStorageHookViewedResult = ['true', mockSetSecondValue];
const { result } = renderHook(() => useTelemetryApproval());
expect(result.current[0]).toEqual('false');
expect(result.current[1]).toEqual(expect.any(Function));
expect(result.current[2]).toEqual(false);
expect(result.current[3]).toEqual(expect.any(Function));
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
expect(mockSetValue).not.toHaveBeenCalled();
});
it('on mainnet hook should init properly', () => {
mockStorageHookApprovalResult = [null, mockSetValue];
mockVegaEnv = Networks.MAINNET;
renderHook(() => useTelemetryApproval());
expect(mockSetValue).toHaveBeenCalledWith('false');
});
it('hook should init stuff properly', async () => {
const { result } = renderHook(() => useTelemetryApproval());
await act(() => {
result.current[1]('true');
result.current[1](true);
});
await waitFor(() => {
expect(SentryInit).toHaveBeenCalled();
expect(mockSetValue).toHaveBeenCalledWith('true');
expect(mockSetSecondValue).toHaveBeenCalledWith('true');
expect(mockSetValue).toHaveBeenCalledWith('1');
});
});
it('hook should close stuff properly', async () => {
const { result } = renderHook(() => useTelemetryApproval());
await act(() => {
result.current[1]('false');
result.current[1](false);
});
await waitFor(() => {
expect(SentryClose).toHaveBeenCalled();
expect(mockSetValue).toHaveBeenCalledWith('false');
expect(mockSetSecondValue).toHaveBeenCalledWith('true');
expect(mockRemoveValue).toHaveBeenCalledWith();
});
});
});
@@ -1,51 +1,25 @@
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useCallback, useEffect, useState } from 'react';
import { useCallback } from 'react';
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { useEnvironment } from '@vegaprotocol/environment';
export const STORAGE_KEY = 'vega_telemetry_approval';
export const STORAGE_SECOND_KEY = 'vega_telemetry_viewed';
export const useTelemetryApproval = (): [
value: string,
setValue: (value: string) => void,
shouldOpen: boolean,
close: () => void
value: boolean,
setValue: (value: boolean) => void
] => {
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
const defaultTelemetryValue =
VEGA_ENV === Networks.MAINNET ? 'false' : 'true';
const [value, setValue] = useLocalStorage(STORAGE_KEY);
const [viewedValue, setViewedValue] = useLocalStorage(STORAGE_SECOND_KEY);
const [shouldOpen, setShouldOpen] = useState(!value || !viewedValue);
const close = useCallback(() => {
setShouldOpen(false);
setViewedValue('true');
}, [setViewedValue]);
const manageValue = useCallback(
(value: string) => {
if (value === 'true' && SENTRY_DSN) {
const [value, setValue, removeValue] = useLocalStorage(STORAGE_KEY);
const setApprove = useCallback(
(value: boolean) => {
if (value && SENTRY_DSN) {
SentryInit(SENTRY_DSN, VEGA_ENV);
return setValue('true');
return setValue('1');
}
SentryClose();
setValue('false');
removeValue();
},
[setValue, SENTRY_DSN, VEGA_ENV]
[setValue, removeValue, SENTRY_DSN, VEGA_ENV]
);
const setTelemetryValue = useCallback(
(value: string) => {
setShouldOpen(false);
setViewedValue('true');
manageValue(value);
},
[manageValue, setViewedValue]
);
useEffect(() => {
if (!value) {
manageValue(defaultTelemetryValue);
}
}, [value, manageValue, defaultTelemetryValue]);
return [value || '', setTelemetryValue, shouldOpen, close];
return [Boolean(value), setApprove];
};
+6 -21
View File
@@ -4,37 +4,22 @@ export default function Document() {
return (
<>
<Head>
{/*
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="stylesheet" href="https://static.vega.xyz/fonts.css" />
<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"
/>
{/* 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 */}
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<script src="/theme-setter.js" type="text/javascript" async />
</Head>
<Html>
+28 -1
View File
@@ -10,16 +10,23 @@ 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 - Console" />
<meta name="description" content="Vega Protocol - VEGA 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"
@@ -32,6 +39,26 @@ 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 />
</>
+1 -3
View File
@@ -36,7 +36,7 @@ body,
}
/**
* ALLOTMENT
* ALLOTMENT
*/
html {
@@ -81,8 +81,6 @@ html [data-theme='light'] {
--pennant-color-macd-macd: theme(colors.vega.yellow.500);
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
--pennant-candlestick-inner-padding: 0.1;
}
html [data-theme='light'] {
+2
View File
@@ -4,6 +4,7 @@ import produce from 'immer';
interface GlobalStore {
marketId: string | null;
onBoardingDismissed: boolean;
eagerConnecting: boolean;
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
}
@@ -15,6 +16,7 @@ interface PageTitleStore {
export const useGlobalStore = create<GlobalStore>()((set) => ({
marketId: LocalStorage.getItem('marketId') || null,
onBoardingDismissed: false,
eagerConnecting: false,
update: (newState) => {
set(
+1 -1
View File
@@ -7,7 +7,7 @@ envCmd=""
if [[ ! -z "${ENV_NAME}" ]]; then
if [[ "${ENV_NAME}" != "ops-vega" ]]; then
envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
envCmd="envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
fi
fi
+2 -1
View File
@@ -6,11 +6,12 @@ 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 --pure-lockfile
RUN yarn --network-timeout 100000 --pure-lockfile
# work around for different build process in trading
RUN sh docker/docker-build.sh
+4
View File
@@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/accounts",
"version": "0.0.1"
}
+20
View File
@@ -5,6 +5,26 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/accounts",
"tsConfig": "libs/accounts/tsconfig.lib.json",
"project": "libs/accounts/package.json",
"entryFile": "libs/accounts/src/index.ts",
"external": ["react", "react-dom", "react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/accounts/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
@@ -2,8 +2,8 @@ import { ETHERSCAN_ADDRESS, useEtherscanLink } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import {
ActionsDropdown,
TradingDropdownCopyItem,
TradingDropdownItem,
DropdownMenuCopyItem,
DropdownMenuItem,
Link,
VegaIcon,
VegaIconNames,
@@ -30,49 +30,49 @@ export const AccountsActionsDropdown = ({
return (
<ActionsDropdown>
<TradingDropdownItem
<DropdownMenuItem
key={'deposit'}
data-testid="deposit"
onClick={onClickDeposit}
>
<VegaIcon name={VegaIconNames.DEPOSIT} size={16} />
{t('Deposit')}
</TradingDropdownItem>
<TradingDropdownItem
</DropdownMenuItem>
<DropdownMenuItem
key={'withdraw'}
data-testid="withdraw"
onClick={onClickWithdraw}
>
<VegaIcon name={VegaIconNames.WITHDRAW} size={16} />
{t('Withdraw')}
</TradingDropdownItem>
<TradingDropdownItem
</DropdownMenuItem>
<DropdownMenuItem
key={'transfer'}
data-testid="transfer"
onClick={onClickTransfer}
>
<VegaIcon name={VegaIconNames.TRANSFER} size={16} />
{t('Transfer')}
</TradingDropdownItem>
<TradingDropdownItem
</DropdownMenuItem>
<DropdownMenuItem
key={'breakdown'}
data-testid="breakdown"
onClick={onClickBreakdown}
>
<VegaIcon name={VegaIconNames.BREAKDOWN} size={16} />
{t('View usage breakdown')}
</TradingDropdownItem>
<TradingDropdownItem
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
openAssetDialog(assetId, e.target as HTMLElement);
}}
>
<VegaIcon name={VegaIconNames.INFO} size={16} />
{t('View asset details')}
</TradingDropdownItem>
<TradingDropdownCopyItem value={assetId} text={t('Copy asset ID')} />
</DropdownMenuItem>
<DropdownMenuCopyItem value={assetId} text={t('Copy asset ID')} />
{assetContractAddress && (
<TradingDropdownItem>
<DropdownMenuItem>
<Link
href={etherscanLink(
ETHERSCAN_ADDRESS.replace(':hash', assetContractAddress)
@@ -84,7 +84,7 @@ export const AccountsActionsDropdown = ({
{t('View on Etherscan')}
</span>
</Link>
</TradingDropdownItem>
</DropdownMenuItem>
)}
</ActionsDropdown>
);
+11 -15
View File
@@ -11,13 +11,9 @@ import type {
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { COL_DEFS } from '@vegaprotocol/datagrid';
import {
Intent,
TradingButton,
VegaIcon,
VegaIconNames,
TooltipCellComponent,
} from '@vegaprotocol/ui-toolkit';
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
IGetRowsParams,
@@ -190,7 +186,7 @@ export const AccountTable = ({
) : (
<>
<span className="underline">{valueFormatted}</span>
<span className="inline-block ml-2 w-14 text-muted">
<span className="ml-2 inline-block w-14 text-muted">
{t('0.00%')}
</span>
</>
@@ -252,26 +248,26 @@ export const AccountTable = ({
colId: 'accounts-actions',
field: 'asset.id',
...COL_DEFS.actions,
minWidth: showDepositButton ? 105 : COL_DEFS.actions.minWidth,
maxWidth: showDepositButton ? 105 : COL_DEFS.actions.maxWidth,
minWidth: showDepositButton ? 130 : COL_DEFS.actions.minWidth,
maxWidth: showDepositButton ? 130 : COL_DEFS.actions.maxWidth,
cellRenderer: ({
value: assetId,
node,
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
if (!assetId) return null;
if (node.rowPinned && node.data?.balance === '0') {
if (node.rowPinned && node.data?.total === '0') {
return (
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
<TradingButton
size="extra-small"
intent={Intent.Primary}
<Button
size="xs"
variant="primary"
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(assetId);
}}
>
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
</TradingButton>
</Button>
</CenteredGridCellWrapper>
);
}
+8 -8
View File
@@ -8,6 +8,7 @@ import {
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
Button,
TradingFormGroup,
TradingInput,
TradingInputError,
@@ -15,7 +16,6 @@ import {
TradingSelect,
Tooltip,
TradingCheckbox,
TradingButton,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
import { normalizeTransfer } from '@vegaprotocol/wallet';
@@ -276,9 +276,9 @@ export const TransferForm = ({
decimals={asset?.decimals}
/>
)}
<TradingButton type="submit" fill={true}>
<Button type="submit" variant="primary" fill={true}>
{t('Confirm transfer')}
</TradingButton>
</Button>
</form>
);
};
@@ -309,8 +309,8 @@ export const TransferFee = ({
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
return (
<div className="flex flex-col mb-4 text-xs gap-2">
<div className="flex flex-wrap items-center justify-between gap-1">
<div className="mb-4 flex flex-col gap-2 text-xs">
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to %s`,
@@ -324,7 +324,7 @@ export const TransferFee = ({
{formatNumber(fee, decimals)}
</div>
</div>
<div className="flex flex-wrap items-center justify-between gap-1">
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The total amount to be transferred (without the fee)`
@@ -337,7 +337,7 @@ export const TransferFee = ({
{formatNumber(amount, decimals)}
</div>
</div>
<div className="flex flex-wrap items-center justify-between gap-1">
<div className="flex justify-between gap-1 items-center flex-wrap">
<Tooltip
description={t(
`The total amount taken from your account. The amount to be transferred plus the fee.`
@@ -384,7 +384,7 @@ export const AddressField = ({
setIsInput((curr) => !curr);
onChange();
}}
className="absolute top-0 right-0 ml-auto text-sm underline"
className="ml-auto text-sm absolute top-0 right-0 underline"
>
{isInput ? t('Select from wallet') : t('Enter manually')}
</button>
+5 -1
View File
@@ -1,4 +1,8 @@
{
"name": "@vegaprotocol/announcements",
"version": "0.0.3"
"version": "0.0.2",
"peerDependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
}
+2 -8
View File
@@ -13,15 +13,9 @@
"tsConfig": "libs/announcements/tsconfig.lib.json",
"project": "libs/announcements/package.json",
"entryFile": "libs/announcements/src/index.ts",
"external": [
"react",
"react-dom",
"react/jsx-runtime",
"@vegaprotocol/react-helpers",
"@vegaprotocol/ui-toolkit"
],
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "swc",
"compiler": "babel",
"assets": [
{
"glob": "libs/announcements/README.md",
+4
View File
@@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/apollo-client",
"version": "0.0.1"
}
+10
View File
@@ -4,6 +4,16 @@
"sourceRoot": "libs/apollo-client/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/apollo-client",
"main": "libs/apollo-client/src/index.ts",
"tsConfig": "libs/apollo-client/tsconfig.lib.json",
"assets": ["libs/apollo-client/*.md"]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
+4
View File
@@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/assets",
"version": "0.0.1"
}
+20
View File
@@ -5,6 +5,26 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/assets",
"tsConfig": "libs/assets/tsconfig.lib.json",
"project": "libs/assets/package.json",
"entryFile": "libs/assets/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/assets/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],

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