From 9ac199f59a0ba07237daefe7a13a7a052106348c Mon Sep 17 00:00:00 2001 From: Maciek Date: Tue, 29 Aug 2023 15:57:02 +0200 Subject: [PATCH] chore(ledger): remove ledger entries table, adjust download form (#4611) Co-authored-by: Matthew Russell --- .../src/integration/trading-portfolio.cy.ts | 48 +--- .../ledger-container/ledger-container.tsx | 67 +++-- libs/assets/src/lib/Assets.graphql | 23 ++ libs/assets/src/lib/__generated__/Assets.ts | 64 ++++- libs/assets/src/lib/party-assets.mock.ts | 47 ++++ libs/cypress/mock.ts | 2 +- libs/ledger/src/index.ts | 3 +- libs/ledger/src/lib/LedgerEntries.graphql | 47 ---- .../src/lib/__generated__/LedgerEntries.ts | 88 ------- .../src/lib/ledger-entries-data-provider.ts | 77 ------ libs/ledger/src/lib/ledger-entries.mock.ts | 247 ------------------ .../src/lib/ledger-export-form.spec.tsx | 215 +++++++++++++++ libs/ledger/src/lib/ledger-export-form.tsx | 172 ++++++++++++ .../src/lib/ledger-export-link.spec.tsx | 85 ------ libs/ledger/src/lib/ledger-export-link.tsx | 71 ----- libs/ledger/src/lib/ledger-manager.tsx | 73 ------ libs/ledger/src/lib/ledger-table.tsx | 215 --------------- libs/types/src/__generated__/types.ts | 2 + libs/utils/src/lib/validate/common.ts | 3 +- specs/7007-LEEN-ledger-entries.md | 11 + 20 files changed, 591 insertions(+), 969 deletions(-) create mode 100644 libs/assets/src/lib/party-assets.mock.ts delete mode 100644 libs/ledger/src/lib/LedgerEntries.graphql delete mode 100644 libs/ledger/src/lib/__generated__/LedgerEntries.ts delete mode 100644 libs/ledger/src/lib/ledger-entries-data-provider.ts delete mode 100644 libs/ledger/src/lib/ledger-entries.mock.ts create mode 100644 libs/ledger/src/lib/ledger-export-form.spec.tsx create mode 100644 libs/ledger/src/lib/ledger-export-form.tsx delete mode 100644 libs/ledger/src/lib/ledger-export-link.spec.tsx delete mode 100644 libs/ledger/src/lib/ledger-export-link.tsx delete mode 100644 libs/ledger/src/lib/ledger-manager.tsx delete mode 100644 libs/ledger/src/lib/ledger-table.tsx create mode 100644 specs/7007-LEEN-ledger-entries.md diff --git a/apps/trading-e2e/src/integration/trading-portfolio.cy.ts b/apps/trading-e2e/src/integration/trading-portfolio.cy.ts index 6fe9208c9..d9789117d 100644 --- a/apps/trading-e2e/src/integration/trading-portfolio.cy.ts +++ b/apps/trading-e2e/src/integration/trading-portfolio.cy.ts @@ -1,59 +1,25 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress'; -import { ledgerEntriesQuery } from '@vegaprotocol/mock'; +import { partyAssetsQuery } from '@vegaprotocol/mock'; describe('Portfolio page', { tags: '@smoke' }, () => { beforeEach(() => { - cy.mockTradingPage(); cy.mockGQL((req) => { - aliasGQLQuery(req, 'LedgerEntries', ledgerEntriesQuery()); + aliasGQLQuery(req, 'PartyAssets', partyAssetsQuery()); }); + cy.mockTradingPage(); cy.mockSubscription(); cy.setVegaWallet(); }); describe('Ledger entries', () => { - it('List should be properly rendered', () => { + it('Download form should be properly rendered', () => { + // 7007-LEEN-001 cy.visit('/#/portfolio'); 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.wrap($headers) - .get('.ag-header-cell-text') - .each(($header, i) => { - cy.wrap($header).should('have.text', headers[i]); - }); + .getByTestId('ledger-download-button') + .should('be.visible'); }); - 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'); }); }); }); diff --git a/apps/trading/components/ledger-container/ledger-container.tsx b/apps/trading/components/ledger-container/ledger-container.tsx index 25977711d..36bddc043 100644 --- a/apps/trading/components/ledger-container/ledger-container.tsx +++ b/apps/trading/components/ledger-container/ledger-container.tsx @@ -1,23 +1,30 @@ -import { useDataGridEvents } from '@vegaprotocol/datagrid'; import { t } from '@vegaprotocol/i18n'; -import { LedgerManager } from '@vegaprotocol/ledger'; -import { Splash } from '@vegaprotocol/ui-toolkit'; +import { LedgerExportForm } from '@vegaprotocol/ledger'; +import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import type { DataGridSlice } from '../../stores/datagrid-store-slice'; -import { createDataGridSlice } from '../../stores/datagrid-store-slice'; -import { create } from 'zustand'; -import { persist } from 'zustand/middleware'; +import { useEnvironment } from '@vegaprotocol/environment'; +import type { PartyAssetFieldsFragment } from '@vegaprotocol/assets'; +import { usePartyAssetsQuery } from '@vegaprotocol/assets'; export const LedgerContainer = () => { + const VEGA_URL = useEnvironment((store) => store.VEGA_URL); const { pubKey } = useVegaWallet(); - - const gridStore = useLedgerStore((store) => store.gridStore); - const updateGridStore = useLedgerStore((store) => store.updateGridStore); - - const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => { - updateGridStore(colState); + const { data, loading } = usePartyAssetsQuery({ + variables: { partyId: pubKey || '' }, + skip: !pubKey, }); + const assets = (data?.party?.accountsConnection?.edges ?? []) + .map( + (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); + if (!pubKey) { return ( @@ -26,11 +33,31 @@ export const LedgerContainer = () => { ); } - return ; -}; + if (!VEGA_URL) { + return ( + +

{t('Environment not configured')}

+
+ ); + } -const useLedgerStore = create()( - persist(createDataGridSlice, { - name: 'vega_ledger_store', - }) -); + if (loading) { + return ( +
+ +
+ ); + } + + if (!Object.keys(assets).length) { + return ( + +

{t('No ledger entries to export')}

+
+ ); + } + + return ( + + ); +}; diff --git a/libs/assets/src/lib/Assets.graphql b/libs/assets/src/lib/Assets.graphql index fc221c373..f1149233d 100644 --- a/libs/assets/src/lib/Assets.graphql +++ b/libs/assets/src/lib/Assets.graphql @@ -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 + } + } + } + } + } +} diff --git a/libs/assets/src/lib/__generated__/Assets.ts b/libs/assets/src/lib/__generated__/Assets.ts index b9c72a4c0..2c67f6742 100644 --- a/libs/assets/src/lib/__generated__/Assets.ts +++ b/libs/assets/src/lib/__generated__/Assets.ts @@ -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 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` fragment AssetListFields on Asset { id @@ -28,6 +37,14 @@ export const AssetListFieldsFragmentDoc = gql` status } `; +export const PartyAssetFieldsFragmentDoc = gql` + fragment PartyAssetFields on Asset { + id + name + symbol + status +} + `; export const AssetsDocument = gql` query Assets { assetsConnection { @@ -65,4 +82,49 @@ export function useAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions; export type AssetsLazyQueryHookResult = ReturnType; -export type AssetsQueryResult = Apollo.QueryResult; \ No newline at end of file +export type AssetsQueryResult = Apollo.QueryResult; +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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(PartyAssetsDocument, options); + } +export function usePartyAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(PartyAssetsDocument, options); + } +export type PartyAssetsQueryHookResult = ReturnType; +export type PartyAssetsLazyQueryHookResult = ReturnType; +export type PartyAssetsQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/assets/src/lib/party-assets.mock.ts b/libs/assets/src/lib/party-assets.mock.ts new file mode 100644 index 000000000..5ace1ed2f --- /dev/null +++ b/libs/assets/src/lib/party-assets.mock.ts @@ -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 => { + 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; diff --git a/libs/cypress/mock.ts b/libs/cypress/mock.ts index ababae6d4..598b05b3f 100644 --- a/libs/cypress/mock.ts +++ b/libs/cypress/mock.ts @@ -2,6 +2,7 @@ export * from '../accounts/src/lib/accounts.mock'; export * from '../assets/src/lib/asset.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/chart.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 '../fills/src/lib/fills.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 '../markets/src/lib/components/market-info/market-info.mock'; export * from '../markets/src/lib/market-candles.mock'; diff --git a/libs/ledger/src/index.ts b/libs/ledger/src/index.ts index 16820c1b9..6bd8d83a5 100644 --- a/libs/ledger/src/index.ts +++ b/libs/ledger/src/index.ts @@ -1,2 +1 @@ -export * from './lib/ledger-manager'; -export * from './lib/__generated__/LedgerEntries'; +export * from './lib/ledger-export-form'; diff --git a/libs/ledger/src/lib/LedgerEntries.graphql b/libs/ledger/src/lib/LedgerEntries.graphql deleted file mode 100644 index 354020f7c..000000000 --- a/libs/ledger/src/lib/LedgerEntries.graphql +++ /dev/null @@ -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 - } - } -} diff --git a/libs/ledger/src/lib/__generated__/LedgerEntries.ts b/libs/ledger/src/lib/__generated__/LedgerEntries.ts deleted file mode 100644 index 2cbcb25bc..000000000 --- a/libs/ledger/src/lib/__generated__/LedgerEntries.ts +++ /dev/null @@ -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; - dateRange?: Types.InputMaybe; - fromAccountType?: Types.InputMaybe | Types.AccountType>; - toAccountType?: Types.InputMaybe | 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) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(LedgerEntriesDocument, options); - } -export function useLedgerEntriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(LedgerEntriesDocument, options); - } -export type LedgerEntriesQueryHookResult = ReturnType; -export type LedgerEntriesLazyQueryHookResult = ReturnType; -export type LedgerEntriesQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/ledger/src/lib/ledger-entries-data-provider.ts b/libs/ledger/src/lib/ledger-entries-data-provider.ts deleted file mode 100644 index 3119875ef..000000000 --- a/libs/ledger/src/lib/ledger-entries-data-provider.ts +++ /dev/null @@ -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 => - entry !== null; - -const getData = (responseData: LedgerEntriesQuery | null) => { - return ( - responseData?.ledgerEntries?.edges - .filter(isLedgerEntryEdge) - .map((edge) => edge.node) || [] - ); -}; - -const ledgerEntriesOnlyProvider = makeDataProvider< - LedgerEntriesQuery, - ReturnType, - 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; - const assets = partsData[1] as Record; - const markets = partsData[2] as Record; - return entries.map((entry) => { - const asset = entry.assetId - ? (assets as Record)[entry.assetId] - : null; - const marketSender = entry.fromAccountMarketId - ? markets[entry.fromAccountMarketId] - : null; - const marketReceiver = entry.toAccountMarketId - ? markets[entry.toAccountMarketId] - : null; - return { ...entry, asset, marketSender, marketReceiver }; - }); - } -); diff --git a/libs/ledger/src/lib/ledger-entries.mock.ts b/libs/ledger/src/lib/ledger-entries.mock.ts deleted file mode 100644 index 1033a8c0c..000000000 --- a/libs/ledger/src/lib/ledger-entries.mock.ts +++ /dev/null @@ -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 => { - 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', - }, -]; diff --git a/libs/ledger/src/lib/ledger-export-form.spec.tsx b/libs/ledger/src/lib/ledger-export-form.spec.tsx new file mode 100644 index 000000000..37cc66956 --- /dev/null +++ b/libs/ledger/src/lib/ledger-export-form.spec.tsx @@ -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( + + ); + 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( + + ); + 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( + + ); + 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( + + ); + + 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(); + }); +}); diff --git a/libs/ledger/src/lib/ledger-export-form.tsx b/libs/ledger/src/lib/ledger-export-form.tsx new file mode 100644 index 000000000..8ef4fcff2 --- /dev/null +++ b/libs/ledger/src/lib/ledger-export-form.tsx @@ -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) => { + // 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; +} + +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 = ( + { + setAssetId(e.target.value); + }} + className="w-full" + data-testid="select-ledger-asset" + disabled={isDownloading} + > + {Object.keys(assets).map((assetKey) => ( + + ))} + + ); + + const startDownload = async (event: React.FormEvent) => { + 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 ( +
+

{t('Export ledger entries')}

+ + {assetDropDown} + + + setDateFrom(e.target.value)} + disabled={disabled} + max={maxFromDate} + /> + + + setDateTo(e.target.value)} + disabled={disabled} + max={maxToDate} + /> + +
+ {isDownloading && ( +
+ +
+ )} + +
+
+ ); +}; diff --git a/libs/ledger/src/lib/ledger-export-link.spec.tsx b/libs/ledger/src/lib/ledger-export-link.spec.tsx deleted file mode 100644 index 8c623ccfa..000000000 --- a/libs/ledger/src/lib/ledger-export-link.spec.tsx +++ /dev/null @@ -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(); - 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(); - 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` - ); - }); - }); -}); diff --git a/libs/ledger/src/lib/ledger-export-link.tsx b/libs/ledger/src/lib/ledger-export-link.tsx deleted file mode 100644 index aaae61863..000000000 --- a/libs/ledger/src/lib/ledger-export-link.tsx +++ /dev/null @@ -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); - 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 ( - {assets[assetId]}} - > - - {Object.keys(assets).map((assetKey) => ( - setAssetId(assetKey)} - > - {assets[assetKey]} - - ))} - - - ); - }, [assetId, assets]); - - if (!protohost || !entries || entries.length === 0) { - return null; - } - return ( -
-
Export all
- {assetDropDown} - - - -
- ); -}; diff --git a/libs/ledger/src/lib/ledger-manager.tsx b/libs/ledger/src/lib/ledger-manager.tsx deleted file mode 100644 index 1dd4eb490..000000000 --- a/libs/ledger/src/lib/ledger-manager.tsx +++ /dev/null @@ -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; -}) => { - const [filter, setFilter] = useState(defaultFilter); - - const variables = useMemo( - () => ({ - 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 ( -
- - {data && } -
- ); -}; diff --git a/libs/ledger/src/lib/ledger-table.tsx b/libs/ledger/src/lib/ledger-table.tsx deleted file mode 100644 index c175d3c35..000000000 --- a/libs/ledger/src/lib/ledger-table.tsx +++ /dev/null @@ -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 ( -

- {value ? DescriptionTransferTypeMapping[value] : ''} -

- ); -}; - -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; - -export const LedgerTable = (props: LedgerEntryProps) => { - const columnDefs = useMemo( - () => [ - { - headerName: t('Sender'), - field: 'fromAccountPartyId', - cellRenderer: ({ - value, - }: VegaValueFormatterParams) => - truncateByChars(value || ''), - }, - { - headerName: t('Account type'), - filter: SetFilter, - filterParams: { - set: AccountTypeMapping, - }, - field: 'fromAccountType', - cellRenderer: ({ - value, - }: VegaValueFormatterParams) => - value ? AccountTypeMapping[value] : '-', - }, - { - headerName: t('Market'), - field: 'marketSender.tradableInstrument.instrument.code', - cellRenderer: ({ - value, - data, - }: VegaValueFormatterParams< - LedgerEntry, - 'marketSender.tradableInstrument.instrument.code' - >) => - value && ( - - ), - }, - { - headerName: t('Receiver'), - field: 'toAccountPartyId', - cellRenderer: ({ - value, - }: VegaValueFormatterParams) => - truncateByChars(value || ''), - }, - { - headerName: t('Account type'), - filter: SetFilter, - filterParams: { - set: AccountTypeMapping, - }, - field: 'toAccountType', - cellRenderer: ({ - value, - }: VegaValueFormatterParams) => - value ? AccountTypeMapping[value] : '-', - }, - { - headerName: t('Market'), - field: 'marketReceiver.tradableInstrument.instrument.code', - cellRenderer: ({ - value, - data, - }: VegaValueFormatterParams< - LedgerEntry, - 'marketReceiver.tradableInstrument.instrument.code' - >) => - value && ( - - ), - }, - { - headerName: t('Transfer type'), - field: 'transferType', - tooltipField: 'transferType', - filter: SetFilter, - filterParams: { - set: TransferTypeMapping, - }, - valueFormatter: ({ - value, - }: VegaValueFormatterParams) => - value ? TransferTypeMapping[value] : '', - }, - { - headerName: t('Quantity'), - field: 'quantity', - valueFormatter: ({ - value, - data, - }: VegaValueFormatterParams) => { - const assetDecimalPlaces = data?.asset?.decimals || 0; - return value - ? addDecimalsFormatNumber(value, assetDecimalPlaces) - : ''; - }, - }, - { - headerName: t('Asset'), - field: 'assetId', - valueFormatter: ({ - value, - data, - }: VegaValueFormatterParams) => - data?.asset?.symbol || '', - }, - { - headerName: t('Sender account balance'), - field: 'fromAccountBalance', - valueFormatter: ({ - value, - data, - }: VegaValueFormatterParams) => { - const assetDecimalPlaces = data?.asset?.decimals || 0; - return value - ? addDecimalsFormatNumber(value, assetDecimalPlaces) - : ''; - }, - }, - { - headerName: t('Receiver account balance'), - field: 'toAccountBalance', - valueFormatter: ({ - value, - data, - }: VegaValueFormatterParams) => { - const assetDecimalPlaces = data?.asset?.decimals || 0; - return value - ? addDecimalsFormatNumber(value, assetDecimalPlaces) - : ''; - }, - }, - { - headerName: t('Vega time'), - field: 'vegaTime', - valueFormatter: ({ - value, - }: VegaValueFormatterParams) => - value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-', - filterParams: dateRangeFilterParams, - filter: DateRangeFilter, - }, - ], - [] - ); - return ( - - ); -}; diff --git a/libs/types/src/__generated__/types.ts b/libs/types/src/__generated__/types.ts index 28441bfcf..3ea7ba599 100644 --- a/libs/types/src/__generated__/types.ts +++ b/libs/types/src/__generated__/types.ts @@ -4414,6 +4414,8 @@ export type StopOrderFilter = { dateRange?: InputMaybe; /** Zero or more expiry strategies to filter by */ expiryStrategy?: InputMaybe>; + /** Filter for live stop orders only */ + liveOnly?: InputMaybe; /** Zero or more market IDs to filter by */ markets?: InputMaybe>; /** Zero or more party IDs to filter by */ diff --git a/libs/utils/src/lib/validate/common.ts b/libs/utils/src/lib/validate/common.ts index 9590f4cb3..4fb3200d2 100644 --- a/libs/utils/src/lib/validate/common.ts +++ b/libs/utils/src/lib/validate/common.ts @@ -15,8 +15,9 @@ export const ethereumAddress = (value: string) => { return true; }; +export const VEGA_ID_REGEX = /^[A-Fa-f0-9]{64}$/i; 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 true; diff --git a/specs/7007-LEEN-ledger-entries.md b/specs/7007-LEEN-ledger-entries.md new file mode 100644 index 000000000..5468bbbed --- /dev/null +++ b/specs/7007-LEEN-ledger-entries.md @@ -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 (7007-LEEN-001) +- in the form **Must** see a dropdown for select an asset, in which reports will be downloaded (7007-LEEN-002) +- in the form **Must** see inputs for select time period, in which reports will be downloaded (7007-LEEN-003) +- default preselected period **Must** be the last 7 days (7007-LEEN-004) +- during download a loader component **Must** be visible and all interactive elements in the form **Must** be disabled (7007-LEEN-005)