feat(positions): improve tooltips (#5481)

This commit is contained in:
Bartłomiej Głownia 2023-12-13 15:36:57 +01:00 committed by GitHub
parent 08c57b6759
commit 8b94a75ba4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 113 additions and 116 deletions

View File

@ -8,11 +8,13 @@ export const LiquidationPrice = ({
openVolume,
collateralAvailable,
decimalPlaces,
className,
}: {
marketId: string;
openVolume: string;
collateralAvailable: string;
decimalPlaces: number;
className?: string;
}) => {
const t = useT();
const { data: currentData, previousData } = useEstimatePositionQuery({
@ -39,22 +41,19 @@ export const LiquidationPrice = ({
return (
<Tooltip
align="end"
description={
<table>
<tbody>
<tr>
<th>{t('Worst case')}</th>
<td className="pl-2 text-right font-mono">{worstCase}</td>
</tr>
<tr>
<th>{t('Best case')}</th>
<td className="pl-2 text-right font-mono">{bestCase}</td>
</tr>
</tbody>
</table>
<dl className="grid grid-cols-2">
<dt>{t('Worst case')}</dt>
<dd className="pl-2 text-right font-mono">{worstCase}</dd>
<dt className="font-normal">{t('Best case')}</dt>
<dd className="pl-2 text-right font-mono">{bestCase}</dd>
</dl>
}
>
<span data-testid="liquidation-price">{worstCase}</span>
<span data-testid="liquidation-price" className={className}>
{worstCase}
</span>
</Tooltip>
);
};

View File

@ -314,7 +314,9 @@ describe('Positions', () => {
});
const cells = screen.getAllByRole('gridcell');
const cell = cells[1];
await userEvent.hover(cell);
const tooltipTrigger = cell.querySelector('[data-state="closed"]');
expect(tooltipTrigger).not.toBeNull();
await userEvent.hover(tooltipTrigger as Element);
const tooltip = within(await screen.findByRole('tooltip'));
expect(tooltip.getByText(data.text)).toBeInTheDocument();
});
@ -329,8 +331,9 @@ describe('Positions', () => {
});
const cells = screen.getAllByRole('gridcell');
const cell = cells[5];
await userEvent.hover(cell);
const tooltipTrigger = cell.querySelector('[data-state="closed"]');
expect(tooltipTrigger).not.toBeNull();
await userEvent.hover(tooltipTrigger as Element);
const tooltip = within(await screen.findByRole('tooltip'));
expect(tooltip.getByText('Realised PNL: 1.23')).toBeInTheDocument();
expect(

View File

@ -1,5 +1,5 @@
import { useMemo, type CSSProperties, type ReactNode } from 'react';
import { type ColDef, type ITooltipParams } from 'ag-grid-community';
import { type ColDef } from 'ag-grid-community';
import {
AgGrid,
COL_DEFS,
@ -20,6 +20,7 @@ import {
ExternalLink,
VegaIcon,
VegaIconNames,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import {
volumePrefix,
@ -148,15 +149,6 @@ export const PositionsTable = ({
data.positionDecimalPlaces
).toNumber();
},
tooltipValueGetter: ({ data }: ITooltipParams<Position>) => {
if (
!data ||
data.status === PositionStatus.POSITION_STATUS_UNSPECIFIED
) {
return null;
}
return data.status;
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<Position, 'openVolume'>): string => {
@ -171,68 +163,6 @@ export const PositionsTable = ({
return vol;
},
tooltipComponent: (args: ITooltipParams<Position>) => {
if (!args.data) {
return null;
}
const POSITION_RESOLUTION_LINK =
DocsLinks?.POSITION_RESOLUTION ?? '';
let primaryTooltip;
switch (args.data.status) {
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
primaryTooltip = t('Your position was closed.');
break;
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
primaryTooltip = t('Your open orders were cancelled.');
break;
case PositionStatus.POSITION_STATUS_DISTRESSED:
primaryTooltip = t('Your position is distressed.');
break;
}
let secondaryTooltip;
switch (args.data.status) {
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
secondaryTooltip = t(
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
{ assetSymbol: args.data.assetSymbol }
);
break;
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
secondaryTooltip = t(
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
);
break;
case PositionStatus.POSITION_STATUS_DISTRESSED:
secondaryTooltip = t(
'The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.'
);
break;
default:
secondaryTooltip = t('Maintained by network');
}
return (
<TooltipCellComponent
{...args}
value={
<>
<p className="mb-2">{primaryTooltip}</p>
<p className="mb-2">{secondaryTooltip}</p>
<p className="mb-2">
{t('Status: {{status}}', {
status: PositionStatusMapping[args.data.status],
})}
</p>
{POSITION_RESOLUTION_LINK && (
<ExternalLink href={POSITION_RESOLUTION_LINK}>
{t('Read more about position resolution')}
</ExternalLink>
)}
</>
}
/>
);
},
cellRenderer: OpenVolumeCell,
},
{
@ -337,12 +267,15 @@ export const PositionsTable = ({
return '-';
}
return (
<div className="flex h-[45px] items-center">
<LiquidationPrice
className="block text-right grow"
marketId={data.marketId}
openVolume={data.openVolume}
collateralAvailable={data.totalBalance}
decimalPlaces={data.marketDecimalPlaces}
/>
</div>
);
},
},
@ -354,13 +287,13 @@ export const PositionsTable = ({
cellClass: 'font-mono text-right',
filter: 'agNumberColumnFilter',
valueGetter: realisedPNLValueGetter,
// @ts-ignore no type overlap, but the functions are identical
tooltipValueGetter: realisedPNLValueGetter,
tooltipComponent: (args: ITooltipParams) => {
cellRenderer: (
args: VegaICellRendererParams<Position, 'realisedPNL'>
) => {
const LOSS_SOCIALIZATION_LINK =
DocsLinks?.LOSS_SOCIALIZATION ?? '';
if (!args.data) {
if (!args.data || args.value === undefined) {
return null;
}
@ -371,7 +304,11 @@ export const PositionsTable = ({
if (losses <= 0) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return (
<TooltipCellComponent {...args} value={args.valueFormatted} />
<Tooltip description={args.valueFormatted} align="end">
<div>
<PNLCell {...args} />
</div>
</Tooltip>
);
}
@ -381,20 +318,24 @@ export const PositionsTable = ({
);
return (
<TooltipCellComponent
{...args}
value={
<Tooltip
align="end"
description={
<>
<p className="mb-2">
{t('Realised PNL: {{value}}', {
value: args.value,
nsSeparator: '*',
replace: { value: args.value },
})}
</p>
<p className="mb-2">
{t(
'Lifetime loss socialisation deductions: {{losses}}',
{
nsSeparator: '*',
replace: {
losses: lossesFormatted,
},
}
)}
</p>
@ -411,7 +352,11 @@ export const PositionsTable = ({
)}
</>
}
/>
>
<div>
<PNLCell {...args} />
</div>
</Tooltip>
);
},
valueFormatter: ({
@ -428,7 +373,6 @@ export const PositionsTable = ({
headerTooltip: t(
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
),
cellRenderer: PNLCell,
},
{
headerName: t('Unrealised PNL'),
@ -520,10 +464,66 @@ export const OpenVolumeCell = ({
valueFormatted,
data,
}: VegaICellRendererParams<Position, 'openVolume'>) => {
const t = useT();
if (!valueFormatted || !data || !data.notional) {
return <>-</>;
}
const POSITION_RESOLUTION_LINK = DocsLinks?.POSITION_RESOLUTION ?? '';
let primaryTooltip;
switch (data.status) {
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
primaryTooltip = t('Your position was closed.');
break;
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
primaryTooltip = t('Your open orders were cancelled.');
break;
case PositionStatus.POSITION_STATUS_DISTRESSED:
primaryTooltip = t('Your position is distressed.');
break;
}
let secondaryTooltip;
switch (data.status) {
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
secondaryTooltip = t(
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
{ assetSymbol: data.assetSymbol }
);
break;
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
secondaryTooltip = t(
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
);
break;
case PositionStatus.POSITION_STATUS_DISTRESSED:
secondaryTooltip = t(
'The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.'
);
break;
default:
secondaryTooltip = t('Maintained by network');
}
const description = (
<>
<p className="mb-2">{primaryTooltip}</p>
<p className="mb-2">{secondaryTooltip}</p>
<p className="mb-2">
{t('Status: {{status}}', {
nsSeparator: '*',
replace: {
status: PositionStatusMapping[data.status],
},
})}
</p>
{POSITION_RESOLUTION_LINK && (
<ExternalLink href={POSITION_RESOLUTION_LINK}>
{t('Read more about position resolution')}
</ExternalLink>
)}
</>
);
const notional = addDecimalsFormatNumber(
data.notional,
data.marketDecimalPlaces
@ -539,16 +539,11 @@ export const OpenVolumeCell = ({
}
return (
<WarningCell
showIcon={
// not sure why but data.status has become a union of all the enum values
// rather than just being the enum itself
(data.status as PositionStatus) !==
PositionStatus.POSITION_STATUS_UNSPECIFIED
}
>
{cellContent}
</WarningCell>
<Tooltip description={description}>
<div>
<WarningCell showIcon>{cellContent}</WarningCell>
</div>
</Tooltip>
);
};