Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for hooks metadata details #756

Merged
merged 20 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion public/locales/en-US/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -453,5 +453,19 @@
"language_ja-JP": "Japanese",
"xchain_account_claim_count": "XChain Account Claim Count",
"xchain_account_create_count": "XChain Account Create Count",
"min_signer_quorum": "Minimum weight <0>{{quorum}}</0> required"
"min_signer_quorum": "Minimum weight <0>{{quorum}}</0> required",
"hook": "Hook",
"hooks": "Hooks",
"hook_emitted": "this Transaction was emitted by a Hook",
"emit_details": "Emit Details",
"hook_parameters": "Hook Parameters",
"hook_executions": "Hook Executions",
"emit_generation": "Number <0>{{emit}}</0> in the line of generated transactions",
"emit_hook_hash": "Emitted by the hook <0>{{hash}}</0>",
"emit_parent": "Emitted by a hook triggered by <0>{{hash}}</0>",
"emit_callback": "The emit callback is <0>{{callback}}<0>",
"hook_exec_hash": "It triggered the hook <0>{{hash}}</0>",
"hook_exec_account": "On the account <0>{{account}}</0>",
"hook_exec_return": "Returned the code <0>{{code}}</0> with string \"<1>{{string}}</1>\"",
"hook_exec_emit_count": "Emitted <0>{{count}}</0> transactions"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const BalanceSelector = ({
return (
<BalanceSelectorItem
currency={currency}
key={currency}
mvadari marked this conversation as resolved.
Show resolved Hide resolved
handler={() => {
onSetCurrencySelected(currency)
}}
Expand Down
1 change: 1 addition & 0 deletions src/containers/Ledger/LedgerTransactionTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const LedgerTransactionTableRow = ({ tx }: Props) => {
sequence={tx.sequence}
ticketSequence={tx.ticketSequence}
account={tx.account}
isHook={tx.isHook}
/>
</div>
<div className="col col-fee">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const TransactionDescription: FC<{ data: any }> = ({ data }) => {
sequence={data.tx.Sequence}
ticketSequence={data.tx.TicketSequence}
account={data.tx.Account}
isHook={!!data.tx.EmitDetails}
addContextHelp
/>
</b>
Expand Down
134 changes: 134 additions & 0 deletions src/containers/Transactions/DetailTab/HookDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { FC } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { convertHexToString } from '../../../rippled/lib/utils'
import { Account } from '../../shared/components/Account'
import { RouteLink } from '../../shared/routing'
import { TRANSACTION_ROUTE } from '../../App/routes'

const renderHookParameterName = (name: string) => {
// Example:
// 4556520100000000000000000000000000000000000000000000000000000002 -> EVR 2
const split = name.split('0000').filter((d) => d !== '')
if (split.length === 2) {
return `${convertHexToString(split[0])}${Number(split[1])}`
}
return name
}

const EmitDetails: FC<{ emitDetails: any }> = ({ emitDetails }) => {
const { t } = useTranslation()
return (
<div className="detail-subsection" data-test="emit-details">
<div className="detail-subtitle">{t('emit_details')}</div>
<li className="detail-line">
<Trans
i18nKey="emit_generation"
values={{ emit: emitDetails.EmitGeneration }}
/>
</li>
<li className="detail-line">
<Trans
i18nKey="emit_hook_hash"
values={{ hash: emitDetails.EmitHookHash }}
/>
</li>
<li className="detail-line">
<Trans
i18nKey="emit_parent"
values={{
hash: `${emitDetails.EmitParentTxnID.substring(20)}...`,
}}
>
<RouteLink
className="hash"
to={TRANSACTION_ROUTE}
params={{ identifier: emitDetails.EmitParentTxnID }}
/>
</Trans>
</li>
{emitDetails.EmitCallback && (
<li className="detail-line">
<Trans
i18nKey="emit_callback"
values={{ callback: emitDetails.EmitCallback }}
>
<Account account={emitDetails.EmitCallback} />
</Trans>
</li>
)}
</div>
)
}

const HookParameter: FC<{ HookParameter: any }> = ({
HookParameter: param,
}) => (
<li key={param.HookParameterName}>
{renderHookParameterName(param.HookParameterName)}
{': '}
{param.HookParameterValue.length <= 32
? convertHexToString(param.HookParameterValue)
: param.HookParameterValue}
</li>
)

const HookExecution: FC<{ HookExecution: any }> = ({ HookExecution: exec }) => (
<li key={`hook_exec_${exec.HookHash}_${exec.HookExecutionIndex}`}>
<span className="detail-line">
<Trans i18nKey="hook_exec_hash" values={{ hash: exec.HookHash }} />
</span>
<ul className="detail-line">
<Trans
i18nKey="hook_exec_account"
values={{
account: exec.HookAccount,
}}
>
<Account account={exec.HookAccount} />
</Trans>
</ul>
<ul className="detail-line">
<Trans
i18nKey="hook_exec_return"
values={{
code: `0x${exec.HookReturnCode}`,
string: convertHexToString(exec.HookReturnString),
}}
/>
</ul>
<ul className="detail-line">
<Trans
i18nKey="hook_exec_emit_count"
values={{
count: exec.HookEmitCount,
}}
/>
</ul>
</li>
)

export const HookDetails: FC<{ data: { tx: any; meta: any } }> = ({ data }) => {
const { tx, meta } = data
const { t } = useTranslation()

if (!tx.EmitDetails && !tx.HookParameters && !meta.HookExecutions) return null

return (
<div className="detail-section" data-test="hooks">
<div className="title">{t('hooks')}</div>
{tx.EmitDetails && <EmitDetails emitDetails={tx.EmitDetails} />}
{tx.HookParameters && (
<div className="detail-subsection" data-test="hook-params">
<div className="detail-subtitle">{t('hook_parameters')}</div>
{tx.HookParameters.map(HookParameter)}
</div>
)}
{meta.HookExecutions && (
<div className="detail-subsection" data-test="hook-executions">
<div className="detail-subtitle">{t('hook_executions')}</div>
{meta.HookExecutions.map(HookExecution)}
</div>
)}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Trans } from 'react-i18next'
import { CURRENCY_OPTIONS, XRP_BASE } from '../../shared/transactionUtils'
import { localizeNumber } from '../../shared/utils'
import { Account } from '../../shared/components/Account'
import { CURRENCY_OPTIONS, XRP_BASE } from '../../../shared/transactionUtils'
import { localizeNumber } from '../../../shared/utils'
import { Account } from '../../../shared/components/Account'

const render = (t, language, action, node, index) => {
const fields = node.FinalFields || node.NewFields || { Balance: 0 }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Account } from '../../shared/components/Account'
import { Account } from '../../../shared/components/Account'

const render = (t, action, node, index) => {
const fields = node.FinalFields || node.NewFields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
CURRENCY_ORDER,
CURRENCY_OPTIONS,
XRP_BASE,
} from '../../shared/transactionUtils'
import { localizeNumber } from '../../shared/utils'
import { Account } from '../../shared/components/Account'
} from '../../../shared/transactionUtils'
import { localizeNumber } from '../../../shared/utils'
import { Account } from '../../../shared/components/Account'

const normalize = (value, currency) =>
currency === 'XRP' ? (value / XRP_BASE).toString() : value
Expand Down Expand Up @@ -39,7 +39,7 @@ const renderChanges = (t, language, node, index) => {
<span className="field">TakerPays </span>
<b>{paysCurrency}</b>
{renderIssuer(final.TakerPays.issuer)}{' '}
<Trans Trans i18nKey="decreased_from_to">
<Trans i18nKey="decreased_from_to">
decreased by
<b>{{ change: localizeNumber(changePays, language, options) }}</b>
from
Expand Down Expand Up @@ -73,7 +73,7 @@ const renderChanges = (t, language, node, index) => {
<span className="field">TakerGets </span>
<b>{getsCurrency}</b>
{renderIssuer(final.TakerGets.issuer)}{' '}
<Trans Trans i18nKey="decreased_from_to">
<Trans i18nKey="decreased_from_to">
decreased by
<b>{{ change: localizeNumber(changeGets, language, options) }}</b>
from
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Trans } from 'react-i18next'
import { CURRENCY_OPTIONS } from '../../shared/transactionUtils'
import { localizeNumber } from '../../shared/utils'
import { Account } from '../../shared/components/Account'
import { CURRENCY_OPTIONS } from '../../../shared/transactionUtils'
import { localizeNumber } from '../../../shared/utils'
import { Account } from '../../../shared/components/Account'

const MILLION = 1000000

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Trans } from 'react-i18next'
import { CURRENCY_OPTIONS } from '../../shared/transactionUtils'
import { localizeNumber } from '../../shared/utils'
import { Account } from '../../shared/components/Account'
import { CURRENCY_OPTIONS } from '../../../shared/transactionUtils'
import { localizeNumber } from '../../../shared/utils'
import { Account } from '../../../shared/components/Account'

const render = (t, language, action, node, index) => {
const fields = node.FinalFields || node.NewFields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import renderDirectoryNode from './DirectoryNode'
import renderOffer from './Offer'
import renderRippleState from './RippleState'
import renderPayChannel from './PayChannel'
import { groupAffectedNodes } from '../../shared/transactionUtils'
import { useLanguage } from '../../shared/hooks'
import { groupAffectedNodes } from '../../../shared/transactionUtils'
import { useLanguage } from '../../../shared/hooks'

const renderDefault = (t, action, node, index) => (
<li key={`${node.LedgerEntryType}_${index}`} className="meta-line">
Expand Down Expand Up @@ -38,8 +38,8 @@ export const TransactionMeta: FC<{ data: any }> = ({ data }) => {

return (
meta.length !== 0 && (
<div className="meta-section">
<div className="meta-title">{t('nodes_type', { action })}</div>
<div className="detail-subsection">
<div className="detail-subtitle">{t('nodes_type', { action })}</div>
<ul>{meta}</ul>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import '../shared/css/variables';
@import '../../shared/css/variables';

.detail-body {
margin-top: 20px;
Expand All @@ -15,6 +15,10 @@
border: none;
}

li {
word-wrap: break-word;
}

@include for-size(tablet-landscape-up) {
width: 552px;
}
Expand Down Expand Up @@ -68,7 +72,7 @@
}
}

.meta-title {
.detail-subtitle {
margin-top: 16px;
margin-bottom: 2px;
color: $black-40;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import { FC } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { TransactionMeta } from './Meta'
import { TransactionDescription } from './Description'
import { Account } from '../shared/components/Account'
import { localizeDate, localizeNumber } from '../shared/utils'
import { Account } from '../../shared/components/Account'
import { localizeDate, localizeNumber } from '../../shared/utils'
import {
DATE_OPTIONS,
CURRENCY_OPTIONS,
SUCCESSFUL_TRANSACTION,
XRP_BASE,
buildFlags,
buildMemos,
} from '../shared/transactionUtils'
} from '../../shared/transactionUtils'
import './detailTab.scss'
import { useLanguage } from '../shared/hooks'
import { RouteLink } from '../shared/routing'
import { LEDGER_ROUTE } from '../App/routes'
import { useLanguage } from '../../shared/hooks'
import { HookDetails } from './HookDetails'
import { RouteLink } from '../../shared/routing'
import { LEDGER_ROUTE } from '../../App/routes'

export const DetailTab: FC<{ data: any }> = ({ data }) => {
const { t } = useTranslation()
Expand Down Expand Up @@ -127,6 +128,7 @@ export const DetailTab: FC<{ data: any }> = ({ data }) => {
{renderStatus()}
<TransactionDescription data={data} />
{renderSigners()}
<HookDetails data={data} />
{renderFlags()}
{renderFee()}
{renderMemos()}
Expand Down
3 changes: 3 additions & 0 deletions src/containers/Transactions/SimpleTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const SimpleTab: FC<{ data: any; width: number }> = ({
account,
sequence,
ticketSequence,
isHook,
) => (
<>
<SimpleRow
Expand All @@ -62,6 +63,7 @@ export const SimpleTab: FC<{ data: any; width: number }> = ({
sequence={sequence}
ticketSequence={ticketSequence}
account={account}
isHook={isHook}
/>
</SimpleRow>
<SimpleRow label={t('transaction_cost')} data-test="tx-cost">
Expand Down Expand Up @@ -89,6 +91,7 @@ export const SimpleTab: FC<{ data: any; width: number }> = ({
raw.tx.Account,
raw.tx.Sequence,
raw.tx.TicketSequence,
!!raw.tx.EmitDetails,
)

return (
Expand Down
28 changes: 26 additions & 2 deletions src/containers/Transactions/test/Description.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { I18nextProvider } from 'react-i18next'
import i18n from '../../../i18n/testConfig'
import { TransactionDescription } from '../Description'
import i18n from '../../../i18n/testConfigEnglish'
import { TransactionDescription } from '../DetailTab/Description'
import Transaction from './mock_data/Transaction.json'
import OfferCreateTicket from './mock_data/OfferCreateTicket.json'
import EmittedPayment from './mock_data/EmittedPayment.json'

describe('Description container', () => {
const createWrapper = (data = {}) =>
Expand All @@ -18,4 +21,25 @@ describe('Description container', () => {
const wrapper = createWrapper()
wrapper.unmount()
})

it('renders transaction', () => {
const wrapper = createWrapper(Transaction)
wrapper.unmount()
})

it('renders sequence number with ticket', () => {
const wrapper = createWrapper(OfferCreateTicket)
expect(wrapper.find(`[data-test="sequence"]`)).toHaveText(
'79469284 (a Ticket was used for this Transaction)',
)
wrapper.unmount()
})

it('renders sequence number with hook', () => {
const wrapper = createWrapper(EmittedPayment)
expect(wrapper.find(`[data-test="sequence"]`)).toHaveText(
'0 (this Transaction was emitted by a Hook)',
)
wrapper.unmount()
})
})
Loading