Skip to content

Commit

Permalink
[explorer] Displays Transaction Timestamps (MystenLabs#2630)
Browse files Browse the repository at this point in the history
* displays timestamp as a number in the Tx Results and to Tx tables in Object and Address Results
  • Loading branch information
apburnie authored Jul 1, 2022
1 parent fe65c67 commit 518f5b9
Show file tree
Hide file tree
Showing 16 changed files with 517 additions and 208 deletions.
87 changes: 47 additions & 40 deletions explorer/client/src/__tests__/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,45 +155,52 @@ describe('End-to-end Tests', () => {
});
});

// TODO: Use mock data generated by sui/src/generate_json_rpc_spec.rs
// to make sure it's in sync with the backend
// describe('Transaction Results', () => {
// const successID = 'Da4vHc9IwbvOYblE8LnrVsqXwryt2Kmms+xnJ7Zx5E4=';
// it('can be searched', async () => {
// await page.goto(BASE_URL);
// await searchText(page, successID);
// const el = await page.$('#transactionID');
// const value = await page.evaluate((el: any) => el.textContent, el);
// expect(value.trim()).toBe(successID);
// });

// it('can be reached through URL', async () => {
// await page.goto(`${BASE_URL}/transactions/${successID}`);
// const el = await page.$('#transactionID');
// const value = await page.evaluate((el: any) => el.textContent, el);
// expect(value.trim()).toBe(successID);
// });
// it('can go to object and back', async () => {
// const objectID = '7bc832ec31709638cd8d9323e90edf332gff4389';
// await page.goto(`${BASE_URL}/transactions/${successID}`);

// //Go to Object
// const objectLink = await page.$(
// 'div#txview > div:nth-child(4) > div:nth-child(2)'
// );
// await objectLink.click();
// const el = await page.$('#objectID');
// const value = await page.evaluate((x: any) => x.textContent, el);
// expect(value.trim()).toBe(objectID);

// //Go back to Transaction
// const lastTransactionLink = await page.$('#lasttxID > a');
// await lastTransactionLink.click();
// const el2 = await page.$('#transactionID');
// const value2 = await page.evaluate((x: any) => x.textContent, el2);
// expect(value2.trim()).toBe(successID);
// });
// });
describe('Transaction Results', () => {
const successID = 'Da4vHc9IwbvOYblE8LnrVsqXwryt2Kmms+xnJ7Zx5E4=';
it('can be searched', async () => {
await page.goto(BASE_URL);
await searchText(page, successID);
const value = await cssInteract(page)
.with('#transactionID')
.get.textContent();
expect(value.trim()).toBe(successID);
});

it('can be reached through URL', async () => {
await page.goto(`${BASE_URL}/transactions/${successID}`);
const value = await cssInteract(page)
.with('#transactionID')
.get.textContent();
expect(value.trim()).toBe(successID);
});
it('correctly renders days and hours', async () => {
await page.goto(`${BASE_URL}/transactions/${successID}`);
const value = await cssInteract(page)
.with('#timestamp')
.get.textContent();
expect(value.trim()).toBe(
'17 days 1 hour ago (15 Dec 2024 00:00:00 UTC)'
);
});
it('correctly renders a time on the cusp of a year', async () => {
const otherID = 'GHTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=';
await page.goto(`${BASE_URL}/transactions/${otherID}`);
const value = await cssInteract(page)
.with('#timestamp')
.get.textContent();
expect(value.trim()).toBe(
'1 min 3 secs ago (01 Jan 2025 01:12:07 UTC)'
);
});
it('correctly renders a time diff of less than 1 sec', async () => {
const otherID = 'XHTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=';
await page.goto(`${BASE_URL}/transactions/${otherID}`);
const value = await cssInteract(page)
.with('#timestamp')
.get.textContent();
expect(value.trim()).toBe('< 1 sec ago (01 Jan 2025 01:13:09 UTC)');
});
});

describe('Owned Objects have links that enable', () => {
const navigationTemplate = async (
Expand Down Expand Up @@ -404,7 +411,7 @@ describe('End-to-end Tests', () => {
});
describe('Transactions for ID', () => {
const txResults =
'TxIdTxTypeStatusAddressesDa4vHc9IwbvOYblE8LnrVsqXwryt2Kmms+xnJ7Zx5E4=Transfer\u2714From:senderAddressTo:receiv...dressGHTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer\u2716From:senderAddressTo:receiv...dressXHTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer\u2714From:senderAddressTo:receiv...dressYHTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZHTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZITP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZJTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZKTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZLTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZMTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZNTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dressZOTP9gcFmF5KTspnz3KxXjvSH8Bx0jv68KFhdqfpdK8=Transfer✔From:senderAddressTo:receiv...dress';
'TxIdTimeTxTypeStatusAddressesXHTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressYHTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZHTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZITP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZJTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZKTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZLTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZMTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZNTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZOTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressZPTP9gcFmF5K...KFhdqfpdK8=<1secagoTransfer✔From:senderAddressTo:receiv...dressGHTP9gcFmF5K...KFhdqfpdK8=1min3secsagoTransfer✖From:senderAddressTo:receiv...dress';

it('are displayed deduplicated from and to address', async () => {
const address = 'ownsAllAddress';
Expand Down
26 changes: 26 additions & 0 deletions explorer/client/src/__tests__/unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { timeAgo } from '../utils/timeUtils';

const timeNow = 1735693990000;

describe('Unit Tests', () => {
describe('timeAgo', () => {
it('handles days', () => {
expect(timeAgo(1734220800000, timeNow)).toEqual('17 days 1 hour');
});
it('handles hours', () => {
expect(timeAgo(1735610580000, timeNow)).toEqual('23 hours 10 mins');
});
it('handles minutes', () => {
expect(timeAgo(1735693930000, timeNow)).toEqual('1 min');
});
it('handles seconds', () => {
expect(timeAgo(1735693987000, timeNow)).toEqual('3 secs');
});
it('handles milliseconds', () => {
expect(timeAgo(1735693989100, timeNow)).toEqual('< 1 sec');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ div.txcardgrid > div:first-child {
}

.txlatestesults {
@apply w-11/12 md:w-10/12 mx-auto mb-28 mt-10 m-auto;

max-width: 1200px;
margin-bottom: 2rem;
@apply w-11/12 md:w-10/12 max-w-[1200px] mx-auto mb-[2rem] mt-10 m-auto;
}

div.txadd {
Expand All @@ -45,6 +42,10 @@ div.txadd {
@apply md:w-1/12 max-w-sm;
}

.txage {
@apply md:w-3/12 max-w-sm;
}

.txsearch {
@apply w-full;
}
Expand Down
10 changes: 10 additions & 0 deletions explorer/client/src/components/transaction-card/RecentTxCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { IS_STATIC_ENV } from '../../utils/envUtil';
import { getAllMockTransaction } from '../../utils/static/searchUtil';
import { truncate } from '../../utils/stringUtils';
import { timeAgo } from '../../utils/timeUtils';
import ErrorResult from '../error-result/ErrorResult';
import Pagination from '../pagination/Pagination';

Expand All @@ -42,6 +43,7 @@ type TxnData = {
txGas: number;
kind: TransactionKindName | undefined;
From: string;
timestamp_ms?: number;
};

function generateStartEndRange(
Expand Down Expand Up @@ -116,6 +118,9 @@ function LatestTxView({
)}
>
<div className={styles.txcardgridlarge}>TxId</div>
{results.latestTx[0].timestamp_ms && (
<div className={styles.txage}>Time</div>
)}
<div className={styles.txtype}>TxType</div>
<div className={styles.txstatus}>Status</div>
<div className={styles.txgas}>Gas</div>
Expand All @@ -134,6 +139,11 @@ function LatestTxView({
alttext={truncate(tx.txId, TRUNCATE_LENGTH)}
/>
</div>
{tx.timestamp_ms && (
<div className={styles.txage}>{`${timeAgo(
tx.timestamp_ms
)} ago`}</div>
)}
<div className={styles.txtype}> {tx.kind}</div>
<div
className={cl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@
}

.txid {
@apply col-span-5;
@apply col-span-3;
}

.txadd {
@apply col-span-3;
}

.txtype,
.txstatus {
.txstatus,
.txage {
@apply col-span-2;
}

Expand Down
12 changes: 12 additions & 0 deletions explorer/client/src/components/transactions-for-id/TxForID.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { IS_STATIC_ENV } from '../../utils/envUtil';
import { deduplicate } from '../../utils/searchUtil';
import { findTxfromID, findTxDatafromID } from '../../utils/static/searchUtil';
import { truncate } from '../../utils/stringUtils';
import { timeAgo } from '../../utils/timeUtils';
import ErrorResult from '../error-result/ErrorResult';
import Longtext from '../longtext/Longtext';
import PaginationWrapper from '../pagination/PaginationWrapper';
Expand All @@ -37,6 +38,7 @@ type TxnData = {
kind: TransactionKindName | undefined;
From: string;
To?: string;
timestamp_ms?: number;
};

type categoryType = 'address' | 'object';
Expand All @@ -59,6 +61,9 @@ function TxForIDView({ showData }: { showData: TxnData[] | undefined }) {
<div id="tx" className={styles.txresults}>
<div className={styles.txheader}>
<div className={styles.txid}>TxId</div>
{showData[0].timestamp_ms && (
<div className={styles.txage}>Time</div>
)}
<div className={styles.txtype}>TxType</div>
<div className={styles.txstatus}>Status</div>
<div className={styles.txadd}>Addresses</div>
Expand All @@ -71,8 +76,14 @@ function TxForIDView({ showData }: { showData: TxnData[] | undefined }) {
text={x.txId}
category="transactions"
isLink={true}
alttext={truncate(x.txId, 26, '...')}
/>
</div>
{x.timestamp_ms && (
<div className={styles.txage}>
{`${timeAgo(x.timestamp_ms)} ago`}
</div>
)}
<div className={styles.txtype}>{x.kind}</div>
<div
className={cl(
Expand Down Expand Up @@ -149,6 +160,7 @@ function TxForIDAPI({ id, category }: { id: string; category: categoryType }) {
kind: el!.kind,
From: el!.From,
To: el!.To,
timestamp_ms: el!.timestamp_ms,
}));
setData({
data: subData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type TxnState = CertifiedTransaction & {
txError: string;
mutated: SuiObjectRef[];
created: SuiObjectRef[];
timestamp_ms: number;
};
// TODO: update state to include Call types
// TODO: clean up duplicate fields
Expand All @@ -61,6 +62,7 @@ const initState: TxnState = {
status: 'success',
gasFee: 0,
txError: '',
timestamp_ms: 0,
mutated: [],
created: [],
};
Expand Down Expand Up @@ -114,6 +116,7 @@ const transformTransactionResponse = (
loadState: 'loaded',
mutated: getCreatedOrMutatedData(txObj.effects, 'mutated'),
created: getCreatedOrMutatedData(txObj.effects, 'created'),
timestamp_ms: txObj.timestamp_ms,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export type DataType = CertifiedTransaction & {
txError: string;
mutated: SuiObjectRef[];
created: SuiObjectRef[];
timestamp_ms: number;
};
38 changes: 31 additions & 7 deletions explorer/client/src/pages/transaction-result/TransactionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import cl from 'classnames';

import Longtext from '../../components/longtext/Longtext';
import codestyle from '../../styles/bytecode.module.css';
import { convertNumberToDate, timeAgo } from '../../utils/timeUtils';
import { type DataType } from './TransactionResultType';

import type {
Expand All @@ -29,6 +30,7 @@ import styles from './TransactionResult.module.css';

type TxDataProps = CertifiedTransaction & {
status: ExecutionStatusType;
timestamp_ms: number;
gasFee: number;
txError: string;
mutated: SuiObjectRef[];
Expand All @@ -48,6 +50,15 @@ function formatTxResponse(tx: TxDataProps, txId: string) {
value: txId,
className: 'columnheader',
},
...(tx.timestamp_ms
? [
{
label: 'Time',
value: tx.timestamp_ms,
},
]
: []),

{
// May change later
label: 'Status',
Expand Down Expand Up @@ -198,14 +209,20 @@ function formatByTransactionKind(
}
}

function ItemView({ itm, text }: { itm: any; text: string }) {
function ItemView({ itm, text }: { itm: any; text: string | number }) {
switch (true) {
case itm.label === 'Modules':
return <div className={codestyle.code}>{itm.value}</div>;
case itm.label === 'Time':
return (
<>{`${timeAgo(text as number)} ago (${convertNumberToDate(
text as number
)})`}</>
);
case itm.link:
return (
<Longtext
text={text}
text={text as string}
category={itm.category ? itm.category : 'unknown'}
isLink={true}
/>
Expand Down Expand Up @@ -238,6 +255,17 @@ function SubListView({ itm, list }: { itm: any; list: any }) {
);
}

const TestIDMatcher = (label: string) => {
switch (label) {
case 'Transaction ID':
return 'transactionID';
case 'Time':
return 'timestamp';
default:
return '';
}
};

function TransactionView({ txdata }: { txdata: DataType }) {
return (
<>
Expand All @@ -263,11 +291,7 @@ function TransactionView({ txdata }: { txdata: DataType }) {
? styles[itm.classAttr]
: ''
)}
id={
itm.label === 'Transaction ID'
? 'transactionID'
: ''
}
id={TestIDMatcher(itm.label)}
>
{itm.list ? (
<ul className={styles.listitems}>
Expand Down
2 changes: 2 additions & 0 deletions explorer/client/src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
// SPDX-License-Identifier: Apache-2.0

import '@testing-library/jest-dom';

jest.setTimeout(100000);
1 change: 1 addition & 0 deletions explorer/client/src/utils/api/DefaultRpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const getDataOnTxDigests = (
txGas: getTotalGasUsed(txEff),
kind: txKind,
From: res.data.sender,
timestamp_ms: txEff.timestamp_ms,
...(recipient
? {
To: recipient,
Expand Down
Loading

0 comments on commit 518f5b9

Please sign in to comment.