Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abf23e1e77 | ||
|
|
e0a2f5bb03 | ||
|
|
f25a58a3c3 | ||
|
|
37340b4dc3 | ||
|
|
17ba3eb351 | ||
|
|
8f966a4ba6 | ||
|
|
cc7f403718 | ||
|
|
9184c2374c | ||
|
|
37247a504a | ||
|
|
2e7a6a6458 | ||
|
|
bf6c13f523 | ||
|
|
a6f3a08d2f | ||
|
|
f13e456a29 | ||
|
|
5cab040189 | ||
|
|
e47b868c53 | ||
|
|
b6286cd0f3 | ||
|
|
f66e976b66 | ||
|
|
866e7232db | ||
|
|
5a8ff90890 | ||
|
|
7b8f654906 |
@@ -136,22 +136,30 @@ jobs:
|
||||
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
|
||||
fi
|
||||
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
|
||||
if echo "$affected" | grep -q multisig-signer; then
|
||||
echo "Tools are affected"
|
||||
# tools are only applicable to check previews or deploy from develop to mainnet
|
||||
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
|
||||
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
|
||||
if echo "$affected" | grep -q multisig-signer; then
|
||||
echo "Tools are affected"
|
||||
# tools are only applicable to check previews or deploy from develop to mainnet
|
||||
echo "Deploying tools on preview"
|
||||
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
|
||||
projects+=' "multisig-signer" '
|
||||
fi
|
||||
if [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
if echo "$affected" | grep -q multisig-signer; then
|
||||
echo "Tools are affected"
|
||||
# tools are only applicable to check previews or deploy from develop to mainnet
|
||||
echo "Deploying tools on s3"
|
||||
projects+=' "multisig-signer" '
|
||||
fi
|
||||
if echo "$affected" | grep -q static; then
|
||||
echo "Static are affected"
|
||||
echo "Deploying static on s3"
|
||||
projects+=' "static" '
|
||||
fi
|
||||
fi
|
||||
|
||||
projects_e2e=${projects_e2e%?}
|
||||
projects_e2e=[${projects_e2e// /,}]
|
||||
projects=${projects%?}
|
||||
projects=[${projects// /,}]
|
||||
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
|
||||
echo PROJECTS=$projects >> $GITHUB_ENV
|
||||
|
||||
@@ -74,6 +74,10 @@ jobs:
|
||||
envName="mainnet"
|
||||
bucketName="tools.vega.xyz"
|
||||
fi
|
||||
if [[ "${{ matrix.app }}" = "static" ]]; then
|
||||
envName="mainnet"
|
||||
bucketName="static.vega.xyz"
|
||||
fi
|
||||
elif [[ "${{ github.ref }}" =~ .*main$ ]]; then
|
||||
envName="mainnet"
|
||||
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ "${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }}" = "true" ]]; then
|
||||
|
||||
@@ -63,7 +63,7 @@ context(
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
describe('Eth wallet - contains VEGA tokens', function () {
|
||||
describe.skip('Eth wallet - contains VEGA tokens', function () {
|
||||
beforeEach(
|
||||
'teardown wallet & drill into a specific validator',
|
||||
function () {
|
||||
|
||||
@@ -102,7 +102,8 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
</div>
|
||||
|
||||
{proposal.terms.change.__typename !== 'NewMarket' &&
|
||||
proposal.terms.change.__typename !== 'UpdateMarket' && (
|
||||
proposal.terms.change.__typename !== 'UpdateMarket' &&
|
||||
proposal.terms.change.__typename !== 'NewFreeform' && (
|
||||
<div className="mb-4">
|
||||
<ProposalTerms data={proposal.terms} />
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Static
|
||||
|
||||
A static CDN for Vega assets
|
||||
A static CDN for Vega assets: `static.vega.xyz`
|
||||
|
||||
prepare assets by running: `yarn nx build static`
|
||||
|
||||
@@ -26,27 +26,27 @@
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:first-child {
|
||||
animation-delay: -50ms;
|
||||
animation-delay: -0.1s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
animation-delay: 0.3s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(3) {
|
||||
animation-delay: -0.6s;
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(4) {
|
||||
animation-delay: 0.4s;
|
||||
animation-delay: 1s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(5) {
|
||||
animation-delay: -0.5s;
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(6) {
|
||||
animation-delay: 0.3s;
|
||||
animation-delay: 0.9s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(7) {
|
||||
@@ -54,11 +54,11 @@
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(8) {
|
||||
animation-delay: 2s;
|
||||
animation-delay: 1.6s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(9) {
|
||||
animation-delay: -0.9s;
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(10) {
|
||||
@@ -66,7 +66,7 @@
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(11) {
|
||||
animation-delay: -0.55s;
|
||||
animation-delay: -2.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(12) {
|
||||
@@ -74,21 +74,57 @@
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(13) {
|
||||
animation-delay: -0.65s;
|
||||
animation-delay: -1.95s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(14) {
|
||||
animation-delay: 0.7s;
|
||||
animation-delay: 2.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(15) {
|
||||
animation-delay: -3.75s;
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(16) {
|
||||
animation-delay: 1.6s;
|
||||
animation-delay: 4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(17) {
|
||||
animation-delay: -0.85s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(18) {
|
||||
animation-delay: 1.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(19) {
|
||||
animation-delay: -1.9s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(20) {
|
||||
animation-delay: 5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(21) {
|
||||
animation-delay: -5.25s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(22) {
|
||||
animation-delay: 4.4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(23) {
|
||||
animation-delay: -5.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(24) {
|
||||
animation-delay: 4.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(25) {
|
||||
animation-delay: -5s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
animation: flickering 0.4s linear infinite alternate;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: black;
|
||||
}
|
||||
.pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@for $i from 0 through 25 {
|
||||
.loader-item:nth-child(#{$i}) {
|
||||
@if $i % 2 == 0 {
|
||||
animation-delay: #{$i * 50 * random(5)}ms;
|
||||
animation-direction: reverse;
|
||||
} @else {
|
||||
animation-delay: #{$i * -50 * random(5)}ms;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
}
|
||||
}
|
||||
.loader-item {
|
||||
animation: flickering 0.4s linear alternate infinite;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
html.dark {
|
||||
.pre-loader {
|
||||
.loader-item {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,3 +450,17 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
.should('have.text', 'View on Explorer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Closed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-034
|
||||
cy.getByTestId('tab-closed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import type { MarketsQuery } from '@vegaprotocol/markets';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-all-markets"] .ag-center-cols-container .ag-row';
|
||||
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
||||
|
||||
describe('markets all table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -60,7 +63,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
// 6001-MARK-035
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="tradableInstrument.instrument.code"]')
|
||||
.find(colInstrumentCode)
|
||||
.should('have.text', 'SOLUSD');
|
||||
|
||||
// 6001-MARK-036
|
||||
@@ -155,6 +158,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('able to open and sort full market list - market page', () => {
|
||||
// 6001-MARK-064
|
||||
const ExpectedSortedMarkets = [
|
||||
'AAPL.MF21',
|
||||
'BTCUSD.MF21',
|
||||
@@ -167,8 +171,38 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
|
||||
for (let i = 0; i < ExpectedSortedMarkets.length; i++) {
|
||||
cy.get(`[row-index=${i}]`)
|
||||
.find('[col-id="tradableInstrument.instrument.code"]')
|
||||
.find(colInstrumentCode)
|
||||
.should('have.text', ExpectedSortedMarkets[i]);
|
||||
}
|
||||
});
|
||||
|
||||
it('can drag and drop columns', () => {
|
||||
// 6001-MARK-065
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get(colInstrumentCode)
|
||||
.realMouseDown()
|
||||
.realMouseMove(700, 15)
|
||||
.realMouseUp();
|
||||
cy.get(colInstrumentCode).should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const markets: MarketsQuery = {};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Markets', markets);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-048
|
||||
cy.getByTestId('tab-all-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
const marketInfoBtn = 'Info';
|
||||
const row = 'key-value-table-row';
|
||||
const marketTitle = 'accordion-title';
|
||||
const externalLink = 'external-link';
|
||||
const accordionContent = 'accordion-content';
|
||||
const blockExplorerLink = 'block-explorer-link';
|
||||
const dialogClose = 'dialog-close';
|
||||
const dialogContent = 'dialog-content';
|
||||
const externalLink = 'external-link';
|
||||
const githubLink = 'github-link';
|
||||
const liquidityLink = 'view-liquidity-link';
|
||||
const marketInfoBtn = 'Info';
|
||||
const marketTitle = 'accordion-title';
|
||||
const providerName = 'provider-name';
|
||||
const oracleBannerStatus = 'oracle-banner-status';
|
||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
||||
const oracleFullProfile = 'oracle-full-profile';
|
||||
const row = 'key-value-table-row';
|
||||
const verifiedProofs = 'verified-proofs';
|
||||
|
||||
describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -17,12 +20,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
before(() => {
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
undefined,
|
||||
undefined,
|
||||
'COMPROMISED'
|
||||
);
|
||||
cy.mockTradingPage(MarketState.STATE_ACTIVE);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
@@ -30,16 +28,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
cy.wait('@MarketInfo');
|
||||
});
|
||||
|
||||
it('show oracle banner', () => {
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerDialogTrigger)
|
||||
.should('contain.text', 'Show more')
|
||||
.click();
|
||||
cy.getByTestId(oracleFullProfile).should('exist');
|
||||
});
|
||||
|
||||
it('current fees displayed', () => {
|
||||
// 6002-MDET-101
|
||||
cy.getByTestId(marketTitle).contains('Current fees').click();
|
||||
validateMarketDataRow(0, 'Maker Fee', '0.02%');
|
||||
validateMarketDataRow(1, 'Infrastructure Fee', '0.05%');
|
||||
@@ -48,6 +38,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('market price', () => {
|
||||
// 6002-MDET-102
|
||||
cy.getByTestId(marketTitle).contains('Market price').click();
|
||||
validateMarketDataRow(0, 'Mark Price', '46,126.90058');
|
||||
validateMarketDataRow(1, 'Best Bid Price', '44,126.90058 ');
|
||||
@@ -56,6 +47,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('market volume displayed', () => {
|
||||
// 6002-MDET-103
|
||||
cy.getByTestId(marketTitle).contains('Market volume').click();
|
||||
validateMarketDataRow(1, 'Open Interest', '-');
|
||||
validateMarketDataRow(2, 'Best Bid Volume', '1');
|
||||
@@ -65,11 +57,13 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('insurance pool displayed', () => {
|
||||
// 6002-MDET-104
|
||||
cy.getByTestId(marketTitle).contains('Insurance pool').click();
|
||||
validateMarketDataRow(0, 'Balance', '0');
|
||||
});
|
||||
|
||||
it('key details displayed', () => {
|
||||
// 6002-MDET-201
|
||||
cy.getByTestId(marketTitle).contains('Key details').click();
|
||||
|
||||
validateMarketDataRow(0, 'Name', 'BTCUSD Monthly (30 Jun 2022)');
|
||||
@@ -85,6 +79,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('instrument displayed', () => {
|
||||
// 6002-MDET-202
|
||||
cy.getByTestId(marketTitle).contains('Instrument').click();
|
||||
|
||||
validateMarketDataRow(0, 'Market Name', 'BTCUSD Monthly (30 Jun 2022)');
|
||||
@@ -93,7 +88,30 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(3, 'Quote Name', 'BTC');
|
||||
});
|
||||
|
||||
it('oracle displayed', () => {
|
||||
// 6002-MDET-203
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(providerName)
|
||||
.and('contain', 'Another oracle');
|
||||
|
||||
cy.getByTestId(providerName).should('be.visible').click();
|
||||
cy.getByTestId(dialogContent)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(blockExplorerLink).contains('Block explorer');
|
||||
cy.getByTestId(githubLink).contains('Oracle repository');
|
||||
});
|
||||
cy.getByTestId(dialogClose).click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(verifiedProofs)
|
||||
.and('contain', '1');
|
||||
});
|
||||
|
||||
it('settlement asset displayed', () => {
|
||||
// 6002-MDET-206
|
||||
cy.getByTestId(marketTitle).contains('Settlement asset').click();
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
|
||||
@@ -112,9 +130,12 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
);
|
||||
validateMarketDataRow(8, 'Withdrawal threshold', '0.0005');
|
||||
validateMarketDataRow(9, 'Lifetime limit', '1,230');
|
||||
validateMarketDataRow(10, 'Infrastructure fee account balance', '0.00001');
|
||||
validateMarketDataRow(11, 'Global reward pool account balance', '0.00002');
|
||||
});
|
||||
|
||||
it('metadata displayed', () => {
|
||||
// 6002-MDET-207
|
||||
cy.getByTestId(marketTitle).contains('Metadata').click();
|
||||
|
||||
validateMarketDataRow(0, 'Formerly', '076BB86A5AA41E3E');
|
||||
@@ -125,18 +146,21 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('risk model displayed', () => {
|
||||
// 6002-MDET-208
|
||||
cy.getByTestId(marketTitle).contains('Risk model').click();
|
||||
validateMarketDataRow(0, 'Tau', '0.0001140771161');
|
||||
validateMarketDataRow(1, 'Risk Aversion Parameter', '0.01');
|
||||
});
|
||||
|
||||
it('risk parameters displayed', () => {
|
||||
// 6002-MDET-209
|
||||
cy.getByTestId(marketTitle).contains('Risk parameters').click();
|
||||
validateMarketDataRow(0, 'R', '0.016');
|
||||
validateMarketDataRow(1, 'Sigma', '0.3');
|
||||
});
|
||||
|
||||
it('risk factors displayed', () => {
|
||||
// 6002-MDET-210
|
||||
cy.getByTestId(marketTitle).contains('Risk factors').click();
|
||||
|
||||
validateMarketDataRow(0, 'Short', '0.008571790367285281');
|
||||
@@ -144,6 +168,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('price monitoring bounds displayed', () => {
|
||||
// 6002-MDET-211
|
||||
cy.getByTestId(marketTitle).contains('Price monitoring bounds 1').click();
|
||||
cy.get('p.col-span-1').contains('99.99999% probability price bounds');
|
||||
cy.get('p.col-span-1').contains('Within 43,200 seconds');
|
||||
@@ -152,6 +177,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('liquidity monitoring parameters displayed', () => {
|
||||
// 6002-MDET-212
|
||||
cy.getByTestId(marketTitle)
|
||||
.contains('Liquidity monitoring parameters')
|
||||
.click();
|
||||
@@ -162,6 +188,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('liquidity displayed', () => {
|
||||
// 6002-MDET-213
|
||||
cy.getByTestId(marketTitle)
|
||||
.contains(/Liquidity(?! m)/)
|
||||
.click();
|
||||
@@ -169,14 +196,14 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(0, 'Target Stake', '10.00 tBTC');
|
||||
validateMarketDataRow(1, 'Supplied Stake', '0.01 tBTC');
|
||||
validateMarketDataRow(2, 'Market Value Proxy', '20.00 tBTC');
|
||||
|
||||
cy.getByTestId('view-liquidity-link').should(
|
||||
cy.getByTestId(liquidityLink).should(
|
||||
'have.text',
|
||||
'View liquidity provision table'
|
||||
);
|
||||
});
|
||||
|
||||
it('liquidity price range displayed', () => {
|
||||
// 6002-MDET-214
|
||||
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
|
||||
|
||||
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
|
||||
@@ -184,29 +211,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(2, 'Highest Price', '47,049.438 BTC');
|
||||
});
|
||||
|
||||
it('oracle displayed', () => {
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(providerName)
|
||||
.and('contain', 'Another oracle');
|
||||
|
||||
cy.getByTestId(providerName).should('be.visible').click();
|
||||
|
||||
cy.getByTestId('dialog-content')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId('block-explorer-link').contains('Block explorer');
|
||||
cy.getByTestId('github-link').contains('Oracle repository');
|
||||
});
|
||||
cy.getByTestId('dialog-close').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId('verified-proofs')
|
||||
.and('contain', '1');
|
||||
});
|
||||
|
||||
it('proposal displayed', () => {
|
||||
// 6002-MDET-301
|
||||
cy.getByTestId(marketTitle).contains('Proposal').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
cy.wait('@MarketsCandles');
|
||||
});
|
||||
|
||||
// 6001-MARK-066
|
||||
it('can toggle the sidebar', () => {
|
||||
cy.getByTestId('market-selector').should('be.visible');
|
||||
cy.getByTestId('sidebar-toggle').click();
|
||||
@@ -84,7 +85,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 6001-MARK-27
|
||||
// 6001-MARK-027
|
||||
it('can use the filter options', () => {
|
||||
// product type
|
||||
cy.getByTestId('product-Spot').click();
|
||||
@@ -94,7 +95,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId('product-Future').click();
|
||||
cy.getByTestId(list).find('a').should('have.length', 4);
|
||||
|
||||
// 6001-MARK-29
|
||||
// 6001-MARK-029
|
||||
cy.getByTestId(searchInput).clear().type('btc');
|
||||
cy.getByTestId(list).find('a').should('have.length', 2);
|
||||
cy.getByTestId(list).find('a').eq(1).contains('BTCUSD.MF21');
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const marketSummaryBlock = 'header-summary';
|
||||
const marketExpiry = 'market-expiry';
|
||||
const marketPrice = 'market-price';
|
||||
const marketChange = 'market-change';
|
||||
const marketVolume = 'market-volume';
|
||||
const marketMode = 'market-trading-mode';
|
||||
const marketState = 'market-state';
|
||||
const marketSettlement = 'market-settlement-asset';
|
||||
const percentageValue = 'price-change-percentage';
|
||||
const priceChangeValue = 'price-change';
|
||||
const expirtyTooltip = 'expiry-tooltip';
|
||||
const externalLink = 'external-link';
|
||||
const itemHeader = 'item-header';
|
||||
const itemValue = 'item-value';
|
||||
const link = 'link';
|
||||
const liquidityLink = 'view-liquidity-link';
|
||||
const liquiditySupplied = 'liquidity-supplied';
|
||||
const liquiditySuppliedTooltip = 'liquidity-supplied-tooltip';
|
||||
const marketChange = 'market-change';
|
||||
const marketExpiry = 'market-expiry';
|
||||
const marketMode = 'market-trading-mode';
|
||||
const marketName = 'header-title';
|
||||
const marketPrice = 'market-price';
|
||||
const marketSettlement = 'market-settlement-asset';
|
||||
const marketState = 'market-state';
|
||||
const marketSummaryBlock = 'header-summary';
|
||||
const marketVolume = 'market-volume';
|
||||
const percentageValue = 'price-change-percentage';
|
||||
const priceChangeValue = 'price-change';
|
||||
const tradingModeTooltip = 'trading-mode-tooltip';
|
||||
|
||||
describe('Market trading page', () => {
|
||||
before(() => {
|
||||
@@ -31,10 +39,12 @@ describe('Market trading page', () => {
|
||||
// 7002-SORD-001
|
||||
// 7002-SORD-002
|
||||
it('must display market name', () => {
|
||||
cy.getByTestId('header-title').should('not.be.empty');
|
||||
// 6002-MDET-001
|
||||
cy.getByTestId(marketName).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('must see market expiry', () => {
|
||||
// 6002-MDET-002
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketExpiry).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Expiry');
|
||||
@@ -44,6 +54,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market price', () => {
|
||||
// 6002-MDET-003
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketPrice).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Price');
|
||||
@@ -53,6 +64,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market change', () => {
|
||||
// 6002-MDET-004
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketChange).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
|
||||
@@ -63,6 +75,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market volume', () => {
|
||||
// 6002-MDET-005
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketVolume).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
|
||||
@@ -72,16 +85,21 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market mode', () => {
|
||||
// 6002-MDET-006
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketMode).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
cy.getByTestId(itemValue).should(
|
||||
'have.text',
|
||||
'Monitoring auction - liquidity (target not met)'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market state', () => {
|
||||
//7002-SORD-061
|
||||
it('must see market status', () => {
|
||||
// 6002-MDET-007
|
||||
// 7002-SORD-061
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketState).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Status');
|
||||
@@ -91,6 +109,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market settlement', () => {
|
||||
// 6002-MDET-008
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketSettlement).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Settlement asset');
|
||||
@@ -98,15 +117,12 @@ describe('Market trading page', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market mode', () => {
|
||||
it('must see market liquidity supplied', () => {
|
||||
// 6002-MDET-009
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketMode).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
|
||||
cy.getByTestId(itemValue).should(
|
||||
'have.text',
|
||||
'Monitoring auction - liquidity (target not met)'
|
||||
);
|
||||
cy.getByTestId(liquiditySupplied).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Liquidity supplied');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -121,14 +137,14 @@ describe('Market trading page', () => {
|
||||
.realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId('expiry-tooltip')
|
||||
cy.getByTestId(expirtyTooltip)
|
||||
.eq(0)
|
||||
.should(
|
||||
'contain.text',
|
||||
'This market expires when triggered by its oracle, not on a set date.'
|
||||
)
|
||||
.within(() => {
|
||||
cy.getByTestId('link')
|
||||
cy.getByTestId(link)
|
||||
.should('have.attr', 'href')
|
||||
.and('include', Cypress.env('EXPLORER_URL'));
|
||||
});
|
||||
@@ -154,15 +170,14 @@ describe('Market trading page', () => {
|
||||
.realHover();
|
||||
});
|
||||
});
|
||||
|
||||
cy.getByTestId('trading-mode-tooltip')
|
||||
cy.getByTestId(tradingModeTooltip)
|
||||
.should(
|
||||
'contain.text',
|
||||
'This market is in auction until it reaches sufficient liquidity.'
|
||||
)
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
cy.getByTestId(externalLink)
|
||||
.should('have.attr', 'href')
|
||||
.and('include', Cypress.env('TRADING_MODE_LINK'));
|
||||
|
||||
@@ -174,5 +189,27 @@ describe('Market trading page', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should see liquidity supplied tooltip', () => {
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(liquiditySupplied).within(() => {
|
||||
cy.getByTestId(itemValue).realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(liquiditySuppliedTooltip)
|
||||
.should('contain.text', 'Supplied stake')
|
||||
.and('contain.text', 'Target stake')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId(liquidityLink).should(
|
||||
'have.text',
|
||||
'View liquidity provision table'
|
||||
);
|
||||
cy.getByTestId(externalLink).should(
|
||||
'have.text',
|
||||
'Learn about providing liquidity'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { checkSorting } from '@vegaprotocol/cypress';
|
||||
import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
|
||||
import type { ProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row';
|
||||
const colMarketId = '[col-id="market"]';
|
||||
|
||||
describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage().then(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see table headers', () => {
|
||||
@@ -35,10 +35,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
|
||||
it('renders markets correctly', () => {
|
||||
// 6001-MARK-049
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="market"]')
|
||||
.should('have.text', 'ETHUSD');
|
||||
cy.get(rowSelector).first().find(colMarketId).should('have.text', 'ETHUSD');
|
||||
|
||||
// 6001-MARK-050
|
||||
cy.get(rowSelector)
|
||||
@@ -119,6 +116,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
);
|
||||
});
|
||||
it('proposed markets tab should be sorted properly', () => {
|
||||
// 6001-MARK-062
|
||||
cy.get('[data-testid="Proposed markets"]').click({ force: true });
|
||||
const marketColDefault = [
|
||||
'ETHUSD',
|
||||
@@ -196,4 +194,31 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
];
|
||||
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
|
||||
});
|
||||
|
||||
it('can drag and drop columns', () => {
|
||||
// 6001-MARK-063
|
||||
cy.get(colMarketId).realMouseDown().realMouseMove(700, 15).realMouseUp();
|
||||
cy.get(colMarketId).should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const proposal: ProposalsListQuery = {};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-061
|
||||
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
||||
const oracleBannerStatus = 'oracle-banner-status';
|
||||
const oracleFullProfile = 'oracle-full-profile';
|
||||
|
||||
describe('oracle information', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
undefined,
|
||||
undefined,
|
||||
'COMPROMISED'
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('show oracle banner', () => {
|
||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerDialogTrigger)
|
||||
.should('contain.text', 'Show more')
|
||||
.click();
|
||||
cy.getByTestId(oracleFullProfile).should('exist');
|
||||
});
|
||||
});
|
||||
@@ -88,7 +88,8 @@ describe(
|
||||
.pop()
|
||||
?.toLowerCase()} and not accepting orders`
|
||||
);
|
||||
cy.getByTestId('place-order').should('be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId('place-order').should('be.enabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,7 +77,8 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
it('must warn if order size input has too many digits after the decimal place', function () {
|
||||
// 7002-SORD-016
|
||||
cy.getByTestId(orderSizeField).clear().type('1.234');
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('dealticket-error-message-size-market').should(
|
||||
'have.text',
|
||||
'Size must be whole numbers for this market'
|
||||
@@ -86,12 +87,13 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
|
||||
it('must warn if order size is set to 0', function () {
|
||||
cy.getByTestId(orderSizeField).clear().type('0');
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('dealticket-error-message-size-market').should(
|
||||
'have.text',
|
||||
'Size cannot be lower than 1'
|
||||
);
|
||||
});
|
||||
|
||||
it('must have total margin available', () => {
|
||||
// 7001-COLL-011
|
||||
cy.getByTestId('tab-ticket')
|
||||
|
||||
@@ -23,13 +23,14 @@ describe(
|
||||
});
|
||||
|
||||
it('should show an error if your balance is zero', () => {
|
||||
cy.getByTestId('place-order').should('be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId('place-order').should('be.enabled');
|
||||
// 7002-SORD-003
|
||||
cy.getByTestId('dealticket-error-message-zero-balance').should(
|
||||
'have.text',
|
||||
'You need ' +
|
||||
'tDAI' +
|
||||
' in your wallet to trade in this market.See all your collateral.Make a deposit'
|
||||
' in your wallet to trade in this market. See all your collateral.Make a deposit'
|
||||
);
|
||||
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
|
||||
});
|
||||
|
||||
@@ -34,9 +34,9 @@ describe('suspended market validation', { tags: '@regression' }, () => {
|
||||
|
||||
it('should show warning for market order', function () {
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
cy.getByTestId('dealticket-error-message-type').should(
|
||||
'have.text',
|
||||
'This market is in auction until it reaches sufficient liquidity. Only limit orders are permitted when market is in auction'
|
||||
@@ -59,7 +59,7 @@ describe('suspended market validation', { tags: '@regression' }, () => {
|
||||
cy.getByTestId(orderTIFDropDown).select(
|
||||
TIFlist.filter((item) => item.code === 'FOK')[0].value
|
||||
);
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('dealticket-error-message-tif').should(
|
||||
'have.text',
|
||||
'This market is in auction until it reaches sufficient liquidity. Until the auction ends, you can only place GFA, GTT, or GTC limit orders'
|
||||
|
||||
@@ -3,6 +3,18 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { marketsDataQuery } from '@vegaprotocol/mock';
|
||||
import { positionsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
// #region consts
|
||||
const closePosition = 'close-position';
|
||||
const dialogCloseX = 'dialog-close';
|
||||
const dialogContent = 'dialog-content';
|
||||
const dropDownMenu = 'dropdown-menu';
|
||||
const marketActionsContent = 'market-actions-content';
|
||||
const positions = 'Positions';
|
||||
const tabPositions = 'tab-positions';
|
||||
const toastContent = 'toast-content';
|
||||
const tooltipContent = 'tooltip-content';
|
||||
// #endregion
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
@@ -11,8 +23,9 @@ beforeEach(() => {
|
||||
|
||||
describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
it('renders positions on trading page', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Positions').click();
|
||||
visitAndClickPositions();
|
||||
// 7004-POSI-001
|
||||
// 7004-POSI-002
|
||||
validatePositionsDisplayed();
|
||||
});
|
||||
|
||||
@@ -35,174 +48,313 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
}
|
||||
aliasGQLQuery(req, 'Positions', positions);
|
||||
});
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId('Positions').click();
|
||||
visitAndClickPositions();
|
||||
// 7004-POSI-001
|
||||
// 7004-POSI-002
|
||||
validatePositionsDisplayed(true);
|
||||
});
|
||||
describe('renders position among some graphql errors', () => {
|
||||
it('rows should be displayed despite errors', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for market: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['marketsConnection', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const marketData = marketsDataQuery();
|
||||
const edges = marketData.marketsConnection?.edges.map((market) => {
|
||||
const replace =
|
||||
market.node.data?.market.id === 'market-2' ? null : market.node.data;
|
||||
return { ...market, node: { ...market.node, data: replace } };
|
||||
});
|
||||
const overrides = {
|
||||
...marketData,
|
||||
marketsConnection: { ...marketData.marketsConnection, edges },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
const emptyCells = [
|
||||
'notional',
|
||||
'markPrice',
|
||||
'currentLeverage',
|
||||
'averageEntryPrice',
|
||||
];
|
||||
cy.getByTestId('tab-positions')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(
|
||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||
)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
emptyCells.forEach((cell) => {
|
||||
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('error message should be displayed', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for asset: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['assets', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const overrides = {
|
||||
marketsConnection: { edges: [] },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.get('[data-testid="tab-positions"]').contains('no market data');
|
||||
});
|
||||
|
||||
it('Close my position', () => {
|
||||
visitAndClickPositions();
|
||||
cy.getByTestId(closePosition).first().click();
|
||||
// 7004-POSI-010
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
'Awaiting confirmation'
|
||||
);
|
||||
});
|
||||
|
||||
describe('sorting by ag-grid columns should work well', () => {
|
||||
it('sorting by Market', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = [
|
||||
'ACTIVE MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'SUSPENDED MARKET',
|
||||
];
|
||||
const marketsSortedAsc = ['ACTIVE MARKET', 'Apple Monthly (30 Jun 2022)'];
|
||||
const marketsSortedDesc = [
|
||||
'SUSPENDED MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ACTIVE MARKET',
|
||||
];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'marketName',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
it('sorting by notional', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = [
|
||||
'276,761.40348',
|
||||
'46,126.90058',
|
||||
'1,688.20',
|
||||
];
|
||||
const marketsSortedAsc = ['1,688.20', '46,126.90058', '276,761.40348'];
|
||||
const marketsSortedDesc = ['276,761.40348', '46,126.90058', '1,688.20'];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'notional',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
it('sorting by unrealisedPNL', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = ['8.95', '-0.22519', '8.95'];
|
||||
const marketsSortedAsc = ['-0.22519', '8.95', '8.95'];
|
||||
const marketsSortedDesc = ['8.95', '8.95', '-0.22519'];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'unrealisedPNL',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function validatePositionsDisplayed(multiKey = false) {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions').within(() => {
|
||||
cy.get('[col-id="marketName"]')
|
||||
.should('be.visible')
|
||||
.each(($marketSymbol) => {
|
||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('.ag-center-cols-container [col-id="openVolume"]').each(
|
||||
($openVolume) => {
|
||||
cy.wrap($openVolume).invoke('text').should('not.be.empty');
|
||||
}
|
||||
);
|
||||
|
||||
// includes average entry price, mark price, realised PNL & leverage
|
||||
cy.getByTestId('flash-cell').each(($prices) => {
|
||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
if (!multiKey) {
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
}
|
||||
|
||||
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
||||
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
||||
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
||||
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
||||
|
||||
cy.get('.ag-header-row [col-id="notional"]')
|
||||
.should('contain.text', 'Notional')
|
||||
.realHover();
|
||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||
});
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
});
|
||||
describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
it('rows should be displayed despite errors', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for market: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['marketsConnection', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const marketData = marketsDataQuery();
|
||||
const edges = marketData.marketsConnection?.edges.map((market) => {
|
||||
const replace =
|
||||
market.node.data?.market.id === 'market-2' ? null : market.node.data;
|
||||
return { ...market, node: { ...market.node, data: replace } };
|
||||
});
|
||||
const overrides = {
|
||||
...marketData,
|
||||
marketsConnection: { ...marketData.marketsConnection, edges },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
const emptyCells = [
|
||||
'notional',
|
||||
'markPrice',
|
||||
'currentLeverage',
|
||||
'averageEntryPrice',
|
||||
];
|
||||
cy.getByTestId(tabPositions)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(
|
||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||
)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
emptyCells.forEach((cell) => {
|
||||
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('error message should be displayed', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for asset: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['assets', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const overrides = {
|
||||
marketsConnection: { edges: [] },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(tabPositions).contains('no market data');
|
||||
});
|
||||
|
||||
it('sorting by Market', () => {
|
||||
visitAndClickPositions();
|
||||
const marketsSortedDefault = [
|
||||
'ACTIVE MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ETHBTC Quarterly (30 Jun 2022)',
|
||||
'SUSPENDED MARKET',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'ACTIVE MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ETHBTC Quarterly (30 Jun 2022)',
|
||||
'SUSPENDED MARKET',
|
||||
];
|
||||
const marketsSortedDesc = [
|
||||
'SUSPENDED MARKET',
|
||||
'ETHBTC Quarterly (30 Jun 2022)',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ACTIVE MARKET',
|
||||
];
|
||||
cy.getByTestId(positions).click();
|
||||
// 7004-POSI-003
|
||||
checkSorting(
|
||||
'marketName',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
|
||||
it('Resize column', () => {
|
||||
let elementWidth: number;
|
||||
visitAndClickPositions();
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.find('.ag-header-cell-resize')
|
||||
.realMouseDown()
|
||||
.realMouseMove(250, 0)
|
||||
.realMouseUp();
|
||||
});
|
||||
|
||||
// 7004-POSI-006
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.invoke('width')
|
||||
.should('be.greaterThan', 250);
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.invoke('width')
|
||||
.then((width) => {
|
||||
elementWidth = width as number;
|
||||
})
|
||||
.then(() => {
|
||||
let localStorageCopy: Record<string, string>;
|
||||
cy.window().then((win) => {
|
||||
localStorageCopy = { ...win.localStorage };
|
||||
});
|
||||
|
||||
cy.reload();
|
||||
cy.window().then((win) => {
|
||||
Object.keys(localStorageCopy).forEach((key) => {
|
||||
win.localStorage.setItem(key, localStorageCopy[key]);
|
||||
});
|
||||
});
|
||||
|
||||
// 7004-POSI-012
|
||||
cy.get('[col-id="marketName"]')
|
||||
.invoke('width')
|
||||
.should('equal', elementWidth);
|
||||
});
|
||||
});
|
||||
|
||||
it('Scroll horizontally', () => {
|
||||
visitAndClickPositions();
|
||||
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.find('.ag-header-cell-resize')
|
||||
.realMouseDown()
|
||||
.realMouseMove(400, 0)
|
||||
.realMouseUp();
|
||||
});
|
||||
cy.get('[col-id="marketName"]').should('be.visible');
|
||||
cy.get('.ag-body-horizontal-scroll-viewport').realMouseWheel({
|
||||
deltaX: 500,
|
||||
});
|
||||
// 7004-POSI-004
|
||||
cy.get('[col-id="updatedAt"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('Drag and drop columns', () => {
|
||||
visitAndClickPositions();
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get('[col-id="marketName"]')
|
||||
.realMouseDown()
|
||||
.realMouseMove(700, 15)
|
||||
.realMouseUp();
|
||||
|
||||
// 7004-POSI-005
|
||||
cy.get('[col-id="marketName"]').should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
|
||||
it('I can see warnings', () => {
|
||||
visitAndClickPositions();
|
||||
|
||||
cy.get('[col-id="openVolume"]').within(() => {
|
||||
cy.get('[aria-label="warning-sign icon"]')
|
||||
.should('be.visible')
|
||||
.realHover();
|
||||
});
|
||||
// 7004-POSI-011
|
||||
cy.getByTestId(tooltipContent).should('be.visible');
|
||||
});
|
||||
|
||||
it('Positive and Negative color change', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(positions).click();
|
||||
// 7004-POSI-007
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
assertPNLColor(
|
||||
'[col-id="realisedPNL"]',
|
||||
'text-vega-green',
|
||||
'text-vega-pink'
|
||||
);
|
||||
});
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
assertPNLColor(
|
||||
'[col-id="unrealisedPNL"]',
|
||||
'text-vega-green',
|
||||
'text-vega-pink'
|
||||
);
|
||||
});
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
assertPNLColor(
|
||||
'[col-id="openVolume"]',
|
||||
'text-vega-green',
|
||||
'text-vega-pink'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('View settlement asset', () => {
|
||||
visitAndClickPositions();
|
||||
cy.get('[col-id="asset"]').within(() => {
|
||||
cy.get('button[type="button"]').first().click();
|
||||
});
|
||||
// 7004-POSI-008
|
||||
cy.getByTestId(dialogContent).should('be.visible');
|
||||
cy.getByTestId(dialogCloseX).click();
|
||||
cy.getByTestId(dropDownMenu).first().click();
|
||||
cy.getByTestId(marketActionsContent).click();
|
||||
// 7004-POSI-009
|
||||
cy.getByTestId(dialogContent).should('be.visible');
|
||||
});
|
||||
});
|
||||
function validatePositionsDisplayed(multiKey = false) {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions').within(() => {
|
||||
cy.get('[col-id="marketName"]')
|
||||
.should('be.visible')
|
||||
.each(($marketSymbol) => {
|
||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('.ag-center-cols-container [col-id="openVolume"]').each(
|
||||
($openVolume) => {
|
||||
cy.wrap($openVolume).invoke('text').should('not.be.empty');
|
||||
}
|
||||
);
|
||||
|
||||
// includes average entry price, mark price, realised PNL & leverage
|
||||
cy.getByTestId('flash-cell').each(($prices) => {
|
||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
if (!multiKey) {
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
}
|
||||
|
||||
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
||||
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
||||
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
||||
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
||||
|
||||
cy.get('.ag-header-row [col-id="notional"]')
|
||||
.should('contain.text', 'Notional')
|
||||
.realHover();
|
||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||
});
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
function assertPNLColor(
|
||||
pnlSelector: string,
|
||||
positiveClass: string,
|
||||
negativeClass: string
|
||||
) {
|
||||
cy.get(pnlSelector).each(($el) => {
|
||||
const value = parseFloat($el.text());
|
||||
|
||||
if (value > 0) {
|
||||
cy.wrap($el).invoke('attr', 'class').should('contain', positiveClass);
|
||||
} else if (value < 0) {
|
||||
cy.wrap($el).invoke('attr', 'class').should('contain', negativeClass);
|
||||
} else if (value == 0) {
|
||||
cy.wrap($el)
|
||||
.invoke('attr', 'class')
|
||||
.should('not.contain', negativeClass, positiveClass);
|
||||
} else {
|
||||
throw new Error('Unexpected value');
|
||||
}
|
||||
});
|
||||
}
|
||||
function visitAndClickPositions() {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(positions).click();
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.16-core-0.71.5
|
||||
NX_APP_VERSION=v0.20.18-core-0.71.8
|
||||
|
||||
@@ -99,7 +99,7 @@ export const MarketLiquiditySupplied = ({
|
||||
AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS;
|
||||
|
||||
const description = marketId ? (
|
||||
<section>
|
||||
<section data-testid="liquidity-supplied-tooltip">
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('Supplied stake')}</span>
|
||||
|
||||
@@ -38,7 +38,6 @@ type Data = Item[];
|
||||
type QueryData = {
|
||||
data: Data;
|
||||
pageInfo?: PageInfo;
|
||||
totalCount?: number;
|
||||
};
|
||||
|
||||
type CombinedData = {
|
||||
@@ -115,7 +114,6 @@ const paginatedSubscribe = makeDataProvider<
|
||||
first,
|
||||
append: defaultAppend,
|
||||
getPageInfo: (r) => r?.pageInfo ?? null,
|
||||
getTotalCount: (r) => r?.totalCount,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -373,32 +371,10 @@ describe('data provider', () => {
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('fills data with nulls if pagination is enabled', async () => {
|
||||
const totalCount = 1000;
|
||||
const data: Item[] = new Array(first).fill(null).map((v, i) => ({
|
||||
cursor: i.toString(),
|
||||
node: {
|
||||
id: i.toString(),
|
||||
},
|
||||
}));
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data,
|
||||
totalCount,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
},
|
||||
});
|
||||
expect(callback.mock.calls[1][0].data?.length).toBe(totalCount);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('loads requested data blocks and inserts data with total count', async () => {
|
||||
const totalCount = 1000;
|
||||
it('loads requested data blocks', async () => {
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data: generateData(),
|
||||
totalCount,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '100',
|
||||
@@ -407,168 +383,25 @@ describe('data provider', () => {
|
||||
|
||||
// load next page
|
||||
subscription.load && subscription.load();
|
||||
let lastQueryArgs =
|
||||
const lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
expect(lastQueryArgs?.variables?.['pagination']).toEqual({
|
||||
after: '100',
|
||||
first,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(100),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
hasNextPage: false,
|
||||
endCursor: '200',
|
||||
},
|
||||
});
|
||||
|
||||
// load page with skip
|
||||
subscription.load && subscription.load(500, 600);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '200',
|
||||
first,
|
||||
skip: 300,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(500),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '600',
|
||||
},
|
||||
});
|
||||
|
||||
// load in the gap
|
||||
subscription.load && subscription.load(400, 500);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '200',
|
||||
first,
|
||||
skip: 200,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(400),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '500',
|
||||
},
|
||||
});
|
||||
|
||||
// load page after last block
|
||||
subscription.load && subscription.load(700, 800);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '600',
|
||||
first,
|
||||
skip: 100,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(700),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '800',
|
||||
},
|
||||
});
|
||||
|
||||
// load last page shorter than expected
|
||||
subscription.load && subscription.load(950, 1050);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '800',
|
||||
first,
|
||||
skip: 150,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(950, 20),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
endCursor: '970',
|
||||
},
|
||||
});
|
||||
let lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(970);
|
||||
|
||||
// load next page when pageInfo.hasNextPage === false
|
||||
const clientQueryCallsLength = clientQuery.mock.calls.length;
|
||||
subscription.load && subscription.load();
|
||||
expect(clientQuery.mock.calls.length).toBe(clientQueryCallsLength);
|
||||
|
||||
// load last page longer than expected
|
||||
subscription.load && subscription.load(960, 1000);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '960',
|
||||
first,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(960, 40),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '1000',
|
||||
},
|
||||
});
|
||||
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(1000);
|
||||
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('loads requested data blocks and inserts data without totalCount', async () => {
|
||||
const totalCount = undefined;
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data: generateData(),
|
||||
totalCount,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '100',
|
||||
},
|
||||
});
|
||||
let lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(undefined);
|
||||
|
||||
// load next page
|
||||
subscription.load && subscription.load();
|
||||
await resolveQuery({
|
||||
data: generateData(100),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '200',
|
||||
},
|
||||
});
|
||||
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(undefined);
|
||||
|
||||
// load last page
|
||||
subscription.load && subscription.load();
|
||||
await resolveQuery({
|
||||
data: generateData(200, 50),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
endCursor: '250',
|
||||
},
|
||||
});
|
||||
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(250);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('sets total count when first page has no next page', async () => {
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data: generateData(),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
endCursor: '100',
|
||||
},
|
||||
});
|
||||
const lastCallbackArgs =
|
||||
callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(100);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
@@ -752,7 +585,7 @@ describe('derived data provider', () => {
|
||||
subscription.load && subscription.load();
|
||||
const lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
expect(lastQueryArgs?.variables?.['pagination']).toEqual({
|
||||
after: '100',
|
||||
first,
|
||||
});
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface UpdateCallback<Data, Delta> {
|
||||
loading: boolean;
|
||||
loaded: boolean;
|
||||
pageInfo: PageInfo | null;
|
||||
totalCount?: number;
|
||||
}
|
||||
): void;
|
||||
}
|
||||
@@ -40,9 +39,7 @@ export interface Reload {
|
||||
(forceReset?: boolean): void;
|
||||
}
|
||||
|
||||
type Pagination = Schema.Pagination & {
|
||||
skip?: number;
|
||||
};
|
||||
type Pagination = Schema.Pagination;
|
||||
|
||||
export interface PageInfo {
|
||||
startCursor?: string;
|
||||
@@ -83,12 +80,8 @@ export interface Append<Data> {
|
||||
data: Data | null,
|
||||
insertionData: Data | null,
|
||||
insertionPageInfo: PageInfo | null,
|
||||
pagination?: Pagination,
|
||||
totalCount?: number
|
||||
): {
|
||||
data: Data | null;
|
||||
totalCount?: number;
|
||||
};
|
||||
pagination?: Pagination
|
||||
): Data | null;
|
||||
}
|
||||
|
||||
interface GetData<QueryData, Data, Variables> {
|
||||
@@ -99,10 +92,6 @@ interface GetPageInfo<QueryData> {
|
||||
(queryData: QueryData): PageInfo | null;
|
||||
}
|
||||
|
||||
interface GetTotalCount<QueryData> {
|
||||
(queryData: QueryData): number | undefined;
|
||||
}
|
||||
|
||||
interface GetDelta<SubscriptionData, Delta, Variables> {
|
||||
(
|
||||
subscriptionData: SubscriptionData,
|
||||
@@ -119,44 +108,32 @@ export interface Edge<T extends Node> extends Cursor {
|
||||
node: T;
|
||||
}
|
||||
|
||||
export function defaultAppend<Data>(
|
||||
data: Data | null,
|
||||
insertionData: Data | null,
|
||||
export function defaultAppend<T extends Cursor>(
|
||||
data: T[] | null,
|
||||
insertionData: T[] | null,
|
||||
insertionPageInfo: PageInfo | null,
|
||||
pagination?: Pagination,
|
||||
totalCount?: number
|
||||
pagination?: Pagination
|
||||
) {
|
||||
if (data && insertionData && insertionPageInfo) {
|
||||
if (!(data instanceof Array) || !(insertionData instanceof Array)) {
|
||||
throw new Error(
|
||||
'data needs to be instance of Edge[] when using pagination'
|
||||
'data needs to be instance of Array[] when using pagination'
|
||||
);
|
||||
}
|
||||
if (pagination?.after) {
|
||||
if (data[data.length - 1].cursor === pagination?.after) {
|
||||
return [...data, ...insertionData];
|
||||
}
|
||||
const cursors = data.map((item) => item && item.cursor);
|
||||
const startIndex = cursors.lastIndexOf(pagination.after);
|
||||
if (startIndex !== -1) {
|
||||
const start = startIndex + 1 + (pagination.skip ?? 0);
|
||||
const end = start + insertionData.length;
|
||||
let updatedData = [
|
||||
...data.slice(0, start),
|
||||
...insertionData,
|
||||
...data.slice(end),
|
||||
];
|
||||
if (!insertionPageInfo.hasNextPage && end !== (totalCount ?? 0)) {
|
||||
// adjust totalCount if last page is shorter or longer than expected
|
||||
totalCount = end;
|
||||
updatedData = updatedData.slice(0, end);
|
||||
}
|
||||
return {
|
||||
data: updatedData,
|
||||
// increase totalCount if last page is longer than expected
|
||||
totalCount: totalCount && Math.max(updatedData.length, totalCount),
|
||||
};
|
||||
const start = startIndex + 1;
|
||||
const updatedData = [...data.slice(0, start), ...insertionData];
|
||||
return updatedData;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { data, totalCount };
|
||||
return data;
|
||||
}
|
||||
|
||||
interface DataProviderParams<
|
||||
@@ -175,7 +152,6 @@ interface DataProviderParams<
|
||||
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
|
||||
pagination?: {
|
||||
getPageInfo: GetPageInfo<QueryData>;
|
||||
getTotalCount?: GetTotalCount<QueryData>;
|
||||
append: Append<Data>;
|
||||
first: number;
|
||||
};
|
||||
@@ -245,7 +221,6 @@ function makeDataProviderInternal<
|
||||
let client: ApolloClient<object>;
|
||||
let subscription: Subscription[] | undefined;
|
||||
let pageInfo: PageInfo | null = null;
|
||||
let totalCount: number | undefined;
|
||||
|
||||
// notify single callback about current state, delta is passes optionally only if notify was invoked onNext
|
||||
const notify = (
|
||||
@@ -258,7 +233,6 @@ function makeDataProviderInternal<
|
||||
loading,
|
||||
loaded,
|
||||
pageInfo,
|
||||
totalCount,
|
||||
...updateData,
|
||||
});
|
||||
};
|
||||
@@ -301,59 +275,41 @@ function makeDataProviderInternal<
|
||||
}
|
||||
});
|
||||
|
||||
const load = async (start?: number) => {
|
||||
const load = async () => {
|
||||
if (!pagination) {
|
||||
return Promise.reject();
|
||||
}
|
||||
if (!pageInfo?.hasNextPage) {
|
||||
return null;
|
||||
}
|
||||
const paginationVariables: Pagination = {
|
||||
first: pagination.first,
|
||||
after: pageInfo?.endCursor,
|
||||
};
|
||||
if (start !== undefined && data instanceof Array) {
|
||||
if (!start) {
|
||||
paginationVariables.after = undefined;
|
||||
} else if (data && data[start - 1]) {
|
||||
paginationVariables.after = (data[start - 1] as Cursor).cursor;
|
||||
} else {
|
||||
let skip = 1;
|
||||
while (!data[start - 1 - skip] && skip <= start) {
|
||||
skip += 1;
|
||||
}
|
||||
paginationVariables.skip = skip;
|
||||
if (skip === start) {
|
||||
paginationVariables.after = undefined;
|
||||
} else {
|
||||
paginationVariables.after = (data[start - 1 - skip] as Cursor).cursor;
|
||||
}
|
||||
if (data) {
|
||||
const endCursor = (data as Cursor[])[(data as Cursor[]).length - 1]
|
||||
.cursor;
|
||||
if (endCursor) {
|
||||
paginationVariables.after = endCursor;
|
||||
}
|
||||
} else if (!pageInfo?.hasNextPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const res = await call(paginationVariables);
|
||||
|
||||
const insertionData = getData(res.data, variables);
|
||||
const insertionPageInfo = pagination.getPageInfo(res.data);
|
||||
({ data, totalCount } = pagination.append(
|
||||
data = pagination.append(
|
||||
data,
|
||||
insertionData,
|
||||
insertionPageInfo,
|
||||
paginationVariables,
|
||||
totalCount
|
||||
));
|
||||
paginationVariables
|
||||
);
|
||||
pageInfo = insertionPageInfo;
|
||||
totalCount =
|
||||
(pagination.getTotalCount && pagination.getTotalCount(res.data)) ??
|
||||
totalCount;
|
||||
notifyAll({ insertionData, isInsert: true });
|
||||
return insertionData;
|
||||
};
|
||||
|
||||
const setData = (updatedData: Data | null) => {
|
||||
data = updatedData;
|
||||
if (totalCount !== undefined && data instanceof Array) {
|
||||
totalCount = data.length;
|
||||
}
|
||||
};
|
||||
|
||||
const subscriptionSubscribe = () => {
|
||||
@@ -400,16 +356,6 @@ function makeDataProviderInternal<
|
||||
);
|
||||
}
|
||||
pageInfo = pagination.getPageInfo(res.data);
|
||||
if (pageInfo && !pageInfo.hasNextPage) {
|
||||
totalCount = data.length;
|
||||
} else {
|
||||
totalCount =
|
||||
pagination.getTotalCount && pagination.getTotalCount(res.data);
|
||||
}
|
||||
|
||||
if (data && totalCount && data.length < totalCount) {
|
||||
data.push(...new Array(totalCount - data.length).fill(null));
|
||||
}
|
||||
}
|
||||
// if there was some updates received from subscription during initial query loading apply them on just received data
|
||||
if (update && data && updateQueue && updateQueue.length > 0) {
|
||||
@@ -417,9 +363,6 @@ function makeDataProviderInternal<
|
||||
const delta = updateQueue.shift();
|
||||
if (delta) {
|
||||
setData(update(data, delta, reload, variables));
|
||||
if (totalCount !== undefined && data instanceof Array) {
|
||||
totalCount = data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,7 +533,7 @@ const memoize = <
|
||||
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can reload data provider
|
||||
* @param getData transforms received query data to format that will be stored in data provider
|
||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||
* @param pagination pagination related functions { getPageInfo, getTotalCount, append, first }
|
||||
* @param pagination pagination related functions { getPageInfo, append, first }
|
||||
* @returns Subscribe<Data, Delta> subscribe function
|
||||
* @example
|
||||
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
|
||||
|
||||
@@ -12,23 +12,13 @@ export interface useDataProviderParams<
|
||||
Variables extends OperationVariables | undefined = undefined
|
||||
> {
|
||||
dataProvider: Subscribe<Data, Delta, Variables>;
|
||||
update?: ({
|
||||
delta,
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
delta?: Delta;
|
||||
data: Data | null;
|
||||
totalCount?: number;
|
||||
}) => boolean;
|
||||
update?: ({ delta, data }: { delta?: Delta; data: Data | null }) => boolean;
|
||||
insert?: ({
|
||||
insertionData,
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
insertionData?: Data | null;
|
||||
data: Data | null;
|
||||
totalCount?: number;
|
||||
}) => boolean;
|
||||
variables: Variables;
|
||||
skipUpdates?: boolean;
|
||||
@@ -56,7 +46,6 @@ export const useDataProvider = <
|
||||
}: useDataProviderParams<Data, Delta, Variables>) => {
|
||||
const client = useApolloClient();
|
||||
const [data, setData] = useState<Data | null>(null);
|
||||
const [totalCount, setTotalCount] = useState<number>();
|
||||
const [loading, setLoading] = useState<boolean>(!skip);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
const flushRef = useRef<(() => void) | undefined>(undefined);
|
||||
@@ -101,7 +90,6 @@ export const useDataProvider = <
|
||||
error,
|
||||
loading,
|
||||
insertionData,
|
||||
totalCount,
|
||||
isInsert,
|
||||
isUpdate,
|
||||
loaded,
|
||||
@@ -116,19 +104,18 @@ export const useDataProvider = <
|
||||
(skipUpdatesRef.current ||
|
||||
(!skipUpdatesRef.current &&
|
||||
updateRef.current &&
|
||||
updateRef.current({ delta, data, totalCount })))
|
||||
updateRef.current({ delta, data })))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isInsert &&
|
||||
insertRef.current &&
|
||||
insertRef.current({ insertionData, data, totalCount })
|
||||
insertRef.current({ insertionData, data })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setTotalCount(totalCount);
|
||||
setData(data);
|
||||
if (!loading && !isUpdate && updateRef.current) {
|
||||
updateRef.current({ data });
|
||||
@@ -150,7 +137,6 @@ export const useDataProvider = <
|
||||
useEffect(() => {
|
||||
setData(null);
|
||||
setError(undefined);
|
||||
setTotalCount(undefined);
|
||||
if (updateRef.current) {
|
||||
updateRef.current({ data: null });
|
||||
}
|
||||
@@ -184,7 +170,6 @@ export const useDataProvider = <
|
||||
flush,
|
||||
reload,
|
||||
load,
|
||||
totalCount,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export const MarginWarning = ({ margin, balance, asset }: Props) => {
|
||||
text: t(`Deposit ${asset.symbol}`),
|
||||
action: () => openDepositDialog(asset.id),
|
||||
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||
size: 'sm',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -21,10 +21,14 @@ export const ZeroBalanceError = ({
|
||||
testId="dealticket-error-message-zero-balance"
|
||||
message={
|
||||
<>
|
||||
You need {asset.symbol} in your wallet to trade in this market.
|
||||
{t(
|
||||
'You need %s in your wallet to trade in this market. ',
|
||||
asset.symbol
|
||||
)}
|
||||
{onClickCollateral && (
|
||||
<>
|
||||
See all your <Link onClick={onClickCollateral}>collateral</Link>.
|
||||
{t('See all your')}{' '}
|
||||
<Link onClick={onClickCollateral}>collateral</Link>.
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
@@ -33,7 +37,7 @@ export const ZeroBalanceError = ({
|
||||
text: t(`Make a deposit`),
|
||||
action: () => openDepositDialog(asset.id),
|
||||
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||
size: 'md',
|
||||
size: 'sm',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { ButtonVariant } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
variant: ButtonVariant;
|
||||
}
|
||||
|
||||
export const DealTicketButton = ({ disabled, variant }: Props) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const isDisabled = !pubKey || isReadOnly || disabled;
|
||||
export const DealTicketButton = ({ variant }: Props) => {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<Button
|
||||
variant={variant}
|
||||
fill
|
||||
type="submit"
|
||||
disabled={isDisabled}
|
||||
data-testid="place-order"
|
||||
>
|
||||
<Button variant={variant} fill type="submit" data-testid="place-order">
|
||||
{t('Place order')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -42,6 +42,9 @@ describe('DealTicket', () => {
|
||||
it('should display ticket defaults', () => {
|
||||
const { container } = render(generateJsx());
|
||||
|
||||
// place order button should always be enabled
|
||||
expect(screen.getByTestId('place-order')).toBeEnabled();
|
||||
|
||||
// Assert defaults are used
|
||||
expect(
|
||||
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`)
|
||||
|
||||
@@ -146,7 +146,7 @@ export const DealTicket = ({
|
||||
});
|
||||
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
||||
const orders = activeOrders
|
||||
? activeOrders.map<OrderInfo>(({ node: order }) => ({
|
||||
? activeOrders.map<OrderInfo>((order) => ({
|
||||
isMarketOrder: order.type === OrderType.TYPE_MARKET,
|
||||
price: order.price,
|
||||
remaining: order.remaining,
|
||||
@@ -217,6 +217,8 @@ export const DealTicket = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// No error found above clear the error in case it was active on a previous render
|
||||
clearErrors('summary');
|
||||
}, [
|
||||
marketState,
|
||||
@@ -480,7 +482,6 @@ export const DealTicket = ({
|
||||
onClickCollateral={onClickCollateral}
|
||||
/>
|
||||
<DealTicketButton
|
||||
disabled={Object.keys(errors).length >= 1 || isReadOnly}
|
||||
variant={
|
||||
order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'
|
||||
}
|
||||
@@ -562,7 +563,7 @@ const SummaryMessage = memo(
|
||||
text: t('Connect wallet'),
|
||||
action: openVegaWalletDialog,
|
||||
dataTestId: 'order-connect-wallet',
|
||||
size: 'md',
|
||||
size: 'sm',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
|
||||
export const validateMarketState = (state: Schema.MarketState) => {
|
||||
export const validateMarketState = (state: MarketState) => {
|
||||
if (
|
||||
[
|
||||
Schema.MarketState.STATE_SETTLED,
|
||||
Schema.MarketState.STATE_REJECTED,
|
||||
Schema.MarketState.STATE_TRADING_TERMINATED,
|
||||
Schema.MarketState.STATE_CANCELLED,
|
||||
Schema.MarketState.STATE_CLOSED,
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_REJECTED,
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
MarketState.STATE_CANCELLED,
|
||||
MarketState.STATE_CLOSED,
|
||||
].includes(state)
|
||||
) {
|
||||
return t(
|
||||
@@ -16,7 +16,7 @@ export const validateMarketState = (state: Schema.MarketState) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (state === Schema.MarketState.STATE_PROPOSED) {
|
||||
if (state === MarketState.STATE_PROPOSED) {
|
||||
return t(
|
||||
`This market is ${marketTranslations(
|
||||
state
|
||||
@@ -27,11 +27,11 @@ export const validateMarketState = (state: Schema.MarketState) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const marketTranslations = (marketState: Schema.MarketState) => {
|
||||
const marketTranslations = (marketState: MarketState) => {
|
||||
switch (marketState) {
|
||||
case Schema.MarketState.STATE_TRADING_TERMINATED:
|
||||
case MarketState.STATE_TRADING_TERMINATED:
|
||||
return t('terminated');
|
||||
default:
|
||||
return t(Schema.MarketStateMapping[marketState]).toLowerCase();
|
||||
return t(MarketStateMapping[marketState]).toLowerCase();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
|
||||
export const validateMarketTradingMode = (
|
||||
marketTradingMode: Schema.MarketTradingMode
|
||||
marketTradingMode: MarketTradingMode
|
||||
) => {
|
||||
if (marketTradingMode === Schema.MarketTradingMode.TRADING_MODE_NO_TRADING) {
|
||||
if (marketTradingMode === MarketTradingMode.TRADING_MODE_NO_TRADING) {
|
||||
return t('Trading terminated');
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,24 @@ import { Networks } from '../../';
|
||||
jest.mock('../../hooks/use-environment');
|
||||
|
||||
describe('Network switcher', () => {
|
||||
const { location } = window;
|
||||
let hrefSetSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
hrefSetSpy = jest.fn();
|
||||
// @ts-ignore can't set location as optional
|
||||
delete window.location;
|
||||
window.location = {} as Location;
|
||||
Object.defineProperty(window.location, 'href', {
|
||||
// @ts-ignore set cannot take SpyInstance
|
||||
set: hrefSetSpy,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
it.each`
|
||||
network | label
|
||||
${Networks.CUSTOM} | ${envTriggerMapping[Networks.CUSTOM]}
|
||||
@@ -49,21 +67,29 @@ describe('Network switcher', () => {
|
||||
render(<NetworkSwitcher />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
let links = screen.getAllByRole('link');
|
||||
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[1]).toHaveTextContent(envNameMapping[Networks.TESTNET]);
|
||||
expect(links[0]).not.toHaveTextContent(t('current'));
|
||||
expect(links[1]).not.toHaveTextContent(t('current'));
|
||||
expect(links[0]).not.toHaveTextContent(t('not available'));
|
||||
expect(links[1]).not.toHaveTextContent(t('not available'));
|
||||
expect(links[2]).toHaveTextContent(t('Propose a network parameter change'));
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[1]).toHaveTextContent(envNameMapping[Networks.TESTNET]);
|
||||
expect(menuitems[0]).not.toHaveTextContent(t('current'));
|
||||
expect(menuitems[1]).not.toHaveTextContent(t('current'));
|
||||
expect(menuitems[0]).not.toHaveTextContent(t('not available'));
|
||||
expect(menuitems[1]).not.toHaveTextContent(t('not available'));
|
||||
expect(menuitems[2]).toHaveTextContent(t('Advanced'));
|
||||
expect(menuitems[0]).toHaveTextContent('Advanced');
|
||||
|
||||
const links = screen.getAllByRole('link');
|
||||
await userEvent.click(links[0]);
|
||||
expect(hrefSetSpy).toHaveBeenCalledWith(mainnetUrl);
|
||||
|
||||
expect(links[0]).toHaveAttribute('href', mainnetUrl);
|
||||
expect(links[1]).toHaveAttribute('href', testnetUrl);
|
||||
// re open dropdown as clicking an item will close it
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
links = screen.getAllByRole('link');
|
||||
|
||||
await userEvent.click(links[1]);
|
||||
expect(hrefSetSpy).toHaveBeenCalledWith(testnetUrl);
|
||||
});
|
||||
|
||||
it('displays the correct selected network on the default dropdown view', async () => {
|
||||
@@ -82,10 +108,10 @@ describe('Network switcher', () => {
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[0]).toHaveTextContent(t('current'));
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[0]).toHaveTextContent(t('current'));
|
||||
});
|
||||
|
||||
it('displays the correct selected network on the default dropdown view when it does not have an associated url', async () => {
|
||||
@@ -103,10 +129,10 @@ describe('Network switcher', () => {
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[0]).toHaveTextContent(t('current'));
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[0]).toHaveTextContent(t('current'));
|
||||
});
|
||||
|
||||
it('displays the correct state for a network without url on the default dropdown view', async () => {
|
||||
@@ -123,44 +149,59 @@ describe('Network switcher', () => {
|
||||
render(<NetworkSwitcher />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[0]).toHaveTextContent(t('not available'));
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[0]).toHaveTextContent(t('not available'));
|
||||
});
|
||||
|
||||
it('displays the advanced view in the correct state', async () => {
|
||||
const VEGA_NETWORKS: Record<Networks, string | undefined> = {
|
||||
[Networks.CUSTOM]: undefined,
|
||||
[Networks.MAINNET]: 'https://main.net',
|
||||
[Networks.TESTNET]: 'https://test.net',
|
||||
[Networks.VALIDATOR_TESTNET]: 'https://validator-test.net',
|
||||
[Networks.DEVNET]: 'https://dev.net',
|
||||
[Networks.STAGNET1]: 'https://stag1.net',
|
||||
};
|
||||
// @ts-ignore Typescript doesn't know about this module being mocked
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
VEGA_ENV: Networks.DEVNET,
|
||||
VEGA_NETWORKS,
|
||||
}));
|
||||
it.each([Networks.MAINNET, Networks.TESTNET, Networks.DEVNET])(
|
||||
'displays the advanced view in the correct state',
|
||||
async (network) => {
|
||||
const VEGA_NETWORKS: Record<Networks, string | undefined> = {
|
||||
[Networks.CUSTOM]: undefined,
|
||||
[Networks.MAINNET]: 'https://main.net',
|
||||
[Networks.TESTNET]: 'https://test.net',
|
||||
[Networks.VALIDATOR_TESTNET]: 'https://validator-test.net',
|
||||
[Networks.DEVNET]: 'https://dev.net',
|
||||
[Networks.STAGNET1]: 'https://stag1.net',
|
||||
};
|
||||
// @ts-ignore Typescript doesn't know about this module being mocked
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
VEGA_ENV: Networks.DEVNET,
|
||||
VEGA_NETWORKS,
|
||||
}));
|
||||
|
||||
render(<NetworkSwitcher />);
|
||||
render(<NetworkSwitcher />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
await userEvent.click(
|
||||
screen.getByRole('menuitem', { name: t('Advanced') })
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('network-switcher'));
|
||||
|
||||
[Networks.MAINNET, Networks.TESTNET, Networks.DEVNET].forEach((network) => {
|
||||
expect(
|
||||
screen.getByRole('link', { name: envNameMapping[network] })
|
||||
).toHaveAttribute('href', VEGA_NETWORKS[network]);
|
||||
expect(
|
||||
screen.getByText(envDescriptionMapping[network])
|
||||
await screen.findByRole('menuitem', { name: t('Advanced') })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('menuitem', { name: t('Advanced') })
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText(envDescriptionMapping[network])
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('link', {
|
||||
name: new RegExp(`^${envNameMapping[network]}`),
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('link', {
|
||||
name: new RegExp(`^${envNameMapping[network]}`),
|
||||
})
|
||||
);
|
||||
|
||||
expect(hrefSetSpy).toHaveBeenCalledWith(VEGA_NETWORKS[network]);
|
||||
}
|
||||
);
|
||||
|
||||
it('labels the selected network in the advanced view', async () => {
|
||||
const selectedNetwork = Networks.DEVNET;
|
||||
@@ -188,7 +229,7 @@ describe('Network switcher', () => {
|
||||
const label = screen.getByText(`(${t('current')})`);
|
||||
|
||||
expect(label).toBeInTheDocument();
|
||||
expect(label.parentNode?.firstElementChild).toHaveTextContent(
|
||||
expect(label.parentNode?.parentNode?.firstElementChild).toHaveTextContent(
|
||||
envNameMapping[selectedNetwork]
|
||||
);
|
||||
});
|
||||
@@ -217,7 +258,7 @@ describe('Network switcher', () => {
|
||||
const label = screen.getByText('(not available)');
|
||||
|
||||
expect(label).toBeInTheDocument();
|
||||
expect(label.parentNode?.firstElementChild).toHaveTextContent(
|
||||
expect(label.parentNode?.parentNode?.firstElementChild).toHaveTextContent(
|
||||
envNameMapping[Networks.MAINNET]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Link,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -99,7 +98,6 @@ export const NetworkSwitcher = ({
|
||||
},
|
||||
[setOpen, setAdvancedView]
|
||||
);
|
||||
const menuRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const current = currentNetwork || VEGA_ENV;
|
||||
|
||||
@@ -110,7 +108,6 @@ export const NetworkSwitcher = ({
|
||||
trigger={
|
||||
<DropdownMenuTrigger
|
||||
data-testid="network-switcher"
|
||||
ref={menuRef}
|
||||
className={classNames(
|
||||
'flex justify-between items-center text-sm text-vega-dark-600 dark:text-vega-light-600 py-1 px-2 rounded border border-vega-dark-200 whitespace-nowrap dark:hover:bg-vega-dark-500 hover:bg-vega-light-500',
|
||||
className
|
||||
@@ -123,10 +120,7 @@ export const NetworkSwitcher = ({
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
style={{ minWidth: `${menuRef.current?.offsetWidth || 290}px` }}
|
||||
>
|
||||
<DropdownMenuContent align="start">
|
||||
{!isAdvancedView && (
|
||||
<>
|
||||
{standardNetworkKeys.map((key) => (
|
||||
@@ -134,14 +128,16 @@ export const NetworkSwitcher = ({
|
||||
key={key}
|
||||
data-testid="network-item"
|
||||
disabled={!VEGA_NETWORKS[key]}
|
||||
role="link"
|
||||
onClick={() =>
|
||||
(window.location.href = VEGA_NETWORKS[key] || '')
|
||||
}
|
||||
>
|
||||
<a href={VEGA_NETWORKS[key]}>
|
||||
{envNameMapping[key]}
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
/>
|
||||
</a>
|
||||
{envNameMapping[key]}
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuItem
|
||||
@@ -159,10 +155,17 @@ export const NetworkSwitcher = ({
|
||||
{isAdvancedView && (
|
||||
<>
|
||||
{advancedNetworkKeys.map((key) => (
|
||||
<DropdownMenuItem key={key} data-testid="network-item-advanced">
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
data-testid="network-item-advanced"
|
||||
role="link"
|
||||
onClick={() =>
|
||||
(window.location.href = VEGA_NETWORKS[key] || '')
|
||||
}
|
||||
>
|
||||
<div className="w-full flex justify-between gap-2">
|
||||
<div>
|
||||
<Link href={VEGA_NETWORKS[key]}>{envNameMapping[key]}</Link>
|
||||
{envNameMapping[key]}
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './lib/fills-container';
|
||||
export * from './lib/use-fills-list';
|
||||
export * from './lib/fills-data-provider';
|
||||
export * from './lib/__generated__/Fills';
|
||||
|
||||
@@ -48,28 +48,32 @@ query Fills($filter: TradesFilter, $pagination: Pagination) {
|
||||
}
|
||||
}
|
||||
|
||||
fragment FillUpdateFields on TradeUpdate {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
}
|
||||
|
||||
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
||||
tradesStream(filter: $filter) {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
...FillUpdateFields
|
||||
}
|
||||
}
|
||||
|
||||
+29
-22
@@ -15,6 +15,8 @@ export type FillsQueryVariables = Types.Exact<{
|
||||
|
||||
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
||||
|
||||
export type FillUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } };
|
||||
|
||||
export type FillsEventSubscriptionVariables = Types.Exact<{
|
||||
filter: Types.TradesSubscriptionFilter;
|
||||
}>;
|
||||
@@ -60,6 +62,31 @@ export const FillEdgeFragmentDoc = gql`
|
||||
cursor
|
||||
}
|
||||
${FillFieldsFragmentDoc}`;
|
||||
export const FillUpdateFieldsFragmentDoc = gql`
|
||||
fragment FillUpdateFields on TradeUpdate {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const FillsDocument = gql`
|
||||
query Fills($filter: TradesFilter, $pagination: Pagination) {
|
||||
trades(filter: $filter, pagination: $pagination) {
|
||||
@@ -107,30 +134,10 @@ export type FillsQueryResult = Apollo.QueryResult<FillsQuery, FillsQueryVariable
|
||||
export const FillsEventDocument = gql`
|
||||
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
||||
tradesStream(filter: $filter) {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
...FillUpdateFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
${FillUpdateFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useFillsEventSubscription__
|
||||
|
||||
@@ -1,74 +1,34 @@
|
||||
import produce from 'immer';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import {} from '@vegaprotocol/utils';
|
||||
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||
import type { PageInfo, Cursor } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
defaultAppend as append,
|
||||
paginatedCombineDelta as combineDelta,
|
||||
paginatedCombineInsertionData as combineInsertionData,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import { FillsDocument, FillsEventDocument } from './__generated__/Fills';
|
||||
import type {
|
||||
FillsQuery,
|
||||
FillsQueryVariables,
|
||||
FillFieldsFragment,
|
||||
FillEdgeFragment,
|
||||
FillsEventSubscription,
|
||||
FillUpdateFieldsFragment,
|
||||
FillsEventSubscriptionVariables,
|
||||
} from './__generated__/Fills';
|
||||
|
||||
const update = (
|
||||
data: FillEdgeFragment[] | null,
|
||||
delta: FillsEventSubscription['tradesStream']
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
orderBy(delta, 'createdAt').forEach((node) => {
|
||||
if (draft === null) {
|
||||
return;
|
||||
}
|
||||
const index = draft.findIndex((edge) => edge?.node.id === node.id);
|
||||
if (index !== -1) {
|
||||
if (draft[index]?.node) {
|
||||
Object.assign(draft[index]?.node as FillFieldsFragment, node);
|
||||
}
|
||||
} else {
|
||||
const firstNode = draft[0]?.node;
|
||||
if (
|
||||
(firstNode && node.createdAt >= firstNode.createdAt) ||
|
||||
!firstNode
|
||||
) {
|
||||
const { buyerId, sellerId, marketId, ...trade } = node;
|
||||
draft.unshift({
|
||||
node: {
|
||||
...trade,
|
||||
__typename: 'Trade',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
buyer: { id: buyerId, __typename: 'Party' },
|
||||
seller: { id: buyerId, __typename: 'Party' },
|
||||
},
|
||||
cursor: '',
|
||||
__typename: 'TradeEdge',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export type Trade = Omit<FillFieldsFragment, 'market'> & {
|
||||
market?: Market;
|
||||
isLastPlaceholder?: boolean;
|
||||
};
|
||||
export type TradeEdge = Edge<Trade>;
|
||||
|
||||
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
|
||||
responseData?.trades?.edges || [];
|
||||
const getData = (
|
||||
responseData: FillsQuery | null
|
||||
): (FillFieldsFragment & Cursor)[] =>
|
||||
responseData?.trades?.edges.map<FillFieldsFragment & Cursor>((edge) => ({
|
||||
...edge.node,
|
||||
cursor: edge.cursor,
|
||||
})) || [];
|
||||
|
||||
const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
|
||||
responseData?.trades?.pageInfo || null;
|
||||
@@ -76,16 +36,65 @@ const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
|
||||
const getDelta = (subscriptionData: FillsEventSubscription) =>
|
||||
subscriptionData.tradesStream || [];
|
||||
|
||||
const mapFillUpdateToFill = (
|
||||
fillUpdate: FillUpdateFieldsFragment
|
||||
): FillFieldsFragment => {
|
||||
const { buyerId, sellerId, marketId, ...fill } = fillUpdate;
|
||||
return {
|
||||
...fill,
|
||||
__typename: 'Trade',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
buyer: { id: buyerId, __typename: 'Party' },
|
||||
seller: { id: buyerId, __typename: 'Party' },
|
||||
};
|
||||
};
|
||||
|
||||
const mapFillUpdateToFillWithMarket =
|
||||
(markets: Record<string, Market>) =>
|
||||
(fillUpdate: FillUpdateFieldsFragment): Trade => {
|
||||
const { market, ...fill } = mapFillUpdateToFill(fillUpdate);
|
||||
return {
|
||||
...fill,
|
||||
market: markets[market.id],
|
||||
};
|
||||
};
|
||||
|
||||
const update = <T extends Omit<FillFieldsFragment, 'market'> & Cursor>(
|
||||
data: T[] | null,
|
||||
delta: ReturnType<typeof getDelta>,
|
||||
variables: FillsQueryVariables,
|
||||
mapDeltaToData: (delta: FillUpdateFieldsFragment) => T
|
||||
): T[] => {
|
||||
const updatedData = data ? [...data] : ([] as T[]);
|
||||
orderBy(delta, 'createdAt', 'desc').forEach((fillUpdate) => {
|
||||
const index = data?.findIndex((fill) => fill.id === fillUpdate.id) ?? -1;
|
||||
if (index !== -1) {
|
||||
updatedData[index] = {
|
||||
...updatedData[index],
|
||||
...mapDeltaToData(fillUpdate),
|
||||
};
|
||||
} else if (!data?.length || fillUpdate.createdAt >= data[0].createdAt) {
|
||||
updatedData.unshift(mapDeltaToData(fillUpdate));
|
||||
}
|
||||
});
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
export const fillsProvider = makeDataProvider<
|
||||
Parameters<typeof getData>['0'],
|
||||
ReturnType<typeof getData>,
|
||||
Parameters<typeof getDelta>['0'],
|
||||
ReturnType<typeof getDelta>,
|
||||
FillsQueryVariables
|
||||
FillsQueryVariables,
|
||||
FillsEventSubscriptionVariables
|
||||
>({
|
||||
query: FillsDocument,
|
||||
subscriptionQuery: FillsEventDocument,
|
||||
update,
|
||||
update: (data, delta, reload, variables) =>
|
||||
update(data, delta, variables, mapFillUpdateToFill),
|
||||
getData,
|
||||
getDelta,
|
||||
pagination: {
|
||||
@@ -93,30 +102,41 @@ export const fillsProvider = makeDataProvider<
|
||||
append,
|
||||
first: 100,
|
||||
},
|
||||
getSubscriptionVariables: ({ filter }) => {
|
||||
const variables: FillsEventSubscriptionVariables = { filter: {} };
|
||||
if (filter) {
|
||||
variables.filter = {
|
||||
partyIds: filter.partyIds,
|
||||
marketIds: filter.marketIds,
|
||||
};
|
||||
}
|
||||
return variables;
|
||||
},
|
||||
});
|
||||
|
||||
export const fillsWithMarketProvider = makeDerivedDataProvider<
|
||||
(TradeEdge | null)[],
|
||||
Trade[],
|
||||
never,
|
||||
FillsQueryVariables
|
||||
>(
|
||||
[
|
||||
fillsProvider,
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData): (TradeEdge | null)[] =>
|
||||
(partsData[0] as ReturnType<typeof getData>)?.map(
|
||||
(edge) =>
|
||||
edge && {
|
||||
cursor: edge.cursor,
|
||||
node: {
|
||||
...edge.node,
|
||||
market: (partsData[1] as Market[]).find(
|
||||
(market) => market.id === edge.node.market.id
|
||||
),
|
||||
},
|
||||
}
|
||||
) || null,
|
||||
combineDelta<Trade, ReturnType<typeof getDelta>['0']>,
|
||||
combineInsertionData<Trade>
|
||||
(partsData, variables, prevData, parts): Trade[] | null => {
|
||||
if (prevData && parts[0].isUpdate) {
|
||||
return update(
|
||||
prevData,
|
||||
parts[0].delta as ReturnType<typeof getDelta>,
|
||||
variables,
|
||||
mapFillUpdateToFillWithMarket(partsData[1] as Record<string, Market>)
|
||||
);
|
||||
}
|
||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
||||
(trade) => ({
|
||||
...trade,
|
||||
market: (partsData[1] as Record<string, Market>)[trade.market.id],
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import compact from 'lodash/compact';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useRef } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FillsTable } from './fills-table';
|
||||
import { useFillsList } from './use-fills-list';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { fillsWithMarketProvider } from './fills-data-provider';
|
||||
|
||||
interface FillsManagerProps {
|
||||
partyId: string;
|
||||
@@ -20,31 +21,36 @@ export const FillsManager = ({
|
||||
storeKey,
|
||||
}: FillsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const scrolledToTop = useRef(true);
|
||||
const { data, error } = useFillsList({
|
||||
partyId,
|
||||
marketId,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
|
||||
partyIds: [partyId],
|
||||
};
|
||||
if (marketId) {
|
||||
filter.marketIds = [marketId];
|
||||
}
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: fillsWithMarketProvider,
|
||||
update: ({ data }) => {
|
||||
if (data?.length && gridRef.current?.api) {
|
||||
gridRef.current?.api.setRowData(data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
variables: { filter },
|
||||
});
|
||||
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
});
|
||||
|
||||
const fills = compact(data).map((e) => e.node);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<FillsTable
|
||||
ref={gridRef}
|
||||
rowData={fills}
|
||||
partyId={partyId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey={storeKey}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
||||
/>
|
||||
</div>
|
||||
<FillsTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
partyId={partyId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey={storeKey}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useFillsList } from './use-fills-list';
|
||||
import type { TradeEdge } from './fills-data-provider';
|
||||
|
||||
let mockData = null;
|
||||
let mockDataProviderData = {
|
||||
data: mockData as (TradeEdge | null)[] | null,
|
||||
error: undefined,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
let updateMock: jest.Mock;
|
||||
const mockDataProvider = jest.fn((args) => {
|
||||
updateMock = args.update;
|
||||
return mockDataProviderData;
|
||||
});
|
||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
...jest.requireActual('@vegaprotocol/data-provider'),
|
||||
useDataProvider: jest.fn((args) => mockDataProvider(args)),
|
||||
}));
|
||||
|
||||
describe('useFillsList Hook', () => {
|
||||
const mockRefreshAgGridApi = jest.fn();
|
||||
const partyId = 'partyId';
|
||||
const gridRef = {
|
||||
current: {
|
||||
api: {
|
||||
refreshInfiniteCache: mockRefreshAgGridApi,
|
||||
getModel: () => ({ getType: () => 'infinite' }),
|
||||
},
|
||||
} as unknown as AgGridReact,
|
||||
};
|
||||
const scrolledToTop = {
|
||||
current: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return proper dataProvider results', () => {
|
||||
const { result } = renderHook(
|
||||
() => useFillsList({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current).toMatchObject({
|
||||
data: null,
|
||||
error: undefined,
|
||||
loading: true,
|
||||
addNewRows: expect.any(Function),
|
||||
getRows: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('return proper mocked results', () => {
|
||||
mockData = [
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_1',
|
||||
},
|
||||
} as unknown as TradeEdge,
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_2',
|
||||
},
|
||||
} as unknown as TradeEdge,
|
||||
];
|
||||
mockDataProviderData = {
|
||||
...mockDataProviderData,
|
||||
data: mockData,
|
||||
loading: false,
|
||||
};
|
||||
const { result } = renderHook(
|
||||
() => useFillsList({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current).toMatchObject({
|
||||
data: mockData,
|
||||
error: undefined,
|
||||
loading: false,
|
||||
addNewRows: expect.any(Function),
|
||||
getRows: expect.any(Function),
|
||||
});
|
||||
updateMock({ data: mockData });
|
||||
expect(mockRefreshAgGridApi).not.toHaveBeenCalled();
|
||||
updateMock({ data: mockData });
|
||||
expect(mockRefreshAgGridApi).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,123 +0,0 @@
|
||||
import type { RefObject } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { makeInfiniteScrollGetRows } from '@vegaprotocol/data-provider';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import { updateGridData } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { Trade, TradeEdge } from './fills-data-provider';
|
||||
import { fillsWithMarketProvider } from './fills-data-provider';
|
||||
|
||||
interface Props {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
scrolledToTop: RefObject<boolean>;
|
||||
}
|
||||
|
||||
export const useFillsList = ({
|
||||
partyId,
|
||||
marketId,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
}: Props) => {
|
||||
const dataRef = useRef<(TradeEdge | null)[] | null>(null);
|
||||
const totalCountRef = useRef<number | undefined>(undefined);
|
||||
const newRows = useRef(0);
|
||||
const placeholderAdded = useRef(-1);
|
||||
|
||||
const makeBottomPlaceholders = useCallback((trade?: Trade) => {
|
||||
if (!trade) {
|
||||
if (placeholderAdded.current >= 0) {
|
||||
dataRef.current?.splice(placeholderAdded.current, 1);
|
||||
}
|
||||
placeholderAdded.current = -1;
|
||||
} else if (placeholderAdded.current === -1) {
|
||||
dataRef.current?.push({
|
||||
node: { ...trade, id: `${trade?.id}-1`, isLastPlaceholder: true },
|
||||
});
|
||||
placeholderAdded.current = (dataRef.current?.length || 0) - 1;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const addNewRows = useCallback(() => {
|
||||
if (newRows.current === 0) {
|
||||
return;
|
||||
}
|
||||
if (totalCountRef.current !== undefined) {
|
||||
totalCountRef.current += newRows.current;
|
||||
}
|
||||
newRows.current = 0;
|
||||
gridRef.current?.api?.refreshInfiniteCache();
|
||||
}, [gridRef]);
|
||||
|
||||
const update = useCallback(
|
||||
({
|
||||
data,
|
||||
delta,
|
||||
}: {
|
||||
data: (TradeEdge | null)[] | null;
|
||||
delta?: Trade[];
|
||||
}) => {
|
||||
if (dataRef.current?.length) {
|
||||
if (!scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
newRows.current += (delta || []).filter(
|
||||
(trade) => trade.createdAt > createdAt
|
||||
).length;
|
||||
}
|
||||
}
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
}
|
||||
dataRef.current = data;
|
||||
return false;
|
||||
},
|
||||
[gridRef, scrolledToTop]
|
||||
);
|
||||
|
||||
const insert = useCallback(
|
||||
({
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
data: (TradeEdge | null)[] | null;
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
totalCountRef.current = totalCount;
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const filter: Types.TradesFilter & Types.TradesSubscriptionFilter = {
|
||||
partyIds: [partyId],
|
||||
};
|
||||
if (marketId) {
|
||||
filter.marketIds = [marketId];
|
||||
}
|
||||
|
||||
const { data, error, loading, load, totalCount, reload } = useDataProvider({
|
||||
dataProvider: fillsWithMarketProvider,
|
||||
update,
|
||||
insert,
|
||||
variables: { filter },
|
||||
});
|
||||
totalCountRef.current = totalCount;
|
||||
|
||||
const getRows = makeInfiniteScrollGetRows<TradeEdge>(
|
||||
dataRef,
|
||||
totalCountRef,
|
||||
load,
|
||||
newRows
|
||||
);
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
addNewRows,
|
||||
getRows,
|
||||
reload,
|
||||
makeBottomPlaceholders,
|
||||
};
|
||||
};
|
||||
@@ -1,22 +1,12 @@
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { assetsProvider } from '@vegaprotocol/assets';
|
||||
import { assetsMapProvider } from '@vegaprotocol/assets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import { makeInfiniteScrollGetRows } from '@vegaprotocol/data-provider';
|
||||
import { updateGridData } from '@vegaprotocol/datagrid';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
useDataProvider,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import produce from 'immer';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import type { RefObject } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import type { Filter } from './ledger-manager';
|
||||
|
||||
import type {
|
||||
LedgerEntriesQuery,
|
||||
LedgerEntriesQueryVariables,
|
||||
@@ -30,171 +20,58 @@ export type LedgerEntry = LedgerEntryFragment & {
|
||||
marketReceiver: Market | null | undefined;
|
||||
};
|
||||
|
||||
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
|
||||
export type AggregatedLedgerEntriesNode = Omit<
|
||||
AggregatedLedgerEntriesEdge,
|
||||
'node'
|
||||
> & {
|
||||
node: LedgerEntry;
|
||||
};
|
||||
type Edge = LedgerEntriesQuery['ledgerEntries']['edges'][number];
|
||||
|
||||
const isLedgerEntryEdge = (entry: Edge): entry is NonNullable<Edge> =>
|
||||
entry !== null;
|
||||
|
||||
const getData = (responseData: LedgerEntriesQuery | null) => {
|
||||
return responseData?.ledgerEntries?.edges || [];
|
||||
return (
|
||||
responseData?.ledgerEntries?.edges
|
||||
.filter(isLedgerEntryEdge)
|
||||
.map((edge) => edge.node) || []
|
||||
);
|
||||
};
|
||||
|
||||
export const update = (
|
||||
data: ReturnType<typeof getData> | null,
|
||||
delta: ReturnType<typeof getData>,
|
||||
reload: () => void,
|
||||
variables: LedgerEntriesQueryVariables
|
||||
) => {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
return produce(data, (draft) => {
|
||||
// A single update can contain the same order with multiple updates, so we need to find
|
||||
// the latest version of the order and only update using that
|
||||
const incoming = uniqBy(
|
||||
orderBy(delta, (entry) => entry?.node.vegaTime, 'desc'),
|
||||
'id'
|
||||
);
|
||||
|
||||
// Add or update incoming orders
|
||||
incoming.reverse().forEach((node) => {
|
||||
const index = draft.findIndex(
|
||||
(edge) => edge?.node.vegaTime === node?.node.vegaTime
|
||||
);
|
||||
const newer =
|
||||
draft.length === 0 || node?.node.vegaTime >= draft[0]?.node.vegaTime;
|
||||
let doesFilterPass = true;
|
||||
if (
|
||||
doesFilterPass &&
|
||||
variables?.dateRange?.start &&
|
||||
new Date(node?.node.vegaTime) <= new Date(variables?.dateRange?.start)
|
||||
) {
|
||||
doesFilterPass = false;
|
||||
}
|
||||
if (
|
||||
doesFilterPass &&
|
||||
variables?.dateRange?.end &&
|
||||
new Date(node?.node.vegaTime) >= new Date(variables?.dateRange?.end)
|
||||
) {
|
||||
doesFilterPass = false;
|
||||
}
|
||||
if (index !== -1) {
|
||||
if (doesFilterPass) {
|
||||
// Object.assign(draft[index]?.node, node?.node);
|
||||
if (newer) {
|
||||
draft.unshift(...draft.splice(index, 1));
|
||||
}
|
||||
} else {
|
||||
draft.splice(index, 1);
|
||||
}
|
||||
} else if (newer && doesFilterPass) {
|
||||
draft.unshift(node);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const ledgerEntriesOnlyProvider = makeDataProvider({
|
||||
const ledgerEntriesOnlyProvider = makeDataProvider<
|
||||
LedgerEntriesQuery,
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
never,
|
||||
LedgerEntriesQueryVariables
|
||||
>({
|
||||
query: LedgerEntriesDocument,
|
||||
getData,
|
||||
getDelta: getData,
|
||||
update,
|
||||
additionalContext: {
|
||||
isEnlargedTimeout: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const ledgerEntriesProvider = makeDerivedDataProvider<
|
||||
AggregatedLedgerEntriesNode[],
|
||||
AggregatedLedgerEntriesNode[],
|
||||
LedgerEntry[],
|
||||
never,
|
||||
LedgerEntriesQueryVariables
|
||||
>(
|
||||
[
|
||||
ledgerEntriesOnlyProvider,
|
||||
(callback, client) => assetsProvider(callback, client, undefined),
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
(callback, client) => assetsMapProvider(callback, client, undefined),
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
],
|
||||
([entries, assets, markets]) => {
|
||||
return entries.map((edge: AggregatedLedgerEntriesEdge) => {
|
||||
const entry = edge.node;
|
||||
const asset = assets.find((asset: Asset) => asset.id === entry.assetId);
|
||||
const marketSender = markets.find(
|
||||
(market: Market) => market.id === entry.fromAccountMarketId
|
||||
);
|
||||
const marketReceiver = markets.find(
|
||||
(market: Market) => market.id === entry.toAccountMarketId
|
||||
);
|
||||
const cursor = edge?.cursor;
|
||||
return {
|
||||
node: { ...entry, asset, marketSender, marketReceiver },
|
||||
cursor,
|
||||
};
|
||||
(partsData) => {
|
||||
const entries = partsData[0] as ReturnType<typeof getData>;
|
||||
const assets = partsData[1] as Record<string, Asset>;
|
||||
const markets = partsData[1] as Record<string, Market>;
|
||||
return entries.map((entry) => {
|
||||
const asset = entry.assetId
|
||||
? (assets as Record<string, Asset>)[entry.assetId]
|
||||
: null;
|
||||
const marketSender = entry.fromAccountMarketId
|
||||
? markets[entry.fromAccountMarketId]
|
||||
: null;
|
||||
const marketReceiver = entry.toAccountMarketId
|
||||
? markets[entry.toAccountMarketId]
|
||||
: null;
|
||||
return { ...entry, asset, marketSender, marketReceiver };
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
interface Props {
|
||||
partyId: string;
|
||||
filter?: Filter;
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
}
|
||||
|
||||
export const useLedgerEntriesDataProvider = ({
|
||||
partyId,
|
||||
filter,
|
||||
gridRef,
|
||||
}: Props) => {
|
||||
const dataRef = useRef<AggregatedLedgerEntriesEdge[] | null>(null);
|
||||
const totalCountRef = useRef<number>();
|
||||
|
||||
const variables = useMemo<LedgerEntriesQueryVariables>(
|
||||
() => ({
|
||||
partyId,
|
||||
dateRange: filter?.vegaTime?.value,
|
||||
pagination: {
|
||||
first: 5000,
|
||||
},
|
||||
}),
|
||||
[partyId, filter?.vegaTime?.value]
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: AggregatedLedgerEntriesEdge[] | null }) => {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const insert = useCallback(
|
||||
({
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
data: AggregatedLedgerEntriesEdge[] | null;
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
totalCountRef.current = totalCount;
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const { data, error, loading, load, totalCount, reload } = useDataProvider({
|
||||
dataProvider: ledgerEntriesProvider,
|
||||
update,
|
||||
insert,
|
||||
variables,
|
||||
skip: !variables.partyId,
|
||||
});
|
||||
totalCountRef.current = totalCount;
|
||||
|
||||
const getRows = makeInfiniteScrollGetRows<AggregatedLedgerEntriesEdge>(
|
||||
dataRef,
|
||||
totalCountRef,
|
||||
load
|
||||
);
|
||||
return { loading, error, data, getRows, reload };
|
||||
};
|
||||
|
||||
@@ -2,10 +2,12 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { FilterChangedEvent } from 'ag-grid-community';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState, useMemo } from 'react';
|
||||
import { subDays, formatRFC3339 } from 'date-fns';
|
||||
import { useLedgerEntriesDataProvider } from './ledger-entries-data-provider';
|
||||
import { ledgerEntriesProvider } from './ledger-entries-data-provider';
|
||||
import type { LedgerEntriesQueryVariables } from './__generated__/LedgerEntries';
|
||||
import { LedgerTable } from './ledger-table';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import { LedgerExportLink } from './ledger-export-link';
|
||||
|
||||
@@ -26,10 +28,21 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
||||
|
||||
const { data, error } = useLedgerEntriesDataProvider({
|
||||
partyId,
|
||||
filter,
|
||||
gridRef,
|
||||
const variables = useMemo<LedgerEntriesQueryVariables>(
|
||||
() => ({
|
||||
partyId,
|
||||
dateRange: filter?.vegaTime?.value,
|
||||
pagination: {
|
||||
first: 5000,
|
||||
},
|
||||
}),
|
||||
[partyId, filter?.vegaTime?.value]
|
||||
);
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: ledgerEntriesProvider,
|
||||
variables,
|
||||
skip: !variables.partyId,
|
||||
});
|
||||
|
||||
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
|
||||
@@ -37,20 +50,15 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
setFilter(updatedFilter);
|
||||
}, []);
|
||||
|
||||
// allow passing undefined to grid so that loading state is shown
|
||||
const extractedData = data?.map((item) => item.node);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LedgerTable
|
||||
ref={gridRef}
|
||||
rowData={extractedData}
|
||||
rowData={data}
|
||||
onFilterChanged={onFilterChanged}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
||||
/>
|
||||
{extractedData && (
|
||||
<LedgerExportLink entries={extractedData} partyId={partyId} />
|
||||
)}
|
||||
{data && <LedgerExportLink entries={data} partyId={partyId} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||
getRowId={({ data }) => data.id}
|
||||
getRowId={({ data }) => `${data.party.id}-${data.status}`}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -1,29 +1,14 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaValueGetterParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaICellRendererParams,
|
||||
TypedDataAgGrid,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
PriceFlashCell,
|
||||
MarketNameCell,
|
||||
SetFilter,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketTableActions } from './market-table-actions';
|
||||
import { OracleStatus } from './oracle-status';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
|
||||
const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
|
||||
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
||||
|
||||
@@ -51,199 +36,34 @@ const MarketName = (props: MarketNameCellProps) => (
|
||||
</>
|
||||
);
|
||||
|
||||
const defaultColDef = {
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
minWidth: 100,
|
||||
};
|
||||
|
||||
export const MarketListTable = forwardRef<
|
||||
AgGridReact,
|
||||
TypedDataAgGrid<MarketMaybeWithData> & {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
>(({ onMarketClick, ...props }, ref) => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const columnDefs = useColumnDefs({ onMarketClick });
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={getRowId}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
minWidth: 100,
|
||||
}}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus
|
||||
components={{ PriceFlashCell, MarketName }}
|
||||
storeKey="allMarkets"
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="tradableInstrument.instrument.code"
|
||||
cellRenderer="MarketName"
|
||||
cellRendererParams={{ onMarketClick }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Description')}
|
||||
field="tradableInstrument.instrument.name"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Trading mode')}
|
||||
field="tradingMode"
|
||||
minWidth={170}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
||||
if (!data?.data) return undefined;
|
||||
const { trigger, marketTradingMode } = data.data;
|
||||
return marketTradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
trigger &&
|
||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
- ${Schema.AuctionTriggerMapping[trigger]}`
|
||||
: Schema.MarketTradingModeMapping[marketTradingMode];
|
||||
}}
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: Schema.MarketTradingModeMapping,
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="state"
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
||||
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
||||
}}
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: Schema.MarketStateMapping,
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Best bid')}
|
||||
field="data.bestBidPrice"
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceFlashCell"
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.bestBidPrice'>) => {
|
||||
return data?.data?.bestBidPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestBidPrice'
|
||||
>) =>
|
||||
data?.data?.bestBidPrice === undefined
|
||||
? undefined
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestBidPrice,
|
||||
data.decimalPlaces
|
||||
)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Best offer')}
|
||||
field="data.bestOfferPrice"
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceFlashCell"
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestOfferPrice'
|
||||
>) => {
|
||||
return data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.data?.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestOfferPrice'
|
||||
>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Mark price')}
|
||||
field="data.markPrice"
|
||||
type="rightAligned"
|
||||
cellRenderer="PriceFlashCell"
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.markPrice'>) => {
|
||||
return data?.data?.markPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces)
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
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>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="market-actions"
|
||||
field="id"
|
||||
{...COL_DEFS.actions}
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<MarketTableActions
|
||||
marketId={data.id}
|
||||
assetId={
|
||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { render, screen, act, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as DataProviders from '@vegaprotocol/data-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketsContainer } from './markets-container';
|
||||
|
||||
const market = {
|
||||
id: 'id-1',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: { settlementAsset: { id: 'assetId-1' } },
|
||||
},
|
||||
},
|
||||
decimalPlaces: 1,
|
||||
positionDecimalPlaces: 1,
|
||||
state: 'STATE_ACTIVE',
|
||||
tradingMode: 'TRADING_MODE_OPENING_AUCTION',
|
||||
data: {
|
||||
bestBidPrice: 100,
|
||||
},
|
||||
} as unknown as MarketMaybeWithData;
|
||||
|
||||
describe('MarketsContainer', () => {
|
||||
it('context menu should stay open', async () => {
|
||||
const spyOnSelect = jest.fn();
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
error: null,
|
||||
reload: jest.fn(),
|
||||
data: [market],
|
||||
};
|
||||
});
|
||||
|
||||
let rerenderRef: (ui: React.ReactElement) => void;
|
||||
await act(async () => {
|
||||
const { rerender } = render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer onSelect={spyOnSelect} />
|
||||
</MockedProvider>
|
||||
);
|
||||
rerenderRef = rerender;
|
||||
});
|
||||
|
||||
// make sure ag grid is finished initializaing
|
||||
const rowContainer = await screen.findByRole('rowgroup', {
|
||||
name: (_name, element) =>
|
||||
element.classList.contains('ag-center-cols-container'),
|
||||
});
|
||||
expect(within(rowContainer).getAllByRole('row')).toHaveLength(1);
|
||||
expect(
|
||||
screen.getByRole('rowgroup', {
|
||||
name: (_name, element) =>
|
||||
element.classList.contains('ag-pinned-right-cols-container'),
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
|
||||
// open the dropdown
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: (_name, element) =>
|
||||
(element.parentNode as Element)?.getAttribute('id') ===
|
||||
'cell-market-actions-8',
|
||||
})
|
||||
);
|
||||
|
||||
await checkDropdown();
|
||||
|
||||
// reset the mock and rerender so the component
|
||||
// updates with new data
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
error: null,
|
||||
reload: jest.fn(),
|
||||
data: [{ ...market, state: 'STATE_PENDING' }],
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-ignore we await the act above so rerenderRef is definitely defined
|
||||
rerenderRef(
|
||||
<MockedProvider mocks={[]}>
|
||||
<MarketsContainer onSelect={spyOnSelect} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// make sure dropdown is still open
|
||||
await checkDropdown();
|
||||
|
||||
async function checkDropdown() {
|
||||
const dropdownContent = await screen.findByTestId(
|
||||
'market-actions-content'
|
||||
);
|
||||
expect(dropdownContent).toBeInTheDocument();
|
||||
expect(
|
||||
within(dropdownContent).getByRole('menuitem', {
|
||||
name: 'Copy Market ID',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ColDef } 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 } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketTableActions } from './market-table-actions';
|
||||
|
||||
interface Props {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||
|
||||
export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
|
||||
return useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketName',
|
||||
cellRendererParams: { onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
},
|
||||
{
|
||||
headerName: t('Trading mode'),
|
||||
field: 'tradingMode',
|
||||
minWidth: 170,
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
||||
if (!data?.data) return '-';
|
||||
const { trigger, marketTradingMode } = data.data;
|
||||
return marketTradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
trigger &&
|
||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
- ${Schema.AuctionTriggerMapping[trigger]}`
|
||||
: 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('Best bid'),
|
||||
field: 'data.bestBidPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.bestBidPrice'>) => {
|
||||
return data?.data?.bestBidPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestBidPrice'
|
||||
>) =>
|
||||
data?.data?.bestBidPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestBidPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Best offer'),
|
||||
field: 'data.bestOfferPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestOfferPrice'
|
||||
>) => {
|
||||
return data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.data?.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestOfferPrice'
|
||||
>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Mark price'),
|
||||
field: 'data.markPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.markPrice'>) => {
|
||||
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('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>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'market-actions',
|
||||
field: 'id',
|
||||
...COL_DEFS.actions,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<MarketTableActions
|
||||
marketId={data.id}
|
||||
assetId={
|
||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[onMarketClick, openAssetDetailsDialog]
|
||||
);
|
||||
};
|
||||
@@ -1,23 +1,23 @@
|
||||
import { update } from './order-data-provider';
|
||||
import {
|
||||
update,
|
||||
mapOrderUpdateToOrder,
|
||||
filterOrderUpdates,
|
||||
} from './order-data-provider';
|
||||
import type { OrderUpdateFieldsFragment, OrderFieldsFragment } from '../';
|
||||
import type { Edge } from '@vegaprotocol/data-provider';
|
||||
|
||||
describe('order data provider', () => {
|
||||
it('puts incoming data in proper place', () => {
|
||||
const data = [
|
||||
{
|
||||
node: {
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
id: '1',
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
] as Edge<OrderFieldsFragment>[];
|
||||
] as OrderFieldsFragment[];
|
||||
|
||||
const delta = [
|
||||
// this one should be dropped because id don't exits and it's older than newest
|
||||
@@ -52,39 +52,41 @@ describe('order data provider', () => {
|
||||
createdAt: new Date('2022-02-05').toISOString(),
|
||||
},
|
||||
] as OrderUpdateFieldsFragment[];
|
||||
const updatedData = update(data, delta, () => null, { partyId: '0x123' });
|
||||
const updatedData = update(
|
||||
data,
|
||||
filterOrderUpdates(delta),
|
||||
{ partyId: '0x123' },
|
||||
mapOrderUpdateToOrder
|
||||
);
|
||||
expect(updatedData?.findIndex((node) => node.id === delta[0].id)).toEqual(
|
||||
-1
|
||||
);
|
||||
expect(updatedData && updatedData[3].id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[3].updatedAt).toEqual(delta[2].updatedAt);
|
||||
expect(updatedData && updatedData[0].id).toEqual(delta[5].id);
|
||||
expect(updatedData && updatedData[1].id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[2].id).toEqual(delta[4].id);
|
||||
expect(updatedData && updatedData[2].updatedAt).toEqual(delta[4].updatedAt);
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
expect(updatedData && updatedData[3].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[3].node.updatedAt).toEqual(
|
||||
delta[2].updatedAt
|
||||
);
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[5].id);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[4].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
delta[4].updatedAt
|
||||
);
|
||||
expect(update([], delta, () => null, { partyId: '0x123' })?.length).toEqual(
|
||||
5
|
||||
);
|
||||
update(
|
||||
[],
|
||||
filterOrderUpdates(delta),
|
||||
{ partyId: '0x123' },
|
||||
mapOrderUpdateToOrder
|
||||
)?.length
|
||||
).toEqual(5);
|
||||
});
|
||||
it('add only data matching date range filter', () => {
|
||||
const data = [
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
id: '1',
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
},
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
},
|
||||
] as Edge<OrderFieldsFragment>[];
|
||||
] as OrderFieldsFragment[];
|
||||
|
||||
const delta = [
|
||||
// this one should be ignored because it does not match date range
|
||||
@@ -105,22 +107,23 @@ describe('order data provider', () => {
|
||||
},
|
||||
] as OrderUpdateFieldsFragment[];
|
||||
|
||||
const updatedData = update(data, delta, () => null, {
|
||||
partyId: '0x123',
|
||||
filter: {
|
||||
dateRange: { end: new Date('2022-02-01').toISOString() },
|
||||
const updatedData = update(
|
||||
data,
|
||||
filterOrderUpdates(delta),
|
||||
{
|
||||
partyId: '0x123',
|
||||
filter: {
|
||||
dateRange: { end: new Date('2022-02-01').toISOString() },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[0].node.updatedAt).toEqual(
|
||||
delta[2].updatedAt
|
||||
mapOrderUpdateToOrder
|
||||
);
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[1].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
delta[1].updatedAt
|
||||
expect(updatedData?.findIndex((node) => node.id === delta[0].id)).toEqual(
|
||||
-1
|
||||
);
|
||||
expect(updatedData && updatedData[0].id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[0].updatedAt).toEqual(delta[2].updatedAt);
|
||||
expect(updatedData && updatedData[2].id).toEqual(delta[1].id);
|
||||
expect(updatedData && updatedData[2].updatedAt).toEqual(delta[1].updatedAt);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
defaultAppend as append,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import type { PageInfo, Edge, Cursor } from '@vegaprotocol/data-provider';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import type {
|
||||
OrderFieldsFragment,
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
OrdersQuery,
|
||||
OrdersUpdateSubscription,
|
||||
OrdersQueryVariables,
|
||||
OrdersUpdateSubscriptionVariables,
|
||||
} from './__generated__/Orders';
|
||||
import { OrdersDocument, OrdersUpdateDocument } from './__generated__/Orders';
|
||||
import type { ApolloClient } from '@apollo/client';
|
||||
@@ -37,18 +38,21 @@ const orderMatchFilters = (
|
||||
if (!order) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
variables?.filter?.status &&
|
||||
!(order.status && variables.filter.status.includes(order.status))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
variables?.filter?.liveOnly &&
|
||||
!(order.status && liveOnlyOrderStatuses.includes(order.status))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
variables?.filter?.types &&
|
||||
!(order.type && variables.filter.types.includes(order.type))
|
||||
@@ -76,10 +80,11 @@ const orderMatchFilters = (
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const mapOrderUpdateToOrder = (
|
||||
export const mapOrderUpdateToOrder = (
|
||||
orderUpdate: OrderUpdateFieldsFragment
|
||||
): OrderFieldsFragment => {
|
||||
const { marketId, liquidityProvisionId, ...order } = orderUpdate;
|
||||
@@ -101,10 +106,36 @@ const mapOrderUpdateToOrder = (
|
||||
};
|
||||
};
|
||||
|
||||
const mapOrderUpdateToOrderWithMarket =
|
||||
(markets: Record<string, Market>) =>
|
||||
(orderUpdate: OrderUpdateFieldsFragment): Order => {
|
||||
const { market, ...order } = mapOrderUpdateToOrder(orderUpdate);
|
||||
return {
|
||||
...order,
|
||||
market: markets[market.id],
|
||||
};
|
||||
};
|
||||
|
||||
const getData = (
|
||||
responseData: OrdersQuery | null
|
||||
): Edge<OrderFieldsFragment>[] =>
|
||||
responseData?.party?.ordersConnection?.edges || [];
|
||||
): (OrderFieldsFragment & Cursor)[] =>
|
||||
responseData?.party?.ordersConnection?.edges?.map<
|
||||
OrderFieldsFragment & Cursor
|
||||
>((edge) => ({ ...edge.node, cursor: edge.cursor })) || [];
|
||||
|
||||
export const filterOrderUpdates = (
|
||||
orders: OrdersUpdateSubscription['orders']
|
||||
) => {
|
||||
// A single update can contain the same order with multiple updates, so we need to find
|
||||
// the latest version of the order and only update using that
|
||||
return orderBy(
|
||||
uniqBy(
|
||||
orderBy(orders, (order) => order.updatedAt || order.createdAt, 'desc'),
|
||||
'id'
|
||||
),
|
||||
'createdAt'
|
||||
);
|
||||
};
|
||||
|
||||
const getDelta = (
|
||||
subscriptionData: OrdersUpdateSubscription,
|
||||
@@ -114,49 +145,32 @@ const getDelta = (
|
||||
if (!subscriptionData.orders) {
|
||||
return [];
|
||||
}
|
||||
return subscriptionData.orders;
|
||||
return filterOrderUpdates(subscriptionData.orders);
|
||||
};
|
||||
|
||||
export const update = (
|
||||
data: ReturnType<typeof getData> | null,
|
||||
export const update = <T extends Omit<OrderFieldsFragment, 'market'> & Cursor>(
|
||||
data: T[] | null,
|
||||
delta: ReturnType<typeof getDelta>,
|
||||
reload: () => void,
|
||||
variables?: OrdersQueryVariables
|
||||
) => {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
// A single update can contain the same order with multiple updates, so we need to find
|
||||
// the latest version of the order and only update using that
|
||||
const incoming = orderBy(
|
||||
uniqBy(
|
||||
orderBy(delta, (order) => order.updatedAt || order.createdAt, 'desc'),
|
||||
'id'
|
||||
),
|
||||
'createdAt'
|
||||
);
|
||||
|
||||
const updatedData = [...data];
|
||||
incoming.forEach((orderUpdate) => {
|
||||
const index = data.findIndex((edge) => edge.node.id === orderUpdate.id);
|
||||
const newer =
|
||||
data.length === 0 || orderUpdate.createdAt >= data[0].node.createdAt;
|
||||
variables: OrdersQueryVariables,
|
||||
mapDeltaToData: (delta: OrderUpdateFieldsFragment) => T
|
||||
): T[] => {
|
||||
const updatedData = data ? [...data] : ([] as T[]);
|
||||
delta.forEach((orderUpdate) => {
|
||||
const index = data?.findIndex((order) => order.id === orderUpdate.id) ?? -1;
|
||||
const newer = !data?.length || orderUpdate.createdAt >= data[0].createdAt;
|
||||
const doesFilterPass =
|
||||
!variables || orderMatchFilters(orderUpdate, variables);
|
||||
if (index !== -1) {
|
||||
if (doesFilterPass) {
|
||||
updatedData[index] = {
|
||||
...updatedData[index],
|
||||
node: mapOrderUpdateToOrder(orderUpdate),
|
||||
...mapDeltaToData(orderUpdate),
|
||||
};
|
||||
} else {
|
||||
updatedData.splice(index, 1);
|
||||
}
|
||||
} else if (newer && doesFilterPass) {
|
||||
updatedData.unshift({
|
||||
node: mapOrderUpdateToOrder(orderUpdate),
|
||||
cursor: '',
|
||||
});
|
||||
updatedData.unshift(mapDeltaToData(orderUpdate));
|
||||
}
|
||||
});
|
||||
return updatedData;
|
||||
@@ -170,11 +184,13 @@ export const ordersProvider = makeDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
OrdersUpdateSubscription,
|
||||
ReturnType<typeof getDelta>,
|
||||
OrdersQueryVariables
|
||||
OrdersQueryVariables,
|
||||
OrdersUpdateSubscriptionVariables
|
||||
>({
|
||||
query: OrdersDocument,
|
||||
subscriptionQuery: OrdersUpdateDocument,
|
||||
update,
|
||||
update: (data, delta, reload, variables) =>
|
||||
update(data, delta, variables, mapOrderUpdateToOrder),
|
||||
getData,
|
||||
getDelta,
|
||||
pagination: {
|
||||
@@ -185,6 +201,10 @@ export const ordersProvider = makeDataProvider<
|
||||
resetDelay: 1000,
|
||||
additionalContext: { isEnlargedTimeout: true },
|
||||
fetchPolicy: 'no-cache',
|
||||
getSubscriptionVariables: ({ partyId, marketIds }) => ({
|
||||
partyId,
|
||||
marketIds,
|
||||
}),
|
||||
});
|
||||
|
||||
export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
@@ -208,27 +228,36 @@ export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
}
|
||||
const orders = partsData[0] as ReturnType<typeof getData>;
|
||||
return variables.marketId
|
||||
? orders.filter((edge) => variables.marketId === edge.node.market.id)
|
||||
? orders.filter((order) => variables.marketId === order.market.id)
|
||||
: orders;
|
||||
}
|
||||
);
|
||||
|
||||
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
(Order | null)[],
|
||||
Order[],
|
||||
(Order & Cursor)[],
|
||||
never,
|
||||
OrdersQueryVariables
|
||||
>(
|
||||
[
|
||||
ordersProvider,
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData): Order[] =>
|
||||
((partsData[0] as ReturnType<typeof getData>) || []).map((edge) => ({
|
||||
...edge.node,
|
||||
market: (partsData[1] as Market[]).find(
|
||||
(market) => market.id === edge.node.market.id
|
||||
),
|
||||
}))
|
||||
(partsData, variables, prevData, parts): Order[] => {
|
||||
if (prevData && parts[0].isUpdate) {
|
||||
return update(
|
||||
prevData,
|
||||
parts[0].delta,
|
||||
variables,
|
||||
mapOrderUpdateToOrderWithMarket(partsData[1] as Record<string, Market>)
|
||||
);
|
||||
}
|
||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
||||
(order) => ({
|
||||
...order,
|
||||
market: (partsData[1] as Record<string, Market>)[order.market.id],
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const hasActiveOrderProvider = makeDerivedDataProvider<
|
||||
@@ -244,7 +273,7 @@ export const hasAmendableOrderProvider = makeDerivedDataProvider<
|
||||
>([activeOrdersProvider], (parts) => {
|
||||
const activeOrders = parts[0] as ReturnType<typeof getData>;
|
||||
const hasAmendableOrder = activeOrders.some(
|
||||
(edge) => !(edge.node.liquidityProvision || edge.node.peggedOrder)
|
||||
(order) => !(order.liquidityProvision || order.peggedOrder)
|
||||
);
|
||||
return hasAmendableOrder;
|
||||
});
|
||||
|
||||
@@ -32,7 +32,6 @@ describe('OrderListManager', () => {
|
||||
flush: jest.fn(),
|
||||
reload: jest.fn(),
|
||||
load: jest.fn(),
|
||||
totalCount: undefined,
|
||||
});
|
||||
await act(async () => {
|
||||
render(generateJsx());
|
||||
|
||||
@@ -87,7 +87,7 @@ export const OrderListManager = ({
|
||||
gridRef.current.api.setRowData(data);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ const positionFields: PositionFieldsFragment[] = [
|
||||
{
|
||||
__typename: 'Position',
|
||||
realisedPNL: '230000',
|
||||
openVolume: '6',
|
||||
openVolume: '-6',
|
||||
unrealisedPNL: '895000',
|
||||
averageEntryPrice: '1129935',
|
||||
updatedAt: '2022-07-28T15:09:34.441143Z',
|
||||
@@ -82,7 +82,7 @@ const positionFields: PositionFieldsFragment[] = [
|
||||
},
|
||||
{
|
||||
__typename: 'Position',
|
||||
realisedPNL: '230000',
|
||||
realisedPNL: '-230000',
|
||||
openVolume: '1',
|
||||
unrealisedPNL: '-22519',
|
||||
averageEntryPrice: '84400088',
|
||||
@@ -98,6 +98,24 @@ const positionFields: PositionFieldsFragment[] = [
|
||||
lossSocializationAmount: '0',
|
||||
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
__typename: 'Position',
|
||||
realisedPNL: '-303295252',
|
||||
openVolume: '0',
|
||||
unrealisedPNL: '0',
|
||||
averageEntryPrice: '6126312',
|
||||
updatedAt: '2022-07-28T14:53:54.725477Z',
|
||||
positionStatus: PositionStatus.POSITION_STATUS_CLOSED_OUT,
|
||||
lossSocializationAmount: '261',
|
||||
market: {
|
||||
id: 'market-3',
|
||||
__typename: 'Market',
|
||||
},
|
||||
party: {
|
||||
id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||
__typename: 'Party',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const marginsFields: MarginFieldsFragment[] = [
|
||||
|
||||
@@ -10,32 +10,33 @@ fragment TradeFields on Trade {
|
||||
}
|
||||
|
||||
query Trades($marketId: ID!, $pagination: Pagination) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
tradesConnection(pagination: $pagination) {
|
||||
edges {
|
||||
node {
|
||||
...TradeFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
trades(filter: { marketIds: [$marketId] }, pagination: $pagination) {
|
||||
edges {
|
||||
node {
|
||||
...TradeFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment TradeUpdateFields on TradeUpdate {
|
||||
id
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
marketId
|
||||
aggressor
|
||||
}
|
||||
|
||||
subscription TradesUpdate($marketId: ID!) {
|
||||
trades(marketId: $marketId) {
|
||||
id
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
marketId
|
||||
aggressor
|
||||
tradesStream(filter: { marketIds: [$marketId] }) {
|
||||
...TradeUpdateFields
|
||||
}
|
||||
}
|
||||
|
||||
+28
-24
@@ -11,14 +11,16 @@ export type TradesQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type TradesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradesConnection?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null } | null };
|
||||
export type TradesQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
||||
|
||||
export type TradeUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side };
|
||||
|
||||
export type TradesUpdateSubscriptionVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type TradesUpdateSubscription = { __typename?: 'Subscription', trades?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side }> | null };
|
||||
export type TradesUpdateSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side }> | null };
|
||||
|
||||
export const TradeFieldsFragmentDoc = gql`
|
||||
fragment TradeFields on Trade {
|
||||
@@ -32,23 +34,30 @@ export const TradeFieldsFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TradeUpdateFieldsFragmentDoc = gql`
|
||||
fragment TradeUpdateFields on TradeUpdate {
|
||||
id
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
marketId
|
||||
aggressor
|
||||
}
|
||||
`;
|
||||
export const TradesDocument = gql`
|
||||
query Trades($marketId: ID!, $pagination: Pagination) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
tradesConnection(pagination: $pagination) {
|
||||
edges {
|
||||
node {
|
||||
...TradeFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
trades(filter: {marketIds: [$marketId]}, pagination: $pagination) {
|
||||
edges {
|
||||
node {
|
||||
...TradeFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,16 +93,11 @@ export type TradesLazyQueryHookResult = ReturnType<typeof useTradesLazyQuery>;
|
||||
export type TradesQueryResult = Apollo.QueryResult<TradesQuery, TradesQueryVariables>;
|
||||
export const TradesUpdateDocument = gql`
|
||||
subscription TradesUpdate($marketId: ID!) {
|
||||
trades(marketId: $marketId) {
|
||||
id
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
marketId
|
||||
aggressor
|
||||
tradesStream(filter: {marketIds: [$marketId]}) {
|
||||
...TradeUpdateFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
${TradeUpdateFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useTradesUpdateSubscription__
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import compact from 'lodash/compact';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useRef } from 'react';
|
||||
@@ -19,12 +18,11 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
||||
dataProvider: tradesWithMarketProvider,
|
||||
variables: { marketId },
|
||||
});
|
||||
const trades = compact(data).map((d) => d.node);
|
||||
|
||||
return (
|
||||
<TradesTable
|
||||
ref={gridRef}
|
||||
rowData={trades}
|
||||
rowData={data}
|
||||
onClick={(price?: string) => {
|
||||
if (price) {
|
||||
updateOrder(marketId, { price });
|
||||
|
||||
@@ -3,75 +3,95 @@ import {
|
||||
makeDerivedDataProvider,
|
||||
defaultAppend as append,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||
import type { PageInfo, Cursor } from '@vegaprotocol/data-provider';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import type {
|
||||
TradesQuery,
|
||||
TradesQueryVariables,
|
||||
TradeFieldsFragment,
|
||||
TradesUpdateSubscription,
|
||||
TradeUpdateFieldsFragment,
|
||||
TradesUpdateSubscriptionVariables,
|
||||
} from './__generated__/Trades';
|
||||
import { TradesDocument, TradesUpdateDocument } from './__generated__/Trades';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import produce from 'immer';
|
||||
|
||||
export const MAX_TRADES = 500;
|
||||
|
||||
const getData = (
|
||||
responseData: TradesQuery | null
|
||||
): ({
|
||||
cursor: string;
|
||||
node: TradeFieldsFragment;
|
||||
} | null)[] => responseData?.market?.tradesConnection?.edges || [];
|
||||
): (TradeFieldsFragment & Cursor)[] =>
|
||||
responseData?.trades?.edges.map<TradeFieldsFragment & Cursor>((edge) => ({
|
||||
...edge.node,
|
||||
cursor: edge.cursor,
|
||||
})) || [];
|
||||
|
||||
const getDelta = (subscriptionData: TradesUpdateSubscription) =>
|
||||
subscriptionData?.trades || [];
|
||||
subscriptionData?.tradesStream || [];
|
||||
|
||||
const update = (
|
||||
data: ReturnType<typeof getData> | null,
|
||||
delta: ReturnType<typeof getDelta>
|
||||
) => {
|
||||
if (!data) return data;
|
||||
return produce(data, (draft) => {
|
||||
// for each incoming trade add it to the beginning and remove oldest trade
|
||||
orderBy(delta, 'createdAt', 'desc').forEach((node) => {
|
||||
const { marketId, ...nodeData } = node;
|
||||
draft.unshift({
|
||||
node: {
|
||||
...nodeData,
|
||||
__typename: 'Trade',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
},
|
||||
cursor: '',
|
||||
});
|
||||
const mapTradeUpdateToTrade = (
|
||||
tradeUpdate: TradeUpdateFieldsFragment
|
||||
): TradeFieldsFragment => {
|
||||
const { marketId, ...trade } = tradeUpdate;
|
||||
return {
|
||||
...trade,
|
||||
__typename: 'Trade',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (draft.length > MAX_TRADES) {
|
||||
draft.pop();
|
||||
}
|
||||
});
|
||||
const mapTradeUpdateToTradeWithMarket =
|
||||
(markets: Record<string, Market>) =>
|
||||
(tradeUpdate: TradeUpdateFieldsFragment): Trade => {
|
||||
const { market, ...trade } = mapTradeUpdateToTrade(tradeUpdate);
|
||||
return {
|
||||
...trade,
|
||||
market: markets[market.id],
|
||||
};
|
||||
};
|
||||
|
||||
const update = <T extends Omit<TradeFieldsFragment, 'market'> & Cursor>(
|
||||
data: T[] | null,
|
||||
delta: ReturnType<typeof getDelta>,
|
||||
variables: TradesQueryVariables,
|
||||
mapDeltaToData: (delta: TradeUpdateFieldsFragment) => T
|
||||
): T[] => {
|
||||
const updatedData = data ? [...data] : ([] as T[]);
|
||||
orderBy(delta, 'createdAt', 'desc').forEach((tradeUpdate) => {
|
||||
const index = data?.findIndex((trade) => trade.id === tradeUpdate.id) ?? -1;
|
||||
if (index !== -1) {
|
||||
updatedData[index] = {
|
||||
...updatedData[index],
|
||||
...mapDeltaToData(tradeUpdate),
|
||||
};
|
||||
} else if (!data?.length || tradeUpdate.createdAt >= data[0].createdAt) {
|
||||
updatedData.unshift(mapDeltaToData(tradeUpdate));
|
||||
}
|
||||
});
|
||||
return updatedData.slice(0, MAX_TRADES);
|
||||
};
|
||||
|
||||
export type Trade = Omit<TradeFieldsFragment, 'market'> & { market?: Market };
|
||||
export type TradeEdge = Edge<Trade>;
|
||||
|
||||
const getPageInfo = (responseData: TradesQuery | null): PageInfo | null =>
|
||||
responseData?.market?.tradesConnection?.pageInfo || null;
|
||||
responseData?.trades?.pageInfo || null;
|
||||
|
||||
export const tradesProvider = makeDataProvider<
|
||||
Parameters<typeof getData>['0'],
|
||||
ReturnType<typeof getData>,
|
||||
Parameters<typeof getDelta>['0'],
|
||||
ReturnType<typeof getDelta>,
|
||||
TradesQueryVariables
|
||||
TradesQueryVariables,
|
||||
TradesUpdateSubscriptionVariables
|
||||
>({
|
||||
query: TradesDocument,
|
||||
subscriptionQuery: TradesUpdateDocument,
|
||||
update,
|
||||
update: (data, delta, reload, variables) =>
|
||||
update(data, delta, variables, mapTradeUpdateToTrade),
|
||||
getData,
|
||||
getDelta,
|
||||
pagination: {
|
||||
@@ -79,34 +99,32 @@ export const tradesProvider = makeDataProvider<
|
||||
append,
|
||||
first: MAX_TRADES,
|
||||
},
|
||||
getSubscriptionVariables: ({ marketId }) => ({ marketId }),
|
||||
});
|
||||
|
||||
export const tradesWithMarketProvider = makeDerivedDataProvider<
|
||||
(TradeEdge | null)[],
|
||||
Trade[],
|
||||
(Trade & Cursor)[],
|
||||
never,
|
||||
TradesQueryVariables
|
||||
>(
|
||||
[
|
||||
tradesProvider,
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData): (TradeEdge | null)[] | null => {
|
||||
const edges = partsData[0] as ReturnType<typeof getData>;
|
||||
return edges.map((edge) => {
|
||||
if (edge === null) {
|
||||
return null;
|
||||
}
|
||||
const node = {
|
||||
...edge.node,
|
||||
market: (partsData[1] as Market[]).find(
|
||||
(market) => market.id === edge.node.market.id
|
||||
),
|
||||
};
|
||||
const cursor = edge?.cursor || '';
|
||||
return {
|
||||
cursor,
|
||||
node,
|
||||
};
|
||||
});
|
||||
(partsData, variables, prevData, parts): Trade[] | null => {
|
||||
if (prevData && parts[0].isUpdate) {
|
||||
return update(
|
||||
prevData,
|
||||
parts[0].delta as ReturnType<typeof getDelta>,
|
||||
variables,
|
||||
mapTradeUpdateToTradeWithMarket(partsData[1] as Record<string, Market>)
|
||||
);
|
||||
}
|
||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
||||
(trade) => ({
|
||||
...trade,
|
||||
market: (partsData[1] as Record<string, Market>)[trade.market.id],
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -11,24 +11,20 @@ export const tradesQuery = (
|
||||
override?: PartialDeep<TradesQuery>
|
||||
): TradesQuery => {
|
||||
const defaultResult: TradesQuery = {
|
||||
market: {
|
||||
id: 'market-0',
|
||||
tradesConnection: {
|
||||
__typename: 'TradeConnection',
|
||||
edges: trades.map((node, i) => ({
|
||||
__typename: 'TradeEdge',
|
||||
node,
|
||||
cursor: (i + 1).toString(),
|
||||
})),
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
startCursor: '0',
|
||||
endCursor: trades.length.toString(),
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
trades: {
|
||||
__typename: 'TradeConnection',
|
||||
edges: trades.map((node, i) => ({
|
||||
__typename: 'TradeEdge',
|
||||
node,
|
||||
cursor: (i + 1).toString(),
|
||||
})),
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
startCursor: '0',
|
||||
endCursor: trades.length.toString(),
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
__typename: 'Market',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,7 +36,7 @@ export const tradesUpdateSubscription = (
|
||||
): TradesUpdateSubscription => {
|
||||
const defaultResult: TradesUpdateSubscription = {
|
||||
__typename: 'Subscription',
|
||||
trades: [
|
||||
tradesStream: [
|
||||
{
|
||||
__typename: 'TradeUpdate',
|
||||
id: '1234567890',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@for $i from 0 through 16 {
|
||||
@for $i from 0 through 25 {
|
||||
.loader-item:nth-child(#{$i}) {
|
||||
@if $i % 2 == 0 {
|
||||
animation-delay: #{$i * 50 * random(5)}ms;
|
||||
|
||||
@@ -25,7 +25,7 @@ export const Loader = ({ size = 'large', forceTheme }: LoaderProps) => {
|
||||
});
|
||||
const wrapperClasses =
|
||||
size === 'small' ? 'w-[15px] h-[15px]' : 'w-[50px] h-[50px]';
|
||||
const items = size === 'small' ? 9 : 16;
|
||||
const items = size === 'small' ? 9 : 25;
|
||||
|
||||
const generate = useMemo(() => pseudoRandom(1), []);
|
||||
|
||||
|
||||
@@ -13,3 +13,4 @@ export * from './lib/remove-0x';
|
||||
export * from './lib/remove-pagination-wrapper';
|
||||
export * from './lib/time';
|
||||
export * from './lib/validate';
|
||||
export * from './lib/resolve-network-name';
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
const NETWORK_NAME_MAP: Readonly<Record<string, string>> = {
|
||||
'1': 'Ethereum Mainnet',
|
||||
'11155111': 'Sepolia test network',
|
||||
};
|
||||
export const resolveNetworkName = (chainId?: string): string =>
|
||||
NETWORK_NAME_MAP[chainId || ''] || `(chainID: ${chainId})`;
|
||||
@@ -9,7 +9,10 @@ import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { useCallback } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import type { EthWithdrawalApprovalState } from './use-ethereum-withdraw-approvals-store';
|
||||
import { useEthWithdrawApprovalsStore } from './use-ethereum-withdraw-approvals-store';
|
||||
import {
|
||||
useEthWithdrawApprovalsStore,
|
||||
WithdrawalFailure,
|
||||
} from './use-ethereum-withdraw-approvals-store';
|
||||
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
|
||||
import { VerificationStatus } from './withdrawal-approval-status';
|
||||
|
||||
@@ -26,12 +29,23 @@ const EthWithdrawalApprovalToastContent = ({
|
||||
}: {
|
||||
tx: EthWithdrawalApprovalState;
|
||||
}) => {
|
||||
const isConnectionFailure =
|
||||
tx.failureReason &&
|
||||
[
|
||||
WithdrawalFailure.WrongConnection,
|
||||
WithdrawalFailure.NoConnection,
|
||||
].includes(tx.failureReason);
|
||||
|
||||
let title = '';
|
||||
if (tx.status === ApprovalStatus.Error) {
|
||||
title = t('Error occurred');
|
||||
}
|
||||
if (tx.status === ApprovalStatus.Pending) {
|
||||
title = t('Pending approval');
|
||||
if (tx.failureReason && isConnectionFailure) {
|
||||
title = tx.message || t('Withdraw failure');
|
||||
} else {
|
||||
title = t('Pending approval');
|
||||
}
|
||||
}
|
||||
if (tx.status === ApprovalStatus.Delayed) {
|
||||
title = t('Delayed');
|
||||
@@ -39,11 +53,12 @@ const EthWithdrawalApprovalToastContent = ({
|
||||
if (tx.status === ApprovalStatus.Ready) {
|
||||
title = t('Approved');
|
||||
}
|
||||
|
||||
const num = formatNumber(
|
||||
toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
|
||||
tx.withdrawal.asset.decimals
|
||||
);
|
||||
const details = (
|
||||
const details = isConnectionFailure ? null : (
|
||||
<Panel>
|
||||
<strong>
|
||||
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
|
||||
@@ -61,7 +76,8 @@ const EthWithdrawalApprovalToastContent = ({
|
||||
};
|
||||
|
||||
const isFinal = (tx: EthWithdrawalApprovalState) =>
|
||||
[ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status);
|
||||
[ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status) &&
|
||||
!tx.failureReason;
|
||||
|
||||
export const useEthereumWithdrawApprovalsToasts = () => {
|
||||
const [setToast, remove] = useToasts((state) => [
|
||||
@@ -74,21 +90,34 @@ export const useEthereumWithdrawApprovalsToasts = () => {
|
||||
]);
|
||||
|
||||
const fromWithdrawalApproval = useCallback(
|
||||
(tx: EthWithdrawalApprovalState): Toast => ({
|
||||
id: `withdrawal-${tx.id}`,
|
||||
intent: intentMap[tx.status],
|
||||
onClose: () => {
|
||||
if ([ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status)) {
|
||||
deleteTx(tx.id);
|
||||
} else {
|
||||
dismissTx(tx.id);
|
||||
}
|
||||
remove(`withdrawal-${tx.id}`);
|
||||
},
|
||||
loader: tx.status === ApprovalStatus.Pending,
|
||||
content: <EthWithdrawalApprovalToastContent tx={tx} />,
|
||||
closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined,
|
||||
}),
|
||||
(tx: EthWithdrawalApprovalState): Toast => {
|
||||
const loader =
|
||||
tx.status === ApprovalStatus.Pending &&
|
||||
!(
|
||||
tx.failureReason &&
|
||||
[
|
||||
WithdrawalFailure.WrongConnection,
|
||||
WithdrawalFailure.NoConnection,
|
||||
].includes(tx.failureReason)
|
||||
);
|
||||
return {
|
||||
id: `withdrawal-${tx.id}`,
|
||||
intent: intentMap[tx.status],
|
||||
onClose: () => {
|
||||
if (
|
||||
[ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status)
|
||||
) {
|
||||
deleteTx(tx.id);
|
||||
} else {
|
||||
dismissTx(tx.id);
|
||||
}
|
||||
remove(`withdrawal-${tx.id}`);
|
||||
},
|
||||
loader,
|
||||
content: <EthWithdrawalApprovalToastContent tx={tx} />,
|
||||
closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined,
|
||||
};
|
||||
},
|
||||
[deleteTx, dismissTx, remove]
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ import type { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import waitForNextTick from 'flush-promises';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
|
||||
import {
|
||||
ApprovalStatus,
|
||||
WithdrawalFailure,
|
||||
} from './use-ethereum-withdraw-approvals-store';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type {
|
||||
EthWithdrawApprovalStore,
|
||||
@@ -21,7 +24,7 @@ import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
|
||||
const mockWeb3Provider = jest.fn();
|
||||
|
||||
let mockChainId = 111111;
|
||||
let mockChainId: number | undefined = 111111;
|
||||
jest.mock('@web3-react/core', () => ({
|
||||
useWeb3React: () => ({
|
||||
provider: mockWeb3Provider(),
|
||||
@@ -314,9 +317,29 @@ describe('useEthWithdrawApprovalsManager', () => {
|
||||
update,
|
||||
});
|
||||
render();
|
||||
expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Error);
|
||||
expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Pending);
|
||||
expect(update.mock.calls[0][1].message).toEqual('Change network');
|
||||
expect(update.mock.calls[0][1].failureReason).toEqual(
|
||||
WithdrawalFailure.WrongConnection
|
||||
);
|
||||
mockChainId = 111111;
|
||||
});
|
||||
|
||||
it('detect no chainId', () => {
|
||||
mockChainId = undefined;
|
||||
const transaction = createWithdrawTransaction();
|
||||
mockEthTransactionStoreState.mockReturnValue({ create });
|
||||
mockEthWithdrawApprovalsStoreState.mockReturnValue({
|
||||
transactions: [transaction],
|
||||
update,
|
||||
});
|
||||
render();
|
||||
expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Pending);
|
||||
expect(update.mock.calls[0][1].message).toEqual(
|
||||
'You are on the wrong network'
|
||||
'Connect wallet to withdraw'
|
||||
);
|
||||
expect(update.mock.calls[0][1].failureReason).toEqual(
|
||||
WithdrawalFailure.NoConnection
|
||||
);
|
||||
mockChainId = 111111;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { useGetWithdrawThreshold } from './use-get-withdraw-threshold';
|
||||
import { useGetWithdrawDelay } from './use-get-withdraw-delay';
|
||||
@@ -21,8 +21,9 @@ import { WithdrawalApprovalDocument } from '@vegaprotocol/wallet';
|
||||
|
||||
import { useEthTransactionStore } from './use-ethereum-transaction-store';
|
||||
import {
|
||||
useEthWithdrawApprovalsStore,
|
||||
ApprovalStatus,
|
||||
useEthWithdrawApprovalsStore,
|
||||
WithdrawalFailure,
|
||||
} from './use-ethereum-withdraw-approvals-store';
|
||||
|
||||
export const useEthWithdrawApprovalsManager = () => {
|
||||
@@ -55,16 +56,27 @@ export const useEthWithdrawApprovalsManager = () => {
|
||||
message: t(
|
||||
`Invalid asset source: ${withdrawal.asset.source.__typename}`
|
||||
),
|
||||
failureReason: WithdrawalFailure.InvalidAsset,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!chainId) {
|
||||
update(transaction.id, {
|
||||
status: ApprovalStatus.Pending,
|
||||
message: t(`Connect wallet to withdraw`),
|
||||
failureReason: WithdrawalFailure.NoConnection,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (chainId?.toString() !== config?.chain_id) {
|
||||
update(transaction.id, {
|
||||
status: ApprovalStatus.Error,
|
||||
message: t(`You are on the wrong network`),
|
||||
status: ApprovalStatus.Pending,
|
||||
message: t(`Change network`),
|
||||
failureReason: WithdrawalFailure.WrongConnection,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
update(transaction.id, {
|
||||
status: ApprovalStatus.Pending,
|
||||
message: t('Verifying withdrawal approval'),
|
||||
|
||||
@@ -14,6 +14,13 @@ export enum ApprovalStatus {
|
||||
Error = 'Error',
|
||||
Ready = 'Ready',
|
||||
}
|
||||
|
||||
export enum WithdrawalFailure {
|
||||
InvalidAsset,
|
||||
NoConnection,
|
||||
WrongConnection,
|
||||
}
|
||||
|
||||
export interface EthWithdrawalApprovalState {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
@@ -24,6 +31,7 @@ export interface EthWithdrawalApprovalState {
|
||||
dialogOpen?: boolean;
|
||||
withdrawal: WithdrawalBusEventFieldsFragment;
|
||||
approval?: WithdrawalApprovalQuery['erc20WithdrawalApproval'];
|
||||
failureReason?: WithdrawalFailure;
|
||||
}
|
||||
export interface EthWithdrawApprovalStore {
|
||||
transactions: (EthWithdrawalApprovalState | undefined)[];
|
||||
@@ -42,6 +50,7 @@ export interface EthWithdrawApprovalStore {
|
||||
| 'threshold'
|
||||
| 'completeTimestamp'
|
||||
| 'dialogOpen'
|
||||
| 'failureReason'
|
||||
>
|
||||
>
|
||||
) => void;
|
||||
@@ -86,6 +95,7 @@ export const useEthWithdrawApprovalsStore = create<EthWithdrawApprovalStore>()(
|
||||
| 'threshold'
|
||||
| 'completeTimestamp'
|
||||
| 'dialogOpen'
|
||||
| 'failureReason'
|
||||
>
|
||||
>
|
||||
) =>
|
||||
|
||||
@@ -1,17 +1,69 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import {
|
||||
getDateTimeFormat,
|
||||
resolveNetworkName,
|
||||
truncateByChars,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { EthWithdrawalApprovalState } from './use-ethereum-withdraw-approvals-store';
|
||||
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
|
||||
import {
|
||||
ApprovalStatus,
|
||||
useEthWithdrawApprovalsStore,
|
||||
WithdrawalFailure,
|
||||
} from './use-ethereum-withdraw-approvals-store';
|
||||
import { useEthereumConfig } from './use-ethereum-config';
|
||||
import { Button, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3ConnectStore } from './web3-connect-store';
|
||||
|
||||
export const VerificationStatus = ({
|
||||
state,
|
||||
}: {
|
||||
state: EthWithdrawalApprovalState;
|
||||
}) => {
|
||||
const { config } = useEthereumConfig();
|
||||
const openDialog = useWeb3ConnectStore((state) => state.open);
|
||||
const remove = useToasts((state) => state.remove);
|
||||
const deleteTx = useEthWithdrawApprovalsStore((state) => state.delete);
|
||||
|
||||
if (state.status === ApprovalStatus.Error) {
|
||||
return <p>{state.message || t('Something went wrong')}</p>;
|
||||
}
|
||||
|
||||
if (
|
||||
state.failureReason &&
|
||||
[
|
||||
WithdrawalFailure.WrongConnection,
|
||||
WithdrawalFailure.NoConnection,
|
||||
].includes(state.failureReason)
|
||||
) {
|
||||
return state.failureReason === WithdrawalFailure.NoConnection ? (
|
||||
<>
|
||||
<p>
|
||||
{t('To complete this withdrawal, connect the Ethereum wallet %s', [
|
||||
truncateByChars(state.withdrawal.details?.receiverAddress || ' '),
|
||||
])}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => {
|
||||
openDialog();
|
||||
deleteTx(state.id);
|
||||
remove(`withdrawal-${state.id}`);
|
||||
}}
|
||||
>
|
||||
{t('Connect wallet')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>{t('Your Ethereum wallet is connected to the wrong network.')}</p>
|
||||
<p className="mt-2">
|
||||
{t('Go to your Ethereum wallet and connect to the network %s', [
|
||||
resolveNetworkName(config?.chain_id),
|
||||
])}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (state.status === ApprovalStatus.Pending) {
|
||||
return <p>{t('Verifying...')}</p>;
|
||||
}
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@
|
||||
"babel-loader": "8.1.0",
|
||||
"cypress": "^12.9.0",
|
||||
"cypress-mochawesome-reporter": "^3.3.0",
|
||||
"cypress-real-events": "^1.7.6",
|
||||
"cypress-real-events": "^1.8.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"eslint": "8.15.0",
|
||||
"eslint-config-next": "12.2.3",
|
||||
|
||||
@@ -11882,10 +11882,10 @@ cypress-mochawesome-reporter@^3.3.0:
|
||||
mochawesome-merge "^4.2.1"
|
||||
mochawesome-report-generator "^6.2.0"
|
||||
|
||||
cypress-real-events@^1.7.6:
|
||||
version "1.7.6"
|
||||
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.6.tgz#6f17e0b2ceea1d6dc60f6737d8f84cc517bbbb4c"
|
||||
integrity sha512-yP6GnRrbm6HK5q4DH6Nnupz37nOfZu/xn1xFYqsE2o4G73giPWQOdu6375QYpwfU1cvHNCgyD2bQ2hPH9D7NMw==
|
||||
cypress-real-events@^1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.8.1.tgz#d00c7fe93124bbe7c0f27296684838614d24a840"
|
||||
integrity sha512-8fFnA8EzS3EVbAmpSEUf3A8yZCmfU3IPOSGUDVFCdE1ke1gYL1A+gvXXV6HKUbTPRuvKKt2vpaMbUwYLpDRswQ==
|
||||
|
||||
cypress@^12.9.0:
|
||||
version "12.9.0"
|
||||
|
||||
Reference in New Issue
Block a user