From dc14fdd98d3aa86013cdca9595f93de5a9c486ce Mon Sep 17 00:00:00 2001 From: polamoros Date: Mon, 22 Apr 2024 11:14:45 +0200 Subject: [PATCH 1/6] wip --- .../console/ui/src/ui/api-interaction.tsx | 12 ++++---- .../ui/src/ui/custom-resource-http-client.tsx | 30 +++++++++++++------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/apps/wing-console/console/ui/src/ui/api-interaction.tsx b/apps/wing-console/console/ui/src/ui/api-interaction.tsx index d38c90fd0d1..515c98d2189 100644 --- a/apps/wing-console/console/ui/src/ui/api-interaction.tsx +++ b/apps/wing-console/console/ui/src/ui/api-interaction.tsx @@ -55,15 +55,15 @@ export const ApiInteraction = memo( const [routes, setRoutes] = useState([]); const [currentHeaderKey, setCurrentHeaderKey] = usePersistentState(""); - const [currentHeaderValues, setCurrentHeaderValues] = useState( - [], - ); + const [currentHeaderValues, setCurrentHeaderValues] = usePersistentState< + string[] + >([]); const [currentRoute, setCurrentRoute] = usePersistentState(""); const [currentMethod, setCurrentMethod] = usePersistentState("GET"); const bodyId = useId(); - const [bodyPlaceholder, setBodyPlaceholder] = useState< + const [bodyPlaceholder, setBodyPlaceholder] = usePersistentState< string | undefined >(); const [body, setBody] = usePersistentState(""); @@ -215,6 +215,7 @@ export const ApiInteraction = memo( openApiSpec, setHeaders, setBody, + setBodyPlaceholder, setQueryParameters, setPathVariables, queryParameters, @@ -300,6 +301,7 @@ export const ApiInteraction = memo( setHeaders, routes, setBody, + setBodyPlaceholder, setCurrentRoute, currentMethod, setPathVariables, @@ -340,7 +342,7 @@ export const ApiInteraction = memo( return; } setCurrentHeaderValues(getHeaderValues(currentHeaderKey)); - }, [currentHeaderKey]); + }, [currentHeaderKey, setCurrentHeaderValues]); // Sync the query parameters with the current route. useEffect(() => { diff --git a/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx b/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx index 9b0119523da..95955c4f673 100644 --- a/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx +++ b/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx @@ -1,9 +1,11 @@ import { Attribute } from "@wingconsole/design-system"; -import { useContext, useState } from "react"; +import { createPersistentState } from "@wingconsole/use-persistent-state"; +import { useContext } from "react"; import { AppContext } from "../AppContext.js"; import { trpc } from "../services/trpc.js"; import { useApi } from "../services/use-api.js"; +import type { ApiResponse } from "../shared/api.js"; import { ApiInteraction } from "./api-interaction.js"; @@ -19,6 +21,9 @@ export const CustomResourceHttpClientItem = ({ getApiSpecHandler, }: CustomResourceHttpClientItemProps) => { const { appMode } = useContext(AppContext); + const { usePersistentState } = createPersistentState(getUrlHandler); + + const [openApiSpec, setOpenApiSpec] = usePersistentState(); const urlData = trpc["httpClient.getUrl"].useQuery( { @@ -27,17 +32,24 @@ export const CustomResourceHttpClientItem = ({ { enabled: !!getUrlHandler }, ); - const openApiSpecData = trpc["httpClient.getOpenApiSpec"].useQuery( + trpc["httpClient.getOpenApiSpec"].useQuery( { resourcePath: getApiSpecHandler, }, - { enabled: !!getApiSpecHandler }, + { + enabled: !!getApiSpecHandler, + onSuccess: (data) => setOpenApiSpec(data.openApiSpec), + }, ); - const [response, setResponse] = useState(); + const [apiResponse, setApiResponse] = usePersistentState(); + const { callFetch, isLoading } = useApi({ - onFetchDataUpdate: (data) => { - setResponse(data); + onFetchDataUpdate: (data: ApiResponse) => { + if (!data) { + return; + } + setApiResponse(data); }, }); @@ -46,15 +58,15 @@ export const CustomResourceHttpClientItem = ({
- {urlData.data?.url && openApiSpecData.data?.openApiSpec && ( + {urlData.data?.url && openApiSpec && ( )} From ad8b023cdc5a2cac432b6c44eaf40355d2849e28 Mon Sep 17 00:00:00 2001 From: polamoros Date: Mon, 22 Apr 2024 11:53:42 +0200 Subject: [PATCH 2/6] wip --- .../ui/src/features/api-interaction-view.tsx | 13 ++++++++++--- .../ui/src/ui/custom-resource-http-client.tsx | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/wing-console/console/ui/src/features/api-interaction-view.tsx b/apps/wing-console/console/ui/src/features/api-interaction-view.tsx index 0bac5ab408c..4c3a41b54d2 100644 --- a/apps/wing-console/console/ui/src/features/api-interaction-view.tsx +++ b/apps/wing-console/console/ui/src/features/api-interaction-view.tsx @@ -1,4 +1,5 @@ import type { OpenApiSpec } from "@wingconsole/server/src/wingsdk"; +import { createPersistentState } from "@wingconsole/use-persistent-state"; import { memo, useCallback, useContext, useState } from "react"; import { AppContext } from "../AppContext.js"; @@ -13,11 +14,17 @@ export interface ApiViewProps { export const ApiInteractionView = memo(({ resourcePath }: ApiViewProps) => { const { appMode } = useContext(AppContext); + const { usePersistentState } = createPersistentState(resourcePath); - const [apiResponse, setApiResponse] = useState(); + const [apiResponse, setApiResponse] = usePersistentState(); const onFetchDataUpdate = useCallback( - (data: ApiResponse) => setApiResponse(data), - [], + (data: ApiResponse) => { + if (!data) { + return; + } + setApiResponse(data); + }, + [setApiResponse], ); const schema = trpc["api.schema"].useQuery({ resourcePath }); diff --git a/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx b/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx index 95955c4f673..564f3244296 100644 --- a/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx +++ b/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx @@ -21,7 +21,7 @@ export const CustomResourceHttpClientItem = ({ getApiSpecHandler, }: CustomResourceHttpClientItemProps) => { const { appMode } = useContext(AppContext); - const { usePersistentState } = createPersistentState(getUrlHandler); + const { usePersistentState } = createPersistentState(getApiSpecHandler); const [openApiSpec, setOpenApiSpec] = usePersistentState(); @@ -67,6 +67,7 @@ export const CustomResourceHttpClientItem = ({ callFetch={callFetch} isLoading={isLoading} apiResponse={apiResponse} + setApiResponse={setApiResponse} /> )} From 05977c6d6f0eed5f2237a37c3ba977fb9ebfb09c Mon Sep 17 00:00:00 2001 From: polamoros Date: Mon, 22 Apr 2024 12:00:06 +0200 Subject: [PATCH 3/6] wip --- .../console/ui/src/features/api-interaction-view.tsx | 5 ++++- apps/wing-console/console/ui/src/ui/api-interaction.tsx | 4 ++++ .../console/ui/src/ui/custom-resource-http-client.tsx | 8 ++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/wing-console/console/ui/src/features/api-interaction-view.tsx b/apps/wing-console/console/ui/src/features/api-interaction-view.tsx index 4c3a41b54d2..43a625335f0 100644 --- a/apps/wing-console/console/ui/src/features/api-interaction-view.tsx +++ b/apps/wing-console/console/ui/src/features/api-interaction-view.tsx @@ -16,7 +16,9 @@ export const ApiInteractionView = memo(({ resourcePath }: ApiViewProps) => { const { appMode } = useContext(AppContext); const { usePersistentState } = createPersistentState(resourcePath); - const [apiResponse, setApiResponse] = usePersistentState(); + const [apiResponse, setApiResponse] = usePersistentState< + ApiResponse | undefined + >(); const onFetchDataUpdate = useCallback( (data: ApiResponse) => { if (!data) { @@ -42,6 +44,7 @@ export const ApiInteractionView = memo(({ resourcePath }: ApiViewProps) => { callFetch={callFetch} isLoading={isLoading} apiResponse={apiResponse} + resetApiResponse={() => setApiResponse(undefined)} /> ); }); diff --git a/apps/wing-console/console/ui/src/ui/api-interaction.tsx b/apps/wing-console/console/ui/src/ui/api-interaction.tsx index 515c98d2189..a063deaddb5 100644 --- a/apps/wing-console/console/ui/src/ui/api-interaction.tsx +++ b/apps/wing-console/console/ui/src/ui/api-interaction.tsx @@ -36,6 +36,7 @@ export interface ApiInteractionProps { openApiSpec: OpenApiSpec; callFetch: (data: ApiRequest) => void; isLoading: boolean; + resetApiResponse?: () => void; } export const ApiInteraction = memo( @@ -47,6 +48,7 @@ export const ApiInteraction = memo( url, openApiSpec, isLoading, + resetApiResponse = () => {}, }: ApiInteractionProps) => { const { theme } = useTheme(); @@ -296,6 +298,7 @@ export const ApiInteraction = memo( if (isListedRoute && method) { handleMethodChange(path, method); } + resetApiResponse(); }, [ setHeaders, @@ -307,6 +310,7 @@ export const ApiInteraction = memo( setPathVariables, setQueryParameters, handleMethodChange, + resetApiResponse, ], ); diff --git a/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx b/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx index 564f3244296..3a6778b6a56 100644 --- a/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx +++ b/apps/wing-console/console/ui/src/ui/custom-resource-http-client.tsx @@ -42,7 +42,9 @@ export const CustomResourceHttpClientItem = ({ }, ); - const [apiResponse, setApiResponse] = usePersistentState(); + const [apiResponse, setApiResponse] = usePersistentState< + ApiResponse | undefined + >(); const { callFetch, isLoading } = useApi({ onFetchDataUpdate: (data: ApiResponse) => { @@ -67,7 +69,9 @@ export const CustomResourceHttpClientItem = ({ callFetch={callFetch} isLoading={isLoading} apiResponse={apiResponse} - setApiResponse={setApiResponse} + resetApiResponse={() => { + setApiResponse(undefined); + }} /> )} From 5c0038c1ba6b0f63cb06a43d23ce4211011aee1a Mon Sep 17 00:00:00 2001 From: polamoros Date: Mon, 22 Apr 2024 13:19:39 +0200 Subject: [PATCH 4/6] wip --- apps/wing-console/console/app/demo/main.w | 2 +- .../console/ui/src/ui/api-interaction.tsx | 165 +++++++++--------- 2 files changed, 85 insertions(+), 82 deletions(-) diff --git a/apps/wing-console/console/app/demo/main.w b/apps/wing-console/console/app/demo/main.w index df32ae67cd2..7f3bc37f3e0 100644 --- a/apps/wing-console/console/app/demo/main.w +++ b/apps/wing-console/console/app/demo/main.w @@ -254,7 +254,7 @@ class ApiUsersService { "parameters": [ { "in": "header", - "name": "cookie", + "name": "content-type", }, ], "requestBody": { diff --git a/apps/wing-console/console/ui/src/ui/api-interaction.tsx b/apps/wing-console/console/ui/src/ui/api-interaction.tsx index a063deaddb5..1929d2e450e 100644 --- a/apps/wing-console/console/ui/src/ui/api-interaction.tsx +++ b/apps/wing-console/console/ui/src/ui/api-interaction.tsx @@ -1,3 +1,4 @@ +import type { KeyValueItem } from "@wingconsole/design-system"; import { Attribute, Button, @@ -60,18 +61,20 @@ export const ApiInteraction = memo( const [currentHeaderValues, setCurrentHeaderValues] = usePersistentState< string[] >([]); - const [currentRoute, setCurrentRoute] = usePersistentState(""); - const [currentMethod, setCurrentMethod] = usePersistentState("GET"); + const [currentRoute, setCurrentRoute] = usePersistentState(""); + const bodyId = useId(); + const [isBodyEdited, setIsBodyEdited] = usePersistentState(false); + const [body, setBody] = usePersistentState(""); const [bodyPlaceholder, setBodyPlaceholder] = usePersistentState< string | undefined >(); - const [body, setBody] = usePersistentState(""); const [currentOptionsTab, setCurrentOptionsTab] = usePersistentState("headers"); + const [currentResponseTab, setCurrentResponseTab] = usePersistentState("body"); @@ -152,57 +155,62 @@ export const ApiInteraction = memo( const loadDataFromOpenApi = useCallback( (path: string, method: string) => { - // Set the headers - const headersFromSpec = getParametersFromOpenApi({ - path: path, - method: method, - openApi: openApiSpec, - type: "header", + setHeaders((headers) => { + const headersFromSpec = getParametersFromOpenApi({ + path: path, + method: method, + openApi: openApiSpec, + type: "header", + }); + const newHeaders = headersFromSpec.filter( + (header) => + !headers.some( + (existingHeader) => existingHeader.key === header.key, + ), + ); + return [ + ...headers.filter((header) => header.value !== ""), + ...newHeaders, + ]; }); - const newHeaders = headersFromSpec.filter( - (header) => - !headers.some( - (existingHeader) => existingHeader.key === header.key, - ), - ); - setHeaders((headers) => [...headers, ...newHeaders]); - - // Set Query Parameters - const queryParametersFromSpec = getParametersFromOpenApi({ - path: path, - method: method, - openApi: openApiSpec, - type: "query", + + setQueryParameters((queryParameters) => { + const queryParametersFromSpec = getParametersFromOpenApi({ + path: path, + method: method, + openApi: openApiSpec, + type: "query", + }); + const newQueryParameters = queryParametersFromSpec.filter( + (parameter) => + !queryParameters.some( + (existingParameter) => existingParameter.key === parameter.key, + ), + ); + return [ + ...queryParameters.filter((parameter) => parameter.value !== ""), + ...newQueryParameters, + ]; }); - const newQueryParameters = queryParametersFromSpec.filter( - (parameter) => - !queryParameters.some( - (existingParameter) => existingParameter.key === parameter.key, - ), - ); - setQueryParameters((queryParameters) => [ - ...queryParameters, - ...newQueryParameters, - ]); - - // Set Path Variables - const variablesFromSpec = getParametersFromOpenApi({ - path: path, - method: method, - openApi: openApiSpec, - type: "path", + setPathVariables((pathVariables) => { + const variablesFromSpec = getParametersFromOpenApi({ + path: path, + method: method, + openApi: openApiSpec, + type: "path", + }); + const newPathVariables = variablesFromSpec.filter( + (variable) => + !pathVariables.some( + (existingVariable) => existingVariable.key === variable.key, + ), + ); + return [ + ...pathVariables.filter((variable) => variable.value !== ""), + ...newPathVariables, + ]; }); - const newPathVariables = variablesFromSpec.filter( - (variable) => - !pathVariables.some( - (existingVariable) => existingVariable.key === variable.key, - ), - ); - setPathVariables((pathVariables) => [ - ...pathVariables, - ...newPathVariables, - ]); // Set the body const bodyFromSpec = getRequestBodyFromOpenApi( @@ -210,19 +218,23 @@ export const ApiInteraction = memo( method, openApiSpec, ); - setBody(JSON.stringify(bodyFromSpec, undefined, 2)); - setBodyPlaceholder(JSON.stringify(bodyFromSpec, undefined, 2)); + const body = bodyFromSpec + ? JSON.stringify(bodyFromSpec, undefined, 2) + : undefined; + + if (!isBodyEdited) { + setBody(body ?? ""); + } + setBodyPlaceholder(body); }, [ openApiSpec, setHeaders, setBody, + isBodyEdited, setBodyPlaceholder, setQueryParameters, setPathVariables, - queryParameters, - pathVariables, - headers, ], ); @@ -255,32 +267,22 @@ export const ApiInteraction = memo( ); if (!isListedRoute) { - setHeaders([]); - setBody(""); setBodyPlaceholder(undefined); - setPathVariables([]); - setQueryParameters([]); } - setQueryParameters(() => { - const newUrlParameters: { - key: string; - value: string; - }[] = []; + setQueryParameters((currentQueryParameters) => { + const newUrlParameters: KeyValueItem[] = []; for (const [key, value] of urlParameters.entries()) { newUrlParameters.push({ key, value, }); } - return newUrlParameters; + return [...currentQueryParameters, ...newUrlParameters]; }); - setPathVariables(() => { - const newPathVariables: { - key: string; - value: string; - }[] = []; + setPathVariables((currentVariables) => { + const newPathVariables: KeyValueItem[] = []; const matches = newRoute.matchAll(/{(\w+)}/g) || []; for (const match of matches) { @@ -292,24 +294,24 @@ export const ApiInteraction = memo( value: "", }); } - return newPathVariables; + return [...currentVariables, ...newPathVariables]; }); if (isListedRoute && method) { - handleMethodChange(path, method); + setCurrentMethod(method); + loadDataFromOpenApi(path, method); } resetApiResponse(); }, [ - setHeaders, routes, - setBody, setBodyPlaceholder, setCurrentRoute, + setCurrentMethod, currentMethod, setPathVariables, setQueryParameters, - handleMethodChange, + loadDataFromOpenApi, resetApiResponse, ], ); @@ -334,10 +336,10 @@ export const ApiInteraction = memo( handleMethodChange, ]); - // Load the routes from the OpenAPI spec + // load the routes from the open api spec on mount useEffect(() => { loadRoutesFromOpenApi(); - }, [openApiSpec, loadRoutesFromOpenApi]); + }, []); // Load the possible values for the current header key useEffect(() => { @@ -511,7 +513,7 @@ export const ApiInteraction = memo( }, { id: "body", - name: `Body ${body || bodyPlaceholder ? "*" : ""}`, + name: `Body ${isBodyEdited ? "*" : ""}`, panel: (