Skip to content

Commit

Permalink
conditionally formats cli transactions output (#3708)
Browse files Browse the repository at this point in the history
* conditionally formats cli transactions output

we've added a lot of logic to the 'wallet:transactions' command to format the
table to make it easier to read in the CLI. however, this formatting logic
removes data from the table that might be useful when outputting transaction
data with the '--csv' or '--output' flags.

- refactors logic for assigning 'group' string to table rows for the same
  transaction into 'getRowGroup' method
- only includes the 'group' column in output if formatting the table for the CLI
- includes all transaction details (hash, fee, assetId, assetName, etc.) in each
  row if using '--csv' or '--output' flags

* removes default formatted value in getTransactionRows

* defines enum for oclif table format

format can be cli, csv, json, or yaml

the latter three correspond to the three possible values of the '--output' oclif
table flag

uses Format enum instead of boolean 'formatted' to indicate whether to format
table for cli output
  • Loading branch information
hughy authored Mar 30, 2023
1 parent d73b867 commit 324cd55
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 51 deletions.
118 changes: 70 additions & 48 deletions ironfish-cli/src/commands/wallet/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { CliUx, Flags } from '@oclif/core'
import { IronfishCommand } from '../../command'
import { RemoteFlags } from '../../flags'
import { TableCols } from '../../utils/table'
import { Format, TableCols } from '../../utils/table'

export class TransactionsCommand extends IronfishCommand {
static description = `Display the account transactions`
Expand Down Expand Up @@ -52,6 +52,15 @@ export class TransactionsCommand extends IronfishCommand {
const { flags, args } = await this.parse(TransactionsCommand)
const account = args.account as string | undefined

const format: Format =
flags.csv || flags.output === 'csv'
? Format.csv
: flags.output === 'json'
? Format.json
: flags.output === 'yaml'
? Format.yaml
: Format.cli

const client = await this.sdk.connectRpc()
const response = client.getAccountTransactionsStream({
account,
Expand All @@ -62,12 +71,12 @@ export class TransactionsCommand extends IronfishCommand {
confirmations: flags.confirmations,
})

const columns = this.getColumns(flags.extended)
const columns = this.getColumns(flags.extended, format)

let showHeader = !flags['no-header']

for await (const transaction of response.contentStream()) {
const transactionRows = this.getTransactionRows(transaction)
const transactionRows = this.getTransactionRows(transaction, format)

CliUx.ux.table(transactionRows, columns, {
printLine: this.log.bind(this),
Expand All @@ -81,78 +90,65 @@ export class TransactionsCommand extends IronfishCommand {

getTransactionRows(
transaction: GetAccountTransactionsResponse,
format: Format,
): PartialRecursive<TransactionRow>[] {
const nativeAssetId = Asset.nativeId().toString('hex')

const nativeAssetBalanceDelta = transaction.assetBalanceDeltas.find(
(d) => d.assetId === nativeAssetId,
const assetBalanceDeltas = transaction.assetBalanceDeltas.sort((d) =>
d.assetId === nativeAssetId ? -1 : 1,
)

let amount = BigInt(nativeAssetBalanceDelta?.delta ?? '0')

let feePaid = BigInt(transaction.fee)

if (transaction.type !== TransactionType.SEND) {
feePaid = 0n
} else {
amount += feePaid
}
const feePaid = transaction.type === TransactionType.SEND ? BigInt(transaction.fee) : 0n

const transactionRows = []

const assetCount = transaction.assetBalanceDeltas.length
const isGroup = assetCount > 1

// $IRON should appear first if it is the only asset in the transaction or the net amount was non-zero
if (!isGroup || amount !== 0n) {
transactionRows.push({
...transaction,
group: isGroup ? '┏' : '',
assetId: nativeAssetId,
assetName: Buffer.from('$IRON').toString('hex'),
amount,
feePaid,
})
}
let assetCount = assetBalanceDeltas.length

for (const [index, { assetId, assetName, delta }] of assetBalanceDeltas.entries()) {
let amount = BigInt(delta)

for (const [
index,
{ assetId, assetName, delta },
] of transaction.assetBalanceDeltas.entries()) {
// skip the native asset, added above
if (assetId === Asset.nativeId().toString('hex')) {
continue
if (transaction.type === TransactionType.SEND) {
amount += feePaid
}

// exclude the native asset in cli output if no amount was sent/received
if (format === Format.cli && amount === 0n) {
assetCount -= 1
continue
}
}

if (transactionRows.length === 0) {
// include full transaction details if the native asset had no net change
const group = this.getRowGroup(index, assetCount, transactionRows.length)

// include full transaction details in first row or non-cli-formatted output
if (transactionRows.length === 0 || format !== Format.cli) {
transactionRows.push({
...transaction,
group: assetCount === 2 ? '' : '┏',
group,
assetId,
assetName,
amount: BigInt(delta),
amount,
feePaid,
})
} else {
transactionRows.push({
group: index === assetCount - 1 ? '┗' : '┣',
group,
assetId,
assetName,
amount: BigInt(delta),
amount,
})
}
}

return transactionRows
}

getColumns(extended: boolean): CliUx.Table.table.Columns<PartialRecursive<TransactionRow>> {
return {
group: {
header: '',
minWidth: 3,
},
getColumns(
extended: boolean,
format: Format,
): CliUx.Table.table.Columns<PartialRecursive<TransactionRow>> {
const columns: CliUx.Table.table.Columns<PartialRecursive<TransactionRow>> = {
timestamp: TableCols.timestamp({
streaming: true,
}),
Expand Down Expand Up @@ -196,7 +192,7 @@ export class TransactionsCommand extends IronfishCommand {
get: (row) =>
row.feePaid && row.feePaid !== 0n ? CurrencyUtils.renderIron(row.feePaid) : '',
},
...TableCols.asset({ extended }),
...TableCols.asset({ extended, format }),
amount: {
header: 'Net Amount',
get: (row) => {
Expand All @@ -206,11 +202,37 @@ export class TransactionsCommand extends IronfishCommand {
minWidth: 16,
},
}

if (format === Format.cli) {
return {
group: {
header: '',
minWidth: 3,
},
...columns,
}
}

return columns
}

getRowGroup(index: number, assetCount: number, assetRowCount: number): string {
if (assetCount > 1) {
if (assetRowCount === 0) {
return '┏'
} else if (assetRowCount > 0 && index < assetCount - 1) {
return '┣'
} else if (assetRowCount > 0 && index === assetCount - 1) {
return '┗'
}
}

return ''
}
}

type TransactionRow = {
group: string
group?: string
timestamp: number
status: string
type: string
Expand Down
14 changes: 11 additions & 3 deletions ironfish-cli/src/utils/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ const timestamp = <T extends Record<string, unknown>>(options?: {

const asset = <T extends Record<string, unknown>>(options?: {
extended?: boolean
format?: Format
}): Partial<Record<string, table.Column<T>>> => {
if (options?.extended) {
if (options?.extended || options?.format !== Format.cli) {
return {
assetId: {
header: 'Asset ID',
get: (row) => row['assetId'],
minWidth: MAX_ASSET_NAME_COLUMN_WIDTH,
extended: true,
extended: options?.extended ?? false,
},
assetName: {
header: 'Asset Name',
Expand All @@ -78,7 +79,7 @@ const asset = <T extends Record<string, unknown>>(options?: {
return truncateCol(assetName, MAX_ASSET_NAME_COLUMN_WIDTH)
},
minWidth: MAX_ASSET_NAME_COLUMN_WIDTH,
extended: true,
extended: options?.extended ?? false,
},
}
} else {
Expand Down Expand Up @@ -123,4 +124,11 @@ function truncateCol(value: string, maxWidth: number | null): string {
return value.slice(0, maxWidth - 1) + '…'
}

export enum Format {
cli = 'cli',
csv = 'csv',
json = 'json',
yaml = 'yaml',
}

export const TableCols = { timestamp, asset, fixedWidth }

0 comments on commit 324cd55

Please sign in to comment.