diff --git a/commit/api/get_commands.py b/commit/api/get_commands.py index 218a4ec..b64187a 100644 --- a/commit/api/get_commands.py +++ b/commit/api/get_commands.py @@ -56,8 +56,8 @@ def get_site_app_commands(app: str) -> dict: # Call get_commands if it is a callable command_list = [] - if hasattr(app_command_module, 'get_commands') and callable(getattr(app_command_module, 'get_commands')): - commands_from_function = app_command_module.get_commands() + if hasattr(app_command_module, 'commands'): + commands_from_function = app_command_module.commands if commands_from_function: for command_instance in commands_from_function: help_text = getattr(command_instance, 'help', 'No help text available') diff --git a/dashboard/src/components/common/Header.tsx b/dashboard/src/components/common/Header.tsx index e166546..f60b0fc 100644 --- a/dashboard/src/components/common/Header.tsx +++ b/dashboard/src/components/common/Header.tsx @@ -1,8 +1,5 @@ import { Link } from 'react-router-dom' import CommitLogo from '../../assets/commit-logo.png' -import { Button } from "@/components/ui/button" -import { GitHubLogoIcon } from '@radix-ui/react-icons' - export const Header = ({ text }: { text?: string }) => { return ( @@ -13,16 +10,6 @@ export const Header = ({ text }: { text?: string }) => { {text &&

{text}

} -
- - -
) } \ No newline at end of file diff --git a/dashboard/src/components/features/APIClient/APIClientContent.tsx b/dashboard/src/components/features/APIClient/APIClientContent.tsx new file mode 100644 index 0000000..9024319 --- /dev/null +++ b/dashboard/src/components/features/APIClient/APIClientContent.tsx @@ -0,0 +1,285 @@ +import CopyButton from "@/components/common/CopyToClipboard/CopyToClipboard" +import { Button } from "@/components/ui/button" +import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Argument } from "@/types/APIData" +import { FrappeConfig, FrappeContext } from "frappe-react-sdk" +import { useCallback, useContext, useEffect, useState } from "react" +import { FormProvider, useForm } from "react-hook-form" +import { FaCaretDown } from "react-icons/fa" +import { IoAdd } from "react-icons/io5"; + + +export interface APIClientContentProps { + endpoint: string + parameters?: Argument[] + open: boolean +} + +export const APIClientContent = ({ endpoint, open, parameters }: APIClientContentProps) => { + + const [requestType, setRequestType] = useState<"GET" | "POST">('GET') + + const methods = useForm() + + const { setValue, reset, register, handleSubmit, watch } = useForm() + + const [paramsType, setParamsType] = useState<'params' | 'form-data'>('params') + + const [response, setResponse] = useState({}) + + const data = watch() + + const onParamsTypeChange = useCallback((type: 'params' | 'form-data') => { + if (type === 'params') { + setParamsType('params') + if (parameters) { + parameters.forEach((parameter) => { + setValue(parameter.argument, '') + }) + } else { + reset({}) + + } + } else { + setParamsType('form-data') + reset({}) + } + }, [parameters]) + + useEffect(() => { + if (parameters) { + parameters.forEach((parameter) => { + setValue(parameter.argument, '') + }) + + } + }, [parameters]) + + useEffect(() => { + reset({}) + setRequestType('GET') + setParamsType('params') + setResponse({}) + }, [open]) + + const { call } = useContext(FrappeContext) as FrappeConfig + + const returnString = (value: any) => { + try { + // If it's a string but contains JSON-like structure, attempt to parse it + const parsedValue = JSON.parse(value); + + if (Array.isArray(parsedValue)) { + return JSON.stringify(parsedValue); // Convert array to string + } else if (typeof parsedValue === 'object' && parsedValue !== null) { + return JSON.stringify(parsedValue); // Convert object to string + } + } catch (error) { + // If JSON.parse fails, it's not a JSON string, continue to the next checks + } + + // Check if it's already a string + if (typeof value === 'string') { + return value; // Return the string as it is + } + + // Check if it's an array + if (Array.isArray(value)) { + return JSON.stringify(value); // Convert array to string + } + + // Check if it's an object + if (typeof value === 'object' && value !== null) { + return JSON.stringify(value); // Convert object to string + } + + // If it's not string, array, or object, return the string representation of the value + return String(value); + } + + const onSubmit = (data: any) => { + const filteredParameterValues = Object.keys(data).reduce((acc, key) => { + if (data[key] !== '') { + // check if value is string if it not if it is list or object then convert it to string using JSON.stringify + acc[key] = returnString(data[key]) + } + return acc + }, {} as Record) + + const paramsData = handleFormData(filteredParameterValues) + console.log('paramsData', paramsData, filteredParameterValues) + + if (requestType === 'GET') { + // call get api with endpoint and parameterValues + call.get(endpoint, paramsData).then((response) => { + setResponse(response) + }).catch((error) => { + setResponse(error) + }) + } else { + // call post api with endpoint and parameterValues + call.post(endpoint, paramsData).then((response) => { + setResponse(response) + }).catch((error) => { + setResponse(error) + }) + } + } + + const handleFormData = (data: any) => { + if (paramsType === 'form-data') { + const obj = {} + Object.keys(data)?.filter((key) => key.includes('key')).forEach((key) => { + // @ts-ignore + obj[data[key]] = data[key.replace('key', 'value')] + }) + return obj + } + else { + return Object.keys(data)?.filter((key) => !(key.includes('key') || key.includes('value'))).reduce((acc, key) => { + acc[key] = data[key] + return acc + }, {} as Record) + } + } + + return ( + + + + API Request + + Easily test and interact with your API endpoints. + +
+
+
+ + + + + + setRequestType('GET')}> + GET + + setRequestType('POST')}> + POST + + + + + +
+ +
+
+
+
+ + + + + + onParamsTypeChange('params')}> + Params + + onParamsTypeChange('form-data')}> + Form Data + + + +
+
+ + + + + Key + + + Value + + + + + {paramsType === 'params' ? parameters?.filter((params) => params.argument !== '')?.map((parameter) => ( + + {parameter.argument}{parameter.default ? null : *} + + + + + )) : data && Object.keys(data)?.filter((key) => key.startsWith('key-')).map((key) => ( + + + + + + + + + ))} + +
+ {paramsType === 'form-data' &&
+ +
} +
+
+
+ +
+ +
+
+                                        {
+                                            JSON.stringify(response, null, 2)
+                                        }
+                                    
+
+
+
+
+ Note: Please use double quotes (") for parameters instead of single quotes ('). +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/dashboard/src/components/features/api_viewer/APIDetails.tsx b/dashboard/src/components/features/api_viewer/APIDetails.tsx index c8f32e3..cf7651f 100644 --- a/dashboard/src/components/features/api_viewer/APIDetails.tsx +++ b/dashboard/src/components/features/api_viewer/APIDetails.tsx @@ -8,8 +8,13 @@ import { web_url } from "@/config/socket" import { APIData, Argument } from "@/types/APIData" import { XMarkIcon } from "@heroicons/react/24/outline" import { useFrappeGetCall } from "frappe-react-sdk" -import { useMemo } from "react" +import { useMemo, useState } from "react" import { MdOutlineFileDownload } from "react-icons/md" +import Markdown from "react-markdown" +import { AiOutlineThunderbolt } from "react-icons/ai" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { Dialog } from "@/components/ui/dialog" +import { APIClientContent } from "../APIClient/APIClientContent" import { APIDocumentationOfSiteApp, Documentation } from "../documentation/APIDocumentation" export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, setSelectedEndpoint, viewerType }: { project_branch: string, endpointData: APIData[], selectedEndpoint: string, setSelectedEndpoint: React.Dispatch>, viewerType: string }) => { @@ -48,11 +53,13 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set } } + const [apiOpen, setApiOpen] = useState(false) + return (
-

API Details

+

{data?.name}

{data?.allow_guest || data?.xss_safe ?
{data?.allow_guest && Allow Guest @@ -77,15 +84,32 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
-
-
Name :
-
{data?.name}
-
-
+
Endpoint :
-
-
{data?.api_path}
- +
+
+
+ {data?.api_path} +
+ +
+
+ {viewerType === 'app' && + + + + + + Click to make an API call to this endpoint + + + } +
@@ -131,6 +155,9 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set } + + +
) } @@ -188,7 +215,7 @@ export const CodeSnippet = ({ apiData, project_branch, file_path, viewerType }: {isLoading &&
} - +
@@ -231,7 +258,7 @@ export const Bruno = ({ doc }: { doc: APIData }) => { {isLoading &&
} - +
- - - - - - + + + + + + + + + + + + Click to view available bench commands in this app + + +
diff --git a/dashboard/src/components/features/commands/CommandsContent.tsx b/dashboard/src/components/features/commands/CommandsContent.tsx index 195111c..eb1bebd 100644 --- a/dashboard/src/components/features/commands/CommandsContent.tsx +++ b/dashboard/src/components/features/commands/CommandsContent.tsx @@ -85,7 +85,7 @@ const CommandComponent = ({ name, help }: CommandResponse) => { return (
-
+
{`bench ${name}`}
{ const location = useLocation() - const { apps } = location.state as { apps: string[] } + const { apps } = location.state as { apps: string[] } || {} - const [selectedApps, setSelectedApps] = useState(apps) + const [selectedApps, setSelectedApps] = useState(apps ?? []) const [erdDoctypes, setERDDocTypes] = useState<{ doctype: string, project_branch: string }[]>([]) @@ -39,6 +39,12 @@ export const ERDViewer = () => { }, []) + useEffect(() => { + if (!apps) { + setOpen(true) + } + }, [apps]) + const flowRef = useRef(null) return ( @@ -113,6 +119,12 @@ export const ModuleDoctypeListDrawer = ({ open, setOpen, apps, setSelectedApps, setOpenDialog(false) } + useEffect(() => { + if (apps.length === 0) { + setOpenDialog(true) + } + }, []) + return ( <> @@ -145,7 +157,7 @@ export const ModuleDoctypeListDrawer = ({ open, setOpen, apps, setSelectedApps, : null} - {apps.length ? : null} +
diff --git a/dashboard/src/pages/overview/Overview.tsx b/dashboard/src/pages/overview/Overview.tsx index af8c453..64090d7 100644 --- a/dashboard/src/pages/overview/Overview.tsx +++ b/dashboard/src/pages/overview/Overview.tsx @@ -9,9 +9,9 @@ export const Overview = () => { const areAppsAvailable = isSystemAppAvailable() return ( -
+
- {areAppsAvailable ? + {areAppsAvailable ? Projects Site Apps