Feat/637 Market settlement expiry (#1094)

* feat: add market expiry field to the trade grid header

* feat: add oracle spec linking to explorer page

* fix: oracle id to tooltip link

* fix: add dashed underlines to tooltip triggers

* fix: lint

* fix: format

* chore: merge master

* fix: readd type generation WIP

* fix: wording

Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>

* fix: refactor expiry to components

Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
This commit is contained in:
botond 2022-08-25 15:21:28 +01:00 committed by GitHub
parent e9019c04f7
commit eb2f4fd27c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 7 deletions

View File

@ -17,6 +17,7 @@ export const SubHeading = ({
'uppercase',
'mt-12',
'mb-12',
'truncate',
className
);
return (

View File

@ -1,7 +1,8 @@
import { gql, useQuery } from '@apollo/client';
import { useLocation } from 'react-router-dom';
import type { OracleSpecs as OracleSpecsQuery } from './__generated__/OracleSpecs';
import React from 'react';
import React, { useEffect } from 'react';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../components/route-title';
import { t } from '@vegaprotocol/react-helpers';
@ -33,7 +34,20 @@ const ORACLE_SPECS_QUERY = gql`
`;
const Oracles = () => {
const { data } = useQuery<OracleSpecsQuery>(ORACLE_SPECS_QUERY);
const { hash } = useLocation();
const { data, loading } = useQuery<OracleSpecsQuery>(ORACLE_SPECS_QUERY);
useEffect(() => {
if (data && !loading && hash) {
const element = document.getElementById(hash.substring(1));
if (element) {
window.scrollTo({
top: element.offsetTop,
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash, loading, !!data]);
return (
<section>
@ -41,7 +55,7 @@ const Oracles = () => {
{data?.oracleSpecs
? data.oracleSpecs.map((o) => (
<React.Fragment key={o.id}>
<SubHeading>{o.id}</SubHeading>
<SubHeading id={o.id.toString()}>{o.id}</SubHeading>
<SyntaxHighlighter data={o} />
</React.Fragment>
))

View File

@ -48,6 +48,9 @@ const MARKET_QUERY = gql`
}
product {
... on Future {
oracleSpecForTradingTermination {
id
}
quoteName
settlementAsset {
id

View File

@ -81,6 +81,14 @@ export interface Market_market_tradableInstrument_instrument_metadata {
tags: string[] | null;
}
export interface Market_market_tradableInstrument_instrument_product_oracleSpecForTradingTermination {
__typename: "OracleSpec";
/**
* ID is a hash generated from the OracleSpec data.
*/
id: string;
}
export interface Market_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
@ -99,6 +107,10 @@ export interface Market_market_tradableInstrument_instrument_product_settlementA
export interface Market_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* The oracle spec describing the oracle data of interest for trading termination.
*/
oracleSpecForTradingTermination: Market_market_tradableInstrument_instrument_product_oracleSpecForTradingTermination;
/**
* String representing the quote (e.g. BTCUSD -> USD is quote)
*/

View File

@ -8,7 +8,11 @@ import { SelectMarketPopover } from '@vegaprotocol/market-list';
import { OrderListContainer } from '@vegaprotocol/orders';
import { FillsContainer } from '@vegaprotocol/fills';
import { PositionsContainer } from '@vegaprotocol/positions';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import {
addDecimalsFormatNumber,
getDateFormat,
t,
} from '@vegaprotocol/react-helpers';
import { TradesContainer } from '@vegaprotocol/trades';
import {
AuctionTrigger,
@ -18,9 +22,8 @@ import {
} from '@vegaprotocol/types';
import { Allotment, LayoutPriority } from 'allotment';
import classNames from 'classnames';
import { useState } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import { useState } from 'react';
import type { ReactNode } from 'react';
import type { Market_market } from './__generated__/Market';
import type { CandleClose } from '@vegaprotocol/types';
@ -29,11 +32,13 @@ import { AccountsContainer } from '@vegaprotocol/accounts';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { useAssetDetailsDialogStore } from '@vegaprotocol/market-list';
import { useEnvironment } from '@vegaprotocol/environment';
import {
Tab,
Tabs,
PriceCellChange,
Button,
Link,
Tooltip,
ResizablePanel,
} from '@vegaprotocol/ui-toolkit';
@ -54,6 +59,55 @@ const TradingViews = {
type TradingView = keyof typeof TradingViews;
type ExpiryLabelProps = {
market: Market_market;
};
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
if (market.marketTimestamps.close === null) {
return <>{t('Not time-based')}</>;
}
const closeDate = new Date(market.marketTimestamps.close);
const isExpired = Date.now() - closeDate.valueOf() > 0;
const expiryDate = getDateFormat().format(closeDate);
return <>{`${isExpired ? `${t('Expired')} ` : ''} ${expiryDate}`}</>;
};
type ExpiryTooltipContentProps = {
market: Market_market;
explorerUrl?: string;
};
const ExpiryTooltipContent = ({
market,
explorerUrl,
}: ExpiryTooltipContentProps) => {
if (market.marketTimestamps.close === null) {
const oracleId =
market.tradableInstrument.instrument.product
.oracleSpecForTradingTermination?.id;
return (
<>
<p>
{t(
'This market expires when triggered by its oracle, not on a set date.'
)}
</p>
{explorerUrl && oracleId && (
<Link href={`${explorerUrl}/oracles#${oracleId}`} target="_blank">
{t('View oracle specification')}
</Link>
)}
</>
);
}
return null;
};
interface TradeMarketHeaderProps {
market: Market_market;
className?: string;
@ -63,6 +117,7 @@ export const TradeMarketHeader = ({
market,
className,
}: TradeMarketHeaderProps) => {
const { VEGA_EXPLORER_URL } = useEnvironment();
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
useAssetDetailsDialogStore();
const candlesClose: string[] = (market?.candles || [])
@ -87,6 +142,8 @@ export const TradeMarketHeader = ({
}
};
const hasExpiry = market.marketTimestamps.close !== null;
return (
<header className={headerClassName}>
<div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8">
@ -95,6 +152,27 @@ export const TradeMarketHeader = ({
data-testid="market-summary"
className="flex flex-auto items-start gap-64 overflow-x-auto whitespace-nowrap py-8 pr-8"
>
<Tooltip
align="start"
description={
<ExpiryTooltipContent
market={market}
explorerUrl={VEGA_EXPLORER_URL}
/>
}
>
<div className={headerItemClassName}>
<span className={itemClassName}>{t('Expiry')}</span>
<span
data-testid="trading-expiry"
className={classNames(itemValueClassName, {
'underline decoration-dashed': !hasExpiry,
})}
>
<ExpiryLabel market={market} />
</span>
</div>
</Tooltip>
<div className={headerItemClassName}>
<span className={itemClassName}>{t('Change (24h)')}</span>
<PriceCellChange
@ -119,7 +197,13 @@ export const TradeMarketHeader = ({
>
<div className={headerItemClassName}>
<span className={itemClassName}>{t('Trading mode')}</span>
<span data-testid="trading-mode" className={itemValueClassName}>
<span
data-testid="trading-mode"
className={classNames(
itemValueClassName,
'underline decoration-dashed'
)}
>
{market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger &&