Compare commits

..
Author SHA1 Message Date
maciek 28ba02c894 chore: add back telemetry approval - upgrade specs 2023-09-01 10:17:50 +02:00
maciek ce1a0e6896 Merge remote-tracking branch 'origin/develop' into chore/4349-delayed-telemetry-opt-in 2023-09-01 10:09:00 +02:00
maciek c4f42d2a78 chore: add back telemetry approval - refactor view after feedback 2023-09-01 10:08:44 +02:00
maciek a78c2f8063 chore: add back telemetry approval - move view from popup to toast 2023-08-30 14:44:04 +02:00
maciek 268887008a Merge remote-tracking branch 'origin/develop' into chore/4349-delayed-telemetry-opt-in 2023-08-30 11:03:27 +02:00
maciek 6dd0bae46d chore: add back telemetry approval - try to fix failing cypress tests 2023-08-30 11:03:06 +02:00
maciek 8db163912c chore: add back telemetry approval - try to fix failing cypress tests 2023-08-29 17:11:41 +02:00
maciek 903f50ef91 Merge remote-tracking branch 'origin/develop' into chore/4349-delayed-telemetry-opt-in 2023-08-29 16:09:12 +02:00
maciek ec8fec8648 chore: add back telemetry approval - re-refactor a solution 2023-08-29 16:08:53 +02:00
maciek b23f4f11c4 chore: add back telemetry approval - refactor a solution 2023-08-29 14:12:04 +02:00
maciek af172a957f Merge remote-tracking branch 'origin/develop' into chore/4349-delayed-telemetry-opt-in 2023-08-29 13:00:37 +02:00
maciek 3964acc3ac chore: add back telemetry approval - refactor a solution 2023-08-29 13:00:20 +02:00
maciek 58c91db560 Merge remote-tracking branch 'origin/develop' into chore/4349-delayed-telemetry-opt-in 2023-08-29 10:30:52 +02:00
maciek 76c518a86e chore: add back telemetry approval 2023-08-25 16:49:28 +02:00
48 changed files with 490 additions and 759 deletions
+3
View File
@@ -3,3 +3,6 @@
# Lint commit messages to ensure they follow conventional commit standards
yarn commitlint --edit "${1}"
# Lint all staged files
yarn lint-staged
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint all staged files
yarn lint-staged
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint all staged files
yarn nx format:check
# Test all projects with changes
yarn nx affected -t test --exclude trading
@@ -1,68 +0,0 @@
context('Oracle page', { tags: '@smoke' }, () => {
describe('Verify elements on page', () => {
before('create market and navigate to oracle page', () => {
cy.createMarket();
cy.visit('/oracles');
});
it('should see oracle data', () => {
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
cy.getByTestId('oracle-details')
.should('exist')
.eq(0)
.within(() => {
cy.get('tr')
.eq(0)
.within(() => {
cy.get('th').should('have.text', 'ID');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/oracles/');
});
cy.get('tr')
.eq(1)
.within(() => {
cy.get('th').should('have.text', 'Type');
cy.get('td').should('have.text', 'External data');
});
cy.get('tr')
.eq(2)
.within(() => {
cy.get('th').should('have.text', 'Signer');
cy.getByTestId('keytype').should('have.text', 'Vega');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/parties/');
});
cy.get('tr')
.eq(3)
.within(() => {
cy.get('th').should('have.text', 'Settlement for');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/markets/');
});
cy.get('tr')
.eq(4)
.within(() => {
cy.get('th').should('have.text', 'Matched data');
cy.get('td').should('have.text', '❌');
});
cy.get('details')
.eq(0)
.within(() => {
cy.contains('Filter').click();
cy.get('.language-json').should('exist');
});
cy.get('details')
.eq(1)
.within(() => {
cy.contains('JSON').click();
cy.get('.language-json').should('exist');
});
});
});
});
});
@@ -38,12 +38,7 @@ const Oracles = () => {
const dataConnection = o?.node.dataConnection;
return (
<div
id={id}
key={id}
className="mb-10"
data-testid="oracle-details"
>
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id}
dataSource={o?.node}
@@ -54,7 +54,7 @@ export const WalletCardRow = ({
}) => {
const ref = React.useRef<HTMLDivElement | null>(null);
useAnimateValue(ref, value);
const [integers, decimalsPlaces, separator] = useNumberParts(value, decimals);
const [integers, decimalsPlaces] = useNumberParts(value, decimals);
return (
<div
@@ -75,10 +75,7 @@ export const WalletCardRow = ({
className="font-mono flex-1 text-right"
data-testid="associated-amount"
>
<span>
{integers}
{separator}
</span>
<span>{integers}.</span>
<span>{decimalsPlaces}</span>
</span>
)}
@@ -113,10 +110,7 @@ export const WalletCardAsset = ({
border,
subheading,
}: WalletCardAssetProps) => {
const [integers, decimalsPlaces, separator] = useNumberParts(
balance,
decimals
);
const [integers, decimalsPlaces] = useNumberParts(balance, decimals);
return (
<div className="flex flex-nowrap mt-2 mb-4">
@@ -138,10 +132,7 @@ export const WalletCardAsset = ({
</div>
</div>
<div className="px-2 basis-full font-mono" data-testid="currency-value">
<span>
{integers}
{separator}
</span>
<span>{integers}.</span>
<span className="text-neutral-400">{decimalsPlaces}</span>
</div>
</div>
@@ -0,0 +1,117 @@
const orderbookTab = 'Orderbook';
const orderbookTable = 'tab-orderbook';
const askPrice = 'price-9894185';
const bidPrice = 'price-9889001';
const askVolume = 'ask-vol-9894185';
const bidVolume = 'bid-vol-9889001';
const askCumulative = 'cumulative-vol-9894185';
const bidCumulative = 'cumulative-vol-9889001';
const midPrice = 'last-traded-4612690000';
const priceResolution = 'resolution';
const dealTicketPrice = 'order-price';
const dealTicketSize = 'order-size';
const resPrice = 'price-990';
describe('order book', { tags: '@smoke' }, () => {
before(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
beforeEach(() => {
cy.mockTradingPage();
});
it('show order book', () => {
// 6003-ORDB-001
// 6003-ORDB-002
cy.getByTestId(orderbookTab).click();
cy.getByTestId(orderbookTable).should('be.visible');
cy.getByTestId(orderbookTable).should('not.be.empty');
});
it('show orders prices', () => {
// 6003-ORDB-003
cy.getByTestId(askPrice).should('have.text', '98.94185');
cy.getByTestId(bidPrice).should('have.text', '98.89001');
});
it('show prices volumes', () => {
// 6003-ORDB-004
cy.getByTestId(askVolume).should('have.text', '1');
cy.getByTestId(bidVolume).should('have.text', '1');
});
it('show prices cumulative volumes', () => {
// 6003-ORDB-005
cy.getByTestId(askCumulative).should('have.text', '38');
cy.getByTestId(bidCumulative).should('have.text', '7');
});
it('show mid price', () => {
// 6003-ORDB-006
cy.getByTestId(midPrice).should('have.text', '46,126.90');
});
it('sort prices descending', () => {
// 6003-ORDB-007
const prices: number[] = [];
cy.getByTestId(orderbookTable).within(() => {
cy.get('[data-testid*=price]')
.each(($el) => {
prices.push(Number($el.text()));
})
.then(() => {
expect(prices).to.deep.equal(prices.sort((a, b) => b - a));
});
});
});
it('copy price to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(askPrice).click();
cy.getByTestId(dealTicketPrice).should('have.value', '98.94185');
});
it('copy size to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(bidCumulative).click();
cy.getByTestId(dealTicketSize).should('have.value', '7');
});
it('copy size to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(bidVolume).click();
cy.getByTestId(dealTicketSize).should('have.value', '1');
});
it('change price resolution', () => {
// 6003-ORDB-008
const resolutions = [
'0.00000',
'0.0000',
'0.000',
'0.00',
'0.0',
'0',
'10',
'100',
'1,000',
'10,000',
];
cy.getByTestId(priceResolution).click();
cy.get('[role="menu"]')
.find('[role="menuitem"]')
.each(($el, index) => {
expect($el.text()).to.equal(resolutions[index]);
});
cy.get('[role="menuitem"]').eq(4).click();
cy.getByTestId(resPrice).should('have.text', '99.0');
cy.getByTestId(askPrice).should('not.exist');
cy.getByTestId(bidPrice).should('not.exist');
});
});
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const AccountsMenu = () => {
@@ -8,6 +8,7 @@ export const AccountsMenu = () => {
return (
<>
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={() => setView({ type: ViewType.Transfer })}
@@ -15,6 +16,7 @@ export const AccountsMenu = () => {
{t('Transfer')}
</TradingButton>
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
>
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const DepositsMenu = () => {
@@ -7,6 +7,7 @@ export const DepositsMenu = () => {
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
data-testid="deposit-button"
@@ -1,12 +1,13 @@
import { t } from '@vegaprotocol/i18n';
import {
TradingDropdown,
TradingDropdownCheckboxItem,
TradingDropdownContent,
TradingDropdownItemIndicator,
TradingDropdownTrigger,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItemIndicator,
DropdownMenuTrigger,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { MarketSelectorButton } from './market-selector-button';
type Assets = Array<{ id: string; symbol: string }>;
@@ -24,19 +25,17 @@ export const AssetDropdown = ({
}
return (
<TradingDropdown
<DropdownMenu
trigger={
<TradingDropdownTrigger data-testid="asset-trigger">
<MarketSelectorButton>
{triggerText({ assets, checkedAssets })}
</MarketSelectorButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger data-testid="asset-trigger">
<TriggerText assets={assets} checkedAssets={checkedAssets} />
</DropdownMenuTrigger>
}
>
<TradingDropdownContent>
<DropdownMenuContent>
{assets?.map((a) => {
return (
<TradingDropdownCheckboxItem
<DropdownMenuCheckboxItem
key={a.id}
checked={checkedAssets.includes(a.id)}
onCheckedChange={(checked) => {
@@ -47,16 +46,16 @@ export const AssetDropdown = ({
data-testid={`asset-id-${a.id}`}
>
{a.symbol}
<TradingDropdownItemIndicator />
</TradingDropdownCheckboxItem>
<DropdownMenuItemIndicator />
</DropdownMenuCheckboxItem>
);
})}
</TradingDropdownContent>
</TradingDropdown>
</DropdownMenuContent>
</DropdownMenu>
);
};
const triggerText = ({
const TriggerText = ({
assets,
checkedAssets,
}: {
@@ -73,5 +72,9 @@ const triggerText = ({
text = t(`${checkedAssets.length} Assets`);
}
return text;
return (
<span className="flex justify-between items-center">
{text} <VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
</span>
);
};
@@ -1,3 +1,2 @@
export * from './market-selector';
export * from './market-selector-item';
export * from './market-selector-button';
@@ -1,23 +0,0 @@
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ButtonHTMLAttributes } from 'react';
import { forwardRef } from 'react';
export const MarketSelectorButton = forwardRef<
HTMLButtonElement,
ButtonHTMLAttributes<HTMLButtonElement>
>((props, ref) => (
<button
{...props}
className={classNames(
'flex items-center justify-between px-2 border rounded gap-1',
'border-vega-clight-600 dark:border-vega-cdark-600 bg-vega-clight-700 dark:bg-vega-cdark-700',
'text-secondary data-[state=open]:text-vega-clight-50 dark:data-[state=open]:text-vega-cdark-50'
)}
ref={ref}
>
{props.children}
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
</button>
));
MarketSelectorButton.displayName = 'MarketSelectorButton';
@@ -31,7 +31,7 @@ export const MarketSelectorItem = ({
<div style={style} role="row">
<Link
to={`/markets/${market.id}`}
className={classNames('h-full flex items-center gap-2 mx-2 px-2', {
className={classNames('h-full flex items-center gap-2 px-4', {
'hover:bg-vega-clight-700 dark:hover:bg-vega-cdark-700':
market.id !== currentMarketId,
'bg-vega-clight-600 dark:bg-vega-cdark-600':
@@ -94,7 +94,7 @@ const MarketData = ({
return (
<>
<div className="w-2/5" role="gridcell">
<h3 className="overflow-hidden text-sm text-ellipsis lg:text-base whitespace-nowrap">
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
{market.tradableInstrument.instrument.code}{' '}
{allProducts && productType && (
<MarketProductPill productType={productType} />
@@ -107,7 +107,7 @@ const MarketData = ({
)}
</div>
<div
className="w-1/5 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis"
className="w-1/5 text-xs lg:text-sm whitespace-nowrap text-ellipsis overflow-hidden"
title={instrument.product.settlementAsset.symbol}
data-testid="market-selector-price"
role="gridcell"
@@ -115,14 +115,14 @@ const MarketData = ({
{price} {instrument.product.settlementAsset.symbol}
</div>
<div
className="w-1/5 overflow-hidden text-xs text-right lg:text-sm whitespace-nowrap text-ellipsis"
className="w-1/5 text-xs lg:text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
title={t('24h vol')}
data-testid="market-selector-volume"
role="gridcell"
>
{volume}
</div>
<div className="flex justify-end w-1/5" role="gridcell">
<div className="w-1/5 flex justify-end" role="gridcell">
{oneDayCandles && (
<Sparkline
width={64}
@@ -262,7 +262,9 @@ describe('MarketSelector', () => {
await userEvent.click(screen.getByTestId('sort-trigger'));
const options = screen.getAllByTestId(/sort-item/);
expect(options.map((o) => o.textContent?.trim())).toEqual(
Object.entries(Sort).map(([key]) => SortTypeMapping[key as SortType])
Object.entries(Sort)
.filter(([key]) => key !== Sort.None)
.map(([key]) => SortTypeMapping[key as SortType])
);
await userEvent.click(screen.getByTestId('sort-item-Gained'));
expect(
@@ -40,7 +40,7 @@ export const MarketSelector = ({
const [filter, setFilter] = useState<Filter>({
searchTerm: '',
product: Product.All,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
const allProducts = filter.product === Product.All;
@@ -52,8 +52,8 @@ export const MarketSelector = ({
}, [reload]);
return (
<div data-testid="market-selector" className="md:w-[580px]">
<div className="px-2 pt-2 mb-2">
<div data-testid="market-selector">
<div className="pt-2 px-2 mb-2">
<ProductSelector
product={filter.product}
onSelect={(product) => {
@@ -106,6 +106,9 @@ export const MarketSelector = ({
currentSort={filter.sort}
onSelect={(sort) => {
setFilter((curr) => {
if (curr.sort === sort) {
return { ...curr, sort: Sort.None };
}
return {
...curr,
sort,
@@ -291,9 +294,9 @@ const List = ({
const Skeleton = () => {
return (
<div className="px-2 mb-2">
<div className="p-4 rounded-lg bg-vega-light-100 dark:bg-vega-dark-100">
<div className="w-full h-3 mb-2 bg-vega-light-200 dark:bg-vega-dark-200" />
<div className="mb-2 px-2">
<div className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4">
<div className="w-full h-3 bg-vega-light-200 dark:bg-vega-dark-200 mb-2" />
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
</div>
</div>
@@ -33,14 +33,11 @@ export const ProductSelector = ({
return (
<div className="flex mb-2">
{Object.keys(Product).map((t) => {
const classes = classNames(
'text-sm px-3 py-1.5 rounded hover:text-vega-clight-50 dark:hover:text-vega-cdark-50',
{
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
t === product,
'text-secondary': t !== product,
}
);
const classes = classNames('px-3 py-1.5 rounded', {
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
t === product,
'text-secondary': t !== product,
});
return (
<button
key={t}
@@ -56,7 +53,7 @@ export const ProductSelector = ({
})}
<Link
to={Routes.MARKETS}
className="flex items-center ml-auto text-sm gap-2"
className="flex items-center gap-2 ml-auto"
title={t('See all markets')}
>
<span className="underline underline-offset-4">{t('Browse')}</span>
@@ -1,16 +1,17 @@
import { t } from '@vegaprotocol/i18n';
import {
TradingDropdown,
TradingDropdownContent,
TradingDropdownItemIndicator,
TradingDropdownRadioGroup,
TradingDropdownRadioItem,
TradingDropdownTrigger,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItemIndicator,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { MarketSelectorButton } from './market-selector-button';
export const Sort = {
None: 'None',
Gained: 'Gained',
Lost: 'Lost',
New: 'New',
@@ -22,15 +23,17 @@ export type SortType = keyof typeof Sort;
export const SortTypeMapping: {
[key in SortType]: string;
} = {
[Sort.TopTraded]: 'Top traded',
[Sort.None]: 'None',
[Sort.Gained]: 'Top gaining',
[Sort.Lost]: 'Top losing',
[Sort.New]: 'New markets',
[Sort.TopTraded]: 'Top traded',
};
const SortIconMapping: {
[key in SortType]: VegaIconNames;
} = {
[Sort.None]: null as unknown as VegaIconNames, // not shown in list
[Sort.Gained]: VegaIconNames.TREND_UP,
[Sort.Lost]: VegaIconNames.TREND_DOWN,
[Sort.New]: VegaIconNames.STAR,
@@ -45,38 +48,43 @@ export const SortDropdown = ({
onSelect: (sort: SortType) => void;
}) => {
return (
<TradingDropdown
<DropdownMenu
trigger={
<TradingDropdownTrigger data-testid="sort-trigger">
<MarketSelectorButton>
{SortTypeMapping[currentSort]}
</MarketSelectorButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger data-testid="sort-trigger">
<span className="flex justify-between items-center">
{currentSort === SortTypeMapping.None
? t('Sort')
: SortTypeMapping[currentSort]}{' '}
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
</span>
</DropdownMenuTrigger>
}
>
<TradingDropdownContent>
<TradingDropdownRadioGroup
<DropdownMenuContent>
<DropdownMenuRadioGroup
value={currentSort}
onValueChange={(value) => onSelect(value as SortType)}
>
{Object.keys(Sort).map((key) => {
return (
<TradingDropdownRadioItem
inset
key={key}
value={key}
data-testid={`sort-item-${key}`}
>
<span className="flex gap-2">
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
{SortTypeMapping[key as SortType]}
</span>
<TradingDropdownItemIndicator />
</TradingDropdownRadioItem>
);
})}
</TradingDropdownRadioGroup>
</TradingDropdownContent>
</TradingDropdown>
{Object.keys(Sort)
.filter((s) => s !== Sort.None)
.map((key) => {
return (
<DropdownMenuRadioItem
inset
key={key}
value={key}
data-testid={`sort-item-${key}`}
>
<span className="flex gap-2">
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
{SortTypeMapping[key as SortType]}
</span>
<DropdownMenuItemIndicator />
</DropdownMenuRadioItem>
);
})}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
};
@@ -12,10 +12,7 @@ import { useMarketList } from '@vegaprotocol/markets';
import type { Filter } from '../../components/market-selector';
import { subDays } from 'date-fns';
jest.mock('@vegaprotocol/markets', () => ({
...jest.requireActual('@vegaprotocol/markets'),
useMarketList: jest.fn(),
}));
jest.mock('@vegaprotocol/markets');
const mockUseMarketList = useMarketList as jest.Mock;
describe('useMarketSelectorList', () => {
@@ -23,7 +20,7 @@ describe('useMarketSelectorList', () => {
const defaultArgs: Filter = {
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
};
return renderHook((args) => useMarketSelectorList(args), {
@@ -112,21 +109,21 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Spot as 'Future',
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[1]]);
rerender({
searchTerm: '',
product: Product.Perpetual as 'Future',
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[2]]);
rerender({
searchTerm: '',
product: Product.All,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual(markets);
@@ -192,7 +189,7 @@ describe('useMarketSelectorList', () => {
const { result, rerender } = setup({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-0'],
});
expect(result.current.markets).toEqual([markets[0], markets[1]]);
@@ -200,7 +197,7 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-0', 'asset-1'],
});
@@ -213,7 +210,7 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-0', 'asset-1', 'asset-2'],
});
@@ -223,7 +220,7 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: ['asset-invalid'],
});
@@ -278,28 +275,28 @@ describe('useMarketSelectorList', () => {
const { result, rerender } = setup({
searchTerm: 'abc',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[0]]);
rerender({
searchTerm: 'def',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[1], markets[2]]);
rerender({
searchTerm: 'defg',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[2]]);
rerender({
searchTerm: 'zzz',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([]);
@@ -308,14 +305,14 @@ describe('useMarketSelectorList', () => {
rerender({
searchTerm: 'aaa',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([markets[0]]);
rerender({
searchTerm: 'ggg',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([
@@ -325,15 +322,11 @@ describe('useMarketSelectorList', () => {
]);
});
it('sorts by top traded by default', () => {
it('sorts by state and volume by default', () => {
const markets = [
createMarketFragment({
id: 'market-0',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
state: MarketState.STATE_PENDING,
// @ts-ignore candles not on fragment
candles: [
{
@@ -344,10 +337,16 @@ describe('useMarketSelectorList', () => {
createMarketFragment({
id: 'market-1',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
// @ts-ignore candles not on fragment
candles: [
{
volume: '200',
},
],
}),
createMarketFragment({
id: 'market-2',
state: MarketState.STATE_ACTIVE,
// @ts-ignore candles not on fragment
candles: [
{
@@ -356,30 +355,12 @@ describe('useMarketSelectorList', () => {
],
}),
createMarketFragment({
id: 'market-2',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
// @ts-ignore candles not on fragment
candles: [
{
volume: '300',
},
],
}),
createMarketFragment({
state: MarketState.STATE_PENDING,
id: 'market-3',
state: MarketState.STATE_ACTIVE,
// @ts-ignore data not on fragment
data: {
markPrice: '1',
},
// @ts-ignore candles not on fragment
candles: [
{
volume: '400',
volume: '100',
},
],
}),
@@ -394,15 +375,14 @@ describe('useMarketSelectorList', () => {
const { result } = setup({
searchTerm: '',
product: Product.Future,
sort: Sort.TopTraded,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual([
markets[3],
markets[1],
markets[2],
markets[0],
markets[1],
markets[3],
]);
});
@@ -1,7 +1,11 @@
import { useMemo } from 'react';
import orderBy from 'lodash/orderBy';
import { MarketState } from '@vegaprotocol/types';
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
import {
calcCandleVolume,
calcTradedFactor,
useMarketList,
} from '@vegaprotocol/markets';
import { priceChangePercentage } from '@vegaprotocol/utils';
import type { Filter } from '../../components/market-selector/market-selector';
import { Sort } from './sort-dropdown';
@@ -56,6 +60,22 @@ export const useMarketSelectorList = ({
return false;
});
if (sort === Sort.None) {
// Sort by market state primarily and AtoZ secondarily
return orderBy(
markets,
[
(m) => MARKET_TEMPLATE.indexOf(m.state),
(m) => {
if (!m.candles?.length) return 0;
const vol = calcCandleVolume(m.candles);
return Number(vol || 0);
},
],
['asc', 'desc']
);
}
if (sort === Sort.Gained || sort === Sort.Lost) {
const dir = sort === Sort.Gained ? 'desc' : 'asc';
return orderBy(
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { usePositionsStore } from '../positions-container';
export const PositionsMenu = () => {
@@ -7,6 +7,7 @@ export const PositionsMenu = () => {
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={toggle}
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const WithdrawalsMenu = () => {
@@ -7,6 +7,7 @@ export const WithdrawalsMenu = () => {
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Withdraw })}
data-testid="withdraw-dialog-button"
@@ -7,8 +7,8 @@ describe('CandlesMenu', () => {
render(<CandlesMenu />);
await userEvent.click(
screen.getByRole('button', {
name: 'Studies',
screen.getByText('Studies', {
selector: '[type="button"]',
})
);
expect(await screen.findByRole('menu')).toBeInTheDocument();
+51 -61
View File
@@ -10,14 +10,13 @@ import {
studyLabels,
} from 'pennant';
import {
TradingButton,
TradingDropdown,
TradingDropdownCheckboxItem,
TradingDropdownContent,
TradingDropdownItemIndicator,
TradingDropdownRadioGroup,
TradingDropdownRadioItem,
TradingDropdownTrigger,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItemIndicator,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
Icon,
} from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@blueprintjs/icons';
@@ -45,76 +44,69 @@ export const CandlesMenu = () => {
} = useCandlesChartSettings();
const triggerClasses = 'text-xs';
const contentAlign = 'end';
const triggerButtonProps = { size: 'extra-small' } as const;
return (
<>
<TradingDropdown
<DropdownMenu
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
{t(`Interval: ${intervalLabels[interval]}`)}
</TradingButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger className={triggerClasses}>
{t(`Interval: ${intervalLabels[interval]}`)}
</DropdownMenuTrigger>
}
>
<TradingDropdownContent align={contentAlign}>
<TradingDropdownRadioGroup
<DropdownMenuContent align={contentAlign}>
<DropdownMenuRadioGroup
value={interval}
onValueChange={(value) => {
setInterval(value as Interval);
}}
>
{Object.values(Interval).map((timeInterval) => (
<TradingDropdownRadioItem
<DropdownMenuRadioItem
key={timeInterval}
inset
value={timeInterval}
>
{intervalLabels[timeInterval]}
<TradingDropdownItemIndicator />
</TradingDropdownRadioItem>
<DropdownMenuItemIndicator />
</DropdownMenuRadioItem>
))}
</TradingDropdownRadioGroup>
</TradingDropdownContent>
</TradingDropdown>
<TradingDropdown
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
<Icon name={chartTypeIcon.get(chartType) as IconName} />
</TradingButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger className={triggerClasses}>
<Icon name={chartTypeIcon.get(chartType) as IconName} />
</DropdownMenuTrigger>
}
>
<TradingDropdownContent align={contentAlign}>
<TradingDropdownRadioGroup
<DropdownMenuContent align={contentAlign}>
<DropdownMenuRadioGroup
value={chartType}
onValueChange={(value) => {
setType(value as ChartType);
}}
>
{Object.values(ChartType).map((type) => (
<TradingDropdownRadioItem key={type} inset value={type}>
<DropdownMenuRadioItem key={type} inset value={type}>
{chartTypeLabels[type]}
<TradingDropdownItemIndicator />
</TradingDropdownRadioItem>
<DropdownMenuItemIndicator />
</DropdownMenuRadioItem>
))}
</TradingDropdownRadioGroup>
</TradingDropdownContent>
</TradingDropdown>
<TradingDropdown
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
{t('Overlays')}
</TradingButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger className={triggerClasses}>
{t('Overlays')}
</DropdownMenuTrigger>
}
>
<TradingDropdownContent align={contentAlign}>
<DropdownMenuContent align={contentAlign}>
{Object.values(Overlay).map((overlay) => (
<TradingDropdownCheckboxItem
<DropdownMenuCheckboxItem
key={overlay}
checked={overlays.includes(overlay)}
onCheckedChange={() => {
@@ -129,23 +121,21 @@ export const CandlesMenu = () => {
}}
>
{overlayLabels[overlay]}
<TradingDropdownItemIndicator />
</TradingDropdownCheckboxItem>
<DropdownMenuItemIndicator />
</DropdownMenuCheckboxItem>
))}
</TradingDropdownContent>
</TradingDropdown>
<TradingDropdown
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
{t('Studies')}
</TradingButton>
</TradingDropdownTrigger>
<DropdownMenuTrigger className={triggerClasses}>
{t('Studies')}
</DropdownMenuTrigger>
}
>
<TradingDropdownContent align={contentAlign}>
<DropdownMenuContent align={contentAlign}>
{Object.values(Study).map((study) => (
<TradingDropdownCheckboxItem
<DropdownMenuCheckboxItem
key={study}
checked={studies.includes(study)}
onCheckedChange={() => {
@@ -160,11 +150,11 @@ export const CandlesMenu = () => {
}}
>
{studyLabels[study]}
<TradingDropdownItemIndicator />
</TradingDropdownCheckboxItem>
<DropdownMenuItemIndicator />
</DropdownMenuCheckboxItem>
))}
</TradingDropdownContent>
</TradingDropdown>
</DropdownMenuContent>
</DropdownMenu>
</>
);
};
@@ -21,8 +21,6 @@ import {
import * as positionsTools from '@vegaprotocol/positions';
import { OrdersDocument } from '@vegaprotocol/orders';
import { formatForInput } from '@vegaprotocol/utils';
import type { PartialDeep } from 'type-fest';
import type { Market } from '@vegaprotocol/markets';
jest.mock('zustand');
jest.mock('./deal-ticket-fee-details', () => ({
@@ -38,19 +36,12 @@ const market = generateMarket();
const marketData = generateMarketData();
const submit = jest.fn();
function generateJsx(
mocks: MockedResponse[] = [],
marketOverrides: PartialDeep<Market> = {}
) {
const joinedMarket: Market = {
...market,
...marketOverrides,
} as Market;
function generateJsx(mocks: MockedResponse[] = []) {
return (
<MockedProvider mocks={[...mocks]}>
<VegaWalletContext.Provider value={{ pubKey, isReadOnly: false } as any}>
<DealTicket
market={joinedMarket}
market={market}
marketData={marketData}
marketPrice={marketPrice}
submit={submit}
@@ -376,6 +367,7 @@ describe('DealTicket', () => {
expect(screen.getByTestId('iceberg')).toBeDisabled();
});
// eslint-disable-next-line jest/no-disabled-tests
it('handles TIF select box dependent on order type', async () => {
render(generateJsx());
@@ -541,25 +533,6 @@ describe('DealTicket', () => {
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
});
it('validates size when positionDecimalPlaces is negative', async () => {
render(generateJsx([], { positionDecimalPlaces: -4 }));
const sizeErrorMessage = 'deal-ticket-error-message-size';
const sizeInput = 'order-size';
await userEvent.click(screen.getByTestId('place-order'));
// default value should be invalid
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
'Size cannot be lower than 10000'
);
await userEvent.type(screen.getByTestId(sizeInput), '10001');
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
'Size must be a multiple of 10000 for this market'
);
await userEvent.clear(screen.getByTestId(sizeInput));
await userEvent.type(screen.getByTestId(sizeInput), '10000');
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
});
it('validates iceberg field', async () => {
const peakSizeErrorMessage = 'deal-ticket-peak-error-message';
const minimumSizeErrorMessage = 'deal-ticket-minimum-error-message';
+1 -21
View File
@@ -4,6 +4,7 @@ import { getDateTimeFormat } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import type { Trade } from './fills-data-provider';
import { FillsTable, getFeesBreakdown } from './fills-table';
import { generateFill } from './test-helpers';
@@ -214,27 +215,6 @@ describe('FillsTable', () => {
).toBeInTheDocument();
});
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
const partyId = 'party-id';
const negativeDecimalPositionFill = generateFill({
...defaultFill,
market: {
...defaultFill.market,
positionDecimalPlaces: -4,
},
});
await act(async () => {
render(
<FillsTable partyId={partyId} rowData={[negativeDecimalPositionFill]} />
);
});
const sizeCell = screen
.getAllByRole('gridcell')
.find((c) => c.getAttribute('col-id') === 'size');
expect(sizeCell).toHaveTextContent('3,000,000,000');
});
describe('getFeesBreakdown', () => {
it('should return correct fees breakdown for a taker', () => {
const fees = {
@@ -7,7 +7,7 @@ import {
TradingDropdownContent,
TradingDropdownItem,
} from '@vegaprotocol/ui-toolkit';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { formatNumberFixed } from '@vegaprotocol/utils';
export const OrderbookControls = ({
lastTradedPrice,
@@ -15,14 +15,27 @@ export const OrderbookControls = ({
decimalPlaces,
setResolution,
}: {
lastTradedPrice: string;
lastTradedPrice: string | undefined;
resolution: number;
decimalPlaces: number;
setResolution: (resolution: number) => void;
}) => {
const [isOpen, setOpen] = useState(false);
const resolutions = createResolutions(lastTradedPrice, decimalPlaces);
const resolutions = new Array(
Math.max(lastTradedPrice?.toString().length ?? 0, decimalPlaces + 1)
)
.fill(null)
.map((v, i) => Math.pow(10, i));
const formatResolution = (r: number) => {
return formatNumberFixed(
Math.log10(r) - decimalPlaces > 0
? Math.pow(10, Math.log10(r) - decimalPlaces)
: 0,
decimalPlaces - Math.log10(r)
);
};
const increaseResolution = () => {
const index = resolutions.indexOf(resolution);
@@ -43,7 +56,7 @@ export const OrderbookControls = ({
<button
onClick={increaseResolution}
disabled={resolutions.indexOf(resolution) >= resolutions.length - 1}
className="flex items-center px-2 border-r cursor-pointer border-default disabled:cursor-default"
className="flex items-center px-2 border-r cursor-pointer border-default"
data-testid="plus-button"
>
<VegaIcon size={12} name={VegaIconNames.PLUS} />
@@ -54,14 +67,12 @@ export const OrderbookControls = ({
trigger={
<TradingDropdownTrigger data-testid="resolution">
<button
className="flex items-center justify-between px-2 gap-1"
className="flex items-center px-2 text-left gap-1"
style={{
minWidth: `${
Math.max.apply(
null,
resolutions.map(
(item) => formatResolution(item, decimalPlaces).length
)
resolutions.map((item) => formatResolution(item).length)
) + 5
}ch`,
}}
@@ -72,19 +83,15 @@ export const OrderbookControls = ({
isOpen ? VegaIconNames.CHEVRON_UP : VegaIconNames.CHEVRON_DOWN
}
/>
{formatResolution(resolution, decimalPlaces)}
{formatResolution(resolution)}
</button>
</TradingDropdownTrigger>
}
>
<TradingDropdownContent align="start">
{resolutions.map((r) => (
<TradingDropdownItem
key={r}
onClick={() => setResolution(r)}
className="justify-end"
>
{formatResolution(r, decimalPlaces)}
<TradingDropdownItem key={r} onClick={() => setResolution(r)}>
{formatResolution(r)}
</TradingDropdownItem>
))}
</TradingDropdownContent>
@@ -92,7 +99,7 @@ export const OrderbookControls = ({
<button
onClick={decreaseResolution}
disabled={resolutions.indexOf(resolution) <= 0}
className="flex items-center px-2 cursor-pointer border-x border-default disabled:cursor-default"
className="flex items-center px-2 cursor-pointer border-x border-default"
data-testid="minus-button"
>
<VegaIcon size={12} name={VegaIconNames.MINUS} />
@@ -100,49 +107,3 @@ export const OrderbookControls = ({
</div>
);
};
export const formatResolution = (r: number, decimalPlaces: number) => {
let num = addDecimalsFormatNumber(r, decimalPlaces);
// Remove trailing zeroes
num = num.replace(/\.?0+$/, '');
return num;
};
/**
* Create a list of resolutions based on the largest and smallest
* possible values using the last traded price and the market
* decimal places
*/
export const createResolutions = (
lastTradedPrice: string,
decimalPlaces: number
) => {
// number of levels determined by either the number
// of digits in the last traded price OR the number of decimal
// places. For example:
//
// last traded = 1 (0.001)
// dps = 3
// result = 3
//
// last traded = 100001 (1000.01
// dps = 2
// result = 6
const levelCount = Math.max(lastTradedPrice.length ?? 0, decimalPlaces + 1);
const generatedResolutions = new Array(levelCount)
.fill(null)
.map((_, i) => Math.pow(10, i));
const customResolutions = [2, 5, 20, 50, 200, 500];
const combined = customResolutions.concat(generatedResolutions);
combined.sort((a, b) => a - b);
// Remove any resolutions higher than the generated ones as
// we dont want a custom resolution higher than necessary
const resolutions = combined.filter((r) => {
return r <= generatedResolutions[generatedResolutions.length - 1];
});
return resolutions;
};
+2 -2
View File
@@ -12,7 +12,7 @@ export interface OrderbookRowData {
cumulativeVol: number;
}
export const getPriceLevel = (price: string, resolution: number) => {
export const getPriceLevel = (price: string | bigint, resolution: number) => {
const p = BigInt(price);
const r = BigInt(resolution);
let priceLevel = (p / r) * r;
@@ -43,7 +43,7 @@ const updateCumulativeVolumeByType = (
};
export const compactRows = (
data: PriceLevelFieldsFragment[],
data: PriceLevelFieldsFragment[] | null | undefined,
dataType: VolumeType,
resolution: number
) => {
+1 -5
View File
@@ -13,7 +13,6 @@ interface OrderbookRowProps {
cumulativeVolume: number;
decimalPlaces: number;
positionDecimalPlaces: number;
priceFormatDecimalPlaces: number;
price: string;
onClick: (args: { price?: string; size?: string }) => void;
type: VolumeType;
@@ -27,7 +26,6 @@ export const OrderbookRow = memo(
cumulativeVolume,
decimalPlaces,
positionDecimalPlaces,
priceFormatDecimalPlaces,
price,
onClick,
type,
@@ -37,7 +35,6 @@ export const OrderbookRow = memo(
const txtId = type === VolumeType.bid ? 'bid' : 'ask';
const cols =
width >= HIDE_CUMULATIVE_VOL_WIDTH ? 3 : width >= HIDE_VOL_WIDTH ? 2 : 1;
return (
<div className="relative px-1">
<CumulationBar
@@ -57,8 +54,7 @@ export const OrderbookRow = memo(
value={BigInt(price)}
valueFormatted={addDecimalsFixedFormatNumber(
price,
decimalPlaces,
priceFormatDecimalPlaces
decimalPlaces
)}
className={classNames({
'text-market-red dark:text-market-red': type === VolumeType.ask,
+16 -79
View File
@@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event';
import { generateMockData, VolumeType } from './orderbook-data';
import { Orderbook, OrderbookMid } from './orderbook';
import * as orderbookData from './orderbook-data';
import { createResolutions, formatResolution } from './orderbook-controls';
function mockOffsetSize(width: number, height: number) {
Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', {
@@ -38,8 +37,7 @@ describe('Orderbook', () => {
jest.clearAllMocks();
mockOffsetSize(800, 768);
});
it('lastTradedPrice should be in the middle', async () => {
it('markPrice should be in the middle', async () => {
render(
<Orderbook
decimalPlaces={decimalPlaces}
@@ -73,7 +71,6 @@ describe('Orderbook', () => {
expect(
await screen.findByTestId(`last-traded-${params.lastTradedPrice}`)
).toBeInTheDocument();
// Before resolution change the price is 122.934
await userEvent.click(screen.getByTestId('price-122901'));
expect(onClickSpy).toBeCalledWith({ price: '122.901' });
@@ -89,16 +86,15 @@ describe('Orderbook', () => {
expect(orderbookData.compactRows).toHaveBeenCalledWith(
mockedData.bids,
VolumeType.bid,
2
10
);
expect(orderbookData.compactRows).toHaveBeenCalledWith(
mockedData.asks,
VolumeType.ask,
2
10
);
await userEvent.click(screen.getByTestId('price-122938'));
expect(onClickSpy).toBeCalledWith({ price: '122.938' });
await userEvent.click(screen.getByTestId('price-12294'));
expect(onClickSpy).toBeCalledWith({ price: '122.94' });
});
it('plus - minus buttons should change resolution', async () => {
@@ -118,30 +114,26 @@ describe('Orderbook', () => {
1
);
expect(screen.getByTestId('minus-button')).toBeDisabled();
await userEvent.click(screen.getByTestId('plus-button'));
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
2
);
await userEvent.click(screen.getByTestId('plus-button'));
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
5
);
expect(screen.getByTestId('minus-button')).not.toBeDisabled();
await userEvent.click(screen.getByTestId('minus-button'));
userEvent.click(screen.getByTestId('plus-button'));
await waitFor(() => {
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
2
10
);
});
expect(screen.getByTestId('minus-button')).not.toBeDisabled();
userEvent.click(screen.getByTestId('minus-button'));
await waitFor(() => {
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
1
);
});
expect(screen.getByTestId('minus-button')).toBeDisabled();
await userEvent.click(screen.getByTestId('resolution'));
await waitFor(() => {
expect(screen.getByRole('menu')).toBeInTheDocument();
});
await userEvent.click(screen.getAllByRole('menuitem')[11]);
await userEvent.click(screen.getAllByRole('menuitem')[5]);
await waitFor(() => {
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
100000
@@ -231,58 +223,3 @@ describe('OrderbookMid', () => {
expect(screen.getByTestId('icon-arrow-down')).toBeInTheDocument();
});
});
describe('createResolutions', () => {
it('create resolutions relative to the market', () => {
expect(
createResolutions(
'1', // 0.001
3
)
).toEqual([1, 2, 5, 10, 20, 50, 100, 200, 500, 1000]);
expect(
createResolutions(
'190017', // 1900.17
2
)
).toEqual([1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 10000, 100000]);
expect(
createResolutions(
'123456789', // 1234.56789
5
)
).toEqual([
1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 10000, 100000, 1000000,
10000000, 100000000,
]);
});
it('removes resolutions that arent precise enough for the market', () => {
expect(
createResolutions(
'1', // 0.01
2
)
).toEqual([1, 2, 5, 10, 20, 50, 100]);
});
});
describe('formatResolution', () => {
it('formats less than 1', () => {
expect(formatResolution(1, 2)).toEqual('0.01');
expect(formatResolution(1, 3)).toEqual('0.001');
expect(formatResolution(2, 4)).toEqual('0.0002');
expect(formatResolution(5, 8)).toEqual('0.00000005');
expect(formatResolution(10000, 5)).toEqual('0.1');
});
it('formats greater than 1', () => {
expect(formatResolution(1000, 2)).toEqual('10');
expect(formatResolution(100000, 4)).toEqual('10');
expect(formatResolution(10000000, 2)).toEqual('100,000');
expect(formatResolution(500, 2)).toEqual('5');
expect(formatResolution(500, 1)).toEqual('50');
});
});
+2 -13
View File
@@ -23,7 +23,6 @@ const OrderbookSide = ({
type,
decimalPlaces,
positionDecimalPlaces,
priceFormatDecimalPlaces,
onClick,
width,
maxVol,
@@ -32,7 +31,6 @@ const OrderbookSide = ({
resolution: number;
decimalPlaces: number;
positionDecimalPlaces: number;
priceFormatDecimalPlaces: number;
type: VolumeType;
onClick: (args: { price?: string; size?: string }) => void;
width: number;
@@ -55,11 +53,10 @@ const OrderbookSide = ({
{rows.map((data) => (
<OrderbookRow
key={data.price}
price={data.price}
price={(BigInt(data.price) / BigInt(resolution)).toString()}
onClick={onClick}
decimalPlaces={decimalPlaces}
decimalPlaces={decimalPlaces - Math.log10(resolution)}
positionDecimalPlaces={positionDecimalPlaces}
priceFormatDecimalPlaces={priceFormatDecimalPlaces}
volume={data.volume}
cumulativeVolume={data.cumulativeVol}
type={type}
@@ -168,12 +165,6 @@ export const Orderbook = ({
const bestAskPrice = asks[0] ? asks[0].price : '0';
const bestBidPrice = bids[0] ? bids[0].price : '0';
// we'll want to only display a relevant number of dps based on the
// current resolution selection
const priceFormatDecimalPlaces = Math.ceil(
decimalPlaces - Math.log10(resolution)
);
return (
<div className="h-full text-xs grid grid-rows-[1fr_min-content]">
<div>
@@ -212,7 +203,6 @@ export const Orderbook = ({
resolution={resolution}
decimalPlaces={decimalPlaces}
positionDecimalPlaces={positionDecimalPlaces}
priceFormatDecimalPlaces={priceFormatDecimalPlaces}
onClick={onClick}
width={width}
maxVol={maxVol}
@@ -230,7 +220,6 @@ export const Orderbook = ({
resolution={resolution}
decimalPlaces={decimalPlaces}
positionDecimalPlaces={positionDecimalPlaces}
priceFormatDecimalPlaces={priceFormatDecimalPlaces}
onClick={onClick}
width={width}
maxVol={maxVol}
+1 -1
View File
@@ -199,7 +199,7 @@ export const allMarketsWithLiveDataProvider = makeDerivedDataProvider<
return data.find(
(market) =>
market.id ===
(parts[1].delta as MarketDataUpdateFieldsFragment)?.marketId
(parts[1].delta as MarketDataUpdateFieldsFragment).marketId
);
}
);
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n';
import { TradingButton } from '@vegaprotocol/ui-toolkit';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
import { useHasAmendableOrder } from '../../order-hooks';
@@ -28,7 +28,12 @@ export const OpenOrdersMenu = ({ marketId }: { marketId: string }) => {
};
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
<TradingButton size="extra-small" onClick={onClick} data-testid="cancelAll">
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={onClick}
data-testid="cancelAll"
>
{t('Cancel all')}
</TradingButton>
);
@@ -6,7 +6,7 @@ import type { PartialDeep } from 'type-fest';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing';
import type { Order, OrderFieldsFragment, OrderListTableProps } from '../';
import type { OrderFieldsFragment, OrderListTableProps } from '../';
import { OrderListTable } from '../';
import {
generateOrder,
@@ -164,24 +164,6 @@ describe('OrderListTable', () => {
);
});
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
const localMarketOrder = {
...marketOrder,
size: '3000',
market: {
...marketOrder.market,
positionDecimalPlaces: -4,
},
} as Order;
await act(async () => {
render(generateJsx({ rowData: [localMarketOrder] }));
});
const cells = screen.getAllByRole('gridcell');
expect(cells[2]).toHaveTextContent('+30,000,000');
});
describe('amend cell', () => {
it('allows cancelling and editing for permitted orders', async () => {
const mockEdit = jest.fn();
@@ -201,19 +201,6 @@ describe('Positions', () => {
).not.toBeInTheDocument();
});
it('handle negative positionDecimalPlaces', async () => {
await renderComponent({
...singleRow,
openVolume: '-2000',
positionDecimalPlaces: -4,
});
const cells = screen.getAllByRole('gridcell');
const cell = cells[1];
expect(within(cell).getByTestId('stack-cell-primary')).toHaveTextContent(
'-20,000,000'
);
});
describe('PNLCell', () => {
const props = {
data: undefined,
@@ -26,36 +26,22 @@ export const ProtocolUpgradeInProgressNotification = () => {
const [nextUpgrade] = useLocalStorageSnapshot(
NEXT_PROTOCOL_UPGRADE_PROPOSAL_SNAPSHOT
);
const { blocksRising, block } = useBlockRising();
const detailsLink = useProtocolUpgradeProposalLink();
let vegaReleaseTag: string | undefined;
let upgradeBlockHeight: string | undefined;
const hasData = data && !error;
const hasStoredData = nextUpgrade && ALLOW_STORED_PROPOSAL_DATA;
if (hasData) {
// gets tag and height from the data api
vegaReleaseTag = data.vegaReleaseTag;
upgradeBlockHeight = data.upgradeBlockHeight;
} else if (hasStoredData) {
// gets tag and height from stored value if data api is unavailable
if (error && !data && nextUpgrade && ALLOW_STORED_PROPOSAL_DATA) {
try {
const stored = JSON.parse(nextUpgrade) as StoredNextProtocolUpgradeData;
vegaReleaseTag = stored.vegaReleaseTag;
upgradeBlockHeight = stored.upgradeBlockHeight;
} catch {
// NOOP - could not parse stored data
// no op
}
}
const hasUpgradeInfo = vegaReleaseTag && upgradeBlockHeight;
const { blocksRising, block } = useBlockRising(
// skips querying blocks if there's no upgrade information available
!hasUpgradeInfo
);
/**
* If upgrade is in progress then none of the nodes should produce blocks,
* same should be with the tendermint block info otherwise it's a network
@@ -64,7 +50,10 @@ export const ProtocolUpgradeInProgressNotification = () => {
* Once the networks is back then the notification disappears.
*/
const upgradeInProgress =
hasUpgradeInfo && !blocksRising && block <= Number(upgradeBlockHeight);
vegaReleaseTag &&
upgradeBlockHeight &&
!blocksRising &&
block <= Number(upgradeBlockHeight);
if (!upgradeInProgress) return null;
@@ -15,33 +15,31 @@ const CHECK_INTERVAL = 5000; // ms
*/
const ALLOW_STALE = 2; // times -> MAX(this, 1) * CHECK_INTERVAL ~> min check time
export const useBlockRising = (skip = false) => {
export const useBlockRising = () => {
const [blocksRising, setBlocksRising] = useState(true);
const [block, setBlock] = useState(0);
const nodes = useEnvironment((state) => state.nodes);
const clients = useMemo(() => {
return nodes.map((n) => {
if (n && n.length > 0) {
const client = createClient({
return nodes.map(
(n) =>
n &&
n.length > 0 &&
createClient({
url: n,
cacheConfig: undefined,
retry: false,
connectToDevTools: false,
connectToHeaderStore: true,
});
return client;
}
return undefined;
});
})
);
}, [nodes]);
const { refetch: fetchBlockInfo } = useBlockInfo();
useEffect(() => {
if (skip) return;
let stale = 0;
let prev = 0;
const check = async () => {
const queries = clients.map((client) =>
const queries = clients.map((client, index) =>
client
? client
.query<BlockStatisticsQuery>({
@@ -49,16 +47,18 @@ export const useBlockRising = (skip = false) => {
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
})
.catch(() => {
// NOOP - could not retrieve statistics for that node (network error)
})
.catch((err) =>
Promise.reject(
`could not retrieve statistics from ${nodes[index]}`
)
)
: undefined
);
const blockInfo = await fetchBlockInfo();
const results = (await Promise.allSettled(compact(queries))).map(
(res) => {
if (res && res.status === 'fulfilled' && res.value) {
if (res && res.status === 'fulfilled') {
return res.value.data.statistics;
} else {
return undefined;
@@ -86,7 +86,7 @@ export const useBlockRising = (skip = false) => {
return () => {
clearInterval(interval);
};
}, [clients, fetchBlockInfo, blocksRising, nodes, skip]);
}, [clients, fetchBlockInfo, blocksRising, nodes]);
return { blocksRising, block };
};
@@ -5,6 +5,6 @@ import { toNumberParts } from '@vegaprotocol/utils';
export const useNumberParts = (
value: BigNumber | null | undefined,
decimals: number
): [integers: string, decimalPlaces: string, separator: string | undefined] => {
): [integers: string, decimalPlaces: string] => {
return useMemo(() => toNumberParts(value, decimals), [decimals, value]);
};
@@ -84,8 +84,10 @@ const getAffixElement = ({
'absolute z-10 top-0 bottom-0 flex items-center',
{
'fill-black dark:fill-white': prependIconName || appendIconName,
'left-3': prependIconName || prependElement,
'right-3': appendIconName || appendElement,
'left-3': prependIconName,
'right-3': appendIconName,
'left-1': prependElement,
'right-1': appendElement,
}
);
+8 -36
View File
@@ -7,8 +7,6 @@ import {
formatNumberPercentage,
getUnlimitedThreshold,
isNumeric,
removeDecimal,
toBigNum,
quantumDecimalPlaces,
toDecimal,
toNumberParts,
@@ -86,17 +84,17 @@ describe('number utils', () => {
describe('toNumberParts', () => {
it.each([
{ v: null, d: 3, o: ['0', '000', '.'] },
{ v: undefined, d: 3, o: ['0', '000', '.'] },
{ v: new BigNumber(123), d: 3, o: ['123', '00', '.'] },
{ v: new BigNumber(123.123), d: 3, o: ['123', '123', '.'] },
{ v: new BigNumber(123.123), d: 6, o: ['123', '123', '.'] },
{ v: new BigNumber(123.123), d: 0, o: ['123', '', '.'] },
{ v: new BigNumber(123), d: undefined, o: ['123', '00', '.'] },
{ v: null, d: 3, o: ['0', '000'] },
{ v: undefined, d: 3, o: ['0', '000'] },
{ v: new BigNumber(123), d: 3, o: ['123', '00'] },
{ v: new BigNumber(123.123), d: 3, o: ['123', '123'] },
{ v: new BigNumber(123.123), d: 6, o: ['123', '123'] },
{ v: new BigNumber(123.123), d: 0, o: ['123', ''] },
{ v: new BigNumber(123), d: undefined, o: ['123', '00'] },
{
v: new BigNumber(30000),
d: undefined,
o: ['30,000', '00', '.'],
o: ['30,000', '00'],
},
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
expect(toNumberParts(v, d)).toStrictEqual(o);
@@ -155,36 +153,10 @@ describe('number utils', () => {
{ v: 7, o: '0.0000001' },
{ v: 8, o: '0.00000001' },
{ v: 9, o: '0.000000001' },
{ v: -1, o: '10' },
{ v: -2, o: '100' },
{ v: -3, o: '1000' },
])('formats with toNumber given number correctly', ({ v, o }) => {
expect(toDecimal(v)).toStrictEqual(o);
});
});
describe('positive and negative decimals should be handled correctly', () => {
const baseNum = '2000';
const methods = [removeDecimal, toBigNum];
it.each([
{ decimals: 0, result: ['2000', '2000'] },
{ decimals: 1, result: ['20000', '200'] },
{ decimals: -1, result: ['200', '20000'] },
{ decimals: 2, result: ['200000', '20'] },
{ decimals: -2, result: ['20', '200000'] },
{ decimals: 3, result: ['2000000', '2'] },
{ decimals: -3, result: ['2', '2000000'] },
{ decimals: 4, result: ['20000000', '0.2'] },
{ decimals: -4, result: ['0', '20000000'] }, // removeDecimal has toFixed(0) at the end
])(
'number methods should handle negative decimals',
({ decimals, result }) => {
methods.forEach((method, i) => {
expect(method(baseNum, decimals).toString()).toEqual(result[i]);
});
}
);
});
});
describe('quantumDecimalPlaces', () => {
+6 -8
View File
@@ -21,7 +21,7 @@ const MAX_FRACTION_DIGITS = 20;
export function toDecimal(numberOfDecimals: number) {
return new BigNumber(1)
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
.dividedBy(Math.pow(10, numberOfDecimals))
.toString(10);
}
@@ -29,8 +29,7 @@ export function toBigNum(
rawValue: string | number,
decimals: number
): BigNumber {
const divides = new BigNumber(10).exponentiatedBy(decimals);
return new BigNumber(rawValue || 0).dividedBy(divides);
return new BigNumber(rawValue || 0).dividedBy(Math.pow(10, decimals));
}
export function addDecimal(
@@ -49,8 +48,7 @@ export function removeDecimal(
value: string | BigNumber,
decimals: number
): string {
const times = new BigNumber(10).exponentiatedBy(decimals);
return new BigNumber(value || 0).times(times).toFixed(0);
return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0);
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
@@ -165,15 +163,15 @@ export const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
export const toNumberParts = (
value: BigNumber | null | undefined,
decimals = 18
): [integers: string, decimalPlaces: string, separator: string] => {
): [integers: string, decimalPlaces: string] => {
if (!value) {
return ['0', '0'.repeat(decimals), '.'];
return ['0', '0'.repeat(decimals)];
}
const separator = getDecimalSeparator() || '.';
const [integers, decimalsPlaces] = formatNumber(value, decimals)
.toString()
.split(separator);
return [integers, decimalsPlaces || '', separator];
return [integers, decimalsPlaces || ''];
};
export const isNumeric = (
@@ -4,12 +4,6 @@ export const validateAmount = (step: number | string, field: string) => {
const [, stepDecimals = ''] = String(step).split('.');
return (value?: string) => {
if (Number(step) > 1) {
if (Number(value) % Number(step) > 0) {
return t(`${field} must be a multiple of ${step} for this market`);
}
return true;
}
const [, valueDecimals = ''] = (value || '').split('.');
if (stepDecimals.length < valueDecimals.length) {
if (stepDecimals === '') {
@@ -1,7 +1,6 @@
import classNames from 'classnames';
import {
Dialog,
ExternalLink,
Intent,
Pill,
TradingButton,
@@ -40,7 +39,7 @@ import { useVegaWallet } from '../use-vega-wallet';
import { InjectedConnectorForm } from './injected-connector-form';
import { isBrowserWalletInstalled } from '../utils';
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
import { SnapStatus, useSnapStatus } from '../use-snap-status';
import { useIsSnapRunning } from '../use-is-snap-running';
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
export const CLOSE_DELAY = 1700;
@@ -159,7 +158,7 @@ const ConnectDialogContainer = ({
appChainId
);
const snapStatus = useSnapStatus(
const isSnapRunning = useIsSnapRunning(
DEFAULT_SNAP_ID,
Boolean(connectors['snap'])
);
@@ -184,7 +183,7 @@ const ConnectDialogContainer = ({
setWalletUrl={setWalletUrl}
onSelect={handleSelect}
isDesktopWalletRunning={isDesktopWalletRunning}
snapStatus={snapStatus}
isSnapRunning={isSnapRunning}
/>
)}
</ConnectDialogContent>
@@ -199,14 +198,14 @@ const ConnectorList = ({
walletUrl,
setWalletUrl,
isDesktopWalletRunning,
snapStatus,
isSnapRunning,
}: {
connectors: Connectors;
onSelect: (type: WalletType) => void;
walletUrl: string;
setWalletUrl: (value: string) => void;
isDesktopWalletRunning: boolean | null;
snapStatus: SnapStatus;
isSnapRunning: boolean | null;
}) => {
const { pubKey, links } = useVegaWallet();
const title = isBrowserWalletInstalled()
@@ -250,7 +249,7 @@ const ConnectorList = ({
</div>
{connectors['snap'] !== undefined ? (
<div>
{snapStatus === SnapStatus.INSTALLED ? (
{isSnapRunning ? (
<ConnectionOption
type="snap"
text={
@@ -268,34 +267,22 @@ const ConnectorList = ({
}}
/>
) : (
<>
<ConnectionOption
type="snap"
disabled={snapStatus === SnapStatus.NOT_SUPPORTED}
text={
<>
<div className="flex items-center justify-center w-full h-full text-base gap-1">
{t('Install Vega MetaMask Snap')}
</div>
<div className="absolute top-0 flex items-center h-8 right-1">
<VegaIcon name={VegaIconNames.METAMASK} size={24} />
</div>
</>
}
onClick={() => {
requestSnap(DEFAULT_SNAP_ID);
}}
/>
{snapStatus === SnapStatus.NOT_SUPPORTED ? (
<p className="pt-2 text-sm text-default">
{t('No MetaMask version that supports snaps detected.')}{' '}
{t('Learn more about')}{' '}
<ExternalLink href="https://metamask.io/snaps/">
MetaMask Snaps
</ExternalLink>
</p>
) : null}
</>
<ConnectionOption
type="snap"
text={
<>
<div className="flex items-center justify-center w-full h-full text-base gap-1">
{t('Install Vega MetaMask Snap')}
</div>
<div className="absolute top-0 flex items-center h-8 right-1">
<VegaIcon name={VegaIconNames.METAMASK} size={24} />
</div>
</>
}
onClick={() => {
requestSnap(DEFAULT_SNAP_ID);
}}
/>
)}
</div>
) : null}
+8 -4
View File
@@ -118,10 +118,14 @@ export const getSnap = async (
snapId: string,
version?: string
): Promise<Snap | undefined> => {
const snaps = await getSnaps();
return Object.values(snaps).find(
(snap) => snap.id === snapId && (!version || snap.version === version)
);
try {
const snaps = await getSnaps();
return Object.values(snaps).find(
(snap) => snap.id === snapId && (!version || snap.version === version)
);
} catch (e) {
return undefined;
}
};
export const invokeSnap = async <T>(
+27
View File
@@ -0,0 +1,27 @@
import { useEffect, useState } from 'react';
import { getSnap } from './connectors';
const INTERVAL = 2_000;
export const useIsSnapRunning = (snapId: string, shouldCheck: boolean) => {
const [running, setRunning] = useState(false);
useEffect(() => {
if (!shouldCheck) return;
const checkState = async () => {
const snap = await getSnap(snapId);
setRunning(!!snap);
};
const i = setInterval(() => {
checkState();
}, INTERVAL);
checkState();
return () => {
clearInterval(i);
};
}, [snapId, shouldCheck]);
return running;
};
-37
View File
@@ -1,37 +0,0 @@
import { useEffect, useState } from 'react';
import { getSnap } from './connectors';
const INTERVAL = 2_000;
export enum SnapStatus {
NOT_SUPPORTED,
INSTALLED,
NOT_INSTALLED,
}
export const useSnapStatus = (snapId: string, shouldCheck: boolean) => {
const [status, setStatus] = useState<SnapStatus>(SnapStatus.NOT_INSTALLED);
useEffect(() => {
if (!shouldCheck) return;
const checkState = async () => {
try {
const snap = await getSnap(snapId);
setStatus(snap ? SnapStatus.INSTALLED : SnapStatus.NOT_INSTALLED);
} catch (err) {
setStatus(SnapStatus.NOT_SUPPORTED);
}
};
const i = setInterval(() => {
checkState();
}, INTERVAL);
checkState();
return () => {
clearInterval(i);
};
}, [snapId, shouldCheck]);
return status;
};
-2
View File
@@ -13,8 +13,6 @@ When looking to use Vega via a user interface e.g. Dapp (Decentralized web App),
- If there is not running desktop wallet or CLI detected, I can see that I need to open my wallet. (<a name="0002-WCON-0013" href="#0002-WCON-0013">0002-WCON-0013</a>)
- I can find out more about supported browsers i.e. there is a link (Issue "List compatible browsers" vegawallet-browser#360 has to be implemented). (<a name="0002-WCON-0013" href="#0002-WCON-0013">0002-WCON-0013</a>)
- I can find out more about the Vega Wallet and see what "other" versions there are i.e. there is a link to the page on the website (currently - https://vega.xyz/wallet#overview). (<a name="0002-WCON-0014" href="#0002-WCON-0014">0002-WCON-0014</a>)
- I can install and connect vega metamask snap wallet (<a name="0002-WCON-0015" href="#0002-WCON-0015">0002-WCON-0015</a>)
- Browser wallet:
- The browser extension you need is automatically detected if you are using Chrome or Firefox, presenting the specific call to action to install that browser extension in a visible way e.g. with a Chrome or Firefox icon. (<a name="0002-WCON-041" href="#0002-WCON-041">0002-WCON-041</a>)
+5 -4
View File
@@ -29,10 +29,11 @@
## When the popup has been dismissed:
- **Must** I can see the steps to get started with a visible call to action (according to my progress) in the context of the deal ticket, deposit, withdraw, transfer components in the sidebar (<a name="0007-FUGS-013" href="#0007-FUGS-013">0007-FUGS-013</a>)
- **Must** We've replaced "connect wallet" in the top right with "get started" (<a name="0007-FUGS-014" href="#0007-FUGS-014">0007-FUGS-014</a>)
- **Must** When I press the get started CTA, I see the wallet connect popup (<a name="0007-FUGS-015" href="#0007-FUGS-015">0007-FUGS-015</a>)
- **Must** If I have a wallet installed already I don't see this quick start onboarding, and instead call(s) to action in Console revert to connect wallet, not "get started" (button in nav header) (<a name="0007-FUGS-016" href="#0007-FUGS-016">0007-FUGS-016</a>)
- **Must** Remove buttons from pane containers that prompt to connect wallet (<a name="0007-FUGS-014" href="#0007-FUGS-014">0007-FUGS-014</a>)
- **Must** We've replaced "connect wallet" in the top right with "get started" (<a name="0007-FUGS-015" href="#0007-FUGS-015">0007-FUGS-015</a>)
- **Must** When I press the get started CTA, I see the wallet connect popup (<a name="0007-FUGS-016" href="#0007-FUGS-016">0007-FUGS-016</a>)
- **Must** If I have a wallet installed already I don't see this quick start onboarding, and instead call(s) to action in Console revert to connect wallet, not "get started" (button in nav header) (<a name="0007-FUGS-017" href="#0007-FUGS-017">0007-FUGS-017</a>)
## When onboarding process has been accomplished:
- I can see telemetry approval toast: on environment other than mainnet telemetry is enabled by default (<a name="0007-FUGS-018" href="#0007-FUGS-018">0007-FUGS-018</a>)
- I can see telemetry approval toast: on environment other than mainnet telemetry is enabled by default (<a name="0007-FUGS-019" href="#0007-FUGS-019">0007-FUGS-019</a>)
-23
View File
@@ -174,34 +174,11 @@ When populating a deal ticket I...
- **must** see that selecting "Post only" or "reduce only" de-select the other (if selected). As is it not possibly to apply them both to an order (<a name="7003-SORD-059" href="#7003-SORD-059">7003-SORD-059</a>)
- **must** see an option to select expire for stop order limit and market (<a name="7002-SORD-071" href="#7002-SORD-071">7002-SORD-071</a>)
- **must** select strategy for stop order limit and market (<a name="7002-SORD-072" href="#7002-SORD-072">7002-SORD-072</a>)
- **must** see strategy submit (<a name="7002-SORD-073" href="#7002-SORD-073">7002-SORD-073</a>)
if it is oco then submit strategy is not available (<a name="7002-SORD-081" href="#7002-SORD-081">7002-SORD-081</a>)
- **must** see strategy cancel (<a name="7002-SORD-074" href="#7002-SORD-074">7002-SORD-074</a>)
- **must** see expiry time/date (<a name="7002-SORD-075" href="#7002-SORD-075">7002-SORD-075</a>)
- **must** see an option to select oco for stop order limit and market (<a name="7002-SORD-082" href="#7002-SORD-082">7002-SORD-082</a>)
- **must** see a type to select market or limit (<a name="7002-SORD-083" href="#7002-SORD-083">7002-SORD-083</a>)
- **must** see for market type
- **must** see rises above (<a name="7002-SORD-084" href="#7002-SORD-084">7002-SORD-084</a>)
- **must** see falls below (<a name="7002-SORD-085" href="#7002-SORD-085">7002-SORD-085</a>)
- **must** enter a trigger value (<a name="7002-SORD-086" href="#7002-SORD-086">7002-SORD-086</a>)
- **must** select a trigger price (<a name="7002-SORD-087" href="#7002-SORD-087">7002-SORD-087</a>)
- **must** select a trigger trailing percent offset (<a name="7002-SORD-088" href="#7002-SORD-088">7002-SORD-088</a>)
- **must** see for limit type
- **must** enter a price (<a name="7002-SORD-089" href="#7002-SORD-089">7002-SORD-089</a>)
- **must** select a trigger direction (<a name="7002-SORD-090" href="#7002-SORD-090">7002-SORD-090</a>)
- **must** see rises above (<a name="7002-SORD-099" href="#7002-SORD-099">7002-SORD-099</a>)
- **must** see falls below (<a name="7002-SORD-091" href="#7002-SORD-091">7002-SORD-091</a>)
- if the user has not set a preference: trigger direction **must** default to `opposite to the one selected in the stop order` (<a name="7002-SORD-092" href="#7002-SORD-092">7002-SORD-092</a>)
- **must** enter a trigger value (<a name="7002-SORD-093" href="#7002-SORD-093">7002-SORD-093</a>)
- **must** select a trigger type (<a name="7002-SORD-094" href="#7002-SORD-094">7002-SORD-094</a>)
- **must** see a trigger price (<a name="7002-SORD-095" href="#7002-SORD-095">7002-SORD-095</a>)
- **must** see a trigger trailing percent offset (<a name="7002-SORD-096" href="#7002-SORD-096">7002-SORD-096</a>)
- if the user has not set a preference: trigger type **must** default to `price` (<a name="7002-SORD-097" href="#7002-SORD-097">7002-SORD-097</a>)
- if the trigger price is already met on submission, **must** warn the user that the stop order will be triggered immediately (<a name="7002-SORD-098" href="#7002-SORD-098">7002-SORD-098</a>)
... so I can control in more detail how my order is executed
## Auto Populating a deal ticket non-manual methods