parent
f244cd07d4
commit
c573349f68
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal 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
6
libs/accounts/.babelrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"presets": [
|
||||
"@nrwl/next/babel"
|
||||
],
|
||||
"plugins": []
|
||||
}
|
18
libs/accounts/.eslintrc.json
Normal file
18
libs/accounts/.eslintrc.json
Normal 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
7
libs/accounts/README.md
Normal 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).
|
15
libs/accounts/jest.config.js
Normal file
15
libs/accounts/jest.config.js
Normal 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'],
|
||||
};
|
4
libs/accounts/package.json
Normal file
4
libs/accounts/package.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/accounts",
|
||||
"version": "0.0.1"
|
||||
}
|
43
libs/accounts/project.json
Normal file
43
libs/accounts/project.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
libs/accounts/src/index.ts
Normal file
3
libs/accounts/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './lib/accounts-table';
|
||||
export * from './lib/accounts-container';
|
||||
export * from './lib/accounts-data-provider';
|
58
libs/accounts/src/lib/__generated__/AccountFields.ts
generated
Normal file
58
libs/accounts/src/lib/__generated__/AccountFields.ts
generated
Normal 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;
|
||||
}
|
69
libs/accounts/src/lib/__generated__/AccountSubscribe.ts
generated
Normal file
69
libs/accounts/src/lib/__generated__/AccountSubscribe.ts
generated
Normal 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;
|
||||
}
|
81
libs/accounts/src/lib/__generated__/Accounts.ts
generated
Normal file
81
libs/accounts/src/lib/__generated__/Accounts.ts
generated
Normal 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;
|
||||
}
|
18
libs/accounts/src/lib/accounts-container.tsx
Normal file
18
libs/accounts/src/lib/accounts-container.tsx
Normal 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} />;
|
||||
};
|
77
libs/accounts/src/lib/accounts-data-provider.ts
Normal file
77
libs/accounts/src/lib/accounts-data-provider.ts
Normal 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);
|
73
libs/accounts/src/lib/accounts-manager.tsx
Normal file
73
libs/accounts/src/lib/accounts-manager.tsx
Normal 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>
|
||||
);
|
||||
};
|
56
libs/accounts/src/lib/accounts-table.spec.tsx
Normal file
56
libs/accounts/src/lib/accounts-table.spec.tsx
Normal 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]);
|
||||
});
|
||||
});
|
147
libs/accounts/src/lib/accounts-table.tsx
Normal file
147
libs/accounts/src/lib/accounts-table.tsx
Normal 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;
|
1
libs/accounts/src/setup-tests.ts
Normal file
1
libs/accounts/src/setup-tests.ts
Normal file
@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
25
libs/accounts/tsconfig.json
Normal file
25
libs/accounts/tsconfig.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
22
libs/accounts/tsconfig.lib.json
Normal file
22
libs/accounts/tsconfig.lib.json
Normal 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"]
|
||||
}
|
20
libs/accounts/tsconfig.spec.json
Normal file
20
libs/accounts/tsconfig.spec.json
Normal 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"
|
||||
]
|
||||
}
|
@ -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}
|
||||
|
@ -47,7 +47,7 @@ export const MarketsContainer = () => {
|
||||
[gridRef]
|
||||
);
|
||||
const { data, error, loading } = useDataProvider<
|
||||
Markets_markets,
|
||||
Markets_markets[],
|
||||
Markets_markets_data
|
||||
>(marketsDataProvider, update);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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);
|
@ -1,2 +1,3 @@
|
||||
export * from './flash-cell';
|
||||
export * from './price-cell';
|
||||
export * from './summary-rows';
|
75
libs/react-helpers/src/lib/grid/summary-rows.spec.ts
Normal file
75
libs/react-helpers/src/lib/grid/summary-rows.spec.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
53
libs/react-helpers/src/lib/grid/summary-rows.ts
Normal file
53
libs/react-helpers/src/lib/grid/summary-rows.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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"],
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"version": 2,
|
||||
"projects": {
|
||||
"accounts": "libs/accounts",
|
||||
"cypress": "libs/cypress",
|
||||
"deal-ticket": "libs/deal-ticket",
|
||||
"deposits": "libs/deposits",
|
||||
|
Loading…
Reference in New Issue
Block a user