[#185] Add accounts table (#193)

* [#185] Add accounts table

* [#185] Add summary row to accounts table
This commit is contained in:
Bartłomiej Głownia 2022-04-06 19:48:05 +02:00 committed by GitHub
parent f244cd07d4
commit c573349f68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 908 additions and 13 deletions

20
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Jest",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/@nrwl/cli/bin/nx.js",
"args": [
"test",
"accounts",
"--codeCoverage=false",
"--testFile=summary-row.spec.ts",
"--watch"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
}
]
}

6
libs/accounts/.babelrc Normal file
View File

@ -0,0 +1,6 @@
{
"presets": [
"@nrwl/next/babel"
],
"plugins": []
}

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

7
libs/accounts/README.md Normal file
View File

@ -0,0 +1,7 @@
# accounts
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test accounts` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,15 @@
module.exports = {
displayName: 'positions',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/positions',
setupFilesAfterEnv: ['./src/setup-tests.ts'],
};

View File

@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/accounts",
"version": "0.0.1"
}

View File

@ -0,0 +1,43 @@
{
"root": "libs/accounts",
"sourceRoot": "libs/accounts/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/accounts",
"tsConfig": "libs/accounts/tsconfig.lib.json",
"project": "libs/accounts/package.json",
"entryFile": "libs/accounts/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/accounts/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/accounts/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/libs/accounts"],
"options": {
"jestConfig": "libs/accounts/jest.config.js",
"passWithNoTests": true
}
}
}
}

View File

@ -0,0 +1,3 @@
export * from './lib/accounts-table';
export * from './lib/accounts-container';
export * from './lib/accounts-data-provider';

View File

@ -0,0 +1,58 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../types/src/__generated__/globalTypes";
// ====================================================
// GraphQL fragment: AccountFields
// ====================================================
export interface AccountFields_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
}
export interface AccountFields_asset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface AccountFields {
__typename: "Account";
/**
* Account type (General, Margin, etc)
*/
type: AccountType;
/**
* Balance as string - current account balance (approx. as balances can be updated several times per second)
*/
balance: string;
/**
* Market (only relevant to margin accounts)
*/
market: AccountFields_market | null;
/**
* Asset, the 'currency'
*/
asset: AccountFields_asset;
}

View File

@ -0,0 +1,69 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../types/src/__generated__/globalTypes";
// ====================================================
// GraphQL subscription operation: AccountSubscribe
// ====================================================
export interface AccountSubscribe_accounts_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
}
export interface AccountSubscribe_accounts_asset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface AccountSubscribe_accounts {
__typename: "Account";
/**
* Account type (General, Margin, etc)
*/
type: AccountType;
/**
* Balance as string - current account balance (approx. as balances can be updated several times per second)
*/
balance: string;
/**
* Market (only relevant to margin accounts)
*/
market: AccountSubscribe_accounts_market | null;
/**
* Asset, the 'currency'
*/
asset: AccountSubscribe_accounts_asset;
}
export interface AccountSubscribe {
/**
* Subscribe to the accounts updates
*/
accounts: AccountSubscribe_accounts;
}
export interface AccountSubscribeVariables {
partyId: string;
}

View File

@ -0,0 +1,81 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: Accounts
// ====================================================
export interface Accounts_party_accounts_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
}
export interface Accounts_party_accounts_asset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface Accounts_party_accounts {
__typename: "Account";
/**
* Account type (General, Margin, etc)
*/
type: AccountType;
/**
* Balance as string - current account balance (approx. as balances can be updated several times per second)
*/
balance: string;
/**
* Market (only relevant to margin accounts)
*/
market: Accounts_party_accounts_market | null;
/**
* Asset, the 'currency'
*/
asset: Accounts_party_accounts_asset;
}
export interface Accounts_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* Collateral accounts relating to a party
*/
accounts: Accounts_party_accounts[] | null;
}
export interface Accounts {
/**
* An entity that is trading on the VEGA network
*/
party: Accounts_party | null;
}
export interface AccountsVariables {
partyId: string;
}

View File

@ -0,0 +1,18 @@
import { t } from '@vegaprotocol/react-helpers';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { AccountsManager } from './accounts-manager';
export const AccountsContainer = () => {
const { keypair } = useVegaWallet();
if (!keypair) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return <AccountsManager partyId={keypair.pub} />;
};

View File

@ -0,0 +1,77 @@
import { gql } from '@apollo/client';
import type {
Accounts,
Accounts_party_accounts,
} from './__generated__/Accounts';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type {
AccountSubscribe,
AccountSubscribe_accounts,
} from './__generated__/AccountSubscribe';
const ACCOUNTS_FRAGMENT = gql`
fragment AccountFields on Account {
type
balance
market {
id
name
}
asset {
id
symbol
decimals
}
}
`;
const ACCOUNTS_QUERY = gql`
${ACCOUNTS_FRAGMENT}
query Accounts($partyId: ID!) {
party(id: $partyId) {
id
accounts {
...AccountFields
}
}
}
`;
export const ACCOUNTS_SUB = gql`
${ACCOUNTS_FRAGMENT}
subscription AccountSubscribe($partyId: ID!) {
accounts(partyId: $partyId) {
...AccountFields
}
}
`;
export const getId = (
data: Accounts_party_accounts | AccountSubscribe_accounts
) => `${data.type}-${data.asset.symbol}-${data.market?.id ?? 'null'}`;
const update = (
draft: Accounts_party_accounts[],
delta: AccountSubscribe_accounts
) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
if (index !== -1) {
draft[index] = delta;
} else {
draft.push(delta);
}
};
const getData = (responseData: Accounts): Accounts_party_accounts[] | null =>
responseData.party ? responseData.party.accounts : null;
const getDelta = (
subscriptionData: AccountSubscribe
): AccountSubscribe_accounts => subscriptionData.accounts;
export const accountsDataProvider = makeDataProvider<
Accounts,
Accounts_party_accounts[],
AccountSubscribe,
AccountSubscribe_accounts
>(ACCOUNTS_QUERY, ACCOUNTS_SUB, update, getData, getDelta);

View File

@ -0,0 +1,73 @@
import { useRef, useCallback, useMemo } from 'react';
import { produce } from 'immer';
import merge from 'lodash/merge';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useDataProvider, addSummaryRows } from '@vegaprotocol/react-helpers';
import type { AccountSubscribe_accounts } from './__generated__/AccountSubscribe';
import type { Accounts_party_accounts } from './__generated__/Accounts';
import type { AgGridReact } from 'ag-grid-react';
import {
AccountsTable,
getGroupId,
getGroupSummaryRow,
} from './accounts-table';
import { accountsDataProvider, getId } from './accounts-data-provider';
interface AccountsManagerProps {
partyId: string;
}
export const AccountsManager = ({ partyId }: AccountsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const update = useCallback(
(delta: AccountSubscribe_accounts) => {
const update: Accounts_party_accounts[] = [];
const add: Accounts_party_accounts[] = [];
if (!gridRef.current) {
return false;
}
const rowNode = gridRef.current.api.getRowNode(getId(delta));
if (rowNode) {
const updatedData = produce<Accounts_party_accounts>(
rowNode.data,
(draft: Accounts_party_accounts) => {
merge(draft, delta);
}
);
if (updatedData !== rowNode.data) {
update.push(updatedData);
}
} else {
add.push(delta);
}
if (update.length || add.length) {
gridRef.current.api.applyTransactionAsync({
update,
add,
addIndex: 0,
});
}
if (add.length) {
addSummaryRows(
gridRef.current.api,
gridRef.current.columnApi,
getGroupId,
getGroupSummaryRow
);
}
return true;
},
[gridRef]
);
const { data, error, loading } = useDataProvider<
Accounts_party_accounts[],
AccountSubscribe_accounts
>(accountsDataProvider, update, variables);
return (
<AsyncRenderer loading={loading} error={error} data={data}>
{(data) => <AccountsTable ref={gridRef} data={data} />}
</AsyncRenderer>
);
};

View File

@ -0,0 +1,56 @@
import AccountsTable from './accounts-table';
import { act, render, screen } from '@testing-library/react';
import type { Accounts_party_accounts } from './__generated__/Accounts';
import { AccountType } from '@vegaprotocol/types';
const singleRow: Accounts_party_accounts = {
__typename: 'Account',
type: AccountType.Margin,
balance: '125600000',
market: {
__typename: 'Market',
name: 'BTCUSD Monthly (30 Jun 2022)',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
},
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
decimals: 5,
},
};
const singleRowData = [singleRow];
test('should render successfully', async () => {
await act(async () => {
const { baseElement } = render(<AccountsTable data={[]} />);
expect(baseElement).toBeTruthy();
});
});
test('Render correct columns', async () => {
await act(async () => {
render(<AccountsTable data={singleRowData} />);
});
const headers = screen.getAllByRole('columnheader');
expect(headers).toHaveLength(4);
expect(
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
).toEqual(['Asset', 'Type', 'Market', 'Balance']);
});
test('Correct formatting applied', async () => {
await act(async () => {
render(<AccountsTable data={singleRowData} />);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'tBTC',
singleRow.type,
'BTCUSD Monthly (30 Jun 2022)',
'1,256.00000',
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
});

View File

@ -0,0 +1,147 @@
import { forwardRef } from 'react';
import type { ColumnApi, ValueFormatterParams } from 'ag-grid-community';
import {
PriceCell,
formatNumber,
t,
addSummaryRows,
} from '@vegaprotocol/react-helpers';
import type { SummaryRow } from '@vegaprotocol/react-helpers';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react';
import type { AgGridReact } from 'ag-grid-react';
import type { Accounts_party_accounts } from './__generated__/Accounts';
import { getId as getRowNodeId } from './accounts-data-provider';
interface AccountsTableProps {
data: Accounts_party_accounts[] | null;
}
interface AccountsTableValueFormatterParams extends ValueFormatterParams {
data: Accounts_party_accounts;
}
export const getGroupId = (
data: Accounts_party_accounts & SummaryRow,
columnApi: ColumnApi
) => {
if (data.__summaryRow) {
return null;
}
const sortColumnId = columnApi.getColumnState().find((c) => c.sort)?.colId;
switch (sortColumnId) {
case 'asset.symbol':
return data.asset.id;
}
return undefined;
};
export const getGroupSummaryRow = (
data: Accounts_party_accounts[],
columnApi: ColumnApi
): Partial<Accounts_party_accounts & SummaryRow> | null => {
if (!data.length) {
return null;
}
const sortColumnId = columnApi.getColumnState().find((c) => c.sort)?.colId;
switch (sortColumnId) {
case 'asset.symbol':
return {
__summaryRow: true,
balance: data
.reduce((a, i) => a + (parseFloat(i.balance) || 0), 0)
.toString(),
asset: data[0].asset,
};
}
return null;
};
const comparator = (
valueA: string,
valueB: string,
nodeA: { data: Accounts_party_accounts & SummaryRow },
nodeB: { data: Accounts_party_accounts & SummaryRow },
isInverted: boolean
) => {
if (valueA < valueB) {
return -1;
}
if (valueA > valueB) {
return 1;
}
if (nodeA.data.__summaryRow) {
return isInverted ? -1 : 1;
}
if (nodeB.data.__summaryRow) {
return isInverted ? 1 : -1;
}
return 0;
};
export const AccountsTable = forwardRef<AgGridReact, AccountsTableProps>(
({ data }, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No accounts')}
rowData={data}
getRowNodeId={getRowNodeId}
ref={ref}
defaultColDef={{
flex: 1,
resizable: true,
}}
components={{ PriceCell }}
onSortChanged={({ api, columnApi }) => {
addSummaryRows(api, columnApi, getGroupId, getGroupSummaryRow);
}}
onGridReady={(event) => {
event.columnApi.applyColumnState({
state: [
{
colId: 'asset.symbol',
sort: 'asc',
},
],
});
}}
>
<AgGridColumn
headerName={t('Asset')}
field="asset.symbol"
sortable
sortingOrder={['asc', 'desc']}
comparator={comparator}
/>
<AgGridColumn
headerName={t('Type')}
field="type"
valueFormatter="value || '—'"
/>
<AgGridColumn
headerName={t('Market')}
field="market.name"
valueFormatter="value || '—'"
/>
<AgGridColumn
headerName={t('Balance')}
field="balance"
cellRenderer="PriceCell"
valueFormatter={({
value,
data,
}: AccountsTableValueFormatterParams) =>
formatNumber(value, data.asset.decimals)
}
/>
</AgGrid>
);
}
);
export default AccountsTable;

View File

@ -0,0 +1 @@
import '@testing-library/jest-dom';

View File

@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/next/typings/image.d.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node", "@testing-library/jest-dom"]
},
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts",
"../react-helpers/src/lib/grid-cells/summary-row.spec.ts"
]
}

View File

@ -46,7 +46,8 @@ const transactionStatus = 'default';
function generateJsx() {
return (
<VegaWalletContext.Provider value={{} as never}>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<VegaWalletContext.Provider value={{} as any}>
<DealTicket
defaultOrder={order}
market={market}

View File

@ -47,7 +47,7 @@ export const MarketsContainer = () => {
[gridRef]
);
const { data, error, loading } = useDataProvider<
Markets_markets,
Markets_markets[],
Markets_markets_data
>(marketsDataProvider, update);

View File

@ -69,7 +69,7 @@ const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
export const marketsDataProvider = makeDataProvider<
Markets,
Markets_markets,
Markets_markets[],
MarketDataSub,
MarketDataSub_marketData
>(MARKETS_QUERY, MARKET_DATA_SUB, update, getData, getDelta);

View File

@ -92,7 +92,7 @@ const getDelta = (
export const positionsDataProvider = makeDataProvider<
Positions,
Positions_party_positions,
Positions_party_positions[],
PositionSubscribe,
PositionSubscribe_positions
>(POSITION_QUERY, POSITIONS_SUB, update, getData, getDelta);

View File

@ -50,7 +50,7 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
[gridRef]
);
const { data, error, loading } = useDataProvider<
Positions_party_positions,
Positions_party_positions[],
PositionSubscribe_positions
>(positionsDataProvider, update, variables);
return (

View File

@ -24,7 +24,7 @@ function setup(items: Items, rowNodes: Items) {
return undefined;
},
};
// eslint-disable-next-line
// eslint-disable-next-line @typescript-eslint/no-explicit-any
renderHook(() => useApplyGridTransaction(items, gridApiMock as any));
return gridApiMock;
}

View File

@ -9,7 +9,7 @@ export function useDataProvider<Data, Delta>(
variables?: OperationVariables
) {
const client = useApolloClient();
const [data, setData] = useState<Data[] | null>(null);
const [data, setData] = useState<Data | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | undefined>(undefined);
const initialized = useRef<boolean>(false);

View File

@ -1,6 +1,6 @@
export * from './lib/context';
export * from './lib/format';
export * from './lib/grid-cells';
export * from './lib/grid';
export * from './lib/storage';
export * from './lib/generic-data-provider';
export * from './lib/i18n';

View File

@ -12,7 +12,7 @@ import isEqual from 'lodash/isEqual';
export interface UpdateCallback<Data, Delta> {
(arg: {
data: Data[] | null;
data: Data | null;
error?: Error;
loading: boolean;
delta?: Delta;
@ -30,11 +30,11 @@ export interface Subscribe<Data, Delta> {
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
interface Update<Data, Delta> {
(draft: Draft<Data>[], delta: Delta): void;
(draft: Draft<Data>, delta: Delta): void;
}
interface GetData<QueryData, Data> {
(subscriptionData: QueryData): Data[] | null;
(subscriptionData: QueryData): Data | null;
}
interface GetDelta<SubscriptionData, Delta> {
@ -53,7 +53,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
const updateQueue: Delta[] = [];
let variables: OperationVariables | undefined = undefined;
let data: Data[] | null = null;
let data: Data | null = null;
let error: Error | undefined = undefined;
let loading = false;
let client: ApolloClient<object> | undefined = undefined;

View File

@ -10,7 +10,7 @@ describe('findFirstDiffPos', () => {
it('Returns -1 if a string is undefined (just in case)', () => {
const a = 'test';
// eslint-disable-next-line
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const b = undefined as any as string;
expect(findFirstDiffPos(a, b)).toEqual(-1);

View File

@ -1,2 +1,3 @@
export * from './flash-cell';
export * from './price-cell';
export * from './summary-rows';

View File

@ -0,0 +1,75 @@
import type { GridApi, ColumnApi } from 'ag-grid-community';
import { addSummaryRows } from './summary-rows';
type RowMock = { group: string; count: string };
const getGroupId = jest.fn();
getGroupId.mockImplementation(
(data: { group: string; __summaryRow: boolean }) =>
data.__summaryRow ? null : data.group
);
const getGroupSummaryRow = jest.fn();
getGroupSummaryRow.mockImplementation(
(data: RowMock[]): Partial<RowMock> | null => {
if (!data.length) {
return null;
}
const row: Partial<RowMock> = {
count: data.reduce((a, c) => a + parseFloat(c.count), 0).toString(),
};
return row;
}
);
const api = {
forEachNodeAfterFilterAndSort: jest.fn(),
applyTransactionAsync: jest.fn(),
};
const columnsApi = {};
describe('addSummaryRows', () => {
it('should render search input and button', () => {
const nodes = [
{ data: { group: 'a', count: 10 } },
{ data: { group: 'a', count: 10, __summaryRow: true } },
{ data: { group: 'a', count: 20 } },
{ data: { group: 'b', count: 30 } },
{ data: { group: 'c', count: 40 } },
{ data: { group: 'c', count: 50 } },
{ data: { group: 'c', count: 60 } },
{ data: { group: 'd', count: 10, __summaryRow: true } },
{ data: { group: 'd', count: 70 } },
{ data: { group: 'd', count: 80 } },
];
api.forEachNodeAfterFilterAndSort.mockImplementationOnce(
nodes.forEach.bind(nodes)
);
addSummaryRows(
api as unknown as GridApi,
columnsApi as unknown as ColumnApi,
getGroupId,
getGroupSummaryRow
);
expect(api.forEachNodeAfterFilterAndSort).toBeCalledTimes(1);
expect(api.applyTransactionAsync).toBeCalledTimes(5);
expect(api.applyTransactionAsync).toHaveBeenNthCalledWith(1, {
remove: [nodes[1].data],
});
expect(api.applyTransactionAsync).toHaveBeenNthCalledWith(2, {
add: [{ count: '30' }],
addIndex: 2,
});
expect(api.applyTransactionAsync).toHaveBeenNthCalledWith(3, {
remove: [nodes[7].data],
});
expect(api.applyTransactionAsync).toHaveBeenNthCalledWith(4, {
add: [{ count: '150' }],
addIndex: 7,
});
expect(api.applyTransactionAsync).toHaveBeenNthCalledWith(5, {
add: [{ count: '150' }],
addIndex: 10,
});
});
});

View File

@ -0,0 +1,53 @@
import type { ColumnApi, GridApi } from 'ag-grid-community';
export interface SummaryRow {
__summaryRow?: boolean;
}
export function addSummaryRows<T>(
api: GridApi,
columnApi: ColumnApi,
getGroupId: (
data: T & SummaryRow,
columnApi: ColumnApi
) => string | null | undefined,
getGroupSummaryRow: (
data: (T & SummaryRow)[],
columnApi: ColumnApi
) => Partial<T & SummaryRow> | null
) {
let currentGroupId: string | null | undefined = undefined;
let group: T[] = [];
let addIndex = 0;
api.forEachNodeAfterFilterAndSort((node) => {
const nodeGroupId = getGroupId(node.data, columnApi);
if (currentGroupId === undefined) {
currentGroupId = nodeGroupId;
}
if (node.data.__summaryRow) {
api.applyTransactionAsync({
remove: [node.data],
});
addIndex -= 1;
} else if (currentGroupId !== undefined && currentGroupId !== nodeGroupId) {
if (group.length > 1) {
api.applyTransactionAsync({
add: [getGroupSummaryRow(group, columnApi)],
addIndex,
});
addIndex += 1;
}
group = [node.data];
currentGroupId = nodeGroupId;
} else if (currentGroupId) {
group.push(node.data);
}
addIndex += 1;
});
if (group.length > 1) {
api.applyTransactionAsync({
add: [getGroupSummaryRow(group, columnApi)],
addIndex,
});
}
}

View File

@ -15,6 +15,7 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@vegaprotocol/accounts": ["libs/accounts/src/index.ts"],
"@vegaprotocol/cypress": ["libs/cypress/src/index.ts"],
"@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"],
"@vegaprotocol/deposits": ["libs/deposits/src/index.ts"],

View File

@ -1,6 +1,7 @@
{
"version": 2,
"projects": {
"accounts": "libs/accounts",
"cypress": "libs/cypress",
"deal-ticket": "libs/deal-ticket",
"deposits": "libs/deposits",