Skip to content

Commit

Permalink
Generate JSON response sample (MystenLabs#1971)
Browse files Browse the repository at this point in the history
* Generate JSON response sample in `generate_json_rpc_spec.rs` when schema changes

* Update TypeScript SDK

* Fix image rendering

* Disable outdated test

* Fix e2e test

Co-authored-by: Chris Li <[email protected]>
  • Loading branch information
patrickkuo and 666lcz authored May 16, 2022
1 parent cf65cfe commit 48f2840
Show file tree
Hide file tree
Showing 31 changed files with 1,442 additions and 1,962 deletions.
76 changes: 39 additions & 37 deletions explorer/client/src/__tests__/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,43 +151,45 @@ describe('End-to-end Tests', () => {
});
});

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);
});
});
// 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('Owned Objects have links that enable', () => {
const navigationTemplate = async (
Expand Down
49 changes: 25 additions & 24 deletions explorer/client/src/components/ownedobjects/OwnedObjects.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { getObjectContent, getObjectExistsResponse } from '@mysten/sui.js';
import { Coin, getObjectFields, getObjectId } from '@mysten/sui.js';
import React, { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';

Expand All @@ -25,6 +25,7 @@ import styles from './OwnedObjects.module.css';
type resultType = {
id: string;
Type: string;
_isCoin: boolean;
Version?: string;
display?: string;
balance?: number;
Expand All @@ -34,11 +35,10 @@ const DATATYPE_DEFAULT: resultType = [
{
id: '',
Type: '',
_isCoin: false,
},
];

const IS_COIN_TYPE = (typeDesc: string): boolean => /::Coin::/.test(typeDesc);

const lastRowHas2Elements = (itemList: any[]): boolean =>
itemList.length % 3 === 2;

Expand All @@ -61,6 +61,7 @@ function OwnedObjectStatic({ id }: { id: string }) {
Version: entry?.version,
display: entry?.data?.contents?.display,
balance: entry?.data?.contents?.balance,
_isCoin: entry?.data?.contents?.balance !== undefined,
};
});

Expand All @@ -75,10 +76,6 @@ function OwnedObjectAPI({ id }: { id: string }) {
const [isLoaded, setIsLoaded] = useState(false);
const [isFail, setIsFail] = useState(false);

// TODO - The below will fail for a large number of owned objects
// due to the number of calls to the API
// Backend changes will be required to enable a scalable solution
// getOwnedObjectRefs will need to return id, type and balance for each owned object
useEffect(() => {
setIsFail(false);
setIsLoaded(false);
Expand All @@ -91,25 +88,25 @@ function OwnedObjectAPI({ id }: { id: string }) {
.filter(({ status }) => status === 'Exists')
.map(
(resp) => {
const info = getObjectExistsResponse(resp)!;
const contents = getObjectContent(resp);
const url = parseImageURL(info.object);
const balanceValue = (
typeof contents?.fields.balance ===
'number'
? contents.fields.balance
: undefined
) as number;
const contents = getObjectFields(resp);
const url = parseImageURL(contents);
const objType = parseObjectType(resp);
// TODO: handle big number
const rawBalance = Coin.getBalance(resp);
const balanceValue = rawBalance
? parseInt(rawBalance)
: undefined;
return {
id: info.objectRef.objectId,
Type: parseObjectType(info),
id: getObjectId(resp),
Type: objType,
_isCoin: Coin.isCoin(resp),
display: url
? processDisplayValue(url)
: undefined,
balance: balanceValue,
};
}
//TO DO - add back version
// TODO - add back version
)
);
setIsLoaded(true);
Expand All @@ -126,9 +123,9 @@ function OwnedObjectAPI({ id }: { id: string }) {
}

function OwnedObjectLayout({ results }: { results: resultType }) {
const coin_results = results.filter(({ Type }) => IS_COIN_TYPE(Type));
const coin_results = results.filter(({ _isCoin }) => _isCoin);
const other_results = results
.filter(({ Type }) => !IS_COIN_TYPE(Type))
.filter(({ _isCoin }) => !_isCoin)
.sort((a, b) => {
if (a.Type > b.Type) return 1;
if (a.Type < b.Type) return -1;
Expand Down Expand Up @@ -194,12 +191,13 @@ function GroupView({ results }: { results: resultType }) {
<div>
<span>Balance</span>
<span>
{IS_COIN_TYPE(typeV) &&
{subObjList[0]._isCoin &&
subObjList.every(
(el) => el.balance !== undefined
)
? `${subObjList.reduce(
(prev, current) =>
// TODO: handle number overflow
prev + current.balance!,
0
)}`
Expand Down Expand Up @@ -369,7 +367,7 @@ function OwnedObjectView({ results }: { results: resultType }) {
case 'display':
break;
case 'Type':
if (IS_COIN_TYPE(entryObj.Type)) {
if (entryObj._isCoin) {
break;
} else {
return (
Expand All @@ -386,9 +384,12 @@ function OwnedObjectView({ results }: { results: resultType }) {
default:
if (
key === 'balance' &&
!IS_COIN_TYPE(entryObj.Type)
!entryObj._isCoin
)
break;
if (key.startsWith('_')) {
break;
}
return (
<div>
<span>{key}</span>
Expand Down
32 changes: 16 additions & 16 deletions explorer/client/src/components/transaction-card/RecentTxCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
// SPDX-License-Identifier: Apache-2.0

import {
getSingleTransactionKind,
getTransactionKind,
getTransferTransaction,
getExecutionStatusType,
getTotalGasUsed,
getTransactions,
getTransactionDigest,
getTransactionKindName,
getTransferCoinTransaction,
} from '@mysten/sui.js';
import cl from 'classnames';
import { useEffect, useState } from 'react';
Expand Down Expand Up @@ -60,27 +61,26 @@ async function getRecentTransactions(txNum: number): Promise<TxnData[]> {
const [seq, digest] = transactions.filter(
(transactionId) =>
transactionId[1] ===
txEff.effects.transaction_digest
getTransactionDigest(txEff.certificate)
)[0];
const res: CertifiedTransaction = txEff.certificate;
const singleTransaction = getSingleTransactionKind(
res.data
);
if (!singleTransaction) {
// TODO: handle multiple transactions
const txns = getTransactions(res);
if (txns.length > 1) {
throw new Error(
`Transaction kind not supported yet ${res.data.kind}`
`Handling multiple transactions is not yet supported`
);
}
const txKind = getTransactionKind(res.data);
const recipient = getTransferTransaction(
res.data
)?.recipient;
const txn = txns[0];
const txKind = getTransactionKindName(txn);
const recipient =
getTransferCoinTransaction(txn)?.recipient;

return {
seq,
txId: digest,
status: getExecutionStatusType(txEff.effects.status),
txGas: getTotalGasUsed(txEff.effects.status),
status: getExecutionStatusType(txEff),
txGas: getTotalGasUsed(txEff),
kind: txKind,
From: res.data.sender,
...(recipient
Expand Down Expand Up @@ -165,7 +165,7 @@ function LatestTxView({
styles.txstatus
)}
>
{tx.status === 'Success' ? '✔' : '✖'}
{tx.status === 'success' ? '✔' : '✖'}
</div>
<div className={styles.txgas}>{tx.txGas}</div>
<div className={styles.txadd}>
Expand Down
2 changes: 1 addition & 1 deletion explorer/client/src/pages/object-result/ObjectLoaded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function ObjectLoaded({ data }: { data: DataType }) {
? toHexString(data.data.tx_digest as number[])
: data.data.tx_digest,
owner: processOwner(data.owner),
url: parseImageURL(data.data),
url: parseImageURL(data.data.contents),
};

//TO DO remove when have distinct name field under Description
Expand Down
49 changes: 21 additions & 28 deletions explorer/client/src/pages/object-result/ObjectResultType.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { getMovePackageContent, getObjectContent } from '@mysten/sui.js';
import {
getMovePackageContent,
getObjectId,
getObjectVersion,
getObjectOwner,
getObjectFields,
getObjectPreviousTransactionDigest,
} from '@mysten/sui.js';

import { type AddressOwner } from '../../utils/api/DefaultRpcClient';
import { parseObjectType } from '../../utils/objectUtils';

import type {
GetObjectInfoResponse,
ObjectExistsInfo,
ObjectNotExistsInfo,
ObjectOwner,
ObjectRef,
} from '@mysten/sui.js';
import type { GetObjectInfoResponse, ObjectOwner } from '@mysten/sui.js';

export type DataType = {
id: string;
Expand Down Expand Up @@ -45,41 +46,33 @@ export function instanceOfDataType(object: any): object is DataType {
* to make this more extensible and customizable for different Move types
*/
export function translate(o: GetObjectInfoResponse): DataType {
const { status, details } = o;
switch (status) {
switch (o.status) {
case 'Exists': {
const {
objectRef: { objectId, version },
object: { owner, tx_digest },
} = details as ObjectExistsInfo;

return {
id: objectId,
version: version.toString(),
objType: parseObjectType(details as ObjectExistsInfo)!,
owner: parseOwner(owner),
id: getObjectId(o),
version: getObjectVersion(o)!.toString(),
objType: parseObjectType(o),
owner: parseOwner(getObjectOwner(o)!),
data: {
contents:
getObjectContent(o)?.fields ??
getMovePackageContent(o)!,
tx_digest,
contents: getObjectFields(o) ?? getMovePackageContent(o)!,
tx_digest: getObjectPreviousTransactionDigest(o),
},
};
}
case 'NotExists': {
const { objectId } = details as ObjectNotExistsInfo;
// TODO: implement this
throw new Error(`Implement me: Object ${objectId} does not exist`);
throw new Error(
`Implement me: Object ${getObjectId(o)} does not exist`
);
}
case 'Deleted': {
const { objectId } = details as ObjectRef;
// TODO: implement this
throw new Error(
`Implement me: Object ${objectId} has been deleted`
`Implement me: Object ${getObjectId(o)} has been deleted`
);
}
default: {
throw new Error(`Unexpected status ${status} for object ${o}`);
throw new Error(`Unexpected status ${o.status} for object ${o}`);
}
}
}
Expand Down
Loading

0 comments on commit 48f2840

Please sign in to comment.