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

Feature/165 add decode specification v1 #213

Merged
merged 47 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1b9772c
refactor: Close menu rather than toggling after clicking menu link.
brunomenezes May 8, 2024
d5c4624
feat: Add decoder for abi_params and json_abi modes, custom errors, t…
brunomenezes May 19, 2024
96e040b
refactor: Decode function name and test. Include jsdoc info.
brunomenezes May 20, 2024
df3f301
feat: Add conditionals to match against a input and return a Specific…
brunomenezes May 20, 2024
9f5d6c9
feat: Add system specifications (Portals). conditional test cases and…
brunomenezes May 20, 2024
a7c6af7
test: Add cases for logical operators with multi-conditions defined f…
brunomenezes May 21, 2024
1d2571a
test: Add decoder test cases for struct encoded data and include some…
brunomenezes May 21, 2024
caed66a
feat: Improve error handling when set wrong slice-target.
brunomenezes Jul 7, 2024
7d0ec42
feat: Link and access to specification page.
brunomenezes Jul 7, 2024
13dccc4
feat: Improve error information for specific error types.
brunomenezes Jul 7, 2024
c36d090
refactor: Export type for reuse in the form definitions.
brunomenezes Jul 7, 2024
b939b0b
feat: Add form-context, byte-slices, human-readable-abi and condition…
brunomenezes Jul 7, 2024
bd02a50
feat: Add specification view + form with preview UI.
brunomenezes Jul 7, 2024
e321f79
feat: Align sub-items for byte-slice definitions to be aligned with h…
brunomenezes Jul 8, 2024
db49dfe
refactor: Apply json formatting on stringify call and only build on d…
brunomenezes Jul 8, 2024
867084b
chore: Upgrade ramda to latest version and added ramda-adjunct for ut…
brunomenezes Jul 8, 2024
fe9299b
refactor: Reusing ramda-adjunct utility functions for validation.
brunomenezes Jul 8, 2024
ed16a7e
refactor: Improve the specification constants to have labels.
brunomenezes Jul 9, 2024
e05c9fc
refactor: Reset slice related that when switching off the add-byte-sl…
brunomenezes Jul 9, 2024
3ee9690
feat: Add conditionals section implementation.
brunomenezes Jul 9, 2024
3c65465
feat: Add conditions section to the json-abi mode.
brunomenezes Jul 9, 2024
73e15c4
feat: Add Human readable ABI and ABI-Parameters component. Also, incl…
brunomenezes Jul 10, 2024
b188078
refactor: Extract Label+Tooltip component for reuse.
brunomenezes Jul 11, 2024
7c34dcb
feat: Add ABI tip message, update json-abi mode info message and add …
brunomenezes Jul 11, 2024
5037341
refactor: Update conditions and byte-slices API to be more independen…
brunomenezes Jul 11, 2024
99fb593
feat: Add validation when trying to save the Specification.
brunomenezes Jul 12, 2024
d5c5d97
feat: Add Jotai + local-storage implementation + generic Repository i…
brunomenezes Jul 12, 2024
1e1804e
refactor: Form fields to export form-actions to allow external actions.
brunomenezes Jul 15, 2024
83eca4e
feat: Saving specification when validation pass and using the new for…
brunomenezes Jul 15, 2024
4700224
refactor: Remove property not controlled by big form.
brunomenezes Jul 15, 2024
53bf9d1
refactor: Add data-testid, renaming components and small fixes on hooks.
brunomenezes Jul 15, 2024
3efab7e
feat: Add page for creation and listing specifications with initial d…
brunomenezes Jul 15, 2024
a8c231f
feat: Add code-highlight for the conditionals when defined in a speci…
brunomenezes Jul 16, 2024
8f58fcb
feat: Add specification filtering and solidity-highlight-extensions f…
brunomenezes Jul 16, 2024
3950b7a
refactor: Replace Json-input component by CodeHighlight
brunomenezes Jul 17, 2024
0193725
feat: Add edition capability and rename/reorg components and APIs.
brunomenezes Jul 18, 2024
a5221f1
feat: Feedback when filtering existing specifications returns empty.
brunomenezes Jul 18, 2024
1a65354
feat (packages/ui): Expose event to notify about content-type change.…
brunomenezes Jul 26, 2024
608de4d
refactor: Remove margin for voucher execution. It is now stacked with…
brunomenezes Jul 26, 2024
243ca15
feat: Add Relay as a system spec, exporting types and add a hook for …
brunomenezes Jul 29, 2024
8aa8b04
feat: Extend NewSpecificationButton component props. Matches mantine …
brunomenezes Jul 29, 2024
13c83cd
feat: Integrate decoding spec + input-details-view. Improve error han…
brunomenezes Jul 29, 2024
ea3a064
fix: Add default text for new-spec-button.
brunomenezes Jul 30, 2024
2906c97
test: Add specification feature tests
brunomenezes Jul 30, 2024
418466f
refactor: Exposing onSuccess for better reusability. Removed routing …
brunomenezes Jul 31, 2024
113733f
chore: Small meta and text fixes.
brunomenezes Jul 31, 2024
292e26e
refactor: Change order of specification select and removing useEffect.
brunomenezes Aug 1, 2024
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
9 changes: 7 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@ant-design/web3-icons": "^1.6.0",
"@cartesi/rollups-explorer-ui": "*",
"@cartesi/rollups-wagmi": "*",
"@mantine/code-highlight": "^7.7.1",
"@mantine/core": "^7.7.1",
"@mantine/form": "^7.7.1",
"@mantine/hooks": "^7.7.1",
Expand All @@ -34,11 +35,15 @@
"encoding": "^0.1",
"graphql": "^16",
"graphql-tag": "^2",
"highlight.js": "11.10.0",
"highlightjs-solidity": "^2.0.6",
"jotai": "^2.9.0",
"lokijs": "^1",
"next": "^14.1.4",
"pino-pretty": "^10",
"pretty-ms": "^8",
"ramda": "^0.29.0",
"ramda": "0.30.1",
"ramda-adjunct": "^5.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4",
Expand All @@ -64,7 +69,7 @@
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@types/node": "^20",
"@types/ramda": "^0.29.3",
"@types/ramda": "^0.30.1",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.1.0",
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "@mantine/code-highlight/styles.css";
import { ColorSchemeScript } from "@mantine/core";
import "@mantine/core/styles.css";
import { Notifications } from "@mantine/notifications";
Expand Down
41 changes: 41 additions & 0 deletions apps/web/src/app/specifications/edit/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Group, Stack, Title } from "@mantine/core";
import { Metadata } from "next";
import { TbFileCode } from "react-icons/tb";
import Breadcrumbs from "../../../../components/breadcrumbs";
import { SpecificationContainer } from "../../../../components/specification/SpecificationContainer";

export const metadata: Metadata = {
title: "Edit Specifications",
};

interface PageProps {
params: {
id: string;
};
}

export default function EditSpecificationPage({ params }: PageProps) {
return (
<Stack>
<Breadcrumbs
breadcrumbs={[
{
href: "/",
label: "Home",
},
{
href: "/specifications",
label: "Specifications",
},
]}
/>

<Group mb="xl">
<TbFileCode size={40} />
<Title order={1}>Edit Specifications</Title>
</Group>

<SpecificationContainer specificationId={params.id} />
</Stack>
);
}
35 changes: 35 additions & 0 deletions apps/web/src/app/specifications/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Group, Stack, Title } from "@mantine/core";
import { Metadata } from "next";
import { TbFileCode } from "react-icons/tb";
import Breadcrumbs from "../../../components/breadcrumbs";
import { SpecificationContainer } from "../../../components/specification/SpecificationContainer";

export const metadata: Metadata = {
title: "New Specification",
};

export default function NewSpecificationPage() {
return (
<Stack>
<Breadcrumbs
breadcrumbs={[
{
href: "/",
label: "Home",
},
{
href: "/specifications",
label: "Specifications",
},
]}
/>

<Group mb="xl">
<TbFileCode size={40} />
<Title order={1}>Create a Specification</Title>
</Group>

<SpecificationContainer />
</Stack>
);
}
31 changes: 31 additions & 0 deletions apps/web/src/app/specifications/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Group, Stack, Title } from "@mantine/core";
import { Metadata } from "next";
import { TbFileCode } from "react-icons/tb";
import Breadcrumbs from "../../components/breadcrumbs";
import { SpecificationListView } from "../../components/specification/SpecificationListView";

export const metadata: Metadata = {
title: "Specifications",
};

export default function SpecificationsPage() {
return (
<Stack>
<Breadcrumbs
breadcrumbs={[
{
href: "/",
label: "Home",
},
]}
/>

<Group mb="xl">
<TbFileCode size={40} />
<Title order={1}>Specifications</Title>
</Group>

<SpecificationListView />
</Stack>
);
}
196 changes: 190 additions & 6 deletions apps/web/src/components/inputs/inputDetailsView.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
"use client";
import { InputDetails } from "@cartesi/rollups-explorer-ui";
import { Box } from "@mantine/core";
import { omit, pathOr } from "ramda";
import { Alert, Box, Group, Select, Stack, Text } from "@mantine/core";
import { find, omit, pathOr } from "ramda";
import { included, isNilOrEmpty, isNotNilOrEmpty } from "ramda-adjunct";
import { FC, useEffect, useState } from "react";
import { TbExclamationCircle } from "react-icons/tb";
import { useQuery } from "urql";
import { Address } from "viem";
import { Address, Hex } from "viem";
import { InputItemFragment } from "../../graphql/explorer/operations";
import {
InputDetailsDocument,
InputDetailsQuery,
InputDetailsQueryVariables,
} from "../../graphql/rollups/operations";
import { useConnectionConfig } from "../../providers/connectionConfig/hooks";
import { Voucher } from "../../graphql/rollups/types";
import { useConnectionConfig } from "../../providers/connectionConfig/hooks";
import { theme } from "../../providers/theme";
import { NewSpecificationButton } from "../specification/components/NewSpecificationButton";
import { findSpecificationFor } from "../specification/conditionals";
import { Envelope, decodePayload } from "../specification/decoder";
import { useSpecification } from "../specification/hooks/useSpecification";
import { useSystemSpecifications } from "../specification/hooks/useSystemSpecifications";
import { Specification } from "../specification/types";
import { stringifyContent } from "../specification/utils";
import VoucherExecution from "./voucherExecution";

interface ApplicationInputDataProps {
Expand Down Expand Up @@ -55,6 +65,105 @@ const updateForPrevPage = (
}
};

const getStringifiedDecodedValueOrPayload = (
envelope: Envelope | undefined,
originalPayload: string,
) => {
if (envelope?.error) {
console.error(envelope.error);
return originalPayload;
}

if (!envelope || isNilOrEmpty(envelope?.result)) {
return originalPayload;
}

return stringifyContent(envelope.result);
};

type UseDecodingOnInputResult = [
string,
{
specApplied: Specification | null;
userSpecifications: Specification[];
systemSpecifications: Specification[];
error?: Error;
wasSpecManuallySelected: boolean;
},
];

/**
* Receive the input from a graphQL call and
* It may find a specification that matches a defined condition, therefore decoding the content.
* If an specification is not found it returns the original payload.
* @param input Input data returned by graphQL.
* @param specName Specification id to apply instead of check for matching conditions. (optional)
* @returns { UseDecodingOnInputResult }
*/
const useDecodingOnInput = (
input: InputItemFragment,
specId?: string,
): UseDecodingOnInputResult => {
const { listSpecifications } = useSpecification();
const { systemSpecificationAsList } = useSystemSpecifications();

const userSpecifications = listSpecifications() ?? [];
const specifications = [
...systemSpecificationAsList,
...userSpecifications,
];

const specification = isNilOrEmpty(specId)
? findSpecificationFor(input, specifications)
: find((spec) => spec.id === specId, specifications) ?? null;

const envelope = specification
? decodePayload(specification, input.payload as Hex)
: undefined;

const result = getStringifiedDecodedValueOrPayload(envelope, input.payload);

return [
result,
{
specApplied: specification,
systemSpecifications: systemSpecificationAsList,
userSpecifications,
error: envelope?.error,
wasSpecManuallySelected: isNotNilOrEmpty(specId),
},
];
};

const buildSelectData = (
userSpecifications: Specification[],
systemSpecifications: Specification[],
) => {
const groups = [];

if (userSpecifications.length) {
groups.push({
group: "Your Specifications",
items: userSpecifications.map((spec) => ({
label: spec.name,
value: spec.id!,
})),
});
}

if (systemSpecifications.length) {
groups.push({
group: "System Specifications",
items: systemSpecifications.map((spec) => ({
label: spec.name,
value: spec.id!,
})),
});
}

return groups;
};

/**
* InputDetailsView should be lazy rendered.
* to avoid multiple eager network calls.
Expand All @@ -65,6 +174,7 @@ const InputDetailsView: FC<ApplicationInputDataProps> = ({ input }) => {
const appId = input.application.id as Address;
const inputIdx = input.index;
const connection = getConnection(appId);
const [selectedSpec, setSelectedSpec] = useState<string>("");
const [variables, updateQueryVars] = useState<InputDetailsQueryVariables>({
firstNotices: 1,
firstReports: 1,
Expand Down Expand Up @@ -99,17 +209,91 @@ const InputDetailsView: FC<ApplicationInputDataProps> = ({ input }) => {
const showVoucherForExecution =
showVouchers && vouchersForExecution.length > 0;

const [
inputContent,
{
specApplied,
error,
systemSpecifications,
userSpecifications,
wasSpecManuallySelected,
},
] = useDecodingOnInput(input, selectedSpec);

const selectData = buildSelectData(
userSpecifications,
systemSpecifications,
);

useEffect(() => {
if (connection) execQuery({ url: connection.url });
}, [connection, execQuery, variables]);

const isSystemSpecAppliedManually =
wasSpecManuallySelected && included(systemSpecifications, specApplied);

return (
<Box py="md">
<InputDetails>
<InputDetails.InputContent
content={input.payload}
content={inputContent}
contentType="raw"
/>
>
<Stack gap="sm">
<Group>
<Select
label="Decode Specification"
description="When a specification condition(s) match(es), it will be auto-selected."
placeholder="Decode content with..."
value={specApplied?.id ?? selectedSpec}
size="md"
checkIconPosition="right"
data={selectData}
onChange={(value) => {
setSelectedSpec(value ?? "");
}}
error={
error
? `We're not able to decode using ${specApplied?.name}`
: null
}
/>
</Group>
{isSystemSpecAppliedManually && (
<Group>
<Alert
data-testid="system-spec-applied-warning"
icon={
<TbExclamationCircle
size={theme.other.iconSize}
/>
}
color="orange"
title="System Specifications"
>
Be careful when manually selecting system
specifications.
<br /> It may show readable information by
sheer luck of byte length.
<br /> They are always auto-selected.
</Alert>
</Group>
)}

{!specApplied && (
<Group gap={3}>
<Text c="dimmed">
{`Is this Application ABI encoding it's inputs?`}
</Text>
<NewSpecificationButton
p={0}
variant="transparent"
btnText="Add a Spec!"
/>
</Group>
)}
</Stack>
</InputDetails.InputContent>

{showReports && (
<InputDetails.ReportContent
Expand Down
Loading
Loading