From be59a6d1b595ff2f00e2668dbf6e4495a3edb7d0 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 22 Aug 2023 11:33:25 +0200 Subject: [PATCH 1/4] fix interaction source sanitization before compare --- .../interactions/hooks/use-interaction-name.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/interactions/hooks/use-interaction-name.ts b/frontend/src/pages/interactions/hooks/use-interaction-name.ts index 22610b9f..e4084715 100644 --- a/frontend/src/pages/interactions/hooks/use-interaction-name.ts +++ b/frontend/src/pages/interactions/hooks/use-interaction-name.ts @@ -16,18 +16,22 @@ export function useInteractionName( (template) => props.sourceCode && template.code && - // Ignore imports for comparison, - // since those can differ due to address replacement. - // See: https://developers.flow.com/tooling/fcl-js/api#address-replacement - stripImports(template.code) === stripImports(props.sourceCode) + sanitizeCadenceSource(template.code) === + sanitizeCadenceSource(props.sourceCode) )?.name, [props.sourceCode, templates] ); } -function stripImports(code: string) { - return code +function sanitizeCadenceSource(code: string) { + // Ignore imports for comparison, + // since those can differ due to address replacement. + // See: https://developers.flow.com/tooling/fcl-js/api#address-replacement + const strippedImports = code .split("\n") .filter((line) => !line.startsWith("import")) .join("\n"); + + // Replace all whitespace and newlines + return strippedImports.replaceAll(/[\n\t ]/g, ""); } From 4134027a24e48c376716ca3ff5ba79a251697a23 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 22 Aug 2023 11:55:22 +0200 Subject: [PATCH 2/4] hardcode templates for common flow-cli transactions --- .../components/history/InteractionHistory.tsx | 6 +- .../hooks/use-interaction-name.ts | 37 ------ .../hooks/use-transaction-name.ts | 106 ++++++++++++++++++ 3 files changed, 109 insertions(+), 40 deletions(-) delete mode 100644 frontend/src/pages/interactions/hooks/use-interaction-name.ts create mode 100644 frontend/src/pages/interactions/hooks/use-transaction-name.ts diff --git a/frontend/src/pages/interactions/components/history/InteractionHistory.tsx b/frontend/src/pages/interactions/components/history/InteractionHistory.tsx index 2a694bff..5a0f5ad9 100644 --- a/frontend/src/pages/interactions/components/history/InteractionHistory.tsx +++ b/frontend/src/pages/interactions/components/history/InteractionHistory.tsx @@ -10,7 +10,7 @@ import { FlowserIcon } from "../../../../components/icons/Icons"; import { SizedBox } from "../../../../components/sized-box/SizedBox"; import { Spinner } from "../../../../components/spinner/Spinner"; import { useInteractionRegistry } from "../../contexts/interaction-registry.context"; -import { useInteractionName } from "../../hooks/use-interaction-name"; +import { useTransactionName } from "../../hooks/use-transaction-name"; export function InteractionHistory(): ReactElement { const { data: blocks, firstFetch } = useGetPollingBlocks(); @@ -55,8 +55,8 @@ function BlockItem(props: BlockItemProps) { }); const firstTransaction = data[0]; - const transactionName = useInteractionName({ - sourceCode: firstTransaction?.script, + const transactionName = useTransactionName({ + transaction: firstTransaction, }); function onForkAsTemplate() { diff --git a/frontend/src/pages/interactions/hooks/use-interaction-name.ts b/frontend/src/pages/interactions/hooks/use-interaction-name.ts deleted file mode 100644 index e4084715..00000000 --- a/frontend/src/pages/interactions/hooks/use-interaction-name.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useInteractionRegistry } from "../contexts/interaction-registry.context"; -import { useMemo } from "react"; - -type UseInteractionNameProps = { - sourceCode: string | undefined; -}; - -export function useInteractionName( - props: UseInteractionNameProps -): string | undefined { - const { templates } = useInteractionRegistry(); - - return useMemo( - () => - templates.find( - (template) => - props.sourceCode && - template.code && - sanitizeCadenceSource(template.code) === - sanitizeCadenceSource(props.sourceCode) - )?.name, - [props.sourceCode, templates] - ); -} - -function sanitizeCadenceSource(code: string) { - // Ignore imports for comparison, - // since those can differ due to address replacement. - // See: https://developers.flow.com/tooling/fcl-js/api#address-replacement - const strippedImports = code - .split("\n") - .filter((line) => !line.startsWith("import")) - .join("\n"); - - // Replace all whitespace and newlines - return strippedImports.replaceAll(/[\n\t ]/g, ""); -} diff --git a/frontend/src/pages/interactions/hooks/use-transaction-name.ts b/frontend/src/pages/interactions/hooks/use-transaction-name.ts new file mode 100644 index 00000000..0faa06e0 --- /dev/null +++ b/frontend/src/pages/interactions/hooks/use-transaction-name.ts @@ -0,0 +1,106 @@ +import { useInteractionRegistry } from "../contexts/interaction-registry.context"; +import { useMemo } from "react"; +import { Transaction } from "@flowser/shared"; + +type UseInteractionNameProps = { + transaction: Transaction | undefined; +}; + +enum TransactionKind { + DEPLOY_CONTRACT, + INITIALIZE_ACCOUNT, +} + +const hardcodedTemplates: [string, TransactionKind][] = [ + [ + `transaction(name: String, code: String ) { + prepare(signer: AuthAccount) { + signer.contracts.add(name: name, code: code.decodeHex() ) + } + }`, + TransactionKind.DEPLOY_CONTRACT, + ], + [ + `import Crypto + + transaction(publicKeys: [Crypto.KeyListEntry], contracts: {String: String}) { + prepare(signer: AuthAccount) { + let account = AuthAccount(payer: signer) + + // add all the keys to the account + for key in publicKeys { + account.keys.add(publicKey: key.publicKey, hashAlgorithm: key.hashAlgorithm, weight: key.weight) + } + + // add contracts if provided + for contract in contracts.keys { + account.contracts.add(name: contract, code: contracts[contract]!.decodeHex()) + } + } + }`, + TransactionKind.INITIALIZE_ACCOUNT, + ], +]; + +const transactionKindBySource = new Map( + hardcodedTemplates.map((entry) => [sanitizeCadenceSource(entry[0]), entry[1]]) +); + +export function useTransactionName( + props: UseInteractionNameProps +): string | undefined { + const { transaction } = props; + const { templates } = useInteractionRegistry(); + + return useMemo(() => { + if (!transaction?.script) { + return "Unknown"; + } + + const sanitizedTargetCode = sanitizeCadenceSource(transaction.script); + const matchingTemplateName = templates.find( + (template) => + template.code && + sanitizeCadenceSource(template.code) === sanitizedTargetCode + )?.name; + + return matchingTemplateName ?? getDynamicName(transaction) ?? "Unknown"; + }, [transaction, templates]); +} + +function getDynamicName(transaction: Transaction) { + const kind = transactionKindBySource.get( + sanitizeCadenceSource(transaction.script) + ); + + switch (kind) { + case TransactionKind.DEPLOY_CONTRACT: + return `Deploy ${ + getArgumentValueById(transaction, "name") ?? "contract" + }`; + case TransactionKind.INITIALIZE_ACCOUNT: + return "Init signer account"; + default: + return undefined; + } +} + +function getArgumentValueById(transaction: Transaction, id: string) { + return JSON.parse( + transaction.arguments.find((argument) => argument.identifier === id) + ?.valueAsJson ?? "" + ); +} + +function sanitizeCadenceSource(code: string) { + // Ignore imports for comparison, + // since those can differ due to address replacement. + // See: https://developers.flow.com/tooling/fcl-js/api#address-replacement + const strippedImports = code + .split("\n") + .filter((line) => !line.startsWith("import")) + .join("\n"); + + // Replace all whitespace and newlines + return strippedImports.replaceAll(/[\n\t ]/g, ""); +} From 75b7442ed92e988c5eba837cfe331334d976fd32 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 22 Aug 2023 12:13:58 +0200 Subject: [PATCH 3/4] fix interaction name when forked from history --- .../interactions/components/history/InteractionHistory.tsx | 2 +- frontend/src/pages/interactions/hooks/use-transaction-name.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/interactions/components/history/InteractionHistory.tsx b/frontend/src/pages/interactions/components/history/InteractionHistory.tsx index 5a0f5ad9..f54f3f91 100644 --- a/frontend/src/pages/interactions/components/history/InteractionHistory.tsx +++ b/frontend/src/pages/interactions/components/history/InteractionHistory.tsx @@ -65,7 +65,7 @@ function BlockItem(props: BlockItemProps) { } create({ id: block.id, - name: `Tx from block #${block.height}`, + name: transactionName ?? `Tx from block #${block.height}`, code: firstTransaction.script, fclValuesByIdentifier: new Map( firstTransaction.arguments.map((arg) => [ diff --git a/frontend/src/pages/interactions/hooks/use-transaction-name.ts b/frontend/src/pages/interactions/hooks/use-transaction-name.ts index 0faa06e0..54a954ff 100644 --- a/frontend/src/pages/interactions/hooks/use-transaction-name.ts +++ b/frontend/src/pages/interactions/hooks/use-transaction-name.ts @@ -54,7 +54,7 @@ export function useTransactionName( return useMemo(() => { if (!transaction?.script) { - return "Unknown"; + return undefined; } const sanitizedTargetCode = sanitizeCadenceSource(transaction.script); @@ -64,7 +64,7 @@ export function useTransactionName( sanitizeCadenceSource(template.code) === sanitizedTargetCode )?.name; - return matchingTemplateName ?? getDynamicName(transaction) ?? "Unknown"; + return matchingTemplateName ?? getDynamicName(transaction); }, [transaction, templates]); } From c3f01e4df62093b4b0037f204029a6807adcee8c Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 22 Aug 2023 13:20:08 +0200 Subject: [PATCH 4/4] display tx name in transaction tables --- frontend/src/pages/blocks/details/Details.tsx | 74 +--------------- frontend/src/pages/transactions/main/Main.tsx | 87 +------------------ ...ule.scss => TransactionsTable.module.scss} | 0 .../transactions/main/TransactionsTable.tsx | 19 +++- 4 files changed, 23 insertions(+), 157 deletions(-) rename frontend/src/pages/transactions/main/{Main.module.scss => TransactionsTable.module.scss} (100%) diff --git a/frontend/src/pages/blocks/details/Details.tsx b/frontend/src/pages/blocks/details/Details.tsx index c9cf0ad4..d42b0e7f 100644 --- a/frontend/src/pages/blocks/details/Details.tsx +++ b/frontend/src/pages/blocks/details/Details.tsx @@ -1,87 +1,23 @@ import React, { FunctionComponent, useEffect } from "react"; import { NavLink, useParams } from "react-router-dom"; import { Breadcrumb, useNavigation } from "../../../hooks/use-navigation"; -import Label from "../../../components/label/Label"; -import Value from "../../../components/value/Value"; import classes from "./Details.module.scss"; import FullScreenLoading from "../../../components/fullscreen-loading/FullScreenLoading"; import { useGetBlock, useGetTransactionsByBlock } from "../../../hooks/use-api"; import { FlowUtils } from "../../../utils/flow-utils"; -import { createColumnHelper } from "@tanstack/table-core"; -import { Transaction } from "@flowser/shared"; -import Table from "../../../components/table/Table"; -import MiddleEllipsis from "../../../components/ellipsis/MiddleEllipsis"; -import { ExecutionStatus } from "components/status/ExecutionStatus"; import { DetailsCard, DetailsCardColumn, } from "components/details-card/DetailsCard"; import { TextUtils } from "../../../utils/text-utils"; -import { GrcpStatus } from "../../../components/status/GrcpStatus"; -import { DecoratedPollingEntity } from "contexts/timeout-polling.context"; -import { enableDetailsIntroAnimation } from "../../../config/common"; import { SizedBox } from "../../../components/sized-box/SizedBox"; -import { AccountLink } from "../../../components/account/link/AccountLink"; import { StyledTabs } from "../../../components/tabs/StyledTabs"; +import { TransactionsTable } from "../../transactions/main/TransactionsTable"; type RouteParams = { blockId: string; }; -const txTableColHelper = - createColumnHelper>(); - -const txTableColumns = [ - txTableColHelper.accessor("id", { - header: () => , - cell: (info) => ( - - - - {info.getValue()} - - - - ), - }), - txTableColHelper.accessor("payer", { - header: () => , - cell: (info) => ( - - - - ), - }), - txTableColHelper.accessor("proposalKey", { - header: () => , - cell: (info) => ( - - {info.row.original.proposalKey ? ( - - ) : ( - "-" - )} - - ), - }), - txTableColHelper.accessor("status.grcpStatus", { - header: () => , - cell: (info) => ( -
- -
- ), - }), - txTableColHelper.accessor("status.grcpStatus", { - header: () => , - cell: (info) => ( -
- -
- ), - }), -]; - const Details: FunctionComponent = () => { const { blockId } = useParams(); const { setBreadcrumbs } = useNavigation(); @@ -144,13 +80,7 @@ const Details: FunctionComponent = () => { { id: "transactions", label: "Transactions", - content: ( - > - data={transactions} - columns={txTableColumns} - enableIntroAnimations={enableDetailsIntroAnimation} - /> - ), + content: , }, ]} /> diff --git a/frontend/src/pages/transactions/main/Main.tsx b/frontend/src/pages/transactions/main/Main.tsx index 74f0dbc8..ead2ceb9 100644 --- a/frontend/src/pages/transactions/main/Main.tsx +++ b/frontend/src/pages/transactions/main/Main.tsx @@ -1,98 +1,17 @@ import React, { FunctionComponent, useEffect } from "react"; -import classes from "./Main.module.scss"; import { useNavigation } from "../../../hooks/use-navigation"; import { useGetPollingTransactions } from "../../../hooks/use-api"; -import { createColumnHelper } from "@tanstack/table-core"; -import { Transaction } from "@flowser/shared"; -import Label from "../../../components/label/Label"; -import Value from "../../../components/value/Value"; -import { NavLink } from "react-router-dom"; -import MiddleEllipsis from "../../../components/ellipsis/MiddleEllipsis"; -import Table from "../../../components/table/Table"; -import { ExecutionStatus } from "components/status/ExecutionStatus"; -import { GrcpStatus } from "../../../components/status/GrcpStatus"; -import ReactTimeago from "react-timeago"; -import { DecoratedPollingEntity } from "contexts/timeout-polling.context"; -import { AccountLink } from "../../../components/account/link/AccountLink"; - -// TRANSACTIONS TABLE -const columnHelper = createColumnHelper>(); - -export const transactionTableColumns = [ - columnHelper.accessor("id", { - header: () => , - cell: (info) => ( - - - - {info.getValue()} - - - - ), - }), - columnHelper.accessor("blockId", { - header: () => , - cell: (info) => ( - - - - {info.getValue()} - - - - ), - }), - columnHelper.accessor("payer", { - header: () => , - cell: (info) => ( - - - - ), - }), - columnHelper.accessor("status.executionStatus", { - header: () => , - cell: (info) => ( -
- -
- ), - }), - columnHelper.accessor("status.grcpStatus", { - header: () => , - cell: (info) => ( -
- -
- ), - }), - columnHelper.accessor("createdAt", { - header: () => , - cell: (info) => ( - - - - ), - }), -]; +import { TransactionsTable } from "./TransactionsTable"; const Main: FunctionComponent = () => { const { showNavigationDrawer } = useNavigation(); - const { data, firstFetch, error } = useGetPollingTransactions(); + const { data } = useGetPollingTransactions(); useEffect(() => { showNavigationDrawer(false); }, []); - return ( - > - isInitialLoading={firstFetch} - error={error} - data={data} - columns={transactionTableColumns} - /> - ); + return ; }; export default Main; diff --git a/frontend/src/pages/transactions/main/Main.module.scss b/frontend/src/pages/transactions/main/TransactionsTable.module.scss similarity index 100% rename from frontend/src/pages/transactions/main/Main.module.scss rename to frontend/src/pages/transactions/main/TransactionsTable.module.scss diff --git a/frontend/src/pages/transactions/main/TransactionsTable.tsx b/frontend/src/pages/transactions/main/TransactionsTable.tsx index a80781e2..d2719afd 100644 --- a/frontend/src/pages/transactions/main/TransactionsTable.tsx +++ b/frontend/src/pages/transactions/main/TransactionsTable.tsx @@ -5,13 +5,14 @@ import Label from "../../../components/label/Label"; import Value from "../../../components/value/Value"; import { NavLink } from "react-router-dom"; import MiddleEllipsis from "../../../components/ellipsis/MiddleEllipsis"; -import classes from "./Main.module.scss"; +import classes from "./TransactionsTable.module.scss"; import { AccountLink } from "../../../components/account/link/AccountLink"; import { ExecutionStatus } from "../../../components/status/ExecutionStatus"; import { GrcpStatus } from "../../../components/status/GrcpStatus"; import ReactTimeago from "react-timeago"; import React, { ReactElement } from "react"; import Table from "../../../components/table/Table"; +import { useTransactionName } from "../../interactions/hooks/use-transaction-name"; const columnHelper = createColumnHelper>(); @@ -48,6 +49,15 @@ const columns = [ ), }), + columnHelper.display({ + id: "name", + header: () => , + cell: (info) => ( + + + + ), + }), columnHelper.accessor("status.executionStatus", { header: () => , cell: (info) => ( @@ -86,3 +96,10 @@ export function TransactionsTable(props: TransactionsTableProps): ReactElement { /> ); } + +function TransactionName(props: { transaction: Transaction }) { + const name = useTransactionName({ + transaction: props.transaction, + }); + return {name}; +}