Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
598cfbd372 | ||
|
|
0fd277f7cc | ||
|
|
212a8f6409 | ||
|
|
fdbb41a27c | ||
|
|
72d8805b52 | ||
|
|
e78dd4206c | ||
|
|
5f3414cfec | ||
|
|
0bf1efc3c9 | ||
|
|
475fa9768a | ||
|
|
709076724f | ||
|
|
d9d4d5529d | ||
|
|
f3607a77ab | ||
|
|
5fa7078833 | ||
|
|
ccd0007e36 | ||
|
|
7d3c2ff44c | ||
|
|
420b58c6ea | ||
|
|
287a0cae58 | ||
|
|
c4decf3f2d | ||
|
|
43012d8cdd | ||
|
|
4c6af7bfc6 | ||
|
|
92bf90e976 | ||
|
|
d9d45451bb | ||
|
|
d059b3f710 | ||
|
|
0152720006 | ||
|
|
0d41f2a3cf | ||
|
|
a3bb50fdc5 | ||
|
|
84e19427c7 | ||
|
|
77a391448b | ||
|
|
484d7888cf | ||
|
|
187f1929fe | ||
|
|
0a4333645b | ||
|
|
310506e5af | ||
|
|
0b33ed4299 | ||
|
|
13f2e51798 | ||
|
|
fc047feeee | ||
|
|
005455c870 | ||
|
|
2c2bc391e8 | ||
|
|
445a085190 | ||
|
|
bb1b236cdf | ||
|
|
da66b7b20d | ||
|
|
ec12811f72 | ||
|
|
180de8cf25 | ||
|
|
df88e77cdf | ||
|
|
9441aee8cf |
@@ -265,7 +265,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check out ipfs-redirect
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'main') }}
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'vegaprotocol/ipfs-redirect'
|
||||
@@ -273,11 +273,12 @@ jobs:
|
||||
fetch-depth: '0'
|
||||
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
|
||||
- name: Update console.vega.xyz DNS to redirect to the new console
|
||||
- name: Update interstitial page to point to the new console
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
# set CID
|
||||
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
|
||||
tar -xzf kubo.tgz
|
||||
export PATH="$PATH:$PWD/kubo"
|
||||
@@ -285,19 +286,16 @@ jobs:
|
||||
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
|
||||
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
|
||||
|
||||
ls -al ipfs-redirect
|
||||
|
||||
(
|
||||
cd ipfs-redirect
|
||||
|
||||
# configure git
|
||||
git status
|
||||
cat .git/config
|
||||
git config --global user.email "vega-ci-bot@vega.xyz"
|
||||
git config --global user.name "vega-ci-bot"
|
||||
|
||||
branch_name="update-hash-${{ github.ref }}"
|
||||
git checkout -b "$branch_name"
|
||||
|
||||
# update CID files
|
||||
if echo ${{ github.ref }} | grep -q main; then
|
||||
echo $new_hash > cidv0-mainnet.txt
|
||||
echo $new_cid > cidv1-mainnet.txt
|
||||
@@ -308,12 +306,8 @@ jobs:
|
||||
git add cidv0-fairground.txt cidv1-fairground.txt
|
||||
fi
|
||||
|
||||
# create commit
|
||||
commit_msg="Automated hash update from ${{ github.ref }}"
|
||||
git commit -m "$commit_msg"
|
||||
git push -u origin "$branch_name"
|
||||
pr_url="$(gh pr create --title "${commit_msg}" --body 'automated pull request to update CIDs')"
|
||||
echo $pr_url
|
||||
# once auto merge get's enabled on documentation repo let's do follow up
|
||||
sleep 5
|
||||
gh pr merge "${pr_url}" --delete-branch --squash --admin
|
||||
git push -u origin "main"
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ module.exports = defineConfig({
|
||||
vegaTokenContractAddress: '0xF41bD86d462D36b997C0bbb4D97a0a3382f205B7',
|
||||
vegaTokenAddress: '0x67175Da1D5e966e40D11c4B2519392B2058373de',
|
||||
txTimeout: { timeout: 70000 },
|
||||
epochTimeout: { timeout: 10000 },
|
||||
epochTimeout: { timeout: 12000 },
|
||||
blockConfirmations: 3,
|
||||
grepTags: '@regression @smoke @slow',
|
||||
grepFilterSpecs: true,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
export const previousEpochData = {
|
||||
epoch: {
|
||||
id: '7611',
|
||||
validatorsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'cd96782bc0ad5679869cf69fe7838a92212da7f53b4a214bed68067117494122',
|
||||
stakedTotal: '3154229668720612941799',
|
||||
rewardScore: {
|
||||
rawValidatorScore: '0.2',
|
||||
performanceScore: '1',
|
||||
multisigScore: '0',
|
||||
validatorScore: '0.2',
|
||||
normalisedScore: '0.2007216887087119',
|
||||
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RewardScore',
|
||||
},
|
||||
rankingScore: {
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
rankingScore: '0.211713765544955625',
|
||||
stakeScore: '0.2016321576618625',
|
||||
performanceScore: '1',
|
||||
votingPower: '2007',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '887d936f797a47032eceb572a13b69b581d3b1fa595a7d021a3e3cf2a5d2acfd',
|
||||
stakedTotal: '3151161904761904764551',
|
||||
rewardScore: {
|
||||
rawValidatorScore: '0.2',
|
||||
performanceScore: '1',
|
||||
multisigScore: '1',
|
||||
validatorScore: '0.2',
|
||||
normalisedScore: '0.2007216887087119',
|
||||
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RewardScore',
|
||||
},
|
||||
rankingScore: {
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
rankingScore: '0.211507855409133255',
|
||||
stakeScore: '0.2014360527706031',
|
||||
performanceScore: '1',
|
||||
votingPower: '2007',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
],
|
||||
__typename: 'NodesConnection',
|
||||
},
|
||||
__typename: 'Epoch',
|
||||
},
|
||||
};
|
||||
@@ -89,8 +89,14 @@ context(
|
||||
});
|
||||
cy.get(proposalStatus).should('have.text', 'Open');
|
||||
voteForProposal('for');
|
||||
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Passed');
|
||||
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted');
|
||||
cy.get(proposalStatus, proposalTimeout)
|
||||
.should('have.text', 'Passed')
|
||||
.then(() => {
|
||||
cy.get(proposalStatus, proposalTimeout).should(
|
||||
'have.text',
|
||||
'Enacted'
|
||||
);
|
||||
});
|
||||
cy.get(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
|
||||
@@ -130,7 +130,7 @@ context(
|
||||
createRawProposal();
|
||||
});
|
||||
|
||||
it.skip('Creating a proposal - proposal rejected - when closing time sooner than system default', function () {
|
||||
it('Creating a proposal - proposal rejected - when closing time sooner than system default', function () {
|
||||
goToMakeNewProposal(governanceProposalType.FREEFORM);
|
||||
enterUniqueFreeFormProposalBody('0.1', generateFreeFormProposalTitle());
|
||||
cy.get('input:invalid')
|
||||
@@ -138,7 +138,7 @@ context(
|
||||
.should('equal', 'Value must be greater than or equal to 1.');
|
||||
});
|
||||
|
||||
it.skip('Creating a proposal - proposal rejected - when closing time later than system default', function () {
|
||||
it('Creating a proposal - proposal rejected - when closing time later than system default', function () {
|
||||
goToMakeNewProposal(governanceProposalType.FREEFORM);
|
||||
enterUniqueFreeFormProposalBody(
|
||||
'100000',
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
getDownloadedProposalJsonPath,
|
||||
getProposalFromTitle,
|
||||
submitUniqueRawProposal,
|
||||
} from '../../support/governance.functions';
|
||||
import {
|
||||
@@ -55,7 +56,8 @@ const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const proposalTermsSection = 'proposal';
|
||||
const proposalJsonToggle = 'proposal-json-toggle';
|
||||
const proposalJsonSection = 'proposal-json';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const fUSDCId =
|
||||
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc';
|
||||
@@ -186,7 +188,7 @@ context(
|
||||
cy.get(maxVoteDeadline).click();
|
||||
cy.get(enactmentDeadlineError).should(
|
||||
'have.text',
|
||||
'Proposal will fail if enactment is earlier than the voting deadline'
|
||||
'The proposal will fail if enactment is earlier than the voting deadline'
|
||||
);
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
@@ -210,6 +212,7 @@ context(
|
||||
'Able to submit valid new market proposal',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
const proposalTitle = 'Test new market proposal';
|
||||
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -231,6 +234,23 @@ context(
|
||||
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
|
||||
});
|
||||
});
|
||||
navigateTo(navigation.proposals);
|
||||
getProposalFromTitle(proposalTitle).within(() =>
|
||||
cy.getByTestId('view-proposal-btn').click()
|
||||
);
|
||||
cy.getByTestId('proposal-market-data').within(() => {
|
||||
cy.getByTestId('proposal-market-data-toggle').click();
|
||||
cy.contains('Key details').click();
|
||||
getMarketProposalDetailsFromTable('Name').should(
|
||||
'have.text',
|
||||
'Token test market'
|
||||
);
|
||||
cy.contains('Settlement asset').click();
|
||||
// Settlement asset symbol
|
||||
cy.getByTestId('3_value').should('have.text', 'fBTC');
|
||||
cy.contains('Oracle').click();
|
||||
cy.getByTestId('oracle-spec-links').should('have.attr', 'href');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -454,8 +474,8 @@ context(
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
cy.getByTestId('proposal-terms-toggle').click();
|
||||
cy.getByTestId(proposalTermsSection).within(() => {
|
||||
cy.getByTestId(proposalJsonToggle).click();
|
||||
cy.getByTestId(proposalJsonSection).within(() => {
|
||||
cy.contains('USDT Coin').should('be.visible');
|
||||
cy.contains('USDT').should('be.visible');
|
||||
});
|
||||
@@ -501,13 +521,11 @@ context(
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
// 3001-VOTE-030 3001-VOTE-031
|
||||
cy.getByTestId('proposal-terms-toggle').click();
|
||||
cy.getByTestId('proposal-terms').within(() => {
|
||||
getProposalInformationFromTable('assetId').should('have.text', assetId);
|
||||
getProposalInformationFromTable('lifetimeLimit').should(
|
||||
'have.text',
|
||||
'10'
|
||||
);
|
||||
cy.getByTestId(proposalJsonToggle).click();
|
||||
cy.getByTestId(proposalJsonSection).within(() => {
|
||||
cy.contains(assetId).should('be.visible');
|
||||
cy.contains('lifetimeLimit').should('be.visible');
|
||||
cy.contains('10').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -605,5 +623,13 @@ context(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getMarketProposalDetailsFromTable(heading: string) {
|
||||
return cy
|
||||
.getByTestId('key-value-table-row')
|
||||
.contains(heading)
|
||||
.parent()
|
||||
.siblings();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -58,8 +58,6 @@ context(
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.visit('/');
|
||||
ethereumWalletConnect();
|
||||
// this is a workaround for #2422 which can be removed once issue is resolved
|
||||
cy.associateTokensToVegaWallet('4');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
navigation,
|
||||
verifyPageHeader,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
clickOnValidatorFromList,
|
||||
waitForBeginningOfEpoch,
|
||||
} from '../../support/staking.functions';
|
||||
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
|
||||
|
||||
const guideLink = '[data-testid="staking-guide-link"]';
|
||||
const validatorTitle = '[data-testid="validator-node-title"]';
|
||||
@@ -30,7 +32,7 @@ const normalisedVotingPowerToolTip =
|
||||
'[data-testid="normalised-voting-power-tooltip"]';
|
||||
const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
|
||||
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
|
||||
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
|
||||
const multisigPenaltyToolTip = '[data-testid="multisig-error-tooltip"]';
|
||||
const epochCountDown = '[data-testid="epoch-countdown"]';
|
||||
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
|
||||
|
||||
@@ -143,9 +145,6 @@ context('Validators Page - verify elements on page', function () {
|
||||
cy.get(overstakedPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886
|
||||
cy.get(totalPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Total penalties: 60.00%');
|
||||
});
|
||||
|
||||
it('Should be able to see validator pending stake', function () {
|
||||
@@ -155,6 +154,22 @@ context('Validators Page - verify elements on page', function () {
|
||||
cy.wrap($pendingStake).should('contain.text', '0.00');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to see multisig error', function () {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'PreviousEpoch', previousEpochData);
|
||||
});
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId('total-penalty').first().realHover();
|
||||
cy.get(multisigPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Multisig penalty: 100%');
|
||||
|
||||
cy.getByTestId('total-penalty').eq(1).realHover();
|
||||
cy.get(multisigPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Multisig penalty: 100%');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -287,28 +287,28 @@ context(
|
||||
name: 'USDC (fake)',
|
||||
symbol: 'fUSDC',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
expectedAmount: 10.0,
|
||||
},
|
||||
{
|
||||
id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665',
|
||||
name: 'DAI (fake)',
|
||||
symbol: 'fDAI',
|
||||
amount: '200000',
|
||||
expectedAmount: '2.00',
|
||||
expectedAmount: 2.0,
|
||||
},
|
||||
{
|
||||
id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0',
|
||||
name: 'BTC (fake)',
|
||||
symbol: 'fBTC',
|
||||
amount: '600000',
|
||||
expectedAmount: '6.00',
|
||||
expectedAmount: 6.0,
|
||||
},
|
||||
{
|
||||
id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567',
|
||||
name: 'EURO (fake)',
|
||||
symbol: 'fEURO',
|
||||
amount: '800000',
|
||||
expectedAmount: '8.00',
|
||||
expectedAmount: 8.0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -319,12 +319,6 @@ context(
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.at.least',
|
||||
5
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
for (const { name, symbol, expectedAmount } of assets) {
|
||||
@@ -338,10 +332,10 @@ context(
|
||||
.contains(name)
|
||||
.parent()
|
||||
.siblings()
|
||||
.invoke('text')
|
||||
.should('have.length.at.least', 4)
|
||||
.then(parseFloat)
|
||||
.should('be.gte', parseFloat(expectedAmount));
|
||||
.then((elementAmount) => {
|
||||
const displayedAmount = parseFloat(elementAmount.text());
|
||||
expect(displayedAmount).be.gte(expectedAmount);
|
||||
});
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(name)
|
||||
|
||||
@@ -37,7 +37,7 @@ export function navigateTo(page: navigation) {
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(page).eq(0).click();
|
||||
cy.get(page).eq(0).click({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,6 +786,7 @@
|
||||
"performancePenalty": "Performance penalty",
|
||||
"overstaked": "Overstaked",
|
||||
"overstakedPenalty": "Overstaked penalty",
|
||||
"multisigPenalty": "Multisig penalty",
|
||||
"homeProposalsIntro": "Decisions on the Vega network are on-chain, with tokenholders creating proposals that other tokenholders vote to approve or reject. Network upgrades are proposed and approved by validators.",
|
||||
"homeProposalsButtonText": "Browse, vote, and propose",
|
||||
"homeValidatorsIntro": "Vega runs on a delegated proof of stake blockchain, where validators earn fees for validating block transactions. Tokenholders can nominate validators by staking tokens to them.",
|
||||
|
||||
+8
@@ -36,6 +36,7 @@ import {
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { VALIDATOR_LOGO_MAP } from './logo-map';
|
||||
import { getMultisigStatusInfo } from '../../../../lib/get-multisig-status-info';
|
||||
|
||||
interface CanonisedConsensusNodeProps {
|
||||
id: string;
|
||||
@@ -137,6 +138,10 @@ export const ConsensusValidatorsTable = ({
|
||||
[totalStake]
|
||||
);
|
||||
|
||||
const multisigStatus = previousEpochData
|
||||
? getMultisigStatusInfo(previousEpochData)
|
||||
: undefined;
|
||||
|
||||
const allNodesInPreviousEpoch = removePaginationWrapper(
|
||||
previousEpochData?.epoch.validatorsConnection?.edges
|
||||
);
|
||||
@@ -223,6 +228,8 @@ export const ConsensusValidatorsTable = ({
|
||||
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare
|
||||
? formatNumberPercentage(new BigNumber(userStakeShare), 2)
|
||||
: undefined,
|
||||
[ValidatorFields.MULTISIG_ERROR]:
|
||||
multisigStatus?.showMultisigStatusError,
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -332,6 +339,7 @@ export const ConsensusValidatorsTable = ({
|
||||
data,
|
||||
decimals,
|
||||
hideTopThird,
|
||||
multisigStatus?.showMultisigStatusError,
|
||||
previousEpochData,
|
||||
thirdOfTotalStake,
|
||||
validatorsView,
|
||||
|
||||
@@ -39,6 +39,7 @@ export enum ValidatorFields {
|
||||
STAKED_BY_USER = 'stakedByUser',
|
||||
PENDING_USER_STAKE = 'pendingUserStake',
|
||||
USER_STAKE_SHARE = 'userStakeShare',
|
||||
MULTISIG_ERROR = 'multisigError',
|
||||
}
|
||||
|
||||
export const addUserDataToValidator = (
|
||||
@@ -326,6 +327,7 @@ interface TotalPenaltiesRendererProps {
|
||||
overstakedAmount: string;
|
||||
overstakingPenalty: string;
|
||||
totalPenalties: string;
|
||||
multisigError?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -344,10 +346,11 @@ export const TotalPenaltiesRenderer = ({
|
||||
<div data-testid="overstaked-penalty-tooltip">
|
||||
{t('overstakedPenalty')}: {data.overstakingPenalty}
|
||||
</div>
|
||||
<div data-testid="total-penalty-tooltip">
|
||||
{t('totalPenalties')}:{' '}
|
||||
<span className="font-bold">{data.totalPenalties}</span>
|
||||
</div>
|
||||
{data.multisigError && (
|
||||
<div data-testid="multisig-error-tooltip">
|
||||
{t('multisigPenalty')}: 100%
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
describe('charts', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('Depth').click();
|
||||
});
|
||||
|
||||
it('can see market depth chart', () => {
|
||||
// 6006-DEPC-001
|
||||
cy.getByTestId('tab-depth').should('be.visible');
|
||||
cy.get('.depth-chart-module_canvas__260De').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -182,7 +182,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
.contains('Liquidity monitoring parameters')
|
||||
.click();
|
||||
|
||||
validateMarketDataRow(0, 'Triggering Ratio', '0');
|
||||
validateMarketDataRow(0, 'Triggering Ratio', '0.7');
|
||||
validateMarketDataRow(1, 'Time Window', '3,600');
|
||||
validateMarketDataRow(2, 'Scaling Factor', '10');
|
||||
});
|
||||
|
||||
@@ -81,6 +81,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||
|
||||
cy.get(rowSelector).first().find(colFee).should('have.text', '0.09%');
|
||||
|
||||
// 5002-LIQP-013
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colAverageEntryValuation)
|
||||
@@ -165,7 +166,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('can see liquidity supplied', () => {
|
||||
//// 5002-LIQP-008
|
||||
// 5002-LIQP-008
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId('liquidity-supplied').within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Liquidity supplied');
|
||||
@@ -237,6 +238,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
.find(colFee)
|
||||
.should('have.text', '0.09%');
|
||||
|
||||
// 5002-LIQP-013
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colAverageEntryValuation)
|
||||
@@ -268,7 +270,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('renders liquidity inactive table correctly', () => {
|
||||
//// 5002-LIQP-012
|
||||
// 5002-LIQP-012
|
||||
cy.getByTestId('Inactive').click();
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
|
||||
@@ -128,6 +128,7 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
cy.wrap(btn).click();
|
||||
});
|
||||
}
|
||||
cy.contains('Loading...').should('not.exist');
|
||||
});
|
||||
// 7001-COLL-010
|
||||
it('sorting by asset', () => {
|
||||
@@ -146,12 +147,17 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
it('sorting by total', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00',
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
];
|
||||
const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
@@ -188,19 +194,24 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('sorting by total', () => {
|
||||
it('sorting by available', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00',
|
||||
'1,000.01',
|
||||
'1,000.00002',
|
||||
'1,000.00',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
];
|
||||
const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
'total',
|
||||
'available',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { checkSorting } from '@vegaprotocol/cypress';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { checkSorting, aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { marketsDataQuery } from '@vegaprotocol/mock';
|
||||
import { positionsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
@@ -15,13 +14,12 @@ const toastContent = 'toast-content';
|
||||
const tooltipContent = 'tooltip-content';
|
||||
// #endregion
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
it('renders positions on trading page', () => {
|
||||
visitAndClickPositions();
|
||||
// 7004-POSI-001
|
||||
@@ -64,7 +62,14 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
it('rows should be displayed despite errors', () => {
|
||||
const errors = [
|
||||
{
|
||||
@@ -164,8 +169,9 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
);
|
||||
});
|
||||
|
||||
// let elementWidth: number;
|
||||
|
||||
it('Resize column', () => {
|
||||
let elementWidth: number;
|
||||
visitAndClickPositions();
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
@@ -180,29 +186,33 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
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]);
|
||||
});
|
||||
});
|
||||
// This test depends on the previous one
|
||||
it('Has persisted column widths', () => {
|
||||
const width = 400;
|
||||
|
||||
// 7004-POSI-012
|
||||
cy.get('[col-id="marketName"]')
|
||||
.invoke('width')
|
||||
.should('equal', elementWidth);
|
||||
});
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem(
|
||||
'vega_positions_store',
|
||||
JSON.stringify({
|
||||
state: {
|
||||
gridStore: {
|
||||
columnState: [{ colId: 'marketName', width }],
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
visitAndClickPositions();
|
||||
|
||||
// 7004-POSI-012
|
||||
cy.get('.ag-center-cols-container .ag-row')
|
||||
.first()
|
||||
.find('[col-id="marketName"]')
|
||||
.invoke('outerWidth')
|
||||
.should('equal', width);
|
||||
});
|
||||
|
||||
it('Scroll horizontally', () => {
|
||||
@@ -291,6 +301,7 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
cy.getByTestId(dialogContent).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
function validatePositionsDisplayed(multiKey = false) {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions')
|
||||
@@ -326,6 +337,7 @@ function validatePositionsDisplayed(multiKey = false) {
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
|
||||
function assertPNLColor(
|
||||
pnlSelector: string,
|
||||
positiveClass: string,
|
||||
@@ -347,6 +359,7 @@ function assertPNLColor(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function visitAndClickPositions() {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(positions).click();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import {
|
||||
matchFilter,
|
||||
liquidityProvisionsDataProvider,
|
||||
LiquidityTable,
|
||||
lpAggregatedDataProvider,
|
||||
useCheckLiquidityStatus,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
@@ -24,18 +22,16 @@ import {
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
|
||||
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
|
||||
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
import type { Filter } from '@vegaprotocol/liquidity';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
import { useMarket, useStaticMarketData } from '@vegaprotocol/markets';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
|
||||
const enum LiquidityTabs {
|
||||
Active = 'active',
|
||||
@@ -49,62 +45,6 @@ export const Liquidity = () => {
|
||||
return <LiquidityViewContainer marketId={marketId} />;
|
||||
};
|
||||
|
||||
const useReloadLiquidityData = (marketId: string | undefined) => {
|
||||
const { reload } = useDataProvider({
|
||||
dataProvider: liquidityProvisionsDataProvider,
|
||||
variables: { marketId: marketId || '' },
|
||||
update: () => true,
|
||||
skip: !marketId,
|
||||
});
|
||||
useEffect(() => {
|
||||
const interval = setInterval(reload, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [reload]);
|
||||
};
|
||||
|
||||
export const LiquidityContainer = ({
|
||||
marketId,
|
||||
filter,
|
||||
}: {
|
||||
marketId: string | undefined;
|
||||
filter?: Filter;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data: market } = useMarket(marketId);
|
||||
|
||||
// To be removed when liquidityProvision subscriptions are working
|
||||
useReloadLiquidityData(marketId);
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: lpAggregatedDataProvider,
|
||||
variables: { marketId: marketId || '', filter },
|
||||
skip: !marketId,
|
||||
});
|
||||
|
||||
const assetDecimalPlaces =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
|
||||
const symbol =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.market_liquidity_stakeToCcyVolume,
|
||||
]);
|
||||
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LiquidityTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
stakeToCcyVolume={stakeToCcyVolume}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No data')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
|
||||
const { data: market } = useMarket(marketId);
|
||||
const { data: marketData } = useStaticMarketData(marketId);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@@ -184,7 +184,6 @@ const MarketList = ({
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
@@ -204,6 +203,29 @@ const MarketList = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface ListItemData {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
onSelect?: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}
|
||||
|
||||
const ListItem = ({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}: {
|
||||
index: number;
|
||||
style: CSSProperties;
|
||||
data: ListItemData;
|
||||
}) => (
|
||||
<MarketSelectorItem
|
||||
market={data.data[index]}
|
||||
currentMarketId={data.currentMarketId}
|
||||
style={style}
|
||||
onSelect={data.onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
const List = ({
|
||||
data,
|
||||
loading,
|
||||
@@ -212,28 +234,20 @@ const List = ({
|
||||
onSelect,
|
||||
noItems,
|
||||
currentMarketId,
|
||||
}: {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
}: ListItemData & {
|
||||
loading: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
noItems: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}) => {
|
||||
const row = ({ index, style }: { index: number; style: CSSProperties }) => {
|
||||
const market = data[index];
|
||||
|
||||
return (
|
||||
<MarketSelectorItem
|
||||
market={market}
|
||||
currentMarketId={currentMarketId}
|
||||
style={style}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const itemKey = useCallback(
|
||||
(index: number, data: ListItemData) => data.data[index].id,
|
||||
[]
|
||||
);
|
||||
const itemData = useMemo(
|
||||
() => ({ data, onSelect, currentMarketId }),
|
||||
[data, onSelect, currentMarketId]
|
||||
);
|
||||
if (!data || loading) {
|
||||
return (
|
||||
<div style={{ width, height }}>
|
||||
@@ -259,11 +273,13 @@ const List = ({
|
||||
<FixedSizeList
|
||||
className="virtualized-list"
|
||||
itemCount={data.length}
|
||||
itemData={itemData}
|
||||
itemSize={130}
|
||||
itemKey={itemKey}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
{row}
|
||||
{ListItem}
|
||||
</FixedSizeList>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,10 +19,7 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
useMarketClickHandler,
|
||||
useMarketLiquidityClickHandler,
|
||||
} from '../../lib/hooks/use-market-click-handler';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { HeaderTitle } from '../../components/header';
|
||||
import {
|
||||
@@ -49,7 +46,6 @@ const MarketBottomPanel = memo(
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' });
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
|
||||
return 'xxxl' === screenSize ? (
|
||||
<ResizableGrid
|
||||
@@ -69,10 +65,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketOpenOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -81,10 +73,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketClosedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -93,22 +81,12 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketRejectOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketAllOrders"
|
||||
/>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
@@ -116,7 +94,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="marketFills"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -134,8 +111,6 @@ const MarketBottomPanel = memo(
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
storeKey="marketPositions"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -145,7 +120,6 @@ const MarketBottomPanel = memo(
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
storeKey="marketCollateral"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -158,10 +132,7 @@ const MarketBottomPanel = memo(
|
||||
<Tabs storageKey="console-trade-grid-bottom">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="marketPositions"
|
||||
/>
|
||||
<TradingViews.positions.component onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
@@ -169,10 +140,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketOpenOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -181,10 +148,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketClosedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -193,22 +156,12 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketRejectedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketAllOrders"
|
||||
/>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
@@ -216,7 +169,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="marketFills"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -226,7 +178,6 @@ const MarketBottomPanel = memo(
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
storeKey="marketCollateral"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
|
||||
@@ -39,7 +39,7 @@ export const TradePanels = ({
|
||||
}: TradePanelsProps) => {
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
const renderView = () => {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||
import { MarketInfoAccordionContainer } from '@vegaprotocol/markets';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
import { OrderListContainer, Filter } from '@vegaprotocol/orders';
|
||||
import type { OrderListContainerProps } from '@vegaprotocol/orders';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { NO_MARKET } from './constants';
|
||||
import { LiquidityContainer } from '../liquidity/liquidity';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import type { OrderContainerProps } from '../../components/orders-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
|
||||
type MarketDependantView =
|
||||
| typeof CandlesChartContainer
|
||||
@@ -65,25 +66,25 @@ export const TradingViews = {
|
||||
positions: { label: 'Positions', component: PositionsContainer },
|
||||
activeOrders: {
|
||||
label: 'Active',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Open} />
|
||||
component: (props: OrderContainerProps) => (
|
||||
<OrdersContainer {...props} filter={Filter.Open} />
|
||||
),
|
||||
},
|
||||
closedOrders: {
|
||||
label: 'Closed',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Closed} />
|
||||
component: (props: OrderContainerProps) => (
|
||||
<OrdersContainer {...props} filter={Filter.Closed} />
|
||||
),
|
||||
},
|
||||
rejectedOrders: {
|
||||
label: 'Rejected',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Rejected} />
|
||||
component: (props: OrderContainerProps) => (
|
||||
<OrdersContainer {...props} filter={Filter.Rejected} />
|
||||
),
|
||||
},
|
||||
orders: {
|
||||
label: 'All',
|
||||
component: OrderListContainer,
|
||||
component: OrdersContainer,
|
||||
},
|
||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||
fills: { label: 'Fills', component: FillsContainer },
|
||||
|
||||
@@ -313,7 +313,6 @@ const ClosedMarketsDataGrid = ({
|
||||
minWidth: 100,
|
||||
}}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
storeKey="closedMarkets"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useRef } from 'react';
|
||||
@@ -17,17 +16,13 @@ export const DepositsContainer = () => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
const openDepositDialog = useDepositDialog((state) => state.open);
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className="h-full relative">
|
||||
<DepositsTable
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
</div>
|
||||
<DepositsTable
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
{!isReadOnly && (
|
||||
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
|
||||
<Button
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
|
||||
import { usePaneLayout } from '@vegaprotocol/react-helpers';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { DepositsContainer } from './deposits-container';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { LedgerContainer } from '@vegaprotocol/ledger';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { DepositsContainer } from './deposits-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
import { AccountHistoryContainer } from './account-history-container';
|
||||
import {
|
||||
useMarketClickHandler,
|
||||
useMarketLiquidityClickHandler,
|
||||
} from '../../lib/hooks/use-market-click-handler';
|
||||
import {
|
||||
ResizableGrid,
|
||||
ResizableGridPanel,
|
||||
} from '../../components/resizable-grid';
|
||||
|
||||
const WithdrawalsIndicator = () => {
|
||||
const { ready } = useIncompleteWithdrawals();
|
||||
if (!ready || ready.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="bg-vega-blue-450 text-white text-[10px] rounded p-[3px] pb-[2px] leading-none">
|
||||
{ready.length}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const Portfolio = () => {
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
@@ -34,7 +44,6 @@ export const Portfolio = () => {
|
||||
}, [updateTitle]);
|
||||
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||
const wrapperClasses = 'h-full max-h-full flex flex-col';
|
||||
return (
|
||||
@@ -50,29 +59,17 @@ export const Portfolio = () => {
|
||||
</Tab>
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<PositionsContainer
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
storeKey="portfolioPositions"
|
||||
allKeys
|
||||
/>
|
||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<VegaWalletContainer>
|
||||
<OrderListContainer
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
storeKey="portfolioOrders"
|
||||
/>
|
||||
<OrdersContainer />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<FillsContainer
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="portfolioFills"
|
||||
/>
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
@@ -92,10 +89,7 @@ export const Portfolio = () => {
|
||||
<Tabs storageKey="console-portfolio-bottom">
|
||||
<Tab id="collateral" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountsContainer
|
||||
storeKey="portfolioCollateral"
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
<AccountsContainer />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="deposits" name={t('Deposits')}>
|
||||
@@ -103,7 +97,11 @@ export const Portfolio = () => {
|
||||
<DepositsContainer />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="withdrawals" name={t('Withdrawals')}>
|
||||
<Tab
|
||||
id="withdrawals"
|
||||
name={t('Withdrawals')}
|
||||
indicator={<WithdrawalsIndicator />}
|
||||
>
|
||||
<WithdrawalsContainer />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
withdrawalProvider,
|
||||
useWithdrawalDialog,
|
||||
WithdrawalsTable,
|
||||
useIncompleteWithdrawals,
|
||||
} from '@vegaprotocol/withdraws';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -17,6 +18,7 @@ export const WithdrawalsContainer = () => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
|
||||
const { ready, delayed } = useIncompleteWithdrawals();
|
||||
|
||||
return (
|
||||
<VegaWalletContainer>
|
||||
@@ -25,6 +27,8 @@ export const WithdrawalsContainer = () => {
|
||||
data-testid="withdrawals-history"
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
|
||||
ready={ready}
|
||||
delayed={delayed}
|
||||
/>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
|
||||
@@ -8,16 +8,19 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
|
||||
import { useDepositDialog } from '@vegaprotocol/deposits';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
|
||||
export const AccountsContainer = ({
|
||||
pinnedAsset,
|
||||
hideButtons,
|
||||
storeKey,
|
||||
onMarketClick,
|
||||
}: {
|
||||
pinnedAsset?: PinnedAsset;
|
||||
hideButtons?: boolean;
|
||||
storeKey?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
@@ -26,6 +29,12 @@ export const AccountsContainer = ({
|
||||
const openDepositDialog = useDepositDialog((store) => store.open);
|
||||
const openTransferDialog = useTransferDialog((store) => store.open);
|
||||
|
||||
const gridStore = useAccountStore((store) => store.gridStore);
|
||||
const updateGridStore = useAccountStore((store) => store.updateGridStore);
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
});
|
||||
|
||||
const onClickAsset = useCallback(
|
||||
(assetId?: string) => {
|
||||
assetId && openAssetDetailsDialog(assetId);
|
||||
@@ -51,7 +60,7 @@ export const AccountsContainer = ({
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
pinnedAsset={pinnedAsset}
|
||||
storeKey={storeKey}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
{!isReadOnly && !hideButtons && (
|
||||
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
|
||||
@@ -75,3 +84,9 @@ export const AccountsContainer = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useAccountStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_accounts_store',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { DefaultWeb3ProviderContextShape } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEthereumConfig,
|
||||
createConnectors,
|
||||
Web3Provider as Web3ProviderInternal,
|
||||
useWeb3ConnectStore,
|
||||
createDefaultProvider,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
@@ -17,10 +20,13 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
const connectors = useWeb3ConnectStore((store) => store.connectors);
|
||||
const initializeConnectors = useWeb3ConnectStore((store) => store.initialize);
|
||||
const [defaultProvider, setDefaultProvider] = useState<
|
||||
DefaultWeb3ProviderContextShape['provider'] | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (config?.chain_id) {
|
||||
return initializeConnectors(
|
||||
initializeConnectors(
|
||||
createConnectors(
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
Number(config?.chain_id),
|
||||
@@ -29,6 +35,11 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
),
|
||||
Number(config.chain_id)
|
||||
);
|
||||
const defaultProvider = createDefaultProvider(
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
Number(config?.chain_id)
|
||||
);
|
||||
setDefaultProvider(defaultProvider);
|
||||
}
|
||||
}, [
|
||||
config?.chain_id,
|
||||
@@ -49,7 +60,10 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
}}
|
||||
noDataMessage={t('Could not fetch Ethereum configuration')}
|
||||
>
|
||||
<Web3ProviderInternal connectors={connectors}>
|
||||
<Web3ProviderInternal
|
||||
connectors={connectors}
|
||||
defaultProvider={defaultProvider}
|
||||
>
|
||||
<>{children}</>
|
||||
</Web3ProviderInternal>
|
||||
</AsyncRenderer>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { FillsManager } from '@vegaprotocol/fills';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
|
||||
export const FillsContainer = ({
|
||||
marketId,
|
||||
onMarketClick,
|
||||
}: {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const gridStore = useFillsStore((store) => store.gridStore);
|
||||
const updateGridStore = useFillsStore((store) => store.updateGridStore);
|
||||
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
});
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Please connect Vega wallet')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FillsManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const useFillsStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_fills_store',
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './fills-container';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ledger-container';
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { LedgerManager } from '@vegaprotocol/ledger';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export const LedgerContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const gridStore = useLedgerStore((store) => store.gridStore);
|
||||
const updateGridStore = useLedgerStore((store) => store.updateGridStore);
|
||||
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
});
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Please connect Vega wallet')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return <LedgerManager partyId={pubKey} gridProps={gridStoreCallbacks} />;
|
||||
};
|
||||
|
||||
const useLedgerStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_ledger_store',
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './liquidity-container';
|
||||
@@ -0,0 +1,93 @@
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
lpAggregatedDataProvider,
|
||||
type Filter,
|
||||
LiquidityTable,
|
||||
liquidityProvisionsDataProvider,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export const LiquidityContainer = ({
|
||||
marketId,
|
||||
filter,
|
||||
}: {
|
||||
marketId: string | undefined;
|
||||
filter?: Filter;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
|
||||
const gridStore = useLiquidityStore((store) => store.gridStore);
|
||||
const updateGridStore = useLiquidityStore((store) => store.updateGridStore);
|
||||
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
});
|
||||
const { data: market } = useMarket(marketId);
|
||||
|
||||
// To be removed when liquidityProvision subscriptions are working
|
||||
useReloadLiquidityData(marketId);
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: lpAggregatedDataProvider,
|
||||
variables: { marketId: marketId || '', filter },
|
||||
skip: !marketId,
|
||||
});
|
||||
|
||||
const assetDecimalPlaces =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
|
||||
const quantum =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0;
|
||||
const symbol =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.market_liquidity_stakeToCcyVolume,
|
||||
]);
|
||||
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LiquidityTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
quantum={quantum}
|
||||
stakeToCcyVolume={stakeToCcyVolume}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No data')}
|
||||
{...gridStoreCallbacks}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useReloadLiquidityData = (marketId: string | undefined) => {
|
||||
const { reload } = useDataProvider({
|
||||
dataProvider: liquidityProvisionsDataProvider,
|
||||
variables: { marketId: marketId || '' },
|
||||
update: () => true,
|
||||
skip: !marketId,
|
||||
});
|
||||
useEffect(() => {
|
||||
const interval = setInterval(reload, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [reload]);
|
||||
};
|
||||
|
||||
const useLiquidityStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_ledger_store',
|
||||
})
|
||||
);
|
||||
@@ -125,14 +125,19 @@ export const MarketLiquiditySupplied = ({
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
<br />
|
||||
<Link href={`/#/liquidity/${marketId}`} data-testid="view-liquidity-link">
|
||||
{t('View liquidity provision table')}
|
||||
</Link>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.LIQUIDITY} className="mt-2">
|
||||
{t('Learn about providing liquidity')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Link
|
||||
href={`/#/liquidity/${marketId}`}
|
||||
data-testid="view-liquidity-link"
|
||||
>
|
||||
{t('View liquidity provision table')}
|
||||
</Link>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.LIQUIDITY} className="mt-2">
|
||||
{t('Learn about providing liquidity')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</div>
|
||||
{showMessage && (
|
||||
<p className="mt-4">
|
||||
{t(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './orders-container';
|
||||
@@ -0,0 +1,106 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import {
|
||||
FilterStatusValue,
|
||||
STORAGE_KEY,
|
||||
useOrderListGridState,
|
||||
} from './orders-container';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { OrderType } from '@vegaprotocol/types';
|
||||
|
||||
describe('useOrderListGridState', () => {
|
||||
afterAll(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
const setup = (filter: Filter | undefined) => {
|
||||
return renderHook(() => useOrderListGridState(filter));
|
||||
};
|
||||
|
||||
it.each(Object.values(Filter))(
|
||||
'providers correct AgGrid filter for %s',
|
||||
(filter) => {
|
||||
const { result } = setup(filter);
|
||||
expect(typeof result.current.updateGridState).toBe('function');
|
||||
expect(result.current.gridState).toEqual({
|
||||
columnState: undefined,
|
||||
filterModel: {
|
||||
status: {
|
||||
value: FilterStatusValue[filter],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('provides correct AgGrid filter for all', () => {
|
||||
const { result } = setup(undefined);
|
||||
expect(typeof result.current.updateGridState).toBe('function');
|
||||
expect(result.current.gridState).toEqual({
|
||||
columnState: undefined,
|
||||
filterModel: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(Object.values(Filter))(
|
||||
'sets and stores column state and filters for %s',
|
||||
(filter) => {
|
||||
const filterModel = {
|
||||
type: {
|
||||
value: [OrderType.TYPE_LIMIT],
|
||||
},
|
||||
};
|
||||
const { result } = setup(filter);
|
||||
|
||||
act(() => {
|
||||
result.current.updateGridState(filter, {
|
||||
filterModel,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.gridState).toEqual({
|
||||
columnState: undefined,
|
||||
filterModel: {
|
||||
...filterModel,
|
||||
status: {
|
||||
value: FilterStatusValue[filter],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const columnState = [{ colId: 'status', width: 200 }];
|
||||
|
||||
act(() => {
|
||||
result.current.updateGridState(filter, {
|
||||
columnState,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.gridState).toEqual({
|
||||
columnState,
|
||||
filterModel: {
|
||||
...filterModel,
|
||||
status: {
|
||||
value: FilterStatusValue[filter],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const storeKeyMap = {
|
||||
[Filter.Open]: 'open',
|
||||
[Filter.Rejected]: 'rejected',
|
||||
[Filter.Closed]: 'closed',
|
||||
};
|
||||
|
||||
expect(JSON.parse(localStorage.getItem(STORAGE_KEY) || '')).toMatchObject(
|
||||
{
|
||||
state: {
|
||||
[storeKeyMap[filter]]: {
|
||||
columnState,
|
||||
filterModel, // no need to check that status is set, hook will return status
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { OrderListManager } from '@vegaprotocol/orders';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useMarketClickHandler,
|
||||
useMarketLiquidityClickHandler,
|
||||
} from '../../lib/hooks/use-market-click-handler';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { DataGridStore } from '../../stores/datagrid-store-slice';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
|
||||
export const FilterStatusValue = {
|
||||
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
[Filter.Closed]: [
|
||||
OrderStatus.STATUS_CANCELLED,
|
||||
OrderStatus.STATUS_EXPIRED,
|
||||
OrderStatus.STATUS_FILLED,
|
||||
OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
OrderStatus.STATUS_STOPPED,
|
||||
],
|
||||
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
|
||||
};
|
||||
|
||||
export interface OrderContainerProps {
|
||||
marketId?: string;
|
||||
filter?: Filter;
|
||||
}
|
||||
|
||||
export const OrdersContainer = ({ marketId, filter }: OrderContainerProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
const { gridState, updateGridState } = useOrderListGridState(filter);
|
||||
const gridStoreCallbacks = useDataGridEvents(gridState, (newState) => {
|
||||
updateGridState(filter, newState);
|
||||
});
|
||||
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
|
||||
return (
|
||||
<OrderListManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
filter={filter}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
isReadOnly={isReadOnly}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const STORAGE_KEY = 'vega_order_list_store';
|
||||
const useOrderListStore = create<{
|
||||
open: DataGridStore;
|
||||
closed: DataGridStore;
|
||||
rejected: DataGridStore;
|
||||
all: DataGridStore;
|
||||
update: (filter: Filter | undefined, gridStore: DataGridStore) => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
open: {},
|
||||
closed: {},
|
||||
rejected: {},
|
||||
all: {},
|
||||
update: (filter, newStore) => {
|
||||
switch (filter) {
|
||||
case Filter.Open: {
|
||||
set((curr) => ({
|
||||
open: {
|
||||
...curr.open,
|
||||
...newStore,
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
case Filter.Closed: {
|
||||
set((curr) => ({
|
||||
closed: {
|
||||
...curr.closed,
|
||||
...newStore,
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
case Filter.Rejected: {
|
||||
set((curr) => ({
|
||||
rejected: {
|
||||
...curr.rejected,
|
||||
...newStore,
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
case undefined: {
|
||||
set((curr) => ({
|
||||
all: {
|
||||
...curr.all,
|
||||
...newStore,
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const useOrderListGridState = (filter: Filter | undefined) => {
|
||||
const updateGridState = useOrderListStore((store) => store.update);
|
||||
const gridState = useOrderListStore((store) => {
|
||||
// Return the column/filter state for the given filter but ensuring that
|
||||
// each filter controlled by the tab is always applied
|
||||
switch (filter) {
|
||||
case Filter.Open: {
|
||||
return {
|
||||
columnState: store.open.columnState,
|
||||
filterModel: {
|
||||
...store.open.filterModel,
|
||||
status: {
|
||||
value: FilterStatusValue[Filter.Open],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case Filter.Closed: {
|
||||
return {
|
||||
columnState: store.closed.columnState,
|
||||
filterModel: {
|
||||
...store.closed.filterModel,
|
||||
status: {
|
||||
value: FilterStatusValue[Filter.Closed],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case Filter.Rejected: {
|
||||
return {
|
||||
columnState: store.rejected.columnState,
|
||||
filterModel: {
|
||||
...store.rejected.filterModel,
|
||||
status: {
|
||||
value: FilterStatusValue[Filter.Rejected],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return store.all;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { gridState, updateGridState };
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './positions-container';
|
||||
+19
-7
@@ -1,21 +1,28 @@
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { PositionsManager } from '@vegaprotocol/positions';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { PositionsManager } from './positions-manager';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export const PositionsContainer = ({
|
||||
onMarketClick,
|
||||
noBottomPlaceholder,
|
||||
storeKey,
|
||||
allKeys,
|
||||
}: {
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
noBottomPlaceholder?: boolean;
|
||||
storeKey?: string;
|
||||
allKeys?: boolean;
|
||||
}) => {
|
||||
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
|
||||
|
||||
const gridStore = usePositionsStore((store) => store.gridStore);
|
||||
const updateGridStore = usePositionsStore((store) => store.updateGridStore);
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
});
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
@@ -38,8 +45,13 @@ export const PositionsContainer = ({
|
||||
partyIds={partyIds}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
noBottomPlaceholder={noBottomPlaceholder}
|
||||
storeKey={storeKey}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const usePositionsStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_positions_store',
|
||||
})
|
||||
);
|
||||
@@ -20,20 +20,8 @@ export const useMarketClickHandler = (replace = false) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const useMarketLiquidityClickHandler = (replace = false) => {
|
||||
const navigate = useNavigate();
|
||||
const { marketId } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const isLiquidityPage = pathname.match(/^\/liquidity\/(.+)/);
|
||||
return useCallback(
|
||||
(selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links[Routes.LIQUIDITY](selectedId);
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else if (selectedId !== marketId || !isLiquidityPage) {
|
||||
navigate(link, { replace });
|
||||
}
|
||||
},
|
||||
[navigate, marketId, replace, isLiquidityPage]
|
||||
);
|
||||
export const useMarketLiquidityClickHandler = () => {
|
||||
return useCallback((selectedId: string, metaKey?: boolean) => {
|
||||
window.open(`/#/liquidity/${selectedId}`, metaKey ? '_blank' : '_self');
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -3,12 +3,17 @@ import { useUpdateNetworkParametersToasts } from '@vegaprotocol/proposals';
|
||||
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
|
||||
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
|
||||
import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
|
||||
import { Routes } from './client-router';
|
||||
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
|
||||
|
||||
export const ToastsManager = () => {
|
||||
useUpdateNetworkParametersToasts();
|
||||
useVegaTransactionToasts();
|
||||
useEthereumTransactionToasts();
|
||||
useEthereumWithdrawApprovalsToasts();
|
||||
useReadyToWithdrawalToasts({
|
||||
withdrawalsLink: `${Routes.PORTFOLIO}`,
|
||||
});
|
||||
|
||||
const toasts = useToasts((store) => store.toasts);
|
||||
return <ToastsContainer order="desc" toasts={toasts} />;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ColumnState } from 'ag-grid-community';
|
||||
import type { StateCreator } from 'zustand';
|
||||
|
||||
export type DataGridStore = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
filterModel?: { [key: string]: any };
|
||||
columnState?: ColumnState[];
|
||||
};
|
||||
|
||||
export type DataGridSlice = {
|
||||
gridStore: DataGridStore;
|
||||
updateGridStore: (gridStore: DataGridStore) => void;
|
||||
};
|
||||
|
||||
export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
|
||||
gridStore: {},
|
||||
updateGridStore: (newStore) => {
|
||||
set((curr) => ({
|
||||
gridStore: {
|
||||
...curr.gridStore,
|
||||
...newStore,
|
||||
},
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@testing-library/react';
|
||||
import * as helpers from '@vegaprotocol/data-provider';
|
||||
import { AccountManager } from './accounts-manager';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
|
||||
const mockedUseDataProvider = jest.fn();
|
||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
@@ -14,6 +15,13 @@ jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
useDataProvider: jest.fn(() => mockedUseDataProvider()),
|
||||
}));
|
||||
|
||||
const gridProps = {
|
||||
onGridReady: jest.fn(),
|
||||
onColumnResized: jest.fn(),
|
||||
onFilterChanged: jest.fn(),
|
||||
onSortChanged: jest.fn(),
|
||||
} as unknown as ReturnType<typeof useDataGridEvents>;
|
||||
|
||||
describe('AccountManager', () => {
|
||||
describe('when rerender', () => {
|
||||
beforeEach(() => {
|
||||
@@ -43,6 +51,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
@@ -55,6 +64,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyTwo"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -72,6 +82,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
rerenderer = rerender;
|
||||
@@ -85,6 +96,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -110,6 +122,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { PinnedAsset } from './accounts-table';
|
||||
import { AccountTable } from './accounts-table';
|
||||
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
import BreakdownTable from './breakdown-table';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
|
||||
const AccountBreakdown = ({
|
||||
assetId,
|
||||
@@ -72,7 +73,6 @@ export const AccountBreakdownDialog = memo(
|
||||
onClose: () => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}) => {
|
||||
console.log('render');
|
||||
return (
|
||||
<Dialog
|
||||
size="medium"
|
||||
@@ -103,7 +103,7 @@ interface AccountManagerProps {
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
isReadOnly: boolean;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
storeKey?: string;
|
||||
gridProps?: ReturnType<typeof useDataGridEvents>;
|
||||
}
|
||||
|
||||
export const AccountManager = ({
|
||||
@@ -113,12 +113,11 @@ export const AccountManager = ({
|
||||
partyId,
|
||||
isReadOnly,
|
||||
pinnedAsset,
|
||||
storeKey,
|
||||
onMarketClick,
|
||||
gridProps,
|
||||
}: AccountManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: aggregatedAccountsDataProvider,
|
||||
variables: { partyId },
|
||||
@@ -145,8 +144,8 @@ export const AccountManager = ({
|
||||
onClickBreakdown={setBreakdownAssetId}
|
||||
isReadOnly={isReadOnly}
|
||||
pinnedAsset={pinnedAsset}
|
||||
storeKey={storeKey}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
|
||||
{...gridProps}
|
||||
/>
|
||||
<AccountBreakdownDialog
|
||||
assetId={breakdownAssetId}
|
||||
|
||||
@@ -73,7 +73,6 @@ export interface AccountTableProps extends AgGridReactProps {
|
||||
onClickBreakdown?: (assetId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
storeKey?: string;
|
||||
}
|
||||
|
||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
@@ -307,7 +306,6 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No accounts')}
|
||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
|
||||
@@ -290,6 +290,14 @@ export const TransferFee = ({
|
||||
decimals?: number;
|
||||
}) => {
|
||||
if (!feeFactor || !amount || !transferAmount || !fee) return null;
|
||||
if (
|
||||
isNaN(Number(feeFactor)) ||
|
||||
isNaN(Number(amount)) ||
|
||||
isNaN(Number(transferAmount)) ||
|
||||
isNaN(Number(fee))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { sendVegaTx } from './wallet-client';
|
||||
import { waitForProposal } from './propose-market';
|
||||
import { determineId } from '../utils';
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
|
||||
export async function submitProposal(proposalTx: ProposalSubmissionBody) {
|
||||
cy.highlight('Submitting proposal');
|
||||
const result = await sendVegaTx(vegaPubKey, proposalTx);
|
||||
await waitForProposal(determineId(result.result.transaction.signature.value));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createWalletClient, sendVegaTx } from '../capsule/wallet-client';
|
||||
import { createWalletClient } from '../capsule/wallet-client';
|
||||
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { submitProposal } from '../capsule/submit-proposal';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
@@ -15,11 +16,8 @@ export const addVegaWalletSubmitProposal = () => {
|
||||
Cypress.Commands.add('VegaWalletSubmitProposal', (proposalTx) => {
|
||||
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
|
||||
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
|
||||
createWalletClient(vegaWalletUrl, token);
|
||||
|
||||
cy.highlight('Submitting proposal');
|
||||
sendVegaTx(vegaPubKey, proposalTx);
|
||||
cy.wrap(submitProposal(proposalTx));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -44,27 +44,35 @@ export const checkSorting = (
|
||||
orderTabDesc: string[]
|
||||
) => {
|
||||
checkSortChange(orderTabDefault, column);
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).last().click();
|
||||
});
|
||||
checkSortChange(orderTabAsc, column);
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
checkSortChange(orderTabDesc, column);
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
};
|
||||
|
||||
const checkSortChange = (tabsArr: string[], column: string) => {
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
tabsArr.forEach((entry, i) => {
|
||||
cy.get(`[row-index="${i}"]`).within(() => {
|
||||
cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]);
|
||||
cy.get('.ag-center-cols-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
tabsArr.forEach((entry, i) => {
|
||||
cy.get(`[row-index="${i}"]`).within(() => {
|
||||
cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
type Edges = { node: unknown }[];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './lib/ag-grid/ag-grid-lazy';
|
||||
export * from './lib/ag-grid/use-column-sizes';
|
||||
|
||||
export * from './lib/column-definitions';
|
||||
|
||||
@@ -24,4 +23,4 @@ export * from './lib/type-helpers';
|
||||
export * from './lib/cells/grid-progress-bar';
|
||||
|
||||
export * from './lib/ag-grid-update';
|
||||
export * from './lib/use-bottom-placeholder';
|
||||
export * from './lib/use-datagrid-events';
|
||||
|
||||
@@ -2,23 +2,16 @@ import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useColumnSizes } from './use-column-sizes';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const AgGridThemed = ({
|
||||
style,
|
||||
gridRef,
|
||||
storeKey,
|
||||
...props
|
||||
}: (AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
gridRef?: React.ForwardedRef<AgGridReact>;
|
||||
storeKey?: string;
|
||||
}) => {
|
||||
const commonColumnCallbacks = useColumnSizes({
|
||||
storeKey,
|
||||
props,
|
||||
});
|
||||
const { theme } = useThemeSwitcher();
|
||||
const defaultProps = {
|
||||
rowHeight: 22,
|
||||
@@ -36,12 +29,7 @@ export const AgGridThemed = ({
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses} style={style}>
|
||||
<AgGridReact
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
{...commonColumnCallbacks}
|
||||
ref={gridRef}
|
||||
/>
|
||||
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
|
||||
type Props = AgGridReactProps & {
|
||||
style?: React.CSSProperties;
|
||||
gridRef?: React.Ref<AgGridReact>;
|
||||
storeKey?: string;
|
||||
};
|
||||
|
||||
export const AgGridLazyInternal = lazy(() =>
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import type {
|
||||
Column,
|
||||
ColumnResizedEvent,
|
||||
GridSizeChangedEvent,
|
||||
GridReadyEvent,
|
||||
} from 'ag-grid-community';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useColumnSizes } from './use-column-sizes';
|
||||
import * as reactHelpers from '@vegaprotocol/react-helpers';
|
||||
|
||||
const mockApis = {
|
||||
api: {
|
||||
sizeColumnsToFit: jest.fn(),
|
||||
},
|
||||
columnApi: {
|
||||
setColumnWidths: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const mockValueSetter = jest.fn();
|
||||
const mockStore = {
|
||||
sizes: { testid: { col1: 100 } },
|
||||
valueSetter: mockValueSetter,
|
||||
};
|
||||
jest.mock('zustand', () => ({
|
||||
...jest.requireActual('zustand'),
|
||||
create: () =>
|
||||
jest.fn(() =>
|
||||
jest.fn().mockImplementation((creator) => {
|
||||
return creator(mockStore);
|
||||
})
|
||||
),
|
||||
}));
|
||||
describe('UseColumnSizes hook', () => {
|
||||
const storeKey = 'testid';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should return proper methods', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useColumnSizes({ storeKey, props: {} })
|
||||
);
|
||||
expect(Object.keys(result.current)).toHaveLength(3);
|
||||
expect(result.current).toStrictEqual({
|
||||
onColumnResized: expect.any(Function),
|
||||
onGridReady: expect.any(Function),
|
||||
onGridSizeChanged: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('onGridSizeChanged should call setSize', async () => {
|
||||
jest
|
||||
.spyOn(reactHelpers, 'useScreenDimensions')
|
||||
.mockReturnValue({ screenSize: 'xxl' });
|
||||
const { result } = renderHook(() =>
|
||||
useColumnSizes({ storeKey, props: {} })
|
||||
);
|
||||
await act(() => {
|
||||
result.current.onGridSizeChanged?.({
|
||||
clientWidth: 1000,
|
||||
...mockApis,
|
||||
} as GridSizeChangedEvent);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(mockApis.columnApi.setColumnWidths).toHaveBeenCalledWith([
|
||||
{ key: 'col1', newWidth: 100 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('onColumnResized should fill up store', async () => {
|
||||
const columns: Column[] = [
|
||||
{ getColId: () => 'col1', getActualWidth: () => 100 },
|
||||
{ getColId: () => 'col2', getActualWidth: () => 200 },
|
||||
] as Column[];
|
||||
const sizeObj = { col1: 100, col2: 200, clientWidth: 1000 };
|
||||
const { result } = renderHook(() =>
|
||||
useColumnSizes({ storeKey, props: {} })
|
||||
);
|
||||
await act(() => {
|
||||
result.current.onGridSizeChanged?.({
|
||||
clientWidth: 1000,
|
||||
...mockApis,
|
||||
} as GridSizeChangedEvent);
|
||||
});
|
||||
await act(() => {
|
||||
result.current.onColumnResized?.({
|
||||
columns,
|
||||
finished: true,
|
||||
source: 'uiColumnDragged',
|
||||
...mockApis,
|
||||
} as ColumnResizedEvent);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(mockValueSetter).toHaveBeenCalledWith(storeKey, sizeObj);
|
||||
});
|
||||
});
|
||||
|
||||
it('onGridReady should call setSizes', async () => {
|
||||
const props = { onGridReady: jest.fn() };
|
||||
|
||||
const { result } = renderHook(() => useColumnSizes({ storeKey, props }));
|
||||
const obTest = { cool: 1, ...mockApis };
|
||||
await act(() => {
|
||||
result.current.onGridReady?.(obTest as GridReadyEvent);
|
||||
});
|
||||
expect(props.onGridReady).toHaveBeenCalledWith(obTest);
|
||||
expect(mockApis.api.sizeColumnsToFit).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('if no storeKey should be transparent', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useColumnSizes({ storeKey: '', props: {} })
|
||||
);
|
||||
expect(result.current).toStrictEqual({
|
||||
onColumnResized: undefined,
|
||||
onGridReady: undefined,
|
||||
onGridSizeChanged: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,145 +0,0 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import type {
|
||||
GridSizeChangedEvent,
|
||||
GridReadyEvent,
|
||||
ColumnResizedEvent,
|
||||
} from 'ag-grid-community';
|
||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const STORAGE_KEY = 'vega_columns_sizes_store';
|
||||
|
||||
export const useColumnSizesStore = create<{
|
||||
sizes: Record<string, Record<string, number>>;
|
||||
valueSetter: (storeKey: string, value: Record<string, number>) => void;
|
||||
}>()(
|
||||
persist(
|
||||
immer((set) => ({
|
||||
sizes: {},
|
||||
valueSetter: (storeKey, value) =>
|
||||
set((state) => {
|
||||
state.sizes[storeKey] = {
|
||||
...(state.sizes[storeKey] || {}),
|
||||
...value,
|
||||
};
|
||||
return state;
|
||||
}),
|
||||
})),
|
||||
{ name: STORAGE_KEY }
|
||||
)
|
||||
);
|
||||
|
||||
interface UseColumnSizesProps {
|
||||
props: AgGridReactProps | AgReactUiProps;
|
||||
storeKey?: string;
|
||||
}
|
||||
|
||||
export const useColumnSizes = ({
|
||||
storeKey = '',
|
||||
props,
|
||||
}: UseColumnSizesProps): {
|
||||
onColumnResized?: (event: ColumnResizedEvent) => void;
|
||||
onGridReady?: (event: GridReadyEvent) => void;
|
||||
onGridSizeChanged?: (event: GridSizeChangedEvent) => void;
|
||||
} => {
|
||||
const sizes = useColumnSizesStore((store) => store.sizes[storeKey] || {});
|
||||
const valueSetter = useColumnSizesStore((store) => store.valueSetter);
|
||||
const widthRef = useRef(sizes['clientWidth'] || 0);
|
||||
const {
|
||||
onColumnResized: parentOnColumnResized,
|
||||
onGridReady: parentOnGridReady,
|
||||
onGridSizeChanged: parentOnGridSizeChanged,
|
||||
} = props;
|
||||
const recalculateSizes = useCallback((sizes: Record<string, number>) => {
|
||||
if (
|
||||
widthRef.current &&
|
||||
sizes['clientWidth'] &&
|
||||
widthRef.current !== sizes['clientWidth']
|
||||
) {
|
||||
const oldWidth = sizes['clientWidth'];
|
||||
const ratio = widthRef.current / oldWidth;
|
||||
return {
|
||||
...Object.entries(sizes).reduce((agg, [key, value]) => {
|
||||
agg[key] = value * ratio;
|
||||
return agg;
|
||||
}, {} as Record<string, number>),
|
||||
width: widthRef.current,
|
||||
} as Record<string, number>;
|
||||
}
|
||||
return sizes;
|
||||
}, []);
|
||||
|
||||
const onColumnResized = useCallback(
|
||||
(event: ColumnResizedEvent) => {
|
||||
parentOnColumnResized?.(event);
|
||||
if (
|
||||
storeKey &&
|
||||
event.source === 'uiColumnDragged' &&
|
||||
event.finished &&
|
||||
widthRef.current
|
||||
) {
|
||||
const { columns } = event;
|
||||
if (columns?.length) {
|
||||
const sizesObj = columns.reduce((aggr, column) => {
|
||||
aggr[column.getColId()] = column.getActualWidth();
|
||||
return aggr;
|
||||
}, {} as Record<string, number>);
|
||||
sizesObj['clientWidth'] = widthRef.current;
|
||||
valueSetter(storeKey, sizesObj);
|
||||
}
|
||||
}
|
||||
},
|
||||
[valueSetter, storeKey, parentOnColumnResized]
|
||||
);
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
const setSizes = useCallback(
|
||||
(apiEvent: GridReadyEvent | GridSizeChangedEvent) => {
|
||||
if (!storeKey || !Object.keys(sizes).length || !widthRef.current) {
|
||||
largeScreen && apiEvent?.api.sizeColumnsToFit();
|
||||
} else {
|
||||
const recalculatedSizes = recalculateSizes(sizes);
|
||||
const newSizes = Object.entries(recalculatedSizes).map(
|
||||
([key, size]) => ({
|
||||
key,
|
||||
newWidth: size,
|
||||
})
|
||||
);
|
||||
apiEvent.columnApi.setColumnWidths(newSizes);
|
||||
}
|
||||
},
|
||||
[storeKey, recalculateSizes, sizes, largeScreen]
|
||||
);
|
||||
|
||||
const onGridReady = useCallback(
|
||||
(event: GridReadyEvent) => {
|
||||
parentOnGridReady?.(event);
|
||||
setSizes(event);
|
||||
},
|
||||
[setSizes, parentOnGridReady]
|
||||
);
|
||||
|
||||
const onGridSizeChanged = useCallback(
|
||||
(event: GridSizeChangedEvent) => {
|
||||
parentOnGridSizeChanged?.(event);
|
||||
widthRef.current = event.clientWidth;
|
||||
setSizes(event);
|
||||
},
|
||||
[parentOnGridSizeChanged, setSizes]
|
||||
);
|
||||
if (storeKey) {
|
||||
return {
|
||||
onGridReady,
|
||||
onGridSizeChanged,
|
||||
onColumnResized,
|
||||
};
|
||||
}
|
||||
return {
|
||||
onGridReady: parentOnGridReady,
|
||||
onGridSizeChanged: parentOnGridSizeChanged,
|
||||
onColumnResized: parentOnColumnResized,
|
||||
};
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { IsFullWidthRowParams, RowHeightParams } from 'ag-grid-community';
|
||||
|
||||
const NO_HOVER_CSS_RULE = { 'no-hover': 'data?.isLastPlaceholder' };
|
||||
const ROW_ID = 'bottom-placeholder';
|
||||
const fullWidthCellRenderer = () => null;
|
||||
const isFullWidthRow = (params: IsFullWidthRowParams) =>
|
||||
params.rowNode.data?.isLastPlaceholder;
|
||||
|
||||
interface Props {
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export const useBottomPlaceholder = ({ gridRef, disabled }: Props) => {
|
||||
const onBodyScrollEnd = useCallback(() => {
|
||||
const rowCont = gridRef.current?.api.getDisplayedRowCount() ?? 0;
|
||||
if (rowCont) {
|
||||
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(rowCont - 1);
|
||||
if (lastRow && lastRow.data) {
|
||||
const placeholderRow = {
|
||||
...lastRow.data,
|
||||
isLastPlaceholder: true,
|
||||
id: ROW_ID,
|
||||
};
|
||||
const transaction = gridRef.current?.api.getRowNode(ROW_ID)
|
||||
? { update: [placeholderRow] }
|
||||
: { add: [placeholderRow] };
|
||||
gridRef.current?.api.applyTransaction(transaction);
|
||||
}
|
||||
}
|
||||
}, [gridRef]);
|
||||
|
||||
const onRowsChanged = useCallback(() => {
|
||||
const placeholderNode = gridRef.current?.api.getRowNode(ROW_ID);
|
||||
if (placeholderNode) {
|
||||
const transaction = {
|
||||
remove: [placeholderNode.data],
|
||||
};
|
||||
gridRef.current?.api.applyTransaction(transaction);
|
||||
}
|
||||
onBodyScrollEnd();
|
||||
}, [gridRef, onBodyScrollEnd]);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
(params: RowHeightParams) =>
|
||||
params.data?.isLastPlaceholder ? 50 : undefined,
|
||||
[]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
!disabled
|
||||
? {
|
||||
onBodyScrollEnd,
|
||||
rowClassRules: NO_HOVER_CSS_RULE,
|
||||
isFullWidthRow,
|
||||
fullWidthCellRenderer,
|
||||
onSortChanged: onRowsChanged,
|
||||
onFilterChanged: onRowsChanged,
|
||||
getRowHeight,
|
||||
}
|
||||
: {},
|
||||
[onBodyScrollEnd, onRowsChanged, disabled, getRowHeight]
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,185 @@
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
useDataGridEvents,
|
||||
GRID_EVENT_DEBOUNCE_TIME,
|
||||
} from './use-datagrid-events';
|
||||
import { AgGridThemed } from './ag-grid/ag-grid-lazy-themed';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
const gridProps = {
|
||||
rowData: [{ id: 1 }],
|
||||
columnDefs: [
|
||||
{
|
||||
field: 'id',
|
||||
width: 100,
|
||||
resizable: true,
|
||||
filter: 'agNumberColumnFilter',
|
||||
},
|
||||
],
|
||||
style: { width: 500, height: 300 },
|
||||
};
|
||||
|
||||
// Not using render hook so I can pass event callbacks
|
||||
// to a rendered grid
|
||||
function setup(...args: Parameters<typeof useDataGridEvents>) {
|
||||
let gridRef;
|
||||
|
||||
function TestComponent() {
|
||||
const hookCallbacks = useDataGridEvents(...args);
|
||||
gridRef = useRef<AgGridReact | null>(null);
|
||||
return <AgGridThemed gridRef={gridRef} {...gridProps} {...hookCallbacks} />;
|
||||
}
|
||||
render(<TestComponent />);
|
||||
return gridRef as unknown as MutableRefObject<AgGridReact>;
|
||||
}
|
||||
|
||||
describe('useDataGridEvents', () => {
|
||||
const originalWarn = console.warn;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
// disabling some ag grid warnings that are caused by test setup only
|
||||
console.warn = () => undefined;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
console.warn = originalWarn;
|
||||
});
|
||||
|
||||
it('default state is set and callback is called on column or filter event', async () => {
|
||||
const callback = jest.fn();
|
||||
const initialState = {
|
||||
filterModel: undefined,
|
||||
columnState: undefined,
|
||||
};
|
||||
|
||||
const result = setup(initialState, callback);
|
||||
|
||||
// column state was not updated, so the default width provided by the
|
||||
// col def should be set
|
||||
expect(result.current.columnApi.getColumnState()[0].width).toEqual(
|
||||
gridProps.columnDefs[0].width
|
||||
);
|
||||
// no filters set
|
||||
expect(result.current.api.getFilterModel()).toEqual({});
|
||||
|
||||
const newWidth = 400;
|
||||
|
||||
// Set col width
|
||||
await act(async () => {
|
||||
result.current.columnApi.setColumnWidth('id', newWidth);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
|
||||
});
|
||||
|
||||
expect(callback).toHaveBeenCalledWith({
|
||||
columnState: [expect.objectContaining({ colId: 'id', width: newWidth })],
|
||||
filterModel: {},
|
||||
});
|
||||
callback.mockClear();
|
||||
expect(result.current.columnApi.getColumnState()[0].width).toEqual(
|
||||
newWidth
|
||||
);
|
||||
|
||||
// Set filter
|
||||
await act(async () => {
|
||||
result.current.columnApi.applyColumnState({
|
||||
state: [{ colId: 'id', sort: 'asc' }],
|
||||
applyOrder: true,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
|
||||
});
|
||||
|
||||
expect(callback).toHaveBeenCalledWith({
|
||||
columnState: [expect.objectContaining({ colId: 'id', sort: 'asc' })],
|
||||
filterModel: {},
|
||||
});
|
||||
callback.mockClear();
|
||||
expect(result.current.columnApi.getColumnState()[0].sort).toEqual('asc');
|
||||
|
||||
// Set filter
|
||||
const idFilter = {
|
||||
filter: 1,
|
||||
filterType: 'number',
|
||||
type: 'equals',
|
||||
};
|
||||
await act(async () => {
|
||||
result.current.api.setFilterModel({
|
||||
id: idFilter,
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
|
||||
});
|
||||
|
||||
expect(callback).toHaveBeenCalledWith({
|
||||
columnState: expect.any(Object),
|
||||
filterModel: {
|
||||
id: idFilter,
|
||||
},
|
||||
});
|
||||
callback.mockClear();
|
||||
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
|
||||
});
|
||||
|
||||
it('applies grid state on ready', async () => {
|
||||
const idFilter = {
|
||||
filter: 1,
|
||||
filterType: 'number',
|
||||
type: 'equals',
|
||||
};
|
||||
const colState = { colId: 'id', width: 300, sort: 'desc' as const };
|
||||
const initialState = {
|
||||
filterModel: {
|
||||
id: idFilter,
|
||||
},
|
||||
columnState: [colState],
|
||||
};
|
||||
|
||||
const result = setup(initialState, jest.fn());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
|
||||
expect(result.current.columnApi.getColumnState()[0]).toEqual(
|
||||
expect.objectContaining(colState)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('debounces events', async () => {
|
||||
const callback = jest.fn();
|
||||
const initialState = {
|
||||
filterModel: undefined,
|
||||
columnState: undefined,
|
||||
};
|
||||
|
||||
const result = setup(initialState, callback);
|
||||
|
||||
const newWidth = 400;
|
||||
|
||||
// Set col width multiple times
|
||||
await act(async () => {
|
||||
result.current.columnApi.setColumnWidth('id', newWidth);
|
||||
result.current.columnApi.setColumnWidth('id', newWidth);
|
||||
result.current.columnApi.setColumnWidth('id', newWidth);
|
||||
});
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
|
||||
});
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import type {
|
||||
ColumnResizedEvent,
|
||||
ColumnState,
|
||||
FilterChangedEvent,
|
||||
GridReadyEvent,
|
||||
SortChangedEvent,
|
||||
} from 'ag-grid-community';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
type State = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
filterModel?: { [key: string]: any };
|
||||
columnState?: ColumnState[];
|
||||
};
|
||||
|
||||
type Event = ColumnResizedEvent | FilterChangedEvent | SortChangedEvent;
|
||||
|
||||
export const GRID_EVENT_DEBOUNCE_TIME = 300;
|
||||
|
||||
export const useDataGridEvents = (
|
||||
state: State,
|
||||
callback: (data: State) => void
|
||||
) => {
|
||||
// This function can be called very frequently by the onColumnResized
|
||||
// grid callback, so its memoized to only update after resizing is finished
|
||||
const onGridChange = useMemo(
|
||||
() =>
|
||||
debounce(({ api, columnApi }: Event) => {
|
||||
if (!api || !columnApi) return;
|
||||
const columnState = columnApi.getColumnState();
|
||||
const filterModel = api.getFilterModel();
|
||||
callback({ columnState, filterModel });
|
||||
}, GRID_EVENT_DEBOUNCE_TIME),
|
||||
[callback]
|
||||
);
|
||||
|
||||
// check if we have stored column states or filter models and apply if we do
|
||||
const onGridReady = useCallback(
|
||||
({ api, columnApi }: GridReadyEvent) => {
|
||||
if (!api || !columnApi) return;
|
||||
|
||||
if (state.columnState) {
|
||||
columnApi.applyColumnState({
|
||||
state: state.columnState,
|
||||
applyOrder: true,
|
||||
});
|
||||
} else {
|
||||
// ensure columns fit available space if no widths are set
|
||||
api.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
if (state.filterModel) {
|
||||
api.setFilterModel(state.filterModel);
|
||||
}
|
||||
},
|
||||
[state]
|
||||
);
|
||||
|
||||
return {
|
||||
onGridReady,
|
||||
onColumnResized: onGridChange,
|
||||
onFilterChanged: onGridChange,
|
||||
onSortChanged: onGridChange,
|
||||
};
|
||||
};
|
||||
@@ -113,6 +113,7 @@ export const DealTicketFeeDetails = ({
|
||||
const { settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const marketDecimals = market.decimalPlaces;
|
||||
let marginRequiredBestCase: string | undefined = undefined;
|
||||
let marginRequiredWorstCase: string | undefined = undefined;
|
||||
if (marginEstimate) {
|
||||
@@ -227,6 +228,9 @@ export const DealTicketFeeDetails = ({
|
||||
liquidationEstimateWorstCaseIncludingSellOrders
|
||||
? liquidationEstimateWorstCaseIncludingBuyOrders
|
||||
: liquidationEstimateWorstCaseIncludingSellOrders;
|
||||
|
||||
// The estimate order query API gives us the liquidation price in formatted by asset decimals.
|
||||
// We need to calculate it with asset decimals, but display it with market decimals precision until the API changes.
|
||||
liquidationPriceEstimate = formatRange(
|
||||
(liquidationEstimateBestCase < liquidationEstimateWorstCase
|
||||
? liquidationEstimateBestCase
|
||||
@@ -247,14 +251,16 @@ export const DealTicketFeeDetails = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, assetDecimals)}
|
||||
formattedValue={formatValue(notionalSize, assetDecimals, quantum)}
|
||||
symbol={assetSymbol}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol)}
|
||||
value={formatValue(notionalSize, marketDecimals)}
|
||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Fees')}
|
||||
@@ -335,8 +341,9 @@ export const DealTicketFeeDetails = ({
|
||||
{projectedMargin}
|
||||
<DealTicketFeeDetail
|
||||
label={t('Liquidation price estimate')}
|
||||
value={liquidationPriceEstimate}
|
||||
formattedValue={liquidationPriceEstimate}
|
||||
symbol={assetSymbol}
|
||||
symbol={quoteName}
|
||||
labelDescription={LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}
|
||||
/>
|
||||
{partyId && (
|
||||
|
||||
@@ -24,10 +24,8 @@ export const DepositsTable = forwardRef<
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{ resizable: true }}
|
||||
defaultColDef={{ flex: 1 }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
suppressCellFocus={true}
|
||||
storeKey="depositTable"
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './lib/fills-container';
|
||||
export * from './lib/fills-manager';
|
||||
export * from './lib/fills-data-provider';
|
||||
export * from './lib/__generated__/Fills';
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { FillsManager } from './fills-manager';
|
||||
|
||||
export const FillsContainer = ({
|
||||
marketId,
|
||||
onMarketClick,
|
||||
storeKey,
|
||||
}: {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
storeKey?: string;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Please connect Vega wallet')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FillsManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey={storeKey}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useRef } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FillsTable } from './fills-table';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { fillsWithMarketProvider } from './fills-data-provider';
|
||||
@@ -11,14 +11,14 @@ interface FillsManagerProps {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
storeKey?: string;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}
|
||||
|
||||
export const FillsManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
onMarketClick,
|
||||
storeKey,
|
||||
gridProps,
|
||||
}: FillsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
|
||||
@@ -38,9 +38,6 @@ export const FillsManager = ({
|
||||
},
|
||||
variables: { filter },
|
||||
});
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
});
|
||||
|
||||
return (
|
||||
<FillsTable
|
||||
@@ -48,9 +45,8 @@ export const FillsManager = ({
|
||||
rowData={data}
|
||||
partyId={partyId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey={storeKey}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
||||
{...gridProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -39,7 +39,6 @@ export type Role = typeof TAKER | typeof MAKER | '-';
|
||||
export type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
partyId: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
storeKey?: string;
|
||||
};
|
||||
|
||||
export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './lib/ledger-container';
|
||||
export * from './lib/ledger-manager';
|
||||
export * from './lib/__generated__/LedgerEntries';
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { LedgerManager } from './ledger-manager';
|
||||
|
||||
export const LedgerContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Please connect Vega wallet')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return <LedgerManager partyId={pubKey} />;
|
||||
};
|
||||
@@ -10,6 +10,7 @@ import { LedgerTable } from './ledger-table';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import { LedgerExportLink } from './ledger-export-link';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
|
||||
export interface Filter {
|
||||
vegaTime?: {
|
||||
@@ -24,7 +25,13 @@ const defaultFilter = {
|
||||
},
|
||||
};
|
||||
|
||||
export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
export const LedgerManager = ({
|
||||
partyId,
|
||||
gridProps,
|
||||
}: {
|
||||
partyId: string;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
||||
|
||||
@@ -33,7 +40,7 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
partyId,
|
||||
dateRange: filter?.vegaTime?.value,
|
||||
pagination: {
|
||||
first: 5000,
|
||||
first: 10,
|
||||
},
|
||||
}),
|
||||
[partyId, filter?.vegaTime?.value]
|
||||
@@ -45,18 +52,23 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
skip: !variables.partyId,
|
||||
});
|
||||
|
||||
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
|
||||
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
|
||||
setFilter(updatedFilter);
|
||||
}, []);
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
|
||||
setFilter(updatedFilter);
|
||||
gridProps.onFilterChanged(event);
|
||||
},
|
||||
[gridProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LedgerTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
onFilterChanged={onFilterChanged}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
||||
{...gridProps}
|
||||
onFilterChanged={onFilterChanged}
|
||||
/>
|
||||
{data && <LedgerExportLink entries={data} partyId={partyId} />}
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
(props, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: 'calc(100% - 50px)' }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
@@ -61,9 +61,6 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
buttons: ['reset'],
|
||||
},
|
||||
}}
|
||||
storeKey="ledgerTable"
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Liquidity Provisions
|
||||
|
||||
fragment LiquidityProvisionFields on LiquidityProvision {
|
||||
id
|
||||
party {
|
||||
id
|
||||
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
|
||||
|
||||
+3
-2
@@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
||||
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
||||
|
||||
export type LiquidityProvisionsQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
|
||||
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
|
||||
|
||||
export type LiquidityProvisionsUpdateSubscriptionVariables = Types.Exact<{
|
||||
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||
@@ -31,6 +31,7 @@ export type LiquidityProviderFeeShareQuery = { __typename?: 'Query', market?: {
|
||||
|
||||
export const LiquidityProvisionFieldsFragmentDoc = gql`
|
||||
fragment LiquidityProvisionFields on LiquidityProvision {
|
||||
id
|
||||
party {
|
||||
id
|
||||
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
|
||||
|
||||
@@ -38,8 +38,7 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
) => {
|
||||
return produce(data || [], (draft) => {
|
||||
deltas?.forEach((delta) => {
|
||||
const id = delta.id;
|
||||
const index = draft.findIndex((a) => delta.id === id);
|
||||
const index = draft.findIndex((a) => delta.id === a.id);
|
||||
if (index !== -1) {
|
||||
draft[index].commitmentAmount = delta.commitmentAmount;
|
||||
draft[index].fee = delta.fee;
|
||||
@@ -47,6 +46,7 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
draft[index].status = delta.status;
|
||||
} else {
|
||||
draft.unshift({
|
||||
id: delta.id,
|
||||
commitmentAmount: delta.commitmentAmount,
|
||||
fee: delta.fee,
|
||||
status: delta.status,
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
formatNumberPercentage,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaValueFormatterParams,
|
||||
TypedDataAgGrid,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||
import type {
|
||||
ColDef,
|
||||
ITooltipParams,
|
||||
ValueFormatterParams,
|
||||
} from 'ag-grid-community';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { LiquidityProvisionStatus } from '@vegaprotocol/types';
|
||||
import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types';
|
||||
import type { LiquidityProvisionData } from './liquidity-data-provider';
|
||||
|
||||
@@ -35,27 +37,158 @@ export interface LiquidityTableProps
|
||||
symbol?: string;
|
||||
assetDecimalPlaces?: number;
|
||||
stakeToCcyVolume: string | null;
|
||||
quantum?: string | number;
|
||||
}
|
||||
|
||||
export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
({ symbol = '', assetDecimalPlaces, stakeToCcyVolume, ...props }, ref) => {
|
||||
const assetDecimalsFormatter = ({ value }: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0, 5)}`;
|
||||
};
|
||||
const stakeToCcyVolumeFormatter = ({ value }: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
const newValue = new BigNumber(value)
|
||||
.times(Number(stakeToCcyVolume) || 1)
|
||||
.toString();
|
||||
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0, 5)}`;
|
||||
};
|
||||
(
|
||||
{ symbol = '', assetDecimalPlaces, stakeToCcyVolume, quantum, ...props },
|
||||
ref
|
||||
) => {
|
||||
const colDefs = useMemo(() => {
|
||||
const assetDecimalsFormatter = ({ value }: ITooltipParams) => {
|
||||
if (!value) return '-';
|
||||
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0)}`;
|
||||
};
|
||||
|
||||
const assetDecimalsQuantumFormatter = ({
|
||||
value,
|
||||
}: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
return `${addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
)}`;
|
||||
};
|
||||
|
||||
const stakeToCcyVolumeFormatter = ({ value }: ITooltipParams) => {
|
||||
if (!value) return '-';
|
||||
const newValue = new BigNumber(value)
|
||||
.times(Number(stakeToCcyVolume) || 1)
|
||||
.toString();
|
||||
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0)}`;
|
||||
};
|
||||
|
||||
const stakeToCcyVolumeQuantumFormatter = ({
|
||||
value,
|
||||
}: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
const newValue = new BigNumber(value)
|
||||
.times(Number(stakeToCcyVolume) || 1)
|
||||
.toString();
|
||||
return `${addDecimalsFormatNumberQuantum(
|
||||
newValue,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
)}`;
|
||||
};
|
||||
|
||||
const defs: ColDef[] = [
|
||||
{
|
||||
headerName: t('Party'),
|
||||
field: 'party.id',
|
||||
headerTooltip: t(
|
||||
'The public key of the party making this commitment.'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t(`Commitment (${symbol})`),
|
||||
field: 'commitmentAmount',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'The amount committed to the market by this liquidity provider.'
|
||||
),
|
||||
valueFormatter: assetDecimalsQuantumFormatter,
|
||||
tooltipValueGetter: assetDecimalsFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t(`Share`),
|
||||
field: 'equityLikeShare',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
|
||||
),
|
||||
valueFormatter: percentageFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Proposed fee'),
|
||||
headerTooltip: t(
|
||||
'The fee percentage (per trade) proposed by each liquidity provider.'
|
||||
),
|
||||
field: 'fee',
|
||||
type: 'rightAligned',
|
||||
valueFormatter: percentageFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Market valuation at entry'),
|
||||
field: 'averageEntryValuation',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'The valuation of the market at the time the liquidity commitment was made. Commitments made at a lower valuation earlier in the lifetime of the market would be expected to have a higher equity-like share if the market has grown. If a commitment is amended, value will reflect the average of the market valuations across the lifetime of the commitment.'
|
||||
),
|
||||
minWidth: 160,
|
||||
valueFormatter: assetDecimalsQuantumFormatter,
|
||||
tooltipValueGetter: assetDecimalsFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Obligation'),
|
||||
field: 'commitmentAmount',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume. The obligation can be met by a combination of LP orders and limit orders on the order book.`
|
||||
),
|
||||
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
||||
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Supplied'),
|
||||
field: 'balance',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
|
||||
),
|
||||
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
||||
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
headerTooltip: t('The current status of this liquidity provision.'),
|
||||
field: 'status',
|
||||
valueFormatter: ({ value }) => {
|
||||
if (!value) return value;
|
||||
return LiquidityProvisionStatusMapping[
|
||||
value as LiquidityProvisionStatus
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Created'),
|
||||
headerTooltip: t(
|
||||
'The date and time this liquidity provision was created.'
|
||||
),
|
||||
field: 'createdAt',
|
||||
type: 'rightAligned',
|
||||
valueFormatter: dateValueFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Updated'),
|
||||
headerTooltip: t(
|
||||
'The date and time this liquidity provision was last updated.'
|
||||
),
|
||||
field: 'updatedAt',
|
||||
type: 'rightAligned',
|
||||
valueFormatter: dateValueFormatter,
|
||||
},
|
||||
];
|
||||
return defs;
|
||||
}, [assetDecimalPlaces, quantum, stakeToCcyVolume, symbol]);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||
getRowId={({ data }) => `${data.party.id}-${data.status}`}
|
||||
getRowId={({ data }) => data.id}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
@@ -64,101 +197,9 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
}}
|
||||
storeKey="liquidityProvisionTable"
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Party')}
|
||||
field="party.id"
|
||||
headerTooltip={t(
|
||||
'The public key of the party making this commitment.'
|
||||
)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t(`Commitment (${symbol})`)}
|
||||
field="commitmentAmount"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
'The amount committed to the market by this liquidity provider.'
|
||||
)}
|
||||
valueFormatter={assetDecimalsFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Share')}
|
||||
field="equityLikeShare"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
|
||||
)}
|
||||
valueFormatter={percentageFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Proposed fee')}
|
||||
headerTooltip={t(
|
||||
'The fee percentage (per trade) proposed by each liquidity provider.'
|
||||
)}
|
||||
field="fee"
|
||||
type="rightAligned"
|
||||
valueFormatter={percentageFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Market valuation at entry')}
|
||||
field="averageEntryValuation"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
'The valuation of the market at the time the liquidity commitment was made. Commitments made at a lower valuation earlier in the lifetime of the market would be expected to have a higher equity-like share if the market has grown. If a commitment is amended, value will reflect the average of the market valuations across the lifetime of the commitment.'
|
||||
)}
|
||||
minWidth={160}
|
||||
valueFormatter={assetDecimalsFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Obligation')}
|
||||
field="commitmentAmount"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_siskas network parameter to convert into units of liquidity volume. The obligation can be met by a combination of LP orders and limit orders on the order book.`
|
||||
)}
|
||||
valueFormatter={stakeToCcyVolumeFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Supplied')}
|
||||
headerTooltip={t(
|
||||
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
|
||||
)}
|
||||
field="balance"
|
||||
type="rightAligned"
|
||||
valueFormatter={stakeToCcyVolumeFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
headerTooltip={t('The current status of this liquidity provision.')}
|
||||
field="status"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LiquidityProvisionData, 'status'>) => {
|
||||
if (!value) return value;
|
||||
return LiquidityProvisionStatusMapping[value];
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Created')}
|
||||
headerTooltip={t(
|
||||
'The date and time this liquidity provision was created.'
|
||||
)}
|
||||
field="createdAt"
|
||||
type="rightAligned"
|
||||
valueFormatter={dateValueFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Updated')}
|
||||
headerTooltip={t(
|
||||
'The last time this liquidity provision was updated.'
|
||||
)}
|
||||
field="updatedAt"
|
||||
type="rightAligned"
|
||||
valueFormatter={dateValueFormatter}
|
||||
/>
|
||||
</AgGrid>
|
||||
columnDefs={colDefs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -47,7 +46,10 @@ const Row = ({
|
||||
if (asPercentage) {
|
||||
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||
}
|
||||
return `${formatNumber(Number(value))} ${assetSymbol}`;
|
||||
if (assetSymbol) {
|
||||
return `${Number(value).toLocaleString()} ${assetSymbol}`;
|
||||
}
|
||||
return Number(value).toLocaleString();
|
||||
};
|
||||
|
||||
const formattedValue = getFormattedValue(value);
|
||||
|
||||
@@ -94,7 +94,7 @@ export const marketInfoQuery = (
|
||||
},
|
||||
lpPriceRange: '0.02',
|
||||
liquidityMonitoringParameters: {
|
||||
triggeringRatio: '0',
|
||||
triggeringRatio: '0.7',
|
||||
targetStakeParameters: {
|
||||
timeWindow: 3600,
|
||||
scalingFactor: 10,
|
||||
|
||||
@@ -61,7 +61,6 @@ export const MarketListTable = forwardRef<
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus
|
||||
components={{ PriceFlashCell, MarketName }}
|
||||
storeKey="allMarkets"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,50 @@
|
||||
import { marketDataErrorPolicyGuard } from '@vegaprotocol/data-provider';
|
||||
import { makeDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { MarketsDataQuery } from './__generated__/markets-data';
|
||||
import type {
|
||||
MarketsDataQuery,
|
||||
MarketsDataQueryVariables,
|
||||
} from './__generated__/markets-data';
|
||||
import type {
|
||||
MarketDataUpdateSubscription,
|
||||
MarketDataUpdateFieldsFragment,
|
||||
MarketDataUpdateSubscriptionVariables,
|
||||
} from './__generated__/market-data';
|
||||
import { MarketDataUpdateDocument } from './__generated__/market-data';
|
||||
import { MarketsDataDocument } from './__generated__/markets-data';
|
||||
import type { MarketData } from './market-data-provider';
|
||||
|
||||
const getData = (responseData: MarketsDataQuery | null): MarketData[] | null =>
|
||||
const getData = (responseData: MarketsDataQuery | null): MarketData[] =>
|
||||
responseData?.marketsConnection?.edges
|
||||
.filter((edge) => edge.node.data)
|
||||
.map((edge) => edge.node.data as MarketData) || null;
|
||||
.map((edge) => edge.node.data as MarketData) || [];
|
||||
|
||||
export const mapMarketDataUpdateToMarketData = (
|
||||
delta: MarketDataUpdateFieldsFragment
|
||||
): MarketData => {
|
||||
const { marketId, __typename, ...marketData } = delta;
|
||||
return { ...marketData, market: { id: marketId } };
|
||||
};
|
||||
|
||||
const update = (
|
||||
data: MarketData[] | null,
|
||||
delta: MarketDataUpdateFieldsFragment
|
||||
) => {
|
||||
const updatedData = data ? [...data] : [];
|
||||
const item = mapMarketDataUpdateToMarketData(delta);
|
||||
const index = updatedData.findIndex(
|
||||
(data) => data.market.id === item.market.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
updatedData[index] = { ...updatedData[index], ...item };
|
||||
} else {
|
||||
updatedData.push(item);
|
||||
}
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
const getDelta = (
|
||||
subscriptionData: MarketDataUpdateSubscription
|
||||
): MarketDataUpdateFieldsFragment => subscriptionData.marketsData[0];
|
||||
|
||||
export const marketsDataProvider = makeDataProvider<
|
||||
MarketsDataQuery,
|
||||
@@ -19,3 +56,25 @@ export const marketsDataProvider = makeDataProvider<
|
||||
getData,
|
||||
errorPolicyGuard: marketDataErrorPolicyGuard,
|
||||
});
|
||||
|
||||
type Variables = { marketIds: string[] };
|
||||
|
||||
export const marketsLiveDataProvider = makeDataProvider<
|
||||
MarketsDataQuery,
|
||||
MarketData[],
|
||||
MarketDataUpdateSubscription,
|
||||
MarketDataUpdateFieldsFragment,
|
||||
Variables,
|
||||
MarketDataUpdateSubscriptionVariables,
|
||||
MarketsDataQueryVariables
|
||||
>({
|
||||
query: MarketsDataDocument,
|
||||
subscriptionQuery: MarketDataUpdateDocument,
|
||||
getData,
|
||||
getDelta,
|
||||
update,
|
||||
errorPolicyGuard: marketDataErrorPolicyGuard,
|
||||
getQueryVariables: () => ({}),
|
||||
getSubscriptionVariables: ({ marketIds }: Variables) =>
|
||||
marketIds.map((marketId) => ({ marketId })),
|
||||
});
|
||||
|
||||
@@ -10,10 +10,15 @@ import type {
|
||||
} from './__generated__/markets';
|
||||
import type { MarketsCandlesQueryVariables } from './__generated__/markets-candles';
|
||||
|
||||
import { marketsDataProvider } from './markets-data-provider';
|
||||
import {
|
||||
marketsDataProvider,
|
||||
marketsLiveDataProvider,
|
||||
mapMarketDataUpdateToMarketData,
|
||||
} from './markets-data-provider';
|
||||
import { marketDataProvider } from './market-data-provider';
|
||||
import { marketsCandlesProvider } from './markets-candles-provider';
|
||||
import type { MarketData } from './market-data-provider';
|
||||
import type { MarketDataUpdateFieldsFragment } from './__generated__';
|
||||
import type { MarketCandles } from './markets-candles-provider';
|
||||
import { useMemo } from 'react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
@@ -160,6 +165,44 @@ export const allMarketsWithDataProvider = makeDerivedDataProvider<
|
||||
addData(parts[0] as Market[], parts[1] as MarketData[])
|
||||
);
|
||||
|
||||
export const allMarketsWithLiveDataProvider = makeDerivedDataProvider<
|
||||
MarketMaybeWithData[],
|
||||
MarketMaybeWithData,
|
||||
{ marketIds: string[] }
|
||||
>(
|
||||
[
|
||||
(callback, client, variables) =>
|
||||
marketsProvider(callback, client, undefined),
|
||||
marketsLiveDataProvider,
|
||||
],
|
||||
(partsData, variables, prevData, parts) => {
|
||||
if (prevData && parts[1].isUpdate) {
|
||||
const data = mapMarketDataUpdateToMarketData(parts[1].delta);
|
||||
const index = prevData.findIndex(
|
||||
(market) => market.id === data.market.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
const updatedData = [...prevData];
|
||||
updatedData[index] = { ...updatedData[index], data };
|
||||
return updatedData;
|
||||
} else {
|
||||
return prevData;
|
||||
}
|
||||
}
|
||||
return addData(partsData[0] as Market[], partsData[1] as MarketData[]);
|
||||
},
|
||||
(data, parts) => {
|
||||
if (!parts[1].isUpdate && parts[1].delta) {
|
||||
return;
|
||||
}
|
||||
return data.find(
|
||||
(market) =>
|
||||
market.id ===
|
||||
(parts[1].delta as MarketDataUpdateFieldsFragment).marketId
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export type MarketMaybeWithDataAndCandles = MarketMaybeWithData &
|
||||
MarketMaybeWithCandles;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './order-data-provider';
|
||||
export * from './order-list';
|
||||
export * from './order-list-manager';
|
||||
export * from './order-list-container';
|
||||
export * from './mocks/generate-orders';
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { Filter } from './order-list-manager';
|
||||
import { OrderListManager } from './order-list-manager';
|
||||
|
||||
export interface OrderListContainerProps {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
filter?: Filter;
|
||||
storeKey?: string;
|
||||
}
|
||||
|
||||
export const OrderListContainer = ({
|
||||
marketId,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
enforceBottomPlaceholder,
|
||||
filter,
|
||||
storeKey,
|
||||
}: OrderListContainerProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
|
||||
return (
|
||||
<OrderListManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
filter={filter}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
isReadOnly={isReadOnly}
|
||||
enforceBottomPlaceholder={enforceBottomPlaceholder}
|
||||
storeKey={storeKey}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -2,11 +2,9 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { GridReadyEvent, FilterChangedEvent } from 'ag-grid-community';
|
||||
|
||||
import { OrderListTable } from '../order-list/order-list';
|
||||
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import {
|
||||
@@ -16,59 +14,31 @@ import {
|
||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
|
||||
export enum Filter {
|
||||
'Open',
|
||||
'Closed',
|
||||
'Rejected',
|
||||
'Open' = 'Open',
|
||||
'Closed' = 'Closed',
|
||||
'Rejected' = 'Rejected',
|
||||
}
|
||||
|
||||
const FilterStatusValue = {
|
||||
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
[Filter.Closed]: [
|
||||
OrderStatus.STATUS_CANCELLED,
|
||||
OrderStatus.STATUS_EXPIRED,
|
||||
OrderStatus.STATUS_FILLED,
|
||||
OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
OrderStatus.STATUS_STOPPED,
|
||||
],
|
||||
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
|
||||
};
|
||||
|
||||
export interface OrderListManagerProps {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
isReadOnly: boolean;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
filter?: Filter;
|
||||
storeKey?: string;
|
||||
gridProps?: ReturnType<typeof useDataGridEvents>;
|
||||
}
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={onClick}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const OrderListManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
isReadOnly,
|
||||
enforceBottomPlaceholder,
|
||||
filter,
|
||||
storeKey,
|
||||
gridProps,
|
||||
}: OrderListManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
@@ -91,14 +61,6 @@ export const OrderListManager = ({
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
onFilterChanged: bottomPlaceholderOnFilterChanged,
|
||||
...bottomPlaceholderProps
|
||||
} = useBottomPlaceholder({
|
||||
gridRef,
|
||||
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder,
|
||||
});
|
||||
|
||||
const cancel = useCallback(
|
||||
(order: Order) => {
|
||||
if (!order.market) return;
|
||||
@@ -112,26 +74,6 @@ export const OrderListManager = ({
|
||||
[create]
|
||||
);
|
||||
|
||||
const onGridReady = useCallback(
|
||||
({ api }: GridReadyEvent) => {
|
||||
if (filter !== undefined) {
|
||||
api.setFilterModel({
|
||||
status: {
|
||||
value: FilterStatusValue[filter],
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[filter]
|
||||
);
|
||||
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
bottomPlaceholderOnFilterChanged?.();
|
||||
},
|
||||
[bottomPlaceholderOnFilterChanged]
|
||||
);
|
||||
|
||||
const cancelAll = useCallback(() => {
|
||||
create({
|
||||
orderCancellation: {},
|
||||
@@ -142,20 +84,17 @@ export const OrderListManager = ({
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
<OrderListTable
|
||||
rowData={data as Order[]}
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
filter={filter}
|
||||
onGridReady={onGridReady}
|
||||
onCancel={cancel}
|
||||
onEdit={setEditOrder}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
onFilterChanged={onFilterChanged}
|
||||
isReadOnly={isReadOnly}
|
||||
storeKey={storeKey}
|
||||
suppressAutoSize
|
||||
overlayNoRowsTemplate={error ? error.message : t('No orders')}
|
||||
{...bottomPlaceholderProps}
|
||||
{...gridProps}
|
||||
/>
|
||||
</div>
|
||||
{!isReadOnly && hasAmendableOrder && (
|
||||
@@ -198,3 +137,16 @@ export const OrderListManager = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={onClick}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,14 +2,13 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
isNumeric,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, forwardRef } from 'react';
|
||||
import { memo, forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
SetFilter,
|
||||
@@ -24,11 +23,13 @@ import type {
|
||||
TypedDataAgGrid,
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderActionsDropdown } from '../order-actions-dropdown';
|
||||
import { Filter } from '../order-list-manager';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
|
||||
export type OrderListTableProps = TypedDataAgGrid<Order> & {
|
||||
marketId?: string;
|
||||
@@ -38,7 +39,6 @@ export type OrderListTableProps = TypedDataAgGrid<Order> & {
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
filter?: Filter;
|
||||
isReadOnly: boolean;
|
||||
storeKey?: string;
|
||||
};
|
||||
|
||||
export const OrderListTable = memo<
|
||||
@@ -54,49 +54,40 @@ export const OrderListTable = memo<
|
||||
: filter === undefined || filter === Filter.Open
|
||||
? true
|
||||
: false;
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell, OrderTypeCell }}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.code"
|
||||
cellRenderer="MarketNameCell"
|
||||
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
field="size"
|
||||
cellClass="font-mono text-right"
|
||||
type="rightAligned"
|
||||
cellClassRules={{
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: Order }) =>
|
||||
data?.side === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: Order }) =>
|
||||
data?.side === Schema.Side.SIDE_SELL,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
||||
return data?.size && data.market
|
||||
? toBigNum(data.size, data.market.positionDecimalPlaces ?? 0)
|
||||
.multipliedBy(data.side === Schema.Side.SIDE_SELL ? -1 : 1)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'size'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value)) {
|
||||
if (!data?.market || !isNumeric(data.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
@@ -107,33 +98,33 @@ export const OrderListTable = memo<
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
value,
|
||||
data.size,
|
||||
data.market.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
}}
|
||||
minWidth={80}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="type"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
}}
|
||||
cellRenderer="OrderTypeCell"
|
||||
cellRendererParams={{
|
||||
},
|
||||
cellRenderer: 'OrderTypeCell',
|
||||
cellRendererParams: {
|
||||
onClick: onOrderTypeClick,
|
||||
}}
|
||||
minWidth={80}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="status"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderStatusMapping,
|
||||
readonly: filter !== undefined,
|
||||
}}
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'status'>) => {
|
||||
@@ -145,8 +136,8 @@ export const OrderListTable = memo<
|
||||
}`;
|
||||
}
|
||||
return value ? Schema.OrderStatusMapping[value] : '';
|
||||
}}
|
||||
cellRenderer={({
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
@@ -156,45 +147,51 @@ export const OrderListTable = memo<
|
||||
<span data-testid={`order-status-${data?.id}`}>
|
||||
{valueFormatted}
|
||||
</span>
|
||||
)}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Filled')}
|
||||
field="remaining"
|
||||
cellClass="font-mono text-right"
|
||||
type="rightAligned"
|
||||
valueFormatter={({
|
||||
),
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Filled'),
|
||||
field: 'remaining',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
||||
return data?.size && data.market
|
||||
? toBigNum(
|
||||
(BigInt(data.size) - BigInt(data.remaining)).toString(),
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
).toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>): string => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
||||
return '-';
|
||||
}
|
||||
const dps = data.market.positionDecimalPlaces;
|
||||
const size = new BigNumber(data.size);
|
||||
const remaining = new BigNumber(value);
|
||||
const fills = size.minus(remaining);
|
||||
const { positionDecimalPlaces } = data.market;
|
||||
const filled = BigInt(data.size) - BigInt(data.remaining);
|
||||
return `${addDecimalsFormatNumber(
|
||||
fills.toString(),
|
||||
dps
|
||||
)}/${addDecimalsFormatNumber(size.toString(), dps)}`;
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="price"
|
||||
type="rightAligned"
|
||||
cellClass="font-mono text-right"
|
||||
valueFormatter={({
|
||||
filled.toString(),
|
||||
positionDecimalPlaces
|
||||
)}/${addDecimalsFormatNumber(data.size, positionDecimalPlaces)}`;
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'price'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
@@ -204,16 +201,16 @@ export const OrderListTable = memo<
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="timeInForce"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'timeInForce'>) => {
|
||||
@@ -235,13 +232,13 @@ export const OrderListTable = memo<
|
||||
}${data?.reduceOnly ? t('. Reduce only') : ''}`;
|
||||
|
||||
return label;
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="createdAt"
|
||||
filter={DateRangeFilter}
|
||||
cellRenderer={({
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
filter: DateRangeFilter,
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<Order, 'createdAt'>) => {
|
||||
return (
|
||||
@@ -249,12 +246,12 @@ export const OrderListTable = memo<
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : value}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="updatedAt"
|
||||
cellRenderer={({
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<Order, 'updatedAt'>) => {
|
||||
@@ -266,15 +263,15 @@ export const OrderListTable = memo<
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="amend"
|
||||
{...COL_DEFS.actions}
|
||||
minWidth={showAllActions ? 120 : COL_DEFS.actions.minWidth}
|
||||
maxWidth={showAllActions ? 120 : COL_DEFS.actions.minWidth}
|
||||
cellRenderer={({ data }: { data?: Order }) => {
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
colId: 'amend',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: Order }) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
@@ -298,9 +295,37 @@ export const OrderListTable = memo<
|
||||
<OrderActionsDropdown id={data?.id} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
filter,
|
||||
onCancel,
|
||||
onEdit,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
props.isReadOnly,
|
||||
showAllActions,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell, OrderTypeCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export * from './lib/__generated__/Positions';
|
||||
export * from './lib/positions-container';
|
||||
export * from './lib/positions-data-providers';
|
||||
export * from './lib/positions-table';
|
||||
export * from './lib/positions-manager';
|
||||
export * from './lib/use-market-margin';
|
||||
export * from './lib/use-open-volume';
|
||||
export * from './lib/use-positions-data';
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
MarketMaybeWithData,
|
||||
MarketDataQueryVariables,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { allMarketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { allMarketsWithLiveDataProvider } from '@vegaprotocol/markets';
|
||||
import type {
|
||||
PositionsQuery,
|
||||
PositionFieldsFragment,
|
||||
@@ -144,8 +144,9 @@ export const update = (
|
||||
data: PositionFieldsFragment[] | null,
|
||||
deltas: PositionsSubscriptionSubscription['positions']
|
||||
) => {
|
||||
return produce(data || [], (draft) => {
|
||||
const updatedData = produce(data || [], (draft) => {
|
||||
deltas.forEach((delta) => {
|
||||
const { marketId, partyId, __typename, ...position } = delta;
|
||||
const index = draft.findIndex(
|
||||
(node) =>
|
||||
node.market.id === delta.marketId && node.party.id === delta.partyId
|
||||
@@ -154,29 +155,25 @@ export const update = (
|
||||
const currNode = draft[index];
|
||||
draft[index] = {
|
||||
...currNode,
|
||||
realisedPNL: delta.realisedPNL,
|
||||
unrealisedPNL: delta.unrealisedPNL,
|
||||
openVolume: delta.openVolume,
|
||||
averageEntryPrice: delta.averageEntryPrice,
|
||||
updatedAt: delta.updatedAt,
|
||||
lossSocializationAmount: delta.lossSocializationAmount,
|
||||
positionStatus: delta.positionStatus,
|
||||
...position,
|
||||
};
|
||||
} else {
|
||||
draft.unshift({
|
||||
...delta,
|
||||
...position,
|
||||
__typename: 'Position',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: delta.marketId,
|
||||
},
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: delta.partyId,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
const getSubscriptionVariables = (
|
||||
@@ -242,41 +239,54 @@ export const rejoinPositionData = (
|
||||
| null => {
|
||||
if (positions && marketsData) {
|
||||
return positions.map((node) => {
|
||||
const market =
|
||||
marketsData?.find((market) => market.id === node.market.id) || null;
|
||||
return {
|
||||
...node,
|
||||
market:
|
||||
marketsData?.find((market) => market.id === node.market.id) || null,
|
||||
market,
|
||||
};
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const positionsMarketsProvider = makeDerivedDataProvider<
|
||||
string[],
|
||||
never,
|
||||
PositionsQueryVariables
|
||||
>([positionsDataProvider], ([positions]) => {
|
||||
return Array.from(
|
||||
new Set(
|
||||
(positions as PositionFieldsFragment[]).map(
|
||||
(position) => position.market.id
|
||||
)
|
||||
)
|
||||
).sort();
|
||||
});
|
||||
|
||||
export const positionsMetricsProvider = makeDerivedDataProvider<
|
||||
Position[],
|
||||
Position[],
|
||||
PositionsQueryVariables
|
||||
PositionsQueryVariables & { marketIds: string[] }
|
||||
>(
|
||||
[
|
||||
positionsDataProvider,
|
||||
(callback, client, variables) =>
|
||||
positionsDataProvider(callback, client, { partyIds: variables.partyIds }),
|
||||
(callback, client, variables) =>
|
||||
accountsDataProvider(callback, client, {
|
||||
partyId: Array.isArray(variables.partyIds)
|
||||
? variables.partyIds[0]
|
||||
: variables.partyIds,
|
||||
}),
|
||||
(callback, client) =>
|
||||
allMarketsWithDataProvider(callback, client, undefined),
|
||||
(callback, client, variables) =>
|
||||
allMarketsWithLiveDataProvider(callback, client, {
|
||||
marketIds: variables.marketIds,
|
||||
}),
|
||||
],
|
||||
([positions, accounts, marketsData], variables) => {
|
||||
([positions, accounts, marketsData]) => {
|
||||
const positionsData = rejoinPositionData(positions, marketsData);
|
||||
if (!variables) {
|
||||
return [];
|
||||
}
|
||||
return sortBy(
|
||||
getMetrics(positionsData, accounts as Account[] | null),
|
||||
'marketName'
|
||||
);
|
||||
const metrics = getMetrics(positionsData, accounts as Account[] | null);
|
||||
return sortBy(metrics, 'marketName');
|
||||
},
|
||||
(data, delta, previousData) =>
|
||||
data.filter((row) => {
|
||||
|
||||
@@ -1,65 +1,67 @@
|
||||
import { useRef } from 'react';
|
||||
import { usePositionsData } from './use-positions-data';
|
||||
import { useCallback } from 'react';
|
||||
import { PositionsTable } from './positions-table';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
positionsMetricsProvider,
|
||||
positionsMarketsProvider,
|
||||
} from './positions-data-providers';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
interface PositionsManagerProps {
|
||||
partyIds: string[];
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
noBottomPlaceholder?: boolean;
|
||||
storeKey?: string;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}
|
||||
|
||||
export const PositionsManager = ({
|
||||
partyIds,
|
||||
onMarketClick,
|
||||
isReadOnly,
|
||||
noBottomPlaceholder,
|
||||
storeKey,
|
||||
gridProps,
|
||||
}: PositionsManagerProps) => {
|
||||
const { pubKeys, pubKey } = useVegaWallet();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data, error } = usePositionsData(partyIds, gridRef);
|
||||
const create = useVegaTransactionStore((store) => store.create);
|
||||
const onClose = ({
|
||||
marketId,
|
||||
openVolume,
|
||||
}: {
|
||||
marketId: string;
|
||||
openVolume: string;
|
||||
}) =>
|
||||
create({
|
||||
batchMarketInstructions: {
|
||||
cancellations: [
|
||||
{
|
||||
marketId,
|
||||
orderId: '', // omit order id to cancel all active orders
|
||||
},
|
||||
],
|
||||
submissions: [
|
||||
{
|
||||
marketId: marketId,
|
||||
type: Schema.OrderType.TYPE_MARKET as const,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC as const,
|
||||
side: openVolume.startsWith('-')
|
||||
? Schema.Side.SIDE_BUY
|
||||
: Schema.Side.SIDE_SELL,
|
||||
size: openVolume.replace('-', ''),
|
||||
reduceOnly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const onClose = useCallback(
|
||||
({ marketId, openVolume }: { marketId: string; openVolume: string }) =>
|
||||
create({
|
||||
batchMarketInstructions: {
|
||||
cancellations: [
|
||||
{
|
||||
marketId,
|
||||
orderId: '', // omit order id to cancel all active orders
|
||||
},
|
||||
],
|
||||
submissions: [
|
||||
{
|
||||
marketId: marketId,
|
||||
type: Schema.OrderType.TYPE_MARKET as const,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC as const,
|
||||
side: openVolume.startsWith('-')
|
||||
? Schema.Side.SIDE_BUY
|
||||
: Schema.Side.SIDE_SELL,
|
||||
size: openVolume.replace('-', ''),
|
||||
reduceOnly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[create]
|
||||
);
|
||||
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
disabled: noBottomPlaceholder,
|
||||
const { data: marketIds } = useDataProvider({
|
||||
dataProvider: positionsMarketsProvider,
|
||||
variables: { partyIds },
|
||||
});
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: positionsMetricsProvider,
|
||||
variables: { partyIds, marketIds: marketIds || [] },
|
||||
skip: !marketIds,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -68,14 +70,12 @@ export const PositionsManager = ({
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys}
|
||||
rowData={error ? [] : data}
|
||||
ref={gridRef}
|
||||
onMarketClick={onMarketClick}
|
||||
onClose={onClose}
|
||||
isReadOnly={isReadOnly}
|
||||
{...bottomPlaceholderProps}
|
||||
storeKey={storeKey}
|
||||
multipleKeys={partyIds.length > 1}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No positions')}
|
||||
{...gridProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -36,7 +36,6 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Position } from './positions-data-providers';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { getRowId } from './use-positions-data';
|
||||
import { PositionStatus, PositionStatusMapping } from '@vegaprotocol/types';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { PositionTableActions } from './position-actions-dropdown';
|
||||
@@ -49,7 +48,6 @@ interface Props extends TypedDataAgGrid<Position> {
|
||||
onMarketClick?: (id: string, metaKey?: boolean) => void;
|
||||
style?: CSSProperties;
|
||||
isReadOnly: boolean;
|
||||
storeKey?: string;
|
||||
multipleKeys?: boolean;
|
||||
pubKeys?: VegaWalletContextShape['pubKeys'];
|
||||
pubKey?: VegaWalletContextShape['pubKey'];
|
||||
@@ -86,6 +84,9 @@ export const AmountCell = ({ valueFormatted }: AmountCellProps) => {
|
||||
|
||||
AmountCell.displayName = 'AmountCell';
|
||||
|
||||
export const getRowId = ({ data }: { data: Position }) =>
|
||||
`${data.partyId}-${data.marketId}`;
|
||||
|
||||
export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
(
|
||||
{
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { usePositionsData } from './use-positions-data';
|
||||
import type { Position } from './positions-data-providers';
|
||||
|
||||
let mockData: Position[] = [
|
||||
{
|
||||
marketName: 'M1',
|
||||
marketId: 'market-0',
|
||||
openVolume: '1',
|
||||
},
|
||||
{
|
||||
marketName: 'M2',
|
||||
marketId: 'market-1',
|
||||
openVolume: '-1985',
|
||||
},
|
||||
{
|
||||
marketName: 'M3',
|
||||
marketId: 'market-2',
|
||||
openVolume: '0',
|
||||
},
|
||||
{
|
||||
marketName: 'M4',
|
||||
marketId: 'market-3',
|
||||
openVolume: '0',
|
||||
},
|
||||
{
|
||||
marketName: 'M5',
|
||||
marketId: 'market-4',
|
||||
openVolume: '3',
|
||||
},
|
||||
] as Position[];
|
||||
|
||||
let mockDataProviderData = {
|
||||
data: mockData,
|
||||
error: undefined,
|
||||
loading: false,
|
||||
totalCount: undefined,
|
||||
};
|
||||
|
||||
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('usePositionData Hook', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
const mockRefreshInfiniteCache = jest.fn();
|
||||
const mockGetRowNode = jest
|
||||
.fn()
|
||||
.mockImplementation((id: string) =>
|
||||
mockData.find((position) => position.marketId === id)
|
||||
);
|
||||
const partyIds = ['partyId'];
|
||||
const anUpdatedOne = {
|
||||
marketId: 'market-1',
|
||||
openVolume: '1',
|
||||
};
|
||||
const gridRef = {
|
||||
current: {
|
||||
api: {
|
||||
refreshInfiniteCache: mockRefreshInfiniteCache,
|
||||
getRowNode: mockGetRowNode,
|
||||
getModel: () => ({ getType: () => 'infinite' }),
|
||||
},
|
||||
} as unknown as AgGridReact,
|
||||
};
|
||||
|
||||
it('should return proper data', async () => {
|
||||
const { result } = renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
expect(result.current.data?.length ?? 0).toEqual(5);
|
||||
});
|
||||
|
||||
it('should call mockRefreshInfiniteCache', async () => {
|
||||
renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
await waitFor(() => {
|
||||
updateMock({ delta: [anUpdatedOne] as Position[] });
|
||||
});
|
||||
|
||||
expect(mockRefreshInfiniteCache).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('no data should return null', () => {
|
||||
mockData = [];
|
||||
mockDataProviderData = {
|
||||
...mockDataProviderData,
|
||||
data: mockData,
|
||||
loading: false,
|
||||
};
|
||||
const { result } = renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
expect(result.current.data).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Position } from './positions-data-providers';
|
||||
import { positionsMetricsProvider } from './positions-data-providers';
|
||||
import type { PositionsQueryVariables } from './__generated__/Positions';
|
||||
import { updateGridData } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { GetRowsParams } from '@vegaprotocol/datagrid';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
export const getRowId = ({
|
||||
data,
|
||||
}: {
|
||||
data: Position & { isLastPlaceholder?: boolean; id?: string };
|
||||
}) =>
|
||||
data.isLastPlaceholder && data.id
|
||||
? data.id
|
||||
: `${data.partyId}-${data.marketId}`;
|
||||
|
||||
export const usePositionsData = (
|
||||
partyIds: string[],
|
||||
gridRef: RefObject<AgGridReact>
|
||||
) => {
|
||||
const variables = useMemo<PositionsQueryVariables>(
|
||||
() => ({ partyIds }),
|
||||
[partyIds]
|
||||
);
|
||||
const dataRef = useRef<Position[] | null>(null);
|
||||
const update = useCallback(
|
||||
({ data }: { data: Position[] | null }) => {
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
}
|
||||
|
||||
const update: Position[] = [];
|
||||
const add: Position[] = [];
|
||||
data?.forEach((row) => {
|
||||
const rowNode = gridRef.current?.api?.getRowNode(
|
||||
getRowId({ data: row })
|
||||
);
|
||||
if (rowNode) {
|
||||
if (!isEqual(rowNode.data, row)) {
|
||||
update.push(row);
|
||||
}
|
||||
} else {
|
||||
add.push(row);
|
||||
}
|
||||
});
|
||||
gridRef.current?.api?.applyTransaction({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
const { data, error, loading, reload } = useDataProvider({
|
||||
dataProvider: positionsMetricsProvider,
|
||||
update,
|
||||
variables,
|
||||
});
|
||||
const getRows = useCallback(
|
||||
async ({ successCallback, startRow, endRow }: GetRowsParams<Position>) => {
|
||||
const rowsThisBlock = dataRef.current
|
||||
? dataRef.current.slice(startRow, endRow)
|
||||
: [];
|
||||
const lastRow = dataRef.current ? dataRef.current.length : 0;
|
||||
successCallback(rowsThisBlock, lastRow);
|
||||
},
|
||||
[]
|
||||
);
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
getRows,
|
||||
reload,
|
||||
};
|
||||
};
|
||||
@@ -38,7 +38,6 @@ export const ProposalsList = () => {
|
||||
columnDefs={columnDefs}
|
||||
rowData={filteredData}
|
||||
defaultColDef={defaultColDef}
|
||||
storeKey="proposedMarkets"
|
||||
getRowId={({ data }) => data.id}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
|
||||
@@ -55,7 +55,6 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/ui-toolkit",
|
||||
"version": "0.12.4"
|
||||
"version": "0.12.5"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
import { Icon } from '../icon';
|
||||
@@ -164,10 +164,9 @@ export const DropdownMenuSeparator = forwardRef<
|
||||
/**
|
||||
* Container element for submenus
|
||||
*/
|
||||
export const DropdownMenuSub = forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Sub>,
|
||||
React.ComponentProps<typeof DropdownMenuPrimitive.Sub>
|
||||
>(({ ...subProps }) => <DropdownMenuPrimitive.Sub {...subProps} />);
|
||||
export const DropdownMenuSub = (
|
||||
subProps: ComponentProps<typeof DropdownMenuPrimitive.Sub>
|
||||
) => <DropdownMenuPrimitive.Sub {...subProps} />;
|
||||
|
||||
/**
|
||||
* Container within a DropdownMenuSub specifically for the content
|
||||
@@ -201,10 +200,9 @@ export const DropdownMenuSubTrigger = forwardRef<
|
||||
* Portal to ensure menu portions are rendered outwith where they appear in the
|
||||
* DOM.
|
||||
*/
|
||||
export const DropdownMenuPortal = forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Portal>,
|
||||
React.ComponentProps<typeof DropdownMenuPrimitive.Portal>
|
||||
>(({ ...portalProps }) => <DropdownMenuPrimitive.Portal {...portalProps} />);
|
||||
export const DropdownMenuPortal = (
|
||||
portalProps: ComponentProps<typeof DropdownMenuPrimitive.Portal>
|
||||
) => <DropdownMenuPrimitive.Portal {...portalProps} />;
|
||||
|
||||
/**
|
||||
* Wraps a regular DropdownMenuItem with copy to clip board functionality
|
||||
|
||||
@@ -45,7 +45,8 @@ export const Tabs = ({
|
||||
'cursor-default': isActive,
|
||||
'text-neutral-400 hover:text-neutral-500 dark:hover:text-neutral-300':
|
||||
!isActive,
|
||||
}
|
||||
},
|
||||
'flex items-center gap-2'
|
||||
);
|
||||
const borderClass = classNames(
|
||||
'absolute bottom-[-1px] left-0 w-full h-0 border-b',
|
||||
@@ -58,6 +59,7 @@ export const Tabs = ({
|
||||
value={child.props.id}
|
||||
className={triggerClass}
|
||||
>
|
||||
{child.props.indicator}
|
||||
{child.props.name}
|
||||
<span className={borderClass} />
|
||||
</TabsPrimitive.Trigger>
|
||||
@@ -87,6 +89,7 @@ interface TabProps {
|
||||
children: ReactNode;
|
||||
id: string;
|
||||
name: string;
|
||||
indicator?: ReactNode;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ export type ToastContent = JSX.Element | undefined;
|
||||
|
||||
type ToastState = 'initial' | 'showing' | 'expired';
|
||||
|
||||
type WithdrawalInfoMeta = { withdrawalId: string | undefined };
|
||||
|
||||
export type Toast = {
|
||||
id: string;
|
||||
intent: Intent;
|
||||
@@ -26,6 +28,9 @@ export type Toast = {
|
||||
onClose?: () => void;
|
||||
signal?: 'close';
|
||||
loader?: boolean;
|
||||
hidden?: boolean;
|
||||
// meta information
|
||||
meta?: WithdrawalInfoMeta | undefined;
|
||||
};
|
||||
|
||||
type ToastProps = Toast & {
|
||||
|
||||
@@ -13,11 +13,13 @@ import { Portal } from '@radix-ui/react-portal';
|
||||
type ToastsContainerProps = {
|
||||
toasts: Toasts;
|
||||
order: 'asc' | 'desc';
|
||||
showHidden?: boolean;
|
||||
};
|
||||
|
||||
export const ToastsContainer = ({
|
||||
toasts,
|
||||
order = 'asc',
|
||||
showHidden = false,
|
||||
}: ToastsContainerProps) => {
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const closeAll = useToasts((store) => store.closeAll);
|
||||
@@ -41,6 +43,10 @@ export const ToastsContainer = ({
|
||||
};
|
||||
}, [count, order, toasts]);
|
||||
|
||||
const validToasts = Object.values(toasts).filter(
|
||||
(t) => !t.hidden || showHidden
|
||||
);
|
||||
|
||||
return (
|
||||
<Portal
|
||||
ref={ref as Ref<HTMLDivElement>}
|
||||
@@ -71,14 +77,16 @@ export const ToastsContainer = ({
|
||||
'flex-col-reverse': order === 'desc',
|
||||
})}
|
||||
>
|
||||
{toasts &&
|
||||
Object.values(toasts).map((toast) => {
|
||||
return (
|
||||
<li key={toast.id}>
|
||||
<Toast {...toast} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{validToasts.length > 0 &&
|
||||
validToasts
|
||||
.filter((t) => !t.hidden || showHidden)
|
||||
.map((toast) => {
|
||||
return (
|
||||
<li key={toast.id}>
|
||||
<Toast {...toast} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
title={t('Dismiss all toasts')}
|
||||
size="sm"
|
||||
@@ -89,7 +97,7 @@ export const ToastsContainer = ({
|
||||
'opacity-0 group-hover:opacity-50 hover:!opacity-100',
|
||||
'text-sm text-black dark:text-white bg-white dark:bg-black hover:!bg-white hover:dark:!bg-black',
|
||||
{
|
||||
hidden: Object.keys(toasts).length === 0,
|
||||
hidden: validToasts.length === 0,
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
|
||||
@@ -46,12 +46,20 @@ type Actions = {
|
||||
* Arbitrary removes all toasts
|
||||
*/
|
||||
removeAll: () => void;
|
||||
/**
|
||||
* Checks if a given toasts exists in the collection
|
||||
*/
|
||||
hasToast: (id: string) => boolean;
|
||||
/**
|
||||
* Closes toast by meta
|
||||
*/
|
||||
closeBy: (meta: Toast['meta']) => void;
|
||||
};
|
||||
|
||||
type ToastsStore = State & Actions;
|
||||
|
||||
export const useToasts = create<ToastsStore>()(
|
||||
immer((set) => ({
|
||||
immer((set, get) => ({
|
||||
toasts: {},
|
||||
count: 0,
|
||||
add: (toast) =>
|
||||
@@ -97,6 +105,18 @@ export const useToasts = create<ToastsStore>()(
|
||||
}
|
||||
}),
|
||||
removeAll: () => set({ toasts: {}, count: 0 }),
|
||||
hasToast: (id) => get().toasts[id] != null,
|
||||
closeBy: (meta) => {
|
||||
if (!meta) return;
|
||||
set((state) => {
|
||||
const found = Object.values(state.toasts).find((t) =>
|
||||
isEqual(t.meta, meta)
|
||||
);
|
||||
if (found) {
|
||||
found.signal = 'close';
|
||||
}
|
||||
});
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import type { ITooltipParams } from 'ag-grid-community';
|
||||
|
||||
const tooltipContentClasses =
|
||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs break-word';
|
||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs text-black dark:text-white break-word';
|
||||
export interface TooltipProps {
|
||||
children: React.ReactElement;
|
||||
description?: string | ReactNode;
|
||||
|
||||
@@ -29,14 +29,21 @@ describe('number utils', () => {
|
||||
{ v: new BigNumber(123000), d: 3, o: '123.00', q: 0.1 },
|
||||
{ v: new BigNumber(123000), d: 1, o: '12,300.00', q: 0.1 },
|
||||
{ v: new BigNumber(123001000), d: 2, o: '1,230,010.00', q: 0.1 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230', q: 100 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 100 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 0.1 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 1 },
|
||||
{
|
||||
v: BigNumber('123456789123456789'),
|
||||
d: 10,
|
||||
o: '12,345,678.9123457',
|
||||
o: '12,345,678.91234568',
|
||||
q: '0.00003846',
|
||||
},
|
||||
{
|
||||
v: BigNumber('123456789123456789'),
|
||||
d: 10,
|
||||
o: '12,345,678.91234568',
|
||||
q: '1',
|
||||
},
|
||||
])(
|
||||
'formats with addDecimalsFormatNumberQuantum given number correctly',
|
||||
({ v, d, o, q }) => {
|
||||
@@ -70,80 +77,80 @@ describe('number utils', () => {
|
||||
])('formats given number correctly', ({ v, d, o }) => {
|
||||
expect(formatNumberPercentage(v, d)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNumberParts', () => {
|
||||
it.each([
|
||||
{ v: null, d: 3, o: ['0', '000'] },
|
||||
{ v: undefined, d: 3, o: ['0', '000'] },
|
||||
{ v: new BigNumber(123), d: 3, o: ['123', '00'] },
|
||||
{ v: new BigNumber(123.123), d: 3, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 6, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 0, o: ['123', ''] },
|
||||
{ v: new BigNumber(123), d: undefined, o: ['123', '00'] },
|
||||
{
|
||||
v: new BigNumber(30000),
|
||||
d: undefined,
|
||||
o: ['30,000', '00'],
|
||||
},
|
||||
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
|
||||
expect(toNumberParts(v, d)).toStrictEqual(o);
|
||||
describe('toNumberParts', () => {
|
||||
it.each([
|
||||
{ v: null, d: 3, o: ['0', '000'] },
|
||||
{ v: undefined, d: 3, o: ['0', '000'] },
|
||||
{ v: new BigNumber(123), d: 3, o: ['123', '00'] },
|
||||
{ v: new BigNumber(123.123), d: 3, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 6, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 0, o: ['123', ''] },
|
||||
{ v: new BigNumber(123), d: undefined, o: ['123', '00'] },
|
||||
{
|
||||
v: new BigNumber(30000),
|
||||
d: undefined,
|
||||
o: ['30,000', '00'],
|
||||
},
|
||||
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
|
||||
expect(toNumberParts(v, d)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNumeric', () => {
|
||||
it.each([
|
||||
{ i: null, o: false },
|
||||
{ i: undefined, o: false },
|
||||
{ i: 1, o: true },
|
||||
{ i: '1', o: true },
|
||||
{ i: '-1', o: true },
|
||||
{ i: 0.1, o: true },
|
||||
{ i: '.1', o: true },
|
||||
{ i: '-.1', o: true },
|
||||
{ i: 123, o: true },
|
||||
{ i: -123, o: true },
|
||||
{ i: '123', o: true },
|
||||
{ i: '123.01', o: true },
|
||||
{ i: '-123.01', o: true },
|
||||
{ i: '--123.01', o: false },
|
||||
{ i: '123.', o: false },
|
||||
{ i: '123.1.1', o: false },
|
||||
{ i: BigInt(123), o: true },
|
||||
{ i: BigInt(-1), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: new BigNumber(123.123), o: true },
|
||||
{ i: new BigNumber(123.123).toString(), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: Infinity, o: false },
|
||||
{ i: NaN, o: false },
|
||||
])(
|
||||
'returns correct results',
|
||||
({
|
||||
i,
|
||||
o,
|
||||
}: {
|
||||
i: number | string | undefined | null | BigNumber | bigint;
|
||||
o: boolean;
|
||||
}) => {
|
||||
expect(isNumeric(i)).toStrictEqual(o);
|
||||
}
|
||||
);
|
||||
});
|
||||
describe('isNumeric', () => {
|
||||
it.each([
|
||||
{ i: null, o: false },
|
||||
{ i: undefined, o: false },
|
||||
{ i: 1, o: true },
|
||||
{ i: '1', o: true },
|
||||
{ i: '-1', o: true },
|
||||
{ i: 0.1, o: true },
|
||||
{ i: '.1', o: true },
|
||||
{ i: '-.1', o: true },
|
||||
{ i: 123, o: true },
|
||||
{ i: -123, o: true },
|
||||
{ i: '123', o: true },
|
||||
{ i: '123.01', o: true },
|
||||
{ i: '-123.01', o: true },
|
||||
{ i: '--123.01', o: false },
|
||||
{ i: '123.', o: false },
|
||||
{ i: '123.1.1', o: false },
|
||||
{ i: BigInt(123), o: true },
|
||||
{ i: BigInt(-1), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: new BigNumber(123.123), o: true },
|
||||
{ i: new BigNumber(123.123).toString(), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: Infinity, o: false },
|
||||
{ i: NaN, o: false },
|
||||
])(
|
||||
'returns correct results',
|
||||
({
|
||||
i,
|
||||
o,
|
||||
}: {
|
||||
i: number | string | undefined | null | BigNumber | bigint;
|
||||
o: boolean;
|
||||
}) => {
|
||||
expect(isNumeric(i)).toStrictEqual(o);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('toDecimal', () => {
|
||||
it.each([
|
||||
{ v: 0, o: '1' },
|
||||
{ v: 1, o: '0.1' },
|
||||
{ v: 2, o: '0.01' },
|
||||
{ v: 3, o: '0.001' },
|
||||
{ v: 4, o: '0.0001' },
|
||||
{ v: 5, o: '0.00001' },
|
||||
{ v: 6, o: '0.000001' },
|
||||
{ v: 7, o: '0.0000001' },
|
||||
{ v: 8, o: '0.00000001' },
|
||||
{ v: 9, o: '0.000000001' },
|
||||
])('formats with toNumber given number correctly', ({ v, o }) => {
|
||||
expect(toDecimal(v)).toStrictEqual(o);
|
||||
describe('toDecimal', () => {
|
||||
it.each([
|
||||
{ v: 0, o: '1' },
|
||||
{ v: 1, o: '0.1' },
|
||||
{ v: 2, o: '0.01' },
|
||||
{ v: 3, o: '0.001' },
|
||||
{ v: 4, o: '0.0001' },
|
||||
{ v: 5, o: '0.00001' },
|
||||
{ v: 6, o: '0.000001' },
|
||||
{ v: 7, o: '0.0000001' },
|
||||
{ v: 8, o: '0.00000001' },
|
||||
{ v: 9, o: '0.000000001' },
|
||||
])('formats with toNumber given number correctly', ({ v, o }) => {
|
||||
expect(toDecimal(v)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,8 @@ export const addDecimalsFormatNumberQuantum = (
|
||||
if (isNaN(Number(quantum))) {
|
||||
return addDecimalsFormatNumber(rawValue, decimalPlaces);
|
||||
}
|
||||
const numberDP = Math.max(0, Math.log10(100 / Number(quantum)));
|
||||
const quantumValue = addDecimal(quantum, decimalPlaces);
|
||||
const numberDP = Math.max(0, Math.log10(100 / Number(quantumValue)));
|
||||
return addDecimalsFormatNumber(rawValue, decimalPlaces, Math.ceil(numberDP));
|
||||
};
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@ describe('formatValue', () => {
|
||||
{ v: 123000, d: 3, o: '123.00', q: '0.1' },
|
||||
{ v: 123000, d: 1, o: '12,300.00', q: '0.1' },
|
||||
{ v: 123001000, d: 2, o: '1,230,010.00', q: '0.1' },
|
||||
{ v: 123001, d: 2, o: '1,230', q: '100' },
|
||||
{ v: 123001, d: 2, o: '1,230.01', q: '100' },
|
||||
{ v: 123001, d: 2, o: '1,230.01', q: '0.1' },
|
||||
{
|
||||
v: '123456789123456789',
|
||||
d: 10,
|
||||
o: '12,345,678.9123457',
|
||||
o: '12,345,678.91234568',
|
||||
q: '0.00003846',
|
||||
},
|
||||
])(
|
||||
@@ -38,7 +38,13 @@ describe('formatValue', () => {
|
||||
});
|
||||
describe('formatRange', () => {
|
||||
it.each([
|
||||
{ min: 123000, max: 12300011111, d: 5, o: '1.23 - 123,000.111', q: '0.1' },
|
||||
{
|
||||
min: 123000,
|
||||
max: 12300011111,
|
||||
d: 5,
|
||||
o: '1.23 - 123,000.11111',
|
||||
q: '0.1',
|
||||
},
|
||||
{
|
||||
min: 123000,
|
||||
max: 12300011111,
|
||||
@@ -57,7 +63,7 @@ describe('formatRange', () => {
|
||||
min: 123001000,
|
||||
max: 12300011111,
|
||||
d: 2,
|
||||
o: '1,230,010 - 123,000,111',
|
||||
o: '1,230,010.00 - 123,000,111.11',
|
||||
q: '100',
|
||||
},
|
||||
])(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user