Compare commits

..
Author SHA1 Message Date
Dexter 7edb5d20c3 chore: hack fallbaclk to imaginary vesting contract 2023-01-31 19:42:05 +00:00
mattrussell36 f7f00dc7bb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 18:07:31 +00:00
macqbat d151b6e923 chore(trading): add sub statuses of opening auction trading mode (#2797) 2023-01-31 17:31:55 +01:00
Sam Keen 8f8e9c1061 feat(ui-toolkit): announcement banner to highlight mainnet sims (#2800) 2023-01-31 16:12:44 +00:00
m.ray b40358a723 fix(trading): consolidate view as user mode (#2778) 2023-01-31 16:04:52 +00:00
Art 01f0934da3 chore(ui-toolkit): new colour palette (#2783) 2023-01-31 16:33:16 +01:00
mattrussell36 df7755dbc4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 12:06:33 +00:00
Joe Tsang 9073234040 test(ci): fix tags in cypress workflow (#2788) 2023-01-31 11:35:42 +00:00
Art 053cd0fa27 fix(trading): asset option on mobile got overflown (#2782) 2023-01-31 11:10:55 +01:00
mattrussell36 961dc88dc7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 06:08:30 +00:00
mattrussell36 b16607a844 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 00:13:30 +00:00
Matthew Russell d0d804631c chore(ci): add nx scope linting for pr title (#2781) 2023-01-30 21:06:18 +00:00
daro-maj 3e778a88dc chore: capsule tests refactor and increase deposit ac (#2773) 2023-01-30 20:35:58 +01:00
Edd dbb09c9c79 fix(explorer): correct calculation for whether party txs list has more txs (#2774) 2023-01-30 18:23:09 +00:00
Edd 06f44b67de fix(explorer): tx viewer dropdown did not correctly represent state (#2780) 2023-01-30 18:17:47 +00:00
Joe Tsang 1861852852 test(2704): proposals with market (#2775) 2023-01-30 18:06:04 +00:00
mattrussell36 3ee0eee80d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 12:09:00 +00:00
mattrussell36 69ce870a81 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 06:06:56 +00:00
mattrussell36 d8467c3206 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 00:12:51 +00:00
mattrussell36 de87f40a01 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 18:08:12 +00:00
mattrussell36 f22fb56afd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 12:06:12 +00:00
mattrussell36 f2627e6ebf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 06:07:03 +00:00
mattrussell36 24e6a13d93 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 00:13:02 +00:00
mattrussell36 b56050ad1c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 18:07:37 +00:00
mattrussell36 cff83ed062 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 12:06:05 +00:00
mattrussell36 8ecce874b0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 06:08:30 +00:00
Bartłomiej Głownia 00e319b3c6 chore: handle not found errors as correct response (#2759) 2023-01-27 18:35:45 -08:00
mattrussell36 05559f3ea0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 00:11:14 +00:00
m.ray 4b3b5c322a fix: trades grid colors (#2766) 2023-01-27 18:55:20 +00:00
Bartłomiej Głownia b1280c8285 fix: hide close button if openVolume is zero (#2768) 2023-01-27 18:44:20 +00:00
Edd 1e0e1c7859 feat(explorer): add long text component for hashes and tx viewer (#2765) 2023-01-27 18:37:59 +00:00
mattrussell36 ec2bb81ec8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 18:08:36 +00:00
m.ray fe95c6fcbc fix: show markets link & deal ticket validation (#2763) 2023-01-27 18:10:20 +01:00
macqbat 853ec8f69c chore(2351): improve handling wallet errors (#2729) 2023-01-27 15:45:29 +01:00
128 changed files with 2323 additions and 1312 deletions
+1 -2
View File
@@ -64,12 +64,11 @@ jobs:
######
- name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
CYPRESS_grepTags: ${{ inputs.tags }}
######
## Upload logs
+7 -5
View File
@@ -8,14 +8,16 @@ jobs:
lint_pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.14.0
- name: Install commitlint cli and config
run: npm install @commitlint/cli @commitlint/config-conventional
- name: Create config
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
- name: Install root dependencies
run: yarn install
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
@@ -63,7 +63,7 @@ export const TxDataView = ({ txData, blockData }: TxDataViewProps) => {
<Select
placeholder="View as..."
onChange={(v) => setShowTxData(v.target.value as ShowTxDataType)}
value={'JSON'}
value={showTxData}
>
<option value={'JSON'}>JSON</option>
<option value={'base64'}>Base64</option>
+5 -4
View File
@@ -5,6 +5,7 @@ import type {
BlockExplorerTransactions,
} from '../routes/types/block-explorer-response';
import { DATA_SOURCES } from '../config';
import isNumber from 'lodash/isNumber';
export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[];
@@ -54,15 +55,15 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => {
} = useFetch<BlockExplorerTransactions>(url, {}, false);
useEffect(() => {
if (data?.transactions?.length) {
if (data && isNumber(data?.transactions?.length)) {
setTxsState((prev) => ({
txsData: [...prev.txsData, ...data.transactions],
hasMoreTxs: true,
hasMoreTxs: data.transactions.length > 0,
lastCursor:
data.transactions[data.transactions.length - 1].cursor || '',
data.transactions[data.transactions.length - 1]?.cursor || '',
}));
}
}, [setTxsState, data?.transactions]);
}, [setTxsState, data]);
const loadTxs = useCallback(() => {
return refetch({
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

File diff suppressed because it is too large Load Diff
+13 -1
View File
@@ -16,4 +16,16 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
#Test configuration variables
CYPRESS_FAIRGROUND=false
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_WALLET_API_TOKEN=
CYPRESS_VEGA_WALLET_API_TOKEN=jpeAkxcffzTLCzBX2m5TZIp3hF500YZhHwESwNKOGksdGPXeeIznXypaDfpNe2M9
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
CYPRESS_VEGA_ENV=CUSTOM
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
@@ -2,56 +2,59 @@
"changes": {
"decimalPlaces": "5",
"positionDecimalPlaces": "5",
"lpPriceRange": "10",
"instrument": {
"name": "Oranges Daily",
"code": "ORANGES.24h",
"name": "Token test market",
"code": "Token.24h",
"future": {
"settlementAsset": "8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4",
"quoteName": "tEuro",
"settlementDataDecimals": 5,
"settlementAsset": "fBTC",
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
}
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER"
},
"conditions": [
"external": {
"oracle": {
"signers": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.ETH.value",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": "0"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
]
}
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "vegaprotocol.builtin.timestamp",
"type": "TYPE_TIMESTAMP"
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
"operator": "OPERATOR_EQUALS",
"value": "true"
}
]
}
@@ -59,7 +62,7 @@
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.BTC.value",
"tradingTerminationProperty": "vegaprotocol.builtin.timestamp"
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
@@ -1,83 +1,78 @@
{
"marketId": "315a8e48db0a292c92b617264728048c82c20efc922c75fd292fc54e5c727c81",
"changes": {
"instrument": {
"code": "ORANGES.24h",
"future": {
"quoteName": "tEuro",
"settlementDataDecimals": 5,
"dataSourceSpecForSettlementData": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
"instrument": {
"code": "TEST.24h",
"future": {
"quoteName": "fUSDC",
"settlementDataDecimals": 5,
"dataSourceSpecForSettlementData": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.ETH.value",
"type": "TYPE_INTEGER"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
}
}
],
"filters": [
{
"key": {
"name": "vegaprotocol.builtin.timestamp",
"type": "TYPE_TIMESTAMP"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
}
]
}
]
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.BTC.value",
"tradingTerminationProperty": "vegaprotocol.builtin.timestamp"
}
]
}
]
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.ETH.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.3
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.3
}
}
}
@@ -1 +1 @@
123
0123245
@@ -11,6 +11,7 @@ const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const dialogCloseButton = '[data-testid="dialog-close"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const openProposals = '[data-testid="open-proposals"]';
const closedProposals = '[data-testid="closed-proposals"]';
const proposalVoteProgressForPercentage =
'[data-testid="vote-progress-indicator-percentage-for"]';
const proposalVoteProgressAgainstPercentage =
@@ -22,7 +23,9 @@ const proposalVoteProgressAgainstTokens =
const changeVoteButton = '[data-testid="change-vote-button"]';
const proposalDetailsTitle = '[data-testid="proposal-title"]';
const proposalDetailsDescription = '[data-testid="proposal-description"]';
const proposalStatus = '[data-testid="proposal-status"]';
const rawProposalData = '[data-testid="proposal-data"]';
const votesTable = '[data-testid="votes-table"]';
const minVoteButton = '[data-testid="min-vote"]';
const maxVoteButton = '[data-testid="max-vote"]';
const voteButtons = '[data-testid="vote-buttons"]';
@@ -37,6 +40,10 @@ const txTimeout = Cypress.env('txTimeout');
const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 };
const minCloseDays = 2;
const maxCloseDays = 3;
const requiredParticipation = 0.001;
const governanceProposalType = {
NETWORK_PARAMETER: 'Network parameter',
NEW_MARKET: 'New market',
@@ -65,21 +72,6 @@ context(
network_parameters['governance.proposal.freeform.requiredMajority'] *
100
).as('requiredMajority');
cy.wrap(
network_parameters[
'governance.proposal.freeform.requiredParticipation'
] * 100
).as('requiredParticipation');
cy.wrap(
network_parameters['governance.proposal.freeform.minClose'].split(
'h'
)[0] / 24
).as('minCloseDays');
cy.wrap(
network_parameters['governance.proposal.freeform.maxClose'].split(
'h'
)[0] / 24
).as('maxCloseDays');
cy.wrap(
network_parameters['governance.proposal.freeform.minClose'].split(
'h'
@@ -108,21 +100,6 @@ context(
0.00001,
'Asserting that value is at least 0.00001 for network parameter minVoterBalance'
);
assert.isAtLeast(
parseFloat(this.requiredParticipation),
0.00001,
'Asserting that value is at least 0.00001 for network parameter requiredParticipation'
);
assert.isAtLeast(
parseInt(this.minCloseDays),
1,
'Asserting that value is at least 1 for network parameter minCloseDays'
);
assert.isAtLeast(
parseInt(this.maxCloseDays),
parseInt(this.minCloseDays + 1),
'Asserting that network parameter maxCloseDays is at least 1 day higher than minCloseDays'
);
// workaround for first eth tx hanging
associateTokenStartOfTests();
}
@@ -182,10 +159,10 @@ context(
this.minProposerBalance
);
let proposalDays = [
this.minCloseDays + 1,
this.maxCloseDays,
this.minCloseDays + 3,
this.minCloseDays + 2,
minCloseDays + 1,
maxCloseDays,
minCloseDays + 3,
minCloseDays + 2,
];
for (var index = 0; index < proposalDays.length; index++) {
cy.go_to_make_new_proposal(governanceProposalType.RAW);
@@ -227,6 +204,7 @@ context(
cy.navigate_to('validators');
cy.click_on_validator_from_list(0);
cy.staking_validator_page_add_stake('2');
cy.close_staking_dialog();
cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2');
@@ -297,10 +275,12 @@ context(
});
});
// Skipping test due to bug: #1320
it.skip('Newly created freeform proposals list - shows proposal participation - both met and not', function () {
createFreeformProposal(this.minProposerBalance);
cy.get_submitted_proposal_from_proposal_list()
// 3001-VOTE-071
it('Newly created freeform proposals list - shows proposal participation - both met and not', function () {
const proposalTitle = generateProposalTitle();
createFreeformProposal(this.minProposerBalance, proposalTitle);
cy.get_submitted_proposal_from_proposal_list(proposalTitle)
.as('submittedProposal')
.within(() => {
// 3001-VOTE-039
@@ -312,7 +292,7 @@ context(
.invoke('text')
.then((totalSupply) => {
let tokensRequiredToAchieveResult = parseFloat(
(totalSupply.replace(/,/g, '') * this.requiredParticipation) / 100
(totalSupply.replace(/,/g, '') * requiredParticipation) / 100
).toFixed(2);
cy.ensure_specified_unstaked_tokens_are_associated(
tokensRequiredToAchieveResult
@@ -321,16 +301,17 @@ context(
cy.get('@submittedProposal').within(() =>
cy.get(viewProposalButton).click()
);
cy.get_proposal_information_from_table('Participation met')
cy.get_proposal_information_from_table('Token participation met')
.contains('👍')
.should('be.visible');
cy.navigate_to('proposals');
cy.get('@submittedProposal').within(() =>
cy.get(voteStatus).should('have.text', 'Participation met')
cy.get(voteStatus).should('have.text', 'Set to pass')
);
});
});
// 3001-VOTE-055
it('Newly created raw proposal details - shows proposal title and full description', function () {
createRawProposal(this.minProposerBalance);
cy.get('@rawProposal').then((rawProposal) => {
@@ -357,6 +338,7 @@ context(
});
});
// 3001-VOTE-043
it('Newly created freeform proposal details - shows proposed and closing dates', function () {
const closingVoteHrs = '72';
const proposalTitle = generateProposalTitle();
@@ -399,6 +381,7 @@ context(
it('Newly created proposal details - shows default status set to fail', function () {
// 3001-VOTE-037
// 3001-VOTE-040
// 3001-VOTE-067
createRawProposal(this.minProposerBalance);
cy.get('@rawProposal').then((rawProposal) => {
cy.get_submitted_proposal_from_proposal_list(
@@ -413,15 +396,17 @@ context(
.should('be.visible');
// 3001-VOTE-062
// 3001-VOTE-040
// 3001-VOTE-070
cy.get_proposal_information_from_table('Token majority met')
.contains('👎')
.should('be.visible');
// 3001-VOTE-068
cy.get_proposal_information_from_table('Token participation met')
.contains('👎')
.should('be.visible');
});
// 3001-VOTE-080 3001-VOTE-090
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
it('Newly created proposal details - ability to vote for and against proposal - with minimum required tokens associated', function () {
createRawProposal(this.minProposerBalance);
cy.get('@rawProposal').then((rawProposal) => {
@@ -446,7 +431,7 @@ context(
.contains(votedDate)
.should('be.visible');
});
cy.get(proposalVoteProgressForPercentage)
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%')
.and('be.visible');
cy.get(proposalVoteProgressAgainstPercentage)
@@ -466,10 +451,10 @@ context(
.and('be.visible');
// 3001-VOTE-061
cy.get_proposal_information_from_table('Participation required')
.contains(`${this.requiredParticipation}%`)
.contains(`${requiredParticipation}%`)
.should('be.visible');
// 3001-VOTE-066
cy.get_proposal_information_from_table('Majority Required')
cy.get_proposal_information_from_table('Majority Required') // 3001-VOTE-073
.contains(`${parseFloat(this.requiredMajority).toFixed(2)}%`)
.should('be.visible');
cy.get_proposal_information_from_table('Number of voting parties')
@@ -512,7 +497,7 @@ context(
.invoke('text')
.then((totalSupply) => {
let tokensRequiredToAchieveResult = parseFloat(
(totalSupply.replace(/,/g, '') * this.requiredParticipation) / 100
(totalSupply.replace(/,/g, '') * requiredParticipation) / 100
).toFixed(2);
cy.ensure_specified_unstaked_tokens_are_associated(
tokensRequiredToAchieveResult
@@ -536,6 +521,11 @@ context(
cy.get(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.get_proposal_information_from_table(
'Total tokens voted percentage'
)
.should('have.text', '0.00%')
.and('be.visible');
cy.get_proposal_information_from_table('Tokens for proposal')
.should('have.text', tokensRequiredToAchieveResult)
.and('be.visible');
@@ -588,6 +578,7 @@ context(
);
});
// 3001-VOTE-006
it('Creating a proposal - proposal rejected - able to access rejected proposals', function () {
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
@@ -649,6 +640,7 @@ context(
cy.get(dialogCloseButton).click();
});
// 3002-PROP-009
it('Unable to create a proposal - when some but not enough tokens are associated', function () {
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance - 0.000001
@@ -669,7 +661,7 @@ context(
});
it('Unable to create a freeform proposal - when json parent section contains unexpected field', function () {
// 3001-VOTE-038
// 3001-VOTE-038 3002-PROP-013 3002-PROP-014
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
@@ -695,6 +687,9 @@ context(
'Invalid params: the transaction is malformed'
);
cy.get(dialogCloseButton).click();
cy.get(rawProposalData)
.invoke('val')
.should('contain', "i shouldn't be here");
});
it('Unable to create a freeform proposal - when json terms section contains unexpected field', function () {
@@ -792,6 +787,107 @@ context(
cy.contains('You voted: Against').should('be.visible');
});
// 3001-VOTE-006
it('Able to view enacted proposal', function () {
cy.createMarket();
cy.reload();
cy.wait_for_spinner();
cy.get(closedProposals).within(() => {
cy.get(proposalDetailsTitle).should(
'have.text',
'Add Lorem Ipsum market'
);
cy.get(proposalStatus).should('have.text', 'Enacted ');
cy.get(viewProposalButton).click();
});
cy.getByTestId('proposal-type').should('have.text', 'New market');
cy.get_proposal_information_from_table('State')
.contains('Enacted')
.and('be.visible');
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
});
// 3001-VOTE-047
it('Able to enact freeform proposal', function () {
const proposalTitle = 'Add New free form proposal with short enactment';
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
cy.sendWalletTxFreeFormProposal();
cy.navigate_to('proposals');
cy.reload();
cy.wait_for_spinner();
cy.contains(proposalTitle)
.parentsUntil('[data-testid="proposals-list-item"]')
.within(() => cy.get(viewProposalButton).click());
cy.get_proposal_information_from_table('State')
.contains('Open')
.and('be.visible');
cy.vote_for_proposal('for');
cy.get_proposal_information_from_table('State')
.contains('Enacted', epochTimeout)
.and('be.visible');
});
// 3001-VOTE-046 3001-VOTE-044 3001-VOTE-074 3001-VOTE-074
it('Able to enact proposal by voting', function () {
const proposalTitle = 'Add New proposal with short enactment';
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
cy.sendWalletTxUpdateNetworkProposal();
cy.navigate_to('proposals');
cy.reload();
cy.wait_for_spinner();
cy.contains(proposalTitle)
.parentsUntil('[data-testid="proposals-list-item"]')
.within(() => cy.get(viewProposalButton).click());
cy.get_proposal_information_from_table('State')
.contains('Open')
.and('be.visible');
cy.vote_for_proposal('for');
cy.get_proposal_information_from_table('State') // 3001-VOTE-047
.contains('Passed', txTimeout)
.and('be.visible');
cy.get_proposal_information_from_table('State')
.contains('Enacted', epochTimeout)
.and('be.visible');
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
cy.get(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
});
// 3001-VOTE-048 3001-VOTE-049
it('Able to fail proposal due to lack of participation', function () {
const proposalTitle = 'Add New free form proposal with short enactment';
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
cy.sendWalletTxFreeFormProposal();
cy.navigate_to('proposals');
cy.reload();
cy.wait_for_spinner();
cy.contains(proposalTitle)
.parentsUntil('[data-testid="proposals-list-item"]')
.within(() => cy.get(viewProposalButton).click());
cy.get_proposal_information_from_table('State')
.contains('Open')
.and('be.visible');
cy.get_proposal_information_from_table('State') // 3001-VOTE-047
.contains('Declined', txTimeout)
.and('be.visible');
cy.get_proposal_information_from_table('Rejection reason')
.contains('PROPOSAL_ERROR_PARTICIPATION_THRESHOLD_NOT_REACHED')
.and('be.visible');
});
function createRawProposal(proposerBalance) {
if (proposerBalance)
cy.ensure_specified_unstaked_tokens_are_associated(proposerBalance);
@@ -1,3 +1,7 @@
const proposalListItem = '[data-testid="proposals-list-item"]';
const openProposals = '[data-testid="open-proposals"]';
const proposalType = '[data-testid="proposal-type"]';
const proposalDetails = '[data-testid="proposal-details"]';
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]';
const proposalValidationDeadline =
@@ -209,18 +213,15 @@ context(
);
});
// skipped because no markets available to select in capsule
it.skip('Able to submit update market proposal', function () {
const marketId =
'315a8e48db0a292c92b617264728048c82c20efc922c75fd292fc54e5c727c81';
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update asset proposal');
cy.get(newProposalTitle).type('Test update market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select(marketId);
cy.get(proposalMarketSelect).select('Test market 1');
cy.get('[data-testid="update-market-details"]').within(() => {
cy.get('dd').eq(0).should('have.text', 'Oranges Daily');
cy.get('dd').eq(1).should('have.text', 'ORANGES.24h');
cy.get('dd').eq(2).should('have.text', marketId);
cy.get('dd').eq(0).should('have.text', 'Test market 1');
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
cy.get('dd').eq(2).should('not.be.empty');
});
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
@@ -281,12 +282,28 @@ context(
});
it('Able to submit update asset proposal using min deadline', function () {
const assetId =
'ebcd94151ae1f0d39a4bde3b21a9c7ae81a80ea4352fb075a92e07608d9c953d';
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails();
cy.get(minVoteDeadline).click();
cy.get(minEnactDeadline).click();
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.wait_for_proposal_submitted();
cy.navigate_to('proposals');
cy.get(openProposals).within(() => {
cy.get(proposalType)
.contains('Update asset')
.parentsUntil(proposalListItem)
.within(() => {
cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029
cy.getByTestId('view-proposal-btn').click();
});
});
cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044
.invoke('text')
.should('not.be.empty');
});
it('Able to submit update asset proposal using max deadline', function () {
@@ -20,7 +20,10 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
it('Able to connect public key via wallet', function () {
verifyConnectedToPubKey();
cy.getByTestId('currency-title').should('contain.text', 'USDC (fake)');
cy.getByTestId('currency-title', Cypress.env('epochTimeout')).should(
'contain.text',
'USDC (fake)'
);
});
it('Able to connect public key using url', function () {
@@ -46,6 +46,6 @@ Cypress.Commands.add('verify_page_header', (text) => {
});
Cypress.Commands.add('wait_for_spinner', () => {
cy.get(navigation.pageSpinner).should('exist');
cy.get(navigation.pageSpinner, Cypress.env('epochTimeout')).should('exist');
cy.get(navigation.pageSpinner, { timeout: 20000 }).should('not.exist');
});
+1
View File
@@ -6,6 +6,7 @@ import './governance.functions.js';
import './wallet-eth.functions.js';
import './wallet-teardown.functions.js';
import './wallet-vega.functions.js';
import './proposal.functions.js';
import registerCypressGrep from '@cypress/grep';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
@@ -0,0 +1,103 @@
import { addSeconds, millisecondsToSeconds } from 'date-fns';
const walletName = Cypress.env('vegaWalletName');
const walletPubKey = Cypress.env('vegaWalletPublicKey');
const walletLocation = Cypress.env('vegaWalletLocation');
const walletPassphraseFile = './src/fixtures/wallet/passphrase';
Cypress.Commands.add('sendWalletTxUpdateNetworkProposal', () => {
const MIN_CLOSE_SEC = 8;
const MIN_ENACT_SEC = 5;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
cy.exec(
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
"proposalSubmission": {
"rationale": {
"title": "Add New proposal with short enactment",
"description": "E2E enactment test"
},
"terms": {
"updateNetworkParameter": {
"changes": {
"key": "governance.proposal.updateNetParam.minProposerBalance",
"value": "2"
}
},
"closingTimestamp": ${closingTimestamp},
"enactmentTimestamp": ${enactmentTimestamp}
}
}
}' --home ${walletLocation}`,
{ failOnNonZeroExit: false }
)
.its('stderr')
.should('contain', '');
});
Cypress.Commands.add('sendWalletTxUpdateAssetProposal', () => {
const MIN_CLOSE_SEC = 8;
const MIN_ENACT_SEC = 5;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
cy.exec(
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
"proposalSubmission": {
"rationale": {
"title": "Update Asset set to fail",
"description": "E2E fail test"
},
"terms": {
"updateAsset": {
"assetId": "ebcd94151ae1f0d39a4bde3b21a9c7ae81a80ea4352fb075a92e07608d9c953d",
"changes": {
"quantum": "1",
"erc20": {
"withdrawThreshold": "10",
"lifetimeLimit": "10"
}
}
},
"closingTimestamp": ${closingTimestamp},
"enactmentTimestamp": ${enactmentTimestamp}
}
}
}' --home ${walletLocation}`,
{ failOnNonZeroExit: false }
)
.its('stderr')
.should('contain', '');
});
Cypress.Commands.add('sendWalletTxFreeFormProposal', () => {
const MIN_CLOSE_SEC = 5;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
cy.exec(
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
"proposalSubmission": {
"rationale": {
"title": "Add New free form proposal with short enactment",
"description": "E2E enactment test"
},
"terms": {
"newFreeform": {},
"closingTimestamp": ${closingTimestamp}
}
}
}' --home ${walletLocation}`,
{ failOnNonZeroExit: false }
)
.its('stderr')
.should('contain', '');
});
@@ -72,8 +72,8 @@ export const LockedProgress = ({
unlocked,
leftLabel,
rightLabel,
leftColor = Colors.vega.pink,
rightColor = Colors.vega.green,
leftColor = Colors.vega.pink.DEFAULT,
rightColor = Colors.vega.green.DEFAULT,
decimals = 2,
}: LockedProgressProps) => {
const lockedPercentage = React.useMemo(() => {
@@ -63,7 +63,8 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
signer || provider
),
vesting: new TokenVesting(
config.token_vesting_contract.address,
config.token_vesting_contract?.address ||
'0xadFcb7f93a24F8743a8e548d74d2ecB373c92866',
signer || provider
),
claim: new Claim(ENV.addresses.claimAddress, signer || provider),
+7 -4
View File
@@ -30,9 +30,12 @@ export function useAnimateValue(
) {
elRef.current?.animate(
[
{ backgroundColor: customColors.vega.pink, color: colors.white },
{
backgroundColor: customColors.vega.pink,
backgroundColor: customColors.vega.pink.DEFAULT,
color: colors.white,
},
{
backgroundColor: customColors.vega.pink.DEFAULT,
color: colors.white,
offset: 0.8,
},
@@ -53,11 +56,11 @@ export function useAnimateValue(
elRef.current?.animate(
[
{
backgroundColor: customColors.vega.green,
backgroundColor: customColors.vega.green.DEFAULT,
color: colors.white,
},
{
backgroundColor: customColors.vega.green,
backgroundColor: customColors.vega.green.DEFAULT,
color: colors.white,
offset: 0.8,
},
@@ -102,7 +102,7 @@ export const VoteDetails = ({
</table>
</section>
)}
<section>
<section data-testid="votes-table">
<SubHeading title={t('tokenVotes')} />
<p>
<span>
@@ -67,10 +67,14 @@ export const TokenDetails = ({
data-testid="token-contract"
title={t('View on Etherscan (opens in a new tab)')}
className="font-mono text-white text-right"
href={`${ETHERSCAN_URL}/address/${config.token_vesting_contract.address}`}
href={`${ETHERSCAN_URL}/address/${
config.token_vesting_contract?.address ||
'0xadFcb7f93a24F8743a8e548d74d2ecB373c92866'
}`}
target="_blank"
>
{config.token_vesting_contract.address}
{config.token_vesting_contract?.address ||
'0xadFcb7f93a24F8743a8e548d74d2ecB373c92866'}
</Link>
</KeyValueTableRow>
<KeyValueTableRow>
@@ -29,7 +29,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Locked')}</span>
<ProgressBar
width={220}
color={Colors.vega.pink}
color={Colors.vega.pink.DEFAULT}
percentage={lockedPercentage}
/>
<span className="tranches__progress-numbers">
@@ -40,7 +40,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Redeemed')}</span>
<ProgressBar
width={220}
color={Colors.vega.green}
color={Colors.vega.green.DEFAULT}
percentage={removedPercentage}
/>
<span className="tranches__progress-numbers">
@@ -34,10 +34,10 @@ export const VestingChart = () => {
<AreaChart data={data}>
<defs>
{[
['pink', Colors.vega.pink],
['green', Colors.vega.green],
['pink', Colors.vega.pink.DEFAULT],
['green', Colors.vega.green.DEFAULT],
['orange', Colors.warning],
['yellow', Colors.vega.yellow],
['yellow', Colors.vega.yellow.DEFAULT],
].map(([key, color]) => (
<linearGradient key={key} id={key} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity={0.85} />
@@ -97,7 +97,7 @@ export const VestingChart = () => {
dot={false}
type="linear"
dataKey="team"
stroke={Colors.vega.pink}
stroke={Colors.vega.pink.DEFAULT}
fill="url(#pink)"
yAxisId={0}
strokeWidth={2}
@@ -109,7 +109,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="earlyInvestors"
stroke={Colors.vega.green}
stroke={Colors.vega.green.DEFAULT}
fill="url(#green)"
yAxisId={0}
strokeWidth={2}
@@ -121,7 +121,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="publicSale"
stroke={Colors.vega.yellow}
stroke={Colors.vega.yellow.DEFAULT}
fill="url(#yellow)"
yAxisId={0}
strokeWidth={2}
+9 -2
View File
@@ -5,10 +5,12 @@ import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import {
useWithdrawals,
withdrawalProvider,
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '../index';
@@ -29,7 +31,12 @@ const Withdrawals = ({ name }: RouteChildProps) => {
const WithdrawPendingContainer = () => {
const openWithdrawalDialog = useWithdrawalDialog((state) => state.open);
const { t } = useTranslation();
const { data, loading, error } = useWithdrawals();
const { pubKey } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
if (error) {
return (
+66 -41
View File
@@ -21,15 +21,21 @@ const orderUpdatedAt = 'updatedAt';
const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]';
const txTimeout = Cypress.env('txTimeout');
const btcName = 'BTC (local)';
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName =
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
const btcSymbol = 'tBTC';
const usdcSymbol = 'fUSDC';
const toastContent = 'toast-content';
const ordersTab = 'Orders';
const depositsTab = 'Deposits';
const toastCloseBtn = 'toast-close';
const price = '390';
const size = '0.0005';
const newPrice = '200';
// TODO: ensure this test runs only if capsule is running via workflow
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
describe('capsule', { tags: '@slow' }, () => {
before(() => {
cy.createMarket();
@@ -50,8 +56,8 @@ describe('capsule', { tags: '@slow' }, () => {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_BUY,
size: '0.0005',
price: '390',
size: size,
price: price,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
};
const rawPrice = removeDecimal(order.price, market.decimalPlaces);
@@ -64,7 +70,8 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+0.0005 @ 390.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
// orderbook cells are keyed by price level
@@ -75,7 +82,7 @@ describe('capsule', { tags: '@slow' }, () => {
.should('contain.text', rawSize);
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').should('contain.text', 'Edit');
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
cy.getByTestId('tab-orders').within(() => {
cy.get('.ag-center-cols-container')
.children()
@@ -113,35 +120,41 @@ describe('capsule', { tags: '@slow' }, () => {
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderCreatedAt);
});
});
//edit order
});
it('can edit order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').first().should('be.visible').click();
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
cy.get('#limitPrice').focus().clear().type('200');
cy.get('#limitPrice').focus().clear().type(newPrice);
cy.getByTestId('edit-order').find('[type="submit"]').click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+0.0005 @ 200.00 ${usdcSymbol}+0.0005 @ 200.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(ordersTab).click();
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.get('.ag-center-cols-container')
.children()
.first()
.within(() => {
cy.get(`[col-id='${orderPrice}']`).then(($price) => {
expect(parseFloat($price.text())).to.equal(parseFloat('200'));
expect(parseFloat($price.text())).to.equal(parseFloat(newPrice));
});
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
});
//cancel order
});
it('can cancel order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('cancel').first().click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+0.0005 @ 200.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('tab-orders')
.get('.ag-center-cols-container')
@@ -151,7 +164,10 @@ describe('capsule', { tags: '@slow' }, () => {
.should('contain.text', OrderStatusMapping.STATUS_CANCELLED);
});
it('can deposit and withdrawal', function () {
it('can deposit', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
// 1001-DEPO-001
// 1001-DEPO-002
// 1001-DEPO-003
@@ -160,27 +176,12 @@ describe('capsule', { tags: '@slow' }, () => {
// 1001-DEPO-007
// 1001-DEPO-008
// 1001-DEPO-009
// 1002-WITH-001
// 1002-WITH-006
// 1002-WITH-009
// 002-WITH-011
// 1002-WITH-024
// 1002-WITH-012
// 1002-WITH-013
// 1002-WITH-014
// 1002-WITH-015
// 1002-WITH-016
// 1002-WITH-019
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.highlight('creating deposit');
// 1001-DEPO-010
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName);
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
@@ -188,7 +189,8 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Transaction completedYour transaction has been completedView on EtherscanDeposit 1.00 ${btcSymbol}`
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 1.00 ${btcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
@@ -196,8 +198,6 @@ describe('capsule', { tags: '@slow' }, () => {
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
// need to reload page to see deposit history complete
cy.reload();
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
@@ -214,15 +214,29 @@ describe('capsule', { tags: '@slow' }, () => {
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/tx');
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
cy.highlight('creating withdrawals');
it('can withdrawal', function () {
// 1002-WITH-001
// 1002-WITH-006
// 1002-WITH-009
// 1002-WITH-011
// 1002-WITH-024
// 1002-WITH-012
// 1002-WITH-013
// 1002-WITH-014
// 1002-WITH-015
// 1002-WITH-016
// 1002-WITH-019
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField).select(btcName);
cy.get(assetSelectField, txTimeout).select(
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC',
{ force: true }
);
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -243,7 +257,7 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('toast-complete-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Transaction completed'
'Transaction confirmed'
);
cy.getByTestId('complete-withdrawal', txTimeout).should('not.exist');
@@ -258,17 +272,28 @@ describe('capsule', { tags: '@slow' }, () => {
cy.get('[col-id="details.receiverAddress"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/');
.and('contain', `${sepoliaUrl}/address/`);
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="withdrawnTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Completed');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/tx/0x');
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
it('deposit - if approved amount is less than deposit: must see that an approval is needed and be prompted to approve more', function () {
// 1001-DEPO-006
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('20000000');
cy.getByTestId('deposit-approve-submit').should('be.visible');
});
});
function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
cy.get(`[col-id='${date}'] .ag-cell-wrapper`)
.children('span')
@@ -56,6 +56,7 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
});
it('insufficient funds', () => {
// 1001-DEPO-005
// Deposit amount is valid, but less than approved. This will always be the case because our
// CI wallet wont have approved any assets
cy.get(amountField)
@@ -1,4 +1,7 @@
import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
describe('markets table', { tags: '@smoke' }, () => {
beforeEach(() => {
@@ -111,6 +114,53 @@ describe('markets table', { tags: '@smoke' }, () => {
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
);
});
it('opening auction subsets should be properly displayed', () => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
);
cy.mockGQL((req) => {
const override = {
market: {
tradableInstrument: {
instrument: {
name: `opening auction MARKET`,
},
},
state: Schema.MarketState.STATE_ACTIVE,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
},
};
const market = marketQuery(override);
aliasGQLQuery(req, 'Market', market);
aliasGQLQuery(req, 'ProposalOfMarket', {
proposal: { terms: { enactmentDatetime: '2023-01-31 12:00:01' } },
});
});
cy.visit('/');
cy.visit('#/markets/market-0');
cy.getByTestId('item-value').contains('Opening auction').realHover();
cy.getByTestId('opening-auction-sub-status').should(
'contain.text',
'Opening auction: Not enough liquidity to open'
);
const now = new Date(Date.parse('2023-01-30 12:00:01')).getTime();
cy.clock(now, ['Date']); // Set "now" to BEFORE reservation
cy.visit('/');
cy.visit('#/markets/market-0');
cy.getByTestId('item-value').contains('Opening auction').realHover();
cy.getByTestId('opening-auction-sub-status').should(
'contain.text',
`Opening auction: Closing on ${getDateTimeFormat().format(
new Date('2023-01-31 12:00:01')
)}`
);
cy.clock().then((clock) => {
clock.restore();
});
});
});
function openMarketDropDown() {
@@ -454,8 +454,4 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
);
});
});
it.skip('tbd for 7003-MORD', () => {
// NOT COVERED: must see the reference, offset and direction for each part pegged order - waiting for clarification
// NOT COVERED: must see the reference, offset and direction for each part liquidity order order - waiting for clarification
});
});
@@ -1,22 +1,28 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { useDeposits } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/react-helpers';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const DepositsContainer = () => {
const { deposits, loading, error } = useDeposits();
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const openDepositDialog = useDepositDialog((state) => state.open);
return (
<div className="h-full grid grid-rows-[1fr,min-content]">
<div className="h-full relative">
<DepositsTable
rowData={deposits || []}
rowData={data || []}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={deposits}
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
@@ -24,15 +30,17 @@ export const DepositsContainer = () => {
/>
</div>
</div>
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button
size="sm"
onClick={() => openDepositDialog()}
data-testid="deposit-button"
>
{t('Deposit')}
</Button>
</div>
{!isReadOnly && (
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button
size="sm"
onClick={() => openDepositDialog()}
data-testid="deposit-button"
>
{t('Deposit')}
</Button>
</div>
)}
</div>
);
};
@@ -1,14 +1,20 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import {
useWithdrawals,
withdrawalProvider,
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { t } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
export const WithdrawalsContainer = () => {
const { data, loading, error } = useWithdrawals();
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
return (
@@ -30,15 +36,17 @@ export const WithdrawalsContainer = () => {
/>
</div>
</div>
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button
size="sm"
onClick={() => openWithdrawDialog()}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
{!isReadOnly && (
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button
size="sm"
onClick={() => openWithdrawDialog()}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
)}
</div>
</VegaWalletContainer>
);
@@ -9,7 +9,7 @@ import { AccountManager } from '@vegaprotocol/accounts';
import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = () => {
const { pubKey } = useVegaWallet();
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
const openDepositDialog = useDepositDialog((store) => store.open);
@@ -37,13 +37,16 @@ export const AccountsContainer = () => {
onClickAsset={onClickAsset}
onClickWithdraw={openWithdrawalDialog}
onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly}
/>
</div>
<div className="flex justify-end p-2 px-[11px]">
<Button size="sm" onClick={() => openDepositDialog()}>
{t('Deposit')}
</Button>
</div>
{!isReadOnly && (
<div className="flex justify-end p-2 px-[11px]">
<Button size="sm" onClick={() => openDepositDialog()}>
{t('Deposit')}
</Button>
</div>
)}
</div>
);
};
@@ -170,7 +170,7 @@ export const SelectMarketPopover = ({
</div>
) : (
<table className="relative text-sm w-full whitespace-nowrap">
{pubKey && (positions?.length ?? 0) > 0 ? (
{pubKey && (positions?.length ?? 0) && (markets?.length ?? 0) ? (
<>
<TableTitle>{t('My markets')}</TableTitle>
<SelectAllMarketsTableBody
+22 -22
View File
@@ -10,49 +10,49 @@ body,
/* Styles for allotment */
html {
--focus-border: theme('colors.vega.pink');
--separator-border: theme('colors.neutral.300');
--pennant-color-danger: theme('colors.vega.pink');
--focus-border: theme('colors.vega.pink.500');
--separator-border: theme('colors.vega.light.200');
--pennant-color-danger: theme('colors.vega.pink.500');
}
html.dark {
--focus-border: theme('colors.vega.yellow');
--separator-border: theme('colors.neutral.600');
--focus-border: theme('colors.vega.yellow.500');
--separator-border: theme('colors.vega.dark.200');
}
.border-default {
@apply border-neutral-300 dark:border-neutral-600;
@apply border-vega-light-200 dark:border-vega-dark-200;
}
/* Styles for charts */
html [data-theme='dark'] {
--pennant-color-danger: theme('colors.vega.pink');
--pennant-color-danger: theme('colors.vega.pink.DEFAULT');
/* candles */
--pennant-color-buy-fill: theme('colors.vega.green-data-dark');
--pennant-color-buy-stroke: theme('colors.vega.green');
--pennant-color-buy-fill: theme('colors.vega.green.650');
--pennant-color-buy-stroke: theme('colors.vega.green.500');
/* sell candles only use stroke as the candle is solid (without border) */
--pennant-color-sell-stroke: theme('colors.vega.pink');
--pennant-color-sell-stroke: theme('colors.vega.pink.500');
/* depth chart */
--pennant-color-depth-buy-fill: theme('colors.vega.green-data-dark');
--pennant-color-depth-buy-stroke: theme('colors.vega.green');
--pennant-color-depth-sell-fill: theme('colors.vega.pink-data-dark');
--pennant-color-depth-sell-stroke: theme('colors.vega.pink');
--pennant-color-depth-buy-fill: theme('colors.vega.green.650');
--pennant-color-depth-buy-stroke: theme('colors.vega.green.500');
--pennant-color-depth-sell-fill: theme('colors.vega.pink.650');
--pennant-color-depth-sell-stroke: theme('colors.vega.pink.500');
}
html [data-theme='light'] {
--pennant-color-danger: theme('colors.vega.pink');
--pennant-color-danger: theme('colors.vega.pink.500');
/* candles */
--pennant-color-buy-fill: theme('colors.vega.green-data-light');
--pennant-color-buy-stroke: theme('colors.vega.green-dark');
--pennant-color-buy-fill: theme('colors.vega.green.400');
--pennant-color-buy-stroke: theme('colors.vega.green.550');
/* sell candles only use stroke as the candle is solid (without border) */
--pennant-color-sell-stroke: theme('colors.vega.pink-data-light');
--pennant-color-sell-stroke: theme('colors.vega.pink.400');
/* depth chart */
--pennant-color-depth-buy-fill: theme('colors.vega.green-data-light');
--pennant-color-depth-buy-stroke: theme('colors.vega.green-dark');
--pennant-color-depth-sell-fill: theme('colors.vega.pink-data-light');
--pennant-color-depth-sell-stroke: theme('colors.vega.pink-dark');
--pennant-color-depth-buy-fill: theme('colors.vega.green.400');
--pennant-color-depth-buy-stroke: theme('colors.vega.green.550');
--pennant-color-depth-sell-fill: theme('colors.vega.pink.400');
--pennant-color-depth-sell-stroke: theme('colors.vega.pink.550');
}
+13
View File
@@ -0,0 +1,13 @@
const { utils } = require('@commitlint/config-nx-scopes');
module.exports = {
extends: ['@commitlint/config-conventional', '@commitlint/config-nx-scopes'],
rules: {
'scope-empty': [2, 'never'],
'scope-enum': async (ctx) => [
2,
'always',
['ci', 'docs', ...(await utils.getProjects(ctx))],
],
},
};
@@ -50,10 +50,10 @@ export type Account = Omit<AccountFieldsFragment, 'market' | 'asset'> & {
};
const update = (
data: AccountFieldsFragment[],
data: AccountFieldsFragment[] | null,
deltas: AccountEventsSubscription['accounts']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas.forEach((delta) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
@@ -73,15 +73,8 @@ const update = (
});
};
const getData = (
responseData: AccountsQuery
): AccountFieldsFragment[] | null => {
return (
removePaginationWrapper(responseData.party?.accountsConnection?.edges) ??
null
);
};
const getData = (responseData: AccountsQuery | null): AccountFieldsFragment[] =>
removePaginationWrapper(responseData?.party?.accountsConnection?.edges) || [];
const getDelta = (
subscriptionData: AccountEventsSubscription
): AccountEventsSubscription['accounts'] => {
@@ -23,13 +23,23 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
describe('AccountManager', () => {
it('change partyId should reload data provider', async () => {
const { rerender } = render(
<AccountManager partyId="partyOne" onClickAsset={jest.fn} />
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[0][0].variables.partyId
).toEqual('partyOne');
await act(() => {
rerender(<AccountManager partyId="partyTwo" onClickAsset={jest.fn} />);
rerender(
<AccountManager
partyId="partyTwo"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
});
expect(
(helpers.useDataProvider as jest.Mock).mock.calls[1][0].variables.partyId
@@ -38,7 +48,13 @@ describe('AccountManager', () => {
it('update method should return proper result', async () => {
await act(() => {
render(<AccountManager partyId="partyOne" onClickAsset={jest.fn} />);
render(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
});
await waitFor(() => {
expect(screen.getByText('No accounts')).toBeInTheDocument();
@@ -16,6 +16,7 @@ interface AccountManagerProps {
onClickAsset: (assetId: string) => void;
onClickWithdraw?: (assetId?: string) => void;
onClickDeposit?: (assetId?: string) => void;
isReadOnly: boolean;
}
export const AccountManager = ({
@@ -23,6 +24,7 @@ export const AccountManager = ({
onClickWithdraw,
onClickDeposit,
partyId,
isReadOnly,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const dataRef = useRef<AccountFields[] | null>(null);
@@ -58,6 +60,7 @@ export const AccountManager = ({
onClickAsset={onClickAsset}
onClickDeposit={onClickDeposit}
onClickWithdraw={onClickWithdraw}
isReadOnly={isReadOnly}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
+10 -2
View File
@@ -35,7 +35,11 @@ describe('AccountsTable', () => {
it('should render correct columns', async () => {
await act(async () => {
render(
<AccountTable rowData={singleRowData} onClickAsset={() => null} />
<AccountTable
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={false}
/>
);
});
const expectedHeaders = ['Asset', 'Total', 'Used', 'Available', ''];
@@ -49,7 +53,11 @@ describe('AccountsTable', () => {
it('should apply correct formatting', async () => {
await act(async () => {
render(
<AccountTable rowData={singleRowData} onClickAsset={() => null} />
<AccountTable
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={false}
/>
);
});
const cells = await screen.findAllByRole('gridcell');
+45 -42
View File
@@ -29,6 +29,7 @@ export interface AccountTableProps extends AgGridReactProps {
onClickAsset: (assetId: string) => void;
onClickWithdraw?: (assetId: string) => void;
onClickDeposit?: (assetId: string) => void;
isReadOnly: boolean;
}
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
@@ -127,48 +128,50 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
}
maxWidth={300}
/>
<AgGridColumn
colId="breakdown"
headerName=""
sortable={false}
minWidth={200}
type="rightAligned"
cellRenderer={({
data,
}: VegaICellRendererParams<AccountFields>) => {
return data ? (
<>
<ButtonLink
data-testid="breakdown"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setBreakdown(data.breakdown || null);
}}
>
{t('Breakdown')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
</>
) : null;
}}
/>
{!props.isReadOnly && (
<AgGridColumn
colId="breakdown"
headerName=""
sortable={false}
minWidth={200}
type="rightAligned"
cellRenderer={({
data,
}: VegaICellRendererParams<AccountFields>) => {
return data ? (
<>
<ButtonLink
data-testid="breakdown"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setBreakdown(data.breakdown || null);
}}
>
{t('Breakdown')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
</>
) : null;
}}
/>
)}
</AgGrid>
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
<div className="h-[35vh] w-full m-auto flex flex-col">
+1 -20
View File
@@ -1,4 +1,4 @@
import type { ApolloError, InMemoryCacheConfig } from '@apollo/client';
import type { InMemoryCacheConfig } from '@apollo/client';
import {
ApolloClient,
from,
@@ -13,7 +13,6 @@ import { createClient as createWSClient } from 'graphql-ws';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import ApolloLinkTimeout from 'apollo-link-timeout';
import type { GraphQLErrors } from '@apollo/client/errors';
import { localLoggerFactory } from '@vegaprotocol/react-helpers';
const isBrowser = typeof window !== 'undefined';
@@ -110,21 +109,3 @@ export function createClient({
connectToDevTools,
});
}
const isApolloGraphQLError = (
error: ApolloError | Error | undefined
): error is ApolloError => {
return !!error && !!(error as ApolloError).graphQLErrors;
};
const hasNotFoundGraphQLErrors = (errors: GraphQLErrors) => {
return errors.some((e) => e.extensions && e.extensions['type'] === NOT_FOUND);
};
export const isNotFoundGraphQLError = (
error: Error | ApolloError | undefined
) => {
return (
isApolloGraphQLError(error) && hasNotFoundGraphQLErrors(error.graphQLErrors)
);
};
+3 -8
View File
@@ -6,20 +6,15 @@ import { AssetDocument } from './__generated__/Asset';
export type Asset = AssetFieldsFragment;
const getData = (responseData: AssetQuery) => {
const foundAssets = responseData.assetsConnection?.edges
const getData = (responseData: AssetQuery | null) => {
const foundAssets = responseData?.assetsConnection?.edges
?.filter((e) => Boolean(e?.node))
.map((e) => e?.node as Asset);
if (foundAssets && foundAssets?.length > 0) return foundAssets[0];
return null;
};
export const assetProvider = makeDataProvider<
AssetQuery,
Asset | null,
never,
never
>({
export const assetProvider = makeDataProvider<AssetQuery, Asset, never, never>({
query: AssetDocument,
getData,
});
+15
View File
@@ -0,0 +1,15 @@
import { Option } from '@vegaprotocol/ui-toolkit';
import type { AssetFieldsFragment } from './__generated__/Asset';
export const AssetOption = ({ asset }: { asset: AssetFieldsFragment }) => {
return (
<Option key={asset.id} value={asset.id}>
<div className="flex flex-col items-start">
<span>{asset.name}</span>
<div className="text-[10px] font-mono w-full text-left break-all">
<span className="text-gray-500">{asset.id} -</span> {asset.symbol}
</div>
</div>
</Option>
);
};
+3 -3
View File
@@ -16,14 +16,14 @@ export type BuiltinAsset = Omit<Asset, 'source'> & {
source: BuiltinAssetSource;
};
const getData = (responseData: AssetsQuery) =>
responseData.assetsConnection?.edges
const getData = (responseData: AssetsQuery | null) =>
responseData?.assetsConnection?.edges
?.filter((e) => Boolean(e?.node))
.map((e) => e?.node as Asset) ?? [];
export const assetsProvider = makeDataProvider<
AssetsQuery,
Asset[] | null,
Asset[],
never,
never
>({
+1
View File
@@ -4,3 +4,4 @@ export * from './asset-data-provider';
export * from './assets-data-provider';
export * from './asset-details-dialog';
export * from './asset-details-table';
export * from './asset-option';
@@ -48,7 +48,7 @@ function createNewMarketProposal(): ProposalSubmissionBody {
signers: [
{
pubKey: {
key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC',
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
@@ -45,7 +45,7 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
};
export const DealTicket = ({ market, submit }: DealTicketProps) => {
const { pubKey } = useVegaWallet();
const { pubKey, isReadOnly } = useVegaWallet();
const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore(
(store) => ({
getPersistedOrder: store.getOrder,
@@ -74,6 +74,7 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
usePersistedOrderStoreSubscription(market.id, (storedOrder) => {
if (order.price !== storedOrder.price) {
clearErrors('price');
setValue('price', storedOrder.price);
}
});
@@ -157,7 +158,11 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
);
return (
<form onSubmit={handleSubmit(onSubmit)} className="p-4" noValidate>
<form
onSubmit={isReadOnly ? () => null : handleSubmit(onSubmit)}
className="p-4"
noValidate
>
<Controller
name="type"
control={control}
@@ -219,13 +224,14 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
/>
)}
<DealTicketButton
disabled={Object.keys(errors).length >= 1}
disabled={Object.keys(errors).length >= 1 || isReadOnly}
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
/>
<SummaryMessage
errorMessage={errors.summary?.message}
market={market}
order={order}
isReadOnly={isReadOnly}
/>
<DealTicketFeeDetails order={order} market={market} />
</form>
@@ -240,9 +246,10 @@ interface SummaryMessageProps {
errorMessage?: string;
market: MarketDealTicket;
order: OrderSubmissionBody['orderSubmission'];
isReadOnly: boolean;
}
const SummaryMessage = memo(
({ errorMessage, market, order }: SummaryMessageProps) => {
({ errorMessage, market, order, isReadOnly }: SummaryMessageProps) => {
// Specific error UI for if balance is so we can
// render a deposit dialog
const asset = market.tradableInstrument.instrument.product.settlementAsset;
@@ -250,6 +257,17 @@ const SummaryMessage = memo(
market,
order,
});
if (isReadOnly) {
return (
<div className="mb-4">
<InputError data-testid="dealticket-error-message-summary">
{
'You need to connect your own wallet to start trading on this market'
}
</InputError>
</div>
);
}
if (errorMessage === SummaryValidationType.NoCollateral) {
return (
<ZeroBalanceError
@@ -1,6 +1,9 @@
import { useMemo } from 'react';
import { parseISO, isValid, isAfter } from 'date-fns';
import classNames from 'classnames';
import { useProposalOfMarketQuery } from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment';
import { DataGrid, t } from '@vegaprotocol/react-helpers';
import { DataGrid, getDateTimeFormat, t } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/react-helpers';
@@ -21,14 +24,24 @@ export const TradingModeTooltip = ({
const { VEGA_DOCS_URL } = useEnvironment();
const market = useMarket(marketId);
const marketData = useStaticMarketData(marketId, skip);
const { marketTradingMode: tradingMode, trigger } = marketData || {};
const variables = useMemo(() => ({ marketId: marketId || '' }), [marketId]);
const { data: proposalData } = useProposalOfMarketQuery({
variables,
skip:
!tradingMode ||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION !== tradingMode,
});
if (!market || !marketData) {
return null;
}
const enactmentDate = parseISO(
proposalData?.proposal?.terms.enactmentDatetime
);
const compiledGrid =
onSelect && compileGridData(market, marketData, onSelect);
const { marketTradingMode: tradingMode, trigger } = marketData;
switch (tradingMode) {
case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: {
@@ -43,12 +56,47 @@ export const TradingModeTooltip = ({
case Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION: {
return (
<section data-testid="trading-mode-tooltip">
<p className={classNames({ 'mb-4': Boolean(compiledGrid) })}>
<span>
{t(
'This new market is in an opening auction to determine a fair mid-price before starting continuous trading.'
)}
</span>{' '}
<p
className={classNames('flex flex-col', {
'mb-4': Boolean(compiledGrid),
})}
>
{isValid(enactmentDate) && isAfter(new Date(), enactmentDate) ? (
<>
<span
className="justify-center font-bold my-2"
data-testid="opening-auction-sub-status"
>
{`${Schema.MarketTradingModeMapping[tradingMode]}: ${t(
'Not enough liquidity to open'
)}`}
</span>
<span>
{t(
'This market is in opening auction until it has reached enough liquidity to move into continuous trading.'
)}
</span>
</>
) : (
<>
{isValid(enactmentDate) && (
<span
className="justify-center font-bold my-2"
data-testid="opening-auction-sub-status"
>
{`${Schema.MarketTradingModeMapping[tradingMode]}: ${t(
'Closing on %s',
getDateTimeFormat().format(enactmentDate)
)}`}
</span>
)}
<span>
{t(
'This is a new market in an opening auction to determine a fair mid-price before starting continuous trading.'
)}
</span>
</>
)}
{VEGA_DOCS_URL && (
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).AUCTION_TYPE_OPENING}
+2 -9
View File
@@ -1,4 +1,5 @@
import type { Asset } from '@vegaprotocol/assets';
import { AssetOption } from '@vegaprotocol/assets';
import {
ethereumAddress,
t,
@@ -17,7 +18,6 @@ import {
Input,
InputError,
RichSelect,
Option,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useWeb3React } from '@web3-react/core';
@@ -185,14 +185,7 @@ export const DepositForm = ({
hasError={Boolean(errors.asset?.message)}
>
{assets.filter(isAssetTypeERC20).map((a) => (
<Option key={a.id} value={a.id}>
<div className="flex flex-col items-start">
<span>{a.name}</span>
<span className="text-[10px] font-mono">
<span className="text-gray-500">{a.id} -</span> {a.symbol}
</span>
</div>
</Option>
<AssetOption asset={a} />
))}
</RichSelect>
)}
@@ -0,0 +1,49 @@
import uniqBy from 'lodash/uniqBy';
import orderBy from 'lodash/orderBy';
import {
getEvents,
makeDataProvider,
removePaginationWrapper,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
DepositsDocument,
DepositEventDocument,
} from './__generated__/Deposit';
import type {
DepositFieldsFragment,
DepositsQuery,
DepositEventSubscription,
DepositEventSubscriptionVariables,
} from './__generated__/Deposit';
export const depositsProvider = makeDataProvider<
DepositsQuery,
DepositFieldsFragment[],
DepositEventSubscription,
DepositEventSubscription,
DepositEventSubscriptionVariables
>({
query: DepositsDocument,
subscriptionQuery: DepositEventDocument,
getData: (data: DepositsQuery | null) =>
orderBy(
removePaginationWrapper(data?.party?.depositsConnection?.edges || []),
['createdTimestamp'],
['desc']
),
getDelta: (data: DepositEventSubscription) => data,
update: (
data: DepositFieldsFragment[] | null,
delta: DepositEventSubscription
) => {
if (!delta.busEvents?.length) {
return data;
}
const incoming = getEvents<DepositFieldsFragment>(
Schema.BusEventType.Deposit,
delta.busEvents
);
return uniqBy([...incoming, ...(data || [])], 'id');
},
});
+1 -1
View File
@@ -5,7 +5,7 @@ export * from './deposit-limits';
export * from './deposit-manager';
export * from './deposits-table';
export * from './use-deposit-balances';
export * from './use-deposits';
export * from './deposits-provider';
export * from './use-get-allowance';
export * from './use-get-balance-of-erc20-token';
export * from './use-get-deposit-maximum';
-100
View File
@@ -1,100 +0,0 @@
import uniqBy from 'lodash/uniqBy';
import orderBy from 'lodash/orderBy';
import { getNodes, getEvents } from '@vegaprotocol/react-helpers';
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEffect, useMemo } from 'react';
import * as Schema from '@vegaprotocol/types';
import {
useDepositsQuery,
DepositEventDocument,
} from './__generated__/Deposit';
import type {
DepositFieldsFragment,
DepositsQuery,
DepositEventSubscription,
DepositEventSubscriptionVariables,
} from './__generated__/Deposit';
export const useDeposits = () => {
const { pubKey } = useVegaWallet();
const { data, loading, error, subscribeToMore } = useDepositsQuery({
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const deposits = useMemo(() => {
if (!data?.party?.depositsConnection?.edges?.length) {
return [];
}
return orderBy(
getNodes<DepositFieldsFragment>(data.party?.depositsConnection),
['createdTimestamp'],
['desc']
);
}, [data]);
useEffect(() => {
if (!pubKey) return;
const unsub = subscribeToMore<
DepositEventSubscription,
DepositEventSubscriptionVariables
>({
document: DepositEventDocument,
variables: { partyId: pubKey },
updateQuery,
});
return () => {
unsub();
};
}, [pubKey, subscribeToMore]);
return { data, loading, error, deposits };
};
const updateQuery: UpdateQueryFn<
DepositsQuery,
DepositEventSubscriptionVariables,
DepositEventSubscription
> = (prev, { subscriptionData, variables }) => {
if (!subscriptionData.data.busEvents?.length || !variables?.partyId) {
return prev;
}
const curr = getNodes<DepositFieldsFragment>(prev.party?.depositsConnection);
const incoming = getEvents<DepositFieldsFragment>(
Schema.BusEventType.Deposit,
subscriptionData.data.busEvents
);
const deposits = uniqBy([...incoming, ...curr], 'id');
if (!prev.party) {
return {
...prev,
party: {
__typename: 'Party',
id: variables?.partyId,
depositsConnection: {
__typename: 'DepositsConnection',
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
},
},
};
}
return {
...prev,
party: {
...prev.party,
id: variables?.partyId,
depositsConnection: {
__typename: 'DepositsConnection',
edges: deposits.map((d) => ({ __typename: 'DepositEdge', node: d })),
},
},
};
};
+2 -2
View File
@@ -59,8 +59,8 @@ const update = (
export type Trade = Omit<FillFieldsFragment, 'market'> & { market?: Market };
export type TradeEdge = Edge<Trade>;
const getData = (responseData: FillsQuery): FillEdgeFragment[] =>
responseData.party?.tradesConnection?.edges || [];
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
responseData?.party?.tradesConnection?.edges || [];
const getPageInfo = (responseData: FillsQuery): PageInfo | null =>
responseData.party?.tradesConnection?.pageInfo || null;
@@ -5,8 +5,8 @@ import type {
} from './__generated__/Proposals';
import { ProposalsListDocument } from './__generated__/Proposals';
const getData = (responseData: ProposalsListQuery) =>
responseData.proposalsConnection?.edges
const getData = (responseData: ProposalsListQuery | null) =>
responseData?.proposalsConnection?.edges
?.filter((edge) => Boolean(edge?.node))
.map((edge) => edge?.node as ProposalListFieldsFragment) || null;
@@ -43,3 +43,12 @@ subscription OnUpdateNetworkParameters {
}
}
}
query ProposalOfMarket($marketId: ID!) {
proposal(id: $marketId) {
id
terms {
enactmentDatetime
}
}
}
@@ -19,6 +19,13 @@ export type OnUpdateNetworkParametersSubscriptionVariables = Types.Exact<{ [key:
export type OnUpdateNetworkParametersSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order' } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, datetime: any, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null, change: { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } } } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal' } }> | null };
export type ProposalOfMarketQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type ProposalOfMarketQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null } } | null };
export const ProposalEventFieldsFragmentDoc = gql`
fragment ProposalEventFields on Proposal {
id
@@ -113,4 +120,42 @@ export function useOnUpdateNetworkParametersSubscription(baseOptions?: Apollo.Su
return Apollo.useSubscription<OnUpdateNetworkParametersSubscription, OnUpdateNetworkParametersSubscriptionVariables>(OnUpdateNetworkParametersDocument, options);
}
export type OnUpdateNetworkParametersSubscriptionHookResult = ReturnType<typeof useOnUpdateNetworkParametersSubscription>;
export type OnUpdateNetworkParametersSubscriptionResult = Apollo.SubscriptionResult<OnUpdateNetworkParametersSubscription>;
export type OnUpdateNetworkParametersSubscriptionResult = Apollo.SubscriptionResult<OnUpdateNetworkParametersSubscription>;
export const ProposalOfMarketDocument = gql`
query ProposalOfMarket($marketId: ID!) {
proposal(id: $marketId) {
id
terms {
enactmentDatetime
}
}
}
`;
/**
* __useProposalOfMarketQuery__
*
* To run a query within a React component, call `useProposalOfMarketQuery` and pass it any options that fit your needs.
* When your component renders, `useProposalOfMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useProposalOfMarketQuery({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useProposalOfMarketQuery(baseOptions: Apollo.QueryHookOptions<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>(ProposalOfMarketDocument, options);
}
export function useProposalOfMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>(ProposalOfMarketDocument, options);
}
export type ProposalOfMarketQueryHookResult = ReturnType<typeof useProposalOfMarketQuery>;
export type ProposalOfMarketLazyQueryHookResult = ReturnType<typeof useProposalOfMarketLazyQuery>;
export type ProposalOfMarketQueryResult = Apollo.QueryResult<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>;
@@ -35,12 +35,12 @@ export type LedgerEntry = LedgerEntryFragment & {
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
const getData = (responseData: LedgerEntriesQuery) => {
return responseData.ledgerEntries?.edges || [];
const getData = (responseData: LedgerEntriesQuery | null) => {
return responseData?.ledgerEntries?.edges || [];
};
export const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getData>,
reload: () => void,
variables?: LedgerEntriesQueryVariables
@@ -35,10 +35,10 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
query: LiquidityProvisionsDocument,
subscriptionQuery: LiquidityProvisionsUpdateDocument,
update: (
data: LiquidityProvisionFieldsFragment[],
data: LiquidityProvisionFieldsFragment[] | null,
deltas: LiquidityProvisionsUpdateSubscription['liquidityProvisions']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
@@ -63,9 +63,9 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
});
});
},
getData: (responseData: LiquidityProvisionsQuery) => {
getData: (responseData: LiquidityProvisionsQuery | null) => {
return (
responseData.market?.liquidityProvisionsConnection?.edges?.map(
responseData?.market?.liquidityProvisionsConnection?.edges?.map(
(e) => e?.node
) ?? []
).filter((e) => !!e) as LiquidityProvisionFieldsFragment[];
@@ -105,7 +105,7 @@ export const marketLiquidityDataProvider = makeDataProvider<
never
>({
query: MarketLpDocument,
getData: (responseData: MarketLpQuery) => {
getData: (responseData: MarketLpQuery | null) => {
return responseData;
},
});
@@ -119,10 +119,10 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
query: LiquidityProviderFeeShareDocument,
subscriptionQuery: LiquidityProviderFeeShareUpdateDocument,
update: (
data: LiquidityProviderFeeShareFieldsFragment[],
data: LiquidityProviderFeeShareFieldsFragment[] | null,
deltas: LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const id = delta.partyId;
const index = draft.findIndex((a) => a.party.id === id);
@@ -143,7 +143,7 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
});
},
getData: (data) => {
return data.market?.data?.liquidityProviderFeeShare || [];
return data?.market?.data?.liquidityProviderFeeShare || [];
},
getDelta: (subscriptionData: LiquidityProviderFeeShareUpdateSubscription) => {
return subscriptionData.marketsData[0].liquidityProviderFeeShare;
@@ -58,7 +58,7 @@ export interface Markets {
}
const getData = (
responseData: LiquidityProvisionMarketsQuery
responseData: LiquidityProvisionMarketsQuery | null
): LiquidityProvisionMarket[] | null => {
return (
responseData?.marketsConnection?.edges.map((edge) => {
@@ -53,7 +53,7 @@ export const update: Update<
return data;
};
const getData = (responseData: MarketDepthQuery) => responseData.market;
const getData = (responseData: MarketDepthQuery | null) => responseData?.market;
const getDelta = (subscriptionData: MarketDepthUpdateSubscription) =>
subscriptionData.marketsDepthUpdate;
@@ -9,5 +9,5 @@ export const marketInfoDataProvider = makeDataProvider<
never
>({
query: MarketInfoDocument,
getData: (responseData: MarketInfoQuery) => responseData,
getData: (responseData: MarketInfoQuery | null) => responseData,
});
@@ -24,7 +24,7 @@ export const update = (data: Candle[] | null, delta: Candle) => {
return [delta];
};
const getData = (responseData: MarketCandlesQuery): Candle[] | null =>
const getData = (responseData: MarketCandlesQuery | null): Candle[] | null =>
responseData?.marketsConnection?.edges[0]?.node.candlesConnection?.edges
?.filter((edge) => edge?.node)
.map((edge) => edge?.node as Candle) || null;
@@ -18,14 +18,20 @@ import type {
export type MarketData = MarketDataFieldsFragment;
const update = (data: MarketData, delta: MarketDataUpdateFieldsFragment) => {
return produce(data, (draft) => {
const { marketId, __typename, ...marketData } = delta;
Object.assign(draft, marketData);
});
const update = (
data: MarketData | null,
delta: MarketDataUpdateFieldsFragment
) => {
return (
data &&
produce(data, (draft) => {
const { marketId, __typename, ...marketData } = delta;
Object.assign(draft, marketData);
})
);
};
const getData = (responseData: MarketDataQuery): MarketData | null =>
const getData = (responseData: MarketDataQuery | null): MarketData | null =>
responseData?.marketsConnection?.edges[0].node.data || null;
const getDelta = (
+1 -1
View File
@@ -11,7 +11,7 @@ import type { MarketData } from './market-data-provider';
import { marketDataProvider } from './market-data-provider';
const getData = (
responseData: MarketQuery
responseData: MarketQuery | null
): SingleMarketFieldsFragment | null => responseData?.market || null;
export const marketProvider = makeDataProvider<
@@ -8,7 +8,9 @@ export interface MarketCandles {
candles: Candle[] | undefined;
}
const getData = (responseData: MarketsCandlesQuery): MarketCandles[] | null =>
const getData = (
responseData: MarketsCandlesQuery | null
): MarketCandles[] | null =>
responseData?.marketsConnection?.edges.map((edge) => ({
marketId: edge.node.id,
candles: edge.node.candlesConnection?.edges
@@ -3,8 +3,8 @@ import type { MarketsDataQuery } from './__generated__/markets-data';
import { MarketsDataDocument } from './__generated__/markets-data';
import type { MarketData } from './market-data-provider';
const getData = (responseData: MarketsDataQuery): MarketData[] | null =>
responseData.marketsConnection?.edges
const getData = (responseData: MarketsDataQuery | null): MarketData[] | null =>
responseData?.marketsConnection?.edges
.filter((edge) => edge.node.data)
.map((edge) => edge.node.data as MarketData) || null;
+1 -1
View File
@@ -21,7 +21,7 @@ import type { Candle } from './market-candles-provider';
export type Market = MarketFieldsFragment;
const getData = (responseData: MarketsQuery): Market[] | null =>
const getData = (responseData: MarketsQuery | null): Market[] | null =>
responseData?.marketsConnection?.edges.map((edge) => edge.node) || null;
export const marketsProvider = makeDataProvider<
@@ -62,7 +62,7 @@ describe('order data provider', () => {
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
delta[4].updatedAt
);
expect(update([], delta, () => null, { partyId: '0x123' }).length).toEqual(
expect(update([], delta, () => null, { partyId: '0x123' })?.length).toEqual(
4
);
});
@@ -71,7 +71,7 @@ const orderMatchFilters = (
return true;
};
const getData = (responseData: OrdersQuery) =>
const getData = (responseData: OrdersQuery | null) =>
responseData?.party?.ordersConnection?.edges || [];
const getDelta = (subscriptionData: OrdersUpdateSubscription) =>
@@ -81,7 +81,7 @@ const getPageInfo = (responseData: OrdersQuery): PageInfo | null =>
responseData.party?.ordersConnection?.pageInfo || null;
export const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getDelta>,
reload: () => void,
variables?: OrdersQueryVariables
@@ -10,7 +10,7 @@ export const OrderListContainer = ({
marketId?: string;
onMarketClick?: (marketId: string) => void;
}) => {
const { pubKey } = useVegaWallet();
const { pubKey, isReadOnly } = useVegaWallet();
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
@@ -21,6 +21,7 @@ export const OrderListContainer = ({
partyId={pubKey}
marketId={marketId}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
/>
);
};
@@ -13,7 +13,7 @@ const generateJsx = () => {
return (
<MockedProvider>
<VegaWalletContext.Provider value={{ pubKey } as VegaWalletContextShape}>
<OrderListManager partyId={pubKey} />
<OrderListManager partyId={pubKey} isReadOnly={false} />
</VegaWalletContext.Provider>
</MockedProvider>
);
@@ -31,6 +31,7 @@ export interface OrderListManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string) => void;
isReadOnly: boolean;
}
export const TransactionComplete = ({
@@ -72,6 +73,7 @@ export const OrderListManager = ({
partyId,
marketId,
onMarketClick,
isReadOnly,
}: OrderListManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true);
@@ -146,6 +148,7 @@ export const OrderListManager = ({
}}
setEditOrder={setEditOrder}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -19,6 +19,7 @@ const defaultProps: OrderListTableProps = {
rowData: [],
setEditOrder: jest.fn(),
cancel: jest.fn(),
isReadOnly: false,
};
const generateJsx = (
@@ -156,6 +157,29 @@ describe('OrderListTable', () => {
expect(mockCancel).toHaveBeenCalledWith(order);
});
it('does not allow cancelling and editing for permitted orders if read only', async () => {
const mockEdit = jest.fn();
const mockCancel = jest.fn();
const order = generateOrder({
type: Schema.OrderType.TYPE_LIMIT,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
liquidityProvision: null,
peggedOrder: null,
});
await act(async () => {
render(
generateJsx({
rowData: [order],
setEditOrder: mockEdit,
cancel: mockCancel,
isReadOnly: true,
})
);
});
const amendCell = getAmendCell();
expect(amendCell.queryAllByRole('button')).toHaveLength(0);
});
it('shows if an order is a liquidity provision order and does not show order actions', async () => {
const order = generateOrder({
type: Schema.OrderType.TYPE_LIMIT,
@@ -22,6 +22,7 @@ const Template: Story = (args) => {
setEditOrder={() => {
return;
}}
isReadOnly={false}
/>
</div>
);
@@ -48,6 +49,7 @@ const Template2: Story = (args) => {
rowData={args.data}
cancel={cancel}
setEditOrder={setEditOrder}
isReadOnly={false}
/>
</div>
<VegaTransactionDialog
@@ -32,6 +32,7 @@ export type OrderListTableProps = OrderListProps & {
cancel: (order: Order) => void;
setEditOrder: (order: Order) => void;
onMarketClick?: (marketId: string) => void;
isReadOnly: boolean;
};
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
@@ -248,7 +249,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
minWidth={100}
type="rightAligned"
cellRenderer={({ data, node }: VegaICellRendererParams<Order>) => {
return data && isOrderAmendable(data) ? (
return data && isOrderAmendable(data) && !props.isReadOnly ? (
<>
<ButtonLink
data-testid="edit"
@@ -14,10 +14,10 @@ import type {
} from './__generated__/Positions';
const update = (
data: MarginFieldsFragment[],
data: MarginFieldsFragment[] | null,
delta: MarginsSubscriptionSubscription['margins']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
const { marketId } = delta;
const index = draft.findIndex((node) => node.market.id === marketId);
if (index !== -1) {
@@ -49,8 +49,9 @@ const update = (
});
};
const getData = (responseData: MarginsQuery) =>
removePaginationWrapper(responseData.party?.marginsConnection?.edges) || [];
const getData = (responseData: MarginsQuery | null) =>
removePaginationWrapper(responseData?.party?.marginsConnection?.edges) || [];
const getDelta = (subscriptionData: MarginsSubscriptionSubscription) =>
subscriptionData.margins;
@@ -8,7 +8,7 @@ export const PositionsContainer = ({
}: {
onMarketClick?: (marketId: string) => void;
}) => {
const { pubKey } = useVegaWallet();
const { pubKey, isReadOnly } = useVegaWallet();
if (!pubKey) {
return (
@@ -17,5 +17,11 @@ export const PositionsContainer = ({
</Splash>
);
}
return <PositionsManager partyId={pubKey} onMarketClick={onMarketClick} />;
return (
<PositionsManager
partyId={pubKey}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
/>
);
};
@@ -4,8 +4,8 @@ import BigNumber from 'bignumber.js';
import sortBy from 'lodash/sortBy';
import type { Account } from '@vegaprotocol/accounts';
import { accountsDataProvider } from '@vegaprotocol/accounts';
import { toBigNum } from '@vegaprotocol/react-helpers';
import {
toBigNum,
makeDataProvider,
makeDerivedDataProvider,
removePaginationWrapper,
@@ -171,10 +171,10 @@ export const getMetrics = (
};
export const update = (
data: PositionFieldsFragment[],
data: PositionFieldsFragment[] | null,
deltas: PositionsSubscriptionSubscription['positions']
) => {
return produce(data, (draft) => {
return produce(data || [], (draft) => {
deltas.forEach((delta) => {
const index = draft.findIndex(
(node) => node.market.id === delta.marketId
@@ -212,8 +212,8 @@ export const positionsDataProvider = makeDataProvider<
query: PositionsDocument,
subscriptionQuery: PositionsSubscriptionDocument,
update,
getData: (responseData: PositionsQuery) =>
removePaginationWrapper(responseData.party?.positionsConnection?.edges) ||
getData: (responseData: PositionsQuery | null) =>
removePaginationWrapper(responseData?.party?.positionsConnection?.edges) ||
[],
getDelta: (subscriptionData: PositionsSubscriptionSubscription) =>
subscriptionData.positions,
+32 -25
View File
@@ -9,16 +9,45 @@ import { t } from '@vegaprotocol/react-helpers';
interface PositionsManagerProps {
partyId: string;
onMarketClick?: (marketId: string) => void;
isReadOnly: boolean;
}
export const PositionsManager = ({
partyId,
onMarketClick,
isReadOnly,
}: PositionsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data, error, loading, getRows } = usePositionsData(partyId, 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_FOK as const,
side: openVolume.startsWith('-')
? Schema.Side.SIDE_BUY
: Schema.Side.SIDE_SELL,
size: openVolume.replace('-', ''),
},
],
},
});
return (
<div className="h-full relative">
<PositionsTable
@@ -26,31 +55,9 @@ export const PositionsManager = ({
ref={gridRef}
datasource={{ getRows }}
onMarketClick={onMarketClick}
onClose={({ marketId, openVolume }) =>
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_FOK as const,
side: openVolume.startsWith('-')
? Schema.Side.SIDE_BUY
: Schema.Side.SIDE_SELL,
size: openVolume.replace('-', ''),
},
],
},
})
}
onClose={onClose}
noRowsOverlayComponent={() => null}
isReadOnly={isReadOnly}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
+60 -13
View File
@@ -32,14 +32,16 @@ const singleRowData = [singleRow];
it('should render successfully', async () => {
await act(async () => {
const { baseElement } = render(<PositionsTable rowData={[]} />);
const { baseElement } = render(
<PositionsTable rowData={[]} isReadOnly={false} />
);
expect(baseElement).toBeTruthy();
});
});
it('Render correct columns', async () => {
it('render correct columns', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} />);
render(<PositionsTable rowData={singleRowData} isReadOnly={true} />);
});
const headers = screen.getAllByRole('columnheader');
@@ -64,7 +66,7 @@ it('Render correct columns', async () => {
it('renders market name', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} />);
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
});
expect(screen.getByText('ETH/BTC (31 july 2022)')).toBeTruthy();
});
@@ -75,7 +77,7 @@ it('Does not fail if the market name does not match the split pattern', async ()
Object.assign({}, singleRow, { marketName: breakingMarketName }),
];
await act(async () => {
render(<PositionsTable rowData={row} />);
render(<PositionsTable rowData={row} isReadOnly={false} />);
});
expect(screen.getByText(breakingMarketName)).toBeTruthy();
@@ -84,7 +86,9 @@ it('Does not fail if the market name does not match the split pattern', async ()
it('add color and sign to amount, displays positive notional value', async () => {
let result: RenderResult;
await act(async () => {
result = render(<PositionsTable rowData={singleRowData} />);
result = render(
<PositionsTable rowData={singleRowData} isReadOnly={false} />
);
});
let cells = screen.getAllByRole('gridcell');
@@ -94,7 +98,10 @@ it('add color and sign to amount, displays positive notional value', async () =>
expect(cells[1].textContent).toEqual('1,230.0');
await act(async () => {
result.rerender(
<PositionsTable rowData={[{ ...singleRow, openVolume: '-100' }]} />
<PositionsTable
rowData={[{ ...singleRow, openVolume: '-100' }]}
isReadOnly={false}
/>
);
});
cells = screen.getAllByRole('gridcell');
@@ -107,7 +114,9 @@ it('add color and sign to amount, displays positive notional value', async () =>
it('displays mark price', async () => {
let result: RenderResult;
await act(async () => {
result = render(<PositionsTable rowData={singleRowData} />);
result = render(
<PositionsTable rowData={singleRowData} isReadOnly={false} />
);
});
let cells = screen.getAllByRole('gridcell');
@@ -123,6 +132,7 @@ it('displays mark price', async () => {
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
},
]}
isReadOnly={false}
/>
);
});
@@ -134,14 +144,19 @@ it('displays mark price', async () => {
it("displays properly entry, liquidation price and liquidation bar and it's intent", async () => {
let result: RenderResult;
await act(async () => {
result = render(<PositionsTable rowData={singleRowData} />);
result = render(
<PositionsTable rowData={singleRowData} isReadOnly={false} />
);
});
let cells = screen.getAllByRole('gridcell');
const entryPrice = cells[5].firstElementChild?.firstElementChild?.textContent;
expect(entryPrice).toEqual('13.3');
await act(async () => {
result.rerender(
<PositionsTable rowData={[{ ...singleRow, lowMarginLevel: true }]} />
<PositionsTable
rowData={[{ ...singleRow, lowMarginLevel: true }]}
isReadOnly={false}
/>
);
});
cells = screen.getAllByRole('gridcell');
@@ -149,7 +164,7 @@ it("displays properly entry, liquidation price and liquidation bar and it's inte
it('displays leverage', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} />);
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[7].textContent).toEqual('1.1');
@@ -157,7 +172,7 @@ it('displays leverage', async () => {
it('displays allocated margin', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} />);
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
});
const cells = screen.getAllByRole('gridcell');
const cell = cells[8];
@@ -166,9 +181,41 @@ it('displays allocated margin', async () => {
it('displays realised and unrealised PNL', async () => {
await act(async () => {
render(<PositionsTable rowData={singleRowData} />);
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[9].textContent).toEqual('1.23');
expect(cells[10].textContent).toEqual('4.56');
});
it('displays close button', async () => {
await act(async () => {
render(
<PositionsTable
rowData={singleRowData}
onClose={() => {
return;
}}
isReadOnly={false}
/>
);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[12].textContent).toEqual('Close');
});
it('do not display close button if openVolume is zero', async () => {
await act(async () => {
render(
<PositionsTable
rowData={[{ ...singleRow, openVolume: '0' }]}
onClose={() => {
return;
}}
isReadOnly={false}
/>
);
});
const cells = screen.getAllByRole('gridcell');
expect(cells[12].textContent).toEqual('');
});
+12 -9
View File
@@ -33,6 +33,7 @@ interface Props extends TypedDataAgGrid<Position> {
onClose?: (data: Position) => void;
onMarketClick?: (id: string) => void;
style?: CSSProperties;
isReadOnly: boolean;
}
export interface AmountCellProps {
@@ -378,17 +379,19 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
return getDateTimeFormat().format(new Date(value));
}}
/>
{onClose ? (
{onClose && !props.isReadOnly ? (
<AgGridColumn
type="rightAligned"
cellRenderer={({ data }: VegaICellRendererParams<Position>) => (
<ButtonLink
data-testid="close-position"
onClick={() => data && onClose(data)}
>
{t('Close')}
</ButtonLink>
)}
cellRenderer={({ data }: VegaICellRendererParams<Position>) =>
data?.openVolume && data?.openVolume !== '0' ? (
<ButtonLink
data-testid="close-position"
onClick={() => data && onClose(data)}
>
{t('Close')}
</ButtonLink>
) : null
}
/>
) : null}
</AgGrid>
@@ -2,10 +2,7 @@ import { useCallback, useState } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { positionsDataProvider } from './positions-data-providers';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import type {
PositionFieldsFragment,
PositionsSubscriptionSubscription,
} from './__generated__/Positions';
import type { PositionFieldsFragment } from './__generated__/Positions';
export const useMarketPositionOpenVolume = (marketId: string) => {
const { pubKey } = useVegaWallet();
@@ -21,10 +18,7 @@ export const useMarketPositionOpenVolume = (marketId: string) => {
[setOpenVolume, marketId]
);
useDataProvider<
PositionFieldsFragment[],
PositionsSubscriptionSubscription['positions']
>({
useDataProvider({
dataProvider: positionsDataProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey || !marketId,
+1 -19
View File
@@ -1,20 +1,2 @@
export * from './hooks';
export * from './lib/format';
export * from './lib/generic-data-provider';
export * from './lib/get-nodes';
export * from './lib/get-events';
export * from './lib/grid';
export * from './lib/i18n';
export * from './lib/pagination';
export * from './lib/ag-grid-update';
export * from './lib/remove-0x';
export * from './lib/storage';
export * from './lib/time';
export * from './lib/validate';
export * from './lib/links';
export * from './lib/is-asset-erc20';
export * from './lib/remove-pagination-wrapper';
export * from './lib/__generated__/ChainId';
export * from './lib/data-grid';
export * from './lib/local-logger';
export * from './lib/market-expires';
export * from './lib';
@@ -0,0 +1,29 @@
import type { ApolloError } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
const NOT_FOUND = 'NotFound';
const isApolloGraphQLError = (
error: ApolloError | Error | undefined
): error is ApolloError => {
return !!error && !!(error as ApolloError).graphQLErrors;
};
const hasNotFoundGraphQLErrors = (errors: GraphQLErrors, path?: string) => {
return errors.some(
(e) =>
e.extensions &&
e.extensions['type'] === NOT_FOUND &&
(!path || e.path?.[0] === path)
);
};
export const isNotFoundGraphQLError = (
error: Error | ApolloError | undefined,
path?: string
) => {
return (
isApolloGraphQLError(error) &&
hasNotFoundGraphQLErrors(error.graphQLErrors, path)
);
};
@@ -62,7 +62,7 @@ const subscribe = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
query,
subscriptionQuery,
update,
getData: (r) => r.data,
getData: (r) => r?.data || null,
getDelta: (r) => r.data,
});
@@ -91,7 +91,7 @@ const paginatedSubscribe = makeDataProvider<
query,
subscriptionQuery,
update,
getData: (r) => r.data,
getData: (r) => r?.data || null,
getDelta: (r) => r.data,
pagination: {
first,
@@ -215,7 +215,7 @@ describe('data provider', () => {
const subscription = subscribe(callback, client);
await resolveQuery({ data });
const delta: Item[] = [];
update.mockImplementationOnce((data, delta) => [...data, ...delta]);
update.mockImplementationOnce((data, delta) => [...(data || []), ...delta]);
// calling onNext from client.subscribe({ query }).subscribe(onNext)
await clientSubscribeSubscribe.mock.calls[
clientSubscribeSubscribe.mock.calls.length - 1
@@ -234,7 +234,7 @@ describe('data provider', () => {
const subscription = subscribe(callback, client);
await resolveQuery({ data });
const delta: Item[] = [];
update.mockImplementationOnce((data, delta) => data);
update.mockImplementationOnce((data, delta) => data || []);
const callbackCallsLength = callback.mock.calls.length;
// calling onNext from client.subscribe({ query }).subscribe(onNext)
await clientSubscribeSubscribe.mock.calls[
@@ -591,7 +591,7 @@ describe('derived data provider', () => {
await resolveQuery({ data: part2 });
expect(combineData).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
update.mockImplementation((data, delta) => [...data, ...delta]);
update.mockImplementation((data, delta) => [...(data || []), ...delta]);
combineData.mockReturnValueOnce({ ...data });
const combinedDelta = {};
combineDelta.mockReturnValueOnce(combinedDelta);
@@ -629,7 +629,7 @@ describe('derived data provider', () => {
await resolveQuery({ data: [] });
expect(combineData).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
update.mockImplementation((data, delta) => [...data, ...delta]);
update.mockImplementation((data, delta) => [...(data || []), ...delta]);
combineData.mockReturnValueOnce({ ...data });
const combinedInsertionData = {};
combineInsertionData.mockReturnValueOnce(combinedInsertionData);
@@ -8,6 +8,7 @@ import type {
} from '@apollo/client';
import type { Subscription } from 'zen-observable-ts';
import isEqual from 'lodash/isEqual';
import { isNotFoundGraphQLError } from './apollo-client';
import type * as Schema from '@vegaprotocol/types';
interface UpdateData<Data, Delta> {
delta?: Delta;
@@ -71,7 +72,12 @@ export interface Update<
Delta,
Variables extends OperationVariables = OperationVariables
> {
(data: Data, delta: Delta, reload: Reload, variables?: Variables): Data;
(
data: Data | null,
delta: Delta,
reload: Reload,
variables?: Variables
): Data;
}
export interface Append<Data> {
@@ -88,7 +94,7 @@ export interface Append<Data> {
}
interface GetData<QueryData, Data, Variables> {
(queryData: QueryData, variables?: Variables): Data | null;
(queryData: QueryData | null, variables?: Variables): Data | null;
}
interface GetPageInfo<QueryData> {
@@ -160,7 +166,7 @@ interface DataProviderParams<
> {
query: Query<QueryData>;
subscriptionQuery?: Query<SubscriptionData>;
update?: Update<Data, Delta, Variables>;
update?: Update<Data | null, Delta, Variables>;
getData: GetData<QueryData, Data, Variables>;
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
pagination?: {
@@ -349,6 +355,11 @@ function makeDataProviderInternal<
}
loaded = true;
} catch (e) {
if (isNotFoundGraphQLError(e as Error, 'party')) {
data = getData(null, variables);
loaded = true;
return;
}
// if error will occur data provider stops subscription
error = e as Error;
if (subscription) {
@@ -384,7 +395,7 @@ function makeDataProviderInternal<
return;
}
const delta = getDelta(subscriptionData, variables);
if (loading || !data) {
if (loading) {
updateQueue.push(delta);
} else {
const updatedData = update(data, delta, reload, variables);
@@ -409,7 +420,6 @@ function makeDataProviderInternal<
if (!client) {
return;
}
if (subscriptionQuery && getDelta && update) {
subscription = client
.subscribe<SubscriptionData>({
@@ -71,8 +71,8 @@ export const FlashCell = memo(({ children, value }: FlashCellProps) => {
if (value < previousValue) {
ref.current?.animate(
[
{ color: theme.colors.vega.pink },
{ color: theme.colors.vega.pink, offset: 0.8 },
{ color: theme.colors.vega.pink.DEFAULT },
{ color: theme.colors.vega.pink.DEFAULT, offset: 0.8 },
{ color: 'inherit' },
],
FLASH_DURATION
@@ -80,8 +80,8 @@ export const FlashCell = memo(({ children, value }: FlashCellProps) => {
} else if (value > previousValue) {
ref.current?.animate(
[
{ color: theme.colors.vega.green },
{ color: theme.colors.vega.green, offset: 0.8 },
{ color: theme.colors.vega.green.DEFAULT },
{ color: theme.colors.vega.green.DEFAULT, offset: 0.8 },
{ color: 'inherit' },
],
FLASH_DURATION
+4 -4
View File
@@ -19,16 +19,16 @@ export const Size = ({
data-testid="size"
className={classNames('text-right', {
// BUY
'text-vega-green-dark dark:text-vega-green':
'text-vega-green-550 dark:text-vega-green':
side === Schema.Side.SIDE_BUY && !forceTheme,
'text-vega-green-dark':
'text-vega-green-550':
side === Schema.Side.SIDE_BUY && forceTheme === 'light',
'text-vega-green':
side === Schema.Side.SIDE_BUY && forceTheme === 'dark',
// SELL
'text-vega-pink-dark dark:text-vega-pink':
'text-vega-pink-550 dark:text-vega-pink':
side === Schema.Side.SIDE_SELL && !forceTheme,
'text-vega-pink-dark':
'text-vega-pink-550':
side === Schema.Side.SIDE_SELL && forceTheme === 'light',
'text-vega-pink':
side === Schema.Side.SIDE_SELL && forceTheme === 'dark',
+2 -2
View File
@@ -20,8 +20,8 @@ export interface IVolCellProps extends ICellRendererParams {
valueFormatted: Omit<VolProps, 'value'>;
}
export const BID_COLOR = tailwind.theme.colors.vega['green'];
export const ASK_COLOR = tailwind.theme.colors.vega['pink'];
export const BID_COLOR = tailwind.theme.colors.vega.green.DEFAULT;
export const ASK_COLOR = tailwind.theme.colors.vega.pink.DEFAULT;
export const Vol = React.memo(
({ value, valueFormatted, relativeValue, type, testId }: VolProps) => {
+17 -13
View File
@@ -1,16 +1,20 @@
export * from './format';
export * from './grid';
export * from './storage';
export * from './validate';
export * from './generic-data-provider';
export * from './get-nodes';
export * from './get-events';
export * from './i18n';
export * from './pagination';
export * from './remove-0x';
export * from './time';
export * from './links';
export * from './remove-pagination-wrapper';
export * from './__generated__/ChainId';
export * from './ag-grid-update';
export * from './apollo-client';
export * from './data-grid';
export * from './format';
export * from './generic-data-provider';
export * from './get-events';
export * from './get-nodes';
export * from './grid';
export * from './i18n';
export * from './is-asset-erc20';
export * from './links';
export * from './local-logger';
export * from './market-expires';
export * from './pagination';
export * from './remove-0x';
export * from './remove-pagination-wrapper';
export * from './storage';
export * from './time';
export * from './validate';
+104 -13
View File
@@ -10,22 +10,113 @@ module.exports = {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: '#000000',
white: '#FFFFFF',
vega: {
yellow: '#DFFF0B',
'yellow-dark': '#B6DC26',
pink: '#FF077F',
'pink-data-light': '#FF6AB2',
'pink-dark': '#CF0064',
'pink-data-dark': '#7A033D',
green: '#00F780',
'green-data-light': '#85FBC2',
'green-dark': '#00D46E',
'green-data-dark': '#006333',
orange: '#FF7A1A',
blue: '#1DA2FB',
// YELLOW
yellow: {
700: '#23290E',
650: '#515E1E',
600: '#7E932F',
550: '#ABC840',
DEFAULT: '#D7FB50',
500: '#D7FB50',
450: '#E0FC75',
400: '#E8FD9A',
350: '#F0FDBE',
300: '#F9FEE3',
},
// GREEN
green: {
700: '#012915',
650: '#015D30',
600: '#01914B',
550: '#01C566',
DEFAULT: '#00F780',
500: '#00F780',
450: '#37F99B',
400: '#6CFAB6',
350: '#A1FCD0',
300: '#D6FEEB',
},
// BLUE
blue: {
700: '#01142A',
650: '#012C60',
600: '#014595',
550: '#015ECB',
DEFAULT: '#0075FF',
500: '#0075FF',
450: '#3793FF',
400: '#6CAFFF',
350: '#A1CCFF',
300: '#D6E9FF',
},
// PURPLE
purple: {
700: '#15072A',
650: '#301060',
600: '#4B1895',
550: '#6620CB',
DEFAULT: '#8028FF',
500: '#8028FF',
450: '#9B56FF',
400: '#B683FF',
350: '#D0B0FF',
300: '#EBDDFF',
},
// PINK
pink: {
700: '#210215',
650: '#600330',
600: '#95054B',
550: '#CB0666',
DEFAULT: '#FF077F',
500: '#FF077F',
450: '#FF3C9A',
400: '#FF70B5',
350: '#FFA3D0',
300: '#FFD7EA',
},
// ORANGE
orange: {
700: '#2A1701',
650: '#603301',
600: '#954F01',
550: '#CB6C01',
DEFAULT: '#FF8700',
500: '#FF8700',
450: '#FFA137',
400: '#FFBA6C',
350: '#FFD3A1',
300: '#FFECD6',
},
// DARK
dark: {
400: '#161616',
300: '#262626',
200: '#404040',
150: '#8B8B8B',
100: '#C0C0C0',
},
// LIGHT
light: {
400: '#F0F0F0',
300: '#E9E9E9',
200: '#D2D2D2',
150: '#939393',
100: '#626262',
},
},
danger: '#FF077F',
warning: '#FF7A1A',
warning: '#FF8700',
success: '#00F780',
},
fontFamily: {
@@ -22,17 +22,17 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) {
},
'.dark .syntax-highlighter-wrapper .hljs': {
background: colors.neutral[800],
color: theme.colors.vega.green,
color: theme.colors.vega.green.DEFAULT,
border: 0,
},
'.syntax-highlighter-wrapper .hljs-literal': {
color: theme.colors.vega.pink,
color: theme.colors.vega.pink.DEFAULT,
},
'.syntax-highlighter-wrapper .hljs-number': {
color: theme.colors.vega.orange,
color: theme.colors.vega.orange.DEFAULT,
},
'.syntax-highlighter-wrapper .hljs-string': {
color: theme.colors.vega.blue,
color: theme.colors.vega.blue.DEFAULT,
},
'.clip-path-rounded': {
clipPath: 'circle(50%)',
+2
View File
@@ -3,6 +3,7 @@ fragment TradeFields on Trade {
price
size
createdAt
aggressor
market {
id
}
@@ -35,5 +36,6 @@ subscription TradesUpdate($marketId: ID!) {
size
createdAt
marketId
aggressor
}
}
+5 -3
View File
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type TradeFieldsFragment = { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, market: { __typename?: 'Market', id: string } };
export type TradeFieldsFragment = { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } };
export type TradesQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
@@ -11,14 +11,14 @@ export type TradesQueryVariables = Types.Exact<{
}>;
export type TradesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradesConnection?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null } | null };
export type TradesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradesConnection?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null } | null };
export type TradesUpdateSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type TradesUpdateSubscription = { __typename?: 'Subscription', trades?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string }> | null };
export type TradesUpdateSubscription = { __typename?: 'Subscription', trades?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side }> | null };
export const TradeFieldsFragmentDoc = gql`
fragment TradeFields on Trade {
@@ -26,6 +26,7 @@ export const TradeFieldsFragmentDoc = gql`
price
size
createdAt
aggressor
market {
id
}
@@ -89,6 +90,7 @@ export const TradesUpdateDocument = gql`
size
createdAt
marketId
aggressor
}
}
`;
+2 -2
View File
@@ -20,7 +20,7 @@ import produce from 'immer';
export const MAX_TRADES = 50;
const getData = (
responseData: TradesQuery
responseData: TradesQuery | null
): ({
cursor: string;
node: TradeFieldsFragment;
@@ -30,7 +30,7 @@ const getDelta = (subscriptionData: TradesUpdateSubscription) =>
subscriptionData?.trades || [];
const update = (
data: ReturnType<typeof getData>,
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getDelta>
) => {
return produce(data, (draft) => {
+54 -53
View File
@@ -1,13 +1,15 @@
import { act, render, screen } from '@testing-library/react';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
import { DOWN_CLASS, TradesTable, UP_CLASS } from './trades-table';
import { SELL_CLASS, TradesTable, BUY_CLASS } from './trades-table';
import type { Trade } from './trades-data-provider';
import { Side } from '@vegaprotocol/types';
const trade: Trade = {
__typename: 'Trade',
id: 'trade-id',
price: '111122200',
size: '2000',
aggressor: Side.SIDE_BUY,
createdAt: new Date('2022-04-06T19:00:00').toISOString(),
market: {
__typename: 'Market',
@@ -17,59 +19,58 @@ const trade: Trade = {
} as Trade['market'],
};
it('Correct columns are rendered', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
});
const expectedHeaders = ['Price', 'Size', 'Created at'];
const headers = screen.getAllByRole('columnheader');
expect(headers).toHaveLength(expectedHeaders.length);
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
});
it('Number and data columns are formatted', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
describe('TradesTable', () => {
it('should render correct columns', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
});
const expectedHeaders = ['Price', 'Size', 'Created at'];
const headers = screen.getAllByRole('columnheader');
expect(headers).toHaveLength(expectedHeaders.length);
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'1,111,222.00',
'20.00',
getDateTimeFormat().format(new Date(trade.createdAt)),
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
it('should format number and data columns', async () => {
await act(async () => {
render(<TradesTable rowData={[trade]} />);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'1,111,222.00',
'20.00',
getDateTimeFormat().format(new Date(trade.createdAt)),
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
});
it('should format price and size columns', async () => {
const trade2 = {
...trade,
id: 'trade-id-2',
price: (Number(trade.price) + 10).toString(),
size: (Number(trade.size) - 10).toString(),
};
await act(async () => {
render(<TradesTable rowData={[trade2, trade]} />);
});
const cells = screen.getAllByRole('gridcell');
const priceCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'price'
);
const sizeCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'size'
);
// For first trade price should have green class
// row 1
expect(priceCells[0]).toHaveClass(BUY_CLASS);
expect(priceCells[1]).not.toHaveClass(SELL_CLASS);
expect(sizeCells[1]).not.toHaveClass(SELL_CLASS);
expect(sizeCells[1]).not.toHaveClass(BUY_CLASS);
});
});
it('Price and size columns are formatted', async () => {
const trade2 = {
...trade,
id: 'trade-id-2',
price: (Number(trade.price) + 10).toString(),
size: (Number(trade.size) - 10).toString(),
};
await act(async () => {
render(<TradesTable rowData={[trade2, trade]} />);
});
const cells = screen.getAllByRole('gridcell');
const priceCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'price'
);
const sizeCells = cells.filter(
(cell) => cell.getAttribute('col-id') === 'size'
);
// For first trade price should have green class and size should have red class
// row 1
expect(priceCells[0]).toHaveClass(UP_CLASS);
expect(priceCells[1]).not.toHaveClass(DOWN_CLASS);
expect(priceCells[1]).not.toHaveClass(UP_CLASS);
expect(sizeCells[0]).toHaveClass(DOWN_CLASS);
expect(sizeCells[1]).not.toHaveClass(DOWN_CLASS);
expect(sizeCells[1]).not.toHaveClass(UP_CLASS);
});
+13 -23
View File
@@ -13,31 +13,22 @@ import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
import type { CellClassParams, ValueFormatterParams } from 'ag-grid-community';
import type { AgGridReactProps } from 'ag-grid-react';
import type { Trade } from './trades-data-provider';
import BigNumber from 'bignumber.js';
import { Side } from '@vegaprotocol/types';
export const UP_CLASS = 'text-vega-green dark:text-vega-green';
export const DOWN_CLASS = 'text-vega-pink dark:text-vega-pink';
export const BUY_CLASS = 'text-vega-green dark:text-vega-green';
export const SELL_CLASS = 'text-vega-pink dark:text-vega-pink';
const changeCellClass =
(dataKey: string) =>
({ api, value, node }: CellClassParams) => {
const rowIndex = node?.rowIndex;
let colorClass = '';
const changeCellClass = ({ node }: CellClassParams) => {
let colorClass = '';
if (typeof rowIndex === 'number') {
const prevRowNode = api.getModel().getRow(rowIndex + 1);
const prevValue = prevRowNode?.data && prevRowNode.data[dataKey];
const valueNum = new BigNumber(value);
if (node.data?.aggressor === Side.SIDE_BUY) {
colorClass = BUY_CLASS;
} else if (node.data?.aggressor === Side.SIDE_SELL) {
colorClass = SELL_CLASS;
}
if (valueNum.isGreaterThan(prevValue)) {
colorClass = UP_CLASS;
} else if (valueNum.isLessThan(prevValue)) {
colorClass = DOWN_CLASS;
}
}
return ['font-mono text-right', colorClass].join(' ');
};
return ['font-mono text-right', colorClass].join(' ');
};
export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> {
successCallback(rowsThisBlock: (Trade | null)[], lastRow?: number): void;
@@ -77,7 +68,7 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
field="price"
type="rightAligned"
width={130}
cellClass={changeCellClass('price')}
cellClass={changeCellClass}
valueFormatter={({
value,
data,
@@ -127,7 +118,6 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
}
return addDecimal(value, data.market.positionDecimalPlaces);
}}
cellClass={changeCellClass('size')}
/>
<AgGridColumn
headerName={t('Created at')}
+5
View File
@@ -1,3 +1,4 @@
import { Side } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
@@ -47,6 +48,7 @@ export const tradesUpdateSubscription = (
size: '24',
createdAt: '2022-04-06T16:19:42.692598951Z',
marketId: 'market-0',
aggressor: Side.SIDE_BUY,
},
],
};
@@ -59,6 +61,7 @@ const trades: TradeFieldsFragment[] = [
price: '17116898',
size: '24',
createdAt: '2022-04-06T16:19:42.692598951Z',
aggressor: Side.SIDE_BUY,
market: {
id: 'market-0',
__typename: 'Market',
@@ -70,6 +73,7 @@ const trades: TradeFieldsFragment[] = [
price: '17209102',
size: '7',
createdAt: '2022-04-07T06:59:44.835686754Z',
aggressor: Side.SIDE_SELL,
market: {
id: 'market-0',
__typename: 'Market',
@@ -81,6 +85,7 @@ const trades: TradeFieldsFragment[] = [
price: '17106734',
size: '18',
createdAt: '2022-04-07T17:56:47.997938583Z',
aggressor: Side.SIDE_BUY,
market: {
id: 'market-0',
__typename: 'Market',
@@ -0,0 +1,9 @@
import { render } from '@testing-library/react';
import { AnnouncementBanner } from './announcement-banner';
describe('Banner', () => {
it('should render successfully', () => {
const { baseElement } = render(<AnnouncementBanner>Hi</AnnouncementBanner>);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,18 @@
import type { Story, ComponentMeta } from '@storybook/react';
import { AnnouncementBanner } from './announcement-banner';
export default {
component: AnnouncementBanner,
title: 'Announcement Banner',
} as ComponentMeta<typeof AnnouncementBanner>;
const Template: Story = (args) => (
<div className="mb-8">
<AnnouncementBanner {...args} />
</div>
);
export const Default = Template.bind({});
Default.args = {
children: <div className="text-center">Banner text</div>,
};
@@ -0,0 +1,15 @@
import classnames from 'classnames';
import type { ReactNode } from 'react';
export interface BannerProps {
children?: ReactNode;
}
export const AnnouncementBanner = ({ children }: BannerProps) => {
const bannerClasses = classnames(
"bg-[url('https://static.vega.xyz/assets/img/banner-bg.jpg')] bg-cover bg-center bg-no-repeat",
'p-4'
);
return <div className={bannerClasses}>{children}</div>;
};

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