Compare commits

..
Author SHA1 Message Date
Matthew Russell 664cf369f9 chore: formatting 2023-11-20 13:30:39 -08:00
Matthew Russell 4c0fe722f2 chore: re enable margin estimates for perps 2023-11-20 13:03:35 -08:00
16 changed files with 241 additions and 507 deletions
-1
View File
@@ -52,4 +52,3 @@ cypress.env.json
/apps/**/cypress/reports/
/apps/**/cypress/downloads/
/apps/**/fixtures/wallet/node**
.nx/
@@ -15,12 +15,15 @@ export const LedgerContainer = () => {
});
const assets = (data?.party?.accountsConnection?.edges ?? [])
.map((item) => item?.node?.asset)
.filter((asset): asset is PartyAssetFieldsFragment => !!asset?.id)
.reduce(
(aggr, item) => Object.assign(aggr, { [item.id]: item.symbol }),
{} as Record<string, string>
);
.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) {
return (
@@ -1,12 +0,0 @@
import { Settings } from './settings';
import { render, screen } from '@testing-library/react';
describe('Settings', () => {
it('should the settings component with all the options', () => {
render(<Settings />);
expect(screen.getByText('Dark mode')).toBeInTheDocument();
expect(screen.getByText('Share usage data')).toBeInTheDocument();
expect(screen.getByText('Toast location')).toBeInTheDocument();
expect(screen.getByText('Reset to default')).toBeInTheDocument();
});
});
+2 -55
View File
@@ -1,19 +1,12 @@
import { t } from '@vegaprotocol/i18n';
import {
Dialog,
Intent,
Switch,
ToastPositionSetter,
TradingButton,
} from '@vegaprotocol/ui-toolkit';
import { Switch, ToastPositionSetter } from '@vegaprotocol/ui-toolkit';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
import { useState, type ReactNode } from 'react';
import type { ReactNode } from 'react';
export const Settings = () => {
const { theme, setTheme } = useThemeSwitcher();
const [isApproved, setIsApproved] = useTelemetryApproval();
const [open, setOpen] = useState(false);
return (
<div>
<SettingsGroup label={t('Dark mode')}>
@@ -38,52 +31,6 @@ export const Settings = () => {
<SettingsGroup label={t('Toast location')}>
<ToastPositionSetter />
</SettingsGroup>
<SettingsGroup label={t('Reset to default')}>
<TradingButton
name="reset-to-defaults"
size="small"
intent={Intent.None}
onClick={() => {
setOpen(true);
}}
>
{t('Reset')}
</TradingButton>
<Dialog open={open} title={t('Reset')}>
<div className="mb-4">
<p>
{t(
'You will lose all persisted settings and you will be logged out.'
)}
</p>
<p>
{t('Are you sure you want to reset all settings to default?')}
</p>
</div>
<div className="flex flex-col gap-4">
<TradingButton
name="reset-to-defaults-cancel"
intent={Intent.Primary}
onClick={() => {
localStorage.clear();
window.location.reload();
}}
>
{t('Yes, clear cache and refresh')}
</TradingButton>
<TradingButton
name="reset-to-defaults-cancel"
intent={Intent.None}
onClick={() => {
setOpen(false);
}}
>
{t('No, keep settings')}
</TradingButton>
</div>
</Dialog>
</SettingsGroup>
</div>
);
};
+3 -3
View File
@@ -170,7 +170,7 @@ html [data-theme='dark'] {
.ag-theme-balham,
.ag-theme-balham-dark {
--ag-grid-size: 3px; /* Used for compactness */
--ag-grid-size: 2px; /* Used for compactness */
--ag-row-height: 36px;
--ag-header-height: 28px;
}
@@ -184,7 +184,7 @@ html [data-theme='dark'] {
/* Light variables */
.ag-theme-balham {
--ag-background-color: theme(colors.vega.clight.900);
--ag-background-color: transparent;
--ag-border-color: theme(colors.vega.clight.600);
--ag-header-background-color: theme(colors.vega.clight.700);
--ag-odd-row-background-color: transparent;
@@ -196,7 +196,7 @@ html [data-theme='dark'] {
/* Dark variables */
.ag-theme-balham-dark {
--ag-background-color: theme(colors.vega.cdark.900);
--ag-background-color: transparent;
--ag-border-color: theme(colors.vega.cdark.600);
--ag-header-background-color: theme(colors.vega.cdark.700);
--ag-odd-row-background-color: transparent;
+40 -28
View File
@@ -1,23 +1,10 @@
import { act, render, screen, within } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import * as Types from '@vegaprotocol/types';
import type { AccountFields } from './accounts-data-provider';
import { getAccountData } from './accounts-data-provider';
import { AccountTable } from './accounts-table';
import userEvent from '@testing-library/user-event';
const asset1 = {
__typename: 'Asset',
id: 'asset-1',
symbol: 'tBTC',
decimals: 5,
name: 'T BTC',
};
const asset2 = {
__typename: 'Asset',
id: 'asset-2',
symbol: 'aBTC',
decimals: 5,
name: 'A BTC',
};
const singleRow = {
__typename: 'AccountBalance',
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
@@ -26,7 +13,12 @@ const singleRow = {
__typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
},
asset: asset1,
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
decimals: 5,
},
available: '125600000',
used: '125600000',
total: '251200000',
@@ -41,7 +33,12 @@ const secondRow = {
__typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
},
asset: asset2,
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'aBTC',
decimals: 5,
},
available: '125600001',
used: '125600001',
total: '251200002',
@@ -137,7 +134,7 @@ describe('AccountsTable', () => {
it('should sort assets', async () => {
// 7001-COLL-010
render(
const { container } = render(
<AccountTable
rowData={multiRowData}
onClickAsset={() => null}
@@ -145,13 +142,13 @@ describe('AccountsTable', () => {
/>
);
const headerCell = screen
.getAllByRole('columnheader')
.find((h) => h?.getAttribute('col-id') === 'asset.symbol') as HTMLElement;
await userEvent.click(within(headerCell).getByText(/asset/i));
expect(headerCell).toHaveAttribute('aria-sort', 'ascending');
const headerCell = screen.getByText('Asset');
await userEvent.click(headerCell);
const rows = container.querySelectorAll(
'.ag-center-cols-container .ag-row'
);
expect(rows[0].textContent).toContain('aBTC');
expect(rows[1].textContent).toContain('tBTC');
});
it('should apply correct formatting in view as user mode', async () => {
@@ -179,7 +176,12 @@ describe('AccountsTable', () => {
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={false}
pinnedAsset={asset1}
pinnedAsset={{
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
name: 'tBTC',
}}
/>
);
await screen.findAllByRole('rowgroup');
@@ -211,13 +213,23 @@ describe('AccountsTable', () => {
const result = getAccountData([singleRow]);
const expected = [
{
asset: asset1,
asset: {
__typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
available: '0',
balance: '0',
breakdown: [
{
__typename: 'AccountBalance',
asset: asset1,
asset: {
__typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
available: '0',
balance: '125600000',
total: '125600000',
+10 -94
View File
@@ -1,10 +1,4 @@
import {
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import BigNumber from 'bignumber.js';
import {
@@ -13,7 +7,7 @@ import {
TransferForm,
type TransferFormProps,
} from './transfer-form';
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
import { AccountType } from '@vegaprotocol/types';
import { removeDecimal } from '@vegaprotocol/utils';
describe('TransferForm', () => {
@@ -45,7 +39,8 @@ describe('TransferForm', () => {
};
const amount = '100';
const pubKey = '1'.repeat(64);
const pubKey =
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
const asset = {
id: 'eur',
symbol: '€',
@@ -55,7 +50,10 @@ describe('TransferForm', () => {
};
const props = {
pubKey,
pubKeys: [pubKey, '2'.repeat(64)],
pubKeys: [
pubKey,
'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce',
],
feeFactor: '0.001',
submitTransfer: jest.fn(),
accounts: [
@@ -184,7 +182,7 @@ describe('TransferForm', () => {
// Test use max button
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('1000.00');
expect(amountInput).toHaveValue('1000');
// Test amount validation
await userEvent.clear(amountInput);
@@ -269,7 +267,7 @@ describe('TransferForm', () => {
// Test use max button
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue('100.00');
expect(amountInput).toHaveValue('100');
// If transfering from a vested account 'include fees' checkbox should
// be disabled and fees should be 0
@@ -298,88 +296,6 @@ describe('TransferForm', () => {
});
});
it('handles lots of decimal places', async () => {
const balance = '904195168829277777';
const expectedBalance = '0.904195168829277777';
const longDecimalAsset = {
id: 'assetId',
symbol: 'VEGA',
name: 'VEGA',
decimals: 18,
quantum: '1',
};
const account = {
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
asset: longDecimalAsset,
balance,
};
const mockSubmit = jest.fn();
renderComponent({
...props,
accounts: [account],
submitTransfer: mockSubmit,
minQuantumMultiple: '100000',
});
// Select a pubkey
await userEvent.selectOptions(
screen.getByLabelText('To Vega key'),
props.pubKeys[1] // Use not current pubkey so we can check it switches to current pubkey later
);
// Select asset
await selectAsset(longDecimalAsset);
const accountSelect = screen.getByLabelText('From account');
const option = within(accountSelect)
.getAllByRole('option')
.find(
(o) => o.getAttribute('value') === `${account.type}-${account.asset.id}`
);
// plus one for disabled 'please select' option
expect(option).toHaveTextContent(
`${AccountTypeMapping[account.type]} (${expectedBalance} ${
account.asset.symbol
})`
);
await userEvent.selectOptions(
accountSelect,
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${longDecimalAsset.id}`
);
expect(accountSelect).toHaveValue(
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${longDecimalAsset.id}`
);
// Check switch back to connected key
const amountInput = screen.getByLabelText('Amount');
// Test use max button
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
expect(amountInput).toHaveValue(expectedBalance);
await submit();
await waitFor(() => {
// 1003-TRAN-023
expect(mockSubmit).toHaveBeenCalledTimes(1);
expect(mockSubmit).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKey,
asset: longDecimalAsset.id,
amount: balance,
oneOff: {},
});
});
});
describe('IncludeFeesCheckbox', () => {
it('validates fields and submits when checkbox is checked', async () => {
const mockSubmit = jest.fn();
+10 -4
View File
@@ -5,6 +5,7 @@ import {
vegaPublicKey,
addDecimal,
formatNumber,
addDecimalsFormatNumber,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
@@ -213,7 +214,12 @@ export const TransferForm = ({
<AssetOption
key={a.key}
asset={a}
balance={<Balance balance={a.balance} symbol={a.symbol} />}
balance={
<Balance
balance={formatNumber(a.balance, a.decimals)}
symbol={a.symbol}
/>
}
/>
))}
</TradingRichSelect>
@@ -280,8 +286,8 @@ export const TransferForm = ({
return (
<option value={id} key={id}>
{AccountTypeMapping[a.type]} (
{addDecimal(a.balance, a.asset.decimals)} {a.asset.symbol}
)
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
{a.asset.symbol})
</option>
);
})}
@@ -407,7 +413,7 @@ export const TransferForm = ({
type="button"
className="absolute top-0 right-0 ml-auto text-xs underline"
onClick={() =>
setValue('amount', accountBalance, {
setValue('amount', parseFloat(accountBalance).toString(), {
shouldValidate: true,
})
}
@@ -37,9 +37,6 @@ export const AgGridThemed = ({
<div className={wrapperClasses}>
<AgGridReact
defaultColDef={defaultColDef}
overlayLoadingTemplate={t('Loading...')}
overlayNoRowsTemplate={t('No data')}
suppressDragLeaveHidesColumns
ref={gridRef}
{...defaultProps}
{...props}
@@ -278,4 +278,18 @@ describe('createDownloadUrl', () => {
)}&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();
});
});
+145 -200
View File
@@ -1,18 +1,18 @@
import { useRef, useCallback } from 'react';
import { subDays } from 'date-fns';
import { Controller, useForm } from 'react-hook-form';
import { useRef, useState } from 'react';
import { format, subDays } from 'date-fns';
import {
InputError,
Intent,
Loader,
TradingButton,
TradingFormGroup,
TradingInput,
TradingSelect,
} from '@vegaprotocol/ui-toolkit';
import { z } from 'zod';
import {
formatForInput,
getDateTimeFormat,
toNanoSeconds,
VEGA_ID_REGEX,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { localLoggerFactory } from '@vegaprotocol/logger';
@@ -35,18 +35,18 @@ const getProtoHost = (vegaurl: string) => {
return `${loc.protocol}//${loc.host}`;
};
type LedgerFormValues = {
assetId: string;
dateFrom: string;
dateTo?: string;
};
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);
export const createDownloadUrl = (
args: LedgerFormValues & {
partyId: string;
protohost: string;
}
) => {
const params = new URLSearchParams();
params.append('partyId', args.partyId);
params.append('assetId', args.assetId);
@@ -71,102 +71,115 @@ interface Props {
export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
const now = useRef(new Date());
const { control, handleSubmit, watch } = useForm<LedgerFormValues>({
defaultValues: {
dateFrom: formatForInput(subDays(now.current, 7)),
dateTo: '',
assetId: Object.keys(assets)[0],
},
const [dateFrom, setDateFrom] = useState(() => {
return formatForInput(subDays(now.current, 7));
});
const dateTo = watch('dateTo');
const [dateTo, setDateTo] = useState('');
const maxFromDate = formatForInput(new Date(dateTo || now.current));
const maxToDate = formatForInput(now.current);
const [assetId, setAssetId] = useState(Object.keys(assets)[0]);
const protohost = getProtoHost(vegaUrl);
const disabled = Boolean(!assetId);
const hasItem = useLedgerDownloadFile((store) => store.hasItem);
const updateDownloadQueue = useLedgerDownloadFile(
(store) => store.updateQueue
);
const startDownload = useCallback(
async (formValues: LedgerFormValues) => {
const link = createDownloadUrl({
protohost,
partyId,
...formValues,
});
const dateTimeFormatter = getDateTimeFormat();
const title = t('Downloading for %s from %s till %s', [
assets[formValues.assetId],
dateTimeFormatter.format(new Date(formValues.dateFrom)),
dateTimeFormatter.format(new Date(formValues.dateTo || Date.now())),
]);
const downloadStoreItem = {
title,
link,
isChanged: true,
};
if (hasItem(link)) {
updateDownloadQueue(downloadStoreItem);
return;
}
const ts = setTimeout(() => {
updateDownloadQueue({
...downloadStoreItem,
intent: Intent.Warning,
isDelayed: true,
isChanged: true,
});
}, 1000 * 30);
try {
updateDownloadQueue(downloadStoreItem);
const resp = await fetch(link);
if (!resp?.ok) {
if (resp?.status === 429) {
throw new Error('Too many requests. Try again later.');
}
throw new Error('Download of ledger entries failed');
}
const { headers } = resp;
const nameHeader = headers.get('content-disposition');
const filename =
nameHeader?.split('=').pop() ?? DEFAULT_EXPORT_FILE_NAME;
updateDownloadQueue({
...downloadStoreItem,
filename,
});
const blob = await resp.blob();
if (blob) {
updateDownloadQueue({
...downloadStoreItem,
blob,
isDownloaded: true,
isChanged: true,
intent: Intent.Success,
});
}
} catch (err) {
localLoggerFactory({ application: 'ledger' }).error(
'Download file',
err
);
updateDownloadQueue({
...downloadStoreItem,
intent: Intent.Danger,
isError: true,
isChanged: true,
errorMessage: (err as Error).message || undefined,
});
} finally {
clearTimeout(ts);
}
},
[assets, hasItem, partyId, protohost, updateDownloadQueue]
const assetDropDown = (
<TradingSelect
id="select-ledger-asset"
value={assetId}
onChange={(e) => {
setAssetId(e.target.value);
}}
className="w-full"
data-testid="select-ledger-asset"
>
{Object.keys(assets).map((assetKey) => (
<option key={assetKey} value={assetKey}>
{assets[assetKey]}
</option>
))}
</TradingSelect>
);
const link = createDownloadUrl({
protohost,
partyId,
assetId,
dateFrom,
dateTo,
});
const startDownload = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const title = t('Downloading for %s from %s till %s', [
assets[assetId],
format(new Date(dateFrom), 'dd MMMM yyyy HH:mm'),
format(new Date(dateTo || Date.now()), 'dd MMMM yyyy HH:mm'),
]);
const downloadStoreItem = {
title,
link,
isChanged: true,
};
if (hasItem(link)) {
updateDownloadQueue(downloadStoreItem);
return;
}
const ts = setTimeout(() => {
updateDownloadQueue({
...downloadStoreItem,
intent: Intent.Warning,
isDelayed: true,
isChanged: true,
});
}, 1000 * 30);
try {
updateDownloadQueue(downloadStoreItem);
const resp = await fetch(link);
if (!resp?.ok) {
if (resp?.status === 429) {
throw new Error('Too many requests. Try again later.');
}
throw new Error('Download of ledger entries failed');
}
const { headers } = resp;
const nameHeader = headers.get('content-disposition');
const filename = nameHeader?.split('=').pop() ?? DEFAULT_EXPORT_FILE_NAME;
updateDownloadQueue({
...downloadStoreItem,
filename,
});
const blob = await resp.blob();
if (blob) {
updateDownloadQueue({
...downloadStoreItem,
blob,
isDownloaded: true,
isChanged: true,
intent: Intent.Success,
});
}
} catch (err) {
localLoggerFactory({ application: 'ledger' }).error('Download file', err);
updateDownloadQueue({
...downloadStoreItem,
intent: Intent.Danger,
isError: true,
isChanged: true,
errorMessage: (err as Error).message || undefined,
});
} finally {
clearTimeout(ts);
}
};
if (!protohost || Object.keys(assets).length === 0) {
return null;
}
@@ -174,117 +187,49 @@ export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
const offset = new Date().getTimezoneOffset();
return (
<form
onSubmit={handleSubmit(startDownload)}
className="p-4 w-[350px]"
noValidate
>
<form onSubmit={startDownload} className="p-4 w-[350px]">
<h2 className="mb-4">{t('Export ledger entries')}</h2>
<Controller
name="assetId"
control={control}
rules={{
required: t('You need to select an asset'),
}}
render={({ field, fieldState }) => (
<div className="mb-2">
<TradingFormGroup
label={t('Select asset')}
labelFor="asset"
compact
>
<TradingSelect
{...field}
id="select-ledger-asset"
className="w-full"
data-testid="select-ledger-asset"
>
{Object.keys(assets).map((assetKey) => (
<option key={assetKey} value={assetKey}>
{assets[assetKey]}
</option>
))}
</TradingSelect>
</TradingFormGroup>
{fieldState.error && (
<InputError>{fieldState.error.message}</InputError>
)}
</div>
)}
/>
<Controller
name="dateFrom"
control={control}
rules={{
required: t('You need to provide a date from'),
max: {
value: maxFromDate,
message: dateTo
? t('Date from cannot be greater than date to')
: t('Date from cannot be in the future'),
},
deps: ['dateTo'],
}}
render={({ field, fieldState }) => (
<div className="mb-2">
<TradingFormGroup
label={t('Date from')}
labelFor="date-from"
compact
>
<TradingInput
{...field}
type="datetime-local"
data-testid="date-from"
id="date-from"
max={maxFromDate}
/>
</TradingFormGroup>
{fieldState.error && (
<InputError>{fieldState.error.message}</InputError>
)}
</div>
)}
/>
<Controller
name="dateTo"
control={control}
rules={{
max: {
value: maxToDate,
message: t('Date to cannot be in the future'),
},
}}
render={({ field, fieldState }) => (
<div className="mb-2">
<TradingFormGroup label={t('Date to')} labelFor="date-to" compact>
<TradingInput
{...field}
type="datetime-local"
data-testid="date-to"
id="date-to"
max={maxToDate}
/>
</TradingFormGroup>
{fieldState.error && (
<InputError>{fieldState.error.message}</InputError>
)}
</div>
)}
/>
<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)}
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)}
max={maxToDate}
/>
</TradingFormGroup>
<div className="relative text-sm" title={t('Download all to .csv file')}>
<TradingButton fill type="submit" data-testid="ledger-download-button">
<TradingButton
fill
disabled={disabled}
type="submit"
data-testid="ledger-download-button"
>
{t('Download')}
</TradingButton>
</div>
{offset ? (
{offset && (
<p className="text-xs text-neutral-400 mt-1">
{t(
'The downloaded file uses the UTC time zone for all listed times. Your time zone is UTC%s.',
[toHoursAndMinutes(offset)]
)}
</p>
) : null}
)}
</form>
);
};
+4 -1
View File
@@ -106,6 +106,9 @@ export const LiquidityTable = ({
const feesAccruedTooltip = ({ value, data }: ITooltipParams) => {
if (!value) return '-';
const newValue = new BigNumber(value)
.times(Number(stakeToCcyVolume) || 1)
.toString();
let lessThanFull = false,
lessThanMinimum = false;
if (data.sla) {
@@ -150,7 +153,7 @@ export const LiquidityTable = ({
]
);
}
return addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0);
return addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0);
};
const stakeToCcyVolumeQuantumFormatter = ({
@@ -27,9 +27,7 @@ jest.mock('@vegaprotocol/data-provider', () => ({
}));
describe('PositionsManager', () => {
// TODO: temporarily disable close position
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should close position with max uint64', async () => {
it('should close position with max uint64', async () => {
render(<PositionsManager partyIds={['partyId']} isReadOnly={false} />, {
wrapper: MockedProvider,
});
+1 -3
View File
@@ -29,7 +29,6 @@ export const PositionsManager = ({
}: PositionsManagerProps) => {
const { pubKeys, pubKey } = useVegaWallet();
const create = useVegaTransactionStore((store) => store.create);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onClose = useCallback(
({ marketId, openVolume }: { marketId: string; openVolume: string }) =>
create({
@@ -74,8 +73,7 @@ export const PositionsManager = ({
pubKeys={pubKeys}
rowData={data}
onMarketClick={onMarketClick}
// TODO: temporarily disable close position
// onClose={onClose}
onClose={onClose}
isReadOnly={isReadOnly}
multipleKeys={partyIds.length > 1}
overlayNoRowsTemplate={error ? error.message : t('No positions')}
@@ -1,72 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useVegaWallet } from '../use-vega-wallet';
import { useChainId } from './use-chain-id';
global.fetch = jest.fn();
const mockFetch = global.fetch as jest.Mock;
mockFetch.mockImplementation((url: string) => {
return Promise.resolve({ ok: true });
});
jest.mock('../use-vega-wallet', () => {
const original = jest.requireActual('../use-vega-wallet');
return {
...original,
useVegaWallet: jest.fn(),
};
});
describe('useChainId', () => {
it('does not call fetch when statistics url could not be determined', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: '',
});
renderHook(() => useChainId());
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledTimes(0);
});
});
it('calls fetch with correct statistics url', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
renderHook(() => useChainId());
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:1234/statistics'
);
});
});
it('does not return chain id when chain id is not present in response', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
const { result } = renderHook(() => useChainId());
await waitFor(() => {
expect(result.current).toBeUndefined();
});
});
it('returns chain id when chain id is present in response', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
mockFetch.mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => ({
statistics: {
chainId: '1234',
},
}),
});
});
const { result } = renderHook(() => useChainId());
await waitFor(() => {
expect(result.current).not.toBeUndefined();
expect(result.current).toEqual('1234');
});
});
});
+2 -22
View File
@@ -3,37 +3,17 @@ import { useVegaWallet } from '../use-vega-wallet';
const cache: Record<string, string> = {};
/**
* Gets the statistics url for a given vega url.
* Example:
* https://graphql.example.com/graphql -> https://graphql.example.com/statistics
*/
const getNodeStatisticsUrl = (vegaUrl: string) => {
try {
const url = new URL(vegaUrl);
url.pathname = 'statistics';
return url.toString();
} catch (err) {
return undefined;
}
};
export const useChainId = () => {
const { vegaUrl } = useVegaWallet();
const [chainId, setChainId] = useState<undefined | string>(cache[vegaUrl]);
const [fetchAttempt, setFetchAttempt] = useState(1);
const statisticsUrl = getNodeStatisticsUrl(vegaUrl);
useEffect(() => {
// abort when `/statistics` URL could not be determined
if (!statisticsUrl) return;
let isCancelled = false;
if (cache[vegaUrl]) {
setChainId(cache[vegaUrl]);
return;
}
fetch(statisticsUrl)
fetch(vegaUrl.replace('graphql', 'statistics'))
.then((response) => response.json())
.then((response) => {
if (isCancelled) {
@@ -52,6 +32,6 @@ export const useChainId = () => {
return () => {
isCancelled = true;
};
}, [fetchAttempt, statisticsUrl, vegaUrl]);
}, [fetchAttempt, vegaUrl]);
return chainId;
};