Skip to content

Commit

Permalink
Merge pull request #20 from iway1/bugfix/headers
Browse files Browse the repository at this point in the history
headers bug fix + fix inputs not rendering
  • Loading branch information
iway1 authored Dec 22, 2022
2 parents 8d700eb + f3e53e3 commit 3cbcd3b
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 87 deletions.
11 changes: 5 additions & 6 deletions packages/test-app/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const t = initTRPC

async function createContext(opts: trpcExpress.CreateExpressContextOptions) {
const authHeader = opts.req.headers["authorization"];
console.log("Request headers: ");
console.log(authHeader);
return {
authorized: !!authHeader,
};
Expand Down Expand Up @@ -265,10 +267,7 @@ export const testRouter = t.router({
.query(({ input }) => {
return { greeting: `Hello ${input.name}!` };
}),
sayHello2: t.procedure
.meta({ /* 👉 */ description: "This shows in the panel." })
.input(z.object({ name: z.string() }))
.query(({ input }) => {
return { greeting: `Hello ${input.name}!` };
}),
nonObjectInput: t.procedure.input(z.string()).query(({ input }) => {
return `Your input was ${input}`;
}),
});
14 changes: 7 additions & 7 deletions packages/trpc-panel/src/react-app/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createTRPCReact, httpBatchLink } from "@trpc/react-query";
import {
HeadersContext,
HeadersContextProvider,
useHeaders,
} from "@src/react-app/components/contexts/HeadersContext";
import { HeadersPopup } from "@src/react-app/components/HeadersPopup";
Expand All @@ -27,11 +28,10 @@ export function RootComponent({
options: RenderOptions;
trpc: ReturnType<typeof createTRPCReact>;
}) {
const headers = useHeaders();
return (
<SiteNavigationContextProvider>
<AllPathsContextProvider rootRouter={rootRouter}>
<HeadersContext.Provider value={headers}>
<HeadersContextProvider>
<SiteNavigationContextProvider>
<AllPathsContextProvider rootRouter={rootRouter}>
<ClientProviders trpc={trpc} options={options}>
<HotKeysContextProvider>
<SearchOverlay>
Expand All @@ -41,9 +41,9 @@ export function RootComponent({
</SearchOverlay>
</HotKeysContextProvider>
</ClientProviders>
</HeadersContext.Provider>
</AllPathsContextProvider>
</SiteNavigationContextProvider>
</AllPathsContextProvider>
</SiteNavigationContextProvider>
</HeadersContextProvider>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function HeadersPopup() {
</div>
<div className="px-4 py-2 flex flex-col space-y-2">
{headers.map(([headerKey, headerValue], i) => (
<div className="flex flex-col">
<div className="flex flex-col" key={i + ""}>
<div className="flex flex-row items-start">
<BaseTextField
className="flex-1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { createContext, useContext, useRef, useState } from "react";
import React, {
createContext,
ReactNode,
useContext,
useRef,
useState,
} from "react";
type Headers = { [key: string]: string };

type HeadersContextType = {
Expand All @@ -16,7 +22,7 @@ const headersLocalStorageKey = "headers";

const storedHeaders = localStorage.getItem(headersLocalStorageKey);

export function useHeaders(): HeadersContextType {
export function HeadersContextProvider({ children }: { children: ReactNode }) {
const [headersPopupShown, setHeadersPopupShown] = useState(false);
const [saveHeadersToLocalStorage, setSaveHeadersToLocalStorage] = useState(
!!storedHeaders
Expand All @@ -38,17 +44,29 @@ export function useHeaders(): HeadersContextType {
};
}

return {
setHeaders,
getHeaders,
headersPopupShown,
setHeadersPopupShown,
saveHeadersToLocalStorage,
setSaveHeadersToLocalStorage: (val) => {
if (!val) localStorage.removeItem(headersLocalStorageKey);
setSaveHeadersToLocalStorage(val);
},
};
return (
<HeadersContext.Provider
value={{
setHeaders,
getHeaders,
headersPopupShown,
setHeadersPopupShown,
saveHeadersToLocalStorage,
setSaveHeadersToLocalStorage: (val) => {
if (!val) localStorage.removeItem(headersLocalStorageKey);
setSaveHeadersToLocalStorage(val);
},
}}
>
{children}
</HeadersContext.Provider>
);
}

export function useHeaders(): HeadersContextType {
const ctx = useContext(HeadersContext);
if (!ctx) throw new Error("No headers context.");
return ctx;
}

export function useHeadersContext() {
Expand Down
46 changes: 39 additions & 7 deletions packages/trpc-panel/src/react-app/components/form/Field.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ROOT_VALS_PROPERTY_NAME } from "@src/react-app/components/form/ProcedureForm";
import React from "react";
import { Control } from "react-hook-form";
import { ParsedInputNode } from "../../../parse/parseNodeTypes";
Expand All @@ -15,32 +16,63 @@ export function Field({
control,
}: {
inputNode: ParsedInputNode;
control: Control;
control: Control<any>;
}) {
const path = inputNode.path.join(".");
const label = inputNode.path.join(".");
const path = `${ROOT_VALS_PROPERTY_NAME}.${label}`;
switch (inputNode.type) {
case "string":
return <TextField name={path} control={control} node={inputNode} />;
return (
<TextField
name={path}
control={control}
node={inputNode}
label={label}
/>
);
case "number":
return <NumberField name={path} control={control} node={inputNode} />;
return (
<NumberField
name={path}
label={label}
control={control}
node={inputNode}
/>
);
case "object":
return <ObjectField name={path} control={control} node={inputNode} />;
return <ObjectField label={label} control={control} node={inputNode} />;
case "boolean":
return <BooleanField name={path} control={control} node={inputNode} />;
return (
<BooleanField
name={path}
label={label}
control={control}
node={inputNode}
/>
);
case "enum":
return (
<EnumField
name={path}
label={label}
control={control}
options={inputNode.enumValues}
/>
);
case "array":
return <ArrayField name={path} control={control} node={inputNode} />;
return (
<ArrayField
name={path}
label={label}
control={control}
node={inputNode}
/>
);
case "discriminated-union":
return (
<DiscriminatedUnionField
name={path}
label={label}
control={control}
node={inputNode}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ObjectField } from "@src/react-app/components/form/fields/ObjectField";
import { fullFormats } from "ajv-formats/dist/formats";
import type { ParsedInputNode } from "@src/parse/parseNodeTypes";
import { DocumentationSection } from "@src/react-app/components/form/ProcedureForm/DescriptionSection";
import { Field } from "@src/react-app/components/form/Field";

const TRPCErrorSchema = z.object({
shape: z.object({
Expand All @@ -36,6 +37,8 @@ function isTrpcError(error: unknown): error is TRPCErrorType {
return parse.success;
}

export const ROOT_VALS_PROPERTY_NAME = "vals";

export function ProcedureForm({
procedure,
name,
Expand Down Expand Up @@ -91,18 +94,24 @@ export function ProcedureForm({
reset: resetForm,
handleSubmit,
} = useForm({
resolver: ajvResolver(procedure.inputSchema as any, {
resolver: ajvResolver(wrapJsonSchema(procedure.inputSchema as any), {
formats: fullFormats,
}),
defaultValues: defaultFormValuesForNode(procedure.node),
defaultValues: {
[ROOT_VALS_PROPERTY_NAME]: defaultFormValuesForNode(procedure.node),
},
});

function onSubmit(data: any) {
function onSubmit(data: { [ROOT_VALS_PROPERTY_NAME]: any }) {
if (procedure.procedureType === "query") {
setQueryInput({ ...data });
invalidateQuery(data);
const newData = { ...data };
setQueryInput(newData[ROOT_VALS_PROPERTY_NAME]);
invalidateQuery(data.vals);
} else {
mutation.mutateAsync(data).then(setMutationResponse).catch();
mutation
.mutateAsync(data[ROOT_VALS_PROPERTY_NAME])
.then(setMutationResponse)
.catch();
}
}

Expand All @@ -127,6 +136,9 @@ export function ProcedureForm({
procedure.procedureType === "query" ? query.data : mutationResponse;
const error =
procedure.procedureType == "query" ? query.error : mutation.error;

const fieldName = procedure.node.path.join(".");

return (
<CollapsableSection
titleElement={
Expand All @@ -145,31 +157,34 @@ export function ProcedureForm({
>
<div className="flex flex-col">
<DocumentationSection extraData={procedure.extraData} />
{procedure.node.type == "object" && (
<FormSection
title="Input"
topRightElement={<XButton control={control} reset={reset} />}
>
{Object.keys(procedure.node.children).length > 0 && (

<FormSection
title="Input"
topRightElement={<XButton control={control} reset={reset} />}
>
{procedure.node.type === "object" ? (
Object.keys(procedure.node.children).length > 0 && (
<ObjectField
node={
procedure.node as ParsedInputNode & {
type: "object";
}
}
label={fieldName}
control={control}
name={procedure.node.path.join(".")}
topLevel
/>
)}

<ProcedureFormButton
text={`Execute ${name}`}
colorScheme={"neutral"}
loading={query.fetchStatus === "fetching" || mutation.isLoading}
/>
</FormSection>
)}
)
) : (
<Field inputNode={procedure.node} control={control} />
)}

<ProcedureFormButton
text={`Execute ${name}`}
colorScheme={"neutral"}
loading={query.fetchStatus === "fetching" || mutation.isLoading}
/>
</FormSection>
</div>
</form>
<div className="flex flex-col space-y-4">
Expand Down Expand Up @@ -211,3 +226,17 @@ function XButton({
</div>
);
}

function wrapJsonSchema(jsonSchema: any) {
delete jsonSchema["$schema"];

return {
type: "object",
properties: {
[ROOT_VALS_PROPERTY_NAME]: jsonSchema,
},
required: [ROOT_VALS_PROPERTY_NAME],
additionalProperties: false,
$schema: "http://json-schema.org/draft-07/schema#",
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ import { InputGroupContainer } from "@src/react-app/components/InputGroupContain
import DataArray from "@mui/icons-material/DataArray";
import { AddItemButton } from "@src/react-app/components/AddItemButton";
import { FieldError } from "@src/react-app/components/form/fields/FieldError";
import { ROOT_VALS_PROPERTY_NAME } from "@src/react-app/components/form/ProcedureForm";

var currentKeyCount = 0;

export function ArrayField({
name,
label,
control,
node,
}: {
name: string;
label: string;
control: Control<any>;
node: ParsedInputNode & { type: "array" };
}) {
Expand All @@ -35,7 +38,9 @@ export function ArrayField({

function getValueFromWatch() {
var r = watch;
for (var p of node.path) {
for (var p of [ROOT_VALS_PROPERTY_NAME].concat(
node.path.map((e) => e + "")
)) {
r = r[p];
}
return r;
Expand All @@ -59,10 +64,10 @@ export function ArrayField({
return (
<InputGroupContainer
iconElement={<DataArray className="mr-1" />}
title={name}
title={label}
>
{field.value.map((_: ParsedInputNode, i: number) => (
<span className="flex flex-row items-start">
<span key={i + ""} className="flex flex-row items-start">
<span className="flex flex-1 flex-col">
<Field
key={textFieldKeys[i]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { BaseCheckboxField } from "@src/react-app/components/form/fields/base/Ba

export function BooleanField({
name,
label,
control,
node,
}: {
name: string;
label: string;
control: Control<any>;
node: ParsedInputNode;
}) {
Expand All @@ -17,7 +19,7 @@ export function BooleanField({
return (
<BaseCheckboxField
fieldId={path}
label={name}
label={label}
onChange={field.onChange}
value={field.value}
errorMessage={fieldState.error?.message}
Expand Down
Loading

0 comments on commit 3cbcd3b

Please sign in to comment.