Skip to content

Commit

Permalink
[TOOL-2801] Insight Playground: Use OpenAPIV3 types (#5804)
Browse files Browse the repository at this point in the history
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the handling of OpenAPI specifications within the codebase, improving type definitions, and refining the UI components to accommodate optional fields and better manage parameters.

### Detailed summary
- Added `openapi-types` dependency in `package.json`.
- Updated `blueprintSpec` to use optional chaining for safer access.
- Modified `BlueprintCard` to handle optional `description` and `title`.
- Changed `BlueprintParameter` type to use `OpenAPIV3.ParameterObject`.
- Enhanced `BlueprintPlaygroundUI` to filter and manage parameters more effectively.
- Updated parameter handling in forms to leverage new schema definitions.
- Refined UI components to conditionally render descriptions and titles.

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

<!-- end pr-codex -->
MananTank committed Dec 19, 2024

Verified

This commit was signed with the committer’s verified signature.
MananTank Manan Tank
1 parent b2a52d9 commit d7d9415
Showing 7 changed files with 613 additions and 301 deletions.
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
@@ -73,6 +73,7 @@
"next-seo": "^6.5.0",
"next-themes": "^0.4.4",
"nextjs-toploader": "^1.6.12",
"openapi-types": "^12.1.3",
"papaparse": "^5.4.1",
"pluralize": "^8.0.0",
"posthog-js": "1.67.1",
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import {
PlayIcon,
} from "lucide-react";
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 { z } from "zod";
@@ -118,8 +119,10 @@ function modifyParametersForPlayground(_parameters: BlueprintParameter[]) {
name: "chainId",
in: "path",
required: true,
description: "Chain ID",
type: "integer",
schema: {
type: "integer",
description: "Chain ID of the blockchain",
},
});
}

@@ -156,7 +159,10 @@ export function BlueprintPlaygroundUI(props: {
}) {
const trackEvent = useTrack();
const parameters = useMemo(() => {
return modifyParametersForPlayground(props.metadata.parameters);
const filteredParams = props.metadata.parameters?.filter(
isOpenAPIV3ParameterObject,
);
return modifyParametersForPlayground(filteredParams || []);
}, [props.metadata.parameters]);

const formSchema = useMemo(() => {
@@ -166,7 +172,11 @@ export function BlueprintPlaygroundUI(props: {
const defaultValues = useMemo(() => {
const values: Record<string, string | number> = {};
for (const param of parameters) {
values[param.name] = param.default || "";
if (param.schema && "type" in param.schema && param.schema.default) {
values[param.name] = param.schema.default;
} else {
values[param.name] = "";
}
}
return values;
}, [parameters]);
@@ -200,7 +210,7 @@ export function BlueprintPlaygroundUI(props: {
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex grow flex-col">
<BlueprintMetaHeader
title={props.metadata.summary}
title={props.metadata.summary || "Blueprint Playground"}
description={props.metadata.description}
backLink={props.backLink}
/>
@@ -263,7 +273,7 @@ export function BlueprintPlaygroundUI(props: {

function BlueprintMetaHeader(props: {
title: string;
description: string;
description: string | undefined;
backLink: string;
}) {
return (
@@ -285,9 +295,11 @@ function BlueprintMetaHeader(props: {
<h1 className="font-semibold text-2xl tracking-tight lg:text-3xl">
{props.title}
</h1>
<p className="mt-1 text-muted-foreground text-sm">
{props.description}
</p>
{props.description && (
<p className="mt-1 text-muted-foreground text-sm">
{props.description}
</p>
)}
</div>
</div>
</div>
@@ -457,6 +469,11 @@ function ParameterSection(props: {
<h3 className="mb-3 font-medium text-sm"> {props.title} </h3>
<div className="overflow-hidden rounded-lg border">
{props.parameters.map((param, i) => {
const description =
param.schema && "type" in param.schema
? param.schema.description
: undefined;

const hasError = !!props.form.formState.errors[param.name];
return (
<FormField
@@ -517,7 +534,7 @@ function ParameterSection(props: {
{...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",
param.description && "lg:pr-10",
description && "lg:pr-10",
hasError && "text-destructive-text",
)}
placeholder={
@@ -528,8 +545,8 @@ function ParameterSection(props: {
: "Value"
}
/>
{param.description && (
<ToolTipLabel label={param.description}>
{description && (
<ToolTipLabel label={description}>
<Button
asChild
variant="ghost"
@@ -651,32 +668,63 @@ function ResponseSection(props: {
);
}

function createParametersFormSchema(parameters: BlueprintParameter[]) {
const shape: z.ZodRawShape = {};
for (const param of parameters) {
// integer
if (param.type === "integer") {
function openAPIV3ParamToZodFormSchema(param: BlueprintParameter) {
if (!param.schema) {
return;
}

if (!("type" in param.schema)) {
return;
}

switch (param.schema.type) {
case "integer": {
const intSchema = z.coerce
.number({
message: "Must be an integer",
})
.int({
message: "Must be an integer",
});
shape[param.name] = param.required
return param.required
? intSchema.min(1, {
message: "Required",
})
: intSchema.optional();
}

// default: string
else {
shape[param.name] = param.required
? z.string().min(1, {
case "number": {
const numberSchema = z.coerce.number();
return param.required
? numberSchema.min(1, {
message: "Required",
})
: z.string().optional();
: numberSchema.optional();
}

case "boolean": {
const booleanSchema = z.coerce.boolean();
return param.required ? booleanSchema : booleanSchema.optional();
}

// everything else - just accept it as a string;
default: {
const stringSchema = z.string();
return param.required
? stringSchema.min(1, {
message: "Required",
})
: stringSchema.optional();
}
}
}

function createParametersFormSchema(parameters: BlueprintParameter[]) {
const shape: z.ZodRawShape = {};
for (const param of parameters) {
const paramSchema = openAPIV3ParamToZodFormSchema(param);
if (paramSchema) {
shape[param.name] = paramSchema;
}
}

@@ -747,3 +795,9 @@ function ElapsedTimeCounter() {
</span>
);
}

function isOpenAPIV3ParameterObject(
x: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject,
): x is OpenAPIV3.ParameterObject {
return !("$ref" in x);
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -65,8 +65,9 @@ export default async function Page(props: {
const isInsightEnabled = !!apiKey.services?.find((s) => s.name === "insight");

const supportedChainIds =
blueprintSpec.openapiJson.servers[0]?.variables.chainId.enum.map(Number) ||
[];
blueprintSpec.openapiJson.servers?.[0]?.variables?.chainId?.enum?.map(
Number,
) || [];

return (
<BlueprintPlayground
Original file line number Diff line number Diff line change
@@ -76,8 +76,8 @@ async function BlueprintsSection(params: {
}
return (
<BlueprintCard
description={pathObj.get.description}
title={pathObj.get.summary}
description={pathObj.get?.description}
title={pathObj.get?.summary || pathName}
href={`${params.layoutPath}/${blueprint.id}?path=${pathName}`}
key={pathName}
/>
@@ -95,7 +95,7 @@ async function BlueprintsSection(params: {
function BlueprintCard(props: {
href: string;
title: string;
description: string;
description: string | undefined;
}) {
return (
<div className="relative flex items-center gap-3 rounded-lg border border-border bg-muted/50 px-4 py-5 hover:bg-muted/70">
@@ -113,9 +113,11 @@ function BlueprintCard(props: {
<h2 className="font-medium text-base">{props.title}</h2>
</Link>

<p className="line-clamp-1 text-muted-foreground text-sm">
{props.description}
</p>
{props.description && (
<p className="line-clamp-1 text-muted-foreground text-sm">
{props.description}
</p>
)}
</div>
</div>
);
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "server-only";

import type { OpenAPIV3 } from "openapi-types";
import { getVercelEnv } from "../../../../../lib/vercel-utils";

type BlueprintListItem = {
@@ -34,44 +35,14 @@ async function fetchBlueprintList(props: {
return json.data;
}

export type BlueprintParameter = {
type: string;
description: string;
name: string;
in: "path" | "query";
required?: boolean;
default?: string | number;
};
export type BlueprintParameter = OpenAPIV3.ParameterObject;
export type BlueprintPathMetadata = OpenAPIV3.PathItemObject;

// NOTE: this is not the full object type, irrelevant fields are omitted
type BlueprintSpec = {
id: string;
name: string;
description: string;
openapiJson: {
servers: Array<{
url: string;
variables: {
chainId: {
// Note: This list is current empty on dev, but works on prod
// so we show all chains in playground if this list is empty, and only show chains in this list if it's not empty
enum: string[];
};
};
}>;
paths: Record<
string,
{
get: BlueprintPathMetadata;
}
>;
};
};

export type BlueprintPathMetadata = {
description: string;
summary: string;
parameters: BlueprintParameter[];
openapiJson: OpenAPIV3.Document;
};

export async function fetchBlueprintSpec(params: {
310 changes: 119 additions & 191 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

0 comments on commit d7d9415

Please sign in to comment.