chore(ledger): remove ledger entries table, adjust download form (#4611)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
332cc302c3
commit
9ac199f59a
@ -1,59 +1,25 @@
|
|||||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||||
import { ledgerEntriesQuery } from '@vegaprotocol/mock';
|
import { partyAssetsQuery } from '@vegaprotocol/mock';
|
||||||
|
|
||||||
describe('Portfolio page', { tags: '@smoke' }, () => {
|
describe('Portfolio page', { tags: '@smoke' }, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockGQL((req) => {
|
cy.mockGQL((req) => {
|
||||||
aliasGQLQuery(req, 'LedgerEntries', ledgerEntriesQuery());
|
aliasGQLQuery(req, 'PartyAssets', partyAssetsQuery());
|
||||||
});
|
});
|
||||||
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.setVegaWallet();
|
cy.setVegaWallet();
|
||||||
});
|
});
|
||||||
describe('Ledger entries', () => {
|
describe('Ledger entries', () => {
|
||||||
it('List should be properly rendered', () => {
|
it('Download form should be properly rendered', () => {
|
||||||
|
// 7007-LEEN-001
|
||||||
cy.visit('/#/portfolio');
|
cy.visit('/#/portfolio');
|
||||||
cy.getByTestId('"Ledger entries"').click();
|
cy.getByTestId('"Ledger entries"').click();
|
||||||
const headers = [
|
|
||||||
'Sender',
|
|
||||||
'Account type',
|
|
||||||
'Market',
|
|
||||||
'Receiver',
|
|
||||||
'Account type',
|
|
||||||
'Market',
|
|
||||||
'Transfer type',
|
|
||||||
'Quantity',
|
|
||||||
'Asset',
|
|
||||||
'Sender account balance',
|
|
||||||
'Receiver account balance',
|
|
||||||
'Vega time',
|
|
||||||
];
|
|
||||||
cy.getByTestId('tab-ledger-entries').within(($headers) => {
|
cy.getByTestId('tab-ledger-entries').within(($headers) => {
|
||||||
cy.wrap($headers)
|
cy.wrap($headers)
|
||||||
.get('.ag-header-cell-text')
|
.getByTestId('ledger-download-button')
|
||||||
.each(($header, i) => {
|
.should('be.visible');
|
||||||
cy.wrap($header).should('have.text', headers[i]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
cy.get(
|
|
||||||
'[data-testid="tab-ledger-entries"] .ag-center-cols-container .ag-row'
|
|
||||||
).should('have.length', ledgerEntriesQuery().ledgerEntries.edges.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('account filters should be callable', () => {
|
|
||||||
cy.visit('/#/portfolio');
|
|
||||||
cy.getByTestId('"Ledger entries"').click();
|
|
||||||
cy.get('[role="columnheader"][col-id="fromAccountType"]').realHover();
|
|
||||||
cy.get(
|
|
||||||
'[role="columnheader"][col-id="fromAccountType"] .ag-header-cell-menu-button'
|
|
||||||
).click();
|
|
||||||
cy.get('fieldset.ag-simple-filter-body-wrapper')
|
|
||||||
.should('be.visible')
|
|
||||||
.within((fields) => {
|
|
||||||
cy.wrap(fields).find('label').should('have.length', 18);
|
|
||||||
});
|
|
||||||
cy.getByTestId('"Ledger entries"').click();
|
|
||||||
cy.get('fieldset.ag-simple-filter-body-wrapper').should('not.exist');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,23 +1,30 @@
|
|||||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { LedgerManager } from '@vegaprotocol/ledger';
|
import { LedgerExportForm } from '@vegaprotocol/ledger';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
import type { PartyAssetFieldsFragment } from '@vegaprotocol/assets';
|
||||||
import { create } from 'zustand';
|
import { usePartyAssetsQuery } from '@vegaprotocol/assets';
|
||||||
import { persist } from 'zustand/middleware';
|
|
||||||
|
|
||||||
export const LedgerContainer = () => {
|
export const LedgerContainer = () => {
|
||||||
|
const VEGA_URL = useEnvironment((store) => store.VEGA_URL);
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
|
const { data, loading } = usePartyAssetsQuery({
|
||||||
const gridStore = useLedgerStore((store) => store.gridStore);
|
variables: { partyId: pubKey || '' },
|
||||||
const updateGridStore = useLedgerStore((store) => store.updateGridStore);
|
skip: !pubKey,
|
||||||
|
|
||||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
|
||||||
updateGridStore(colState);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const assets = (data?.party?.accountsConnection?.edges ?? [])
|
||||||
|
.map<PartyAssetFieldsFragment>(
|
||||||
|
(item) => item?.node?.asset ?? ({} as PartyAssetFieldsFragment)
|
||||||
|
)
|
||||||
|
.reduce((aggr, item) => {
|
||||||
|
if ('id' in item && 'symbol' in item) {
|
||||||
|
aggr[item.id as string] = item.symbol as string;
|
||||||
|
}
|
||||||
|
return aggr;
|
||||||
|
}, {} as Record<string, string>);
|
||||||
|
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
return (
|
return (
|
||||||
<Splash>
|
<Splash>
|
||||||
@ -26,11 +33,31 @@ export const LedgerContainer = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <LedgerManager partyId={pubKey} gridProps={gridStoreCallbacks} />;
|
if (!VEGA_URL) {
|
||||||
};
|
return (
|
||||||
|
<Splash>
|
||||||
|
<p>{t('Environment not configured')}</p>
|
||||||
|
</Splash>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const useLedgerStore = create<DataGridSlice>()(
|
if (loading) {
|
||||||
persist(createDataGridSlice, {
|
return (
|
||||||
name: 'vega_ledger_store',
|
<div className="relative flex items-center justify-center w-full h-full">
|
||||||
})
|
<Loader />
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(assets).length) {
|
||||||
|
return (
|
||||||
|
<Splash>
|
||||||
|
<p>{t('No ledger entries to export')}</p>
|
||||||
|
</Splash>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LedgerExportForm partyId={pubKey} vegaUrl={VEGA_URL} assets={assets} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -24,3 +24,26 @@ query Assets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragment PartyAssetFields on Asset {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
symbol
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
query PartyAssets($partyId: ID!) {
|
||||||
|
party(id: $partyId) {
|
||||||
|
id
|
||||||
|
accountsConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
type
|
||||||
|
asset {
|
||||||
|
...PartyAssetFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
64
libs/assets/src/lib/__generated__/Assets.ts
generated
64
libs/assets/src/lib/__generated__/Assets.ts
generated
@ -10,6 +10,15 @@ export type AssetsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
|||||||
|
|
||||||
export type AssetsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string, status: Types.AssetStatus, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string } } } | null> | null } | null };
|
export type AssetsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string, status: Types.AssetStatus, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string } } } | null> | null } | null };
|
||||||
|
|
||||||
|
export type PartyAssetFieldsFragment = { __typename?: 'Asset', id: string, name: string, symbol: string, status: Types.AssetStatus };
|
||||||
|
|
||||||
|
export type PartyAssetsQueryVariables = Types.Exact<{
|
||||||
|
partyId: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type PartyAssetsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string, symbol: string, status: Types.AssetStatus } } } | null> | null } | null } | null };
|
||||||
|
|
||||||
export const AssetListFieldsFragmentDoc = gql`
|
export const AssetListFieldsFragmentDoc = gql`
|
||||||
fragment AssetListFields on Asset {
|
fragment AssetListFields on Asset {
|
||||||
id
|
id
|
||||||
@ -28,6 +37,14 @@ export const AssetListFieldsFragmentDoc = gql`
|
|||||||
status
|
status
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const PartyAssetFieldsFragmentDoc = gql`
|
||||||
|
fragment PartyAssetFields on Asset {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
symbol
|
||||||
|
status
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const AssetsDocument = gql`
|
export const AssetsDocument = gql`
|
||||||
query Assets {
|
query Assets {
|
||||||
assetsConnection {
|
assetsConnection {
|
||||||
@ -65,4 +82,49 @@ export function useAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<Ass
|
|||||||
}
|
}
|
||||||
export type AssetsQueryHookResult = ReturnType<typeof useAssetsQuery>;
|
export type AssetsQueryHookResult = ReturnType<typeof useAssetsQuery>;
|
||||||
export type AssetsLazyQueryHookResult = ReturnType<typeof useAssetsLazyQuery>;
|
export type AssetsLazyQueryHookResult = ReturnType<typeof useAssetsLazyQuery>;
|
||||||
export type AssetsQueryResult = Apollo.QueryResult<AssetsQuery, AssetsQueryVariables>;
|
export type AssetsQueryResult = Apollo.QueryResult<AssetsQuery, AssetsQueryVariables>;
|
||||||
|
export const PartyAssetsDocument = gql`
|
||||||
|
query PartyAssets($partyId: ID!) {
|
||||||
|
party(id: $partyId) {
|
||||||
|
id
|
||||||
|
accountsConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
type
|
||||||
|
asset {
|
||||||
|
...PartyAssetFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${PartyAssetFieldsFragmentDoc}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __usePartyAssetsQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `usePartyAssetsQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `usePartyAssetsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = usePartyAssetsQuery({
|
||||||
|
* variables: {
|
||||||
|
* partyId: // value for 'partyId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function usePartyAssetsQuery(baseOptions: Apollo.QueryHookOptions<PartyAssetsQuery, PartyAssetsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(PartyAssetsDocument, options);
|
||||||
|
}
|
||||||
|
export function usePartyAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PartyAssetsQuery, PartyAssetsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(PartyAssetsDocument, options);
|
||||||
|
}
|
||||||
|
export type PartyAssetsQueryHookResult = ReturnType<typeof usePartyAssetsQuery>;
|
||||||
|
export type PartyAssetsLazyQueryHookResult = ReturnType<typeof usePartyAssetsLazyQuery>;
|
||||||
|
export type PartyAssetsQueryResult = Apollo.QueryResult<PartyAssetsQuery, PartyAssetsQueryVariables>;
|
47
libs/assets/src/lib/party-assets.mock.ts
Normal file
47
libs/assets/src/lib/party-assets.mock.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import merge from 'lodash/merge';
|
||||||
|
import type { PartyAssetsQuery } from './__generated__/Assets';
|
||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
|
||||||
|
export const partyAssetsQuery = (
|
||||||
|
override?: PartialDeep<PartyAssetsQuery>
|
||||||
|
): PartyAssetsQuery => {
|
||||||
|
const defaultAssets: PartyAssetsQuery = {
|
||||||
|
party: {
|
||||||
|
__typename: 'Party',
|
||||||
|
id: 'partyId',
|
||||||
|
accountsConnection: {
|
||||||
|
edges: partyAccountFields.map((node) => ({
|
||||||
|
__typename: 'AccountEdge',
|
||||||
|
node,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return merge(defaultAssets, override);
|
||||||
|
};
|
||||||
|
|
||||||
|
const partyAccountFields = [
|
||||||
|
{
|
||||||
|
__typename: 'AccountBalance',
|
||||||
|
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||||
|
asset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: 'asset-id',
|
||||||
|
symbol: 'tEURO',
|
||||||
|
name: 'Euro',
|
||||||
|
status: Types.AssetStatus.STATUS_ENABLED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: 'AccountBalance',
|
||||||
|
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||||
|
asset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: 'asset-id-2',
|
||||||
|
symbol: 'tDAI',
|
||||||
|
name: 'DAI',
|
||||||
|
status: Types.AssetStatus.STATUS_ENABLED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as const;
|
@ -2,6 +2,7 @@
|
|||||||
export * from '../accounts/src/lib/accounts.mock';
|
export * from '../accounts/src/lib/accounts.mock';
|
||||||
export * from '../assets/src/lib/asset.mock';
|
export * from '../assets/src/lib/asset.mock';
|
||||||
export * from '../assets/src/lib/assets.mock';
|
export * from '../assets/src/lib/assets.mock';
|
||||||
|
export * from '../assets/src/lib/party-assets.mock';
|
||||||
export * from '../candles-chart/src/lib/candles.mock';
|
export * from '../candles-chart/src/lib/candles.mock';
|
||||||
export * from '../candles-chart/src/lib/chart.mock';
|
export * from '../candles-chart/src/lib/chart.mock';
|
||||||
export * from '../deal-ticket/src/hooks/estimate-order.mock';
|
export * from '../deal-ticket/src/hooks/estimate-order.mock';
|
||||||
@ -10,7 +11,6 @@ export * from '../environment/src/utils/node.mock';
|
|||||||
export * from '../environment/src/components/node-guard/node-guard.mock';
|
export * from '../environment/src/components/node-guard/node-guard.mock';
|
||||||
export * from '../fills/src/lib/fills.mock';
|
export * from '../fills/src/lib/fills.mock';
|
||||||
export * from '../proposals/src/lib/proposals-data-provider/proposals.mock';
|
export * from '../proposals/src/lib/proposals-data-provider/proposals.mock';
|
||||||
export * from '../ledger/src/lib/ledger-entries.mock';
|
|
||||||
export * from '../market-depth/src/lib/market-depth.mock';
|
export * from '../market-depth/src/lib/market-depth.mock';
|
||||||
export * from '../markets/src/lib/components/market-info/market-info.mock';
|
export * from '../markets/src/lib/components/market-info/market-info.mock';
|
||||||
export * from '../markets/src/lib/market-candles.mock';
|
export * from '../markets/src/lib/market-candles.mock';
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export * from './lib/ledger-manager';
|
export * from './lib/ledger-export-form';
|
||||||
export * from './lib/__generated__/LedgerEntries';
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
fragment LedgerEntry on AggregatedLedgerEntry {
|
|
||||||
vegaTime
|
|
||||||
quantity
|
|
||||||
assetId
|
|
||||||
transferType
|
|
||||||
toAccountType
|
|
||||||
toAccountMarketId
|
|
||||||
toAccountPartyId
|
|
||||||
toAccountBalance
|
|
||||||
fromAccountType
|
|
||||||
fromAccountMarketId
|
|
||||||
fromAccountPartyId
|
|
||||||
fromAccountBalance
|
|
||||||
}
|
|
||||||
|
|
||||||
query LedgerEntries(
|
|
||||||
$partyId: ID!
|
|
||||||
$pagination: Pagination
|
|
||||||
$dateRange: DateRange
|
|
||||||
$fromAccountType: [AccountType!]
|
|
||||||
$toAccountType: [AccountType!]
|
|
||||||
) {
|
|
||||||
ledgerEntries(
|
|
||||||
filter: {
|
|
||||||
FromAccountFilter: {
|
|
||||||
partyIds: [$partyId]
|
|
||||||
accountTypes: $fromAccountType
|
|
||||||
}
|
|
||||||
ToAccountFilter: { partyIds: [$partyId], accountTypes: $toAccountType }
|
|
||||||
}
|
|
||||||
pagination: $pagination
|
|
||||||
dateRange: $dateRange
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
...LedgerEntry
|
|
||||||
}
|
|
||||||
cursor
|
|
||||||
}
|
|
||||||
pageInfo {
|
|
||||||
startCursor
|
|
||||||
endCursor
|
|
||||||
hasNextPage
|
|
||||||
hasPreviousPage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
88
libs/ledger/src/lib/__generated__/LedgerEntries.ts
generated
88
libs/ledger/src/lib/__generated__/LedgerEntries.ts
generated
@ -1,88 +0,0 @@
|
|||||||
import * as Types from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
import { gql } from '@apollo/client';
|
|
||||||
import * as Apollo from '@apollo/client';
|
|
||||||
const defaultOptions = {} as const;
|
|
||||||
export type LedgerEntryFragment = { __typename?: 'AggregatedLedgerEntry', vegaTime: any, quantity: string, assetId?: string | null, transferType?: Types.TransferType | null, toAccountType?: Types.AccountType | null, toAccountMarketId?: string | null, toAccountPartyId?: string | null, toAccountBalance: string, fromAccountType?: Types.AccountType | null, fromAccountMarketId?: string | null, fromAccountPartyId?: string | null, fromAccountBalance: string };
|
|
||||||
|
|
||||||
export type LedgerEntriesQueryVariables = Types.Exact<{
|
|
||||||
partyId: Types.Scalars['ID'];
|
|
||||||
pagination?: Types.InputMaybe<Types.Pagination>;
|
|
||||||
dateRange?: Types.InputMaybe<Types.DateRange>;
|
|
||||||
fromAccountType?: Types.InputMaybe<Array<Types.AccountType> | Types.AccountType>;
|
|
||||||
toAccountType?: Types.InputMaybe<Array<Types.AccountType> | Types.AccountType>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type LedgerEntriesQuery = { __typename?: 'Query', ledgerEntries: { __typename?: 'AggregatedLedgerEntriesConnection', edges: Array<{ __typename?: 'AggregatedLedgerEntriesEdge', cursor: string, node: { __typename?: 'AggregatedLedgerEntry', vegaTime: any, quantity: string, assetId?: string | null, transferType?: Types.TransferType | null, toAccountType?: Types.AccountType | null, toAccountMarketId?: string | null, toAccountPartyId?: string | null, toAccountBalance: string, fromAccountType?: Types.AccountType | null, fromAccountMarketId?: string | null, fromAccountPartyId?: string | null, fromAccountBalance: string } } | null>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } };
|
|
||||||
|
|
||||||
export const LedgerEntryFragmentDoc = gql`
|
|
||||||
fragment LedgerEntry on AggregatedLedgerEntry {
|
|
||||||
vegaTime
|
|
||||||
quantity
|
|
||||||
assetId
|
|
||||||
transferType
|
|
||||||
toAccountType
|
|
||||||
toAccountMarketId
|
|
||||||
toAccountPartyId
|
|
||||||
toAccountBalance
|
|
||||||
fromAccountType
|
|
||||||
fromAccountMarketId
|
|
||||||
fromAccountPartyId
|
|
||||||
fromAccountBalance
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export const LedgerEntriesDocument = gql`
|
|
||||||
query LedgerEntries($partyId: ID!, $pagination: Pagination, $dateRange: DateRange, $fromAccountType: [AccountType!], $toAccountType: [AccountType!]) {
|
|
||||||
ledgerEntries(
|
|
||||||
filter: {FromAccountFilter: {partyIds: [$partyId], accountTypes: $fromAccountType}, ToAccountFilter: {partyIds: [$partyId], accountTypes: $toAccountType}}
|
|
||||||
pagination: $pagination
|
|
||||||
dateRange: $dateRange
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
...LedgerEntry
|
|
||||||
}
|
|
||||||
cursor
|
|
||||||
}
|
|
||||||
pageInfo {
|
|
||||||
startCursor
|
|
||||||
endCursor
|
|
||||||
hasNextPage
|
|
||||||
hasPreviousPage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${LedgerEntryFragmentDoc}`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useLedgerEntriesQuery__
|
|
||||||
*
|
|
||||||
* To run a query within a React component, call `useLedgerEntriesQuery` and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useLedgerEntriesQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
|
||||||
* you can use to render your UI.
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const { data, loading, error } = useLedgerEntriesQuery({
|
|
||||||
* variables: {
|
|
||||||
* partyId: // value for 'partyId'
|
|
||||||
* pagination: // value for 'pagination'
|
|
||||||
* dateRange: // value for 'dateRange'
|
|
||||||
* fromAccountType: // value for 'fromAccountType'
|
|
||||||
* toAccountType: // value for 'toAccountType'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useLedgerEntriesQuery(baseOptions: Apollo.QueryHookOptions<LedgerEntriesQuery, LedgerEntriesQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useQuery<LedgerEntriesQuery, LedgerEntriesQueryVariables>(LedgerEntriesDocument, options);
|
|
||||||
}
|
|
||||||
export function useLedgerEntriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<LedgerEntriesQuery, LedgerEntriesQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useLazyQuery<LedgerEntriesQuery, LedgerEntriesQueryVariables>(LedgerEntriesDocument, options);
|
|
||||||
}
|
|
||||||
export type LedgerEntriesQueryHookResult = ReturnType<typeof useLedgerEntriesQuery>;
|
|
||||||
export type LedgerEntriesLazyQueryHookResult = ReturnType<typeof useLedgerEntriesLazyQuery>;
|
|
||||||
export type LedgerEntriesQueryResult = Apollo.QueryResult<LedgerEntriesQuery, LedgerEntriesQueryVariables>;
|
|
@ -1,77 +0,0 @@
|
|||||||
import type { Asset } from '@vegaprotocol/assets';
|
|
||||||
import { assetsMapProvider } from '@vegaprotocol/assets';
|
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
|
||||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
|
||||||
import {
|
|
||||||
makeDataProvider,
|
|
||||||
makeDerivedDataProvider,
|
|
||||||
} from '@vegaprotocol/data-provider';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
LedgerEntriesQuery,
|
|
||||||
LedgerEntriesQueryVariables,
|
|
||||||
LedgerEntryFragment,
|
|
||||||
} from './__generated__/LedgerEntries';
|
|
||||||
import { LedgerEntriesDocument } from './__generated__/LedgerEntries';
|
|
||||||
|
|
||||||
export type LedgerEntry = LedgerEntryFragment & {
|
|
||||||
asset: Asset | null | undefined;
|
|
||||||
marketSender: Market | null | undefined;
|
|
||||||
marketReceiver: Market | null | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Edge = LedgerEntriesQuery['ledgerEntries']['edges'][number];
|
|
||||||
|
|
||||||
const isLedgerEntryEdge = (entry: Edge): entry is NonNullable<Edge> =>
|
|
||||||
entry !== null;
|
|
||||||
|
|
||||||
const getData = (responseData: LedgerEntriesQuery | null) => {
|
|
||||||
return (
|
|
||||||
responseData?.ledgerEntries?.edges
|
|
||||||
.filter(isLedgerEntryEdge)
|
|
||||||
.map((edge) => edge.node) || []
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ledgerEntriesOnlyProvider = makeDataProvider<
|
|
||||||
LedgerEntriesQuery,
|
|
||||||
ReturnType<typeof getData>,
|
|
||||||
never,
|
|
||||||
never,
|
|
||||||
LedgerEntriesQueryVariables
|
|
||||||
>({
|
|
||||||
query: LedgerEntriesDocument,
|
|
||||||
getData,
|
|
||||||
additionalContext: {
|
|
||||||
isEnlargedTimeout: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ledgerEntriesProvider = makeDerivedDataProvider<
|
|
||||||
LedgerEntry[],
|
|
||||||
never,
|
|
||||||
LedgerEntriesQueryVariables
|
|
||||||
>(
|
|
||||||
[
|
|
||||||
ledgerEntriesOnlyProvider,
|
|
||||||
(callback, client) => assetsMapProvider(callback, client, undefined),
|
|
||||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
|
||||||
],
|
|
||||||
(partsData) => {
|
|
||||||
const entries = partsData[0] as ReturnType<typeof getData>;
|
|
||||||
const assets = partsData[1] as Record<string, Asset>;
|
|
||||||
const markets = partsData[2] as Record<string, Market>;
|
|
||||||
return entries.map((entry) => {
|
|
||||||
const asset = entry.assetId
|
|
||||||
? (assets as Record<string, Asset>)[entry.assetId]
|
|
||||||
: null;
|
|
||||||
const marketSender = entry.fromAccountMarketId
|
|
||||||
? markets[entry.fromAccountMarketId]
|
|
||||||
: null;
|
|
||||||
const marketReceiver = entry.toAccountMarketId
|
|
||||||
? markets[entry.toAccountMarketId]
|
|
||||||
: null;
|
|
||||||
return { ...entry, asset, marketSender, marketReceiver };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,247 +0,0 @@
|
|||||||
import type {
|
|
||||||
LedgerEntriesQuery,
|
|
||||||
LedgerEntryFragment,
|
|
||||||
} from './__generated__/LedgerEntries';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
import type { PartialDeep } from 'type-fest';
|
|
||||||
import merge from 'lodash/merge';
|
|
||||||
|
|
||||||
export const ledgerEntriesQuery = (
|
|
||||||
override?: PartialDeep<LedgerEntriesQuery>
|
|
||||||
): LedgerEntriesQuery => {
|
|
||||||
const defaultResult: LedgerEntriesQuery = {
|
|
||||||
__typename: 'Query',
|
|
||||||
ledgerEntries: {
|
|
||||||
__typename: 'AggregatedLedgerEntriesConnection',
|
|
||||||
edges: ledgerEntries.map((node) => ({
|
|
||||||
__typename: 'AggregatedLedgerEntriesEdge',
|
|
||||||
node,
|
|
||||||
cursor: 'cursor-1',
|
|
||||||
})),
|
|
||||||
pageInfo: {
|
|
||||||
startCursor:
|
|
||||||
'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDE3OjI3OjU2LjczNDM2NFoifQ==',
|
|
||||||
endCursor:
|
|
||||||
'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDEzOjExOjE2LjU0NjM2M1oifQ==',
|
|
||||||
hasNextPage: false,
|
|
||||||
hasPreviousPage: false,
|
|
||||||
__typename: 'PageInfo',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return merge(defaultResult, override);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ledgerEntries: LedgerEntryFragment[] = [
|
|
||||||
{
|
|
||||||
vegaTime: '1669224476734364000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: 'asset-id',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
|
||||||
toAccountMarketId: 'market-1',
|
|
||||||
toAccountPartyId: 'network',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
fromAccountMarketId: 'market-0',
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669221452175594000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: 'asset-id-2',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
|
||||||
toAccountMarketId: 'market-0',
|
|
||||||
toAccountPartyId: 'network',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
fromAccountMarketId: 'market-2',
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209347054198000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: 'asset-id',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
|
||||||
toAccountMarketId: 'market-3',
|
|
||||||
toAccountPartyId: 'network',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
fromAccountMarketId: 'market-2',
|
|
||||||
fromAccountPartyId: 'sender party id',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209345512806000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: 'asset-id',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209316163397000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: 'asset-id-2',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209299051286000',
|
|
||||||
quantity: '1326783',
|
|
||||||
assetId: 'asset-id-2',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MTM_WIN,
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209151328614000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209117655380000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209082788024000',
|
|
||||||
quantity: '1326783',
|
|
||||||
assetId: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MTM_WIN,
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '1669209076546363000',
|
|
||||||
quantity: '0',
|
|
||||||
assetId: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '2022-11-24T13:36:42.13989Z',
|
|
||||||
quantity: '9078407730948615',
|
|
||||||
assetId: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_LOW,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
toAccountMarketId: null,
|
|
||||||
toAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
|
||||||
fromAccountMarketId:
|
|
||||||
'0942d767cb2cb5a795e14216e8e53c2b6f75e46dc1732c5aeda8a5aba4ad193d',
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
vegaTime: '2022-11-24T13:35:49.257039Z',
|
|
||||||
quantity: '263142253070974',
|
|
||||||
assetId: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_LOW,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
toAccountMarketId: null,
|
|
||||||
toAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
|
||||||
fromAccountMarketId:
|
|
||||||
'0942d767cb2cb5a795e14216e8e53c2b6f75e46dc1732c5aeda8a5aba4ad193d',
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '2022-11-24T12:41:22.054428Z',
|
|
||||||
quantity: '1000000000',
|
|
||||||
assetId: '4e4e80abff30cab933b8c4ac6befc618372eb76b2cbddc337eff0b4a3a4d25b8',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
|
||||||
toAccountMarketId: null,
|
|
||||||
toAccountPartyId: 'network',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
fromAccountMarketId: null,
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '2022-11-24T12:39:11.516154Z',
|
|
||||||
quantity: '1000000000000',
|
|
||||||
assetId: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
|
||||||
toAccountMarketId: null,
|
|
||||||
toAccountPartyId: 'network',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
fromAccountMarketId: null,
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '2022-11-24T12:37:26.832226Z',
|
|
||||||
quantity: '10000000000000000000000',
|
|
||||||
assetId: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
|
||||||
toAccountMarketId: null,
|
|
||||||
toAccountPartyId: 'network',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
fromAccountMarketId: null,
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
vegaTime: '2022-11-24T12:24:52.844901Z',
|
|
||||||
quantity: '49390000000000000000000',
|
|
||||||
assetId: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
|
||||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
|
||||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
|
||||||
toAccountMarketId: null,
|
|
||||||
toAccountPartyId: 'network',
|
|
||||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
|
||||||
fromAccountMarketId: null,
|
|
||||||
fromAccountPartyId:
|
|
||||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
|
||||||
__typename: 'AggregatedLedgerEntry',
|
|
||||||
toAccountBalance: '0',
|
|
||||||
fromAccountBalance: '0',
|
|
||||||
},
|
|
||||||
];
|
|
215
libs/ledger/src/lib/ledger-export-form.spec.tsx
Normal file
215
libs/ledger/src/lib/ledger-export-form.spec.tsx
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||||
|
import { createDownloadUrl, LedgerExportForm } from './ledger-export-form';
|
||||||
|
import { formatForInput, toNanoSeconds } from '@vegaprotocol/utils';
|
||||||
|
|
||||||
|
const vegaUrl = 'https://vega-url.co.uk/querystuff';
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
headers: { get: jest.fn() },
|
||||||
|
blob: () => '',
|
||||||
|
};
|
||||||
|
global.fetch = jest.fn().mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const assetsMock = {
|
||||||
|
['a'.repeat(64)]: 'symbol asset-id',
|
||||||
|
['b'.repeat(64)]: 'symbol asset-id-2',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('LedgerExportForm', () => {
|
||||||
|
const partyId = 'c'.repeat(64);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers().setSystemTime(new Date('2023-08-10T10:10:10.000Z'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be properly rendered', async () => {
|
||||||
|
render(
|
||||||
|
<LedgerExportForm
|
||||||
|
partyId={partyId}
|
||||||
|
vegaUrl={vegaUrl}
|
||||||
|
assets={assetsMock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('symbol asset-id')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// userEvent does not work with faked timers
|
||||||
|
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||||
|
|
||||||
|
expect(screen.getByTestId('download-spinner')).toBeInTheDocument();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||||
|
Object.keys(assetsMock)[0]
|
||||||
|
}&dateRange.startTimestamp=1691057410000000000`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assetID should be properly change request url', async () => {
|
||||||
|
render(
|
||||||
|
<LedgerExportForm
|
||||||
|
partyId={partyId}
|
||||||
|
vegaUrl={vegaUrl}
|
||||||
|
assets={assetsMock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('symbol asset-id')).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByTestId('select-ledger-asset'), {
|
||||||
|
target: { value: Object.keys(assetsMock)[1] },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('symbol asset-id-2')).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||||
|
|
||||||
|
expect(screen.getByTestId('download-spinner')).toBeInTheDocument();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||||
|
Object.keys(assetsMock)[1]
|
||||||
|
}&dateRange.startTimestamp=1691057410000000000`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('date-from should properly change request url', async () => {
|
||||||
|
const newDate = new Date(1691230210000);
|
||||||
|
render(
|
||||||
|
<LedgerExportForm
|
||||||
|
partyId={partyId}
|
||||||
|
vegaUrl={vegaUrl}
|
||||||
|
assets={assetsMock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByLabelText('Date from')).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText('Date from'), {
|
||||||
|
target: { value: formatForInput(newDate) },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('date-from')).toHaveValue(
|
||||||
|
`${formatForInput(newDate)}.000`
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||||
|
|
||||||
|
expect(screen.getByTestId('download-spinner')).toBeInTheDocument();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||||
|
Object.keys(assetsMock)[0]
|
||||||
|
}&dateRange.startTimestamp=${toNanoSeconds(newDate)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('date-to should properly change request url', async () => {
|
||||||
|
const newDate = new Date(1691230210000);
|
||||||
|
render(
|
||||||
|
<LedgerExportForm
|
||||||
|
partyId={partyId}
|
||||||
|
vegaUrl={vegaUrl}
|
||||||
|
assets={assetsMock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Date to')).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText('Date to'), {
|
||||||
|
target: { value: formatForInput(newDate) },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('date-to')).toHaveValue(
|
||||||
|
`${formatForInput(newDate)}.000`
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(global.fetch).toHaveBeenCalledWith(
|
||||||
|
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||||
|
Object.keys(assetsMock)[0]
|
||||||
|
}&dateRange.startTimestamp=1691057410000000000&dateRange.endTimestamp=${toNanoSeconds(
|
||||||
|
newDate
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDownloadUrl', () => {
|
||||||
|
const fromTimestamp = 1690848000000;
|
||||||
|
const toTimestamp = 1691107200000;
|
||||||
|
const args = {
|
||||||
|
protohost: 'https://vega-url.co.uk',
|
||||||
|
partyId: 'a'.repeat(64),
|
||||||
|
assetId: 'b'.repeat(64),
|
||||||
|
dateFrom: new Date(fromTimestamp).toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
it('formats url with without an end date', () => {
|
||||||
|
expect(createDownloadUrl(args)).toEqual(
|
||||||
|
`${args.protohost}/api/v2/ledgerentry/export?partyId=${
|
||||||
|
args.partyId
|
||||||
|
}&assetId=${args.assetId}&dateRange.startTimestamp=${toNanoSeconds(
|
||||||
|
args.dateFrom
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats url with with an end date', () => {
|
||||||
|
const dateTo = new Date(toTimestamp).toISOString();
|
||||||
|
expect(
|
||||||
|
createDownloadUrl({
|
||||||
|
...args,
|
||||||
|
dateTo,
|
||||||
|
})
|
||||||
|
).toEqual(
|
||||||
|
`${args.protohost}/api/v2/ledgerentry/export?partyId=${
|
||||||
|
args.partyId
|
||||||
|
}&assetId=${args.assetId}&dateRange.startTimestamp=${toNanoSeconds(
|
||||||
|
args.dateFrom
|
||||||
|
)}&dateRange.endTimestamp=${toNanoSeconds(dateTo)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if invalid args are provided', () => {
|
||||||
|
// invalid url
|
||||||
|
expect(() => {
|
||||||
|
// @ts-ignore override z.infer type
|
||||||
|
createDownloadUrl({ ...args, protohost: 'foo' });
|
||||||
|
}).toThrow();
|
||||||
|
|
||||||
|
// invalid partyId
|
||||||
|
expect(() => {
|
||||||
|
// @ts-ignore override z.infer type
|
||||||
|
createDownloadUrl({ ...args, partyId: 'z'.repeat(64) });
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
});
|
172
libs/ledger/src/lib/ledger-export-form.tsx
Normal file
172
libs/ledger/src/lib/ledger-export-form.tsx
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Loader,
|
||||||
|
TradingFormGroup,
|
||||||
|
TradingInput,
|
||||||
|
TradingSelect,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { toNanoSeconds, VEGA_ID_REGEX } from '@vegaprotocol/utils';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||||
|
import { formatForInput } from '@vegaprotocol/utils';
|
||||||
|
import { subDays } from 'date-fns';
|
||||||
|
|
||||||
|
const DEFAULT_EXPORT_FILE_NAME = 'ledger_entries.csv';
|
||||||
|
|
||||||
|
const getProtoHost = (vegaurl: string) => {
|
||||||
|
const loc = new URL(vegaurl);
|
||||||
|
return `${loc.protocol}//${loc.host}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadSchema = z.object({
|
||||||
|
protohost: z.string().url().nonempty(),
|
||||||
|
partyId: z.string().regex(VEGA_ID_REGEX).nonempty(),
|
||||||
|
assetId: z.string().regex(VEGA_ID_REGEX).nonempty(),
|
||||||
|
dateFrom: z.string().nonempty(),
|
||||||
|
dateTo: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createDownloadUrl = (args: z.infer<typeof downloadSchema>) => {
|
||||||
|
// check args from form inputs
|
||||||
|
downloadSchema.parse(args);
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('partyId', args.partyId);
|
||||||
|
params.append('assetId', args.assetId);
|
||||||
|
params.append('dateRange.startTimestamp', toNanoSeconds(args.dateFrom));
|
||||||
|
|
||||||
|
if (args.dateTo) {
|
||||||
|
params.append('dateRange.endTimestamp', toNanoSeconds(args.dateTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(args.protohost);
|
||||||
|
url.pathname = '/api/v2/ledgerentry/export';
|
||||||
|
url.search = params.toString();
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
partyId: string;
|
||||||
|
vegaUrl: string;
|
||||||
|
assets: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
|
||||||
|
const now = useRef(new Date());
|
||||||
|
const [dateFrom, setDateFrom] = useState(() => {
|
||||||
|
return formatForInput(subDays(now.current, 7));
|
||||||
|
});
|
||||||
|
const [dateTo, setDateTo] = useState('');
|
||||||
|
const maxFromDate = formatForInput(new Date(dateTo || now.current));
|
||||||
|
const maxToDate = formatForInput(now.current);
|
||||||
|
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
const [assetId, setAssetId] = useState(Object.keys(assets)[0]);
|
||||||
|
const protohost = getProtoHost(vegaUrl);
|
||||||
|
const disabled = Boolean(!assetId || isDownloading);
|
||||||
|
|
||||||
|
const assetDropDown = (
|
||||||
|
<TradingSelect
|
||||||
|
id="select-ledger-asset"
|
||||||
|
value={assetId}
|
||||||
|
onChange={(e) => {
|
||||||
|
setAssetId(e.target.value);
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
data-testid="select-ledger-asset"
|
||||||
|
disabled={isDownloading}
|
||||||
|
>
|
||||||
|
{Object.keys(assets).map((assetKey) => (
|
||||||
|
<option key={assetKey} value={assetKey}>
|
||||||
|
{assets[assetKey]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</TradingSelect>
|
||||||
|
);
|
||||||
|
|
||||||
|
const startDownload = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
try {
|
||||||
|
const link = createDownloadUrl({
|
||||||
|
protohost,
|
||||||
|
partyId,
|
||||||
|
assetId,
|
||||||
|
dateFrom,
|
||||||
|
dateTo,
|
||||||
|
});
|
||||||
|
setIsDownloading(true);
|
||||||
|
const resp = await fetch(link);
|
||||||
|
const { headers } = resp;
|
||||||
|
const nameHeader = headers.get('content-disposition');
|
||||||
|
const filename = nameHeader?.split('=').pop() ?? DEFAULT_EXPORT_FILE_NAME;
|
||||||
|
const blob = await resp.blob();
|
||||||
|
if (blob) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
localLoggerFactory({ application: 'ledger' }).error('Download file', err);
|
||||||
|
} finally {
|
||||||
|
setIsDownloading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!protohost || Object.keys(assets).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={startDownload} className="p-4 w-[350px]">
|
||||||
|
<h2 className="mb-4">{t('Export ledger entries')}</h2>
|
||||||
|
<TradingFormGroup label={t('Select asset')} labelFor="asset">
|
||||||
|
{assetDropDown}
|
||||||
|
</TradingFormGroup>
|
||||||
|
<TradingFormGroup label={t('Date from')} labelFor="date-from">
|
||||||
|
<TradingInput
|
||||||
|
type="datetime-local"
|
||||||
|
data-testid="date-from"
|
||||||
|
id="date-from"
|
||||||
|
value={dateFrom}
|
||||||
|
onChange={(e) => setDateFrom(e.target.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
max={maxFromDate}
|
||||||
|
/>
|
||||||
|
</TradingFormGroup>
|
||||||
|
<TradingFormGroup label={t('Date to')} labelFor="date-to">
|
||||||
|
<TradingInput
|
||||||
|
type="datetime-local"
|
||||||
|
data-testid="date-to"
|
||||||
|
id="date-to"
|
||||||
|
value={dateTo}
|
||||||
|
onChange={(e) => setDateTo(e.target.value)}
|
||||||
|
disabled={disabled}
|
||||||
|
max={maxToDate}
|
||||||
|
/>
|
||||||
|
</TradingFormGroup>
|
||||||
|
<div className="relative text-sm" title={t('Download all to .csv file')}>
|
||||||
|
{isDownloading && (
|
||||||
|
<div
|
||||||
|
className="absolute flex items-center justify-center w-full h-full"
|
||||||
|
data-testid="download-spinner"
|
||||||
|
>
|
||||||
|
<Loader size="small" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
fill
|
||||||
|
disabled={disabled}
|
||||||
|
type="submit"
|
||||||
|
data-testid="ledger-download-button"
|
||||||
|
>
|
||||||
|
{t('Download')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
@ -1,85 +0,0 @@
|
|||||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { LedgerExportLink } from './ledger-export-link';
|
|
||||||
import * as Types from '@vegaprotocol/types';
|
|
||||||
import { ledgerEntries } from './ledger-entries.mock';
|
|
||||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
|
||||||
|
|
||||||
const VEGA_URL = 'https://vega-url.co.uk/querystuff';
|
|
||||||
const mockEnvironment = jest.fn(() => VEGA_URL);
|
|
||||||
jest.mock('@vegaprotocol/environment', () => ({
|
|
||||||
useEnvironment: jest.fn(() => mockEnvironment()),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const asset = {
|
|
||||||
id: 'assetID',
|
|
||||||
name: 'assetName',
|
|
||||||
symbol: 'assetSymbol',
|
|
||||||
decimals: 1,
|
|
||||||
quantum: '1',
|
|
||||||
status: Types.AssetStatus,
|
|
||||||
source: {
|
|
||||||
__typename: 'ERC20',
|
|
||||||
contractAddress: 'contractAddres',
|
|
||||||
lifetimeLimit: 'lifetimeLimit',
|
|
||||||
withdrawThreshold: 'withdraw',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('LedgerExportLink', () => {
|
|
||||||
const partyId = 'partyId';
|
|
||||||
const entries = ledgerEntries.map((entry) => {
|
|
||||||
return {
|
|
||||||
...entry,
|
|
||||||
asset: entry.assetId
|
|
||||||
? {
|
|
||||||
...asset,
|
|
||||||
id: entry.assetId,
|
|
||||||
name: `name ${entry.assetId}`,
|
|
||||||
symbol: `symbol ${entry.assetId}`,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
marketSender: null,
|
|
||||||
marketReceiver: null,
|
|
||||||
} as LedgerEntry;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be properly rendered', async () => {
|
|
||||||
render(<LedgerExportLink partyId={partyId} entries={entries} />);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByRole('link')).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole('link')).toHaveAttribute(
|
|
||||||
'href',
|
|
||||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=asset-id`
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
screen.getByRole('button', { name: /^symbol asset-id/ })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should be properly change link url', async () => {
|
|
||||||
render(<LedgerExportLink partyId={partyId} entries={entries} />);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.getByRole('button', { name: /^symbol asset-id/ })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
act(() => {
|
|
||||||
userEvent.click(screen.getByRole('button', { name: /^symbol asset-id/ }));
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
act(() => {
|
|
||||||
userEvent.click(
|
|
||||||
screen.getByRole('menuitem', { name: /^symbol asset-id-2/ })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByRole('link')).toHaveAttribute(
|
|
||||||
'href',
|
|
||||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=asset-id-2`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,71 +0,0 @@
|
|||||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Link,
|
|
||||||
Button,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
|
|
||||||
const getProtoHost = (vegaurl: string) => {
|
|
||||||
const loc = new URL(vegaurl);
|
|
||||||
return `${loc.protocol}//${loc.host}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LedgerExportLink = ({
|
|
||||||
partyId,
|
|
||||||
entries,
|
|
||||||
}: {
|
|
||||||
partyId: string;
|
|
||||||
entries: LedgerEntry[];
|
|
||||||
}) => {
|
|
||||||
const assets = entries.reduce((aggr, item) => {
|
|
||||||
if (item.asset && !(item.asset.id in aggr)) {
|
|
||||||
aggr[item.asset.id] = item.asset.symbol;
|
|
||||||
}
|
|
||||||
return aggr;
|
|
||||||
}, {} as Record<string, string>);
|
|
||||||
const [assetId, setAssetId] = useState(Object.keys(assets)[0]);
|
|
||||||
const VEGA_URL = useEnvironment((store) => store.VEGA_URL);
|
|
||||||
const protohost = VEGA_URL ? getProtoHost(VEGA_URL) : '';
|
|
||||||
|
|
||||||
const assetDropDown = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<DropdownMenu
|
|
||||||
trigger={<DropdownMenuTrigger>{assets[assetId]}</DropdownMenuTrigger>}
|
|
||||||
>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
{Object.keys(assets).map((assetKey) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={assetKey}
|
|
||||||
onSelect={() => setAssetId(assetKey)}
|
|
||||||
>
|
|
||||||
{assets[assetKey]}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
}, [assetId, assets]);
|
|
||||||
|
|
||||||
if (!protohost || !entries || entries.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="flex shrink items-stretch gap-2 p-2">
|
|
||||||
<div className="flex items-center">Export all</div>
|
|
||||||
{assetDropDown}
|
|
||||||
<Link
|
|
||||||
className="text-sm"
|
|
||||||
title={t('Download all to .csv file')}
|
|
||||||
href={`${protohost}/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${assetId}`}
|
|
||||||
>
|
|
||||||
<Button size="sm">{t('Download')}</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,73 +0,0 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import type * as Schema from '@vegaprotocol/types';
|
|
||||||
import type { FilterChangedEvent } from 'ag-grid-community';
|
|
||||||
import { useCallback, useState, useMemo } from 'react';
|
|
||||||
import { subDays, formatRFC3339 } from 'date-fns';
|
|
||||||
import { ledgerEntriesProvider } from './ledger-entries-data-provider';
|
|
||||||
import type { LedgerEntriesQueryVariables } from './__generated__/LedgerEntries';
|
|
||||||
import { LedgerTable } from './ledger-table';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import type * as Types from '@vegaprotocol/types';
|
|
||||||
import { LedgerExportLink } from './ledger-export-link';
|
|
||||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
|
||||||
|
|
||||||
export interface Filter {
|
|
||||||
vegaTime?: {
|
|
||||||
value: Schema.DateRange;
|
|
||||||
};
|
|
||||||
fromAccountType?: { value: Types.AccountType[] };
|
|
||||||
toAccountType?: { value: Types.AccountType[] };
|
|
||||||
}
|
|
||||||
const defaultFilter = {
|
|
||||||
vegaTime: {
|
|
||||||
value: { start: formatRFC3339(subDays(Date.now(), 7)) },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LedgerManager = ({
|
|
||||||
partyId,
|
|
||||||
gridProps,
|
|
||||||
}: {
|
|
||||||
partyId: string;
|
|
||||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
|
||||||
}) => {
|
|
||||||
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
|
||||||
|
|
||||||
const variables = useMemo<LedgerEntriesQueryVariables>(
|
|
||||||
() => ({
|
|
||||||
partyId,
|
|
||||||
dateRange: filter?.vegaTime?.value,
|
|
||||||
pagination: {
|
|
||||||
first: 10,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[partyId, filter?.vegaTime?.value]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, error } = useDataProvider({
|
|
||||||
dataProvider: ledgerEntriesProvider,
|
|
||||||
variables,
|
|
||||||
skip: !variables.partyId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onFilterChanged = useCallback(
|
|
||||||
(event: FilterChangedEvent) => {
|
|
||||||
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
|
|
||||||
setFilter(updatedFilter);
|
|
||||||
gridProps.onFilterChanged(event);
|
|
||||||
},
|
|
||||||
[gridProps]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-full relative">
|
|
||||||
<LedgerTable
|
|
||||||
rowData={data}
|
|
||||||
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
|
||||||
{...gridProps}
|
|
||||||
onFilterChanged={onFilterChanged}
|
|
||||||
/>
|
|
||||||
{data && <LedgerExportLink entries={data} partyId={partyId} />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,215 +0,0 @@
|
|||||||
import {
|
|
||||||
addDecimalsFormatNumber,
|
|
||||||
fromNanoSeconds,
|
|
||||||
getDateTimeFormat,
|
|
||||||
truncateByChars,
|
|
||||||
} from '@vegaprotocol/utils';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import type {
|
|
||||||
VegaValueFormatterParams,
|
|
||||||
TypedDataAgGrid,
|
|
||||||
} from '@vegaprotocol/datagrid';
|
|
||||||
import {
|
|
||||||
AgGridLazy as AgGrid,
|
|
||||||
DateRangeFilter,
|
|
||||||
MarketNameCell,
|
|
||||||
SetFilter,
|
|
||||||
} from '@vegaprotocol/datagrid';
|
|
||||||
import type * as Types from '@vegaprotocol/types';
|
|
||||||
import type { ColDef } from 'ag-grid-community';
|
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
|
||||||
import {
|
|
||||||
AccountTypeMapping,
|
|
||||||
DescriptionTransferTypeMapping,
|
|
||||||
TransferTypeMapping,
|
|
||||||
} from '@vegaprotocol/types';
|
|
||||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { formatRFC3339, subDays } from 'date-fns';
|
|
||||||
|
|
||||||
export const TransferTooltipCellComponent = ({
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
value: Types.TransferType;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<p className="max-w-sm px-4 py-2 z-20 rounded text-sm break-word">
|
|
||||||
{value ? DescriptionTransferTypeMapping[value] : ''}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultValue = { start: formatRFC3339(subDays(Date.now(), 7)) };
|
|
||||||
const dateRangeFilterParams = {
|
|
||||||
maxNextDays: 0,
|
|
||||||
defaultValue,
|
|
||||||
};
|
|
||||||
const defaultColDef = {
|
|
||||||
resizable: true,
|
|
||||||
sortable: true,
|
|
||||||
tooltipComponent: TransferTooltipCellComponent,
|
|
||||||
filterParams: {
|
|
||||||
...dateRangeFilterParams,
|
|
||||||
buttons: ['reset'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
|
||||||
|
|
||||||
export const LedgerTable = (props: LedgerEntryProps) => {
|
|
||||||
const columnDefs = useMemo<ColDef[]>(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
headerName: t('Sender'),
|
|
||||||
field: 'fromAccountPartyId',
|
|
||||||
cellRenderer: ({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
|
|
||||||
truncateByChars(value || ''),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Account type'),
|
|
||||||
filter: SetFilter,
|
|
||||||
filterParams: {
|
|
||||||
set: AccountTypeMapping,
|
|
||||||
},
|
|
||||||
field: 'fromAccountType',
|
|
||||||
cellRenderer: ({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
|
|
||||||
value ? AccountTypeMapping[value] : '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Market'),
|
|
||||||
field: 'marketSender.tradableInstrument.instrument.code',
|
|
||||||
cellRenderer: ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<
|
|
||||||
LedgerEntry,
|
|
||||||
'marketSender.tradableInstrument.instrument.code'
|
|
||||||
>) =>
|
|
||||||
value && (
|
|
||||||
<MarketNameCell value={value} data={data?.marketSender as Market} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Receiver'),
|
|
||||||
field: 'toAccountPartyId',
|
|
||||||
cellRenderer: ({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
|
|
||||||
truncateByChars(value || ''),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Account type'),
|
|
||||||
filter: SetFilter,
|
|
||||||
filterParams: {
|
|
||||||
set: AccountTypeMapping,
|
|
||||||
},
|
|
||||||
field: 'toAccountType',
|
|
||||||
cellRenderer: ({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
|
|
||||||
value ? AccountTypeMapping[value] : '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Market'),
|
|
||||||
field: 'marketReceiver.tradableInstrument.instrument.code',
|
|
||||||
cellRenderer: ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<
|
|
||||||
LedgerEntry,
|
|
||||||
'marketReceiver.tradableInstrument.instrument.code'
|
|
||||||
>) =>
|
|
||||||
value && (
|
|
||||||
<MarketNameCell
|
|
||||||
value={value}
|
|
||||||
data={data?.marketReceiver as Market}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Transfer type'),
|
|
||||||
field: 'transferType',
|
|
||||||
tooltipField: 'transferType',
|
|
||||||
filter: SetFilter,
|
|
||||||
filterParams: {
|
|
||||||
set: TransferTypeMapping,
|
|
||||||
},
|
|
||||||
valueFormatter: ({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
|
|
||||||
value ? TransferTypeMapping[value] : '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Quantity'),
|
|
||||||
field: 'quantity',
|
|
||||||
valueFormatter: ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
|
|
||||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
|
||||||
return value
|
|
||||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
|
||||||
: '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Asset'),
|
|
||||||
field: 'assetId',
|
|
||||||
valueFormatter: ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
|
|
||||||
data?.asset?.symbol || '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Sender account balance'),
|
|
||||||
field: 'fromAccountBalance',
|
|
||||||
valueFormatter: ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
|
|
||||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
|
||||||
return value
|
|
||||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
|
||||||
: '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Receiver account balance'),
|
|
||||||
field: 'toAccountBalance',
|
|
||||||
valueFormatter: ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
|
|
||||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
|
||||||
return value
|
|
||||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
|
||||||
: '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Vega time'),
|
|
||||||
field: 'vegaTime',
|
|
||||||
valueFormatter: ({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
|
|
||||||
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-',
|
|
||||||
filterParams: dateRangeFilterParams,
|
|
||||||
filter: DateRangeFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<AgGrid
|
|
||||||
tooltipShowDelay={500}
|
|
||||||
defaultColDef={defaultColDef}
|
|
||||||
columnDefs={columnDefs}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
2
libs/types/src/__generated__/types.ts
generated
2
libs/types/src/__generated__/types.ts
generated
@ -4414,6 +4414,8 @@ export type StopOrderFilter = {
|
|||||||
dateRange?: InputMaybe<DateRange>;
|
dateRange?: InputMaybe<DateRange>;
|
||||||
/** Zero or more expiry strategies to filter by */
|
/** Zero or more expiry strategies to filter by */
|
||||||
expiryStrategy?: InputMaybe<Array<StopOrderExpiryStrategy>>;
|
expiryStrategy?: InputMaybe<Array<StopOrderExpiryStrategy>>;
|
||||||
|
/** Filter for live stop orders only */
|
||||||
|
liveOnly?: InputMaybe<Scalars['Boolean']>;
|
||||||
/** Zero or more market IDs to filter by */
|
/** Zero or more market IDs to filter by */
|
||||||
markets?: InputMaybe<Array<Scalars['ID']>>;
|
markets?: InputMaybe<Array<Scalars['ID']>>;
|
||||||
/** Zero or more party IDs to filter by */
|
/** Zero or more party IDs to filter by */
|
||||||
|
@ -15,8 +15,9 @@ export const ethereumAddress = (value: string) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const VEGA_ID_REGEX = /^[A-Fa-f0-9]{64}$/i;
|
||||||
export const vegaPublicKey = (value: string) => {
|
export const vegaPublicKey = (value: string) => {
|
||||||
if (!/^[A-Fa-f0-9]{64}$/i.test(value)) {
|
if (!VEGA_ID_REGEX.test(value)) {
|
||||||
return t('Invalid Vega key');
|
return t('Invalid Vega key');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
11
specs/7007-LEEN-ledger-entries.md
Normal file
11
specs/7007-LEEN-ledger-entries.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Ledger entries
|
||||||
|
|
||||||
|
## Ledger entries tab in portfolio
|
||||||
|
|
||||||
|
When I enter on ledger entries tab in portfolio page
|
||||||
|
|
||||||
|
- **Must** see a form for download report file (<a name="7007-LEEN-001" href="#7007-LEEN-001">7007-LEEN-001</a>)
|
||||||
|
- in the form **Must** see a dropdown for select an asset, in which reports will be downloaded (<a name="7007-LEEN-002" href="#7007-LEEN-002">7007-LEEN-002</a>)
|
||||||
|
- in the form **Must** see inputs for select time period, in which reports will be downloaded (<a name="7007-LEEN-003" href="#7007-LEEN-003">7007-LEEN-003</a>)
|
||||||
|
- default preselected period **Must** be the last 7 days (<a name="7007-LEEN-004" href="#7007-LEEN-004">7007-LEEN-004</a>)
|
||||||
|
- during download a loader component **Must** be visible and all interactive elements in the form **Must** be disabled (<a name="7007-LEEN-005" href="#7007-LEEN-005">7007-LEEN-005</a>)
|
Loading…
Reference in New Issue
Block a user