Commit 504de39d authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #1553 from blockscout/interpretation-domains

add domain type to tx interpretation
parents 9802a721 6c0b765f
......@@ -96,7 +96,7 @@ Type extends EventTypes.PAGE_WIDGET ? (
}
) :
Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? {
'Type': 'Address click' | 'Token click';
'Type': 'Address click' | 'Token click' | 'Domain click';
} :
Type extends EventTypes.EXPERIMENT_STARTED ? {
'Experiment name': string;
......
......@@ -17,9 +17,10 @@ export type TxInterpretationVariable =
TxInterpretationVariableCurrency |
TxInterpretationVariableTimestamp |
TxInterpretationVariableToken |
TxInterpretationVariableAddress;
TxInterpretationVariableAddress |
TxInterpretationVariableDomain;
export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address';
export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address' | 'domain';
export type TxInterpretationVariableString = {
type: 'string';
......@@ -45,3 +46,8 @@ export type TxInterpretationVariableAddress = {
type: 'address';
value: AddressParam;
}
export type TxInterpretationVariableDomain = {
type: 'domain';
value: string;
}
import { Skeleton, Flex, Text, chakra } from '@chakra-ui/react';
import { Skeleton, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { TxInterpretationSummary, TxInterpretationVariable } from 'types/api/txInterpretation';
import type {
TxInterpretationSummary,
TxInterpretationVariable,
TxInterpretationVariableString,
} from 'types/api/txInterpretation';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import * as mixpanel from 'lib/mixpanel/index';
import { currencyUnits } from 'lib/units';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import IconSvg from 'ui/shared/IconSvg';
import { extractVariables, getStringChunks, NATIVE_COIN_SYMBOL_VAR_NAME } from './utils';
import { extractVariables, getStringChunks, fillStringVariables, NATIVE_COIN_SYMBOL_VAR_NAME } from './utils';
type Props = {
summary?: TxInterpretationSummary;
......@@ -19,7 +25,9 @@ type Props = {
className?: string;
}
const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretationVariable }) => {
type NonStringTxInterpretationVariable = Exclude<TxInterpretationVariable, TxInterpretationVariableString>
const TxInterpretationElementByType = ({ variable }: { variable?: NonStringTxInterpretationVariable }) => {
const onAddressClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' });
}, []);
......@@ -28,6 +36,10 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Token click' });
}, []);
const onDomainClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Domain click' });
}, []);
if (!variable) {
return null;
}
......@@ -36,28 +48,47 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati
switch (type) {
case 'address': {
return (
<AddressEntity
address={ value }
truncation="constant"
sx={{ ':not(:first-child)': { marginLeft: 1 } }}
whiteSpace="initial"
onClick={ onAddressClick }
/>
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<AddressEntity
address={ value }
truncation="constant"
onClick={ onAddressClick }
whiteSpace="initial"
/>
</chakra.span>
);
}
case 'token':
return (
<TokenEntity
token={ value }
onlySymbol
noCopy
width="fit-content"
sx={{ ':not(:first-child)': { marginLeft: 1 } }}
mr={ 2 }
whiteSpace="initial"
onClick={ onTokenClick }
/>
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<TokenEntity
token={ value }
onlySymbol
noCopy
width="fit-content"
_notFirst={{ marginLeft: 1 }}
mr={ 2 }
whiteSpace="initial"
onClick={ onTokenClick }
/>
</chakra.span>
);
case 'domain': {
if (config.features.nameService.isEnabled) {
return (
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<EnsEntity
name={ value }
width="fit-content"
_notFirst={{ marginLeft: 1 }}
whiteSpace="initial"
onClick={ onDomainClick }
/>
</chakra.span>
);
}
return <chakra.span color="text_secondary" whiteSpace="pre">{ value + ' ' }</chakra.span>;
}
case 'currency': {
let numberString = '';
if (BigNumber(value).isLessThan(0.1)) {
......@@ -69,14 +100,10 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati
} else {
numberString = BigNumber(value).dividedBy(1000000).toFormat(2) + 'M';
}
return <Text>{ numberString + ' ' }</Text>;
return <chakra.span>{ numberString + ' ' }</chakra.span>;
}
case 'timestamp':
// timestamp is in unix format
return <Text color="text_secondary">{ dayjs(Number(value) * 1000).format('llll') + ' ' }</Text>;
case 'string':
default: {
return <Text color="text_secondary">{ value.toString() + ' ' }</Text>;
case 'timestamp': {
return <chakra.span color="text_secondary" whiteSpace="pre">{ dayjs(Number(value) * 1000).format('MMM DD YYYY') }</chakra.span>;
}
}
};
......@@ -89,23 +116,24 @@ const TxInterpretation = ({ summary, isLoading, className }: Props) => {
const template = summary.summary_template;
const variables = summary.summary_template_variables;
const variablesNames = extractVariables(template);
const intermediateResult = fillStringVariables(template, variables);
const chunks = getStringChunks(template);
const variablesNames = extractVariables(intermediateResult);
const chunks = getStringChunks(intermediateResult);
return (
<Skeleton display="flex" flexWrap="wrap" alignItems="center" isLoaded={ !isLoading } className={ className }>
<IconSvg name="lightning" boxSize={ 5 } color="text_secondary" mr={ 2 }/>
<Skeleton isLoaded={ !isLoading } className={ className } fontWeight={ 500 } whiteSpace="pre-wrap" >
<IconSvg name="lightning" boxSize={ 5 } color="text_secondary" mr={ 2 } verticalAlign="text-top"/>
{ chunks.map((chunk, index) => {
return (
<Flex whiteSpace="pre" key={ chunk + index } fontWeight={ 500 }>
<Text color="text_secondary">{ chunk.trim() + (chunk.trim() && variablesNames[index] ? ' ' : '') }</Text>
<chakra.span key={ chunk + index }>
<chakra.span color="text_secondary">{ chunk.trim() + (chunk.trim() && variablesNames[index] ? ' ' : '') }</chakra.span>
{ index < variablesNames.length && (
variablesNames[index] === NATIVE_COIN_SYMBOL_VAR_NAME ?
<Text>{ currencyUnits.ether + ' ' }</Text> :
<TxInterpretationElementByType variable={ variables[variablesNames[index]] }/>
<chakra.span>{ currencyUnits.ether + ' ' }</chakra.span> :
<TxInterpretationElementByType variable={ variables[variablesNames[index]] as NonStringTxInterpretationVariable }/>
) }
</Flex>
</chakra.span>
);
}) }
</Skeleton>
......
// we use that regex as a separator when splitting template and dont want to capture variables
import type { TxInterpretationVariable } from 'types/api/txInterpretation';
// eslint-disable-next-line regexp/no-useless-non-capturing-group
export const VAR_REGEXP = /\{(?:[^}]+)\}/g;
......@@ -16,3 +19,16 @@ export function extractVariables(templateString: string) {
export function getStringChunks(template: string) {
return template.split(VAR_REGEXP);
}
export function fillStringVariables(template: string, variables: Record<string, TxInterpretationVariable>) {
const variablesNames = extractVariables(template);
let result = template;
variablesNames.forEach(name => {
if (variables[name] && variables[name].type === 'string') {
result = result.replace(`{${ name }}`, variables[name].value as string);
}
});
return result;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment