From 95658d337f9f4aaab4df69faacf9d643f4f0d78d Mon Sep 17 00:00:00 2001 From: Patrick Dinh Date: Tue, 9 Apr 2024 10:02:17 +1000 Subject: [PATCH 1/3] chore: fix up transaction visualisation --- .../components/payment-transaction.tsx | 21 ++++++- .../components/transaction-visualisation.tsx | 63 +++++++++++-------- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/features/transactions/components/payment-transaction.tsx b/src/features/transactions/components/payment-transaction.tsx index 63d16f7e3..95a8f013e 100644 --- a/src/features/transactions/components/payment-transaction.tsx +++ b/src/features/transactions/components/payment-transaction.tsx @@ -10,6 +10,7 @@ import { PaymentTransactionModel } from '../models' import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer' import { DescriptionList } from '@/features/common/components/description-list' import { TransactionVisualisation } from './transaction-visualisation' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/features/common/components/tabs' export type Props = { transaction: PaymentTransactionModel @@ -56,7 +57,25 @@ export function PaymentTransaction({ transaction, rawTransaction }: Props) { - +
+

Transaction

+ + + + Visual + + + Table + + + + + + + {transaction.base64Note} + + +
diff --git a/src/features/transactions/components/transaction-visualisation.tsx b/src/features/transactions/components/transaction-visualisation.tsx index 3a9ec0e01..3851c86f8 100644 --- a/src/features/transactions/components/transaction-visualisation.tsx +++ b/src/features/transactions/components/transaction-visualisation.tsx @@ -49,10 +49,10 @@ function ConnectionToParent() { ) } -function TransactionName({ hasParent, name }: { hasParent: boolean; name: string }) { +function TransactionId({ hasParent, name }: { hasParent: boolean; name: string }) { return (
{id} +} + function ConnectionToSibbling() { // The connection between this transaction and the next sibbling return ( @@ -160,7 +164,7 @@ function TransactionRow({ style={{ marginLeft: (indentLevel ?? 0) * graphConfig.indentationWidth }} > {hasParent && } - + {hasParent && hasNextSibbling && } {hasChildren && }
@@ -174,7 +178,7 @@ function TransactionRow({ -
Transaction: {transaction.id}
+
Transaction: {transaction.id}
) @@ -185,7 +189,7 @@ function TransactionRow({ -
Transaction: {transaction.id}
+
Transaction: {transaction.id}
) @@ -209,35 +213,35 @@ function TransactionRow({ ) } -function extractSendersAndReceivers(transaction: TransactionModel) { - let transactionCount = 0 +function extractSendersAndReceivers(transaction: TransactionModel, nestingLevel = 0) { + let transactionCount = 1 let accounts: string[] = [] + let maxNestingLevel = nestingLevel - function extract(transactionArr: TransactionModel[]) { - if (transactionArr) { - transactionArr.forEach((transaction) => { - transactionCount++ - accounts.push(transaction.sender) - if (transaction.type === TransactionType.Payment) { - accounts.push(transaction.receiver) - } - if (transaction.transactions) { - extract(transaction.transactions) - } - }) - } + accounts.push(transaction.sender) + if (transaction.type === TransactionType.Payment) { + accounts.push(transaction.receiver) } - extract(transaction.transactions ?? []) + transaction.transactions?.forEach((transaction) => { + const { + transactionCount: childTransactionCount, + accounts: childAccounts, + nestingLevel: childNestingLevel, + } = extractSendersAndReceivers(transaction, nestingLevel + 1) + + transactionCount += childTransactionCount + accounts = [...accounts, ...childAccounts] + maxNestingLevel = Math.max(maxNestingLevel, childNestingLevel) + }) // Remove duplicates accounts = Array.from(new Set(accounts)) - // Sort - accounts = accounts.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)) return { transactionCount: transactionCount, accounts: accounts, + nestingLevel: maxNestingLevel, } } @@ -267,20 +271,20 @@ type Props = { } export function TransactionVisualisation({ transaction }: Props) { - const { transactionCount, accounts } = extractSendersAndReceivers(transaction) + const { transactionCount, accounts, nestingLevel } = extractSendersAndReceivers(transaction) return (
{/* The first header cell is empty */}
{accounts.map((account, index) => (
-

{account}

+
))} {/* The below div is for drawing the background dash lines */} @@ -307,6 +311,13 @@ export function TransactionVisualisation({ transaction }: Props) {
+ 0} + hasParent={false} + accounts={accounts} + verticalBars={[]} + /> {transaction.transactions?.map((transaction, index, arr) => ( Date: Tue, 9 Apr 2024 11:14:47 +1000 Subject: [PATCH 2/3] chore: visual it --- .../common/components/display-algo.tsx | 5 +- .../components/payment-transaction.tsx | 11 +-- .../components/transaction-view-table.tsx | 82 +++++++++++++++++++ ...sation.tsx => transaction-view-visual.tsx} | 8 +- src/features/transactions/data.ts | 2 +- 5 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 src/features/transactions/components/transaction-view-table.tsx rename src/features/transactions/components/{transaction-visualisation.tsx => transaction-view-visual.tsx} (97%) diff --git a/src/features/common/components/display-algo.tsx b/src/features/common/components/display-algo.tsx index 21a170000..dd66d6094 100644 --- a/src/features/common/components/display-algo.tsx +++ b/src/features/common/components/display-algo.tsx @@ -3,12 +3,13 @@ import { cn } from '../utils' import { AlgoAmount } from '@algorandfoundation/algokit-utils/types/amount' export type Props = { + className?: string amount: AlgoAmount } -export function DisplayAlgo({ amount }: Props) { +export function DisplayAlgo({ className, amount }: Props) { return ( -
+
{amount.algos}
diff --git a/src/features/transactions/components/payment-transaction.tsx b/src/features/transactions/components/payment-transaction.tsx index 95a8f013e..3b9bd874e 100644 --- a/src/features/transactions/components/payment-transaction.tsx +++ b/src/features/transactions/components/payment-transaction.tsx @@ -9,8 +9,9 @@ import { useMemo } from 'react' import { PaymentTransactionModel } from '../models' import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer' import { DescriptionList } from '@/features/common/components/description-list' -import { TransactionVisualisation } from './transaction-visualisation' +import { TransactionViewVisual } from './transaction-view-visual' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/features/common/components/tabs' +import { TransactionViewTable } from './transaction-view-table' export type Props = { transaction: PaymentTransactionModel @@ -58,8 +59,8 @@ export function PaymentTransaction({ transaction, rawTransaction }: Props) {
-

Transaction

- +

View

+ Visual @@ -69,10 +70,10 @@ export function PaymentTransaction({ transaction, rawTransaction }: Props) { - + - {transaction.base64Note} +
diff --git a/src/features/transactions/components/transaction-view-table.tsx b/src/features/transactions/components/transaction-view-table.tsx new file mode 100644 index 000000000..0b2267b5f --- /dev/null +++ b/src/features/transactions/components/transaction-view-table.tsx @@ -0,0 +1,82 @@ +import { cn } from '@/features/common/utils' +import { TransactionModel, TransactionType } from '../models' +import { DisplayAlgo } from '@/features/common/components/display-algo' + +const graphConfig = { + indentationWidth: 20, +} + +function unpackTransaction(transaction: TransactionModel, indentationLevel = 0) { + const transactionWithIndentationLevel: { + indentationLevel: number + transaction: TransactionModel + }[] = [] + + transactionWithIndentationLevel.push({ + indentationLevel: indentationLevel, + transaction: transaction, + }) + + transaction.transactions?.forEach((transaction) => { + const childTransactionWithIndentationLevel = unpackTransaction(transaction, indentationLevel + 1) + + transactionWithIndentationLevel.push(...childTransactionWithIndentationLevel) + }) + + return transactionWithIndentationLevel +} + +function TruncatedId({ id }: { id: string }) { + return
{id}
+} + +type Props = { + transaction: TransactionModel +} + +export function TransactionViewTable({ transaction }: Props) { + const transactionsWithIndentationLevel = unpackTransaction(transaction) + + return ( + + + + + + + + + + + + {transactionsWithIndentationLevel.map(({ transaction, indentationLevel }) => ( + + + + + + + + ))} + +
Transaction IDFromToTypeAmount
+
+ +
+
+ + + + {transaction.type} + {transaction.type === TransactionType.Payment ? ( + + ) : ( + 'N/A' + )} +
+ ) +} diff --git a/src/features/transactions/components/transaction-visualisation.tsx b/src/features/transactions/components/transaction-view-visual.tsx similarity index 97% rename from src/features/transactions/components/transaction-visualisation.tsx rename to src/features/transactions/components/transaction-view-visual.tsx index 3851c86f8..5e6753975 100644 --- a/src/features/transactions/components/transaction-visualisation.tsx +++ b/src/features/transactions/components/transaction-view-visual.tsx @@ -213,7 +213,7 @@ function TransactionRow({ ) } -function extractSendersAndReceivers(transaction: TransactionModel, nestingLevel = 0) { +function unpackTransaction(transaction: TransactionModel, nestingLevel = 0) { let transactionCount = 1 let accounts: string[] = [] let maxNestingLevel = nestingLevel @@ -228,7 +228,7 @@ function extractSendersAndReceivers(transaction: TransactionModel, nestingLevel transactionCount: childTransactionCount, accounts: childAccounts, nestingLevel: childNestingLevel, - } = extractSendersAndReceivers(transaction, nestingLevel + 1) + } = unpackTransaction(transaction, nestingLevel + 1) transactionCount += childTransactionCount accounts = [...accounts, ...childAccounts] @@ -270,8 +270,8 @@ type Props = { transaction: TransactionModel } -export function TransactionVisualisation({ transaction }: Props) { - const { transactionCount, accounts, nestingLevel } = extractSendersAndReceivers(transaction) +export function TransactionViewVisual({ transaction }: Props) { + const { transactionCount, accounts, nestingLevel } = unpackTransaction(transaction) return (
Date: Tue, 9 Apr 2024 13:07:53 +1000 Subject: [PATCH 3/3] chore: wip - payment tooltip --- .../common/components/description-list.tsx | 6 +- .../components/transaction-view-visual.tsx | 101 +++++++++++++----- .../pages/transaction-page.test.tsx | 2 +- src/tests/object-mother/transaction-model.ts | 2 +- 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/features/common/components/description-list.tsx b/src/features/common/components/description-list.tsx index 231eec252..bfb726faf 100644 --- a/src/features/common/components/description-list.tsx +++ b/src/features/common/components/description-list.tsx @@ -6,9 +6,9 @@ type Props = { export function DescriptionList({ items }: Props) { return items.map((item, index) => ( -
-
{item.dt}
-
{item.dd}
+
+
{item.dt}
+
{item.dd}
)) } diff --git a/src/features/transactions/components/transaction-view-visual.tsx b/src/features/transactions/components/transaction-view-visual.tsx index 5e6753975..bec6c55f6 100644 --- a/src/features/transactions/components/transaction-view-visual.tsx +++ b/src/features/transactions/components/transaction-view-visual.tsx @@ -7,6 +7,8 @@ import { fixedForwardRef } from '@/utils/fixed-forward-ref' import { isDefined } from '@/utils/is-defined' import { useMemo } from 'react' import { TransactionModel, TransactionType } from '../models' +import { DisplayAlgo } from '@/features/common/components/display-algo' +import { DescriptionList } from '@/features/common/components/description-list' const graphConfig = { rowHeight: 40, @@ -14,6 +16,7 @@ const graphConfig = { indentationWidth: 20, lineWidth: 2, circleDimension: 20, + paymentTransactionColor: 'rgb(126 200 191)', } type Arrow = { @@ -97,35 +100,45 @@ function ConnectionToChildren({ indentLevel }: { indentLevel: number | undefined ) } -const DisplayArrow = fixedForwardRef(({ arrow, ...rest }: { arrow: Arrow }, ref?: React.LegacyRef) => { - return ( -
- +const DisplayArrow = fixedForwardRef( + ({ transaction, arrow, ...rest }: { transaction: TransactionModel; arrow: Arrow }, ref?: React.LegacyRef) => { + const color = graphConfig.paymentTransactionColor + + return (
- {arrow.direction === 'rightToLeft' && } -
- {arrow.direction === 'leftToRight' && } + +
+ {arrow.direction === 'rightToLeft' && } +
+ {arrow.direction === 'leftToRight' && } +
+ {transaction.type === TransactionType.Payment && ( +
+ Payment + +
+ )} +
-
Payment
- -
- ) -}) + ) + } +) const DisplaySelfTransaction = fixedForwardRef((props: object, ref?: React.LegacyRef) => { return ( @@ -135,6 +148,40 @@ const DisplaySelfTransaction = fixedForwardRef((props: object, ref?: React.Legac ) }) +function PaymentTransactionToolTipContent({ transaction }: { transaction: TransactionModel }) { + const items = useMemo( + () => [ + { + dt: 'Transaction ID', + dd: transaction.id, + }, + { + dt: 'Type', + dd: 'Payment', + }, + { + dt: 'Sender', + dd: transaction.sender, + }, + { + dt: 'Receiver', + dd: transaction.receiver, + }, + { + dt: 'Amount', + dd: , + }, + ], + [transaction.amount, transaction.id, transaction.receiver, transaction.sender] + ) + + return ( +
+ +
+ ) +} + type TransactionRowProps = { transaction: TransactionModel hasParent?: boolean @@ -178,7 +225,7 @@ function TransactionRow({ -
Transaction: {transaction.id}
+ {transaction.type === TransactionType.Payment && }
) @@ -186,10 +233,10 @@ function TransactionRow({ return ( - + -
Transaction: {transaction.id}
+ {transaction.type === TransactionType.Payment && }
) diff --git a/src/features/transactions/pages/transaction-page.test.tsx b/src/features/transactions/pages/transaction-page.test.tsx index cfa8ab343..c9e6ed341 100644 --- a/src/features/transactions/pages/transaction-page.test.tsx +++ b/src/features/transactions/pages/transaction-page.test.tsx @@ -23,7 +23,7 @@ describe('given a invalid transaction ID', () => { }) describe('given a payment transaction', () => { - const paymentTransaction = transactionModelMother.simplePaymentTransaction().build() + const paymentTransaction = transactionModelMother.paymentTransactionWithNoChildren().build() it('it should be rendered', async () => { vi.mocked(useParams).mockImplementation(() => ({ transactionId: paymentTransaction.id })) diff --git a/src/tests/object-mother/transaction-model.ts b/src/tests/object-mother/transaction-model.ts index 72776f7ac..641cf81b7 100644 --- a/src/tests/object-mother/transaction-model.ts +++ b/src/tests/object-mother/transaction-model.ts @@ -1,7 +1,7 @@ import { transactionResultBuilder } from '../builders/transaction-result-builder' export const transactionModelMother = { - simplePaymentTransaction: () => { + paymentTransactionWithNoChildren: () => { return transactionResultBuilder() .withId('FBORGSDC4ULLWHWZUMUFIYQLSDC26HGLTFD7EATQDY37FHCIYBBQ') ['withConfirmed-round'](36570178)