Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3355ce84df | ||
|
|
ec5d1a7775 | ||
|
|
380974c9c3 | ||
|
|
7ab536fd41 | ||
|
|
a3c41c8075 | ||
|
|
7df012a38f | ||
|
|
341d01508f |
@@ -151,6 +151,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
projects_e2e=${projects_e2e%?}
|
projects_e2e=${projects_e2e%?}
|
||||||
projects_e2e=[${projects_e2e// /,}]
|
projects_e2e=[${projects_e2e// /,}]
|
||||||
|
projects=${projects%?}
|
||||||
projects=[${projects// /,}]
|
projects=[${projects// /,}]
|
||||||
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
|
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
|
||||||
echo PROJECTS=$projects >> $GITHUB_ENV
|
echo PROJECTS=$projects >> $GITHUB_ENV
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ context(
|
|||||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.skip('Eth wallet - contains VEGA tokens', function () {
|
describe('Eth wallet - contains VEGA tokens', function () {
|
||||||
beforeEach(
|
beforeEach(
|
||||||
'teardown wallet & drill into a specific validator',
|
'teardown wallet & drill into a specific validator',
|
||||||
function () {
|
function () {
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{proposal.terms.change.__typename !== 'NewMarket' &&
|
{proposal.terms.change.__typename !== 'NewMarket' &&
|
||||||
proposal.terms.change.__typename !== 'UpdateMarket' &&
|
proposal.terms.change.__typename !== 'UpdateMarket' && (
|
||||||
proposal.terms.change.__typename !== 'NewFreeform' && (
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<ProposalTerms data={proposal.terms} />
|
<ProposalTerms data={proposal.terms} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,18 +3,6 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
|||||||
import { marketsDataQuery } from '@vegaprotocol/mock';
|
import { marketsDataQuery } from '@vegaprotocol/mock';
|
||||||
import { positionsQuery } from '@vegaprotocol/mock';
|
import { positionsQuery } from '@vegaprotocol/mock';
|
||||||
|
|
||||||
// #region consts
|
|
||||||
const closePosition = 'close-position';
|
|
||||||
const dialogCloseX = 'dialog-close';
|
|
||||||
const dialogContent = 'dialog-content';
|
|
||||||
const dropDownMenu = 'dropdown-menu';
|
|
||||||
const marketActionsContent = 'market-actions-content';
|
|
||||||
const positions = 'Positions';
|
|
||||||
const tabPositions = 'tab-positions';
|
|
||||||
const toastContent = 'toast-content';
|
|
||||||
const tooltipContent = 'tooltip-content';
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
@@ -23,9 +11,8 @@ beforeEach(() => {
|
|||||||
|
|
||||||
describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||||
it('renders positions on trading page', () => {
|
it('renders positions on trading page', () => {
|
||||||
visitAndClickPositions();
|
cy.visit('/#/markets/market-0');
|
||||||
// 7004-POSI-001
|
cy.getByTestId('Positions').click();
|
||||||
// 7004-POSI-002
|
|
||||||
validatePositionsDisplayed();
|
validatePositionsDisplayed();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,313 +35,174 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
|||||||
}
|
}
|
||||||
aliasGQLQuery(req, 'Positions', positions);
|
aliasGQLQuery(req, 'Positions', positions);
|
||||||
});
|
});
|
||||||
visitAndClickPositions();
|
cy.visit('/#/portfolio');
|
||||||
// 7004-POSI-001
|
cy.getByTestId('Positions').click();
|
||||||
// 7004-POSI-002
|
|
||||||
validatePositionsDisplayed(true);
|
validatePositionsDisplayed(true);
|
||||||
});
|
});
|
||||||
|
describe('renders position among some graphql errors', () => {
|
||||||
it('Close my position', () => {
|
it('rows should be displayed despite errors', () => {
|
||||||
visitAndClickPositions();
|
const errors = [
|
||||||
cy.getByTestId(closePosition).first().click();
|
{
|
||||||
// 7004-POSI-010
|
message:
|
||||||
cy.getByTestId(toastContent).should(
|
'no market data for market: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||||
'contain.text',
|
path: ['marketsConnection', 'edges'],
|
||||||
'Awaiting confirmation'
|
extensions: {
|
||||||
);
|
code: 13,
|
||||||
});
|
type: 'Internal',
|
||||||
});
|
},
|
||||||
describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
|
||||||
it('rows should be displayed despite errors', () => {
|
|
||||||
const errors = [
|
|
||||||
{
|
|
||||||
message:
|
|
||||||
'no market data for market: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
|
||||||
path: ['marketsConnection', 'edges'],
|
|
||||||
extensions: {
|
|
||||||
code: 13,
|
|
||||||
type: 'Internal',
|
|
||||||
},
|
},
|
||||||
},
|
];
|
||||||
];
|
const marketData = marketsDataQuery();
|
||||||
const marketData = marketsDataQuery();
|
const edges = marketData.marketsConnection?.edges.map((market) => {
|
||||||
const edges = marketData.marketsConnection?.edges.map((market) => {
|
const replace =
|
||||||
const replace =
|
market.node.data?.market.id === 'market-2' ? null : market.node.data;
|
||||||
market.node.data?.market.id === 'market-2' ? null : market.node.data;
|
return { ...market, node: { ...market.node, data: replace } };
|
||||||
return { ...market, node: { ...market.node, data: replace } };
|
});
|
||||||
});
|
const overrides = {
|
||||||
const overrides = {
|
...marketData,
|
||||||
...marketData,
|
marketsConnection: { ...marketData.marketsConnection, edges },
|
||||||
marketsConnection: { ...marketData.marketsConnection, edges },
|
};
|
||||||
};
|
cy.mockGQL((req) => {
|
||||||
cy.mockGQL((req) => {
|
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
});
|
||||||
});
|
cy.visit('/#/markets/market-0');
|
||||||
cy.visit('/#/markets/market-0');
|
const emptyCells = [
|
||||||
const emptyCells = [
|
'notional',
|
||||||
'notional',
|
'markPrice',
|
||||||
'markPrice',
|
'currentLeverage',
|
||||||
'currentLeverage',
|
'averageEntryPrice',
|
||||||
'averageEntryPrice',
|
];
|
||||||
];
|
cy.getByTestId('tab-positions')
|
||||||
cy.getByTestId(tabPositions)
|
.first()
|
||||||
.first()
|
.within(() => {
|
||||||
.within(() => {
|
cy.get(
|
||||||
cy.get(
|
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
)
|
||||||
)
|
.eq(1)
|
||||||
.eq(1)
|
.within(() => {
|
||||||
.within(() => {
|
emptyCells.forEach((cell) => {
|
||||||
emptyCells.forEach((cell) => {
|
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
||||||
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
it('error message should be displayed', () => {
|
||||||
|
const errors = [
|
||||||
it('error message should be displayed', () => {
|
{
|
||||||
const errors = [
|
message:
|
||||||
{
|
'no market data for asset: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||||
message:
|
path: ['assets', 'edges'],
|
||||||
'no market data for asset: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
extensions: {
|
||||||
path: ['assets', 'edges'],
|
code: 13,
|
||||||
extensions: {
|
type: 'Internal',
|
||||||
code: 13,
|
},
|
||||||
type: 'Internal',
|
|
||||||
},
|
},
|
||||||
},
|
];
|
||||||
];
|
const overrides = {
|
||||||
const overrides = {
|
marketsConnection: { edges: [] },
|
||||||
marketsConnection: { edges: [] },
|
};
|
||||||
};
|
cy.mockGQL((req) => {
|
||||||
cy.mockGQL((req) => {
|
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
|
||||||
});
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.getByTestId(tabPositions).contains('no market data');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorting by Market', () => {
|
|
||||||
visitAndClickPositions();
|
|
||||||
const marketsSortedDefault = [
|
|
||||||
'ACTIVE MARKET',
|
|
||||||
'Apple Monthly (30 Jun 2022)',
|
|
||||||
'ETHBTC Quarterly (30 Jun 2022)',
|
|
||||||
'SUSPENDED MARKET',
|
|
||||||
];
|
|
||||||
const marketsSortedAsc = [
|
|
||||||
'ACTIVE MARKET',
|
|
||||||
'Apple Monthly (30 Jun 2022)',
|
|
||||||
'ETHBTC Quarterly (30 Jun 2022)',
|
|
||||||
'SUSPENDED MARKET',
|
|
||||||
];
|
|
||||||
const marketsSortedDesc = [
|
|
||||||
'SUSPENDED MARKET',
|
|
||||||
'ETHBTC Quarterly (30 Jun 2022)',
|
|
||||||
'Apple Monthly (30 Jun 2022)',
|
|
||||||
'ACTIVE MARKET',
|
|
||||||
];
|
|
||||||
cy.getByTestId(positions).click();
|
|
||||||
// 7004-POSI-003
|
|
||||||
checkSorting(
|
|
||||||
'marketName',
|
|
||||||
marketsSortedDefault,
|
|
||||||
marketsSortedAsc,
|
|
||||||
marketsSortedDesc
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Resize column', () => {
|
|
||||||
let elementWidth: number;
|
|
||||||
visitAndClickPositions();
|
|
||||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
|
||||||
cy.get('.ag-header-container').within(() => {
|
|
||||||
cy.get(`[col-id="marketName"]`)
|
|
||||||
.find('.ag-header-cell-resize')
|
|
||||||
.realMouseDown()
|
|
||||||
.realMouseMove(250, 0)
|
|
||||||
.realMouseUp();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 7004-POSI-006
|
|
||||||
cy.get(`[col-id="marketName"]`)
|
|
||||||
.invoke('width')
|
|
||||||
.should('be.greaterThan', 250);
|
|
||||||
cy.get(`[col-id="marketName"]`)
|
|
||||||
.invoke('width')
|
|
||||||
.then((width) => {
|
|
||||||
elementWidth = width as number;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
let localStorageCopy: Record<string, string>;
|
|
||||||
cy.window().then((win) => {
|
|
||||||
localStorageCopy = { ...win.localStorage };
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.reload();
|
|
||||||
cy.window().then((win) => {
|
|
||||||
Object.keys(localStorageCopy).forEach((key) => {
|
|
||||||
win.localStorage.setItem(key, localStorageCopy[key]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 7004-POSI-012
|
|
||||||
cy.get('[col-id="marketName"]')
|
|
||||||
.invoke('width')
|
|
||||||
.should('equal', elementWidth);
|
|
||||||
});
|
});
|
||||||
});
|
cy.visit('/#/markets/market-0');
|
||||||
|
cy.get('[data-testid="tab-positions"]').contains('no market data');
|
||||||
it('Scroll horizontally', () => {
|
|
||||||
visitAndClickPositions();
|
|
||||||
|
|
||||||
cy.get('.ag-header-container').within(() => {
|
|
||||||
cy.get(`[col-id="marketName"]`)
|
|
||||||
.find('.ag-header-cell-resize')
|
|
||||||
.realMouseDown()
|
|
||||||
.realMouseMove(400, 0)
|
|
||||||
.realMouseUp();
|
|
||||||
});
|
|
||||||
cy.get('[col-id="marketName"]').should('be.visible');
|
|
||||||
cy.get('.ag-body-horizontal-scroll-viewport').realMouseWheel({
|
|
||||||
deltaX: 500,
|
|
||||||
});
|
|
||||||
// 7004-POSI-004
|
|
||||||
cy.get('[col-id="updatedAt"]').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Drag and drop columns', () => {
|
|
||||||
visitAndClickPositions();
|
|
||||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
|
||||||
cy.get('[col-id="marketName"]')
|
|
||||||
.realMouseDown()
|
|
||||||
.realMouseMove(700, 15)
|
|
||||||
.realMouseUp();
|
|
||||||
|
|
||||||
// 7004-POSI-005
|
|
||||||
cy.get('[col-id="marketName"]').should(($element) => {
|
|
||||||
const attributeValue = $element.attr('aria-colindex');
|
|
||||||
expect(attributeValue).not.to.equal('1');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('I can see warnings', () => {
|
describe('sorting by ag-grid columns should work well', () => {
|
||||||
visitAndClickPositions();
|
it('sorting by Market', () => {
|
||||||
|
cy.visit('/#/markets/market-0');
|
||||||
|
const marketsSortedDefault = [
|
||||||
|
'ACTIVE MARKET',
|
||||||
|
'Apple Monthly (30 Jun 2022)',
|
||||||
|
'SUSPENDED MARKET',
|
||||||
|
];
|
||||||
|
const marketsSortedAsc = ['ACTIVE MARKET', 'Apple Monthly (30 Jun 2022)'];
|
||||||
|
const marketsSortedDesc = [
|
||||||
|
'SUSPENDED MARKET',
|
||||||
|
'Apple Monthly (30 Jun 2022)',
|
||||||
|
'ACTIVE MARKET',
|
||||||
|
];
|
||||||
|
cy.getByTestId('Positions').click();
|
||||||
|
checkSorting(
|
||||||
|
'marketName',
|
||||||
|
marketsSortedDefault,
|
||||||
|
marketsSortedAsc,
|
||||||
|
marketsSortedDesc
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('sorting by notional', () => {
|
||||||
|
cy.visit('/#/markets/market-0');
|
||||||
|
const marketsSortedDefault = [
|
||||||
|
'276,761.40348',
|
||||||
|
'46,126.90058',
|
||||||
|
'1,688.20',
|
||||||
|
];
|
||||||
|
const marketsSortedAsc = ['1,688.20', '46,126.90058', '276,761.40348'];
|
||||||
|
const marketsSortedDesc = ['276,761.40348', '46,126.90058', '1,688.20'];
|
||||||
|
cy.getByTestId('Positions').click();
|
||||||
|
checkSorting(
|
||||||
|
'notional',
|
||||||
|
marketsSortedDefault,
|
||||||
|
marketsSortedAsc,
|
||||||
|
marketsSortedDesc
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('sorting by unrealisedPNL', () => {
|
||||||
|
cy.visit('/#/markets/market-0');
|
||||||
|
const marketsSortedDefault = ['8.95', '-0.22519', '8.95'];
|
||||||
|
const marketsSortedAsc = ['-0.22519', '8.95', '8.95'];
|
||||||
|
const marketsSortedDesc = ['8.95', '8.95', '-0.22519'];
|
||||||
|
cy.getByTestId('Positions').click();
|
||||||
|
checkSorting(
|
||||||
|
'unrealisedPNL',
|
||||||
|
marketsSortedDefault,
|
||||||
|
marketsSortedAsc,
|
||||||
|
marketsSortedDesc
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
cy.get('[col-id="openVolume"]').within(() => {
|
function validatePositionsDisplayed(multiKey = false) {
|
||||||
cy.get('[aria-label="warning-sign icon"]')
|
cy.getByTestId('tab-positions').should('be.visible');
|
||||||
|
cy.getByTestId('tab-positions').within(() => {
|
||||||
|
cy.get('[col-id="marketName"]')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.realHover();
|
.each(($marketSymbol) => {
|
||||||
});
|
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||||
// 7004-POSI-011
|
});
|
||||||
cy.getByTestId(tooltipContent).should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Positive and Negative color change', () => {
|
cy.get('.ag-center-cols-container [col-id="openVolume"]').each(
|
||||||
cy.visit('/#/markets/market-0');
|
($openVolume) => {
|
||||||
cy.getByTestId(positions).click();
|
cy.wrap($openVolume).invoke('text').should('not.be.empty');
|
||||||
// 7004-POSI-007
|
}
|
||||||
cy.get('.ag-center-cols-container').within(() => {
|
|
||||||
assertPNLColor(
|
|
||||||
'[col-id="realisedPNL"]',
|
|
||||||
'text-vega-green',
|
|
||||||
'text-vega-pink'
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
cy.get('.ag-center-cols-container').within(() => {
|
|
||||||
assertPNLColor(
|
|
||||||
'[col-id="unrealisedPNL"]',
|
|
||||||
'text-vega-green',
|
|
||||||
'text-vega-pink'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
cy.get('.ag-center-cols-container').within(() => {
|
|
||||||
assertPNLColor(
|
|
||||||
'[col-id="openVolume"]',
|
|
||||||
'text-vega-green',
|
|
||||||
'text-vega-pink'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('View settlement asset', () => {
|
// includes average entry price, mark price, realised PNL & leverage
|
||||||
visitAndClickPositions();
|
cy.getByTestId('flash-cell').each(($prices) => {
|
||||||
cy.get('[col-id="asset"]').within(() => {
|
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||||
cy.get('button[type="button"]').first().click();
|
|
||||||
});
|
|
||||||
// 7004-POSI-008
|
|
||||||
cy.getByTestId(dialogContent).should('be.visible');
|
|
||||||
cy.getByTestId(dialogCloseX).click();
|
|
||||||
cy.getByTestId(dropDownMenu).first().click();
|
|
||||||
cy.getByTestId(marketActionsContent).click();
|
|
||||||
// 7004-POSI-009
|
|
||||||
cy.getByTestId(dialogContent).should('be.visible');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
function validatePositionsDisplayed(multiKey = false) {
|
|
||||||
cy.getByTestId('tab-positions').should('be.visible');
|
|
||||||
cy.getByTestId('tab-positions').within(() => {
|
|
||||||
cy.get('[col-id="marketName"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.each(($marketSymbol) => {
|
|
||||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('.ag-center-cols-container [col-id="openVolume"]').each(
|
if (!multiKey) {
|
||||||
($openVolume) => {
|
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||||
cy.wrap($openVolume).invoke('text').should('not.be.empty');
|
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||||
|
.should('contain.text', '0.01');
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// includes average entry price, mark price, realised PNL & leverage
|
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
||||||
cy.getByTestId('flash-cell').each(($prices) => {
|
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
||||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
});
|
||||||
|
|
||||||
|
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
||||||
|
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
||||||
|
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
||||||
|
|
||||||
|
cy.get('.ag-header-row [col-id="notional"]')
|
||||||
|
.should('contain.text', 'Notional')
|
||||||
|
.realHover();
|
||||||
|
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!multiKey) {
|
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
}
|
||||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
});
|
||||||
.should('contain.text', '0.01');
|
|
||||||
}
|
|
||||||
|
|
||||||
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
|
||||||
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
|
||||||
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
|
||||||
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
|
||||||
|
|
||||||
cy.get('.ag-header-row [col-id="notional"]')
|
|
||||||
.should('contain.text', 'Notional')
|
|
||||||
.realHover();
|
|
||||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
|
||||||
}
|
|
||||||
function assertPNLColor(
|
|
||||||
pnlSelector: string,
|
|
||||||
positiveClass: string,
|
|
||||||
negativeClass: string
|
|
||||||
) {
|
|
||||||
cy.get(pnlSelector).each(($el) => {
|
|
||||||
const value = parseFloat($el.text());
|
|
||||||
|
|
||||||
if (value > 0) {
|
|
||||||
cy.wrap($el).invoke('attr', 'class').should('contain', positiveClass);
|
|
||||||
} else if (value < 0) {
|
|
||||||
cy.wrap($el).invoke('attr', 'class').should('contain', negativeClass);
|
|
||||||
} else if (value == 0) {
|
|
||||||
cy.wrap($el)
|
|
||||||
.invoke('attr', 'class')
|
|
||||||
.should('not.contain', negativeClass, positiveClass);
|
|
||||||
} else {
|
|
||||||
throw new Error('Unexpected value');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function visitAndClickPositions() {
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.getByTestId(positions).click();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
|||||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||||
NX_APP_VERSION=v0.20.18-core-0.71.8
|
NX_APP_VERSION=v0.20.16-core-0.71.5
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type Data = Item[];
|
|||||||
type QueryData = {
|
type QueryData = {
|
||||||
data: Data;
|
data: Data;
|
||||||
pageInfo?: PageInfo;
|
pageInfo?: PageInfo;
|
||||||
|
totalCount?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CombinedData = {
|
type CombinedData = {
|
||||||
@@ -114,6 +115,7 @@ const paginatedSubscribe = makeDataProvider<
|
|||||||
first,
|
first,
|
||||||
append: defaultAppend,
|
append: defaultAppend,
|
||||||
getPageInfo: (r) => r?.pageInfo ?? null,
|
getPageInfo: (r) => r?.pageInfo ?? null,
|
||||||
|
getTotalCount: (r) => r?.totalCount,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -371,10 +373,32 @@ describe('data provider', () => {
|
|||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads requested data blocks', async () => {
|
it('fills data with nulls if pagination is enabled', async () => {
|
||||||
|
const totalCount = 1000;
|
||||||
|
const data: Item[] = new Array(first).fill(null).map((v, i) => ({
|
||||||
|
cursor: i.toString(),
|
||||||
|
node: {
|
||||||
|
id: i.toString(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const subscription = paginatedSubscribe(callback, client, variables);
|
||||||
|
await resolveQuery({
|
||||||
|
data,
|
||||||
|
totalCount,
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(callback.mock.calls[1][0].data?.length).toBe(totalCount);
|
||||||
|
subscription.unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads requested data blocks and inserts data with total count', async () => {
|
||||||
|
const totalCount = 1000;
|
||||||
const subscription = paginatedSubscribe(callback, client, variables);
|
const subscription = paginatedSubscribe(callback, client, variables);
|
||||||
await resolveQuery({
|
await resolveQuery({
|
||||||
data: generateData(),
|
data: generateData(),
|
||||||
|
totalCount,
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
hasNextPage: true,
|
hasNextPage: true,
|
||||||
endCursor: '100',
|
endCursor: '100',
|
||||||
@@ -383,25 +407,168 @@ describe('data provider', () => {
|
|||||||
|
|
||||||
// load next page
|
// load next page
|
||||||
subscription.load && subscription.load();
|
subscription.load && subscription.load();
|
||||||
const lastQueryArgs =
|
let lastQueryArgs =
|
||||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||||
expect(lastQueryArgs?.variables?.['pagination']).toEqual({
|
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||||
after: '100',
|
after: '100',
|
||||||
first,
|
first,
|
||||||
});
|
});
|
||||||
await resolveQuery({
|
await resolveQuery({
|
||||||
data: generateData(100),
|
data: generateData(100),
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
hasNextPage: false,
|
hasNextPage: true,
|
||||||
endCursor: '200',
|
endCursor: '200',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// load page with skip
|
||||||
|
subscription.load && subscription.load(500, 600);
|
||||||
|
lastQueryArgs =
|
||||||
|
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||||
|
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||||
|
after: '200',
|
||||||
|
first,
|
||||||
|
skip: 300,
|
||||||
|
});
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(500),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: true,
|
||||||
|
endCursor: '600',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// load in the gap
|
||||||
|
subscription.load && subscription.load(400, 500);
|
||||||
|
lastQueryArgs =
|
||||||
|
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||||
|
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||||
|
after: '200',
|
||||||
|
first,
|
||||||
|
skip: 200,
|
||||||
|
});
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(400),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: true,
|
||||||
|
endCursor: '500',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// load page after last block
|
||||||
|
subscription.load && subscription.load(700, 800);
|
||||||
|
lastQueryArgs =
|
||||||
|
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||||
|
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||||
|
after: '600',
|
||||||
|
first,
|
||||||
|
skip: 100,
|
||||||
|
});
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(700),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: true,
|
||||||
|
endCursor: '800',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// load last page shorter than expected
|
||||||
|
subscription.load && subscription.load(950, 1050);
|
||||||
|
lastQueryArgs =
|
||||||
|
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||||
|
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||||
|
after: '800',
|
||||||
|
first,
|
||||||
|
skip: 150,
|
||||||
|
});
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(950, 20),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: false,
|
||||||
|
endCursor: '970',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||||
|
expect(lastCallbackArgs[0].totalCount).toBe(970);
|
||||||
|
|
||||||
// load next page when pageInfo.hasNextPage === false
|
// load next page when pageInfo.hasNextPage === false
|
||||||
const clientQueryCallsLength = clientQuery.mock.calls.length;
|
const clientQueryCallsLength = clientQuery.mock.calls.length;
|
||||||
subscription.load && subscription.load();
|
subscription.load && subscription.load();
|
||||||
expect(clientQuery.mock.calls.length).toBe(clientQueryCallsLength);
|
expect(clientQuery.mock.calls.length).toBe(clientQueryCallsLength);
|
||||||
|
|
||||||
|
// load last page longer than expected
|
||||||
|
subscription.load && subscription.load(960, 1000);
|
||||||
|
lastQueryArgs =
|
||||||
|
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||||
|
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||||
|
after: '960',
|
||||||
|
first,
|
||||||
|
});
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(960, 40),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: true,
|
||||||
|
endCursor: '1000',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||||
|
expect(lastCallbackArgs[0].totalCount).toBe(1000);
|
||||||
|
|
||||||
|
subscription.unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads requested data blocks and inserts data without totalCount', async () => {
|
||||||
|
const totalCount = undefined;
|
||||||
|
const subscription = paginatedSubscribe(callback, client, variables);
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(),
|
||||||
|
totalCount,
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: true,
|
||||||
|
endCursor: '100',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||||
|
expect(lastCallbackArgs[0].totalCount).toBe(undefined);
|
||||||
|
|
||||||
|
// load next page
|
||||||
|
subscription.load && subscription.load();
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(100),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: true,
|
||||||
|
endCursor: '200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||||
|
expect(lastCallbackArgs[0].totalCount).toBe(undefined);
|
||||||
|
|
||||||
|
// load last page
|
||||||
|
subscription.load && subscription.load();
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(200, 50),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: false,
|
||||||
|
endCursor: '250',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||||
|
expect(lastCallbackArgs[0].totalCount).toBe(250);
|
||||||
|
subscription.unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets total count when first page has no next page', async () => {
|
||||||
|
const subscription = paginatedSubscribe(callback, client, variables);
|
||||||
|
await resolveQuery({
|
||||||
|
data: generateData(),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: false,
|
||||||
|
endCursor: '100',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const lastCallbackArgs =
|
||||||
|
callback.mock.calls[callback.mock.calls.length - 1];
|
||||||
|
expect(lastCallbackArgs[0].totalCount).toBe(100);
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -585,7 +752,7 @@ describe('derived data provider', () => {
|
|||||||
subscription.load && subscription.load();
|
subscription.load && subscription.load();
|
||||||
const lastQueryArgs =
|
const lastQueryArgs =
|
||||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||||
expect(lastQueryArgs?.variables?.['pagination']).toEqual({
|
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||||
after: '100',
|
after: '100',
|
||||||
first,
|
first,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface UpdateCallback<Data, Delta> {
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
loaded: boolean;
|
loaded: boolean;
|
||||||
pageInfo: PageInfo | null;
|
pageInfo: PageInfo | null;
|
||||||
|
totalCount?: number;
|
||||||
}
|
}
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
@@ -39,7 +40,9 @@ export interface Reload {
|
|||||||
(forceReset?: boolean): void;
|
(forceReset?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pagination = Schema.Pagination;
|
type Pagination = Schema.Pagination & {
|
||||||
|
skip?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export interface PageInfo {
|
export interface PageInfo {
|
||||||
startCursor?: string;
|
startCursor?: string;
|
||||||
@@ -80,8 +83,12 @@ export interface Append<Data> {
|
|||||||
data: Data | null,
|
data: Data | null,
|
||||||
insertionData: Data | null,
|
insertionData: Data | null,
|
||||||
insertionPageInfo: PageInfo | null,
|
insertionPageInfo: PageInfo | null,
|
||||||
pagination?: Pagination
|
pagination?: Pagination,
|
||||||
): Data | null;
|
totalCount?: number
|
||||||
|
): {
|
||||||
|
data: Data | null;
|
||||||
|
totalCount?: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetData<QueryData, Data, Variables> {
|
interface GetData<QueryData, Data, Variables> {
|
||||||
@@ -92,6 +99,10 @@ interface GetPageInfo<QueryData> {
|
|||||||
(queryData: QueryData): PageInfo | null;
|
(queryData: QueryData): PageInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetTotalCount<QueryData> {
|
||||||
|
(queryData: QueryData): number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
interface GetDelta<SubscriptionData, Delta, Variables> {
|
interface GetDelta<SubscriptionData, Delta, Variables> {
|
||||||
(
|
(
|
||||||
subscriptionData: SubscriptionData,
|
subscriptionData: SubscriptionData,
|
||||||
@@ -108,32 +119,44 @@ export interface Edge<T extends Node> extends Cursor {
|
|||||||
node: T;
|
node: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultAppend<T extends Cursor>(
|
export function defaultAppend<Data>(
|
||||||
data: T[] | null,
|
data: Data | null,
|
||||||
insertionData: T[] | null,
|
insertionData: Data | null,
|
||||||
insertionPageInfo: PageInfo | null,
|
insertionPageInfo: PageInfo | null,
|
||||||
pagination?: Pagination
|
pagination?: Pagination,
|
||||||
|
totalCount?: number
|
||||||
) {
|
) {
|
||||||
if (data && insertionData && insertionPageInfo) {
|
if (data && insertionData && insertionPageInfo) {
|
||||||
if (!(data instanceof Array) || !(insertionData instanceof Array)) {
|
if (!(data instanceof Array) || !(insertionData instanceof Array)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'data needs to be instance of Array[] when using pagination'
|
'data needs to be instance of Edge[] when using pagination'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (pagination?.after) {
|
if (pagination?.after) {
|
||||||
if (data[data.length - 1].cursor === pagination?.after) {
|
|
||||||
return [...data, ...insertionData];
|
|
||||||
}
|
|
||||||
const cursors = data.map((item) => item && item.cursor);
|
const cursors = data.map((item) => item && item.cursor);
|
||||||
const startIndex = cursors.lastIndexOf(pagination.after);
|
const startIndex = cursors.lastIndexOf(pagination.after);
|
||||||
if (startIndex !== -1) {
|
if (startIndex !== -1) {
|
||||||
const start = startIndex + 1;
|
const start = startIndex + 1 + (pagination.skip ?? 0);
|
||||||
const updatedData = [...data.slice(0, start), ...insertionData];
|
const end = start + insertionData.length;
|
||||||
return updatedData;
|
let updatedData = [
|
||||||
|
...data.slice(0, start),
|
||||||
|
...insertionData,
|
||||||
|
...data.slice(end),
|
||||||
|
];
|
||||||
|
if (!insertionPageInfo.hasNextPage && end !== (totalCount ?? 0)) {
|
||||||
|
// adjust totalCount if last page is shorter or longer than expected
|
||||||
|
totalCount = end;
|
||||||
|
updatedData = updatedData.slice(0, end);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data: updatedData,
|
||||||
|
// increase totalCount if last page is longer than expected
|
||||||
|
totalCount: totalCount && Math.max(updatedData.length, totalCount),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data;
|
return { data, totalCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataProviderParams<
|
interface DataProviderParams<
|
||||||
@@ -152,6 +175,7 @@ interface DataProviderParams<
|
|||||||
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
|
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
|
||||||
pagination?: {
|
pagination?: {
|
||||||
getPageInfo: GetPageInfo<QueryData>;
|
getPageInfo: GetPageInfo<QueryData>;
|
||||||
|
getTotalCount?: GetTotalCount<QueryData>;
|
||||||
append: Append<Data>;
|
append: Append<Data>;
|
||||||
first: number;
|
first: number;
|
||||||
};
|
};
|
||||||
@@ -221,6 +245,7 @@ function makeDataProviderInternal<
|
|||||||
let client: ApolloClient<object>;
|
let client: ApolloClient<object>;
|
||||||
let subscription: Subscription[] | undefined;
|
let subscription: Subscription[] | undefined;
|
||||||
let pageInfo: PageInfo | null = null;
|
let pageInfo: PageInfo | null = null;
|
||||||
|
let totalCount: number | undefined;
|
||||||
|
|
||||||
// notify single callback about current state, delta is passes optionally only if notify was invoked onNext
|
// notify single callback about current state, delta is passes optionally only if notify was invoked onNext
|
||||||
const notify = (
|
const notify = (
|
||||||
@@ -233,6 +258,7 @@ function makeDataProviderInternal<
|
|||||||
loading,
|
loading,
|
||||||
loaded,
|
loaded,
|
||||||
pageInfo,
|
pageInfo,
|
||||||
|
totalCount,
|
||||||
...updateData,
|
...updateData,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -275,41 +301,59 @@ function makeDataProviderInternal<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const load = async () => {
|
const load = async (start?: number) => {
|
||||||
if (!pagination) {
|
if (!pagination) {
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
if (!pageInfo?.hasNextPage) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const paginationVariables: Pagination = {
|
const paginationVariables: Pagination = {
|
||||||
first: pagination.first,
|
first: pagination.first,
|
||||||
|
after: pageInfo?.endCursor,
|
||||||
};
|
};
|
||||||
if (data) {
|
if (start !== undefined && data instanceof Array) {
|
||||||
const endCursor = (data as Cursor[])[(data as Cursor[]).length - 1]
|
if (!start) {
|
||||||
.cursor;
|
paginationVariables.after = undefined;
|
||||||
if (endCursor) {
|
} else if (data && data[start - 1]) {
|
||||||
paginationVariables.after = endCursor;
|
paginationVariables.after = (data[start - 1] as Cursor).cursor;
|
||||||
|
} else {
|
||||||
|
let skip = 1;
|
||||||
|
while (!data[start - 1 - skip] && skip <= start) {
|
||||||
|
skip += 1;
|
||||||
|
}
|
||||||
|
paginationVariables.skip = skip;
|
||||||
|
if (skip === start) {
|
||||||
|
paginationVariables.after = undefined;
|
||||||
|
} else {
|
||||||
|
paginationVariables.after = (data[start - 1 - skip] as Cursor).cursor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if (!pageInfo?.hasNextPage) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await call(paginationVariables);
|
const res = await call(paginationVariables);
|
||||||
|
|
||||||
const insertionData = getData(res.data, variables);
|
const insertionData = getData(res.data, variables);
|
||||||
const insertionPageInfo = pagination.getPageInfo(res.data);
|
const insertionPageInfo = pagination.getPageInfo(res.data);
|
||||||
data = pagination.append(
|
({ data, totalCount } = pagination.append(
|
||||||
data,
|
data,
|
||||||
insertionData,
|
insertionData,
|
||||||
insertionPageInfo,
|
insertionPageInfo,
|
||||||
paginationVariables
|
paginationVariables,
|
||||||
);
|
totalCount
|
||||||
|
));
|
||||||
pageInfo = insertionPageInfo;
|
pageInfo = insertionPageInfo;
|
||||||
|
totalCount =
|
||||||
|
(pagination.getTotalCount && pagination.getTotalCount(res.data)) ??
|
||||||
|
totalCount;
|
||||||
notifyAll({ insertionData, isInsert: true });
|
notifyAll({ insertionData, isInsert: true });
|
||||||
return insertionData;
|
return insertionData;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setData = (updatedData: Data | null) => {
|
const setData = (updatedData: Data | null) => {
|
||||||
data = updatedData;
|
data = updatedData;
|
||||||
|
if (totalCount !== undefined && data instanceof Array) {
|
||||||
|
totalCount = data.length;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscriptionSubscribe = () => {
|
const subscriptionSubscribe = () => {
|
||||||
@@ -356,6 +400,16 @@ function makeDataProviderInternal<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
pageInfo = pagination.getPageInfo(res.data);
|
pageInfo = pagination.getPageInfo(res.data);
|
||||||
|
if (pageInfo && !pageInfo.hasNextPage) {
|
||||||
|
totalCount = data.length;
|
||||||
|
} else {
|
||||||
|
totalCount =
|
||||||
|
pagination.getTotalCount && pagination.getTotalCount(res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && totalCount && data.length < totalCount) {
|
||||||
|
data.push(...new Array(totalCount - data.length).fill(null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if there was some updates received from subscription during initial query loading apply them on just received data
|
// if there was some updates received from subscription during initial query loading apply them on just received data
|
||||||
if (update && data && updateQueue && updateQueue.length > 0) {
|
if (update && data && updateQueue && updateQueue.length > 0) {
|
||||||
@@ -363,6 +417,9 @@ function makeDataProviderInternal<
|
|||||||
const delta = updateQueue.shift();
|
const delta = updateQueue.shift();
|
||||||
if (delta) {
|
if (delta) {
|
||||||
setData(update(data, delta, reload, variables));
|
setData(update(data, delta, reload, variables));
|
||||||
|
if (totalCount !== undefined && data instanceof Array) {
|
||||||
|
totalCount = data.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,7 +590,7 @@ const memoize = <
|
|||||||
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can reload data provider
|
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can reload data provider
|
||||||
* @param getData transforms received query data to format that will be stored in data provider
|
* @param getData transforms received query data to format that will be stored in data provider
|
||||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||||
* @param pagination pagination related functions { getPageInfo, append, first }
|
* @param pagination pagination related functions { getPageInfo, getTotalCount, append, first }
|
||||||
* @returns Subscribe<Data, Delta> subscribe function
|
* @returns Subscribe<Data, Delta> subscribe function
|
||||||
* @example
|
* @example
|
||||||
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
|
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
|
||||||
|
|||||||
@@ -12,13 +12,23 @@ export interface useDataProviderParams<
|
|||||||
Variables extends OperationVariables | undefined = undefined
|
Variables extends OperationVariables | undefined = undefined
|
||||||
> {
|
> {
|
||||||
dataProvider: Subscribe<Data, Delta, Variables>;
|
dataProvider: Subscribe<Data, Delta, Variables>;
|
||||||
update?: ({ delta, data }: { delta?: Delta; data: Data | null }) => boolean;
|
update?: ({
|
||||||
|
delta,
|
||||||
|
data,
|
||||||
|
totalCount,
|
||||||
|
}: {
|
||||||
|
delta?: Delta;
|
||||||
|
data: Data | null;
|
||||||
|
totalCount?: number;
|
||||||
|
}) => boolean;
|
||||||
insert?: ({
|
insert?: ({
|
||||||
insertionData,
|
insertionData,
|
||||||
data,
|
data,
|
||||||
|
totalCount,
|
||||||
}: {
|
}: {
|
||||||
insertionData?: Data | null;
|
insertionData?: Data | null;
|
||||||
data: Data | null;
|
data: Data | null;
|
||||||
|
totalCount?: number;
|
||||||
}) => boolean;
|
}) => boolean;
|
||||||
variables: Variables;
|
variables: Variables;
|
||||||
skipUpdates?: boolean;
|
skipUpdates?: boolean;
|
||||||
@@ -46,6 +56,7 @@ export const useDataProvider = <
|
|||||||
}: useDataProviderParams<Data, Delta, Variables>) => {
|
}: useDataProviderParams<Data, Delta, Variables>) => {
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [data, setData] = useState<Data | null>(null);
|
const [data, setData] = useState<Data | null>(null);
|
||||||
|
const [totalCount, setTotalCount] = useState<number>();
|
||||||
const [loading, setLoading] = useState<boolean>(!skip);
|
const [loading, setLoading] = useState<boolean>(!skip);
|
||||||
const [error, setError] = useState<Error | undefined>(undefined);
|
const [error, setError] = useState<Error | undefined>(undefined);
|
||||||
const flushRef = useRef<(() => void) | undefined>(undefined);
|
const flushRef = useRef<(() => void) | undefined>(undefined);
|
||||||
@@ -90,6 +101,7 @@ export const useDataProvider = <
|
|||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
insertionData,
|
insertionData,
|
||||||
|
totalCount,
|
||||||
isInsert,
|
isInsert,
|
||||||
isUpdate,
|
isUpdate,
|
||||||
loaded,
|
loaded,
|
||||||
@@ -104,18 +116,19 @@ export const useDataProvider = <
|
|||||||
(skipUpdatesRef.current ||
|
(skipUpdatesRef.current ||
|
||||||
(!skipUpdatesRef.current &&
|
(!skipUpdatesRef.current &&
|
||||||
updateRef.current &&
|
updateRef.current &&
|
||||||
updateRef.current({ delta, data })))
|
updateRef.current({ delta, data, totalCount })))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
isInsert &&
|
isInsert &&
|
||||||
insertRef.current &&
|
insertRef.current &&
|
||||||
insertRef.current({ insertionData, data })
|
insertRef.current({ insertionData, data, totalCount })
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setTotalCount(totalCount);
|
||||||
setData(data);
|
setData(data);
|
||||||
if (!loading && !isUpdate && updateRef.current) {
|
if (!loading && !isUpdate && updateRef.current) {
|
||||||
updateRef.current({ data });
|
updateRef.current({ data });
|
||||||
@@ -137,6 +150,7 @@ export const useDataProvider = <
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setData(null);
|
setData(null);
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
setTotalCount(undefined);
|
||||||
if (updateRef.current) {
|
if (updateRef.current) {
|
||||||
updateRef.current({ data: null });
|
updateRef.current({ data: null });
|
||||||
}
|
}
|
||||||
@@ -170,6 +184,7 @@ export const useDataProvider = <
|
|||||||
flush,
|
flush,
|
||||||
reload,
|
reload,
|
||||||
load,
|
load,
|
||||||
|
totalCount,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export const DealTicket = ({
|
|||||||
});
|
});
|
||||||
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
||||||
const orders = activeOrders
|
const orders = activeOrders
|
||||||
? activeOrders.map<OrderInfo>((order) => ({
|
? activeOrders.map<OrderInfo>(({ node: order }) => ({
|
||||||
isMarketOrder: order.type === OrderType.TYPE_MARKET,
|
isMarketOrder: order.type === OrderType.TYPE_MARKET,
|
||||||
price: order.price,
|
price: order.price,
|
||||||
remaining: order.remaining,
|
remaining: order.remaining,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './lib/fills-container';
|
export * from './lib/fills-container';
|
||||||
|
export * from './lib/use-fills-list';
|
||||||
export * from './lib/fills-data-provider';
|
export * from './lib/fills-data-provider';
|
||||||
export * from './lib/__generated__/Fills';
|
export * from './lib/__generated__/Fills';
|
||||||
|
|||||||
@@ -48,32 +48,28 @@ query Fills($filter: TradesFilter, $pagination: Pagination) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment FillUpdateFields on TradeUpdate {
|
|
||||||
id
|
|
||||||
marketId
|
|
||||||
buyOrder
|
|
||||||
sellOrder
|
|
||||||
buyerId
|
|
||||||
sellerId
|
|
||||||
aggressor
|
|
||||||
price
|
|
||||||
size
|
|
||||||
createdAt
|
|
||||||
type
|
|
||||||
buyerFee {
|
|
||||||
makerFee
|
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
|
||||||
sellerFee {
|
|
||||||
makerFee
|
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
||||||
tradesStream(filter: $filter) {
|
tradesStream(filter: $filter) {
|
||||||
...FillUpdateFields
|
id
|
||||||
|
marketId
|
||||||
|
buyOrder
|
||||||
|
sellOrder
|
||||||
|
buyerId
|
||||||
|
sellerId
|
||||||
|
aggressor
|
||||||
|
price
|
||||||
|
size
|
||||||
|
createdAt
|
||||||
|
type
|
||||||
|
buyerFee {
|
||||||
|
makerFee
|
||||||
|
infrastructureFee
|
||||||
|
liquidityFee
|
||||||
|
}
|
||||||
|
sellerFee {
|
||||||
|
makerFee
|
||||||
|
infrastructureFee
|
||||||
|
liquidityFee
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-29
@@ -15,8 +15,6 @@ export type FillsQueryVariables = Types.Exact<{
|
|||||||
|
|
||||||
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
||||||
|
|
||||||
export type FillUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } };
|
|
||||||
|
|
||||||
export type FillsEventSubscriptionVariables = Types.Exact<{
|
export type FillsEventSubscriptionVariables = Types.Exact<{
|
||||||
filter: Types.TradesSubscriptionFilter;
|
filter: Types.TradesSubscriptionFilter;
|
||||||
}>;
|
}>;
|
||||||
@@ -62,31 +60,6 @@ export const FillEdgeFragmentDoc = gql`
|
|||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
${FillFieldsFragmentDoc}`;
|
${FillFieldsFragmentDoc}`;
|
||||||
export const FillUpdateFieldsFragmentDoc = gql`
|
|
||||||
fragment FillUpdateFields on TradeUpdate {
|
|
||||||
id
|
|
||||||
marketId
|
|
||||||
buyOrder
|
|
||||||
sellOrder
|
|
||||||
buyerId
|
|
||||||
sellerId
|
|
||||||
aggressor
|
|
||||||
price
|
|
||||||
size
|
|
||||||
createdAt
|
|
||||||
type
|
|
||||||
buyerFee {
|
|
||||||
makerFee
|
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
|
||||||
sellerFee {
|
|
||||||
makerFee
|
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export const FillsDocument = gql`
|
export const FillsDocument = gql`
|
||||||
query Fills($filter: TradesFilter, $pagination: Pagination) {
|
query Fills($filter: TradesFilter, $pagination: Pagination) {
|
||||||
trades(filter: $filter, pagination: $pagination) {
|
trades(filter: $filter, pagination: $pagination) {
|
||||||
@@ -134,10 +107,30 @@ export type FillsQueryResult = Apollo.QueryResult<FillsQuery, FillsQueryVariable
|
|||||||
export const FillsEventDocument = gql`
|
export const FillsEventDocument = gql`
|
||||||
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
||||||
tradesStream(filter: $filter) {
|
tradesStream(filter: $filter) {
|
||||||
...FillUpdateFields
|
id
|
||||||
|
marketId
|
||||||
|
buyOrder
|
||||||
|
sellOrder
|
||||||
|
buyerId
|
||||||
|
sellerId
|
||||||
|
aggressor
|
||||||
|
price
|
||||||
|
size
|
||||||
|
createdAt
|
||||||
|
type
|
||||||
|
buyerFee {
|
||||||
|
makerFee
|
||||||
|
infrastructureFee
|
||||||
|
liquidityFee
|
||||||
|
}
|
||||||
|
sellerFee {
|
||||||
|
makerFee
|
||||||
|
infrastructureFee
|
||||||
|
liquidityFee
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${FillUpdateFieldsFragmentDoc}`;
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useFillsEventSubscription__
|
* __useFillsEventSubscription__
|
||||||
|
|||||||
@@ -1,34 +1,74 @@
|
|||||||
|
import produce from 'immer';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import type { PageInfo, Cursor } from '@vegaprotocol/data-provider';
|
import {} from '@vegaprotocol/utils';
|
||||||
|
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||||
import {
|
import {
|
||||||
makeDataProvider,
|
makeDataProvider,
|
||||||
makeDerivedDataProvider,
|
makeDerivedDataProvider,
|
||||||
defaultAppend as append,
|
defaultAppend as append,
|
||||||
|
paginatedCombineDelta as combineDelta,
|
||||||
|
paginatedCombineInsertionData as combineInsertionData,
|
||||||
} from '@vegaprotocol/data-provider';
|
} from '@vegaprotocol/data-provider';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
import { marketsProvider } from '@vegaprotocol/markets';
|
||||||
import { FillsDocument, FillsEventDocument } from './__generated__/Fills';
|
import { FillsDocument, FillsEventDocument } from './__generated__/Fills';
|
||||||
import type {
|
import type {
|
||||||
FillsQuery,
|
FillsQuery,
|
||||||
FillsQueryVariables,
|
FillsQueryVariables,
|
||||||
FillFieldsFragment,
|
FillFieldsFragment,
|
||||||
|
FillEdgeFragment,
|
||||||
FillsEventSubscription,
|
FillsEventSubscription,
|
||||||
FillUpdateFieldsFragment,
|
|
||||||
FillsEventSubscriptionVariables,
|
|
||||||
} from './__generated__/Fills';
|
} from './__generated__/Fills';
|
||||||
|
|
||||||
|
const update = (
|
||||||
|
data: FillEdgeFragment[] | null,
|
||||||
|
delta: FillsEventSubscription['tradesStream']
|
||||||
|
) => {
|
||||||
|
return produce(data, (draft) => {
|
||||||
|
orderBy(delta, 'createdAt').forEach((node) => {
|
||||||
|
if (draft === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const index = draft.findIndex((edge) => edge?.node.id === node.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
if (draft[index]?.node) {
|
||||||
|
Object.assign(draft[index]?.node as FillFieldsFragment, node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const firstNode = draft[0]?.node;
|
||||||
|
if (
|
||||||
|
(firstNode && node.createdAt >= firstNode.createdAt) ||
|
||||||
|
!firstNode
|
||||||
|
) {
|
||||||
|
const { buyerId, sellerId, marketId, ...trade } = node;
|
||||||
|
draft.unshift({
|
||||||
|
node: {
|
||||||
|
...trade,
|
||||||
|
__typename: 'Trade',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: marketId,
|
||||||
|
},
|
||||||
|
buyer: { id: buyerId, __typename: 'Party' },
|
||||||
|
seller: { id: buyerId, __typename: 'Party' },
|
||||||
|
},
|
||||||
|
cursor: '',
|
||||||
|
__typename: 'TradeEdge',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export type Trade = Omit<FillFieldsFragment, 'market'> & {
|
export type Trade = Omit<FillFieldsFragment, 'market'> & {
|
||||||
market?: Market;
|
market?: Market;
|
||||||
isLastPlaceholder?: boolean;
|
isLastPlaceholder?: boolean;
|
||||||
};
|
};
|
||||||
|
export type TradeEdge = Edge<Trade>;
|
||||||
|
|
||||||
const getData = (
|
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
|
||||||
responseData: FillsQuery | null
|
responseData?.trades?.edges || [];
|
||||||
): (FillFieldsFragment & Cursor)[] =>
|
|
||||||
responseData?.trades?.edges.map<FillFieldsFragment & Cursor>((edge) => ({
|
|
||||||
...edge.node,
|
|
||||||
cursor: edge.cursor,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
|
const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
|
||||||
responseData?.trades?.pageInfo || null;
|
responseData?.trades?.pageInfo || null;
|
||||||
@@ -36,65 +76,16 @@ const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
|
|||||||
const getDelta = (subscriptionData: FillsEventSubscription) =>
|
const getDelta = (subscriptionData: FillsEventSubscription) =>
|
||||||
subscriptionData.tradesStream || [];
|
subscriptionData.tradesStream || [];
|
||||||
|
|
||||||
const mapFillUpdateToFill = (
|
|
||||||
fillUpdate: FillUpdateFieldsFragment
|
|
||||||
): FillFieldsFragment => {
|
|
||||||
const { buyerId, sellerId, marketId, ...fill } = fillUpdate;
|
|
||||||
return {
|
|
||||||
...fill,
|
|
||||||
__typename: 'Trade',
|
|
||||||
market: {
|
|
||||||
__typename: 'Market',
|
|
||||||
id: marketId,
|
|
||||||
},
|
|
||||||
buyer: { id: buyerId, __typename: 'Party' },
|
|
||||||
seller: { id: buyerId, __typename: 'Party' },
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapFillUpdateToFillWithMarket =
|
|
||||||
(markets: Record<string, Market>) =>
|
|
||||||
(fillUpdate: FillUpdateFieldsFragment): Trade => {
|
|
||||||
const { market, ...fill } = mapFillUpdateToFill(fillUpdate);
|
|
||||||
return {
|
|
||||||
...fill,
|
|
||||||
market: markets[market.id],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const update = <T extends Omit<FillFieldsFragment, 'market'> & Cursor>(
|
|
||||||
data: T[] | null,
|
|
||||||
delta: ReturnType<typeof getDelta>,
|
|
||||||
variables: FillsQueryVariables,
|
|
||||||
mapDeltaToData: (delta: FillUpdateFieldsFragment) => T
|
|
||||||
): T[] => {
|
|
||||||
const updatedData = data ? [...data] : ([] as T[]);
|
|
||||||
orderBy(delta, 'createdAt', 'desc').forEach((fillUpdate) => {
|
|
||||||
const index = data?.findIndex((fill) => fill.id === fillUpdate.id) ?? -1;
|
|
||||||
if (index !== -1) {
|
|
||||||
updatedData[index] = {
|
|
||||||
...updatedData[index],
|
|
||||||
...mapDeltaToData(fillUpdate),
|
|
||||||
};
|
|
||||||
} else if (!data?.length || fillUpdate.createdAt >= data[0].createdAt) {
|
|
||||||
updatedData.unshift(mapDeltaToData(fillUpdate));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return updatedData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fillsProvider = makeDataProvider<
|
export const fillsProvider = makeDataProvider<
|
||||||
Parameters<typeof getData>['0'],
|
Parameters<typeof getData>['0'],
|
||||||
ReturnType<typeof getData>,
|
ReturnType<typeof getData>,
|
||||||
Parameters<typeof getDelta>['0'],
|
Parameters<typeof getDelta>['0'],
|
||||||
ReturnType<typeof getDelta>,
|
ReturnType<typeof getDelta>,
|
||||||
FillsQueryVariables,
|
FillsQueryVariables
|
||||||
FillsEventSubscriptionVariables
|
|
||||||
>({
|
>({
|
||||||
query: FillsDocument,
|
query: FillsDocument,
|
||||||
subscriptionQuery: FillsEventDocument,
|
subscriptionQuery: FillsEventDocument,
|
||||||
update: (data, delta, reload, variables) =>
|
update,
|
||||||
update(data, delta, variables, mapFillUpdateToFill),
|
|
||||||
getData,
|
getData,
|
||||||
getDelta,
|
getDelta,
|
||||||
pagination: {
|
pagination: {
|
||||||
@@ -102,41 +93,30 @@ export const fillsProvider = makeDataProvider<
|
|||||||
append,
|
append,
|
||||||
first: 100,
|
first: 100,
|
||||||
},
|
},
|
||||||
getSubscriptionVariables: ({ filter }) => {
|
|
||||||
const variables: FillsEventSubscriptionVariables = { filter: {} };
|
|
||||||
if (filter) {
|
|
||||||
variables.filter = {
|
|
||||||
partyIds: filter.partyIds,
|
|
||||||
marketIds: filter.marketIds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return variables;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fillsWithMarketProvider = makeDerivedDataProvider<
|
export const fillsWithMarketProvider = makeDerivedDataProvider<
|
||||||
|
(TradeEdge | null)[],
|
||||||
Trade[],
|
Trade[],
|
||||||
never,
|
|
||||||
FillsQueryVariables
|
FillsQueryVariables
|
||||||
>(
|
>(
|
||||||
[
|
[
|
||||||
fillsProvider,
|
fillsProvider,
|
||||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
(callback, client) => marketsProvider(callback, client, undefined),
|
||||||
],
|
],
|
||||||
(partsData, variables, prevData, parts): Trade[] | null => {
|
(partsData): (TradeEdge | null)[] =>
|
||||||
if (prevData && parts[0].isUpdate) {
|
(partsData[0] as ReturnType<typeof getData>)?.map(
|
||||||
return update(
|
(edge) =>
|
||||||
prevData,
|
edge && {
|
||||||
parts[0].delta as ReturnType<typeof getDelta>,
|
cursor: edge.cursor,
|
||||||
variables,
|
node: {
|
||||||
mapFillUpdateToFillWithMarket(partsData[1] as Record<string, Market>)
|
...edge.node,
|
||||||
);
|
market: (partsData[1] as Market[]).find(
|
||||||
}
|
(market) => market.id === edge.node.market.id
|
||||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
),
|
||||||
(trade) => ({
|
},
|
||||||
...trade,
|
}
|
||||||
market: (partsData[1] as Record<string, Market>)[trade.market.id],
|
) || null,
|
||||||
})
|
combineDelta<Trade, ReturnType<typeof getDelta>['0']>,
|
||||||
);
|
combineInsertionData<Trade>
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import compact from 'lodash/compact';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { FillsTable } from './fills-table';
|
import { FillsTable } from './fills-table';
|
||||||
|
import { useFillsList } from './use-fills-list';
|
||||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import type * as Schema from '@vegaprotocol/types';
|
|
||||||
import { fillsWithMarketProvider } from './fills-data-provider';
|
|
||||||
|
|
||||||
interface FillsManagerProps {
|
interface FillsManagerProps {
|
||||||
partyId: string;
|
partyId: string;
|
||||||
@@ -21,36 +20,31 @@ export const FillsManager = ({
|
|||||||
storeKey,
|
storeKey,
|
||||||
}: FillsManagerProps) => {
|
}: FillsManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
|
const scrolledToTop = useRef(true);
|
||||||
partyIds: [partyId],
|
const { data, error } = useFillsList({
|
||||||
};
|
partyId,
|
||||||
if (marketId) {
|
marketId,
|
||||||
filter.marketIds = [marketId];
|
gridRef,
|
||||||
}
|
scrolledToTop,
|
||||||
const { data, error } = useDataProvider({
|
|
||||||
dataProvider: fillsWithMarketProvider,
|
|
||||||
update: ({ data }) => {
|
|
||||||
if (data?.length && gridRef.current?.api) {
|
|
||||||
gridRef.current?.api.setRowData(data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
variables: { filter },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||||
gridRef,
|
gridRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fills = compact(data).map((e) => e.node);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FillsTable
|
<div className="h-full relative">
|
||||||
ref={gridRef}
|
<FillsTable
|
||||||
rowData={data}
|
ref={gridRef}
|
||||||
partyId={partyId}
|
rowData={fills}
|
||||||
onMarketClick={onMarketClick}
|
partyId={partyId}
|
||||||
storeKey={storeKey}
|
onMarketClick={onMarketClick}
|
||||||
{...bottomPlaceholderProps}
|
storeKey={storeKey}
|
||||||
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
{...bottomPlaceholderProps}
|
||||||
/>
|
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { useFillsList } from './use-fills-list';
|
||||||
|
import type { TradeEdge } from './fills-data-provider';
|
||||||
|
|
||||||
|
let mockData = null;
|
||||||
|
let mockDataProviderData = {
|
||||||
|
data: mockData as (TradeEdge | null)[] | null,
|
||||||
|
error: undefined,
|
||||||
|
loading: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let updateMock: jest.Mock;
|
||||||
|
const mockDataProvider = jest.fn((args) => {
|
||||||
|
updateMock = args.update;
|
||||||
|
return mockDataProviderData;
|
||||||
|
});
|
||||||
|
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||||
|
...jest.requireActual('@vegaprotocol/data-provider'),
|
||||||
|
useDataProvider: jest.fn((args) => mockDataProvider(args)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useFillsList Hook', () => {
|
||||||
|
const mockRefreshAgGridApi = jest.fn();
|
||||||
|
const partyId = 'partyId';
|
||||||
|
const gridRef = {
|
||||||
|
current: {
|
||||||
|
api: {
|
||||||
|
refreshInfiniteCache: mockRefreshAgGridApi,
|
||||||
|
getModel: () => ({ getType: () => 'infinite' }),
|
||||||
|
},
|
||||||
|
} as unknown as AgGridReact,
|
||||||
|
};
|
||||||
|
const scrolledToTop = {
|
||||||
|
current: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return proper dataProvider results', () => {
|
||||||
|
const { result } = renderHook(
|
||||||
|
() => useFillsList({ partyId, gridRef, scrolledToTop }),
|
||||||
|
{
|
||||||
|
wrapper: MockedProvider,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result.current).toMatchObject({
|
||||||
|
data: null,
|
||||||
|
error: undefined,
|
||||||
|
loading: true,
|
||||||
|
addNewRows: expect.any(Function),
|
||||||
|
getRows: expect.any(Function),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return proper mocked results', () => {
|
||||||
|
mockData = [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'data_id_1',
|
||||||
|
},
|
||||||
|
} as unknown as TradeEdge,
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'data_id_2',
|
||||||
|
},
|
||||||
|
} as unknown as TradeEdge,
|
||||||
|
];
|
||||||
|
mockDataProviderData = {
|
||||||
|
...mockDataProviderData,
|
||||||
|
data: mockData,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
const { result } = renderHook(
|
||||||
|
() => useFillsList({ partyId, gridRef, scrolledToTop }),
|
||||||
|
{
|
||||||
|
wrapper: MockedProvider,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result.current).toMatchObject({
|
||||||
|
data: mockData,
|
||||||
|
error: undefined,
|
||||||
|
loading: false,
|
||||||
|
addNewRows: expect.any(Function),
|
||||||
|
getRows: expect.any(Function),
|
||||||
|
});
|
||||||
|
updateMock({ data: mockData });
|
||||||
|
expect(mockRefreshAgGridApi).not.toHaveBeenCalled();
|
||||||
|
updateMock({ data: mockData });
|
||||||
|
expect(mockRefreshAgGridApi).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import type { RefObject } from 'react';
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { makeInfiniteScrollGetRows } from '@vegaprotocol/data-provider';
|
||||||
|
import type * as Types from '@vegaprotocol/types';
|
||||||
|
import { updateGridData } from '@vegaprotocol/datagrid';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
import type { Trade, TradeEdge } from './fills-data-provider';
|
||||||
|
import { fillsWithMarketProvider } from './fills-data-provider';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
partyId: string;
|
||||||
|
marketId?: string;
|
||||||
|
gridRef: RefObject<AgGridReact>;
|
||||||
|
scrolledToTop: RefObject<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFillsList = ({
|
||||||
|
partyId,
|
||||||
|
marketId,
|
||||||
|
gridRef,
|
||||||
|
scrolledToTop,
|
||||||
|
}: Props) => {
|
||||||
|
const dataRef = useRef<(TradeEdge | null)[] | null>(null);
|
||||||
|
const totalCountRef = useRef<number | undefined>(undefined);
|
||||||
|
const newRows = useRef(0);
|
||||||
|
const placeholderAdded = useRef(-1);
|
||||||
|
|
||||||
|
const makeBottomPlaceholders = useCallback((trade?: Trade) => {
|
||||||
|
if (!trade) {
|
||||||
|
if (placeholderAdded.current >= 0) {
|
||||||
|
dataRef.current?.splice(placeholderAdded.current, 1);
|
||||||
|
}
|
||||||
|
placeholderAdded.current = -1;
|
||||||
|
} else if (placeholderAdded.current === -1) {
|
||||||
|
dataRef.current?.push({
|
||||||
|
node: { ...trade, id: `${trade?.id}-1`, isLastPlaceholder: true },
|
||||||
|
});
|
||||||
|
placeholderAdded.current = (dataRef.current?.length || 0) - 1;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const addNewRows = useCallback(() => {
|
||||||
|
if (newRows.current === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (totalCountRef.current !== undefined) {
|
||||||
|
totalCountRef.current += newRows.current;
|
||||||
|
}
|
||||||
|
newRows.current = 0;
|
||||||
|
gridRef.current?.api?.refreshInfiniteCache();
|
||||||
|
}, [gridRef]);
|
||||||
|
|
||||||
|
const update = useCallback(
|
||||||
|
({
|
||||||
|
data,
|
||||||
|
delta,
|
||||||
|
}: {
|
||||||
|
data: (TradeEdge | null)[] | null;
|
||||||
|
delta?: Trade[];
|
||||||
|
}) => {
|
||||||
|
if (dataRef.current?.length) {
|
||||||
|
if (!scrolledToTop.current) {
|
||||||
|
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||||
|
if (createdAt) {
|
||||||
|
newRows.current += (delta || []).filter(
|
||||||
|
(trade) => trade.createdAt > createdAt
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updateGridData(dataRef, data, gridRef);
|
||||||
|
}
|
||||||
|
dataRef.current = data;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[gridRef, scrolledToTop]
|
||||||
|
);
|
||||||
|
|
||||||
|
const insert = useCallback(
|
||||||
|
({
|
||||||
|
data,
|
||||||
|
totalCount,
|
||||||
|
}: {
|
||||||
|
data: (TradeEdge | null)[] | null;
|
||||||
|
totalCount?: number;
|
||||||
|
}) => {
|
||||||
|
totalCountRef.current = totalCount;
|
||||||
|
return updateGridData(dataRef, data, gridRef);
|
||||||
|
},
|
||||||
|
[gridRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const filter: Types.TradesFilter & Types.TradesSubscriptionFilter = {
|
||||||
|
partyIds: [partyId],
|
||||||
|
};
|
||||||
|
if (marketId) {
|
||||||
|
filter.marketIds = [marketId];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error, loading, load, totalCount, reload } = useDataProvider({
|
||||||
|
dataProvider: fillsWithMarketProvider,
|
||||||
|
update,
|
||||||
|
insert,
|
||||||
|
variables: { filter },
|
||||||
|
});
|
||||||
|
totalCountRef.current = totalCount;
|
||||||
|
|
||||||
|
const getRows = makeInfiniteScrollGetRows<TradeEdge>(
|
||||||
|
dataRef,
|
||||||
|
totalCountRef,
|
||||||
|
load,
|
||||||
|
newRows
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
addNewRows,
|
||||||
|
getRows,
|
||||||
|
reload,
|
||||||
|
makeBottomPlaceholders,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
import type { Asset } from '@vegaprotocol/assets';
|
import type { Asset } from '@vegaprotocol/assets';
|
||||||
import { assetsMapProvider } from '@vegaprotocol/assets';
|
import { assetsProvider } from '@vegaprotocol/assets';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
import { marketsProvider } from '@vegaprotocol/markets';
|
||||||
|
import { makeInfiniteScrollGetRows } from '@vegaprotocol/data-provider';
|
||||||
|
import { updateGridData } from '@vegaprotocol/datagrid';
|
||||||
import {
|
import {
|
||||||
makeDataProvider,
|
makeDataProvider,
|
||||||
makeDerivedDataProvider,
|
makeDerivedDataProvider,
|
||||||
|
useDataProvider,
|
||||||
} from '@vegaprotocol/data-provider';
|
} from '@vegaprotocol/data-provider';
|
||||||
|
import type * as Schema from '@vegaprotocol/types';
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import produce from 'immer';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import uniqBy from 'lodash/uniqBy';
|
||||||
|
import type { RefObject } from 'react';
|
||||||
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
|
import type { Filter } from './ledger-manager';
|
||||||
import type {
|
import type {
|
||||||
LedgerEntriesQuery,
|
LedgerEntriesQuery,
|
||||||
LedgerEntriesQueryVariables,
|
LedgerEntriesQueryVariables,
|
||||||
@@ -20,58 +30,171 @@ export type LedgerEntry = LedgerEntryFragment & {
|
|||||||
marketReceiver: Market | null | undefined;
|
marketReceiver: Market | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Edge = LedgerEntriesQuery['ledgerEntries']['edges'][number];
|
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
|
||||||
|
export type AggregatedLedgerEntriesNode = Omit<
|
||||||
const isLedgerEntryEdge = (entry: Edge): entry is NonNullable<Edge> =>
|
AggregatedLedgerEntriesEdge,
|
||||||
entry !== null;
|
'node'
|
||||||
|
> & {
|
||||||
const getData = (responseData: LedgerEntriesQuery | null) => {
|
node: LedgerEntry;
|
||||||
return (
|
|
||||||
responseData?.ledgerEntries?.edges
|
|
||||||
.filter(isLedgerEntryEdge)
|
|
||||||
.map((edge) => edge.node) || []
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ledgerEntriesOnlyProvider = makeDataProvider<
|
const getData = (responseData: LedgerEntriesQuery | null) => {
|
||||||
LedgerEntriesQuery,
|
return responseData?.ledgerEntries?.edges || [];
|
||||||
ReturnType<typeof getData>,
|
};
|
||||||
never,
|
|
||||||
never,
|
export const update = (
|
||||||
LedgerEntriesQueryVariables
|
data: ReturnType<typeof getData> | null,
|
||||||
>({
|
delta: ReturnType<typeof getData>,
|
||||||
|
reload: () => void,
|
||||||
|
variables: LedgerEntriesQueryVariables
|
||||||
|
) => {
|
||||||
|
if (!data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return produce(data, (draft) => {
|
||||||
|
// A single update can contain the same order with multiple updates, so we need to find
|
||||||
|
// the latest version of the order and only update using that
|
||||||
|
const incoming = uniqBy(
|
||||||
|
orderBy(delta, (entry) => entry?.node.vegaTime, 'desc'),
|
||||||
|
'id'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add or update incoming orders
|
||||||
|
incoming.reverse().forEach((node) => {
|
||||||
|
const index = draft.findIndex(
|
||||||
|
(edge) => edge?.node.vegaTime === node?.node.vegaTime
|
||||||
|
);
|
||||||
|
const newer =
|
||||||
|
draft.length === 0 || node?.node.vegaTime >= draft[0]?.node.vegaTime;
|
||||||
|
let doesFilterPass = true;
|
||||||
|
if (
|
||||||
|
doesFilterPass &&
|
||||||
|
variables?.dateRange?.start &&
|
||||||
|
new Date(node?.node.vegaTime) <= new Date(variables?.dateRange?.start)
|
||||||
|
) {
|
||||||
|
doesFilterPass = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
doesFilterPass &&
|
||||||
|
variables?.dateRange?.end &&
|
||||||
|
new Date(node?.node.vegaTime) >= new Date(variables?.dateRange?.end)
|
||||||
|
) {
|
||||||
|
doesFilterPass = false;
|
||||||
|
}
|
||||||
|
if (index !== -1) {
|
||||||
|
if (doesFilterPass) {
|
||||||
|
// Object.assign(draft[index]?.node, node?.node);
|
||||||
|
if (newer) {
|
||||||
|
draft.unshift(...draft.splice(index, 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
draft.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else if (newer && doesFilterPass) {
|
||||||
|
draft.unshift(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ledgerEntriesOnlyProvider = makeDataProvider({
|
||||||
query: LedgerEntriesDocument,
|
query: LedgerEntriesDocument,
|
||||||
getData,
|
getData,
|
||||||
|
getDelta: getData,
|
||||||
|
update,
|
||||||
additionalContext: {
|
additionalContext: {
|
||||||
isEnlargedTimeout: true,
|
isEnlargedTimeout: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ledgerEntriesProvider = makeDerivedDataProvider<
|
export const ledgerEntriesProvider = makeDerivedDataProvider<
|
||||||
LedgerEntry[],
|
AggregatedLedgerEntriesNode[],
|
||||||
never,
|
AggregatedLedgerEntriesNode[],
|
||||||
LedgerEntriesQueryVariables
|
LedgerEntriesQueryVariables
|
||||||
>(
|
>(
|
||||||
[
|
[
|
||||||
ledgerEntriesOnlyProvider,
|
ledgerEntriesOnlyProvider,
|
||||||
(callback, client) => assetsMapProvider(callback, client, undefined),
|
(callback, client) => assetsProvider(callback, client, undefined),
|
||||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
(callback, client) => marketsProvider(callback, client, undefined),
|
||||||
],
|
],
|
||||||
(partsData) => {
|
([entries, assets, markets]) => {
|
||||||
const entries = partsData[0] as ReturnType<typeof getData>;
|
return entries.map((edge: AggregatedLedgerEntriesEdge) => {
|
||||||
const assets = partsData[1] as Record<string, Asset>;
|
const entry = edge.node;
|
||||||
const markets = partsData[1] as Record<string, Market>;
|
const asset = assets.find((asset: Asset) => asset.id === entry.assetId);
|
||||||
return entries.map((entry) => {
|
const marketSender = markets.find(
|
||||||
const asset = entry.assetId
|
(market: Market) => market.id === entry.fromAccountMarketId
|
||||||
? (assets as Record<string, Asset>)[entry.assetId]
|
);
|
||||||
: null;
|
const marketReceiver = markets.find(
|
||||||
const marketSender = entry.fromAccountMarketId
|
(market: Market) => market.id === entry.toAccountMarketId
|
||||||
? markets[entry.fromAccountMarketId]
|
);
|
||||||
: null;
|
const cursor = edge?.cursor;
|
||||||
const marketReceiver = entry.toAccountMarketId
|
return {
|
||||||
? markets[entry.toAccountMarketId]
|
node: { ...entry, asset, marketSender, marketReceiver },
|
||||||
: null;
|
cursor,
|
||||||
return { ...entry, asset, marketSender, marketReceiver };
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
partyId: string;
|
||||||
|
filter?: Filter;
|
||||||
|
gridRef: RefObject<AgGridReact>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLedgerEntriesDataProvider = ({
|
||||||
|
partyId,
|
||||||
|
filter,
|
||||||
|
gridRef,
|
||||||
|
}: Props) => {
|
||||||
|
const dataRef = useRef<AggregatedLedgerEntriesEdge[] | null>(null);
|
||||||
|
const totalCountRef = useRef<number>();
|
||||||
|
|
||||||
|
const variables = useMemo<LedgerEntriesQueryVariables>(
|
||||||
|
() => ({
|
||||||
|
partyId,
|
||||||
|
dateRange: filter?.vegaTime?.value,
|
||||||
|
pagination: {
|
||||||
|
first: 5000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[partyId, filter?.vegaTime?.value]
|
||||||
|
);
|
||||||
|
|
||||||
|
const update = useCallback(
|
||||||
|
({ data }: { data: AggregatedLedgerEntriesEdge[] | null }) => {
|
||||||
|
return updateGridData(dataRef, data, gridRef);
|
||||||
|
},
|
||||||
|
[gridRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const insert = useCallback(
|
||||||
|
({
|
||||||
|
data,
|
||||||
|
totalCount,
|
||||||
|
}: {
|
||||||
|
data: AggregatedLedgerEntriesEdge[] | null;
|
||||||
|
totalCount?: number;
|
||||||
|
}) => {
|
||||||
|
totalCountRef.current = totalCount;
|
||||||
|
return updateGridData(dataRef, data, gridRef);
|
||||||
|
},
|
||||||
|
[gridRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, error, loading, load, totalCount, reload } = useDataProvider({
|
||||||
|
dataProvider: ledgerEntriesProvider,
|
||||||
|
update,
|
||||||
|
insert,
|
||||||
|
variables,
|
||||||
|
skip: !variables.partyId,
|
||||||
|
});
|
||||||
|
totalCountRef.current = totalCount;
|
||||||
|
|
||||||
|
const getRows = makeInfiniteScrollGetRows<AggregatedLedgerEntriesEdge>(
|
||||||
|
dataRef,
|
||||||
|
totalCountRef,
|
||||||
|
load
|
||||||
|
);
|
||||||
|
return { loading, error, data, getRows, reload };
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ import { t } from '@vegaprotocol/i18n';
|
|||||||
import type * as Schema from '@vegaprotocol/types';
|
import type * as Schema from '@vegaprotocol/types';
|
||||||
import type { FilterChangedEvent } from 'ag-grid-community';
|
import type { FilterChangedEvent } from 'ag-grid-community';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { useCallback, useRef, useState, useMemo } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { subDays, formatRFC3339 } from 'date-fns';
|
import { subDays, formatRFC3339 } from 'date-fns';
|
||||||
import { ledgerEntriesProvider } from './ledger-entries-data-provider';
|
import { useLedgerEntriesDataProvider } from './ledger-entries-data-provider';
|
||||||
import type { LedgerEntriesQueryVariables } from './__generated__/LedgerEntries';
|
|
||||||
import { LedgerTable } from './ledger-table';
|
import { LedgerTable } from './ledger-table';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import type * as Types from '@vegaprotocol/types';
|
import type * as Types from '@vegaprotocol/types';
|
||||||
import { LedgerExportLink } from './ledger-export-link';
|
import { LedgerExportLink } from './ledger-export-link';
|
||||||
|
|
||||||
@@ -28,21 +26,10 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
|||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
||||||
|
|
||||||
const variables = useMemo<LedgerEntriesQueryVariables>(
|
const { data, error } = useLedgerEntriesDataProvider({
|
||||||
() => ({
|
partyId,
|
||||||
partyId,
|
filter,
|
||||||
dateRange: filter?.vegaTime?.value,
|
gridRef,
|
||||||
pagination: {
|
|
||||||
first: 5000,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[partyId, filter?.vegaTime?.value]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, error } = useDataProvider({
|
|
||||||
dataProvider: ledgerEntriesProvider,
|
|
||||||
variables,
|
|
||||||
skip: !variables.partyId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
|
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
|
||||||
@@ -50,15 +37,20 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
|||||||
setFilter(updatedFilter);
|
setFilter(updatedFilter);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// allow passing undefined to grid so that loading state is shown
|
||||||
|
const extractedData = data?.map((item) => item.node);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<LedgerTable
|
<LedgerTable
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
rowData={data}
|
rowData={extractedData}
|
||||||
onFilterChanged={onFilterChanged}
|
onFilterChanged={onFilterChanged}
|
||||||
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
||||||
/>
|
/>
|
||||||
{data && <LedgerExportLink entries={data} partyId={partyId} />}
|
{extractedData && (
|
||||||
|
<LedgerExportLink entries={extractedData} partyId={partyId} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
function ReactMarkdown({ children }) {
|
||||||
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReactMarkdown;
|
||||||
@@ -1,14 +1,29 @@
|
|||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import type {
|
||||||
|
VegaValueGetterParams,
|
||||||
|
VegaValueFormatterParams,
|
||||||
|
VegaICellRendererParams,
|
||||||
|
TypedDataAgGrid,
|
||||||
|
} from '@vegaprotocol/datagrid';
|
||||||
|
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
||||||
import {
|
import {
|
||||||
AgGridLazy as AgGrid,
|
AgGridLazy as AgGrid,
|
||||||
PriceFlashCell,
|
PriceFlashCell,
|
||||||
MarketNameCell,
|
MarketNameCell,
|
||||||
|
SetFilter,
|
||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
|
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||||
|
import { MarketTableActions } from './market-table-actions';
|
||||||
import { OracleStatus } from './oracle-status';
|
import { OracleStatus } from './oracle-status';
|
||||||
import { useColumnDefs } from './use-column-defs';
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
|
|
||||||
|
const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||||
|
|
||||||
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
||||||
|
|
||||||
@@ -36,34 +51,199 @@ const MarketName = (props: MarketNameCellProps) => (
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultColDef = {
|
|
||||||
resizable: true,
|
|
||||||
sortable: true,
|
|
||||||
filter: true,
|
|
||||||
filterParams: { buttons: ['reset'] },
|
|
||||||
minWidth: 100,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MarketListTable = forwardRef<
|
export const MarketListTable = forwardRef<
|
||||||
AgGridReact,
|
AgGridReact,
|
||||||
TypedDataAgGrid<MarketMaybeWithData> & {
|
TypedDataAgGrid<MarketMaybeWithData> & {
|
||||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||||
}
|
}
|
||||||
>(({ onMarketClick, ...props }, ref) => {
|
>(({ onMarketClick, ...props }, ref) => {
|
||||||
const columnDefs = useColumnDefs({ onMarketClick });
|
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
getRowId={getRowId}
|
getRowId={getRowId}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
defaultColDef={defaultColDef}
|
defaultColDef={{
|
||||||
columnDefs={columnDefs}
|
resizable: true,
|
||||||
|
sortable: true,
|
||||||
|
filter: true,
|
||||||
|
filterParams: { buttons: ['reset'] },
|
||||||
|
minWidth: 100,
|
||||||
|
}}
|
||||||
suppressCellFocus
|
suppressCellFocus
|
||||||
components={{ PriceFlashCell, MarketName }}
|
components={{ PriceFlashCell, MarketName }}
|
||||||
storeKey="allMarkets"
|
storeKey="allMarkets"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Market')}
|
||||||
|
field="tradableInstrument.instrument.code"
|
||||||
|
cellRenderer="MarketName"
|
||||||
|
cellRendererParams={{ onMarketClick }}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Description')}
|
||||||
|
field="tradableInstrument.instrument.name"
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Trading mode')}
|
||||||
|
field="tradingMode"
|
||||||
|
minWidth={170}
|
||||||
|
valueFormatter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
||||||
|
if (!data?.data) return undefined;
|
||||||
|
const { trigger, marketTradingMode } = data.data;
|
||||||
|
return marketTradingMode ===
|
||||||
|
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||||
|
trigger &&
|
||||||
|
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||||
|
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||||
|
- ${Schema.AuctionTriggerMapping[trigger]}`
|
||||||
|
: Schema.MarketTradingModeMapping[marketTradingMode];
|
||||||
|
}}
|
||||||
|
filter={SetFilter}
|
||||||
|
filterParams={{
|
||||||
|
set: Schema.MarketTradingModeMapping,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Status')}
|
||||||
|
field="state"
|
||||||
|
valueFormatter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
||||||
|
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
||||||
|
}}
|
||||||
|
filter={SetFilter}
|
||||||
|
filterParams={{
|
||||||
|
set: Schema.MarketStateMapping,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Best bid')}
|
||||||
|
field="data.bestBidPrice"
|
||||||
|
type="rightAligned"
|
||||||
|
cellRenderer="PriceFlashCell"
|
||||||
|
filter="agNumberColumnFilter"
|
||||||
|
valueGetter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueGetterParams<MarketMaybeWithData, 'data.bestBidPrice'>) => {
|
||||||
|
return data?.data?.bestBidPrice === undefined
|
||||||
|
? undefined
|
||||||
|
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
|
||||||
|
}}
|
||||||
|
valueFormatter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<
|
||||||
|
MarketMaybeWithData,
|
||||||
|
'data.bestBidPrice'
|
||||||
|
>) =>
|
||||||
|
data?.data?.bestBidPrice === undefined
|
||||||
|
? undefined
|
||||||
|
: addDecimalsFormatNumber(
|
||||||
|
data.data.bestBidPrice,
|
||||||
|
data.decimalPlaces
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Best offer')}
|
||||||
|
field="data.bestOfferPrice"
|
||||||
|
type="rightAligned"
|
||||||
|
cellRenderer="PriceFlashCell"
|
||||||
|
filter="agNumberColumnFilter"
|
||||||
|
valueGetter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueGetterParams<
|
||||||
|
MarketMaybeWithData,
|
||||||
|
'data.bestOfferPrice'
|
||||||
|
>) => {
|
||||||
|
return data?.data?.bestOfferPrice === undefined
|
||||||
|
? undefined
|
||||||
|
: toBigNum(
|
||||||
|
data?.data?.bestOfferPrice,
|
||||||
|
data.decimalPlaces
|
||||||
|
).toNumber();
|
||||||
|
}}
|
||||||
|
valueFormatter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<
|
||||||
|
MarketMaybeWithData,
|
||||||
|
'data.bestOfferPrice'
|
||||||
|
>) =>
|
||||||
|
data?.data?.bestOfferPrice === undefined
|
||||||
|
? undefined
|
||||||
|
: addDecimalsFormatNumber(
|
||||||
|
data.data.bestOfferPrice,
|
||||||
|
data.decimalPlaces
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Mark price')}
|
||||||
|
field="data.markPrice"
|
||||||
|
type="rightAligned"
|
||||||
|
cellRenderer="PriceFlashCell"
|
||||||
|
filter="agNumberColumnFilter"
|
||||||
|
valueGetter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueGetterParams<MarketMaybeWithData, 'data.markPrice'>) => {
|
||||||
|
return data?.data?.markPrice === undefined
|
||||||
|
? undefined
|
||||||
|
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||||
|
}}
|
||||||
|
valueFormatter={({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||||
|
data?.data?.bestOfferPrice === undefined
|
||||||
|
? undefined
|
||||||
|
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Settlement asset')}
|
||||||
|
field="tradableInstrument.instrument.product.settlementAsset.symbol"
|
||||||
|
cellRenderer={({
|
||||||
|
data,
|
||||||
|
}: VegaICellRendererParams<
|
||||||
|
MarketMaybeWithData,
|
||||||
|
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||||
|
>) => {
|
||||||
|
const value =
|
||||||
|
data?.tradableInstrument.instrument.product.settlementAsset;
|
||||||
|
return value ? (
|
||||||
|
<ButtonLink
|
||||||
|
onClick={(e) => {
|
||||||
|
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value.symbol}
|
||||||
|
</ButtonLink>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
colId="market-actions"
|
||||||
|
field="id"
|
||||||
|
{...COL_DEFS.actions}
|
||||||
|
cellRenderer={({
|
||||||
|
data,
|
||||||
|
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||||
|
if (!data) return null;
|
||||||
|
return (
|
||||||
|
<MarketTableActions
|
||||||
|
marketId={data.id}
|
||||||
|
assetId={
|
||||||
|
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AgGrid>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
import { render, screen, act, within } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import * as DataProviders from '@vegaprotocol/data-provider';
|
|
||||||
import { MockedProvider } from '@apollo/react-testing';
|
|
||||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
|
||||||
import { MarketsContainer } from './markets-container';
|
|
||||||
|
|
||||||
const market = {
|
|
||||||
id: 'id-1',
|
|
||||||
tradableInstrument: {
|
|
||||||
instrument: {
|
|
||||||
product: { settlementAsset: { id: 'assetId-1' } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
decimalPlaces: 1,
|
|
||||||
positionDecimalPlaces: 1,
|
|
||||||
state: 'STATE_ACTIVE',
|
|
||||||
tradingMode: 'TRADING_MODE_OPENING_AUCTION',
|
|
||||||
data: {
|
|
||||||
bestBidPrice: 100,
|
|
||||||
},
|
|
||||||
} as unknown as MarketMaybeWithData;
|
|
||||||
|
|
||||||
describe('MarketsContainer', () => {
|
|
||||||
it('context menu should stay open', async () => {
|
|
||||||
const spyOnSelect = jest.fn();
|
|
||||||
jest
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
|
||||||
.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
error: null,
|
|
||||||
reload: jest.fn(),
|
|
||||||
data: [market],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let rerenderRef: (ui: React.ReactElement) => void;
|
|
||||||
await act(async () => {
|
|
||||||
const { rerender } = render(
|
|
||||||
<MockedProvider>
|
|
||||||
<MarketsContainer onSelect={spyOnSelect} />
|
|
||||||
</MockedProvider>
|
|
||||||
);
|
|
||||||
rerenderRef = rerender;
|
|
||||||
});
|
|
||||||
|
|
||||||
// make sure ag grid is finished initializaing
|
|
||||||
const rowContainer = await screen.findByRole('rowgroup', {
|
|
||||||
name: (_name, element) =>
|
|
||||||
element.classList.contains('ag-center-cols-container'),
|
|
||||||
});
|
|
||||||
expect(within(rowContainer).getAllByRole('row')).toHaveLength(1);
|
|
||||||
expect(
|
|
||||||
screen.getByRole('rowgroup', {
|
|
||||||
name: (_name, element) =>
|
|
||||||
element.classList.contains('ag-pinned-right-cols-container'),
|
|
||||||
})
|
|
||||||
).toBeInTheDocument();
|
|
||||||
|
|
||||||
// open the dropdown
|
|
||||||
await userEvent.click(
|
|
||||||
screen.getByRole('button', {
|
|
||||||
name: (_name, element) =>
|
|
||||||
(element.parentNode as Element)?.getAttribute('id') ===
|
|
||||||
'cell-market-actions-8',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await checkDropdown();
|
|
||||||
|
|
||||||
// reset the mock and rerender so the component
|
|
||||||
// updates with new data
|
|
||||||
jest
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
|
||||||
.mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
error: null,
|
|
||||||
reload: jest.fn(),
|
|
||||||
data: [{ ...market, state: 'STATE_PENDING' }],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore we await the act above so rerenderRef is definitely defined
|
|
||||||
rerenderRef(
|
|
||||||
<MockedProvider mocks={[]}>
|
|
||||||
<MarketsContainer onSelect={spyOnSelect} />
|
|
||||||
</MockedProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
// make sure dropdown is still open
|
|
||||||
await checkDropdown();
|
|
||||||
|
|
||||||
async function checkDropdown() {
|
|
||||||
const dropdownContent = await screen.findByTestId(
|
|
||||||
'market-actions-content'
|
|
||||||
);
|
|
||||||
expect(dropdownContent).toBeInTheDocument();
|
|
||||||
expect(
|
|
||||||
within(dropdownContent).getByRole('menuitem', {
|
|
||||||
name: 'Copy Market ID',
|
|
||||||
})
|
|
||||||
).toBeInTheDocument();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import type { ColDef } from 'ag-grid-community';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import type {
|
|
||||||
VegaICellRendererParams,
|
|
||||||
VegaValueFormatterParams,
|
|
||||||
VegaValueGetterParams,
|
|
||||||
} from '@vegaprotocol/datagrid';
|
|
||||||
import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
|
||||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
|
||||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
|
||||||
import { MarketTableActions } from './market-table-actions';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { MarketTradingMode, AuctionTrigger } = Schema;
|
|
||||||
|
|
||||||
export const useColumnDefs = ({ onMarketClick }: Props) => {
|
|
||||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
|
||||||
|
|
||||||
return useMemo<ColDef[]>(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
headerName: t('Market'),
|
|
||||||
field: 'tradableInstrument.instrument.code',
|
|
||||||
cellRenderer: 'MarketName',
|
|
||||||
cellRendererParams: { onMarketClick },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Description'),
|
|
||||||
field: 'tradableInstrument.instrument.name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Trading mode'),
|
|
||||||
field: 'tradingMode',
|
|
||||||
minWidth: 170,
|
|
||||||
valueFormatter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
|
||||||
if (!data?.data) return '-';
|
|
||||||
const { trigger, marketTradingMode } = data.data;
|
|
||||||
return marketTradingMode ===
|
|
||||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
|
||||||
trigger &&
|
|
||||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
|
||||||
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
|
||||||
- ${Schema.AuctionTriggerMapping[trigger]}`
|
|
||||||
: Schema.MarketTradingModeMapping[marketTradingMode];
|
|
||||||
},
|
|
||||||
filter: SetFilter,
|
|
||||||
filterParams: {
|
|
||||||
set: Schema.MarketTradingModeMapping,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Status'),
|
|
||||||
field: 'state',
|
|
||||||
valueFormatter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
|
||||||
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
|
||||||
},
|
|
||||||
filter: SetFilter,
|
|
||||||
filterParams: {
|
|
||||||
set: Schema.MarketStateMapping,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Best bid'),
|
|
||||||
field: 'data.bestBidPrice',
|
|
||||||
type: 'rightAligned',
|
|
||||||
cellRenderer: 'PriceFlashCell',
|
|
||||||
filter: 'agNumberColumnFilter',
|
|
||||||
valueGetter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.bestBidPrice'>) => {
|
|
||||||
return data?.data?.bestBidPrice === undefined
|
|
||||||
? undefined
|
|
||||||
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
|
|
||||||
},
|
|
||||||
valueFormatter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<
|
|
||||||
MarketMaybeWithData,
|
|
||||||
'data.bestBidPrice'
|
|
||||||
>) =>
|
|
||||||
data?.data?.bestBidPrice === undefined
|
|
||||||
? '-'
|
|
||||||
: addDecimalsFormatNumber(
|
|
||||||
data.data.bestBidPrice,
|
|
||||||
data.decimalPlaces
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Best offer'),
|
|
||||||
field: 'data.bestOfferPrice',
|
|
||||||
type: 'rightAligned',
|
|
||||||
cellRenderer: 'PriceFlashCell',
|
|
||||||
filter: 'agNumberColumnFilter',
|
|
||||||
valueGetter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueGetterParams<
|
|
||||||
MarketMaybeWithData,
|
|
||||||
'data.bestOfferPrice'
|
|
||||||
>) => {
|
|
||||||
return data?.data?.bestOfferPrice === undefined
|
|
||||||
? undefined
|
|
||||||
: toBigNum(
|
|
||||||
data?.data?.bestOfferPrice,
|
|
||||||
data.decimalPlaces
|
|
||||||
).toNumber();
|
|
||||||
},
|
|
||||||
valueFormatter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<
|
|
||||||
MarketMaybeWithData,
|
|
||||||
'data.bestOfferPrice'
|
|
||||||
>) =>
|
|
||||||
data?.data?.bestOfferPrice === undefined
|
|
||||||
? '-'
|
|
||||||
: addDecimalsFormatNumber(
|
|
||||||
data.data.bestOfferPrice,
|
|
||||||
data.decimalPlaces
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Mark price'),
|
|
||||||
field: 'data.markPrice',
|
|
||||||
type: 'rightAligned',
|
|
||||||
cellRenderer: 'PriceFlashCell',
|
|
||||||
filter: 'agNumberColumnFilter',
|
|
||||||
valueGetter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.markPrice'>) => {
|
|
||||||
return data?.data?.markPrice === undefined
|
|
||||||
? undefined
|
|
||||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
|
||||||
},
|
|
||||||
valueFormatter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
|
||||||
data?.data?.bestOfferPrice === undefined
|
|
||||||
? '-'
|
|
||||||
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Settlement asset'),
|
|
||||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
|
||||||
cellRenderer: ({
|
|
||||||
data,
|
|
||||||
}: VegaICellRendererParams<
|
|
||||||
MarketMaybeWithData,
|
|
||||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
|
||||||
>) => {
|
|
||||||
const value =
|
|
||||||
data?.tradableInstrument.instrument.product.settlementAsset;
|
|
||||||
return value ? (
|
|
||||||
<ButtonLink
|
|
||||||
onClick={(e) => {
|
|
||||||
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{value.symbol}
|
|
||||||
</ButtonLink>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
colId: 'market-actions',
|
|
||||||
field: 'id',
|
|
||||||
...COL_DEFS.actions,
|
|
||||||
cellRenderer: ({
|
|
||||||
data,
|
|
||||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
|
||||||
if (!data) return null;
|
|
||||||
return (
|
|
||||||
<MarketTableActions
|
|
||||||
marketId={data.id}
|
|
||||||
assetId={
|
|
||||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[onMarketClick, openAssetDetailsDialog]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
import {
|
import { update } from './order-data-provider';
|
||||||
update,
|
|
||||||
mapOrderUpdateToOrder,
|
|
||||||
filterOrderUpdates,
|
|
||||||
} from './order-data-provider';
|
|
||||||
import type { OrderUpdateFieldsFragment, OrderFieldsFragment } from '../';
|
import type { OrderUpdateFieldsFragment, OrderFieldsFragment } from '../';
|
||||||
|
import type { Edge } from '@vegaprotocol/data-provider';
|
||||||
|
|
||||||
describe('order data provider', () => {
|
describe('order data provider', () => {
|
||||||
it('puts incoming data in proper place', () => {
|
it('puts incoming data in proper place', () => {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
id: '2',
|
node: {
|
||||||
createdAt: new Date('2022-01-29').toISOString(),
|
id: '2',
|
||||||
|
createdAt: new Date('2022-01-29').toISOString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: '1',
|
node: {
|
||||||
createdAt: new Date('2022-01-28').toISOString(),
|
id: '1',
|
||||||
|
createdAt: new Date('2022-01-28').toISOString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
] as OrderFieldsFragment[];
|
] as Edge<OrderFieldsFragment>[];
|
||||||
|
|
||||||
const delta = [
|
const delta = [
|
||||||
// this one should be dropped because id don't exits and it's older than newest
|
// this one should be dropped because id don't exits and it's older than newest
|
||||||
@@ -52,41 +52,39 @@ describe('order data provider', () => {
|
|||||||
createdAt: new Date('2022-02-05').toISOString(),
|
createdAt: new Date('2022-02-05').toISOString(),
|
||||||
},
|
},
|
||||||
] as OrderUpdateFieldsFragment[];
|
] as OrderUpdateFieldsFragment[];
|
||||||
const updatedData = update(
|
const updatedData = update(data, delta, () => null, { partyId: '0x123' });
|
||||||
data,
|
|
||||||
filterOrderUpdates(delta),
|
|
||||||
{ partyId: '0x123' },
|
|
||||||
mapOrderUpdateToOrder
|
|
||||||
);
|
|
||||||
expect(updatedData?.findIndex((node) => node.id === delta[0].id)).toEqual(
|
|
||||||
-1
|
|
||||||
);
|
|
||||||
expect(updatedData && updatedData[3].id).toEqual(delta[2].id);
|
|
||||||
expect(updatedData && updatedData[3].updatedAt).toEqual(delta[2].updatedAt);
|
|
||||||
expect(updatedData && updatedData[0].id).toEqual(delta[5].id);
|
|
||||||
expect(updatedData && updatedData[1].id).toEqual(delta[3].id);
|
|
||||||
expect(updatedData && updatedData[2].id).toEqual(delta[4].id);
|
|
||||||
expect(updatedData && updatedData[2].updatedAt).toEqual(delta[4].updatedAt);
|
|
||||||
expect(
|
expect(
|
||||||
update(
|
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||||
[],
|
).toEqual(-1);
|
||||||
filterOrderUpdates(delta),
|
expect(updatedData && updatedData[3].node.id).toEqual(delta[2].id);
|
||||||
{ partyId: '0x123' },
|
expect(updatedData && updatedData[3].node.updatedAt).toEqual(
|
||||||
mapOrderUpdateToOrder
|
delta[2].updatedAt
|
||||||
)?.length
|
);
|
||||||
).toEqual(5);
|
expect(updatedData && updatedData[0].node.id).toEqual(delta[5].id);
|
||||||
|
expect(updatedData && updatedData[1].node.id).toEqual(delta[3].id);
|
||||||
|
expect(updatedData && updatedData[2].node.id).toEqual(delta[4].id);
|
||||||
|
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||||
|
delta[4].updatedAt
|
||||||
|
);
|
||||||
|
expect(update([], delta, () => null, { partyId: '0x123' })?.length).toEqual(
|
||||||
|
5
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('add only data matching date range filter', () => {
|
it('add only data matching date range filter', () => {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
id: '1',
|
node: {
|
||||||
createdAt: new Date('2022-01-29').toISOString(),
|
id: '1',
|
||||||
|
createdAt: new Date('2022-01-29').toISOString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
node: {
|
||||||
createdAt: new Date('2022-01-30').toISOString(),
|
id: '2',
|
||||||
|
createdAt: new Date('2022-01-30').toISOString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
] as OrderFieldsFragment[];
|
] as Edge<OrderFieldsFragment>[];
|
||||||
|
|
||||||
const delta = [
|
const delta = [
|
||||||
// this one should be ignored because it does not match date range
|
// this one should be ignored because it does not match date range
|
||||||
@@ -107,23 +105,22 @@ describe('order data provider', () => {
|
|||||||
},
|
},
|
||||||
] as OrderUpdateFieldsFragment[];
|
] as OrderUpdateFieldsFragment[];
|
||||||
|
|
||||||
const updatedData = update(
|
const updatedData = update(data, delta, () => null, {
|
||||||
data,
|
partyId: '0x123',
|
||||||
filterOrderUpdates(delta),
|
filter: {
|
||||||
{
|
dateRange: { end: new Date('2022-02-01').toISOString() },
|
||||||
partyId: '0x123',
|
|
||||||
filter: {
|
|
||||||
dateRange: { end: new Date('2022-02-01').toISOString() },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mapOrderUpdateToOrder
|
});
|
||||||
|
expect(
|
||||||
|
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||||
|
).toEqual(-1);
|
||||||
|
expect(updatedData && updatedData[0].node.id).toEqual(delta[2].id);
|
||||||
|
expect(updatedData && updatedData[0].node.updatedAt).toEqual(
|
||||||
|
delta[2].updatedAt
|
||||||
);
|
);
|
||||||
expect(updatedData?.findIndex((node) => node.id === delta[0].id)).toEqual(
|
expect(updatedData && updatedData[2].node.id).toEqual(delta[1].id);
|
||||||
-1
|
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||||
|
delta[1].updatedAt
|
||||||
);
|
);
|
||||||
expect(updatedData && updatedData[0].id).toEqual(delta[2].id);
|
|
||||||
expect(updatedData && updatedData[0].updatedAt).toEqual(delta[2].updatedAt);
|
|
||||||
expect(updatedData && updatedData[2].id).toEqual(delta[1].id);
|
|
||||||
expect(updatedData && updatedData[2].updatedAt).toEqual(delta[1].updatedAt);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
defaultAppend as append,
|
defaultAppend as append,
|
||||||
} from '@vegaprotocol/data-provider';
|
} from '@vegaprotocol/data-provider';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
import { marketsProvider } from '@vegaprotocol/markets';
|
||||||
import type { PageInfo, Edge, Cursor } from '@vegaprotocol/data-provider';
|
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||||
import { OrderStatus } from '@vegaprotocol/types';
|
import { OrderStatus } from '@vegaprotocol/types';
|
||||||
import type {
|
import type {
|
||||||
OrderFieldsFragment,
|
OrderFieldsFragment,
|
||||||
@@ -15,7 +15,6 @@ import type {
|
|||||||
OrdersQuery,
|
OrdersQuery,
|
||||||
OrdersUpdateSubscription,
|
OrdersUpdateSubscription,
|
||||||
OrdersQueryVariables,
|
OrdersQueryVariables,
|
||||||
OrdersUpdateSubscriptionVariables,
|
|
||||||
} from './__generated__/Orders';
|
} from './__generated__/Orders';
|
||||||
import { OrdersDocument, OrdersUpdateDocument } from './__generated__/Orders';
|
import { OrdersDocument, OrdersUpdateDocument } from './__generated__/Orders';
|
||||||
import type { ApolloClient } from '@apollo/client';
|
import type { ApolloClient } from '@apollo/client';
|
||||||
@@ -38,21 +37,18 @@ const orderMatchFilters = (
|
|||||||
if (!order) {
|
if (!order) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
variables?.filter?.status &&
|
variables?.filter?.status &&
|
||||||
!(order.status && variables.filter.status.includes(order.status))
|
!(order.status && variables.filter.status.includes(order.status))
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
variables?.filter?.liveOnly &&
|
variables?.filter?.liveOnly &&
|
||||||
!(order.status && liveOnlyOrderStatuses.includes(order.status))
|
!(order.status && liveOnlyOrderStatuses.includes(order.status))
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
variables?.filter?.types &&
|
variables?.filter?.types &&
|
||||||
!(order.type && variables.filter.types.includes(order.type))
|
!(order.type && variables.filter.types.includes(order.type))
|
||||||
@@ -80,11 +76,10 @@ const orderMatchFilters = (
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mapOrderUpdateToOrder = (
|
const mapOrderUpdateToOrder = (
|
||||||
orderUpdate: OrderUpdateFieldsFragment
|
orderUpdate: OrderUpdateFieldsFragment
|
||||||
): OrderFieldsFragment => {
|
): OrderFieldsFragment => {
|
||||||
const { marketId, liquidityProvisionId, ...order } = orderUpdate;
|
const { marketId, liquidityProvisionId, ...order } = orderUpdate;
|
||||||
@@ -106,36 +101,10 @@ export const mapOrderUpdateToOrder = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapOrderUpdateToOrderWithMarket =
|
|
||||||
(markets: Record<string, Market>) =>
|
|
||||||
(orderUpdate: OrderUpdateFieldsFragment): Order => {
|
|
||||||
const { market, ...order } = mapOrderUpdateToOrder(orderUpdate);
|
|
||||||
return {
|
|
||||||
...order,
|
|
||||||
market: markets[market.id],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getData = (
|
const getData = (
|
||||||
responseData: OrdersQuery | null
|
responseData: OrdersQuery | null
|
||||||
): (OrderFieldsFragment & Cursor)[] =>
|
): Edge<OrderFieldsFragment>[] =>
|
||||||
responseData?.party?.ordersConnection?.edges?.map<
|
responseData?.party?.ordersConnection?.edges || [];
|
||||||
OrderFieldsFragment & Cursor
|
|
||||||
>((edge) => ({ ...edge.node, cursor: edge.cursor })) || [];
|
|
||||||
|
|
||||||
export const filterOrderUpdates = (
|
|
||||||
orders: OrdersUpdateSubscription['orders']
|
|
||||||
) => {
|
|
||||||
// A single update can contain the same order with multiple updates, so we need to find
|
|
||||||
// the latest version of the order and only update using that
|
|
||||||
return orderBy(
|
|
||||||
uniqBy(
|
|
||||||
orderBy(orders, (order) => order.updatedAt || order.createdAt, 'desc'),
|
|
||||||
'id'
|
|
||||||
),
|
|
||||||
'createdAt'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDelta = (
|
const getDelta = (
|
||||||
subscriptionData: OrdersUpdateSubscription,
|
subscriptionData: OrdersUpdateSubscription,
|
||||||
@@ -145,32 +114,49 @@ const getDelta = (
|
|||||||
if (!subscriptionData.orders) {
|
if (!subscriptionData.orders) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return filterOrderUpdates(subscriptionData.orders);
|
return subscriptionData.orders;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const update = <T extends Omit<OrderFieldsFragment, 'market'> & Cursor>(
|
export const update = (
|
||||||
data: T[] | null,
|
data: ReturnType<typeof getData> | null,
|
||||||
delta: ReturnType<typeof getDelta>,
|
delta: ReturnType<typeof getDelta>,
|
||||||
variables: OrdersQueryVariables,
|
reload: () => void,
|
||||||
mapDeltaToData: (delta: OrderUpdateFieldsFragment) => T
|
variables?: OrdersQueryVariables
|
||||||
): T[] => {
|
) => {
|
||||||
const updatedData = data ? [...data] : ([] as T[]);
|
if (!data) {
|
||||||
delta.forEach((orderUpdate) => {
|
return data;
|
||||||
const index = data?.findIndex((order) => order.id === orderUpdate.id) ?? -1;
|
}
|
||||||
const newer = !data?.length || orderUpdate.createdAt >= data[0].createdAt;
|
// A single update can contain the same order with multiple updates, so we need to find
|
||||||
|
// the latest version of the order and only update using that
|
||||||
|
const incoming = orderBy(
|
||||||
|
uniqBy(
|
||||||
|
orderBy(delta, (order) => order.updatedAt || order.createdAt, 'desc'),
|
||||||
|
'id'
|
||||||
|
),
|
||||||
|
'createdAt'
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedData = [...data];
|
||||||
|
incoming.forEach((orderUpdate) => {
|
||||||
|
const index = data.findIndex((edge) => edge.node.id === orderUpdate.id);
|
||||||
|
const newer =
|
||||||
|
data.length === 0 || orderUpdate.createdAt >= data[0].node.createdAt;
|
||||||
const doesFilterPass =
|
const doesFilterPass =
|
||||||
!variables || orderMatchFilters(orderUpdate, variables);
|
!variables || orderMatchFilters(orderUpdate, variables);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
if (doesFilterPass) {
|
if (doesFilterPass) {
|
||||||
updatedData[index] = {
|
updatedData[index] = {
|
||||||
...updatedData[index],
|
...updatedData[index],
|
||||||
...mapDeltaToData(orderUpdate),
|
node: mapOrderUpdateToOrder(orderUpdate),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
updatedData.splice(index, 1);
|
updatedData.splice(index, 1);
|
||||||
}
|
}
|
||||||
} else if (newer && doesFilterPass) {
|
} else if (newer && doesFilterPass) {
|
||||||
updatedData.unshift(mapDeltaToData(orderUpdate));
|
updatedData.unshift({
|
||||||
|
node: mapOrderUpdateToOrder(orderUpdate),
|
||||||
|
cursor: '',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return updatedData;
|
return updatedData;
|
||||||
@@ -184,13 +170,11 @@ export const ordersProvider = makeDataProvider<
|
|||||||
ReturnType<typeof getData>,
|
ReturnType<typeof getData>,
|
||||||
OrdersUpdateSubscription,
|
OrdersUpdateSubscription,
|
||||||
ReturnType<typeof getDelta>,
|
ReturnType<typeof getDelta>,
|
||||||
OrdersQueryVariables,
|
OrdersQueryVariables
|
||||||
OrdersUpdateSubscriptionVariables
|
|
||||||
>({
|
>({
|
||||||
query: OrdersDocument,
|
query: OrdersDocument,
|
||||||
subscriptionQuery: OrdersUpdateDocument,
|
subscriptionQuery: OrdersUpdateDocument,
|
||||||
update: (data, delta, reload, variables) =>
|
update,
|
||||||
update(data, delta, variables, mapOrderUpdateToOrder),
|
|
||||||
getData,
|
getData,
|
||||||
getDelta,
|
getDelta,
|
||||||
pagination: {
|
pagination: {
|
||||||
@@ -201,10 +185,6 @@ export const ordersProvider = makeDataProvider<
|
|||||||
resetDelay: 1000,
|
resetDelay: 1000,
|
||||||
additionalContext: { isEnlargedTimeout: true },
|
additionalContext: { isEnlargedTimeout: true },
|
||||||
fetchPolicy: 'no-cache',
|
fetchPolicy: 'no-cache',
|
||||||
getSubscriptionVariables: ({ partyId, marketIds }) => ({
|
|
||||||
partyId,
|
|
||||||
marketIds,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const activeOrdersProvider = makeDerivedDataProvider<
|
export const activeOrdersProvider = makeDerivedDataProvider<
|
||||||
@@ -228,36 +208,27 @@ export const activeOrdersProvider = makeDerivedDataProvider<
|
|||||||
}
|
}
|
||||||
const orders = partsData[0] as ReturnType<typeof getData>;
|
const orders = partsData[0] as ReturnType<typeof getData>;
|
||||||
return variables.marketId
|
return variables.marketId
|
||||||
? orders.filter((order) => variables.marketId === order.market.id)
|
? orders.filter((edge) => variables.marketId === edge.node.market.id)
|
||||||
: orders;
|
: orders;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||||
(Order & Cursor)[],
|
(Order | null)[],
|
||||||
never,
|
Order[],
|
||||||
OrdersQueryVariables
|
OrdersQueryVariables
|
||||||
>(
|
>(
|
||||||
[
|
[
|
||||||
ordersProvider,
|
ordersProvider,
|
||||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
(callback, client) => marketsProvider(callback, client, undefined),
|
||||||
],
|
],
|
||||||
(partsData, variables, prevData, parts): Order[] => {
|
(partsData): Order[] =>
|
||||||
if (prevData && parts[0].isUpdate) {
|
((partsData[0] as ReturnType<typeof getData>) || []).map((edge) => ({
|
||||||
return update(
|
...edge.node,
|
||||||
prevData,
|
market: (partsData[1] as Market[]).find(
|
||||||
parts[0].delta,
|
(market) => market.id === edge.node.market.id
|
||||||
variables,
|
),
|
||||||
mapOrderUpdateToOrderWithMarket(partsData[1] as Record<string, Market>)
|
}))
|
||||||
);
|
|
||||||
}
|
|
||||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
|
||||||
(order) => ({
|
|
||||||
...order,
|
|
||||||
market: (partsData[1] as Record<string, Market>)[order.market.id],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const hasActiveOrderProvider = makeDerivedDataProvider<
|
export const hasActiveOrderProvider = makeDerivedDataProvider<
|
||||||
@@ -273,7 +244,7 @@ export const hasAmendableOrderProvider = makeDerivedDataProvider<
|
|||||||
>([activeOrdersProvider], (parts) => {
|
>([activeOrdersProvider], (parts) => {
|
||||||
const activeOrders = parts[0] as ReturnType<typeof getData>;
|
const activeOrders = parts[0] as ReturnType<typeof getData>;
|
||||||
const hasAmendableOrder = activeOrders.some(
|
const hasAmendableOrder = activeOrders.some(
|
||||||
(order) => !(order.liquidityProvision || order.peggedOrder)
|
(edge) => !(edge.node.liquidityProvision || edge.node.peggedOrder)
|
||||||
);
|
);
|
||||||
return hasAmendableOrder;
|
return hasAmendableOrder;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ describe('OrderListManager', () => {
|
|||||||
flush: jest.fn(),
|
flush: jest.fn(),
|
||||||
reload: jest.fn(),
|
reload: jest.fn(),
|
||||||
load: jest.fn(),
|
load: jest.fn(),
|
||||||
|
totalCount: undefined,
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(generateJsx());
|
render(generateJsx());
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const OrderListManager = ({
|
|||||||
gridRef.current.api.setRowData(data);
|
gridRef.current.api.setRowData(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const positionFields: PositionFieldsFragment[] = [
|
|||||||
{
|
{
|
||||||
__typename: 'Position',
|
__typename: 'Position',
|
||||||
realisedPNL: '230000',
|
realisedPNL: '230000',
|
||||||
openVolume: '-6',
|
openVolume: '6',
|
||||||
unrealisedPNL: '895000',
|
unrealisedPNL: '895000',
|
||||||
averageEntryPrice: '1129935',
|
averageEntryPrice: '1129935',
|
||||||
updatedAt: '2022-07-28T15:09:34.441143Z',
|
updatedAt: '2022-07-28T15:09:34.441143Z',
|
||||||
@@ -82,7 +82,7 @@ const positionFields: PositionFieldsFragment[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: 'Position',
|
__typename: 'Position',
|
||||||
realisedPNL: '-230000',
|
realisedPNL: '230000',
|
||||||
openVolume: '1',
|
openVolume: '1',
|
||||||
unrealisedPNL: '-22519',
|
unrealisedPNL: '-22519',
|
||||||
averageEntryPrice: '84400088',
|
averageEntryPrice: '84400088',
|
||||||
@@ -98,24 +98,6 @@ const positionFields: PositionFieldsFragment[] = [
|
|||||||
lossSocializationAmount: '0',
|
lossSocializationAmount: '0',
|
||||||
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
__typename: 'Position',
|
|
||||||
realisedPNL: '-303295252',
|
|
||||||
openVolume: '0',
|
|
||||||
unrealisedPNL: '0',
|
|
||||||
averageEntryPrice: '6126312',
|
|
||||||
updatedAt: '2022-07-28T14:53:54.725477Z',
|
|
||||||
positionStatus: PositionStatus.POSITION_STATUS_CLOSED_OUT,
|
|
||||||
lossSocializationAmount: '261',
|
|
||||||
market: {
|
|
||||||
id: 'market-3',
|
|
||||||
__typename: 'Market',
|
|
||||||
},
|
|
||||||
party: {
|
|
||||||
id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
|
||||||
__typename: 'Party',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const marginsFields: MarginFieldsFragment[] = [
|
const marginsFields: MarginFieldsFragment[] = [
|
||||||
|
|||||||
@@ -10,33 +10,32 @@ fragment TradeFields on Trade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query Trades($marketId: ID!, $pagination: Pagination) {
|
query Trades($marketId: ID!, $pagination: Pagination) {
|
||||||
trades(filter: { marketIds: [$marketId] }, pagination: $pagination) {
|
market(id: $marketId) {
|
||||||
edges {
|
id
|
||||||
node {
|
tradesConnection(pagination: $pagination) {
|
||||||
...TradeFields
|
edges {
|
||||||
|
node {
|
||||||
|
...TradeFields
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
hasNextPage
|
||||||
|
hasPreviousPage
|
||||||
}
|
}
|
||||||
cursor
|
|
||||||
}
|
|
||||||
pageInfo {
|
|
||||||
startCursor
|
|
||||||
endCursor
|
|
||||||
hasNextPage
|
|
||||||
hasPreviousPage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment TradeUpdateFields on TradeUpdate {
|
|
||||||
id
|
|
||||||
price
|
|
||||||
size
|
|
||||||
createdAt
|
|
||||||
marketId
|
|
||||||
aggressor
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription TradesUpdate($marketId: ID!) {
|
subscription TradesUpdate($marketId: ID!) {
|
||||||
tradesStream(filter: { marketIds: [$marketId] }) {
|
trades(marketId: $marketId) {
|
||||||
...TradeUpdateFields
|
id
|
||||||
|
price
|
||||||
|
size
|
||||||
|
createdAt
|
||||||
|
marketId
|
||||||
|
aggressor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-28
@@ -11,16 +11,14 @@ export type TradesQueryVariables = Types.Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type TradesQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
export type 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 TradeUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side };
|
|
||||||
|
|
||||||
export type TradesUpdateSubscriptionVariables = Types.Exact<{
|
export type TradesUpdateSubscriptionVariables = Types.Exact<{
|
||||||
marketId: Types.Scalars['ID'];
|
marketId: Types.Scalars['ID'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type TradesUpdateSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side }> | 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`
|
export const TradeFieldsFragmentDoc = gql`
|
||||||
fragment TradeFields on Trade {
|
fragment TradeFields on Trade {
|
||||||
@@ -34,30 +32,23 @@ export const TradeFieldsFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TradeUpdateFieldsFragmentDoc = gql`
|
|
||||||
fragment TradeUpdateFields on TradeUpdate {
|
|
||||||
id
|
|
||||||
price
|
|
||||||
size
|
|
||||||
createdAt
|
|
||||||
marketId
|
|
||||||
aggressor
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export const TradesDocument = gql`
|
export const TradesDocument = gql`
|
||||||
query Trades($marketId: ID!, $pagination: Pagination) {
|
query Trades($marketId: ID!, $pagination: Pagination) {
|
||||||
trades(filter: {marketIds: [$marketId]}, pagination: $pagination) {
|
market(id: $marketId) {
|
||||||
edges {
|
id
|
||||||
node {
|
tradesConnection(pagination: $pagination) {
|
||||||
...TradeFields
|
edges {
|
||||||
|
node {
|
||||||
|
...TradeFields
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
hasNextPage
|
||||||
|
hasPreviousPage
|
||||||
}
|
}
|
||||||
cursor
|
|
||||||
}
|
|
||||||
pageInfo {
|
|
||||||
startCursor
|
|
||||||
endCursor
|
|
||||||
hasNextPage
|
|
||||||
hasPreviousPage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,11 +84,16 @@ export type TradesLazyQueryHookResult = ReturnType<typeof useTradesLazyQuery>;
|
|||||||
export type TradesQueryResult = Apollo.QueryResult<TradesQuery, TradesQueryVariables>;
|
export type TradesQueryResult = Apollo.QueryResult<TradesQuery, TradesQueryVariables>;
|
||||||
export const TradesUpdateDocument = gql`
|
export const TradesUpdateDocument = gql`
|
||||||
subscription TradesUpdate($marketId: ID!) {
|
subscription TradesUpdate($marketId: ID!) {
|
||||||
tradesStream(filter: {marketIds: [$marketId]}) {
|
trades(marketId: $marketId) {
|
||||||
...TradeUpdateFields
|
id
|
||||||
|
price
|
||||||
|
size
|
||||||
|
createdAt
|
||||||
|
marketId
|
||||||
|
aggressor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${TradeUpdateFieldsFragmentDoc}`;
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useTradesUpdateSubscription__
|
* __useTradesUpdateSubscription__
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import compact from 'lodash/compact';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
@@ -18,11 +19,12 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
|||||||
dataProvider: tradesWithMarketProvider,
|
dataProvider: tradesWithMarketProvider,
|
||||||
variables: { marketId },
|
variables: { marketId },
|
||||||
});
|
});
|
||||||
|
const trades = compact(data).map((d) => d.node);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TradesTable
|
<TradesTable
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
rowData={data}
|
rowData={trades}
|
||||||
onClick={(price?: string) => {
|
onClick={(price?: string) => {
|
||||||
if (price) {
|
if (price) {
|
||||||
updateOrder(marketId, { price });
|
updateOrder(marketId, { price });
|
||||||
|
|||||||
@@ -3,95 +3,75 @@ import {
|
|||||||
makeDerivedDataProvider,
|
makeDerivedDataProvider,
|
||||||
defaultAppend as append,
|
defaultAppend as append,
|
||||||
} from '@vegaprotocol/data-provider';
|
} from '@vegaprotocol/data-provider';
|
||||||
import type { PageInfo, Cursor } from '@vegaprotocol/data-provider';
|
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
import { marketsProvider } from '@vegaprotocol/markets';
|
||||||
import type {
|
import type {
|
||||||
TradesQuery,
|
TradesQuery,
|
||||||
TradesQueryVariables,
|
TradesQueryVariables,
|
||||||
TradeFieldsFragment,
|
TradeFieldsFragment,
|
||||||
TradesUpdateSubscription,
|
TradesUpdateSubscription,
|
||||||
TradeUpdateFieldsFragment,
|
|
||||||
TradesUpdateSubscriptionVariables,
|
|
||||||
} from './__generated__/Trades';
|
} from './__generated__/Trades';
|
||||||
import { TradesDocument, TradesUpdateDocument } from './__generated__/Trades';
|
import { TradesDocument, TradesUpdateDocument } from './__generated__/Trades';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import produce from 'immer';
|
||||||
|
|
||||||
export const MAX_TRADES = 500;
|
export const MAX_TRADES = 500;
|
||||||
|
|
||||||
const getData = (
|
const getData = (
|
||||||
responseData: TradesQuery | null
|
responseData: TradesQuery | null
|
||||||
): (TradeFieldsFragment & Cursor)[] =>
|
): ({
|
||||||
responseData?.trades?.edges.map<TradeFieldsFragment & Cursor>((edge) => ({
|
cursor: string;
|
||||||
...edge.node,
|
node: TradeFieldsFragment;
|
||||||
cursor: edge.cursor,
|
} | null)[] => responseData?.market?.tradesConnection?.edges || [];
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const getDelta = (subscriptionData: TradesUpdateSubscription) =>
|
const getDelta = (subscriptionData: TradesUpdateSubscription) =>
|
||||||
subscriptionData?.tradesStream || [];
|
subscriptionData?.trades || [];
|
||||||
|
|
||||||
const mapTradeUpdateToTrade = (
|
const update = (
|
||||||
tradeUpdate: TradeUpdateFieldsFragment
|
data: ReturnType<typeof getData> | null,
|
||||||
): TradeFieldsFragment => {
|
delta: ReturnType<typeof getDelta>
|
||||||
const { marketId, ...trade } = tradeUpdate;
|
) => {
|
||||||
return {
|
if (!data) return data;
|
||||||
...trade,
|
return produce(data, (draft) => {
|
||||||
__typename: 'Trade',
|
// for each incoming trade add it to the beginning and remove oldest trade
|
||||||
market: {
|
orderBy(delta, 'createdAt', 'desc').forEach((node) => {
|
||||||
__typename: 'Market',
|
const { marketId, ...nodeData } = node;
|
||||||
id: marketId,
|
draft.unshift({
|
||||||
},
|
node: {
|
||||||
};
|
...nodeData,
|
||||||
};
|
__typename: 'Trade',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: marketId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cursor: '',
|
||||||
|
});
|
||||||
|
|
||||||
const mapTradeUpdateToTradeWithMarket =
|
if (draft.length > MAX_TRADES) {
|
||||||
(markets: Record<string, Market>) =>
|
draft.pop();
|
||||||
(tradeUpdate: TradeUpdateFieldsFragment): Trade => {
|
}
|
||||||
const { market, ...trade } = mapTradeUpdateToTrade(tradeUpdate);
|
});
|
||||||
return {
|
|
||||||
...trade,
|
|
||||||
market: markets[market.id],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const update = <T extends Omit<TradeFieldsFragment, 'market'> & Cursor>(
|
|
||||||
data: T[] | null,
|
|
||||||
delta: ReturnType<typeof getDelta>,
|
|
||||||
variables: TradesQueryVariables,
|
|
||||||
mapDeltaToData: (delta: TradeUpdateFieldsFragment) => T
|
|
||||||
): T[] => {
|
|
||||||
const updatedData = data ? [...data] : ([] as T[]);
|
|
||||||
orderBy(delta, 'createdAt', 'desc').forEach((tradeUpdate) => {
|
|
||||||
const index = data?.findIndex((trade) => trade.id === tradeUpdate.id) ?? -1;
|
|
||||||
if (index !== -1) {
|
|
||||||
updatedData[index] = {
|
|
||||||
...updatedData[index],
|
|
||||||
...mapDeltaToData(tradeUpdate),
|
|
||||||
};
|
|
||||||
} else if (!data?.length || tradeUpdate.createdAt >= data[0].createdAt) {
|
|
||||||
updatedData.unshift(mapDeltaToData(tradeUpdate));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return updatedData.slice(0, MAX_TRADES);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Trade = Omit<TradeFieldsFragment, 'market'> & { market?: Market };
|
export type Trade = Omit<TradeFieldsFragment, 'market'> & { market?: Market };
|
||||||
|
export type TradeEdge = Edge<Trade>;
|
||||||
|
|
||||||
const getPageInfo = (responseData: TradesQuery | null): PageInfo | null =>
|
const getPageInfo = (responseData: TradesQuery | null): PageInfo | null =>
|
||||||
responseData?.trades?.pageInfo || null;
|
responseData?.market?.tradesConnection?.pageInfo || null;
|
||||||
|
|
||||||
export const tradesProvider = makeDataProvider<
|
export const tradesProvider = makeDataProvider<
|
||||||
Parameters<typeof getData>['0'],
|
Parameters<typeof getData>['0'],
|
||||||
ReturnType<typeof getData>,
|
ReturnType<typeof getData>,
|
||||||
Parameters<typeof getDelta>['0'],
|
Parameters<typeof getDelta>['0'],
|
||||||
ReturnType<typeof getDelta>,
|
ReturnType<typeof getDelta>,
|
||||||
TradesQueryVariables,
|
TradesQueryVariables
|
||||||
TradesUpdateSubscriptionVariables
|
|
||||||
>({
|
>({
|
||||||
query: TradesDocument,
|
query: TradesDocument,
|
||||||
subscriptionQuery: TradesUpdateDocument,
|
subscriptionQuery: TradesUpdateDocument,
|
||||||
update: (data, delta, reload, variables) =>
|
update,
|
||||||
update(data, delta, variables, mapTradeUpdateToTrade),
|
|
||||||
getData,
|
getData,
|
||||||
getDelta,
|
getDelta,
|
||||||
pagination: {
|
pagination: {
|
||||||
@@ -99,32 +79,34 @@ export const tradesProvider = makeDataProvider<
|
|||||||
append,
|
append,
|
||||||
first: MAX_TRADES,
|
first: MAX_TRADES,
|
||||||
},
|
},
|
||||||
getSubscriptionVariables: ({ marketId }) => ({ marketId }),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tradesWithMarketProvider = makeDerivedDataProvider<
|
export const tradesWithMarketProvider = makeDerivedDataProvider<
|
||||||
(Trade & Cursor)[],
|
(TradeEdge | null)[],
|
||||||
never,
|
Trade[],
|
||||||
TradesQueryVariables
|
TradesQueryVariables
|
||||||
>(
|
>(
|
||||||
[
|
[
|
||||||
tradesProvider,
|
tradesProvider,
|
||||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
(callback, client) => marketsProvider(callback, client, undefined),
|
||||||
],
|
],
|
||||||
(partsData, variables, prevData, parts): Trade[] | null => {
|
(partsData): (TradeEdge | null)[] | null => {
|
||||||
if (prevData && parts[0].isUpdate) {
|
const edges = partsData[0] as ReturnType<typeof getData>;
|
||||||
return update(
|
return edges.map((edge) => {
|
||||||
prevData,
|
if (edge === null) {
|
||||||
parts[0].delta as ReturnType<typeof getDelta>,
|
return null;
|
||||||
variables,
|
}
|
||||||
mapTradeUpdateToTradeWithMarket(partsData[1] as Record<string, Market>)
|
const node = {
|
||||||
);
|
...edge.node,
|
||||||
}
|
market: (partsData[1] as Market[]).find(
|
||||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
(market) => market.id === edge.node.market.id
|
||||||
(trade) => ({
|
),
|
||||||
...trade,
|
};
|
||||||
market: (partsData[1] as Record<string, Market>)[trade.market.id],
|
const cursor = edge?.cursor || '';
|
||||||
})
|
return {
|
||||||
);
|
cursor,
|
||||||
|
node,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,20 +11,24 @@ export const tradesQuery = (
|
|||||||
override?: PartialDeep<TradesQuery>
|
override?: PartialDeep<TradesQuery>
|
||||||
): TradesQuery => {
|
): TradesQuery => {
|
||||||
const defaultResult: TradesQuery = {
|
const defaultResult: TradesQuery = {
|
||||||
trades: {
|
market: {
|
||||||
__typename: 'TradeConnection',
|
id: 'market-0',
|
||||||
edges: trades.map((node, i) => ({
|
tradesConnection: {
|
||||||
__typename: 'TradeEdge',
|
__typename: 'TradeConnection',
|
||||||
node,
|
edges: trades.map((node, i) => ({
|
||||||
cursor: (i + 1).toString(),
|
__typename: 'TradeEdge',
|
||||||
})),
|
node,
|
||||||
pageInfo: {
|
cursor: (i + 1).toString(),
|
||||||
__typename: 'PageInfo',
|
})),
|
||||||
startCursor: '0',
|
pageInfo: {
|
||||||
endCursor: trades.length.toString(),
|
__typename: 'PageInfo',
|
||||||
hasNextPage: false,
|
startCursor: '0',
|
||||||
hasPreviousPage: false,
|
endCursor: trades.length.toString(),
|
||||||
|
hasNextPage: false,
|
||||||
|
hasPreviousPage: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
__typename: 'Market',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,7 +40,7 @@ export const tradesUpdateSubscription = (
|
|||||||
): TradesUpdateSubscription => {
|
): TradesUpdateSubscription => {
|
||||||
const defaultResult: TradesUpdateSubscription = {
|
const defaultResult: TradesUpdateSubscription = {
|
||||||
__typename: 'Subscription',
|
__typename: 'Subscription',
|
||||||
tradesStream: [
|
trades: [
|
||||||
{
|
{
|
||||||
__typename: 'TradeUpdate',
|
__typename: 'TradeUpdate',
|
||||||
id: '1234567890',
|
id: '1234567890',
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"tasksRunnerOptions": {
|
"tasksRunnerOptions": {
|
||||||
"default": {
|
"default": {
|
||||||
"runner": "nx/tasks-runners/default",
|
"runner": "@nrwl/nx-cloud",
|
||||||
"options": {
|
"options": {
|
||||||
"cacheableOperations": ["build", "lint", "test", "e2e"],
|
"cacheableOperations": ["build", "lint", "test", "e2e"],
|
||||||
"accessToken": "OTY4ZjdlZTItNGIwNy00NDcyLTllZjctOWIzYTg1NWE0Yzg1fHJlYWQtd3JpdGU=",
|
"accessToken": "OTY4ZjdlZTItNGIwNy00NDcyLTllZjctOWIzYTg1NWE0Yzg1fHJlYWQtd3JpdGU=",
|
||||||
|
|||||||
+1
-1
@@ -161,7 +161,7 @@
|
|||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
"cypress": "^12.9.0",
|
"cypress": "^12.9.0",
|
||||||
"cypress-mochawesome-reporter": "^3.3.0",
|
"cypress-mochawesome-reporter": "^3.3.0",
|
||||||
"cypress-real-events": "^1.8.1",
|
"cypress-real-events": "^1.7.6",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"eslint": "8.15.0",
|
"eslint": "8.15.0",
|
||||||
"eslint-config-next": "12.2.3",
|
"eslint-config-next": "12.2.3",
|
||||||
|
|||||||
@@ -11882,10 +11882,10 @@ cypress-mochawesome-reporter@^3.3.0:
|
|||||||
mochawesome-merge "^4.2.1"
|
mochawesome-merge "^4.2.1"
|
||||||
mochawesome-report-generator "^6.2.0"
|
mochawesome-report-generator "^6.2.0"
|
||||||
|
|
||||||
cypress-real-events@^1.8.1:
|
cypress-real-events@^1.7.6:
|
||||||
version "1.8.1"
|
version "1.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.8.1.tgz#d00c7fe93124bbe7c0f27296684838614d24a840"
|
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.6.tgz#6f17e0b2ceea1d6dc60f6737d8f84cc517bbbb4c"
|
||||||
integrity sha512-8fFnA8EzS3EVbAmpSEUf3A8yZCmfU3IPOSGUDVFCdE1ke1gYL1A+gvXXV6HKUbTPRuvKKt2vpaMbUwYLpDRswQ==
|
integrity sha512-yP6GnRrbm6HK5q4DH6Nnupz37nOfZu/xn1xFYqsE2o4G73giPWQOdu6375QYpwfU1cvHNCgyD2bQ2hPH9D7NMw==
|
||||||
|
|
||||||
cypress@^12.9.0:
|
cypress@^12.9.0:
|
||||||
version "12.9.0"
|
version "12.9.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user