Skip to content

Commit 75d8e69

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

File tree

2 files changed

+222
-1
lines changed

2 files changed

+222
-1
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
// If supported, use count_distinct, otherwise remove or replace
96+
{
97+
label: "Unique Event Names",
98+
value: "count_distinct(event_name) AS unique_event_names",
99+
},
100+
{
101+
label: "Unique Addresses",
102+
value: "count_distinct(address) AS unique_addresses",
103+
},
104+
{ label: "Min Block Number", value: "min(block_number) AS min_block" },
105+
{ label: "Max Block Number", value: "max(block_number) AS max_block" },
106+
{ label: "Total Value", value: "sum(value) AS total_value" },
107+
{ label: "Average Value", value: "avg(value) AS avg_value" },
108+
{ label: "Max Value", value: "max(value) AS max_value" },
109+
{ label: "Min Value", value: "min(value) AS min_value" },
110+
];
111+
112+
const BLOCKS_PRESETS: Preset[] = [
113+
{ label: "Block Count", value: "count() AS block_count" },
114+
{ label: "Min Block Number", value: "min(block_number) AS min_block_number" },
115+
{ label: "Max Block Number", value: "max(block_number) AS max_block_number" },
116+
{ label: "Total Gas Used", value: "sum(gas_used) AS total_gas_used" },
117+
{ label: "Average Gas Used", value: "avg(gas_used) AS avg_gas_used" },
118+
{ label: "Max Gas Used", value: "max(gas_used) AS max_gas_used" },
119+
{ label: "Min Gas Used", value: "min(gas_used) AS min_gas_used" },
120+
{
121+
label: "Total Transactions",
122+
value: "sum(transaction_count) AS total_transactions",
123+
},
124+
{
125+
label: "Average Transactions per Block",
126+
value: "avg(transaction_count) AS avg_txs_per_block",
127+
},
128+
{
129+
label: "Max Transactions in a Block",
130+
value: "max(transaction_count) AS max_txs_in_block",
131+
},
132+
{
133+
label: "Min Transactions in a Block",
134+
value: "min(transaction_count) AS min_txs_in_block",
135+
},
136+
];
137+
138+
const ENDPOINT_SPECIFIC_PRESETS: Record<string, Preset[]> = {
139+
"/v1/transactions": GENERAL_TRANSACTIONS_PRESETS,
140+
"/v1/wallets/{wallet_address}/transactions": WALLET_TRANSACTIONS_PRESETS,
141+
"/v1/events": EVENTS_PRESETS,
142+
"/v1/blocks": BLOCKS_PRESETS,
143+
// Add more endpoint paths and their specific presets here
144+
};
145+
146+
function getAggregatePresets(endpointPath: string): Preset[] {
147+
return ENDPOINT_SPECIFIC_PRESETS[endpointPath] || DEFAULT_AGGREGATE_PRESETS;
148+
}
149+
150+
interface AggregateParameterInputProps {
151+
field: ControllerRenderProps<
152+
{
153+
[x: string]: string | number;
154+
},
155+
string
156+
>;
157+
showTip: boolean;
158+
hasError: boolean;
159+
placeholder: string;
160+
endpointPath: string; // New prop
161+
}
162+
163+
export function AggregateParameterInput(props: AggregateParameterInputProps) {
164+
const { field, showTip, hasError, placeholder, endpointPath } = props;
165+
const { value, onChange } = field;
166+
167+
const presets = getAggregatePresets(endpointPath);
168+
169+
return (
170+
<div className="flex flex-col space-y-1">
171+
<Input
172+
{...field}
173+
className={cn(
174+
"h-auto truncate rounded-none border-0 bg-transparent py-5 font-mono text-sm focus-visible:ring-0 focus-visible:ring-offset-0",
175+
showTip && "lg:pr-10",
176+
hasError && "text-destructive-text",
177+
)}
178+
placeholder={placeholder}
179+
/>
180+
<Select
181+
value={presets.find((p) => p.value === value)?.value || ""}
182+
onValueChange={(selectedValue) => {
183+
if (selectedValue) {
184+
onChange({ target: { value: selectedValue } });
185+
}
186+
}}
187+
>
188+
<SelectTrigger
189+
className={cn(
190+
"h-8 border-dashed bg-transparent text-xs focus:ring-0 focus:ring-offset-0",
191+
!presets.find((p) => p.value === value) && "text-muted-foreground",
192+
)}
193+
>
194+
<SelectValue placeholder="Select a preset (optional)" />
195+
</SelectTrigger>
196+
<SelectContent className="font-mono">
197+
{presets.map((preset) => (
198+
<SelectItem key={preset.value} value={preset.value}>
199+
{preset.label}
200+
</SelectItem>
201+
))}
202+
</SelectContent>
203+
</Select>
204+
</div>
205+
);
206+
}

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

Lines changed: 16 additions & 1 deletion
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;
@@ -521,7 +522,8 @@ function ParameterSection(props: {
521522
}}
522523
showTip={showTip}
523524
hasError={hasError}
524-
placeholder={placeholder}
525+
placeholder={param.description || param.name}
526+
endpointPath={props.path}
525527
/>
526528

527529
{showTip && (
@@ -586,6 +588,7 @@ function ParameterInput(props: {
586588
showTip: boolean;
587589
hasError: boolean;
588590
placeholder: string;
591+
endpointPath: string;
589592
}) {
590593
const { param, field, showTip, hasError, placeholder } = props;
591594

@@ -622,6 +625,18 @@ function ParameterInput(props: {
622625
);
623626
}
624627

628+
if (param.name === "aggregate") {
629+
return (
630+
<AggregateParameterInput
631+
field={field}
632+
showTip={showTip}
633+
hasError={hasError}
634+
placeholder={placeholder}
635+
endpointPath={props.endpointPath}
636+
/>
637+
);
638+
}
639+
625640
return (
626641
<Input
627642
{...field}

0 commit comments

Comments
 (0)