Compare commits

...
Author SHA1 Message Date
Matthew Russell 40b12e5f6d fix: switch data provider to allow candles 2023-09-12 11:09:00 -07:00
Matthew Russell cdb34647c0 chore: combine latest column defs for market list changes to market code col 2023-09-12 11:00:45 -07:00
Matthew Russell 8b7aef95e4 fix: ts error in proposals mock 2023-09-12 10:46:33 -07:00
Matthew Russell 1b7772fc5b chore: update closed market list test 2023-09-12 10:46:33 -07:00
Matthew Russell 99fb3dd1e6 test: add unit test to ensure sccr and prnt are rendered properly 2023-09-12 10:46:33 -07:00
Matthew Russell d2fc056ec7 feat: swap text so parentId is labels market correctly as successor 2023-09-12 10:46:33 -07:00
Matthew Russell 859d66099b chore: remove unused imports 2023-09-12 10:46:33 -07:00
Matthew Russell 8188995a55 test: fix closed table tests 2023-09-12 10:46:33 -07:00
Matthew Russell 3ca941e768 test: fix evergreen tests 2023-09-12 10:46:33 -07:00
Matthew Russell 2178767465 chore: add t funcs 2023-09-12 10:46:32 -07:00
Matthew Russell 361f14a8a0 refactor: successor cell as it no longer needs flag 2023-09-12 10:46:31 -07:00
Matthew Russell 4d4eea1138 chore: move stack cell to data grid lib 2023-09-12 10:43:22 -07:00
Matthew Russell 0ac2bca27f chore: rename markets component to open-markets 2023-09-12 10:43:22 -07:00
Matthew Russell dae24d9cb7 chore: add successorMarketID and parentMarketID to market provider 2023-09-12 10:43:22 -07:00
Matthew Russell e974a17907 chore: remove unnecessary div 2023-09-12 10:43:21 -07:00
ArtandMatthew Russell 41804d9869 chore(trading): market list tweaks (#4756)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-09-12 10:41:45 -07:00
Maciek 293288286b chore(trading): adjust gap between candles (#4753) 2023-09-12 09:59:13 -07:00
Matthew Russell a77765b1e9 feat(trading): store study sizes, reduce candles gap (#4708) 2023-09-12 09:56:43 -07:00
MaciekandMatthew Russell 2453d7841a chore(trading): open connect dialog from onboarding dialog (#4744)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-09-12 08:07:37 -07:00
Sam KeenandJoe a0945be721 fix(governance,environment): adjust proposal user journey (#4679)
Co-authored-by: Joe <joe@vega.xyz>
2023-09-11 22:04:30 +01:00
Maciek 84e40ccb2b chore(deal-ticket): make margin warning more subtle (#4732) 2023-09-11 16:46:16 +02:00
daro-maj a868f6d2f5 test(trading): update acs for deal ticket (#4739) 2023-09-11 11:17:07 +02:00
m.rayandMatthew Russell 8b83c51014 feat(trading): improve connect wallet description (#4726)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-09-11 08:48:27 +00:00
Maciek bf7aacf984 chore(trading): initial number of candles depended on pane width (#4737) 2023-09-11 10:27:03 +02:00
Matthew Russell 5183f2591b chore(trading,governance,explorer): add frontend-qa to codeowners (#4735) 2023-09-08 17:00:18 -07:00
Maciek b3308439bd chore(candles-chart): show more candles by default (#4734) 2023-09-08 15:15:36 -07:00
Bartłomiej Głownia 719d210219 chore(trading): remove target build from libs (#4725) 2023-09-08 13:43:54 -07:00
Edd f810a3a0b1 chore(trading): maintenance on ipfs build process (#4736) 2023-09-08 10:50:19 -07:00
Sam Keen db19ee80ac feat(governance): improve the display of Governance app on mobile (#4724) 2023-09-08 09:48:09 +00:00
Sam Keen 45623f33c5 fix(governance): corrected sorting of upgrade proposals by blockheight (#4729) 2023-09-08 09:19:59 +00:00
Joe Tsang 08e204fe31 test(explorer): test for network param grouping (#4710) 2023-09-07 15:15:41 +01:00
Joe Tsang 81f7b0595e chore(governance): update tests with vote changes (#4706) 2023-09-07 15:14:37 +01:00
Radosław Szpiech 4ed0e3a056 chore(trading): add console-test runner (#4711) 2023-09-07 13:22:01 +02:00
47a84b4dac feat(deal-ticket): show maximum number of active stop orders limit warning (#4687)
Co-authored-by: Maciek <maciek@vegaprotocol.io>
Co-authored-by: Edd <edd@vega.xyz>
Co-authored-by: Joe Tsang <30622993+jtsang586@users.noreply.github.com>
Co-authored-by: Sam Keen <samuel.kleinmann@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-09-07 09:25:26 +00:00
Matthew Russell c540d4b17f chore(utils): bump utils version 2023-09-06 22:13:24 -07:00
Matthew Russell f1fa3250a4 chore(utils): remove externals from utils 2023-09-06 22:12:21 -07:00
Matthew Russell 707c00def2 fix(trading,governance,explorer): peer deps for published packages (#4721) 2023-09-06 17:04:52 -07:00
Matthew Russell 78aff54360 chore(trading): remove duplicate stylesheet and icon references (#4707) 2023-09-06 13:51:41 -07:00
Maciek 6160fd7638 chore(trading): 4594 ci optimisations (#4688) 2023-09-06 13:35:49 -07:00
Dexter Edwards c345623a06 feat: add marketCreationQuantumMultiple into tooltip (#3706) 2023-09-06 13:34:18 -07:00
Sam Keen e9173796f5 fix(governance,environment,proposals): network upgrade proposal urls include block height (#4704) 2023-09-06 10:56:18 +00:00
187 changed files with 2244 additions and 2817 deletions
+97 -42
View File
@@ -21,7 +21,6 @@ jobs:
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Cache node modules
id: cache
uses: actions/cache@v3
@@ -39,7 +38,6 @@ jobs:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: yarn install
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile
@@ -51,11 +49,11 @@ jobs:
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
lint-test-build:
timeout-minutes: 60
lint-format:
timeout-minutes: 20
needs: node-modules
runs-on: ubuntu-22.04
name: '(CI) lint + unit test + build'
name: '(CI) lint + format check'
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -81,6 +79,76 @@ jobs:
with:
main-branch-name: develop
- name: Check formatting
run: yarn nx format:check
- name: Lint affected
run: yarn nx affected:lint --max-warnings=0
- name: Build affected spec
run: yarn nx affected --target=build-spec
test-affected:
timeout-minutes: 30
needs: build-sources
runs-on: ubuntu-22.04
name: 'run unit test of affected apps'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: develop
- name: Test affected
run: yarn nx affected:test
build-sources:
timeout-minutes: 30
needs: lint-format
runs-on: ubuntu-22.04
name: 'Build sources of affected apps'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: develop
# See affected apps
- name: See affected apps
run: |
@@ -97,21 +165,8 @@ jobs:
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
- name: Check formatting
run: yarn nx format:check
- name: Lint affected
run: yarn nx affected:lint --max-warnings=0
- name: Build affected spec
run: yarn nx affected --target=build-spec
- name: Test affected
run: yarn nx affected:test
- name: Build affected
run: yarn nx affected:build || (yarn install && yarn nx affected:build)
outputs:
projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }}
@@ -121,38 +176,38 @@ jobs:
preview_tools: ${{ env.PREVIEW_TOOLS }}
console-e2e:
needs: lint-test-build
needs: build-sources
name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml
secrets: inherit
if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
if: ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
with:
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
cypress:
needs: lint-test-build
needs: build-sources
name: '(CI) cypress'
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
projects: ${{ needs.build-sources.outputs.projects-e2e }}
tags: '@smoke'
publish-dist:
needs: lint-test-build
needs: build-sources
name: '(CD) publish dist'
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }}
uses: ./.github/workflows/publish-dist.yml
secrets: inherit
with:
projects: ${{ needs.lint-test-build.outputs.projects }}
projects: ${{ needs.build-sources.outputs.projects }}
dist-check:
runs-on: ubuntu-latest
needs:
- publish-dist
- lint-test-build
- build-sources
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }}
timeout-minutes: 60
name: '(CD) comment preview links'
@@ -168,27 +223,27 @@ jobs:
run: |
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
if [[ "${{ needs.lint-test-build.outputs.preview_governance }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_governance }}"; do
echo "waiting for governance preview: ${{ needs.lint-test-build.outputs.preview_governance }}"
if [[ "${{ needs.build-sources.outputs.preview_governance }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_governance }}"; do
echo "waiting for governance preview: ${{ needs.build-sources.outputs.preview_governance }}"
sleep 5
done
fi
if [[ "${{ needs.lint-test-build.outputs.preview_explorer }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_explorer }}"; do
echo "waiting for explorer preview: ${{ needs.lint-test-build.outputs.preview_explorer }}"
if [[ "${{ needs.build-sources.outputs.preview_explorer }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_explorer }}"; do
echo "waiting for explorer preview: ${{ needs.build-sources.outputs.preview_explorer }}"
sleep 5
done
fi
if [[ "${{ needs.lint-test-build.outputs.preview_trading }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_trading }}"; do
echo "waiting for trading preview: ${{ needs.lint-test-build.outputs.preview_trading }}"
if [[ "${{ needs.build-sources.outputs.preview_trading }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_trading }}"; do
echo "waiting for trading preview: ${{ needs.build-sources.outputs.preview_trading }}"
sleep 5
done
fi
if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
echo "waiting for tools preview: ${{ needs.lint-test-build.outputs.preview_tools }}"
if [[ "${{ needs.build-sources.outputs.preview_tools }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.build-sources.outputs.preview_tools }}"; do
echo "waiting for tools preview: ${{ needs.build-sources.outputs.preview_tools }}"
sleep 5
done
fi
@@ -200,10 +255,10 @@ jobs:
issue-number: ${{ github.event.pull_request.number }}
body: |
Previews
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
* tools: ${{ needs.lint-test-build.outputs.preview_tools }}
* governance: ${{ needs.build-sources.outputs.preview_governance }}
* explorer: ${{ needs.build-sources.outputs.preview_explorer }}
* trading: ${{ needs.build-sources.outputs.preview_trading }}
* tools: ${{ needs.build-sources.outputs.preview_tools }}
# Report single result at the end, to avoid mess with required checks in PR
cypress-check:
+2 -37
View File
@@ -10,7 +10,7 @@ on:
jobs:
run-tests:
name: run-tests
runs-on: 8-cores
runs-on: console-test
timeout-minutes: 20
steps:
#----------------------------------------------
@@ -33,15 +33,6 @@ jobs:
restore-keys: |
${{ runner.os }}-cache-node-modules-
#----------------------------------------------
# setup node
#----------------------------------------------
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
#----------------------------------------------
# install deps if cache missing
#----------------------------------------------
- name: yarn install
@@ -76,32 +67,6 @@ jobs:
repository: vegaprotocol/console-test
path: './console-test'
#----------------------------------------------
# set-up python
#----------------------------------------------
- name: Set up python
id: setup-python
uses: actions/setup-python@v4
with:
python-version: '3.10.11'
#----------------------------------------------
# ----- install & configure poetry -----
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: console-test/.venv
#----------------------------------------------
# load cached venv if cache exists
#----------------------------------------------
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: console-test/.venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
#----------------------------------------------
# install dependencies if cache does not exist
#----------------------------------------------
- name: Install dependencies
@@ -125,7 +90,7 @@ jobs:
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
+3 -1
View File
@@ -37,6 +37,7 @@ jobs:
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
id: cache-node-modules
uses: actions/cache@v3
with:
path: |
@@ -46,6 +47,7 @@ jobs:
# Install frontend dependencies
- name: Install root dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
@@ -67,7 +69,7 @@ jobs:
######
- name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}"
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
+1 -1
View File
@@ -1 +1 @@
16.15.1
16.20.2
+1
View File
@@ -1,2 +1,3 @@
* @vegaprotocol/frontend
* @vegaprotocol/frontend-qa
*.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 -r /usr/share/nginx/html
docker run --rm ${TAG} ipfs add -rQ /usr/share/nginx/html
.PHONY: eject-ipfs-hash
unpack:
+24 -24
View File
@@ -115,11 +115,31 @@ docker run -p 3000:80 [TAG]
## Build instructions
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of console either for the web, or ready for pinning on IPFS
The [`docker`](./docker) subfolder has some docker configurations for easily setting up your own hosted version of Console either for the web, or ready for pinning on IPFS.
### nx build inside the docker
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
At the moment this feature is important only for Console releases.
Each docker build finishes with hash calculation for ` dist`` directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
```bash
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
```
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
### nx build outside the docker
Packaging prepared dist into [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image involves building the application on docker host machine from source.
This Docker image packages a pre-built `dist` folder into an [`nginx`](https://hub.docker.com/_/nginx)([server configuration](./nginx/nginx.conf)) docker image. In this case, the application on docker host machine from source.
As a prerequisite you need to perform build of `dist` directory and move its content for specific application to `dist-result` directory. Use following script to do it with a single command:
@@ -133,38 +153,18 @@ You can build any of the containers locally with the following command:
docker build --dockerfile docker/node-outside-docker.Dockerfile . --tag=[TAG]
```
### nx build inside the docker
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build) example.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=$(cat .nvmrc) --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
At the moment this feature is important only for `trading` (console) releases.
Each docker build finishes with hash calculation for dist directory. Resulting hash is added to file named as `/ipfs-hash`. Once docker image is produced you can run following commad to display ipfs-hash:
```bash
make recalculate-ipfs TAG=vegaprotocol/trading:{YOUR_VERSION}
```
**updating hash:** recompiling dist directory (even if there are no changed to source code) results in different hash computed by ipfs command.
### Verifying ipfs-hash of existing current application version
An IPFS CID will be attached to every [release](https://github.com/vegaprotocol/frontend-monorepo/releases). If you are intending to pin an application on IPFS, you can check that your build matches by running the following steps:
1. Show latest release by runnning: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
1. Show latest release by running: `make latest-release`. You need to configure [`gh`](https://cli.github.com/) for this step to work, otherwise please provide release manually from [github](https://github.com/vegaprotocol/frontend-monorepo/releases) or [dockerhub](https://hub.docker.com/r/vegaprotocol/trading)
2. Set RELEASE environment variable to value that you want to validate: `export RELEASE=$(make latest-release)` or `export RELEASE=vXX.XX.XX`
3. Set TAG environment variable to image that you want to validate: `export TAG=vegaprotocol/trading:$RELEASE`
4. Download docker image with the desired release `docker pull $TAG`.
5. Recalculate hash: `make recalculate-ipfs`
6. You should see exactly same hash produced by ipfs command as one placed with the release notes: `make show-latest-release`
7. If you want to extract dist from docker image to your local filesystem you can run following command: `make unpack`
8. Now `dist` directory contains valid application build. **it is not possible to calculate same ipfs hash on files that are result of copy operation**
8. Now `dist` directory contains valid application build
## Config
@@ -1,12 +1,9 @@
context('Network parameters page', { tags: '@smoke' }, function () {
before('navigate to network parameter page', function () {
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
cy.visit('/network-parameters');
});
describe('Verify elements on page', function () {
beforeEach(() => {
cy.visit('/network-parameters');
});
const networkParametersHeader = '[data-testid="network-param-header"]';
const tableRows = '[data-testid="key-value-table-row"]';
@@ -16,6 +13,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
.and('be.visible');
});
// 0006-NETW-021
it('should list each of the network parameters available', function () {
cy.get_network_parameters().then((network_parameters) => {
const numberOfNetworkParametersInSystem =
@@ -198,6 +196,19 @@ context('Network parameters page', { tags: '@smoke' }, function () {
});
});
// 0006-NETW-022 0006-NETW-023
it('governance assets should be correctly grouped', function () {
cy.getByTestId('governance')
.should('exist')
.parent()
.should('have.attr', 'href', '/network-parameters#governance');
cy.get('[id="governance-proposal-asset"]')
.parent()
.within(() => {
cy.getByTestId('key-value-table-row').should('have.length', 8);
});
});
it('should be able to see network parameters - on mobile', function () {
cy.switchToMobile();
cy.get_network_parameters().then((network_parameters) => {
@@ -34,19 +34,20 @@ import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
const proposalListItem = '[data-testid="proposals-list-item"]';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
const proposalVoteProgressAgainstPercentage =
'vote-progress-indicator-percentage-against';
const proposalVoteProgressForTokens = 'vote-progress-indicator-tokens-for';
const proposalVoteProgressAgainstTokens =
'vote-progress-indicator-tokens-against';
const participationNotMet = 'token-participation-not-met';
const voteStatus = 'vote-status';
const voteMajorityNotMet = 'token-majority-not-met';
const numberOfVotesFor = 'num-votes-for';
const votesForPercentage = 'votes-for-percentage';
const numberOfVotesAgainst = 'num-votes-against';
const votesAgainstPercentage = 'votes-against-percentage';
const totalVotedNumber = 'total-voted';
const totalVotedPercentage = 'total-voted-percentage';
const changeVoteButton = 'change-vote-button';
const proposalDetailsTitle = 'proposal-title';
const proposalDetailsDescription = 'proposal-description';
const openProposals = 'open-proposals';
const viewProposalButton = 'view-proposal-btn';
const voteBreakdownToggle = 'vote-breakdown-toggle';
const proposalTermsToggle = 'proposal-json-toggle';
const marketDataToggle = 'proposal-market-data-toggle';
@@ -150,29 +151,32 @@ describe(
// 3001-VOTE-037
// 3001-VOTE-040
// 3001-VOTE-067
// 3001-VOTE-023
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
);
});
cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
'be.visible'
cy.getByTestId(participationNotMet).should(
'have.text',
'0.000000000000000000000015% participation threshold not met'
);
cy.getByTestId(voteMajorityNotMet).should(
'have.text',
'66% majority threshold not met'
);
cy.getByTestId(voteStatus).should(
'have.text',
'Currently expected to fail'
);
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Expected to pass')
.contains('👎')
.should('be.visible');
// 3001-VOTE-062
// 3001-VOTE-040
// 3001-VOTE-070
getProposalInformationFromTable('Token majority met')
.contains('👎')
.should('be.visible');
// 3001-VOTE-068
getProposalInformationFromTable('Token participation met')
.contains('👎')
.should('be.visible');
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
cy.getByTestId(numberOfVotesAgainst).should('have.text', '0.0M');
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
});
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
@@ -197,43 +201,16 @@ describe(
.contains(votedDate)
.should('be.visible');
});
cy.getByTestId(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%')
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('0.00%')
.and('be.visible');
cy.getByTestId(proposalVoteProgressForTokens)
.contains('1.00')
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Tokens for proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
getProposalInformationFromTable('Tokens against proposal')
.should('have.text', '0.00')
.and('be.visible');
// 3001-VOTE-061
getProposalInformationFromTable('Participation required')
.contains('0.00%')
.should('be.visible');
// 3001-VOTE-066
getProposalInformationFromTable('Majority Required') // 3001-VOTE-073
.contains(`${(66).toFixed(2)}%`)
.should('be.visible');
getProposalInformationFromTable('Number of voting parties')
.should('have.text', '1')
.and('be.visible');
cy.getByTestId(votesForPercentage) // 3001-VOTE-072
.should('have.text', '100%');
cy.getByTestId(votesAgainstPercentage).should('have.text', '0%');
cy.getByTestId('token-majority-progress')
.should('have.attr', 'style')
.and('eq', 'width: 100%;'); // 3001-VOTE-024
cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('for');
// 3001-VOTE-064
cy.getByTestId('user-voted-yes').should('exist');
getProposalInformationFromTable('Tokens for proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
@@ -244,16 +221,7 @@ describe(
});
cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('against');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('100.00%')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Tokens against proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
getProposalInformationFromTable('Number of voting parties')
.should('have.text', '1')
.and('be.visible');
cy.getByTestId(votesAgainstPercentage).should('have.text', '100%');
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
@@ -265,6 +233,7 @@ describe(
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
it('Newly created proposal details - ability to increase associated tokens - by voting again after association', function () {
ensureSpecifiedUnstakedTokensAreAssociated('1');
vegaWalletSetSpecifiedApprovalAmount('1000');
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
@@ -275,69 +244,28 @@ describe(
voteForProposal('for');
// 3001-VOTE-079
cy.contains('You voted: For').should('be.visible');
cy.getByTestId(proposalVoteProgressForTokens)
.contains('1')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Total Supply')
.invoke('text')
.then((totalSupply) => {
const tokensRequiredToAchieveResult = (
(Number(totalSupply.replace(/,/g, '')) * 0.001) /
100
).toFixed(2);
ethereumWalletConnect();
ensureSpecifiedUnstakedTokensAreAssociated(
tokensRequiredToAchieveResult
);
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
);
});
cy.getByTestId(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('0.00%')
.and('be.visible');
// 3001-VOTE-065
cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('for');
cy.getByTestId(proposalVoteProgressForTokens)
.contains(tokensRequiredToAchieveResult)
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Total tokens voted percentage')
.should('have.text', '0.00%')
.and('be.visible');
getProposalInformationFromTable('Tokens for proposal')
.should('have.text', tokensRequiredToAchieveResult)
.and('be.visible');
getProposalInformationFromTable('Tokens against proposal')
.should('have.text', '0.00')
.and('be.visible');
getProposalInformationFromTable('Number of voting parties')
.should('have.text', '1')
.and('be.visible');
getProposalInformationFromTable('Expected to pass')
.contains('👍')
.should('be.visible');
// 3001-VOTE-062
getProposalInformationFromTable('Token majority met')
.contains('👍')
.should('be.visible');
getProposalInformationFromTable('Token participation met')
.contains('👍')
.should('be.visible');
getProposalInformationFromTable('Tokens for proposal')
.contains(tokensRequiredToAchieveResult)
.and('be.visible');
});
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
cy.getByTestId(votesForPercentage).should('have.text', '100%');
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
ethereumWalletConnect();
stakingPageAssociateTokens('1000000', { approve: true });
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
);
});
cy.getByTestId(votesForPercentage).should('have.text', '100%');
cy.getByTestId(numberOfVotesFor).should('have.text', '0.0M');
cy.getByTestId(totalVotedNumber).should('have.text', '0.0M');
cy.getByTestId(totalVotedPercentage).should('have.text', '(0.00%)');
// 3001-VOTE-065
cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('for');
cy.getByTestId(numberOfVotesFor).should('have.text', '1.0M');
cy.getByTestId(totalVotedNumber).should('have.text', '1.0M');
cy.getByTestId(totalVotedPercentage).should('have.text', '(1.54%)');
});
it('Able to vote for proposal twice by switching public key', function () {
@@ -360,10 +288,6 @@ describe(
voteForProposal('against');
cy.contains('You voted: Against').should('be.visible');
switchVegaWalletPubKey();
cy.getByTestId(proposalVoteProgressForTokens).should(
'contain.text',
'1.00'
);
// Checking vote status for different public keys is displayed correctly
cy.contains('You voted: For').should('be.visible');
});
@@ -22,12 +22,10 @@ const proposalListItem = '[data-testid="proposals-list-item"]';
const closedProposals = 'closed-proposals';
const proposalStatus = 'proposal-status';
const viewProposalButton = 'view-proposal-btn';
const votesTable = 'votes-table';
const votesTable = 'user-vote';
const openProposals = 'open-proposals';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
const majorityVoteReached = 'majority-reached';
const minParticipationReached = 'participation-reached';
const majorityVoteReached = 'token-majority-met';
const minParticipationReached = 'token-participation-met';
const proposalTimeout = { timeout: 8000 };
context(
@@ -70,7 +68,6 @@ context(
cy.getByTestId('proposal-type').should('have.text', 'New market');
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
cy.getByTestId(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
});
@@ -92,7 +89,7 @@ context(
// 3001-VOTE-019 time to vote is highlighted red
cy.getByTestId('vote-details')
.find('span')
.should('have.class', 'text-vega-pink');
.should('have.class', 'text-vega-orange');
cy.getByTestId(viewProposalButton).click();
});
});
@@ -109,12 +106,9 @@ context(
);
});
cy.getByTestId(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
cy.getByTestId(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
cy.getByTestId('votes-for-percentage').should('have.text', '100%');
navigateTo(navigation.proposals);
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
@@ -298,9 +298,6 @@ context(
getProposalFromTitle(proposalTitle).within(() =>
cy.getByTestId(viewProposalButton).click()
);
cy.contains('Vote breakdown').should('be.visible', {
timeout: 10000,
});
cy.getByTestId(voteButtons).should('not.exist');
cy.getByTestId('min-proposal-requirements').should(
'have.text',
@@ -52,8 +52,6 @@ const enactmentDeadlineError = 'enactment-before-voting-deadline';
const proposalDownloadBtn = 'proposal-download-json';
const feedbackError = '[data-testid="Error"]';
const viewProposalBtn = 'view-proposal-btn';
const liquidityVoteStatus = 'liquidity-votes-status';
const tokenVoteStatus = 'token-votes-status';
const proposalJsonToggle = 'proposal-json-toggle';
const proposalJsonSection = 'proposal-json';
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
@@ -421,27 +419,28 @@ context(
cy.getByTestId(viewProposalBtn).click();
});
});
cy.getByTestId(liquidityVoteStatus).should(
'contain.text',
'Currently expected to fail'
cy.getByTestId('lp-majority-not-met').should(
'have.text',
'66% majority threshold not met'
);
cy.getByTestId(tokenVoteStatus).should(
'contain.text',
'Currently expected to fail'
cy.getByTestId('token-majority-not-met').should(
'have.text',
'66% majority threshold not met'
);
voteForProposal('for');
cy.getByTestId(liquidityVoteStatus).should(
'contain.text',
'Currently expected to pass'
cy.getByTestId('lp-majority-met').should(
'have.text',
'66% majority threshold met'
);
cy.getByTestId(tokenVoteStatus).should(
'contain.text',
'Currently expected to pass'
cy.getByTestId('token-majority-met').should(
'have.text',
'66% majority threshold met'
);
cy.getByTestId('vote-status').should(
'have.text',
'Currently expected to pass by token vote'
);
cy.getByTestId('vote-breakdown-toggle').click();
getProposalInformationFromTable('Expected to pass')
.contains('👍 by token vote')
.should('be.visible');
});
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
@@ -11,7 +11,6 @@ import {
enterRawProposalBody,
generateFreeFormProposalTitle,
getProposalFromTitle,
getProposalInformationFromTable,
goToMakeNewProposal,
governanceProposalType,
submitUniqueRawProposal,
@@ -30,7 +29,9 @@ const proposalType = 'proposal-type';
const proposalStatus = 'proposal-status';
const proposalClosingDate = 'vote-details';
const viewProposalButton = 'view-proposal-btn';
const voteBreakDownToggle = 'vote-breakdown-toggle';
const voteMajorityNotMet = 'token-majority-not-met';
const voteMajorityMet = 'token-majority-met';
const votesForPercentage = 'votes-for-percentage';
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
before('connect wallets and set approval limit', function () {
@@ -121,10 +122,15 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
submitUniqueRawProposal({ proposalTitle: proposalTitle });
getProposalFromTitle(proposalTitle).within(() => {
// 3001-VOTE-039
cy.getByTestId('participation-not-reached').should(
cy.getByTestId(voteMajorityNotMet).should(
'have.text',
'Min. participation not reached'
'66% majority threshold not met'
);
cy.getByTestId('token-participation-not-met').should(
'have.text',
'0.000000000000000000000015% participation threshold not met'
);
cy.getByTestId(votesForPercentage).should('have.text', '0%');
cy.getByTestId(viewProposalButton).click();
});
voteForProposal('for');
@@ -134,16 +140,15 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
'have.text',
'Currently expected to pass'
);
cy.getByTestId('user-voted-yes').should('exist');
cy.getByTestId('participation-reached').should(
cy.getByTestId(voteMajorityMet).should(
'have.text',
'Min. participation reached'
'66% majority threshold met'
);
cy.getByTestId(votesForPercentage).should('have.text', '100%');
cy.getByTestId('token-participation-met').should(
'have.text',
'0.000000000000000000000015% participation threshold met'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(voteBreakDownToggle).click();
getProposalInformationFromTable('Token participation met')
.contains('👍')
.should('be.visible');
});
});
@@ -37,11 +37,39 @@ context(
});
// 3002-PROP-023 3004-PMAC-002 3005-PASN-002 3006-PASC-002 3007-PNEC-002 3008-PFRO-003
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('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 be able to see a working link for - find out more about Vega governance', function () {
@@ -183,7 +211,7 @@ context(
.first()
.find('[data-testid="view-proposal-btn"]')
.click();
cy.url().should('contain', '/protocol-upgrades/v1');
cy.url().should('contain', '/protocol-upgrades/v1/2015942');
cy.getByTestId('protocol-upgrade-proposal').within(() => {
cy.get('h1').should('have.text', 'Vega Release v1');
cy.getByTestId('protocol-upgrade-block-height').should(
@@ -243,7 +271,7 @@ context(
);
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contain', '/proposals/protocol-upgrade/v1');
.and('contain', '/proposals/protocol-upgrade/v1/2015942');
});
// estimate does not display possibly due to mocks or Cypress unless the proposal is clicked on several times
@@ -146,7 +146,7 @@ export function getProposalInformationFromTable(heading: string) {
}
export function voteForProposal(vote: string) {
cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
cy.get(voteButtons).should('be.visible', { timeout: 10000 });
cy.get(voteButtons).contains(vote).click();
cy.get(dialogTitle, proposalTimeout).should(
'have.text',
@@ -219,7 +219,9 @@ export function ensureSpecifiedUnstakedTokensAreAssociated(
.eq(1)
.invoke('text')
.then((unstakedBalance) => {
if (parseFloat(unstakedBalance) != parseFloat(tokenAmount)) {
const tokenFloat = parseFloat(tokenAmount);
const unstakedFloat = parseFloat(unstakedBalance.replace(/,/g, ''));
if (tokenFloat != unstakedFloat) {
vegaWalletTeardown();
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
'0.00',
+2 -2
View File
@@ -184,7 +184,7 @@ const Web3Container = ({
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 border-t border-neutral-700">
<footer className="p-4 border-t border-neutral-700 break-all">
<NetworkInfo />
</footer>
</AppLayout>
@@ -308,7 +308,7 @@ const AppContainer = () => {
<Router>
<ScrollToTop />
<AppStateProvider>
<div className="min-h-full text-white grid">
<div className="min-h-full text-white">
<NodeGuard
skeleton={<div>{t('Loading')}</div>}
failure={
@@ -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 [rewards.marketCreationQuantumMultiple]",
"rewardsColMarketCreationTooltip": "Market creation rewards are paid out to the creator of any market that exceeds a set threshold of cumulative volume in a given epoch, currently {{marketCreationQuantumMultiple}}",
"rewardsColTotalHeader": "TOTAL",
"ofTotalDistributed": "of total distributed",
"checkBackSoon": "Check back soon",
@@ -708,7 +708,7 @@
"NewAssetProposal": "New asset proposal",
"UpdateAssetProposal": "Update asset proposal",
"NewFreeformProposal": "New freeform proposal",
"NewRawProposal": "New raw proposal",
"NewRawProposal": "New 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,5 +870,9 @@
"Upgraded at": "Upgraded at",
"dataIsIdentical": "Data is identical",
"updatesToMarket": "Updates to market",
"viewAsParty": "View as party"
"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"
}
+3 -3
View File
@@ -279,8 +279,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
trimmedActiveNodes={trimmedActiveNodes}
/>
<section className="grid grid-cols-2 gap-12 mb-16">
<div data-testid="home-rewards">
<section className="flex justify-between flex-wrap gap-12 mb-16">
<div className="min-w-[360px] flex-1" data-testid="home-rewards">
<Heading title={t('Rewards')} marginTop={false} />
<h3 className="mb-6">{t('homeRewardsIntro')}</h3>
<div className="flex items-center mb-8 gap-4">
@@ -290,7 +290,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
</div>
</div>
<div data-testid="home-vega-token">
<div className="min-w-[360px] flex-1" data-testid="home-vega-token">
<Heading title={t('vegaToken')} marginTop={false} />
<h3 className="mb-6">{t('homeVegaTokenIntro')}</h3>
<div className="flex items-center mb-8 gap-4">
@@ -159,7 +159,7 @@ export const ProposalHeader = ({
</div>
</div>
<div data-testid="proposal-title">
<div data-testid="proposal-title" className="break-all">
{isListItem ? (
<header>
<SubHeading
@@ -7,18 +7,13 @@ 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,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Button, Toggle } 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 { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
import { ExternalLinks } from '@vegaprotocol/environment';
interface ProposalsListProps {
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
@@ -55,7 +50,10 @@ export const orderByUpgradeBlockHeight = (
) =>
orderBy(
arr,
[(p) => p?.upgradeBlockHeight, (p) => p.vegaReleaseTag],
[
(p) => (p?.upgradeBlockHeight ? parseInt(p.upgradeBlockHeight, 10) : 0),
(p) => p.vegaReleaseTag,
],
['desc', 'desc']
);
@@ -142,18 +140,13 @@ export const ProposalsList = ({
title={t('pageTitleProposals')}
/>
{DocsLinks && (
<div className="xs:justify-self-end" data-testid="new-proposal-link">
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
<Button variant="primary" size="sm">
<div className="flex items-center gap-1">
{t('NewProposal')}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</div>
</Button>
</ExternalLink>
</div>
)}
<div 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>
</div>
<p className="mb-8">
@@ -224,10 +217,10 @@ export const ProposalsList = ({
sortedProtocolUpgradeProposals.closed.length > 0 &&
filterString.length < 1 && (
<div
className="grid w-full justify-end xl:-mt-12 pb-6"
className="flex justify-end xl:-mt-12 pb-6"
data-testid="toggle-closed-proposals"
>
<div className="w-[440px]">
<div className="w-full max-w-[420px]">
<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,7 +130,10 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
? t('byTokenVote')
: t('byLiquidityVote');
const sectionWrapperClasses = classNames('grid sm:grid-cols-2 gap-6');
const sectionWrapperClasses = classNames(
'flex justify-between flex-wrap gap-6'
);
const sectionClasses = classNames('min-w-[300px] flex-1 flex-grow');
const headingClasses = classNames('mb-2 text-vega-dark-400');
const progressDetailsClasses = classNames(
'flex justify-between flex-wrap mt-2 text-sm'
@@ -166,7 +169,10 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="mb-4">
<h3 className={headingClasses}>{t('liquidityProviderVote')}</h3>
<div className={sectionWrapperClasses}>
<section data-testid="lp-majority-breakdown">
<section
className={sectionClasses}
data-testid="lp-majority-breakdown"
>
<VoteProgress
percentageFor={yesLPPercentage}
colourfulBg={true}
@@ -241,7 +247,10 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
</div>
</section>
<section data-testid="lp-participation-breakdown">
<section
className={sectionClasses}
data-testid="lp-participation-breakdown"
>
<VoteProgress
percentageFor={
lpParticipationThresholdProgress || new BigNumber(0)
@@ -288,7 +297,10 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
{isUpdateMarket && <h3 className={headingClasses}>{t('tokenVote')}</h3>}
<div className={sectionWrapperClasses}>
<section data-testid="token-majority-breakdown">
<section
className={sectionClasses}
data-testid="token-majority-breakdown"
>
<VoteProgress
percentageFor={yesPercentage}
colourfulBg={true}
@@ -308,7 +320,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="flex items-center gap-1">
<span>{t('tokenVotesFor')}:</span>
<Tooltip description={formatNumber(yesTokens, defaultDP)}>
<button>
<button data-testid="num-votes-for">
{yesTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
</button>
</Tooltip>
@@ -317,7 +329,9 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<Tooltip
description={<span>{yesPercentage.toFixed(defaultDP)}%</span>}
>
<button>{yesPercentage.toFixed(0)}%</button>
<button data-testid="votes-for-percentage">
{yesPercentage.toFixed(0)}%
</button>
</Tooltip>
)
</span>
@@ -326,7 +340,7 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="flex items-center gap-1">
<span>{t('tokenVotesAgainst')}:</span>
<Tooltip description={formatNumber(noTokens, defaultDP)}>
<button>
<button data-testid="num-votes-against">
{noTokens.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
</button>
</Tooltip>
@@ -335,7 +349,9 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<Tooltip
description={<span>{noPercentage.toFixed(defaultDP)}%</span>}
>
<button>{noPercentage.toFixed(0)}%</button>
<button data-testid="votes-against-percentage">
{noPercentage.toFixed(0)}%
</button>
</Tooltip>
)
</span>
@@ -343,7 +359,10 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
</div>
</section>
<section data-testid="token-participation-breakdown">
<section
className={sectionClasses}
data-testid="token-participation-breakdown"
>
<VoteProgress
percentageFor={participationThresholdProgress}
testId="token-participation-progress"
@@ -364,11 +383,13 @@ export const VoteBreakdown = ({ proposal }: VoteBreakdownProps) => {
<div className="flex items-center gap-1">
<span>{t('totalTokensVoted')}:</span>
<Tooltip description={formatNumber(totalTokensVoted, defaultDP)}>
<button>
<button data-testid="total-voted">
{totalTokensVoted.dividedBy(toBigNum(10 ** 6, 0)).toFixed(1)}M
</button>
</Tooltip>
<span>({totalTokensPercentage.toFixed(defaultDP)}%)</span>
<span data-testid="total-voted-percentage">
({totalTokensPercentage.toFixed(defaultDP)}%)
</span>
</div>
</div>
</section>
@@ -1,12 +1,17 @@
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { Heading } from '../../../../components/heading';
import {
useEnvironment,
DocsLinks,
ExternalLinks,
} from '@vegaprotocol/environment';
import { Heading, SubHeading } from '../../../../components/heading';
import {
AsyncRenderer,
ExternalLink,
FormGroup,
InputError,
RoundedWrapper,
TextArea,
} from '@vegaprotocol/ui-toolkit';
import { validateJson } from '@vegaprotocol/utils';
@@ -79,15 +84,6 @@ 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')}{' '}
@@ -98,12 +94,44 @@ 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="Make a proposal by submitting JSON"
labelFor="proposal-data"
>
<FormGroup label="Valid JSON required" labelFor="proposal-data">
<TextArea
id="proposal-data"
className="min-h-[200px]"
@@ -43,7 +43,10 @@ describe('EpochIndividualRewardsTable', () => {
it('should render correctly', () => {
const { getByTestId } = render(
<AppStateProvider>
<EpochIndividualRewardsTable data={mockData} />
<EpochIndividualRewardsTable
data={mockData}
marketCreationQuantumMultiple={'1000'}
/>
</AppStateProvider>
);
expect(getByTestId('epoch-individual-rewards-table')).toBeInTheDocument();
@@ -8,6 +8,7 @@ import type { EpochIndividualReward } from './generate-epoch-individual-rewards-
interface EpochIndividualRewardsGridProps {
data: EpochIndividualReward;
marketCreationQuantumMultiple: string | null;
}
interface RewardItemProps {
@@ -69,9 +70,11 @@ const RewardItem = ({
export const EpochIndividualRewardsTable = ({
data,
marketCreationQuantumMultiple,
}: EpochIndividualRewardsGridProps) => {
return (
<RewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
dataTestId="epoch-individual-rewards-table"
epoch={Number(data.epoch)}
>
@@ -9,6 +9,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
import { useNetworkParam } from '@vegaprotocol/network-parameters';
const EPOCHS_PAGE_SIZE = 10;
@@ -26,6 +27,9 @@ export const EpochIndividualRewards = ({
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { delegationsPagination } = ENV;
const { param: marketCreationQuantumMultiple } = useNetworkParam(
'rewards_marketCreationQuantumMultiple'
);
const { data, loading, error, refetch } = useRewardsQuery({
notifyOnNetworkStatusChange: true,
@@ -103,6 +107,7 @@ export const EpochIndividualRewards = ({
{epochIndividualRewardSummaries.map(
(epochIndividualRewardSummary) => (
<EpochIndividualRewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
data={epochIndividualRewardSummary}
/>
)
@@ -66,7 +66,10 @@ describe('EpochTotalRewardsTable', () => {
it('should render correctly', () => {
const { getByTestId } = render(
<AppStateProvider>
<EpochTotalRewardsTable data={mockData} />
<EpochTotalRewardsTable
data={mockData}
marketCreationQuantumMultiple={'1000'}
/>
</AppStateProvider>
);
expect(getByTestId('epoch-total-rewards-table')).toBeInTheDocument();
@@ -8,6 +8,7 @@ import type { EpochTotalSummary } from './generate-epoch-total-rewards-list';
interface EpochTotalRewardsGridProps {
data: EpochTotalSummary;
marketCreationQuantumMultiple: string | null;
}
interface RewardItemProps {
@@ -52,9 +53,14 @@ const RewardItem = ({
export const EpochTotalRewardsTable = ({
data,
marketCreationQuantumMultiple,
}: EpochTotalRewardsGridProps) => {
return (
<RewardsTable dataTestId="epoch-total-rewards-table" epoch={data.epoch}>
<RewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
dataTestId="epoch-total-rewards-table"
epoch={data.epoch}
>
{Array.from(data.assetRewards.values()).map(
({ name, rewards, totalAmount, decimals }, i) => (
<div className="contents" key={i}>
@@ -6,6 +6,7 @@ import { useEpochAssetsRewardsQuery } from '../home/__generated__/Rewards';
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
import { useNetworkParam } from '@vegaprotocol/network-parameters';
const EPOCHS_PAGE_SIZE = 10;
@@ -18,6 +19,9 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
const epochId = Number(currentEpoch.id) - 1;
const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
const { t } = useTranslation();
const { param: marketCreationQuantumMultiple } = useNetworkParam(
'rewards_marketCreationQuantumMultiple'
);
const [page, setPage] = useState(1);
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
notifyOnNetworkStatusChange: true,
@@ -70,7 +74,11 @@ export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
>
{Array.from(epochTotalRewardSummaries.values()).map(
(epochTotalSummary, index) => (
<EpochTotalRewardsTable data={epochTotalSummary} key={index} />
<EpochTotalRewardsTable
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
data={epochTotalSummary}
key={index}
/>
)
)}
<Pagination
@@ -133,24 +133,26 @@ export const RewardsPage = () => {
</p>
</div>
<div className="w-[360px]">
<Toggle
name="epoch-reward-view-toggle"
toggles={[
{
label: t('totalDistributed'),
value: 'total',
},
{
label: t('earnedByMe'),
value: 'individual',
},
]}
checkedValue={toggleRewardsView}
onChange={(e) =>
setToggleRewardsView(e.target.value as RewardsView)
}
/>
<div className="flex justify-end">
<div className="w-full max-w-[360px]">
<Toggle
name="epoch-reward-view-toggle"
toggles={[
{
label: t('totalDistributed'),
value: 'total',
},
{
label: t('earnedByMe'),
value: 'individual',
},
]}
checkedValue={toggleRewardsView}
onChange={(e) =>
setToggleRewardsView(e.target.value as RewardsView)
}
/>
</div>
</div>
</section>
@@ -77,7 +77,11 @@ const ColumnHeader = ({
</div>
);
const ColumnHeaders = () => {
const ColumnHeaders = ({
marketCreationQuantumMultiple,
}: {
marketCreationQuantumMultiple: string | null;
}) => {
const { t } = useTranslation();
return (
<div className="contents">
@@ -89,7 +93,9 @@ const ColumnHeaders = () => {
<ColumnHeader
key={columnTitle}
title={t(columnTitle)}
tooltipContent={t(description)}
tooltipContent={t(description, {
marketCreationQuantumMultiple,
})}
className={headerGridItemStyles()}
/>
))}
@@ -105,6 +111,7 @@ export interface RewardTableProps {
dataTestId: string;
epoch: number;
children: ReactNode;
marketCreationQuantumMultiple: string | null;
}
// Rewards table children will be the row items. Make sure they contain
@@ -113,12 +120,15 @@ export const RewardsTable = ({
dataTestId,
epoch,
children,
marketCreationQuantumMultiple,
}: RewardTableProps) => (
<div data-testid={dataTestId} className="mb-12">
<SubHeading title={`EPOCH ${epoch}`} />
<div className={gridStyles}>
<ColumnHeaders />
<ColumnHeaders
marketCreationQuantumMultiple={marketCreationQuantumMultiple}
/>
{children}
</div>
</div>
+1 -1
View File
@@ -279,7 +279,7 @@ const routerConfig = [
],
},
{
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag`,
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag/:proposalBlockHeight`,
element: <LazyProtocolUpgradeProposal />,
},
{
+3 -3
View File
@@ -100,8 +100,8 @@ const Home = ({ name }: RouteChildProps) => {
</Link>
</p>
</HomeSection>
<div className="flex gap-12">
<div className="flex-1">
<div className="flex justify-between flex-wrap gap-x-12 gap-y-4">
<div className="flex-1 min-w-[360px]">
<HomeSection>
<SubHeading title={t('Staking')} />
<p>
@@ -118,7 +118,7 @@ const Home = ({ name }: RouteChildProps) => {
</p>
</HomeSection>
</div>
<div className="flex-1">
<div className="flex-1 min-w-[360px]">
<HomeSection>
<SubHeading title={t('Governance')} />
<p>
+6
View File
@@ -6,6 +6,12 @@
@tailwind utilities;
@layer base {
*,
*::before,
*::after {
box-sizing: border-box;
}
h1 {
@apply text-2xl text-white uppercase mb-4;
}
+21 -208
View File
@@ -1,4 +1,4 @@
import { act, render, screen, within, waitFor } from '@testing-library/react';
import { act, render, screen, within } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { Closed } from './closed';
import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
@@ -10,55 +10,21 @@ import type {
OracleSpecDataConnectionQuery,
MarketsDataQuery,
MarketsQuery,
SuccessorMarketIdsQuery,
} from '@vegaprotocol/markets';
import {
OracleSpecDataConnectionDocument,
MarketsDataDocument,
MarketsDocument,
SuccessorMarketIdsDocument,
} from '@vegaprotocol/markets';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { FLAGS } from '@vegaprotocol/environment';
import {
createMarketFragment,
marketsQuery,
marketsDataQuery,
createMarketsDataFragment,
} from '@vegaprotocol/mock';
import type { FeatureFlags } from '@vegaprotocol/environment';
jest.mock('@vegaprotocol/markets', () => ({
...jest.requireActual('@vegaprotocol/markets'),
useSuccessorMarket: (marketId: string) =>
marketId === 'include-0'
? {
data: {
id: 'successorMarketID',
state: 'STATE_ACTIVE',
tradableInstrument: {
instrument: {
name: 'Successor Market Name',
code: 'SuccessorCode',
},
},
},
}
: { data: undefined },
}));
jest.mock('@vegaprotocol/environment', () => {
const actual = jest.requireActual('@vegaprotocol/environment');
return {
...actual,
FLAGS: {
...actual.FLAGS,
SUCCESSOR_MARKETS: true,
} as FeatureFlags,
};
});
describe('Closed', () => {
let originalNow: typeof Date.now;
@@ -218,10 +184,8 @@ describe('Closed', () => {
const headers = screen.getAllByRole('columnheader');
const expectedHeaders = [
'Market',
'Description',
'Status',
'Settlement date',
'Successor market',
'Best bid',
'Best offer',
'Mark price',
@@ -235,10 +199,8 @@ describe('Closed', () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
market.tradableInstrument.instrument.code,
market.tradableInstrument.instrument.name,
MarketStateMapping[market.state],
'3 days ago',
'-',
/* eslint-disable @typescript-eslint/no-non-null-assertion */
addDecimalsFormatNumber(marketsData.bestBidPrice, market.decimalPlaces),
addDecimalsFormatNumber(
@@ -340,43 +302,26 @@ describe('Closed', () => {
.getAllByRole('gridcell')
.filter((cell) => cell.getAttribute('col-id') === 'code')
.map((cell) => {
const marketId = within(cell)
.getByTestId('market-code')
.getAttribute('data-market-id');
return marketId;
const marketCode = within(cell).getByTestId('stack-cell-primary');
return marketCode.textContent;
});
expect(cells).toEqual(expectedRows.map((m) => m.node.id));
expect(cells).toEqual(
expectedRows.map((m) => m.node.tradableInstrument.instrument.code)
);
});
it('successor marked should be visible', async () => {
const mixedMarkets = [
const marketsWithSuccessorID = [
{
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'include-0',
state: MarketState.STATE_SETTLED,
successorMarketID: 'successor',
}),
},
{
__typename: 'MarketEdge' as const,
node: {
...createMarketFragment({
id: 'successorMarketID',
state: MarketState.STATE_ACTIVE,
}),
tradableInstrument: {
...createMarketFragment().tradableInstrument,
instrument: {
...createMarketFragment().tradableInstrument.instrument,
id: 'successorAssset',
name: 'Successor Market Name',
code: 'SuccessorCode',
},
},
},
},
];
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
const mockWithSuccessors: MockedResponse<MarketsQuery> = {
request: {
query: MarketsDocument,
},
@@ -384,42 +329,17 @@ describe('Closed', () => {
data: {
marketsConnection: {
__typename: 'MarketConnection',
edges: mixedMarkets,
edges: marketsWithSuccessorID,
},
},
},
};
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
request: {
query: SuccessorMarketIdsDocument,
},
result: {
data: {
marketsConnection: {
__typename: 'MarketConnection',
edges: [
{
node: {
id: 'include-0',
successorMarketID: 'successorMarketID',
parentMarketID: '',
},
},
],
},
},
},
};
await act(() => {
await act(async () => {
render(
<MemoryRouter>
<MockedProvider
mocks={[
mixedMarketsMock,
marketsDataMock,
oracleDataMock,
successorMarketsMock,
]}
mocks={[mockWithSuccessors, marketsDataMock, oracleDataMock]}
>
<VegaWalletContext.Provider
value={{ pubKey } as VegaWalletContextShape}
@@ -431,122 +351,15 @@ describe('Closed', () => {
);
});
await waitFor(() => {
expect(
screen.getByRole('button', { name: /^SuccessorCode/ })
).toBeInTheDocument();
});
expect(
screen.getByRole('columnheader', {
name: (_name, element) =>
element.getAttribute('col-id') === 'successorMarket',
})
).toBeInTheDocument();
screen
.getAllByRole('gridcell', {
name: (_name, element) =>
element.getAttribute('col-id') === 'successorMarket',
})
.forEach((element) => {
expect(element.querySelector('[title="Future"]')?.textContent).toEqual(
'Futr'
);
});
});
const container = within(
document.querySelector('.ag-center-cols-container') as HTMLElement
);
const cell = container.getAllByRole('gridcell', {
name: (_name, element) => element.getAttribute('col-id') === 'code',
})[0];
it('feature flag should hide successors', async () => {
const mockedFlags = jest.mocked(FLAGS);
mockedFlags.SUCCESSOR_MARKETS = false;
const mixedMarkets = [
{
__typename: 'MarketEdge' as const,
node: createMarketFragment({
id: 'include-0',
state: MarketState.STATE_SETTLED,
}),
},
{
__typename: 'MarketEdge' as const,
node: {
...createMarketFragment({
id: 'successorMarketID',
state: MarketState.STATE_ACTIVE,
}),
tradableInstrument: {
...createMarketFragment().tradableInstrument,
instrument: {
...createMarketFragment().tradableInstrument.instrument,
id: 'successorAssset',
name: 'Successor Market Name',
code: 'SuccessorCode',
},
},
},
},
];
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
request: {
query: MarketsDocument,
},
result: {
data: {
marketsConnection: {
__typename: 'MarketConnection',
edges: mixedMarkets,
},
},
},
};
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
request: {
query: SuccessorMarketIdsDocument,
},
result: {
data: {
marketsConnection: {
__typename: 'MarketConnection',
edges: [
{
node: {
id: 'include-0',
successorMarketID: 'successorMarketID',
parentMarketID: '',
},
},
],
},
},
},
};
render(
<MemoryRouter>
<MockedProvider
mocks={[
mixedMarketsMock,
marketsDataMock,
oracleDataMock,
successorMarketsMock,
]}
>
<VegaWalletContext.Provider
value={{ pubKey } as VegaWalletContextShape}
>
<Closed />
</VegaWalletContext.Provider>
</MockedProvider>
</MemoryRouter>
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent(
'PRNT'
);
await waitFor(() => {
expect(
screen.getByRole('columnheader', {
name: (_name, element) =>
element.getAttribute('col-id') === 'settlementDate',
})
).toBeInTheDocument();
});
screen.getAllByRole('columnheader').forEach((element) => {
expect(element.getAttribute('col-id')).not.toEqual('successorMarket');
});
});
});
+23 -34
View File
@@ -4,13 +4,10 @@ import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import {
AgGridLazy as AgGrid,
COL_DEFS,
MarketNameCell,
} from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { useMemo } from 'react';
import { t } from '@vegaprotocol/i18n';
import type { ProductType } from '@vegaprotocol/types';
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
import {
addDecimalsFormatNumber,
@@ -20,17 +17,13 @@ import type {
DataSourceFilterFragment,
MarketMaybeWithData,
} from '@vegaprotocol/markets';
import {
MarketActionsDropdown,
closedMarketsWithDataProvider,
} from '@vegaprotocol/markets';
import { closedMarketsWithDataProvider } from '@vegaprotocol/markets';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import type { ColDef } from 'ag-grid-community';
import { FLAGS } from '@vegaprotocol/environment';
import { SettlementDateCell } from './settlement-date-cell';
import { SettlementPriceCell } from './settlement-price-cell';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { SuccessorMarketRenderer } from './successor-market-cell';
import { MarketActionsDropdown } from './market-table-actions';
import { MarketCodeCell } from './market-code-cell';
type SettlementAsset =
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
@@ -51,7 +44,9 @@ interface Row {
setlementDataSourceFilter: DataSourceFilterFragment | undefined;
tradingTerminationOracleId: string;
settlementAsset: SettlementAsset;
productType: string;
productType: ProductType | undefined;
successorMarketID: string | null | undefined;
parentMarketID: string | null | undefined;
}
export const Closed = () => {
@@ -95,16 +90,19 @@ export const Closed = () => {
tradingTerminationOracleId:
instrument.product.dataSourceSpecForTradingTermination.id,
settlementAsset: instrument.product.settlementAsset,
productType: instrument.product.__typename || '',
productType: instrument.product.__typename,
successorMarketID: market.successorMarketID,
parentMarketID: market.parentMarketID,
};
return row;
});
return (
<div className="h-full relative">
<ClosedMarketsDataGrid rowData={rowData} error={error} />
</div>
);
return <ClosedMarketsDataGrid rowData={rowData} error={error} />;
};
const components = {
MarketCodeCell,
};
const ClosedMarketsDataGrid = ({
@@ -117,15 +115,11 @@ const ClosedMarketsDataGrid = ({
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
const colDefs = useMemo(() => {
const cols: ColDef[] = compact([
return [
{
headerName: t('Market'),
field: 'code',
cellRenderer: 'MarketNameCell',
},
{
headerName: t('Description'),
field: 'name',
cellRenderer: 'MarketCodeCell',
},
{
headerName: t('Status'),
@@ -176,12 +170,6 @@ const ClosedMarketsDataGrid = ({
},
},
},
FLAGS.SUCCESSOR_MARKETS && {
headerName: t('Successor market'),
field: 'id',
colId: 'successorMarket',
cellRenderer: 'SuccessorMarketRenderer',
},
{
headerName: t('Best bid'),
field: 'bestBidPrice',
@@ -263,12 +251,13 @@ const ClosedMarketsDataGrid = ({
<MarketActionsDropdown
marketId={data.id}
assetId={data.settlementAsset.id}
successorMarketID={data.successorMarketID}
parentMarketID={data.parentMarketID}
/>
);
},
},
]);
return cols;
];
}, [openAssetDialog]);
return (
@@ -276,8 +265,8 @@ const ClosedMarketsDataGrid = ({
rowData={rowData}
columnDefs={colDefs}
getRowId={({ data }) => data.id}
components={{ SuccessorMarketRenderer, MarketNameCell }}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
components={components}
/>
);
};
@@ -0,0 +1,82 @@
import { render, screen } from '@testing-library/react';
import { ProductTypeShortName } from '@vegaprotocol/types';
import type { MarketCodeCellProps } from './market-code-cell';
import { MarketCodeCell } from './market-code-cell';
describe('MarketCodeCell', () => {
const renderComponent = (props: MarketCodeCellProps) => {
return render(<MarketCodeCell {...props} />);
};
it('renders SCCR if the market is a successor', () => {
const productType = 'Future';
const code = 'code';
const props = {
value: 'code',
data: {
productType,
parentMarketID: 'foo',
successorMarketID: undefined,
},
} as const;
renderComponent(props);
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
ProductTypeShortName[productType]
);
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
'SCCR'
);
expect(screen.getByTestId('stack-cell-secondary')).not.toHaveTextContent(
'PRNT'
);
});
it('renders PRNT if the market is a parent', () => {
const productType = 'Future';
const code = 'code';
const props = {
value: 'code',
data: {
productType,
parentMarketID: undefined,
successorMarketID: 'foo',
},
} as const;
renderComponent(props);
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
ProductTypeShortName[productType]
);
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
'PRNT'
);
expect(screen.getByTestId('stack-cell-secondary')).not.toHaveTextContent(
'SCCR'
);
});
it('renders both SCCR and PRNT if the market is both a parent and a successor', () => {
const productType = 'Future';
const code = 'code';
const props = {
value: 'code',
data: {
productType,
parentMarketID: 'foo',
successorMarketID: 'bar',
},
} as const;
renderComponent(props);
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
ProductTypeShortName[productType]
);
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
'PRNT'
);
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
'SCCR'
);
});
});
@@ -0,0 +1,51 @@
import compact from 'lodash/compact';
import type { ProductType } from '@vegaprotocol/types';
import { ProductTypeMapping, ProductTypeShortName } from '@vegaprotocol/types';
import { StackedCell } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
export interface MarketCodeCellProps {
value: string | undefined; // market code
data: {
productType: ProductType | undefined;
parentMarketID: string | null | undefined;
successorMarketID: string | null | undefined;
};
}
export const MarketCodeCell = ({ value, data }: MarketCodeCellProps) => {
if (!value || !data || !data.productType) return null;
const infoSpanClasses =
'mr-1 pr-1 uppercase border-r last:pr-0 last:mr-0 last:border-r-0 border-vega-clight-200 dark:border-vega-cdark-200';
const info = compact([
<span
className={infoSpanClasses}
key="productType"
title={ProductTypeMapping[data.productType]}
>
{ProductTypeShortName[data.productType]}
</span>,
data.parentMarketID && (
<span
className={infoSpanClasses}
key="successor"
title={t('Successor of a market')}
>
{t('SCCR')}
</span>
),
data.successorMarketID && (
<span
className={infoSpanClasses}
key="parent"
title={t('Parent of a market')}
>
{t('PRNT')}
</span>
),
]);
return <StackedCell primary={value} secondary={info} />;
};
@@ -0,0 +1,35 @@
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
import { useColumnDefs } from './use-column-defs';
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
const defaultColDef = {
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
};
const components = {
PriceFlashCell,
};
type Props = TypedDataAgGrid<MarketMaybeWithData>;
export const MarketListTable = (props: Props) => {
const columnDefs = useColumnDefs();
return (
<AgGrid
getRowId={getRowId}
defaultColDef={defaultColDef}
columnDefs={columnDefs}
components={components}
rowHeight={45}
{...props}
/>
);
};
export default MarketListTable;
@@ -9,14 +9,21 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { DApp, EXPLORER_MARKET, useLinks } from '@vegaprotocol/environment';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
export const MarketActionsDropdown = ({
marketId,
assetId,
successorMarketID,
parentMarketID,
}: {
marketId: string;
assetId: string;
successorMarketID: string | null | undefined;
parentMarketID: string | null | undefined;
}) => {
const navigate = useNavigate();
const open = useAssetDetailsDialogStore((store) => store.open);
const linkCreator = useLinks(DApp.Explorer);
@@ -42,6 +49,26 @@ export const MarketActionsDropdown = ({
<VegaIcon name={VegaIconNames.INFO} size={16} />
{t('View settlement asset details')}
</TradingDropdownItem>
{parentMarketID && (
<TradingDropdownItem
onClick={() => {
navigate(Links[Routes.MARKET](parentMarketID));
}}
>
<VegaIcon name={VegaIconNames.EYE} size={16} />
{t('View parent market')}
</TradingDropdownItem>
)}
{successorMarketID && (
<TradingDropdownItem
onClick={() => {
navigate(Links[Routes.MARKET](successorMarketID));
}}
>
<VegaIcon name={VegaIconNames.EYE} size={16} />
{t('View successor market')}
</TradingDropdownItem>
)}
</ActionsDropdown>
);
};
@@ -6,7 +6,7 @@ import {
Tab,
TradingAnchorButton,
} from '@vegaprotocol/ui-toolkit';
import { Markets } from './markets';
import { OpenMarkets } from './open-markets';
import { Proposed } from './proposed';
import { usePageTitleStore } from '../../stores';
import { Closed } from './closed';
@@ -33,13 +33,17 @@ export const MarketsPage = () => {
<div className="h-full my-1 border rounded-sm border-default">
<Tabs storageKey="console-markets">
<Tab id="open-markets" name={t('Open markets')}>
<Markets />
<OpenMarkets />
</Tab>
<Tab
id="proposed-markets"
name={t('Proposed markets')}
menu={
<TradingAnchorButton size="extra-small" href={externalLink}>
<TradingAnchorButton
size="extra-small"
data-testid="propose-new-market"
href={externalLink}
>
{t('Propose a new market')}
</TradingAnchorButton>
}
@@ -1,13 +0,0 @@
import { MarketsContainer } from '@vegaprotocol/markets';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { SuccessorMarketRenderer } from './successor-market-cell';
export const Markets = () => {
const handleOnSelect = useMarketClickHandler();
return (
<MarketsContainer
onSelect={handleOnSelect}
SuccessorMarketRenderer={SuccessorMarketRenderer}
/>
);
};
@@ -0,0 +1,63 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
import { marketListProvider } from '@vegaprotocol/markets';
import { useEffect } from 'react';
import { t } from '@vegaprotocol/i18n';
import type { CellClickedEvent } from 'ag-grid-community';
import MarketListTable from './market-list-table';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { Interval } from '@vegaprotocol/types';
import { useYesterday } from '@vegaprotocol/react-helpers';
const POLLING_TIME = 2000;
export const OpenMarkets = () => {
const handleOnSelect = useMarketClickHandler();
const yesterday = useYesterday();
const { data, error, reload } = useDataProvider({
dataProvider: marketListProvider,
variables: {
since: new Date(yesterday).toISOString(),
interval: Interval.INTERVAL_I1H,
},
});
useEffect(() => {
const interval = setInterval(() => {
reload();
}, POLLING_TIME);
return () => {
clearInterval(interval);
};
}, [reload]);
return (
<MarketListTable
rowData={data}
onCellClicked={({
data,
column,
event,
}: CellClickedEvent<MarketMaybeWithData>) => {
if (!data) return;
// prevent navigating to the market page if any of the below cells are clicked
// event.preventDefault or event.stopPropagation dont seem to apply for aggird
const colId = column.getColId();
if (
[
'tradableInstrument.instrument.product.settlementAsset.symbol',
'market-actions',
].includes(colId)
) {
return;
}
// @ts-ignore metaKey exists
handleOnSelect(data.id, event ? event.metaKey : false);
}}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
/>
);
};
@@ -0,0 +1,46 @@
import { useEnvironment } from '@vegaprotocol/environment';
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@blueprintjs/icons';
import type { Market } from '@vegaprotocol/markets';
import {
getMatchingOracleProvider,
getVerifiedStatusIcon,
useOracleProofs,
} from '@vegaprotocol/markets';
export const OracleStatus = ({
dataSourceSpecForSettlementData,
dataSourceSpecForTradingTermination,
}: Pick<
Market['tradableInstrument']['instrument']['product'],
'dataSourceSpecForSettlementData' | 'dataSourceSpecForTradingTermination'
>) => {
const { ORACLE_PROOFS_URL } = useEnvironment();
const { data: providers } = useOracleProofs(ORACLE_PROOFS_URL);
if (providers) {
const settlementDataProvider = getMatchingOracleProvider(
dataSourceSpecForSettlementData.data,
providers
);
const tradingTerminationDataProvider = getMatchingOracleProvider(
dataSourceSpecForTradingTermination.data,
providers
);
let maliciousOracleProvider = null;
if (settlementDataProvider?.oracle.status !== 'GOOD') {
maliciousOracleProvider = settlementDataProvider;
} else if (tradingTerminationDataProvider?.oracle.status !== 'GOOD') {
maliciousOracleProvider = tradingTerminationDataProvider;
}
if (!maliciousOracleProvider) return null;
const { icon } = getVerifiedStatusIcon(maliciousOracleProvider);
return <Icon size={3} name={icon as IconName} className="ml-1" />;
}
return null;
};
@@ -0,0 +1,15 @@
import { useMarketsMapProvider } from '@vegaprotocol/markets';
export const ParentMarketCell = ({
value,
}: {
value: string; // parentMarketId
}) => {
const { data, loading } = useMarketsMapProvider();
if (loading) return null;
if (!data || !data[value]) return <span>-</span>;
return <div>{data[value].tradableInstrument.instrument.code}</div>;
};
@@ -1,6 +1,10 @@
import { ProposalsList } from '@vegaprotocol/proposals';
import { SuccessorMarketRenderer } from './successor-market-cell';
import { ParentMarketCell } from './parent-market-cell';
const cellRenderers = {
ParentMarketCell,
};
export const Proposed = () => {
return <ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />;
return <ProposalsList cellRenderers={cellRenderers} />;
};
@@ -1,190 +0,0 @@
import { render, screen, act, waitFor } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import userEvent from '@testing-library/user-event';
import type { Market } from '@vegaprotocol/markets';
import { SuccessorMarketRenderer } from './successor-market-cell';
import {
MarketsDocument,
SuccessorMarketIdsDocument,
} from '@vegaprotocol/markets';
import { createMarketFragment } from '@vegaprotocol/mock';
const mockSuccessorsQuery = [
{
id: 'market1',
parentMarketID: 'parentMarket1',
successorMarketID: 'successorMarket1',
},
{ id: 'market2', parentMarketID: 'parentMarket2' },
{ id: 'market3', successorMarketID: 'successorMarket3' },
];
const parentMarket1 = {
id: 'parentMarket1',
tradableInstrument: {
instrument: { code: 'code parent 1', id: '1' },
},
} as unknown as Market;
const successorMarket1 = {
id: 'successorMarket1',
tradableInstrument: {
instrument: { code: 'code successor 1', id: '2' },
},
} as unknown as Market;
const parentMarket2 = {
id: 'parentMarket2',
tradableInstrument: {
instrument: { code: 'code parent 2', id: '3' },
},
} as unknown as Market;
const successorMarket3 = {
id: 'successorMarket3',
tradableInstrument: {
instrument: { code: 'code successor 3', id: '4' },
},
} as unknown as Market;
const mockMarkets = [
parentMarket1,
successorMarket1,
parentMarket2,
successorMarket3,
];
const mockClickHandler = jest.fn();
jest.mock('../../lib/hooks/use-market-click-handler', () => ({
useMarketClickHandler: jest.fn().mockImplementation(() => mockClickHandler),
}));
const marketMock = {
request: {
query: MarketsDocument,
variables: undefined,
},
result: {
data: {
marketsConnection: {
edges: mockMarkets.map((item) => ({
node: {
...createMarketFragment(item),
},
})),
},
},
},
};
const successorMock = {
request: {
query: SuccessorMarketIdsDocument,
},
result: {
data: {
marketsConnection: {
edges: mockSuccessorsQuery.map((item) => ({
node: {
...item,
},
})),
},
},
},
};
const mocks = [marketMock, successorMock];
describe('SuccessorMarketRenderer', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should properly rendered successor market', async () => {
const successorValue = 'market1';
render(
<MockedProvider mocks={[...mocks]}>
<SuccessorMarketRenderer value={successorValue} />
</MockedProvider>
);
await waitFor(() => {
expect(screen.getByTestId('market-code')).toBeInTheDocument();
});
expect(screen.getByText('code successor 1')).toBeInTheDocument();
expect(screen.getByText('Futr')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket1', false);
});
});
it('should properly rendered parent market', async () => {
const successorValue = 'market1';
render(
<MockedProvider mocks={[...mocks]}>
<SuccessorMarketRenderer value={successorValue} parent />
</MockedProvider>
);
await waitFor(() => {
expect(screen.getByTestId('market-code')).toBeInTheDocument();
});
expect(screen.getByText('code parent 1')).toBeInTheDocument();
expect(screen.getByText('Futr')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket1', false);
});
});
it('should properly rendered only parent market', async () => {
const successorValue = 'market2';
const { rerender } = render(
<MockedProvider mocks={[...mocks]}>
<SuccessorMarketRenderer value={successorValue} parent />
</MockedProvider>
);
await waitFor(() => {
expect(screen.getByTestId('market-code')).toBeInTheDocument();
});
expect(screen.getByText('code parent 2')).toBeInTheDocument();
expect(screen.getByText('Futr')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket2', false);
});
rerender(
<MockedProvider mocks={[...mocks]}>
<SuccessorMarketRenderer value={successorValue} />
</MockedProvider>
);
expect(screen.getByText('-')).toBeInTheDocument();
});
it('should properly rendered only successor market', async () => {
const successorValue = 'market3';
const { rerender } = render(
<MockedProvider mocks={[...mocks]}>
<SuccessorMarketRenderer value={successorValue} />
</MockedProvider>
);
await waitFor(() => {
expect(screen.getByTestId('market-code')).toBeInTheDocument();
});
expect(screen.getByText('code successor 3')).toBeInTheDocument();
expect(screen.getByText('Futr')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket3', false);
});
await act(() => {
rerender(
<MockedProvider mocks={[...mocks]}>
<SuccessorMarketRenderer value={successorValue} parent />
</MockedProvider>
);
});
expect(screen.getByText('-')).toBeInTheDocument();
});
});
@@ -1,41 +0,0 @@
import React from 'react';
import { MarketNameCell } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketProvider, useSuccessorMarketIds } from '@vegaprotocol/markets';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const SuccessorMarketRenderer = ({
value,
parent,
}: {
value: string;
parent?: boolean;
}) => {
const successors = useSuccessorMarketIds(value);
const onMarketClick = useMarketClickHandler();
const lookupValue = successors
? parent
? successors.parentMarketID
: successors.successorMarketID
: '';
const { data } = useDataProvider({
dataProvider: marketProvider,
variables: {
marketId: lookupValue || '',
},
skip: !lookupValue,
});
return data ? (
<MarketNameCell
value={data.tradableInstrument.instrument.code}
data={data}
onMarketClick={onMarketClick}
productType={data.tradableInstrument.instrument?.product.__typename}
/>
) : (
'-'
);
};
@@ -0,0 +1,226 @@
import { useMemo } from 'react';
import type { ColDef, ValueFormatterParams } from 'ag-grid-community';
import { t } from '@vegaprotocol/i18n';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
VegaValueGetterParams,
} from '@vegaprotocol/datagrid';
import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import type {
MarketMaybeWithData,
MarketMaybeWithDataAndCandles,
} from '@vegaprotocol/markets';
import { MarketActionsDropdown } from './market-table-actions';
import { calcCandleVolume } from '@vegaprotocol/markets';
import { MarketCodeCell } from './market-code-cell';
const { MarketTradingMode, AuctionTrigger } = Schema;
export const useColumnDefs = () => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
return useMemo<ColDef[]>(
() => [
{
headerName: t('Market'),
field: 'tradableInstrument.instrument.code',
flex: 2,
cellRenderer: ({
value,
data,
}: VegaICellRendererParams<
MarketMaybeWithData,
'tradableInstrument.instrument.code'
>) => (
<MarketCodeCell
value={value}
data={{
productType:
data?.tradableInstrument.instrument.product.__typename,
successorMarketID: data?.successorMarketID,
parentMarketID: data?.parentMarketID,
}}
/>
),
},
{
headerName: t('Description'),
field: 'tradableInstrument.instrument.name',
flex: 2,
},
{
headerName: t('Trading mode'),
field: 'tradingMode',
cellRenderer: ({
data,
}: VegaICellRendererParams<MarketMaybeWithData, 'data'>) => {
if (!data?.data) return '-';
const { trigger, marketTradingMode } = data.data;
const withTriggerInfo =
marketTradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger &&
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED;
if (withTriggerInfo) {
return (
<Tooltip
description={`${Schema.MarketTradingModeMapping[marketTradingMode]}
- ${Schema.AuctionTriggerMapping[trigger]}`}
>
<span>
{Schema.MarketTradingModeMapping[marketTradingMode]}
</span>
</Tooltip>
);
}
return Schema.MarketTradingModeMapping[marketTradingMode];
},
filter: SetFilter,
filterParams: {
set: Schema.MarketTradingModeMapping,
},
},
{
headerName: t('Status'),
field: 'state',
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
},
filter: SetFilter,
filterParams: {
set: Schema.MarketStateMapping,
},
},
{
headerName: t('Mark price'),
field: 'data.markPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
return data?.data?.markPrice === undefined
? undefined
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
data?.data?.bestOfferPrice === undefined
? '-'
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
},
{
headerName: t('24h volume'),
type: 'rightAligned',
field: 'data.candles',
valueGetter: ({
data,
}: VegaValueGetterParams<MarketMaybeWithDataAndCandles>) => {
if (!data) return 0;
const candles = data?.candles;
const vol = candles ? calcCandleVolume(candles) : '0';
return Number(vol);
},
valueFormatter: ({
data,
}: ValueFormatterParams<MarketMaybeWithDataAndCandles, 'candles'>) => {
const candles = data?.candles;
const vol = candles ? calcCandleVolume(candles) : '0';
const volume =
data && vol && vol !== '0'
? addDecimalsFormatNumber(vol, data.positionDecimalPlaces)
: '0.00';
return volume;
},
},
{
headerName: t('Settlement asset'),
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
cellRenderer: ({
data,
}: VegaICellRendererParams<
MarketMaybeWithData,
'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => {
const value =
data?.tradableInstrument.instrument.product.settlementAsset;
return value ? (
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value.id, e.target as HTMLElement);
}}
>
{value.symbol}
</ButtonLink>
) : (
''
);
},
},
{
headerName: t('Spread'),
field: 'data.bestBidPrice',
type: 'rightAligned',
filter: 'agNumberColumnFilter',
cellRenderer: 'PriceFlashCell',
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
if (!data || !data.data?.bestOfferPrice || !data.data?.bestBidPrice) {
return undefined;
}
const offer = toBigNum(data.data.bestOfferPrice, data.decimalPlaces);
const bid = toBigNum(data.data.bestBidPrice, data.decimalPlaces);
const spread = offer.minus(bid).toNumber();
// The calculation above can result in '-0' being rendered after formatting
// so return Math.abs to remove it and just render '0'
if (spread === 0) {
return Math.abs(spread);
}
return spread;
},
valueFormatter: ({
value,
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.bestBidPrice'
>) => {
if (!value) return '-';
return value.toString();
},
},
{
colId: 'market-actions',
field: 'id',
...COL_DEFS.actions,
cellRenderer: ({
data,
}: VegaICellRendererParams<MarketMaybeWithData>) => {
if (!data) return null;
return (
<MarketActionsDropdown
marketId={data.id}
assetId={
data.tradableInstrument.instrument.product.settlementAsset.id
}
successorMarketID={data.successorMarketID}
parentMarketID={data.parentMarketID}
/>
);
},
},
],
[openAssetDetailsDialog]
);
};
@@ -22,7 +22,7 @@ describe('GetStarted', () => {
};
const checkTicks = (elements: Element[]) => {
elements.forEach((item, i) => {
if (i + 1 < mockStep) {
if (i + 1 < mockStep - 1) {
expect(item.querySelector('[data-testid="icon-tick"]')).toBeTruthy();
}
});
@@ -57,19 +57,9 @@ 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;
rerender(
<MemoryRouter>
<VegaWalletContext.Provider value={{} as VegaWalletContextShape}>
<GetStarted />
</VegaWalletContext.Provider>
</MemoryRouter>
);
const { rerender, container } = renderComponent();
checkTicks(screen.getAllByRole('listitem'));
expect(screen.getByRole('button', { name: 'Connect' })).toBeInTheDocument();
@@ -93,7 +83,9 @@ describe('GetStarted', () => {
</MemoryRouter>
);
checkTicks(screen.getAllByRole('listitem'));
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Ready to trade' })
).toBeInTheDocument();
mockStep = 5;
rerender(
@@ -7,11 +7,7 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import {
GetWalletButton,
useVegaWallet,
useVegaWalletDialogStore,
} from '@vegaprotocol/wallet';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useNavigate } from 'react-router-dom';
@@ -30,7 +26,6 @@ interface Props {
}
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
const { CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL } = useEnvironment();
const navigate = useNavigate();
const [, setOnboardingViewed] = useLocalStorage(
constants.ONBOARDING_VIEWED_KEY
@@ -47,15 +42,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
let onClickHandle = () => {
openVegaWalletDialog();
};
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) {
if (step <= OnboardingStep.ONBOARDING_CONNECT_STEP) {
buttonText = t('Connect');
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
buttonText = t('Deposit');
@@ -65,7 +52,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
dismiss();
};
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
buttonText = t('Dismiss');
buttonText = t('Ready to trade');
onClickHandle = () => {
navigate(link);
setView({ type: ViewType.Order });
@@ -116,23 +103,18 @@ 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={3}
step={2}
text={t('Deposit funds')}
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
/>
<Step
step={4}
step={3}
text={t('Open a position')}
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
/>
@@ -7,8 +7,6 @@ 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';
@@ -23,7 +21,7 @@ let mockContext: Partial<VegaWalletContextShape> = { pubKey: 'test-pubkey' };
describe('useGetOnboardingStep', () => {
beforeEach(() => {
jest.clearAllMocks();
mockData = [{ id: 'item-id' }];
mockData = [];
mockContext = { pubKey: 'test-pubkey' };
globalThis.window.vega = {} as Vega;
});
@@ -42,13 +40,6 @@ 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 });
@@ -56,15 +47,6 @@ 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
@@ -72,6 +54,7 @@ describe('useGetOnboardingStep', () => {
});
it('should return properly ONBOARDING_ORDER_STEP', async () => {
mockData = [{ id: 'item-id' }];
(useDataProvider as jest.Mock).mockImplementation((args) => {
if (
args.dataProvider === ordersWithMarketProvider ||
@@ -86,6 +69,7 @@ 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 { isBrowserWalletInstalled, useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { depositsProvider } from '@vegaprotocol/deposits';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
@@ -63,20 +63,21 @@ export const useGetOnboardingStep = () => {
collateralData === null ||
positionsData === null)
);
let step = OnboardingStep.ONBOARDING_UNKNOWN_STEP;
if (isLoading) {
return OnboardingStep.ONBOARDING_UNKNOWN_STEP;
}
if (!isBrowserWalletInstalled()) {
return OnboardingStep.ONBOARDING_WALLET_STEP;
return step;
}
if (!pubKey) {
return OnboardingStep.ONBOARDING_CONNECT_STEP;
step = OnboardingStep.ONBOARDING_CONNECT_STEP;
}
if (!deposits && !collaterals) {
return OnboardingStep.ONBOARDING_DEPOSIT_STEP;
if (pubKey) {
step = OnboardingStep.ONBOARDING_DEPOSIT_STEP;
}
if (!orders && !positions) {
return OnboardingStep.ONBOARDING_ORDER_STEP;
if (pubKey && (deposits || collaterals)) {
step = OnboardingStep.ONBOARDING_ORDER_STEP;
}
return OnboardingStep.ONBOARDING_COMPLETE_STEP;
if (pubKey && (orders || positions)) {
step = OnboardingStep.ONBOARDING_COMPLETE_STEP;
}
return step;
};
@@ -1,24 +1,18 @@
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { useCallback } from 'react';
import { Links, Routes } from '../../pages/client-router';
export const useMarketClickHandler = (replace = false) => {
const navigate = useNavigate();
const { marketId } = useParams();
const { pathname } = useLocation();
const isMarketPage = pathname.match(/^\/markets\/(.+)/);
return useCallback(
(selectedId: string, metaKey?: boolean) => {
const link = Links[Routes.MARKET](selectedId);
if (metaKey) {
window.open(`/#${link}`, '_blank');
} else if (selectedId !== marketId || !isMarketPage) {
navigate(link, { replace });
}
},
[navigate, marketId, replace, isMarketPage]
);
return (selectedId: string, metaKey?: boolean) => {
const link = Links[Routes.MARKET](selectedId);
if (metaKey) {
window.open(`/#${link}`, '_blank');
} else {
navigate(link, { replace });
}
};
};
export const useMarketLiquidityClickHandler = () => {
+21 -6
View File
@@ -4,22 +4,37 @@ export default function Document() {
return (
<>
<Head>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{/*
meta tags
- next advised against using _document for this, so they exist in our
- single page index.page.tsx
*/}
{/* icons */}
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link
rel="apple-touch-icon"
content="https://static.vega.xyz/favicon.ico"
/>
{/* fonts */}
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
{/* styles */}
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
{/* scripts */}
<script src="/theme-setter.js" type="text/javascript" async />
</Head>
<Html>
+1 -28
View File
@@ -10,23 +10,16 @@ export default function Index() {
return (
<>
<Head>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - VEGA Console" />
<meta name="description" content="Vega Protocol - Console" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://console.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Console" />
<meta name="og:site_name" content="Vega Protocol - Console" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta
name="twitter:card"
content="https://static.vega.xyz/favicon.ico"
@@ -39,26 +32,6 @@ export default function Index() {
/>
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
<meta name="description" content="Vega Protocol - Console" />
<link
rel="apple-touch-icon"
content="https://static.vega.xyz/favicon.ico"
/>
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<script src="/theme-setter.js" type="text/javascript" async />
</Head>
<ClientRouter />
</>
+5 -1
View File
@@ -36,7 +36,7 @@ body,
}
/**
* ALLOTMENT
* ALLOTMENT
*/
html {
@@ -81,6 +81,10 @@ html [data-theme='light'] {
--pennant-color-macd-macd: theme(colors.vega.yellow.500);
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
/* reduce space between candles */
--pennant-candlestick-inner-padding: 0.175;
--pennant-candlestick-stroke-width: 0.5;
}
html [data-theme='light'] {
+1 -1
View File
@@ -7,7 +7,7 @@ envCmd=""
if [[ ! -z "${ENV_NAME}" ]]; then
if [[ "${ENV_NAME}" != "ops-vega" ]]; then
envCmd="envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
envCmd="yarn env-cmd -f ./apps/${APP}/.env.${ENV_NAME}"
fi
fi
+1 -2
View File
@@ -6,12 +6,11 @@ WORKDIR /app
ARG APP
ARG ENV_NAME=""
RUN apk add --update --no-cache \
python3==3.10.11-r0 \
make==4.3-r0 \
gcc==11.2.1_git20220219-r2 \
g++==11.2.1_git20220219-r2
COPY . ./
RUN yarn --network-timeout 100000 --pure-lockfile
RUN yarn --pure-lockfile
# work around for different build process in trading
RUN sh docker/docker-build.sh
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/accounts",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"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}"],
+1 -5
View File
@@ -1,8 +1,4 @@
{
"name": "@vegaprotocol/announcements",
"version": "0.0.2",
"peerDependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
"version": "0.0.3"
}
+8 -2
View File
@@ -13,9 +13,15 @@
"tsConfig": "libs/announcements/tsconfig.lib.json",
"project": "libs/announcements/package.json",
"entryFile": "libs/announcements/src/index.ts",
"external": ["react/jsx-runtime"],
"external": [
"react",
"react-dom",
"react/jsx-runtime",
"@vegaprotocol/react-helpers",
"@vegaprotocol/ui-toolkit"
],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"compiler": "swc",
"assets": [
{
"glob": "libs/announcements/README.md",
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/apollo-client",
"version": "0.0.1"
}
-10
View File
@@ -4,16 +4,6 @@
"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
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/assets",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"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}"],
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/candles-chart",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/candles-chart",
"tsConfig": "libs/candles-chart/tsconfig.lib.json",
"project": "libs/candles-chart/package.json",
"entryFile": "libs/candles-chart/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/candles-chart/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
+52 -21
View File
@@ -3,6 +3,8 @@ import { CandlestickChart } from 'pennant';
import { VegaDataSource } from './data-source';
import { useApolloClient } from '@apollo/client';
import { useMemo } from 'react';
import debounce from 'lodash/debounce';
import AutoSizer from 'react-virtualized-auto-sizer';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
@@ -12,6 +14,8 @@ export type CandlesChartContainerProps = {
marketId: string;
};
const CANDLES_TO_WIDTH_FACTOR = 0.15;
export const CandlesChartContainer = ({
marketId,
}: CandlesChartContainerProps) => {
@@ -19,32 +23,59 @@ export const CandlesChartContainer = ({
const { pubKey } = useVegaWallet();
const { theme } = useThemeSwitcher();
const { interval, chartType, overlays, studies, merge } =
useCandlesChartSettings();
const {
interval,
chartType,
overlays,
studies,
studySizes,
setStudies,
setStudySizes,
setOverlays,
} = useCandlesChartSettings();
const handlePaneChange = useMemo(
() =>
debounce((sizes: number[]) => {
// first number is main pain, which is greedy so we don't store it
setStudySizes(sizes.filter((_, i) => i !== 0));
}, 300),
[setStudySizes]
);
const dataSource = useMemo(() => {
return new VegaDataSource(client, marketId, pubKey);
}, [client, marketId, pubKey]);
return (
<CandlestickChart
dataSource={dataSource}
options={{
chartType,
overlays,
studies,
notEnoughDataText: (
<span className="text-xs text-center">{t('No data')}</span>
),
}}
interval={interval}
theme={theme}
onOptionsChanged={(options) => {
merge({
overlays: options.overlays,
studies: options.studies,
});
}}
/>
<AutoSizer>
{({ width, height }) => (
<div style={{ width, height }}>
<CandlestickChart
dataSource={dataSource}
options={{
chartType,
overlays,
studies,
notEnoughDataText: (
<span className="text-xs text-center">{t('No data')}</span>
),
initialNumCandlesToDisplay: Math.floor(
width * CANDLES_TO_WIDTH_FACTOR
),
studySize: 150, // default size
studySizes,
}}
interval={interval}
theme={theme}
onOptionsChanged={(options) => {
setStudies(options.studies);
setOverlays(options.overlays);
}}
onPaneChanged={handlePaneChange}
/>
</div>
)}
</AutoSizer>
);
};
@@ -5,36 +5,45 @@ import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
type StudySizes = { [S in Study]?: number };
interface StoredSettings {
interval: Interval;
type: ChartType;
overlays: Overlay[];
studies: Study[];
studySizes: StudySizes;
}
export const STUDY_SIZE = 100;
const STUDY_ORDER: Study[] = [
Study.FORCE_INDEX,
Study.RELATIVE_STRENGTH_INDEX,
Study.ELDAR_RAY,
Study.MACD,
Study.VOLUME,
];
const DEFAULT_CHART_SETTINGS = {
interval: Interval.I15M,
type: ChartType.CANDLE,
overlays: [Overlay.MOVING_AVERAGE],
studies: [Study.MACD, Study.VOLUME],
studySizes: {},
};
export const useCandlesChartSettingsStore = create<
StoredSettings & {
merge: (settings: Partial<StoredSettings>) => void;
setType: (type: ChartType) => void;
setInterval: (interval: Interval) => void;
setOverlays: (overlays: Overlay[]) => void;
setStudies: (studies: Study[]) => void;
setOverlays: (overlays?: Overlay[]) => void;
setStudies: (studies?: Study[]) => void;
setStudySizes: (sizes: number[]) => void;
}
>()(
persist(
immer((set) => ({
...DEFAULT_CHART_SETTINGS,
merge: (settings: Partial<StoredSettings>) =>
set((state) => {
Object.assign(state, settings);
}),
setType: (type) =>
set((state) => {
state.type = type;
@@ -43,14 +52,35 @@ export const useCandlesChartSettingsStore = create<
set((state) => {
state.interval = interval;
}),
setOverlays: (overlays) =>
setOverlays: (overlays) => {
if (!overlays) return;
set((state) => {
state.overlays = overlays;
}),
setStudies: (studies) =>
});
},
setStudies: (studies) => {
if (!studies) return;
// Make sure studies are always returned in the same order
studies.sort((a, b) => {
return STUDY_ORDER.indexOf(a) - STUDY_ORDER.indexOf(b);
});
set((state) => {
state.studies = studies;
}),
});
},
setStudySizes: (sizes) => {
set((state) => {
// for every study find the corresonding size and update
// the size record for that study
state.studies.forEach((s, i) => {
const size = sizes[i];
state.studySizes[s] = size;
});
});
},
})),
{
name: 'vega_candles_chart_store',
@@ -85,15 +115,22 @@ export const useCandlesChartSettings = () => {
[Study.VOLUME]
);
// find the study size
const studySizes = studies.map((s) => {
const size = settings.studySizes[s] || STUDY_SIZE;
return size;
});
return {
interval,
chartType,
overlays,
studies,
studySizes,
setInterval: settings.setInterval,
setType: settings.setType,
setStudies: settings.setStudies,
setOverlays: settings.setOverlays,
merge: settings.merge,
setStudySizes: settings.setStudySizes,
};
};
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/data-provider",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/data-provider",
"tsConfig": "libs/data-provider/tsconfig.lib.json",
"project": "libs/data-provider/package.json",
"entryFile": "libs/data-provider/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/data-provider/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/datagrid",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/datagrid",
"tsConfig": "libs/datagrid/tsconfig.lib.json",
"project": "libs/datagrid/package.json",
"entryFile": "libs/datagrid/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/datagrid/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
+1
View File
@@ -12,6 +12,7 @@ export * from './lib/cells/centered-grid-cell';
export * from './lib/cells/market-name-cell';
export * from './lib/cells/order-type-cell';
export * from './lib/cells/size';
export * from './lib/cells/stacked-cell';
export * from './lib/filters/date-range-filter';
export * from './lib/filters/set-filter';
@@ -59,7 +59,7 @@ export const MarketNameCell = ({
(data as { market: Market })?.market?.tradableInstrument.instrument.product
.__typename;
if (!value) return;
if (!value) return null;
const content = (
<>
<span data-testid="market-code" data-market-id={id}>
@@ -72,7 +72,7 @@ export const MarketNameCell = ({
<button
onClick={handleOnClick}
tabIndex={0}
className="block text-left text-ellipsis overflow-hidden whitespace-nowrap w-full"
className="block w-full overflow-hidden text-left text-ellipsis whitespace-nowrap"
>
{content}
</button>
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/deal-ticket",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/deal-ticket",
"tsConfig": "libs/deal-ticket/tsconfig.lib.json",
"project": "libs/deal-ticket/package.json",
"entryFile": "libs/deal-ticket/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/deal-ticket/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
@@ -1,6 +1,12 @@
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Notification, Intent } from '@vegaprotocol/ui-toolkit';
import {
Intent,
VegaIcon,
VegaIconNames,
Tooltip,
TradingButton,
} from '@vegaprotocol/ui-toolkit';
interface Props {
margin: string;
@@ -14,24 +20,43 @@ interface Props {
}
export const MarginWarning = ({ margin, balance, asset, onDeposit }: Props) => {
const description = (
<div className="flex flex-col items-start gap-2 p-2">
<p className="text-sm">
{t('%s %s is currently required.', [
addDecimalsFormatNumber(margin, asset.decimals),
asset.symbol,
])}
</p>
<p className="text-sm">
{t('You have only %s.', [
addDecimalsFormatNumber(balance, asset.decimals),
])}
</p>
<TradingButton
intent={Intent.Warning}
size="small"
onClick={() => onDeposit(asset.id)}
data-testid="deal-ticket-deposit-dialog-button"
type="button"
>
{t('Deposit %s', [asset.symbol])}
</TradingButton>
</div>
);
return (
<Notification
intent={Intent.Warning}
testId="deal-ticket-warning-margin"
message={`You may not have enough margin available to open this position. ${addDecimalsFormatNumber(
margin,
asset.decimals
)} ${asset.symbol} ${t(
'is currently required. You have only'
)} ${addDecimalsFormatNumber(balance, asset.decimals)} ${
asset.symbol
} ${t('available.')}`}
buttonProps={{
text: t(`Deposit ${asset.symbol}`),
action: () => onDeposit(asset.id),
dataTestId: 'deal-ticket-deposit-dialog-button',
size: 'small',
}}
/>
<Tooltip description={description}>
<div
className="flex text-xs items-center"
data-testid="deal-ticket-warning-margin"
>
<span className="text-yellow-500 mr-2">
<VegaIcon name={VegaIconNames.WARNING} />
</span>
{t('You may not have enough margin available to open this position.')}
</div>
</Tooltip>
);
};
@@ -78,8 +78,18 @@ const triggerPriceWarningMessage = 'stop-order-warning-message-trigger-price';
const triggerTrailingPercentOffsetErrorMessage =
'stop-order-error-message-trigger-trailing-percent-offset';
const numberOfActiveOrdersLimit = 'stop-order-warning-limit';
const ocoPostfix = (id: string, postfix = true) => (postfix ? `${id}-oco` : id);
const mockDataProvider = jest.fn((...args) => ({
data: Array(0),
}));
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn((...args) => mockDataProvider(...args)),
}));
describe('StopOrder', () => {
beforeEach(() => {
localStorage.clear();
@@ -460,4 +470,26 @@ describe('StopOrder', () => {
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
).toEqual(now);
});
it('shows limit of active stop orders number', async () => {
mockDataProvider.mockReturnValue({
data: Array(4),
});
render(generateJsx());
expect(mockDataProvider.mock.lastCall?.[0].skip).toBe(true);
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
expect(mockDataProvider.mock.lastCall?.[0].skip).toBe(false);
expect(screen.getByTestId(numberOfActiveOrdersLimit)).toBeInTheDocument();
});
it('counts oco as two orders', async () => {
mockDataProvider.mockReturnValue({
data: Array(3),
});
render(generateJsx());
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
expect(screen.queryByTestId(numberOfActiveOrdersLimit)).toBeNull();
await userEvent.click(screen.getByTestId(oco));
expect(screen.getByTestId(numberOfActiveOrdersLimit)).toBeInTheDocument();
});
});
@@ -26,6 +26,7 @@ import {
TradingButton as Button,
Pill,
Intent,
Notification,
} from '@vegaprotocol/ui-toolkit';
import { getDerivedPrice } from '@vegaprotocol/markets';
import type { Market } from '@vegaprotocol/markets';
@@ -53,6 +54,8 @@ import { DealTicketFeeDetails } from './deal-ticket-fee-details';
import { validateExpiration } from '../../utils';
import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants';
import { KeyValue } from './key-value';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { stopOrdersProvider } from '@vegaprotocol/orders';
export interface StopOrderProps {
market: Market;
@@ -60,6 +63,8 @@ export interface StopOrderProps {
submit: (order: StopOrdersSubmission) => void;
}
const MAX_NUMBER_OF_ACTIVE_STOP_ORDERS = 4;
const POLLING_TIME = 2000;
const trailingPercentOffsetStep = '0.1';
const getDefaultValues = (
@@ -802,6 +807,27 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
const triggerTrailingPercentOffset = watch('triggerTrailingPercentOffset');
const triggerType = watch('triggerType');
const { data: activeStopOrders, reload } = useDataProvider({
dataProvider: stopOrdersProvider,
variables: {
filter: {
parties: pubKey ? [pubKey] : [],
markets: [market.id],
liveOnly: true,
},
},
skip: !(pubKey && (formState.isDirty || formState.submitCount)),
});
useEffect(() => {
const interval = setInterval(() => {
reload();
}, POLLING_TIME);
return () => {
clearInterval(interval);
};
}, [reload]);
useEffect(() => {
const storedSize = storedFormValues?.[dealTicketType]?.size;
if (storedSize && size !== storedSize) {
@@ -864,7 +890,6 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
{errors.type.message}
</InputError>
)}
<Controller
name="side"
control={control}
@@ -1095,6 +1120,20 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
</>
)}
<NoWalletWarning isReadOnly={isReadOnly} />
{(activeStopOrders?.length ?? 0) + (oco ? 2 : 1) >
MAX_NUMBER_OF_ACTIVE_STOP_ORDERS ? (
<div className="mb-2">
<Notification
intent={Intent.Warning}
testId={'stop-order-warning-limit'}
message={t(
'There is a limit of %s active stop orders per market. Orders submitted above the limit will be immediately rejected.',
[MAX_NUMBER_OF_ACTIVE_STOP_ORDERS.toString()]
)}
/>
</div>
) : null}
<SubmitButton
assetUnit={assetUnit}
market={market}
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/deposits",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/deposits",
"tsConfig": "libs/deposits/tsconfig.lib.json",
"project": "libs/deposits/package.json",
"entryFile": "libs/deposits/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/deposits/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/environment",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/environment",
"tsConfig": "libs/environment/tsconfig.lib.json",
"project": "libs/environment/package.json",
"entryFile": "libs/environment/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/environment/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
+4 -3
View File
@@ -127,7 +127,7 @@ export const TOKEN_GOVERNANCE = '/proposals';
export const TOKEN_PROPOSALS = '/proposals';
export const TOKEN_PROPOSAL = '/proposals/:id';
export const TOKEN_PROTOCOL_UPGRADE_PROPOSAL =
'/proposals/protocol-upgrade/:tag';
'/proposals/protocol-upgrade/:tag/:blockHeight';
export const TOKEN_VALIDATOR = '/validators/:id';
/**
@@ -135,12 +135,12 @@ export const TOKEN_VALIDATOR = '/validators/:id';
*/
export const useProtocolUpgradeProposalLink = () => {
const governance = useLinks(DApp.Token);
return (releaseTag: string) =>
return (releaseTag: string, blockHeight: string) =>
governance(
TOKEN_PROTOCOL_UPGRADE_PROPOSAL.replace(
':tag',
stripFullStops(releaseTag)
)
).replace(':blockHeight', blockHeight)
);
};
@@ -159,6 +159,7 @@ export const ExternalLinks = {
DISCORD: 'https://vega.xyz/discord',
GOVERNANCE_PAGE: 'https://vega.xyz/governance',
VALIDATOR_FORUM: 'https://community.vega.xyz/c/mainnet-validator-candidates',
PROPOSALS_FORUM: 'https://community.vega.xyz/c/governance/25',
MARGIN_CREDIT_RISK:
'https://vega.xyz/papers/margins-and-credit-risk.pdf#page=7',
VEGA_WALLET_URL: 'https://vega.xyz/wallet',
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/fills",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/fills",
"tsConfig": "libs/fills/tsconfig.lib.json",
"project": "libs/fills/package.json",
"entryFile": "libs/fills/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/fills/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "@vegaprotocol/i18n",
"version": "0.0.4",
"version": "0.0.5",
"type": "commonjs"
}
+1 -11
View File
@@ -7,6 +7,7 @@
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"format": ["esm", "cjs"],
"options": {
"outputPath": "dist/libs/i18n",
"main": "libs/i18n/src/index.ts",
@@ -14,17 +15,6 @@
"assets": ["libs/i18n/*.md"]
}
},
"publish": {
"executor": "nx:run-commands",
"options": {
"command": "node tools/scripts/publish.mjs i18n {args.ver} {args.tag}"
},
"dependsOn": [
{
"target": "build"
}
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/ledger",
"version": "0.0.1"
}
-20
View File
@@ -4,26 +4,6 @@
"sourceRoot": "libs/ledger/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/ledger",
"tsConfig": "libs/ledger/tsconfig.lib.json",
"project": "libs/ledger/package.json",
"entryFile": "libs/ledger/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/ledger/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/liquidity",
"version": "0.0.1"
}
-20
View File
@@ -5,26 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/liquidity",
"tsConfig": "libs/liquidity/tsconfig.lib.json",
"project": "libs/liquidity/package.json",
"entryFile": "libs/liquidity/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nx/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/liquidity/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
-5
View File
@@ -1,5 +0,0 @@
{
"name": "@vegaprotocol/logger",
"version": "0.0.1",
"type": "commonjs"
}
-11
View File
@@ -5,17 +5,6 @@
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"format": ["esm", "cjs"],
"options": {
"outputPath": "dist/libs/logger",
"main": "libs/logger/src/index.ts",
"tsConfig": "libs/logger/tsconfig.lib.json",
"assets": ["libs/logger/*.md"]
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
-4
View File
@@ -1,4 +0,0 @@
{
"name": "@vegaprotocol/market-depth",
"version": "0.0.1"
}

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