feat(#1853): ledger entries date filtering (#2267)

* chore: update ledger entries columns

* fix: yarn generate types against stagnet3

* fix: orderbook decimal places issue  (#2235)

* fix: positions table fixes notional dp (#2144)

* fix: update decimals on position notional size

* fix: normalize values

* fix: fix positions unit tests

* fix: remove liquidation price

* fix: positions linting issue

* fix: remove liquidation price test

* fix: remove total summary row

* fix: remove comments

* fix: cypress test to not show trailing 0s

* fix: add back liq. price est as cell only

* fix: remove not used params

* chore: merge with release/testnet

* fix: orderbook dp

* Update libs/positions/src/lib/positions-table.spec.tsx

* feat(#1853): use date range filter

* feat(#1853): add date range filter to ledger entries on update

* chore(#1853): add extra checks

* fix: update types on stagnet3

* fix: add checkpoint balance restore

* fix(#1853): fix ledger generic make data provider cast in ledger entries

* fix(#1853): fix transfer type

* fix(#1853): remove TransferTypeMapping cast type

* fix(#1853): remove pagination filtering and use formatForInput from date

* fix(#1853): call filterChangedCallback in onChange method

* fix(#1853): remove subscription from ledger entries table

* fix(#1853): filterChangedCallback called in useEffect gets triggered also on reset

* fix: use-order-list-data hook order of params for makeInfiniteScrollGetRows

* fix(#1853): fix ledger build import all as schema

* fix(#1853): fix schema import

Co-authored-by: maciek <maciek@vegaprotocol.io>
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
This commit is contained in:
m.ray 2022-12-14 04:43:16 -05:00 committed by GitHub
parent f60e8a2910
commit 817521bb08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 305 additions and 159 deletions

View File

@ -86,10 +86,10 @@ export const useFillsList = ({ partyId, gridRef, scrolledToTop }: Props) => {
totalCountRef.current = totalCount;
const getRows = makeInfiniteScrollGetRows<TradeEdge>(
newRows,
dataRef,
totalCountRef,
load
load,
newRows
);
return { data, error, loading, addNewRows, getRows };
};

View File

@ -11,13 +11,18 @@ fragment LedgerEntry on AggregatedLedgerEntry {
senderPartyId
}
query LedgerEntries($partyId: ID!, $pagination: Pagination) {
query LedgerEntries(
$partyId: ID!
$pagination: Pagination
$dateRange: DateRange
) {
ledgerEntries(
filter: {
SenderAccountFilter: { partyIds: [$partyId] }
ReceiverAccountFilter: { partyIds: [$partyId] }
}
pagination: $pagination
dateRange: $dateRange
) {
edges {
node {

View File

@ -8,6 +8,7 @@ export type LedgerEntryFragment = { __typename?: 'AggregatedLedgerEntry', vegaTi
export type LedgerEntriesQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
pagination?: Types.InputMaybe<Types.Pagination>;
dateRange?: Types.InputMaybe<Types.DateRange>;
}>;
@ -28,10 +29,11 @@ export const LedgerEntryFragmentDoc = gql`
}
`;
export const LedgerEntriesDocument = gql`
query LedgerEntries($partyId: ID!, $pagination: Pagination) {
query LedgerEntries($partyId: ID!, $pagination: Pagination, $dateRange: DateRange) {
ledgerEntries(
filter: {SenderAccountFilter: {partyIds: [$partyId]}, ReceiverAccountFilter: {partyIds: [$partyId]}}
pagination: $pagination
dateRange: $dateRange
) {
edges {
node {
@ -62,6 +64,7 @@ export const LedgerEntriesDocument = gql`
* variables: {
* partyId: // value for 'partyId'
* pagination: // value for 'pagination'
* dateRange: // value for 'dateRange'
* },
* });
*/

View File

@ -3,15 +3,24 @@ import { assetsProvider } from '@vegaprotocol/assets';
import type { Market } from '@vegaprotocol/market-list';
import { marketsProvider } from '@vegaprotocol/market-list';
import type { PageInfo } from '@vegaprotocol/react-helpers';
import { makeInfiniteScrollGetRows } from '@vegaprotocol/react-helpers';
import {
defaultAppend as append,
makeDataProvider,
makeDerivedDataProvider,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
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 {
LedgerEntriesQuery,
LedgerEntriesQueryVariables,
LedgerEntryFragment,
} from './__generated__/LedgerEntries';
import { LedgerEntriesDocument } from './__generated__/LedgerEntries';
@ -23,26 +32,75 @@ export type LedgerEntry = LedgerEntryFragment & {
marketReceiver: Market | null | undefined;
};
const getData = (responseData: LedgerEntriesQuery): LedgerEntry[] => {
console.log('responseData', responseData);
return (
responseData.ledgerEntries?.edges
?.filter((e) => Boolean(e?.node))
.map((e, i) => ({ id: i, ...e?.node } as LedgerEntry)) ?? []
);
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
const getData = (responseData: LedgerEntriesQuery) => {
return responseData.ledgerEntries?.edges || [];
};
export const update = (
data: ReturnType<typeof getData>,
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 getPageInfo = (responseData: LedgerEntriesQuery): PageInfo | null =>
responseData.ledgerEntries?.pageInfo || null;
const ledgerEntriesOnlyProvider = makeDataProvider<
LedgerEntriesQuery,
LedgerEntry[] | null,
never,
never
>({
const ledgerEntriesOnlyProvider = makeDataProvider({
query: LedgerEntriesDocument,
getData,
getDelta: getData,
update,
pagination: {
getPageInfo,
append,
@ -51,12 +109,14 @@ const ledgerEntriesOnlyProvider = makeDataProvider<
});
export const ledgerEntriesProvider = makeDerivedDataProvider<
LedgerEntry[],
never
(AggregatedLedgerEntriesEdge | null)[],
AggregatedLedgerEntriesEdge[],
LedgerEntriesQueryVariables
>(
[ledgerEntriesOnlyProvider, assetsProvider, marketsProvider],
([entries, assets, markets]): LedgerEntry[] =>
entries.map((entry: LedgerEntry) => {
([entries, assets, markets]) => {
return entries.map((edge: AggregatedLedgerEntriesEdge) => {
const entry = edge?.node;
const asset = assets.find((asset: Asset) => asset.id === entry.assetId);
const marketSender = markets.find(
(market: Market) => market.id === entry.senderMarketId
@ -64,15 +124,74 @@ export const ledgerEntriesProvider = makeDerivedDataProvider<
const marketReceiver = markets.find(
(market: Market) => market.id === entry.receiverMarketId
);
return { ...entry, asset, marketSender, marketReceiver };
})
return { node: { ...entry, asset, marketSender, marketReceiver } };
});
}
);
export const useLedgerEntriesDataProvider = (partyId: string) => {
const variables = useMemo(() => ({ partyId }), [partyId]);
return useDataProvider({
interface Props {
partyId: string;
filter?: Filter;
gridRef: RefObject<AgGridReact>;
}
export const useLedgerEntriesDataProvider = ({
partyId,
filter,
gridRef,
}: Props) => {
const dataRef = useRef<(AggregatedLedgerEntriesEdge | null)[] | null>(null);
const totalCountRef = useRef<number>();
const variables = useMemo<LedgerEntriesQueryVariables>(
() => ({
partyId,
dateRange: filter?.vegaTime?.value,
}),
[partyId, filter]
);
const update = useCallback(
({ data }: { data: (AggregatedLedgerEntriesEdge | null)[] | null }) => {
const avoidRerender = !!(
(dataRef.current?.length && data?.length) ||
(!dataRef.current?.length && !data?.length)
);
dataRef.current = data;
gridRef.current?.api?.refreshInfiniteCache();
return avoidRerender;
},
[gridRef]
);
const insert = useCallback(
({
data,
totalCount,
}: {
data: (AggregatedLedgerEntriesEdge | null)[] | null;
totalCount?: number;
}) => {
dataRef.current = data;
totalCountRef.current = totalCount;
return true;
},
[]
);
const { data, error, loading, load, totalCount } = useDataProvider({
dataProvider: ledgerEntriesProvider,
update,
insert,
variables,
skip: !partyId,
skip: !variables.partyId,
});
totalCountRef.current = totalCount;
const getRows = makeInfiniteScrollGetRows<AggregatedLedgerEntriesEdge>(
dataRef,
totalCountRef,
load
);
return { loading, error, data, getRows };
};

View File

@ -1,15 +1,55 @@
import { t } from '@vegaprotocol/react-helpers';
import type * as Schema from '@vegaprotocol/types';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { FilterChangedEvent } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import { useRef, useState } from 'react';
import { useLedgerEntriesDataProvider } from './ledger-entries-data-provider';
import { LedgerTable } from './ledger-table';
// '3ac37999796c2be3546e0c1d87daa8ec7e99d8c423969be44c2f63256c415004'
export interface Filter {
vegaTime?: {
value: Schema.DateRange;
};
}
type LedgerManagerProps = { partyId: string };
export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
const { data, error, loading } = useLedgerEntriesDataProvider(partyId);
const gridRef = useRef<AgGridReact | null>(null);
const [filter, setFilter] = useState<Filter | undefined>();
const { data, error, loading, getRows } = useLedgerEntriesDataProvider({
partyId,
filter,
gridRef,
});
const onFilterChanged = (event: FilterChangedEvent) => {
const updatedFilter = event.api.getFilterModel();
if (Object.keys(updatedFilter).length) {
setFilter(updatedFilter);
} else if (filter) {
setFilter(undefined);
}
};
return (
<AsyncRenderer data={data} error={error} loading={loading}>
<LedgerTable rowData={data} />
</AsyncRenderer>
<>
<LedgerTable
ref={gridRef}
rowModelType="infinite"
datasource={{ getRows }}
onFilterChanged={onFilterChanged}
/>
<div className="pointer-events-none absolute inset-0 top-5">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No entries')}
noDataCondition={(data) => !(data && data.length)}
/>
</div>
</>
);
};

View File

@ -1,5 +1,6 @@
import {
addDecimalsFormatNumber,
DateRangeFilter,
fromNanoSeconds,
getDateTimeFormat,
t,
@ -8,8 +9,10 @@ import {
import type {
VegaValueFormatterParams,
VegaICellRendererParams,
TypedDataAgGrid,
} from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import type * as Types from '@vegaprotocol/types';
import {
@ -18,6 +21,7 @@ import {
TransferTypeMapping,
} from '@vegaprotocol/types';
import type { LedgerEntry } from './ledger-entries-data-provider';
import { forwardRef } from 'react';
export const TransferTooltipCellComponent = ({
value,
@ -83,97 +87,85 @@ const ReceiverCellRenderer = ({
return <LedgerCellRenderer {...props} />;
};
export const LedgerTable = ({ ...props }) => (
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No entries')}
rowHeight={70}
getRowId={({ data }) => data.id}
tooltipShowDelay={500}
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
tooltipComponent: TransferTooltipCellComponent,
}}
{...props}
>
<AgGridColumn
headerName={t('Sender')}
field="senderAccountType"
cellRenderer={SenderCellRenderer}
/>
<AgGridColumn
headerName={t('Receiver')}
field="receiverAccountType"
cellRenderer={ReceiverCellRenderer}
/>
<AgGridColumn
headerName={t('Transfer Type')}
field="transferType"
tooltipField="transferType"
valueFormatter={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
value
? TransferTypeMapping[value as keyof typeof TransferTypeMapping]
: ''
}
/>
<AgGridColumn
headerName={t('Quantity')}
field="quantity"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
const marketDecimalPlaces = data?.marketSender?.decimalPlaces;
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(
value,
assetDecimalPlaces,
marketDecimalPlaces
)
: value;
}}
/>
<AgGridColumn
headerName={t('Asset')}
field="assetId"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
data?.asset?.symbol || value
}
/>
<AgGridColumn
headerName={t('Vega Time')}
field="vegaTime"
valueFormatter={({ value }: { value?: string }) =>
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-'
}
filter="agDateColumnFilter"
filterParams={{
comparator: (
filterLocalDateAtMidnight: number,
dateAsString: string
) => {
if (dateAsString == null) {
return 0;
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
(props, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No entries')}
rowHeight={70}
ref={ref}
getRowId={({ data }) => data.id}
tooltipShowDelay={500}
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
tooltipComponent: TransferTooltipCellComponent,
}}
{...props}
>
<AgGridColumn
headerName={t('Sender')}
field="senderAccountType"
cellRenderer={SenderCellRenderer}
/>
<AgGridColumn
headerName={t('Receiver')}
field="receiverAccountType"
cellRenderer={ReceiverCellRenderer}
/>
<AgGridColumn
headerName={t('Transfer Type')}
field="transferType"
tooltipField="transferType"
valueFormatter={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
value ? TransferTypeMapping[value] : ''
}
const filterDate = new Date(filterLocalDateAtMidnight)
.getTime()
.toString();
if (dateAsString < filterDate) {
return -1;
} else if (dateAsString > filterDate) {
return 1;
/>
<AgGridColumn
headerName={t('Quantity')}
field="quantity"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
const marketDecimalPlaces = data?.marketSender?.decimalPlaces;
const assetDecimalPlaces = data?.asset?.decimals || 0;
return value
? addDecimalsFormatNumber(
value,
assetDecimalPlaces,
marketDecimalPlaces
)
: value;
}}
/>
<AgGridColumn
headerName={t('Asset')}
field="assetId"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
data?.asset?.symbol || value
}
return 0;
},
}}
/>
</AgGrid>
/>
<AgGridColumn
headerName={t('Vega Time')}
field="vegaTime"
valueFormatter={({
value,
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-'
}
filter={DateRangeFilter}
/>
</AgGrid>
);
}
);

View File

@ -116,10 +116,10 @@ export const useOrderListData = ({
totalCountRef.current = totalCount;
const getRows = makeInfiniteScrollGetRows<OrderEdge>(
newRows,
dataRef,
totalCountRef,
load
load,
newRows
);
return { loading, error, data, addNewRows, getRows };
};

View File

@ -260,7 +260,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
valueFormatted: string;
data: Order;
}) => (
<span data-testId={`order-status-${data?.id}`}>
<span data-testid={`order-status-${data?.id}`}>
{valueFormatted}
</span>
)}

View File

@ -43,7 +43,7 @@ export const useClosePosition = () => {
setClosingOrder(undefined);
try {
// figure out if opsition is long or short and make side the opposite
// figure out if position is long or short and make side the opposite
const side = openVolume.startsWith('-')
? Schema.Side.SIDE_BUY
: Schema.Side.SIDE_SELL;

View File

@ -1,25 +1,13 @@
import type { ChangeEvent } from 'react';
import { useEffect } from 'react';
import type * as Schema from '@vegaprotocol/types';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { forwardRef, useImperativeHandle, useState } from 'react';
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
import { isValidDate } from '../format/date';
import { formatForInput } from '../format/date';
import { t } from '../i18n';
const defaultFilterValue: Schema.DateRange = {};
const toInputValue = (value: string) => {
const date = new Date(value);
if (!isValidDate(date)) {
return;
}
return `${date.getFullYear()}-${(date.getMonth() + 1)
.toString()
.padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}T${(
date.getHours() + 1
)
.toString()
.padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
};
export const DateRangeFilter = forwardRef((props: IFilterParams, ref) => {
const [value, setValue] = useState<Schema.DateRange>(defaultFilterValue);
@ -84,16 +72,16 @@ export const DateRangeFilter = forwardRef((props: IFilterParams, ref) => {
};
useEffect(() => {
props.filterChangedCallback();
}, [value]); //eslint-disable-line react-hooks/exhaustive-deps
props?.filterChangedCallback();
}, [value, props]);
const start = (value.start && toInputValue(value.start)) || '';
const end = (value.end && toInputValue(value.end)) || '';
const start = (value.start && formatForInput(new Date(value.start))) || '';
const end = (value.end && formatForInput(new Date(value.end))) || '';
return (
<div className="ag-filter-body-wrapper">
<fieldset className="ag-simple-filter-body-wrapper">
<label className="block" key="start">
<span className="block">start</span>
<span className="block">{t('Start')}</span>
<input
type="datetime-local"
name="start"
@ -102,7 +90,7 @@ export const DateRangeFilter = forwardRef((props: IFilterParams, ref) => {
/>
</label>
<label className="block" key="end">
<span className="block">end</span>
<span className="block">{t('End')}</span>
<input
type="datetime-local"
name="end"

View File

@ -24,10 +24,10 @@ const getLastRow = (
export const makeInfiniteScrollGetRows =
<T extends { node: any }>( // eslint-disable-line @typescript-eslint/no-explicit-any
newRows: MutableRefObject<number>,
data: MutableRefObject<(T | null)[] | null>,
totalCount: MutableRefObject<number | undefined>,
load: Load<(T | null)[]>
load: Load<(T | null)[]>,
newRows?: MutableRefObject<number>
) =>
async ({
successCallback,
@ -35,8 +35,8 @@ export const makeInfiniteScrollGetRows =
startRow,
endRow,
}: IGetRowsParams) => {
startRow += newRows.current;
endRow += newRows.current;
startRow += newRows?.current ?? 0;
endRow += newRows?.current ?? 0;
try {
if (data.current) {
const firstMissingRowIndex = data.current.indexOf(null);
@ -57,7 +57,7 @@ export const makeInfiniteScrollGetRows =
totalCount.current
);
const lastRow = currentLastNumber
? currentLastNumber - newRows.current
? currentLastNumber - (newRows?.current ?? 0)
: currentLastNumber;
successCallback(rowsThisBlock, lastRow);
} catch (e) {

View File

@ -88,10 +88,10 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
});
totalCountRef.current = totalCount;
const getRows = makeInfiniteScrollGetRows<TradeEdge>(
newRows,
dataRef,
totalCountRef,
load
load,
newRows
);
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {

View File

@ -432,6 +432,5 @@ export const DescriptionTransferTypeMapping: TransferTypeMap = {
TRANSFER_TYPE_TRANSFER_FUNDS_DISTRIBUTE: `Funds added to your general account to fulfil a transfer`,
TRANSFER_TYPE_CLEAR_ACCOUNT: `Market-related accounts emptied, and balances moved, because the market has closed`,
TRANSFER_TYPE_UNSPECIFIED: 'Default value, always invalid',
TRANSFER_TYPE_CHECKPOINT_BALANCE_RESTORE:
'When the network is restored from a checkpoint this sets the balances of parties',
TRANSFER_TYPE_CHECKPOINT_BALANCE_RESTORE: `Balances are being restored to the user's account following a checkpoint restart of the network`,
};