Skip to content

Commit 72e77b2

Browse files
committed
feat: add aggregate parameter input with presets
1 parent 317211a commit 72e77b2

File tree

2 files changed

+213
-8
lines changed

2 files changed

+213
-8
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"use client";
2+
3+
import { Input } from "@/components/ui/input";
4+
import {
5+
Select,
6+
SelectContent,
7+
SelectItem,
8+
SelectTrigger,
9+
SelectValue,
10+
} from "@/components/ui/select";
11+
import { cn } from "@/lib/utils";
12+
import type { ControllerRenderProps } from "react-hook-form";
13+
14+
interface Preset {
15+
label: string;
16+
value: string;
17+
}
18+
19+
const DEFAULT_AGGREGATE_PRESETS: Preset[] = [
20+
{ label: "Count All Items", value: "count() AS count_all" },
21+
{ label: "Sum (gas_used)", value: "sum(gas_used) AS total_gas_used" },
22+
{ label: "Average (gas_used)", value: "avg(gas_used) AS avg_gas_used" },
23+
{ label: "Min (gas_used)", value: "min(gas_used) AS min_gas_used" },
24+
{ label: "Max (gas_used)", value: "max(gas_used) AS max_gas_used" },
25+
// Presets for a user-defined field
26+
{
27+
label: "Count (custom field)",
28+
value: "count(your_field_here) AS count_custom",
29+
},
30+
{ label: "Sum (custom field)", value: "sum(your_field_here) AS sum_custom" },
31+
{
32+
label: "Average (custom field)",
33+
value: "avg(your_field_here) AS avg_custom",
34+
},
35+
{ label: "Min (custom field)", value: "min(your_field_here) AS min_custom" },
36+
{ label: "Max (custom field)", value: "max(your_field_here) AS max_custom" },
37+
];
38+
39+
const GENERAL_TRANSACTIONS_PRESETS: Preset[] = [
40+
{ label: "Transaction Count", value: "count() AS transaction_count" },
41+
{
42+
label: "Total Value Transferred (Wei)",
43+
value: "sum(value) AS total_value_wei",
44+
},
45+
{ label: "Total Gas Used", value: "sum(gas_used) AS total_gas_used" },
46+
{
47+
label: "Average Value Transferred (Wei)",
48+
value: "avg(value) AS average_value_wei",
49+
},
50+
{ label: "Average Gas Used", value: "avg(gas_used) AS average_gas_used" },
51+
{
52+
label: "Max Value Transferred (Wei)",
53+
value: "max(value) AS max_value_wei",
54+
},
55+
{ label: "Max Gas Used", value: "max(gas_used) AS max_gas_used" },
56+
{
57+
label: "Min Value Transferred (Wei)",
58+
value: "min(value) AS min_value_wei",
59+
},
60+
{ label: "Min Gas Used", value: "min(gas_used) AS min_gas_used" },
61+
];
62+
63+
const WALLET_TRANSACTIONS_PRESETS: Preset[] = [
64+
{ label: "Wallet Transaction Count", value: "count() AS wallet_tx_count" },
65+
{
66+
label: "Wallet Total Value (Wei)",
67+
value: "sum(value) AS wallet_total_value_wei",
68+
},
69+
{
70+
label: "Wallet Total Gas Spent",
71+
value: "sum(gas_used) AS wallet_total_gas_spent",
72+
},
73+
{
74+
label: "Wallet Average Value (Wei)",
75+
value: "avg(value) AS wallet_avg_value",
76+
},
77+
{
78+
label: "Wallet Average Gas Spent",
79+
value: "avg(gas_used) AS wallet_avg_gas_spent",
80+
},
81+
{
82+
label: "Wallet Max Value Tx (Wei)",
83+
value: "max(value) AS wallet_max_value_tx",
84+
},
85+
{ label: "Wallet Max Gas Tx", value: "max(gas_used) AS wallet_max_gas_tx" },
86+
{
87+
label: "Wallet Min Value Tx (Wei)",
88+
value: "min(value) AS wallet_min_value_tx",
89+
},
90+
{ label: "Wallet Min Gas Tx", value: "min(gas_used) AS wallet_min_gas_tx" },
91+
];
92+
93+
const EVENTS_PRESETS: Preset[] = [
94+
{ label: "Event Count", value: "count() AS event_count" },
95+
{
96+
label: "Unique Addresses",
97+
value: "countDistinct(address) AS unique_addresses",
98+
},
99+
{ label: "Min Block Number", value: "min(block_number) AS min_block" },
100+
{ label: "Max Block Number", value: "max(block_number) AS max_block" },
101+
];
102+
103+
const BLOCKS_PRESETS: Preset[] = [
104+
{ label: "Block Count", value: "count() AS block_count" },
105+
{ label: "Min Block Number", value: "min(block_number) AS min_block_number" },
106+
{ label: "Max Block Number", value: "max(block_number) AS max_block_number" },
107+
{ label: "Total Gas Used", value: "sum(gas_used) AS total_gas_used" },
108+
{ label: "Average Gas Used", value: "avg(gas_used) AS avg_gas_used" },
109+
{ label: "Max Gas Used", value: "max(gas_used) AS max_gas_used" },
110+
{ label: "Min Gas Used", value: "min(gas_used) AS min_gas_used" },
111+
{
112+
label: "Total Transactions",
113+
value: "sum(transaction_count) AS total_transactions",
114+
},
115+
{
116+
label: "Average Transactions per Block",
117+
value: "avg(transaction_count) AS avg_txs_per_block",
118+
},
119+
{
120+
label: "Max Transactions in a Block",
121+
value: "max(transaction_count) AS max_txs_in_block",
122+
},
123+
{
124+
label: "Min Transactions in a Block",
125+
value: "min(transaction_count) AS min_txs_in_block",
126+
},
127+
];
128+
129+
const ENDPOINT_SPECIFIC_PRESETS: Record<string, Preset[]> = {
130+
"/v1/transactions": GENERAL_TRANSACTIONS_PRESETS,
131+
"/v1/wallets/{wallet_address}/transactions": WALLET_TRANSACTIONS_PRESETS,
132+
"/v1/events": EVENTS_PRESETS,
133+
"/v1/blocks": BLOCKS_PRESETS,
134+
// Add more endpoint paths and their specific presets here
135+
};
136+
137+
function getAggregatePresets(endpointPath: string): Preset[] {
138+
return ENDPOINT_SPECIFIC_PRESETS[endpointPath] || DEFAULT_AGGREGATE_PRESETS;
139+
}
140+
141+
interface AggregateParameterInputProps {
142+
field: ControllerRenderProps<
143+
{
144+
[x: string]: string | number;
145+
},
146+
string
147+
>;
148+
showTip: boolean;
149+
hasError: boolean;
150+
placeholder: string;
151+
endpointPath: string; // New prop
152+
}
153+
154+
export function AggregateParameterInput(props: AggregateParameterInputProps) {
155+
const { field, showTip, hasError, placeholder, endpointPath } = props;
156+
const { value, onChange } = field;
157+
158+
const presets = getAggregatePresets(endpointPath);
159+
160+
return (
161+
<div className="flex flex-col space-y-1">
162+
<Input
163+
{...field}
164+
className={cn(
165+
"h-auto truncate rounded-none border-0 bg-transparent py-5 font-mono text-sm focus-visible:ring-0 focus-visible:ring-offset-0",
166+
showTip && "lg:pr-10",
167+
hasError && "text-destructive-text",
168+
)}
169+
placeholder={placeholder}
170+
/>
171+
<Select
172+
value={presets.find((p) => p.value === value)?.value || ""}
173+
onValueChange={(selectedValue) => {
174+
if (selectedValue) {
175+
onChange({ target: { value: selectedValue } });
176+
}
177+
}}
178+
>
179+
<SelectTrigger
180+
className={cn(
181+
"h-8 border-dashed bg-transparent text-xs focus:ring-0 focus:ring-offset-0",
182+
!presets.find((p) => p.value === value) && "text-muted-foreground",
183+
)}
184+
>
185+
<SelectValue placeholder="Select a preset (optional)" />
186+
</SelectTrigger>
187+
<SelectContent className="font-mono">
188+
{presets.map((preset) => (
189+
<SelectItem key={preset.value} value={preset.value}>
190+
{preset.label}
191+
</SelectItem>
192+
))}
193+
</SelectContent>
194+
</Select>
195+
</div>
196+
);
197+
}

apps/playground-web/src/app/insight/[blueprint_slug]/blueprint-playground.client.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { z } from "zod";
3636
import { MultiNetworkSelector } from "../../../components/blocks/NetworkSelectors";
3737
import type { BlueprintParameter, BlueprintPathMetadata } from "../utils";
38+
import { AggregateParameterInput } from "./aggregate-parameter-input.client";
3839

3940
export function BlueprintPlayground(props: {
4041
metadata: BlueprintPathMetadata;
@@ -426,7 +427,6 @@ function ParameterSection(props: {
426427
supportedChainIds: number[];
427428
className?: string;
428429
}) {
429-
const url = `${props.domain}${props.path}`;
430430
return (
431431
<div className={cn("p-4 py-6", props.className)}>
432432
<h3 className="mb-3 font-medium text-sm"> {props.title} </h3>
@@ -450,12 +450,6 @@ function ParameterSection(props: {
450450

451451
const hasError = !!props.form.formState.errors[param.name];
452452

453-
const placeholder = url.includes(`{${param.name}}`)
454-
? `{${param.name}}`
455-
: url.includes(`:${param.name}`)
456-
? `:${param.name}`
457-
: "Value";
458-
459453
return (
460454
<FormField
461455
key={param.name}
@@ -521,7 +515,8 @@ function ParameterSection(props: {
521515
}}
522516
showTip={showTip}
523517
hasError={hasError}
524-
placeholder={placeholder}
518+
placeholder={param.description || param.name}
519+
endpointPath={props.path}
525520
/>
526521

527522
{showTip && (
@@ -586,6 +581,7 @@ function ParameterInput(props: {
586581
showTip: boolean;
587582
hasError: boolean;
588583
placeholder: string;
584+
endpointPath: string;
589585
}) {
590586
const { param, field, showTip, hasError, placeholder } = props;
591587

@@ -622,6 +618,18 @@ function ParameterInput(props: {
622618
);
623619
}
624620

621+
if (param.name === "aggregate") {
622+
return (
623+
<AggregateParameterInput
624+
field={field}
625+
showTip={showTip}
626+
hasError={hasError}
627+
placeholder={placeholder}
628+
endpointPath={props.endpointPath}
629+
/>
630+
);
631+
}
632+
625633
return (
626634
<Input
627635
{...field}

0 commit comments

Comments
 (0)