@motiadev/workbench 0.7.2-beta.135-745094 → 0.7.3-beta.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/App.js +2 -2
- package/dist/src/components/endpoints/endpoint-badge.d.ts +10 -0
- package/dist/src/components/endpoints/endpoint-badge.js +22 -0
- package/dist/src/components/endpoints/endpoint-body-panel.d.ts +13 -0
- package/dist/src/components/endpoints/endpoint-body-panel.js +16 -0
- package/dist/src/components/endpoints/endpoint-call.d.ts +7 -0
- package/dist/src/components/endpoints/endpoint-call.js +58 -0
- package/dist/src/components/endpoints/endpoint-description.d.ts +7 -0
- package/dist/src/components/endpoints/endpoint-description.js +13 -0
- package/dist/src/components/endpoints/endpoint-path-params-panel.d.ts +8 -0
- package/dist/src/components/endpoints/endpoint-path-params-panel.js +17 -0
- package/dist/src/components/endpoints/endpoint-query-params-panel.d.ts +8 -0
- package/dist/src/components/endpoints/endpoint-query-params-panel.js +16 -0
- package/dist/src/components/endpoints/endpoint-response-schema.d.ts +10 -0
- package/dist/src/components/endpoints/endpoint-response-schema.js +17 -0
- package/dist/src/components/endpoints/endpoint-response.d.ts +8 -0
- package/dist/src/components/endpoints/endpoint-response.js +50 -0
- package/dist/src/components/endpoints/endpoint-side-panel.d.ts +8 -0
- package/dist/src/components/endpoints/endpoint-side-panel.js +25 -0
- package/dist/src/components/endpoints/endpoints-page.d.ts +1 -0
- package/dist/src/components/endpoints/endpoints-page.js +14 -0
- package/dist/src/components/endpoints/hooks/use-get-endpoints.d.ts +2 -0
- package/dist/src/components/endpoints/hooks/use-get-endpoints.js +8 -0
- package/dist/src/components/endpoints/hooks/use-json-schema-to-json.d.ts +4 -0
- package/dist/src/components/endpoints/hooks/use-json-schema-to-json.js +11 -0
- package/dist/src/components/endpoints/hooks/use-path-params.d.ts +1 -0
- package/dist/src/components/endpoints/hooks/use-path-params.js +4 -0
- package/dist/src/components/endpoints/hooks/use-state-stream.d.ts +7 -0
- package/dist/src/components/endpoints/hooks/use-state-stream.js +11 -0
- package/dist/src/components/endpoints/hooks/utils.d.ts +1 -0
- package/dist/src/components/endpoints/hooks/utils.js +29 -0
- package/dist/src/components/endpoints/response-body.d.ts +7 -0
- package/dist/src/components/endpoints/response-body.js +6 -0
- package/dist/src/components/flow/base-edge.js +1 -1
- package/dist/src/components/logs/log-detail.js +1 -1
- package/dist/src/components/observability/trace-item/trace-item-detail.js +2 -1
- package/dist/src/components/observability/trace-item/trace-item.js +1 -1
- package/dist/src/components/observability/traces-groups.js +1 -1
- package/dist/src/components/sidebar/sidebar.d.ts +8 -0
- package/dist/src/components/sidebar/sidebar.js +39 -0
- package/dist/src/components/states/state-editor.js +1 -1
- package/dist/src/components/states/state-sidebar.js +1 -1
- package/dist/src/components/ui/table.js +1 -1
- package/dist/src/components/ui/theme-toggle.js +1 -1
- package/dist/src/index.css +6 -10
- package/dist/src/lib/utils.d.ts +2 -0
- package/dist/src/lib/utils.js +5 -0
- package/dist/src/publicComponents/base-node/feature-card.js +1 -1
- package/dist/src/publicComponents/base-node/node-header.js +1 -1
- package/dist/src/publicComponents/base-node/node-sidebar.js +1 -1
- package/dist/tsconfig.app.tsbuildinfo +1 -1
- package/dist/tsconfig.node.tsbuildinfo +1 -1
- package/package.json +3 -4
- /package/dist/src/components/{ui → endpoints}/json-editor.d.ts +0 -0
- /package/dist/src/components/{ui → endpoints}/json-editor.js +0 -0
package/dist/src/App.js
CHANGED
|
@@ -4,15 +4,15 @@ import { analytics } from '@/lib/analytics';
|
|
|
4
4
|
import { ReactFlowProvider } from '@xyflow/react';
|
|
5
5
|
import { File, GanttChart, Link2, LogsIcon } from 'lucide-react';
|
|
6
6
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
|
+
import { EndpointsPage } from './components/endpoints/endpoints-page';
|
|
7
8
|
import { FlowPage } from './components/flow/flow-page';
|
|
8
9
|
import { FlowTabMenuItem } from './components/flow/flow-tab-menu-item';
|
|
9
10
|
import { Header } from './components/header/header';
|
|
10
11
|
import { LogsPage } from './components/logs/logs-page';
|
|
11
12
|
import { TracesPage } from './components/observability/traces-page';
|
|
12
|
-
import { APP_SIDEBAR_CONTAINER_ID } from '
|
|
13
|
+
import { APP_SIDEBAR_CONTAINER_ID } from './components/sidebar/sidebar';
|
|
13
14
|
import { StatesPage } from './components/states/states-page';
|
|
14
15
|
import { useTabsStore } from './stores/use-tabs-store';
|
|
15
|
-
import { EndpointsPage } from '@motiadev/plugin-endpoint';
|
|
16
16
|
var TabLocation;
|
|
17
17
|
(function (TabLocation) {
|
|
18
18
|
TabLocation["TOP"] = "top";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
declare const badgeVariants: (props?: ({
|
|
4
|
+
variant?: "POST" | "GET" | "DELETE" | "PUT" | "PATCH" | "HEAD" | "OPTIONS" | null | undefined;
|
|
5
|
+
defaultVariants?: "variant" | null | undefined;
|
|
6
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
|
+
interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
|
|
8
|
+
}
|
|
9
|
+
export declare function EndpointBadge({ className, variant, ...props }: BadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cva } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
const badgeVariants = cva('rounded-lg px-2 py-1 text-sm font-mono font-bold transition-colors', {
|
|
5
|
+
variants: {
|
|
6
|
+
variant: {
|
|
7
|
+
POST: 'bg-[#258DC3]/15 text-[#258DC3]',
|
|
8
|
+
GET: 'bg-[#709A2D]/15 text-[#709A2D]',
|
|
9
|
+
DELETE: 'bg-[#DE2134]/15 text-[#DE2134]',
|
|
10
|
+
PUT: 'bg-yellow-500/50 text-yellow-100', // TODO color
|
|
11
|
+
PATCH: 'bg-yellow-500/50 text-yellow-100', // TODO color
|
|
12
|
+
HEAD: 'bg-blue-500/50 text-blue-100', // TODO color
|
|
13
|
+
OPTIONS: 'bg-purple-500/50 text-purple-100', // TODO color
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: 'bg-blue-500/50 text-blue-100',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
export function EndpointBadge({ className, variant, ...props }) {
|
|
21
|
+
return _jsx("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ApiEndpoint } from '@/types/endpoint';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
import 'react18-json-view/src/dark.css';
|
|
4
|
+
import 'react18-json-view/src/style.css';
|
|
5
|
+
type Props = {
|
|
6
|
+
endpoint: ApiEndpoint;
|
|
7
|
+
value: string;
|
|
8
|
+
onChange?: (body: string) => void;
|
|
9
|
+
onValidate?: (isValid: boolean) => void;
|
|
10
|
+
panelName: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const EndpointBodyPanel: FC<Props>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Panel } from '@motiadev/ui';
|
|
3
|
+
import { JsonEditor } from './json-editor';
|
|
4
|
+
import ReactJson from 'react18-json-view';
|
|
5
|
+
import 'react18-json-view/src/dark.css';
|
|
6
|
+
import 'react18-json-view/src/style.css';
|
|
7
|
+
export const EndpointBodyPanel = ({ endpoint, onChange, onValidate, panelName, value }) => {
|
|
8
|
+
const shouldHaveBody = ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
|
|
9
|
+
const handleBodyChange = (body) => {
|
|
10
|
+
onChange?.(body);
|
|
11
|
+
};
|
|
12
|
+
if (!shouldHaveBody) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return (_jsx(Panel, { title: "Body", size: "sm", contentClassName: "p-0", "data-testid": `endpoint-body-panel__${panelName}`, children: onChange ? (_jsx(JsonEditor, { value: value, schema: endpoint.bodySchema, onChange: handleBodyChange, onValidate: onValidate })) : (_jsx(ReactJson, { src: value ? JSON.parse(value) : {}, theme: "default", enableClipboard: false, style: { backgroundColor: 'transparent' } })) }));
|
|
16
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '@motiadev/ui';
|
|
3
|
+
import { Loader2, Play } from 'lucide-react';
|
|
4
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { EndpointBodyPanel } from './endpoint-body-panel';
|
|
6
|
+
import { EndpointPathParamsPanel } from './endpoint-path-params-panel';
|
|
7
|
+
import { EndpointQueryParamsPanel } from './endpoint-query-params-panel';
|
|
8
|
+
import { EndpointResponse } from './endpoint-response';
|
|
9
|
+
import { useJsonSchemaToJson } from './hooks/use-json-schema-to-json';
|
|
10
|
+
export const EndpointCall = ({ endpoint }) => {
|
|
11
|
+
const shouldHaveBody = ['post', 'put', 'patch'].includes(endpoint.method.toLowerCase());
|
|
12
|
+
const [isRequestLoading, setIsRequestLoading] = useState(false);
|
|
13
|
+
const [responseCode, setResponseCode] = useState(undefined);
|
|
14
|
+
const [responseBody, setResponseBody] = useState(undefined);
|
|
15
|
+
const [executionTime, setExecutionTime] = useState(undefined);
|
|
16
|
+
const { body, setBody } = useJsonSchemaToJson(endpoint.bodySchema);
|
|
17
|
+
const [isBodyValid, setIsBodyValid] = useState(true);
|
|
18
|
+
const [pathParamsValues, setPathParamsValues] = useState({});
|
|
19
|
+
const [queryParamsValues, setQueryParamsValues] = useState({});
|
|
20
|
+
const isPlayEnabled = useMemo(() => {
|
|
21
|
+
if (shouldHaveBody && !isBodyValid)
|
|
22
|
+
return false;
|
|
23
|
+
return (Object.values(pathParamsValues).every((value) => value) &&
|
|
24
|
+
Object.values(queryParamsValues).every((value) => value));
|
|
25
|
+
}, [pathParamsValues, shouldHaveBody, isBodyValid, queryParamsValues]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (endpoint.id) {
|
|
28
|
+
setResponseCode(undefined);
|
|
29
|
+
setResponseBody(undefined);
|
|
30
|
+
setExecutionTime(undefined);
|
|
31
|
+
setIsRequestLoading(false);
|
|
32
|
+
}
|
|
33
|
+
}, [endpoint.id]);
|
|
34
|
+
const handleRequest = async () => {
|
|
35
|
+
setIsRequestLoading(true);
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
const path = new URL(window.location.origin +
|
|
38
|
+
Object.entries(pathParamsValues).reduce((acc, [param, value]) => {
|
|
39
|
+
return acc.replace(`:${param}`, value);
|
|
40
|
+
}, endpoint.path));
|
|
41
|
+
for (const [key, value] of Object.entries(queryParamsValues)) {
|
|
42
|
+
path.searchParams.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
const response = await fetch(path.toString(), {
|
|
45
|
+
method: endpoint.method,
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: endpoint.method === 'GET' ? null : body,
|
|
48
|
+
});
|
|
49
|
+
const endTime = Date.now();
|
|
50
|
+
const executionTime = endTime - startTime;
|
|
51
|
+
const json = await response.json();
|
|
52
|
+
setResponseCode(response.status);
|
|
53
|
+
setResponseBody(json);
|
|
54
|
+
setExecutionTime(executionTime);
|
|
55
|
+
setIsRequestLoading(false);
|
|
56
|
+
};
|
|
57
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint, onChange: setPathParamsValues }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint, onChange: setQueryParamsValues }), _jsx(EndpointBodyPanel, { endpoint: endpoint, value: body, onChange: setBody, onValidate: setIsBodyValid, panelName: "call" }), _jsxs(Button, { className: "w-fit", onClick: handleRequest, variant: "accent", "data-testid": "endpoint-play-button", disabled: isRequestLoading || !isPlayEnabled, children: [isRequestLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(Play, {}), " Play"] }), _jsx(EndpointResponse, { responseCode: responseCode, responseBody: responseBody, executionTime: executionTime })] }));
|
|
58
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { EndpointResponseSchema } from './endpoint-response-schema';
|
|
3
|
+
import { EndpointPathParamsPanel } from './endpoint-path-params-panel';
|
|
4
|
+
import { EndpointQueryParamsPanel } from './endpoint-query-params-panel';
|
|
5
|
+
import { EndpointBodyPanel } from './endpoint-body-panel';
|
|
6
|
+
import { useJsonSchemaToJson } from './hooks/use-json-schema-to-json';
|
|
7
|
+
export const EndpointDescription = ({ endpoint }) => {
|
|
8
|
+
const { body } = useJsonSchemaToJson(endpoint.bodySchema);
|
|
9
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(EndpointPathParamsPanel, { endpoint: endpoint }), _jsx(EndpointQueryParamsPanel, { endpoint: endpoint }), _jsx(EndpointBodyPanel, { endpoint: endpoint, panelName: "details", value: body }), _jsx(EndpointResponseSchema, { items: Object.entries(endpoint?.responseSchema ?? {}).map(([status, schema]) => ({
|
|
10
|
+
responseCode: status,
|
|
11
|
+
bodySchema: schema,
|
|
12
|
+
})) })] }));
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Input, Panel } from '@motiadev/ui';
|
|
3
|
+
import { Fragment, useState } from 'react';
|
|
4
|
+
import { usePathParams } from './hooks/use-path-params';
|
|
5
|
+
export const EndpointPathParamsPanel = ({ endpoint, onChange }) => {
|
|
6
|
+
const pathParams = usePathParams(endpoint.path);
|
|
7
|
+
const [pathParamsValues, setPathParamsValues] = useState(pathParams?.reduce((acc, param) => ({ ...acc, [param]: '' }), {}));
|
|
8
|
+
const onPathParamChange = (param, value) => {
|
|
9
|
+
const newPathParamsValues = { ...pathParamsValues, [param]: value };
|
|
10
|
+
setPathParamsValues(newPathParamsValues);
|
|
11
|
+
onChange?.(newPathParamsValues);
|
|
12
|
+
};
|
|
13
|
+
if (!pathParams.length) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return (_jsx(Panel, { title: "Path params", size: "sm", variant: "default", children: _jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: '1fr 2fr' }, children: pathParams.map((param) => (_jsxs(Fragment, { children: [_jsx("div", { className: "font-bold leading-[36px] flex text-xs", children: param }), _jsx("div", { className: "flex items-center text-xs", children: onChange && (_jsx(Input, { className: "w-full text-xs", placeholder: param, value: pathParamsValues[param], onChange: (e) => onPathParamChange(param, e.target.value) })) })] }, param))) }) }));
|
|
17
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Input } from '@motiadev/ui';
|
|
3
|
+
import { Panel } from '@motiadev/ui';
|
|
4
|
+
import { Fragment, useState } from 'react';
|
|
5
|
+
export const EndpointQueryParamsPanel = ({ endpoint, onChange }) => {
|
|
6
|
+
const [queryParamsValues, setQueryParamsValues] = useState(endpoint.queryParams?.reduce((acc, param) => ({ ...acc, [param.name]: '' }), {}) ?? {});
|
|
7
|
+
const onQueryParamChange = (param, value) => {
|
|
8
|
+
const newQueryParamsValues = { ...queryParamsValues, [param]: value };
|
|
9
|
+
setQueryParamsValues(newQueryParamsValues);
|
|
10
|
+
onChange?.(newQueryParamsValues);
|
|
11
|
+
};
|
|
12
|
+
if (!endpoint.queryParams?.length) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return (_jsx(Panel, { title: "Query params", size: "sm", variant: "outlined", children: _jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: '1fr 2fr', gridTemplateRows: '1fr 1fr' }, children: endpoint.queryParams.map((param) => (_jsxs(Fragment, { children: [_jsx("div", { className: "font-bold leading-[36px] flex text-xs", children: param.name }), _jsx("div", { className: "flex items-center text-xs ", children: onChange ? (_jsx(Input, { className: "text-xs", placeholder: param.description, value: queryParamsValues[param.name], onChange: (e) => onQueryParamChange(param.name, e.target.value) })) : (_jsx("span", { children: param.description })) })] }, param.name))) }) }));
|
|
16
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export type EndpointResponseItem = {
|
|
3
|
+
responseCode: string;
|
|
4
|
+
bodySchema: Record<string, Record<string, any>>;
|
|
5
|
+
};
|
|
6
|
+
type EndpointResponseProps = {
|
|
7
|
+
items: EndpointResponseItem[];
|
|
8
|
+
};
|
|
9
|
+
export declare const EndpointResponseSchema: FC<EndpointResponseProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { convertJsonSchemaToJson } from '@/components/endpoints/hooks/utils';
|
|
3
|
+
import { useThemeStore } from '@/stores/use-theme-store';
|
|
4
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@motiadev/ui';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import ReactJson from 'react18-json-view';
|
|
7
|
+
const EndpointResponseSchemaItem = ({ responseCode, bodySchema }) => {
|
|
8
|
+
const theme = useThemeStore((store) => store.theme);
|
|
9
|
+
const schema = useMemo(() => convertJsonSchemaToJson(bodySchema), [bodySchema]);
|
|
10
|
+
return (_jsx(TabsContent, { value: responseCode, className: "border-t", children: _jsx("div", { className: "text-xs font-mono rounded-lg whitespace-pre-wrap", children: _jsx(ReactJson, { src: schema, dark: theme === 'dark', enableClipboard: false, style: { backgroundColor: 'transparent' } }) }) }, responseCode));
|
|
11
|
+
};
|
|
12
|
+
export const EndpointResponseSchema = ({ items }) => {
|
|
13
|
+
if (items.length === 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return (_jsx("div", { className: "flex flex-col rounded-lg border", children: _jsxs(Tabs, { defaultValue: items[0].responseCode, children: [_jsx("div", { className: "flex items-center justify-between bg-card", children: _jsx(TabsList, { className: "bg-transparent p-0", children: items.map((item) => (_jsx(TabsTrigger, { value: item.responseCode, className: "text-xs font-bold cursor-pointer", children: item.responseCode }, item.responseCode))) }) }), items.map((props) => (_jsx(EndpointResponseSchemaItem, { ...props }, props.responseCode)))] }) }));
|
|
17
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
type EndpointResponseProps = {
|
|
3
|
+
responseCode: number | string | undefined;
|
|
4
|
+
responseBody: Record<string, any> | undefined;
|
|
5
|
+
executionTime?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const EndpointResponse: FC<EndpointResponseProps>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useStateStream } from '@/components/endpoints/hooks/use-state-stream';
|
|
3
|
+
import { useThemeStore } from '@/stores/use-theme-store';
|
|
4
|
+
import { Panel } from '@motiadev/ui';
|
|
5
|
+
import { XCircle } from 'lucide-react';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import ReactJson from 'react18-json-view';
|
|
8
|
+
const getStatusMessage = (status) => {
|
|
9
|
+
switch (status) {
|
|
10
|
+
case 200:
|
|
11
|
+
return 'OK';
|
|
12
|
+
case 201:
|
|
13
|
+
return 'Created';
|
|
14
|
+
case 204:
|
|
15
|
+
return 'No Content';
|
|
16
|
+
case 400:
|
|
17
|
+
return 'Bad Request';
|
|
18
|
+
case 401:
|
|
19
|
+
return 'Unauthorized';
|
|
20
|
+
case 403:
|
|
21
|
+
return 'Forbidden';
|
|
22
|
+
case 404:
|
|
23
|
+
return 'Not Found';
|
|
24
|
+
case 500:
|
|
25
|
+
return 'Internal Server Error';
|
|
26
|
+
default:
|
|
27
|
+
return 'Unknown Status';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const EndpointResponse = ({ responseCode, executionTime, responseBody }) => {
|
|
31
|
+
const theme = useThemeStore((state) => state.theme);
|
|
32
|
+
const { data, isStreamed, originalData } = useStateStream(responseBody);
|
|
33
|
+
const statusMessage = useMemo(() => getStatusMessage(Number(responseCode)), [responseCode]);
|
|
34
|
+
const isError = Number(responseCode) >= 400;
|
|
35
|
+
if (!responseBody || !responseCode) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return (_jsx(Panel, { "data-testid": 'endpoint-response-container', tabs: isStreamed
|
|
39
|
+
? [
|
|
40
|
+
{
|
|
41
|
+
label: 'Streamed Response',
|
|
42
|
+
content: (_jsx(ReactJson, { src: data, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: 'Original',
|
|
46
|
+
content: (_jsx(ReactJson, { src: originalData, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })),
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
: undefined, title: _jsxs("div", { className: "flex flex-row justify-between items-center flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isError && _jsx(XCircle, { className: "text-red-500 w-4 h-4" }), _jsxs("span", { className: "font-bold text-xs", children: [responseCode, " - ", statusMessage] })] }), !!executionTime && (_jsxs("span", { className: "text-xs text-muted-foreground justify-self-end", children: ["Execution time: ", _jsxs("span", { className: "font-bold", children: [executionTime, "ms"] })] }))] }), subtitle: isStreamed && (_jsxs("span", { className: "col-span-2 flex flex-row items-center font-medium text-card-foreground text-xs mt-2", children: [_jsxs("span", { className: "inline-block w-2 h-2 rounded-full bg-green-500 mr-2 relative", children: [_jsx("span", { className: "absolute inset-0 rounded-full bg-green-500 animate-[ping_1.5s_ease-in-out_infinite]" }), _jsx("span", { className: "absolute inset-0 rounded-full bg-green-500" })] }), "Object is being streamed, this is not the actual response from the API Endpoint"] })), children: !isStreamed && (_jsx(ReactJson, { src: data, dark: theme === 'dark', style: { backgroundColor: 'transparent' } })) }));
|
|
50
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Sidebar } from '@/components/sidebar/sidebar';
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import { EndpointBadge } from './endpoint-badge';
|
|
5
|
+
import { EndpointCall } from './endpoint-call';
|
|
6
|
+
import { EndpointDescription } from './endpoint-description';
|
|
7
|
+
export const EndpointSidePanel = ({ endpoint, onClose }) => {
|
|
8
|
+
return (_jsx(Sidebar, { initialWidth: 600, subtitle: endpoint.description, title: _jsxs("div", { className: "flex flex-row gap-2 items-center", children: [_jsx(EndpointBadge, { variant: endpoint.method, children: endpoint.method.toUpperCase() }), _jsx("span", { className: "text-md font-bold", children: endpoint.path })] }), onClose: onClose, tabs: [
|
|
9
|
+
{
|
|
10
|
+
label: 'Details',
|
|
11
|
+
content: _jsx(EndpointDescription, { endpoint: endpoint }),
|
|
12
|
+
'data-testid': 'endpoint-details-tab',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: 'Call',
|
|
16
|
+
content: _jsx(EndpointCall, { endpoint: endpoint }),
|
|
17
|
+
'data-testid': 'endpoint-call-tab',
|
|
18
|
+
},
|
|
19
|
+
], actions: [
|
|
20
|
+
{
|
|
21
|
+
icon: _jsx(X, { className: "cursor-pointer w-4 h-4", onClick: onClose }),
|
|
22
|
+
onClick: onClose,
|
|
23
|
+
},
|
|
24
|
+
] }));
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const EndpointsPage: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
|
+
import { useGlobalStore } from '@/stores/use-global-store';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { EndpointBadge } from './endpoint-badge';
|
|
6
|
+
import { EndpointSidePanel } from './endpoint-side-panel';
|
|
7
|
+
import { useGetEndpoints } from './hooks/use-get-endpoints';
|
|
8
|
+
export const EndpointsPage = () => {
|
|
9
|
+
const endpoints = useGetEndpoints();
|
|
10
|
+
const selectedEndpointId = useGlobalStore((state) => state.selectedEndpointId);
|
|
11
|
+
const selectEndpointId = useGlobalStore((state) => state.selectEndpointId);
|
|
12
|
+
const selectedEndpoint = useMemo(() => selectedEndpointId && endpoints.find((endpoint) => endpoint.id === selectedEndpointId), [endpoints, selectedEndpointId]);
|
|
13
|
+
return (_jsxs("div", { className: "flex flex-row w-full h-full", children: [_jsx("div", { className: "flex flex-col flex-1 overflow-y-auto", children: endpoints.map((endpoint) => (_jsx("div", { "data-testid": `endpoint-${endpoint.method}-${endpoint.path}`, className: cn(selectedEndpoint === endpoint && 'bg-muted-foreground/10', 'cursor-pointer select-none'), onClick: () => selectEndpointId(endpoint.id), children: _jsxs("div", { className: "flex flex-row gap-2 items-center hover:bg-muted-foreground/10 p-2", children: [_jsx(EndpointBadge, { variant: endpoint.method, children: endpoint.method.toUpperCase() }), _jsx("span", { className: "text-md font-mono font-bold whitespace-nowrap", children: endpoint.path }), _jsx("span", { className: "text-md text-muted-foreground truncate", children: endpoint.description })] }) }, `${endpoint.method} ${endpoint.path}`))) }), selectedEndpoint && (_jsx(EndpointSidePanel, { endpoint: selectedEndpoint, onClose: () => selectEndpointId(undefined) }))] }));
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { convertJsonSchemaToJson } from './utils';
|
|
3
|
+
export const useJsonSchemaToJson = (schema) => {
|
|
4
|
+
const [body, setBody] = useState('');
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
if (schema) {
|
|
7
|
+
setBody(JSON.stringify(convertJsonSchemaToJson(schema), null, 2));
|
|
8
|
+
}
|
|
9
|
+
}, [schema]);
|
|
10
|
+
return { body, setBody };
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const usePathParams: (path: string) => string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useStreamItem } from '@motiadev/stream-client-react';
|
|
2
|
+
export const useStateStream = (object) => {
|
|
3
|
+
const { __motia, ...rest } = object || {};
|
|
4
|
+
const { data } = useStreamItem(__motia);
|
|
5
|
+
const originalData = Array.isArray(object) ? object : rest || object;
|
|
6
|
+
return {
|
|
7
|
+
data: data || originalData,
|
|
8
|
+
originalData,
|
|
9
|
+
isStreamed: !!__motia,
|
|
10
|
+
};
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const convertJsonSchemaToJson: (schema?: Record<string, any>) => any;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const convertJsonSchemaToJson = (schema) => {
|
|
2
|
+
if (!schema)
|
|
3
|
+
return {};
|
|
4
|
+
if (schema.type === 'object') {
|
|
5
|
+
const result = {};
|
|
6
|
+
if (schema.properties) {
|
|
7
|
+
Object.entries(schema.properties).forEach(([key, value]) => {
|
|
8
|
+
result[key] = convertJsonSchemaToJson(value);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
switch (schema.type) {
|
|
14
|
+
case 'array':
|
|
15
|
+
return [convertJsonSchemaToJson(schema.items)];
|
|
16
|
+
case 'string':
|
|
17
|
+
return schema.enum?.[0] ?? schema.description ?? 'string';
|
|
18
|
+
case 'number':
|
|
19
|
+
return schema.description ?? 0;
|
|
20
|
+
case 'integer':
|
|
21
|
+
return 0;
|
|
22
|
+
case 'boolean':
|
|
23
|
+
return schema.description ?? false;
|
|
24
|
+
case 'null':
|
|
25
|
+
return schema.description ?? null;
|
|
26
|
+
default:
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useJsonSchemaToJson } from './hooks/use-json-schema-to-json';
|
|
3
|
+
export const ResponseBody = ({ status, body }) => {
|
|
4
|
+
const { body: responseBody } = useJsonSchemaToJson(body);
|
|
5
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: "text-xs font-bold", children: status }), _jsx("span", { className: "text-xs font-mono dark:bg-black/50 bg-white/50 p-2 rounded-lg whitespace-pre-wrap", children: responseBody })] }));
|
|
6
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { BaseEdge as BaseReactFlowEdge, EdgeLabelRenderer, getSmoothStepPath } from '@xyflow/react';
|
|
3
3
|
import { cva } from 'class-variance-authority';
|
|
4
|
-
import { cn } from '
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
5
|
const labelVariants = cva('absolute pointer-events-all text-cs border p-1 px-2', {
|
|
6
6
|
variants: {
|
|
7
7
|
color: {
|
|
@@ -5,7 +5,7 @@ import ReactJson from 'react18-json-view';
|
|
|
5
5
|
import 'react18-json-view/src/dark.css';
|
|
6
6
|
import 'react18-json-view/src/style.css';
|
|
7
7
|
import { LogLevelDot } from './log-level-dot';
|
|
8
|
-
import { Sidebar } from '
|
|
8
|
+
import { Sidebar } from '@/components/sidebar/sidebar';
|
|
9
9
|
import { X } from 'lucide-react';
|
|
10
10
|
const defaultProps = ['id', 'msg', 'time', 'level', 'step', 'flows', 'traceId'];
|
|
11
11
|
export const LogDetail = ({ log, onClose }) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Sidebar
|
|
2
|
+
import { Sidebar } from '@/components/sidebar/sidebar';
|
|
3
|
+
import { Badge } from '@motiadev/ui';
|
|
3
4
|
import { formatDuration } from '@/lib/utils';
|
|
4
5
|
import { X } from 'lucide-react';
|
|
5
6
|
import { memo } from 'react';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from '
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
3
|
export const TraceItem = ({ trace, group, groupEndTime, onExpand }) => {
|
|
4
4
|
return (_jsxs("div", { className: "flex hover:bg-muted-foreground/10 relative cursor-pointer", onClick: () => onExpand(trace.id), "data-testid": "trace-timeline-item", children: [_jsx("div", { className: "flex items-center min-w-[200px] max-w-[200px] h-[32px] max-h-[32px] py-4 px-2 text-sm font-semibold text-foreground truncate sticky left-0 bg-card z-9", children: trace.name }), _jsx("div", { className: "flex w-full flex-row items-center hover:bg-muted/50 rounded-md", children: _jsx("div", { className: "relative w-full h-[32px] flex items-center", children: _jsx("div", { className: cn('h-[24px] rounded-[4px] hover:opacity-80 transition-all duration-200', {
|
|
5
5
|
'bg-[repeating-linear-gradient(140deg,#BEFE29,#BEFE29_8px,#ABE625_8px,#ABE625_16px)]': trace.status === 'running',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from '
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
3
|
import { formatDistanceToNow } from 'date-fns';
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { TraceStatusBadge } from './trace-status';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type PanelProps } from '@motiadev/ui';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
export declare const APP_SIDEBAR_CONTAINER_ID = "app-sidebar-container";
|
|
4
|
+
export type SidebarProps = PanelProps & {
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
initialWidth?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare const Sidebar: FC<SidebarProps>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Panel } from '@motiadev/ui';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { useEffect, useMemo } from 'react';
|
|
5
|
+
import { useResizable } from 'react-use-resizable';
|
|
6
|
+
import { Equal } from 'lucide-react';
|
|
7
|
+
export const APP_SIDEBAR_CONTAINER_ID = 'app-sidebar-container';
|
|
8
|
+
const CLOSE_PREVIOUS_SIDEBAR_EVENT = 'close-previous-sidebar';
|
|
9
|
+
export const Sidebar = ({ initialWidth, onClose, ...props }) => {
|
|
10
|
+
const sidebarId = useMemo(() => Symbol(), []);
|
|
11
|
+
const { getRootProps, getHandleProps } = useResizable({
|
|
12
|
+
lockVertical: true,
|
|
13
|
+
initialWidth: initialWidth ?? 400,
|
|
14
|
+
initialHeight: '100%',
|
|
15
|
+
onDragStart: () => {
|
|
16
|
+
document.body.style.userSelect = 'none';
|
|
17
|
+
},
|
|
18
|
+
onDragEnd: () => {
|
|
19
|
+
document.body.style.userSelect = '';
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const event = new CustomEvent(CLOSE_PREVIOUS_SIDEBAR_EVENT, { detail: { sidebarId } });
|
|
24
|
+
window.dispatchEvent(event);
|
|
25
|
+
const handleClose = (e) => {
|
|
26
|
+
const customEvent = e;
|
|
27
|
+
if (customEvent.detail.sidebarId !== sidebarId) {
|
|
28
|
+
onClose();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
window.addEventListener(CLOSE_PREVIOUS_SIDEBAR_EVENT, handleClose);
|
|
32
|
+
return () => {
|
|
33
|
+
window.removeEventListener(CLOSE_PREVIOUS_SIDEBAR_EVENT, handleClose);
|
|
34
|
+
};
|
|
35
|
+
}, [sidebarId, onClose]);
|
|
36
|
+
return createPortal(_jsxs("div", { ...getRootProps(), className: "pr-2 py-2 relative", children: [_jsx("div", { ...getHandleProps({
|
|
37
|
+
reverse: true,
|
|
38
|
+
}), className: "flex h-6 w-6 items-center justify-center rounded-full bg-background border border-border absolute top-1/2 -translate-y-1/2 -left-4 z-20", children: _jsx(Equal, { className: "rotate-90 w-4 h-4 text-muted-foreground" }) }), _jsx(Panel, { ...props, variant: "outlined", className: "max-h-[calc(100vh-80px)] h-full", "data-testid": "sidebar-panel" })] }), document.querySelector(`#${APP_SIDEBAR_CONTAINER_ID}`));
|
|
39
|
+
};
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Button } from '@motiadev/ui';
|
|
3
3
|
import { AlertCircle, Check, Loader2, Save } from 'lucide-react';
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
-
import { JsonEditor } from '../
|
|
5
|
+
import { JsonEditor } from '../endpoints/json-editor';
|
|
6
6
|
export const StateEditor = ({ state }) => {
|
|
7
7
|
const [isRequestLoading, setIsRequestLoading] = useState(false);
|
|
8
8
|
const [isValid, setIsValid] = useState(true);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Sidebar } from '
|
|
2
|
+
import { Sidebar } from '@/components/sidebar/sidebar';
|
|
3
3
|
import { X } from 'lucide-react';
|
|
4
4
|
import { StateDetails } from './state-details';
|
|
5
5
|
import { StateEditor } from './state-editor';
|