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

Generate JSON response sample #1971

Merged
merged 5 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
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