Skip to content

Commit

Permalink
[TOOL-2804] Insight Playground: Render Select for Parameters with enu…
Browse files Browse the repository at this point in the history
…m schema + Show Examples (#5805)

## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR enhances the `ToolTipLabel` and `SelectTrigger` components, adding new props for customization. It also introduces a new `ParameterInput` component to streamline input handling for parameters with enums, improving the overall user experience in forms.

### Detailed summary
- Added `contentClassName` prop to `ToolTipLabel` for custom styling.
- Updated `SelectTrigger` to accept `chevronClassName` prop for customization.
- Introduced `ParameterInput` component to handle parameter inputs with enums.
- Enhanced form error handling and display logic in `RequestConfigSection` and `ParameterSection`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
  • Loading branch information
MananTank committed Dec 19, 2024
1 parent d7d9415 commit 58b75d1
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 25 deletions.
8 changes: 5 additions & 3 deletions apps/dashboard/src/@/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ const SelectValue = SelectPrimitive.Value;

const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
chevronClassName?: string;
}
>(({ className, children, chevronClassName, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
Expand All @@ -26,7 +28,7 @@ const SelectTrigger = React.forwardRef<
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
<ChevronDown className={cn("h-4 w-4 opacity-50", chevronClassName)} />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/src/@/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function ToolTipLabel(props: {
rightIcon?: React.ReactNode;
leftIcon?: React.ReactNode;
hoverable?: boolean;
contentClassName?: string;
}) {
if (!props.label) {
return props.children;
Expand All @@ -48,7 +49,10 @@ export function ToolTipLabel(props: {
</TooltipTrigger>
<TooltipContent
sideOffset={10}
className="max-w-[400px] whitespace-normal leading-relaxed"
className={cn(
"max-w-[400px] whitespace-normal leading-relaxed",
props.contentClassName,
)}
>
<div className="flex items-center gap-1.5 p-2 text-sm">
{props.leftIcon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import { Button } from "@/components/ui/button";
import { CodeClient } from "@/components/ui/code/code.client";
import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
Expand All @@ -27,7 +34,11 @@ import {
import Link from "next/link";
import type { OpenAPIV3 } from "openapi-types";
import { useEffect, useMemo, useState } from "react";
import { type UseFormReturn, useForm } from "react-hook-form";
import {
type ControllerRenderProps,
type UseFormReturn,
useForm,
} from "react-hook-form";
import { z } from "zod";
import { useTrack } from "../../../../../../hooks/analytics/useTrack";
import { getVercelEnv } from "../../../../../../lib/vercel-utils";
Expand Down Expand Up @@ -412,14 +423,20 @@ function RequestConfigSection(props: {
supportedChainIds: number[];
}) {
const pathVariables = props.parameters.filter((param) => param.in === "path");

const queryParams = props.parameters.filter((param) => param.in === "query");
const showError =
!props.form.formState.isValid &&
props.form.formState.isDirty &&
props.form.formState.isSubmitted;

return (
<div className="flex grow flex-col overflow-hidden">
<div className="flex min-h-[60px] items-center gap-2 border-b p-4 text-sm">
<ArrowUpRightIcon className="size-5" />
Request
<div className="flex min-h-[60px] items-center justify-between gap-2 border-b p-4 text-sm">
<div className="flex items-center gap-2">
<ArrowUpRightIcon className="size-5" />
Request
</div>
{showError && <Badge variant="destructive">Invalid Request</Badge>}
</div>

<ScrollShadow className="flex-1" scrollableClassName="max-h-full">
Expand Down Expand Up @@ -474,7 +491,25 @@ function ParameterSection(props: {
? param.schema.description
: undefined;

const example =
param.schema && "type" in param.schema
? param.schema.example
: undefined;
const exampleToShow =
typeof example === "string" || typeof example === "number"
? example
: undefined;

const showTip = description !== undefined || example !== undefined;

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

const placeholder = url.includes(`{${param.name}}`)
? `{${param.name}}`
: url.includes(`:${param.name}`)
? `:${param.name}`
: "Value";

return (
<FormField
key={param.name}
Expand Down Expand Up @@ -530,23 +565,39 @@ function ParameterSection(props: {
/>
) : (
<>
<Input
{...field}
className={cn(
"h-auto truncate rounded-none border-0 bg-transparent py-3 font-mono text-sm focus-visible:ring-0 focus-visible:ring-offset-0",
description && "lg:pr-10",
hasError && "text-destructive-text",
)}
placeholder={
url.includes(`{${param.name}}`)
? `{${param.name}}`
: url.includes(`:${param.name}`)
? `:${param.name}`
: "Value"
}
<ParameterInput
param={param}
field={field}
showTip={showTip}
hasError={hasError}
placeholder={placeholder}
/>
{description && (
<ToolTipLabel label={description}>

{showTip && (
<ToolTipLabel
hoverable
contentClassName="max-w-[100vw] break-all"
label={
<div className="flex flex-col gap-2">
{description && (
<p className="text-foreground">
{description}
</p>
)}

{exampleToShow !== undefined && (
<div>
<p className="mb-1 text-muted-foreground">
Example:{" "}
<span className="font-mono">
{exampleToShow}
</span>
</p>
</div>
)}
</div>
}
>
<Button
asChild
variant="ghost"
Expand All @@ -573,6 +624,66 @@ function ParameterSection(props: {
);
}

function ParameterInput(props: {
param: OpenAPIV3.ParameterObject;
field: ControllerRenderProps<
{
[x: string]: string | number;
},
string
>;
showTip: boolean;
hasError: boolean;
placeholder: string;
}) {
const { param, field, showTip, hasError, placeholder } = props;

if (param.schema && "type" in param.schema && param.schema.enum) {
const { value, onChange, ...restField } = field;
return (
<Select
{...restField}
value={value.toString()}
onValueChange={(v) => {
onChange({ target: { value: v } });
}}
>
<SelectTrigger
className={cn(
"border-none bg-transparent pr-10 font-mono focus:ring-0 focus:ring-offset-0",
value === "" && "text-muted-foreground",
)}
chevronClassName="hidden"
>
<SelectValue placeholder="Select" />
</SelectTrigger>

<SelectContent className="font-mono">
{param.schema.enum.map((val) => {
return (
<SelectItem value={val} key={val}>
{val}
</SelectItem>
);
})}
</SelectContent>
</Select>
);
}

return (
<Input
{...field}
className={cn(
"h-auto truncate rounded-none border-0 bg-transparent py-3 font-mono text-sm focus-visible:ring-0 focus-visible:ring-offset-0",
showTip && "lg:pr-10",
hasError && "text-destructive-text",
)}
placeholder={placeholder}
/>
);
}

function formatMilliseconds(ms: number) {
if (ms < 1000) {
return `${Math.round(ms)}ms`;
Expand Down Expand Up @@ -677,6 +788,21 @@ function openAPIV3ParamToZodFormSchema(param: BlueprintParameter) {
return;
}

// if enum values
const enumValues = param.schema.enum;
if (enumValues) {
const enumSchema = z.enum(
// @ts-expect-error - Its correct
enumValues,
);

if (param.required) {
return enumSchema;
}

return enumSchema.or(z.literal(""));
}

switch (param.schema.type) {
case "integer": {
const intSchema = z.coerce
Expand Down

0 comments on commit 58b75d1

Please sign in to comment.