diff --git a/commit/api/api_explorer.py b/commit/api/api_explorer.py index 82e88da..ef457cb 100644 --- a/commit/api/api_explorer.py +++ b/commit/api/api_explorer.py @@ -13,7 +13,6 @@ def get_apis_for_project(project_branch: str): apis = json.loads(branch_doc.whitelisted_apis).get("apis", []) if branch_doc.whitelisted_apis else [] documentation = json.loads(branch_doc.documentation).get("apis", []) if branch_doc.documentation else [] - print('documentation', len(documentation)) for api in apis: # find the documentation for the api whose function_name equals to name and path same as path for doc in documentation: @@ -33,7 +32,8 @@ def get_apis_for_project(project_branch: str): "org_logo": org_logo, "branch_name": branch_doc.branch_name, "project_branch": branch_doc.name, - "last_updated": branch_doc.last_fetched + "last_updated": branch_doc.last_fetched, + 'path_to_folder':branch_doc.path_to_folder } diff --git a/commit/api/get_commands.py b/commit/api/get_commands.py new file mode 100644 index 0000000..218a4ec --- /dev/null +++ b/commit/api/get_commands.py @@ -0,0 +1,70 @@ +import importlib +import sys +import traceback +import os +import frappe +from frappe.utils.bench_helper import get_app_commands + +@frappe.whitelist(allow_guest=True) +def get_project_app_commands(app: str, app_path: str = None) -> dict: + ''' + Gets the commands for the app + ''' + if not app_path or app_path == '': + # Check the permissions of the user + if not frappe.has_permission('System Manager'): + return frappe.throw('You do not have permission to access this resource', frappe.PermissionError) + return get_site_app_commands(app) + else: + ret = [] + try: + if app_path: + # Add the app's directory to the Python path + sys.path.append(app_path) + + app_command_module = importlib.import_module(f"{app}.commands") + except ModuleNotFoundError as e: + if e.name == f"{app}.commands": + return ret + traceback.print_exc() + return ret + except Exception: + traceback.print_exc() + return ret + finally: + if app_path: + # Remove the app's directory from the Python path to avoid side effects + sys.path.remove(app_path) + + 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 commands_from_function: + for command_instance in commands_from_function: + help_text = getattr(command_instance, 'help', 'No help text available') + name = getattr(command_instance, 'name', []) + obj = { + 'name': name, + 'help': help_text + } + command_list.append(obj) + return command_list + +@frappe.whitelist() +def get_site_app_commands(app: str) -> dict: + app_command_module = importlib.import_module(f"{app}.commands") + # 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 commands_from_function: + for command_instance in commands_from_function: + help_text = getattr(command_instance, 'help', 'No help text available') + name = getattr(command_instance, 'name', []) + obj = { + 'name': name, + 'help': help_text + } + command_list.append(obj) + return command_list diff --git a/dashboard/src/components/common/ErrorBanner/ErrorBanner.tsx b/dashboard/src/components/common/ErrorBanner/ErrorBanner.tsx index 0bf5f3a..597b251 100644 --- a/dashboard/src/components/common/ErrorBanner/ErrorBanner.tsx +++ b/dashboard/src/components/common/ErrorBanner/ErrorBanner.tsx @@ -102,7 +102,7 @@ export const ErrorBanner = ({ error, overrideHeading, ...props }: ErrorBannerPro // TODO: Sometimes, error message has links which route to the ERPNext interface. We need to parse the link to route to the correct page in our interface // Links are of format LEAD-00001 - + if (!error) return null return (
diff --git a/dashboard/src/components/features/api_viewer/APIDetails.tsx b/dashboard/src/components/features/api_viewer/APIDetails.tsx index 1c83881..3e26c80 100644 --- a/dashboard/src/components/features/api_viewer/APIDetails.tsx +++ b/dashboard/src/components/features/api_viewer/APIDetails.tsx @@ -158,13 +158,16 @@ export const ParametersTable = ({ parameters }: { parameters?: Argument[] }) => - {parameters?.map((parameter) => ( + {parameters?.map((parameter) => { + const isMandatory = parameter.argument !== '' && parameter.argument && !parameter.default + return ( - {parameter.argument} + {parameter.argument}{isMandatory ? * : ''} {parameter.type ? parameter.type : '-'} {parameter.default ? parameter.default : '-'} - ))} + ) + })} ) diff --git a/dashboard/src/components/features/api_viewer/APIList.tsx b/dashboard/src/components/features/api_viewer/APIList.tsx index a913ffa..48e5a3b 100644 --- a/dashboard/src/components/features/api_viewer/APIList.tsx +++ b/dashboard/src/components/features/api_viewer/APIList.tsx @@ -1,9 +1,13 @@ +import { Button } from "@/components/ui/button" +import { Dialog, DialogTrigger } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { APIData } from "@/types/APIData" import { useEffect, useMemo, useState } from "react" import { AiOutlineBranches } from "react-icons/ai" import { GoPackage } from "react-icons/go" +import { CommandContent } from "../commands/CommandsContent" +import { HiOutlineCommandLine } from "react-icons/hi2"; export interface APIListProps { apiList: APIData[] @@ -11,9 +15,10 @@ export interface APIListProps { branch_name: string setSelectedEndpoint: (endpoint: string) => void selectedEndpoint?: string + path_to_folder: string } -export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, selectedEndpoint }: APIListProps) => { +export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, selectedEndpoint, path_to_folder }: APIListProps) => { const [searchQuery, setSearchQuery] = useState('') const [requestTypeFilter, setRequestTypeFilter] = useState('All') @@ -33,16 +38,27 @@ export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, s return (
-
-
- -

{app_name}

-
-
-
- -

{branch_name}

+
+
+
+ +

{app_name}

+
+
+
+ +

{branch_name}

+
+ + + + + +
diff --git a/dashboard/src/components/features/commands/CommandsContent.tsx b/dashboard/src/components/features/commands/CommandsContent.tsx new file mode 100644 index 0000000..195111c --- /dev/null +++ b/dashboard/src/components/features/commands/CommandsContent.tsx @@ -0,0 +1,100 @@ +import CopyButton from "@/components/common/CopyToClipboard/CopyToClipboard" +import { ErrorBanner } from "@/components/common/ErrorBanner/ErrorBanner" +import { FullPageLoader } from "@/components/common/FullPageLoader/FullPageLoader" +import { Button } from "@/components/ui/button" +import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { DialogClose } from "@radix-ui/react-dialog" +import { useFrappeGetCall } from "frappe-react-sdk" +import { useMemo, useState } from "react" + +export interface CommandContentProps { + app: string, + app_path: string +} + +export interface CommandResponse { + name: string, + help: string, +} + +export const CommandContent = ({ app, app_path }: CommandContentProps) => { + + const { data, error, isLoading } = useFrappeGetCall<{ message: CommandResponse[] }>('commit.api.get_commands.get_project_app_commands', { + app: app, + app_path: app_path + }, undefined, { + revalidateOnFocus: false, + revalidateIfStale: false, + }) + + const [searchQuery, setSearchQuery] = useState('') + + const filteredData = useMemo(() => { + return data?.message?.filter((command) => command.name.toLowerCase().includes(searchQuery.toLowerCase()) || command.help.toLowerCase().includes(searchQuery.toLowerCase())) + }, [data, searchQuery]) + + return ( + + + Commands + + View bench commands for {app} app + + + + {isLoading && } +
+
+
+ setSearchQuery(e.target.value)} className="h-8" value={searchQuery} /> +
+ + {filteredData && filteredData?.length > 0 ?
+ {filteredData?.map((command: CommandResponse) => )} +
:
No commands found
} +
+
+ +
+
Total {filteredData?.length} Command(s)
+ + + +
+
+
+ ) +} + +const CommandComponent = ({ name, help }: CommandResponse) => { + // Regular expression to detect URLs + const urlRegex = /(https?:\/\/[^\s]+)/g; + + // Function to wrap links in anchor tags + const renderHelpText = (text: string) => { + const parts = text.split(urlRegex); + return parts.map((part, index) => { + if (urlRegex.test(part)) { + return {part}; + } + return part; + }); + }; + + return ( +
+ +
+ {`bench ${name}`} +
+ +
+
+ {renderHelpText(help)} +
+
+ ) +} \ No newline at end of file diff --git a/dashboard/src/components/features/meta_apps/YourAppAPIExplorer.tsx b/dashboard/src/components/features/meta_apps/YourAppAPIExplorer.tsx index ed77efa..05aa70c 100644 --- a/dashboard/src/components/features/meta_apps/YourAppAPIExplorer.tsx +++ b/dashboard/src/components/features/meta_apps/YourAppAPIExplorer.tsx @@ -33,12 +33,11 @@ export const YourAppAPIExplorer = () => {
- Explore your API's + Explore your API's and Bench Commands
- Explore and interact with your site installed apps whitelisted API's effortlessly - using our API Explorer. + Effortlessly explore and interact with the whitelisted API's and Bench commands of your site's installed apps using our API Explorer.
diff --git a/dashboard/src/components/features/projects/APIExplorer.tsx b/dashboard/src/components/features/projects/APIExplorer.tsx index 63feda4..c3c027f 100644 --- a/dashboard/src/components/features/projects/APIExplorer.tsx +++ b/dashboard/src/components/features/projects/APIExplorer.tsx @@ -33,12 +33,11 @@ export const APIExplorer = () => {
- Explore your API's + Explore your API's and Bench Commands
- Explore and interact with your whitelisted API's effortlessly - using our API Explorer. + Effortlessly explore and interact with your whitelisted API's and Bench commands using our API Explorer.
diff --git a/dashboard/src/pages/features/api_viewer/APIViewer.tsx b/dashboard/src/pages/features/api_viewer/APIViewer.tsx index 93d077f..a7a7bd5 100644 --- a/dashboard/src/pages/features/api_viewer/APIViewer.tsx +++ b/dashboard/src/pages/features/api_viewer/APIViewer.tsx @@ -19,6 +19,7 @@ interface GetAPIResponse { org_logo?: string, last_updated: string, project_branch: string, + path_to_folder: string } export const APIViewerContainer = () => { const { ID } = useParams() @@ -57,6 +58,7 @@ export const APIViewer = ({ projectBranch }: { projectBranch: string }) => { branch_name={data?.message.branch_name ?? ''} setSelectedEndpoint={setSelectedEndpoint} selectedEndpoint={selectedendpoint} + path_to_folder={data?.message.path_to_folder} />
diff --git a/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx b/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx index 92710d1..98b1543 100644 --- a/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx +++ b/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx @@ -51,6 +51,7 @@ export const AppAPIViewer = ({ appName }: { appName: string }) => { branch_name={data?.message.branch_name ?? ''} setSelectedEndpoint={setSelectedEndpoint} selectedEndpoint={selectedendpoint} + path_to_folder="" />