From 01121d220d3040f273786fac0f76ba63eb53713d Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Fri, 27 Sep 2024 15:53:57 +0530 Subject: [PATCH 1/9] wip --- commit/api/generate_documentation.py | 14 ++-- .../features/api_viewer/APIDetails.tsx | 84 ++++--------------- .../features/api_viewer/APIList.tsx | 36 +++----- .../documentation/APIDocumentation.tsx | 37 ++++---- 4 files changed, 59 insertions(+), 112 deletions(-) diff --git a/commit/api/generate_documentation.py b/commit/api/generate_documentation.py index 43a39df..2bef1de 100644 --- a/commit/api/generate_documentation.py +++ b/commit/api/generate_documentation.py @@ -61,11 +61,11 @@ def generate_docs_for_chunk(api_chunk): "content": ( "You are an expert documentation generator. Create detailed and comprehensive documentation " "for the code provided below in Markdown format. Each function should have the following sections:\n\n" - "- **(api[Function Name])**\n" - "- **Description**: Detailed description of what the function does and what it is used for \n" - "- **Parameters**: List of parameters with their types, descriptions, and indicate which are mandatory or optional\n" - "- **Return Type**: Type and description of the return value\n" - "- **Examples**: Code examples demonstrating how to use the function (enclosed in triple backticks ``````).\n\n" + "- # [Function Name] (as heading 1)\n" + "- ## Description: Detailed description of what the function does and what it is used for \n" + "- ## Parameters: List of parameters with their types, descriptions, and indicate which are mandatory or optional\n" + "- ## Return Type: Type and description of the return value\n" + "- ## Examples: Code examples demonstrating how to use the function (enclosed using
 and  Tags`).\n\n"
                 "The response should be a valid JSON list of objects formatted as follows: "
                 "{function_name: , path: , documentation: }.\n"
                 "Ensure the response is in valid JSON format only, enclosed in triple backticks, and does not include `---`."
@@ -77,8 +77,6 @@ def generate_docs_for_chunk(api_chunk):
         user_message = f"function name: {api['function_name']}, path: {api['path']}, code:\n{api['code']}"
         messages.append({"role": "user", "content": user_message})
 
-    # print("Raw Response:\n", response_text)  # Log raw response for debugging
-
     response_text = open_ai_call(messages)
 
     cleaned_response = clean_response(response_text)
@@ -103,7 +101,7 @@ def generate_docs_for_chunk(api_chunk):
             return json.loads(cleaned_response, strict=False)
         except json.JSONDecodeError as e:
             print("Second JSON Decode Error:", e)
-            return []
+            return generate_docs_for_chunk(api_chunk)
 
     # return cleaned_response
 
diff --git a/dashboard/src/components/features/api_viewer/APIDetails.tsx b/dashboard/src/components/features/api_viewer/APIDetails.tsx
index cf7651f..89dec49 100644
--- a/dashboard/src/components/features/api_viewer/APIDetails.tsx
+++ b/dashboard/src/components/features/api_viewer/APIDetails.tsx
@@ -8,21 +8,14 @@ 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, useState } from "react"
+import { useMemo } 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"
+import { APIDocumentationOfSiteApp } from "../documentation/APIDocumentation"
 
 export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, setSelectedEndpoint, viewerType }: { project_branch: string, endpointData: APIData[], selectedEndpoint: string, setSelectedEndpoint: React.Dispatch>, viewerType: string }) => {
-
     const data = useMemo(() => {
         return endpointData.find((endpoint: APIData) => endpoint.name === selectedEndpoint)
     }, [endpointData, selectedEndpoint])
-
     const requestTypeBgColor = (requestType: string) => {
         switch (requestType) {
             case 'GET':
@@ -37,7 +30,6 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
                 return 'bg-gray-100'
         }
     }
-
     const requestTypeBorderColor = (requestType: string) => {
         switch (requestType) {
             case 'GET':
@@ -52,14 +44,11 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
                 return 'ring-gray-600/20'
         }
     }
-
-    const [apiOpen, setApiOpen] = useState(false)
-
     return (
         
-

{data?.name}

+

API Details

{data?.allow_guest || data?.xss_safe ?
{data?.allow_guest && Allow Guest @@ -84,32 +73,15 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
-
+
+
Name :
+
{data?.name}
+
+
Endpoint :
-
-
-
- {data?.api_path} -
- -
-
- {viewerType === 'app' && - - - - - - Click to make an API call to this endpoint - - - } -
+
+
{data?.api_path}
+
@@ -130,14 +102,12 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
- Parameters Code Bruno - {data?.documentation && Documentation} - {viewerType === 'app' && Documentation} + Documentation @@ -148,23 +118,14 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set - {data?.documentation && - - } - {viewerType === 'app' && - - } + + + - - -
) } - - export const ParametersTable = ({ parameters }: { parameters?: Argument[] }) => { - return ( A list of parameters that can be used in the API @@ -190,10 +151,7 @@ export const ParametersTable = ({ parameters }: { parameters?: Argument[] }) =>
) } - - export const CodeSnippet = ({ apiData, project_branch, file_path, viewerType }: { apiData: APIData, project_branch: string, file_path: string, viewerType: string }) => { - const { data, error, isLoading } = useFrappeGetCall<{ message: { file_content: string } }>('commit.api.api_explorer.get_file_content_from_path', { project_branch: project_branch, file_path: file_path, @@ -207,7 +165,6 @@ export const CodeSnippet = ({ apiData, project_branch, file_path, viewerType }: const copyValue = () => { const content = JSON.parse(JSON.stringify(data?.message?.file_content ?? []) ?? '[]') return content?.join('') - } return (
@@ -215,7 +172,7 @@ export const CodeSnippet = ({ apiData, project_branch, file_path, viewerType }: {isLoading &&
} - +
@@ -227,17 +184,13 @@ export const CodeSnippet = ({ apiData, project_branch, file_path, viewerType }:
) } - - export const Bruno = ({ doc }: { doc: APIData }) => { - const rest = useMemo(() => { if (doc) { const { allow_guest, xss_safe, documentation, block_end, block_start, index, ...rest } = doc return rest } }, [doc]) - const { data, error, isLoading } = useFrappeGetCall('commit.api.bruno.generate_bruno_file', { data: JSON.stringify(rest), type: 'copy' @@ -245,20 +198,17 @@ export const Bruno = ({ doc }: { doc: APIData }) => { revalidateOnFocus: false, revalidateIfStale: false, }) - const copyValue = () => { const content = JSON.parse(JSON.stringify(data ?? '') ?? '[]') return content - } - return (
{error && } {isLoading &&
} - +
- - - - - - - - - - - - Click to view available bench commands in this app - - - + + + + + +
@@ -93,13 +83,13 @@ export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, s
{/* fixed height container */}
- +
) } -export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint }: { list: APIData[], setSelectedEndpoint: (endpoint: string) => void, selectedEndpoint?: string }) => { +export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint, searchQuery }: { list: APIData[], setSelectedEndpoint: (endpoint: string) => void, selectedEndpoint?: string, searchQuery?: string }) => { return (
    @@ -138,8 +128,8 @@ export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint }: { list
{/* create a div which is at fixed location and should be stick bottom which will show total list count at right corner of same w as above ul*/} {list.length &&
-

Total {list.length} API's

+

{list.length} API's {searchQuery ? "found" : ''}

}
) -} +} \ No newline at end of file diff --git a/dashboard/src/components/features/documentation/APIDocumentation.tsx b/dashboard/src/components/features/documentation/APIDocumentation.tsx index 4e0bd2b..e6c612c 100644 --- a/dashboard/src/components/features/documentation/APIDocumentation.tsx +++ b/dashboard/src/components/features/documentation/APIDocumentation.tsx @@ -9,6 +9,7 @@ import { MdOutlineRocketLaunch } from "react-icons/md"; import Markdown from "react-markdown"; import MDEditor from '@uiw/react-md-editor'; import { FiEdit, FiSave } from "react-icons/fi"; +import { isSystemManager } from "@/utils/roles"; export const Documentation = ({ documentation }: { documentation: string }) => { @@ -35,13 +36,24 @@ export interface DocumentationResponse { path: string, documentation: string } -export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, endPoint }: { apiData: APIData, project_branch: string, file_path: string, endPoint: string }) => { +export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, endPoint, viewerType }: { apiData: APIData, project_branch: string, file_path: string, endPoint: string, viewerType: string }) => { + + const renderContent = () => { + // return string by type checking + if (typeof apiData?.documentation === 'string') { + return apiData.documentation + } else if (typeof apiData?.documentation === 'object' && apiData?.documentation !== null && !Array.isArray(apiData?.documentation)) { + return JSON.stringify(apiData?.documentation, null, 2) + } else { + return '' + } + } const { call, error, loading } = useFrappePostCall('commit.api.generate_documentation.get_documentation_for_api') const [edit, setEdit] = useState(false) - const [documentation, setDocumentation] = useState() + const [documentation, setDocumentation] = useState(renderContent()) const generateDocumentation = () => { call({ @@ -49,21 +61,16 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, file_path: file_path, block_start: apiData.block_start ?? 0, block_end: apiData.block_end ?? 0, - endpoint: endPoint + endpoint: endPoint, + viewer_type: viewerType }).then((res) => { - setDocumentation(res.message) + setDocumentation(res.message?.documentation ?? '') setEdit(true) }) } const onDocumentationChange = (value: string) => { - setDocumentation((documentation) => { - return { - function_name: documentation?.function_name ?? endPoint?.split('.').pop() ?? '', - path: documentation?.path ?? endPoint, - documentation: value - } - }) + setDocumentation(value) } const previewMode = useMemo(() => { @@ -79,11 +86,13 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, } } + const isCreateAccess = isSystemManager(); + return (
{error && }
-
+ {isCreateAccess &&
@@ -109,9 +118,9 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, -
+
} onDocumentationChange(value ?? '')} style={{ minHeight: 'calc(100vh - 24rem)', overflowY: 'auto', margin: 8, padding: 4 }} From 16adedb68e54bcdae3147729a56a049c26683fb5 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Fri, 27 Sep 2024 18:08:00 +0530 Subject: [PATCH 2/9] refactor:added last updated timestamp to each documentation --- commit/api/api_explorer.py | 1 + commit/api/generate_documentation.py | 10 ++++---- .../features/api_viewer/APIDetails.tsx | 2 +- .../documentation/APIDocumentation.tsx | 23 +++++++++++++++---- dashboard/src/types/APIData.ts | 1 + 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/commit/api/api_explorer.py b/commit/api/api_explorer.py index ef457cb..dc4001d 100644 --- a/commit/api/api_explorer.py +++ b/commit/api/api_explorer.py @@ -18,6 +18,7 @@ def get_apis_for_project(project_branch: str): for doc in documentation: if doc.get("function_name") == api.get("name") and doc.get("path") == api.get("api_path"): api["documentation"] = doc.get("documentation") + api["last_updated"] = doc.get("last_updated") break app_name, organization, app_logo = frappe.db.get_value("Commit Project", branch_doc.project, ["app_name", "org", "image"]) diff --git a/commit/api/generate_documentation.py b/commit/api/generate_documentation.py index 2bef1de..3e0e68f 100644 --- a/commit/api/generate_documentation.py +++ b/commit/api/generate_documentation.py @@ -67,14 +67,14 @@ def generate_docs_for_chunk(api_chunk): "- ## Return Type: Type and description of the return value\n" "- ## Examples: Code examples demonstrating how to use the function (enclosed using
 and  Tags`).\n\n"
                 "The response should be a valid JSON list of objects formatted as follows: "
-                "{function_name: , path: , documentation: }.\n"
+                "{function_name: , path: , last_updated:, documentation: }.\n"
                 "Ensure the response is in valid JSON format only, enclosed in triple backticks, and does not include `---`."
             )
         }
     ]
-
+    last_updated = frappe.utils.now()
     for api in api_chunk:
-        user_message = f"function name: {api['function_name']}, path: {api['path']}, code:\n{api['code']}"
+        user_message = f"function name: {api['function_name']}, path: {api['path']}, last_updated:{last_updated} ,code:\n{api['code']}"
         messages.append({"role": "user", "content": user_message})
 
     response_text = open_ai_call(messages)
@@ -118,13 +118,13 @@ def generate_documentation_for_api_snippet(api_path:str,code_snippet:str):
                 "- ## Return Type\n Specify the type and description of the return value.\n"
                 "- ## Examples\n Provide code examples demonstrating how to use the function, enclosed in triple backticks (``````).\n\n"
                 "The response should be a valid JSON formatted as follows: "
-                "{function_name: , path: , documentation: }.\n"
+                "{function_name: , path: , last_updated:, documentation: }.\n"
                 "Ensure the response is in valid JSON format only, and does not include `---`."
             )
         }
     ]
     
-    user_message = f"api path: {api_path}, code:\n{code_snippet}"
+    user_message = f"api path: {api_path}, last_updated:{frappe.utils.now()}, code:\n{code_snippet}"
     if not code_snippet:
         return []
     messages.append({"role": "user", "content": user_message})
diff --git a/dashboard/src/components/features/api_viewer/APIDetails.tsx b/dashboard/src/components/features/api_viewer/APIDetails.tsx
index 89dec49..46ab477 100644
--- a/dashboard/src/components/features/api_viewer/APIDetails.tsx
+++ b/dashboard/src/components/features/api_viewer/APIDetails.tsx
@@ -79,7 +79,7 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set
                         
Endpoint :
-
+
{data?.api_path}
diff --git a/dashboard/src/components/features/documentation/APIDocumentation.tsx b/dashboard/src/components/features/documentation/APIDocumentation.tsx index e6c612c..8d1bbd9 100644 --- a/dashboard/src/components/features/documentation/APIDocumentation.tsx +++ b/dashboard/src/components/features/documentation/APIDocumentation.tsx @@ -10,6 +10,8 @@ import Markdown from "react-markdown"; import MDEditor from '@uiw/react-md-editor'; import { FiEdit, FiSave } from "react-icons/fi"; import { isSystemManager } from "@/utils/roles"; +import { IoMdClose } from "react-icons/io"; +import { convertFrappeTimestampToTimeAgo } from "@/components/utils/dateconversion"; export const Documentation = ({ documentation }: { documentation: string }) => { @@ -92,8 +94,10 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path,
{error && }
- {isCreateAccess &&
- +
+
Last Docs Updated - {convertFrappeTimestampToTimeAgo(apiData?.last_updated)}
+ {isCreateAccess &&
+ {!edit && } @@ -113,17 +120,23 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, {edit ? : } - + {edit ? 'Save Documentation' : 'Edit Documentation'}
} +
onDocumentationChange(value ?? '')} - style={{ minHeight: 'calc(100vh - 24rem)', overflowY: 'auto', margin: 8, padding: 4 }} + style={{ + minHeight: (apiData?.last_updated || isCreateAccess) ? 'calc(100vh - 24rem)' : 'calc(100vh - 21rem)', + overflowY: 'auto', margin: 8, padding: 4 + }} />
diff --git a/dashboard/src/types/APIData.ts b/dashboard/src/types/APIData.ts index e982dcb..3fac31a 100644 --- a/dashboard/src/types/APIData.ts +++ b/dashboard/src/types/APIData.ts @@ -13,6 +13,7 @@ export interface APIData { block_start: number block_end: number documentation?: string + last_updated: string } export interface Argument { From f70a6d94afcf0de245be3cf515c1e42b35381c47 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Fri, 27 Sep 2024 18:08:14 +0530 Subject: [PATCH 3/9] refactor: Update APIViewer and AppAPIViewer components - Add useEffect and useRef hooks to handle URL query parameters - Update selected endpoint based on URL query parameter - Update URL search params when selected endpoint changes - Adjust width of APIList and APIDetails components based on selected endpoint --- .../features/api_viewer/APIList.tsx | 47 +++++++++++++--- .../pages/features/api_viewer/APIViewer.tsx | 55 +++++++++++++++---- .../features/api_viewer/AppAPIViewer.tsx | 38 +++++++++++-- 3 files changed, 115 insertions(+), 25 deletions(-) diff --git a/dashboard/src/components/features/api_viewer/APIList.tsx b/dashboard/src/components/features/api_viewer/APIList.tsx index 019c9be..fefa419 100644 --- a/dashboard/src/components/features/api_viewer/APIList.tsx +++ b/dashboard/src/components/features/api_viewer/APIList.tsx @@ -3,7 +3,7 @@ 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 { useEffect, useMemo, useRef, useState } from "react" import { AiOutlineBranches } from "react-icons/ai" import { GoPackage } from "react-icons/go" import { CommandContent } from "../commands/CommandsContent" @@ -16,9 +16,10 @@ export interface APIListProps { setSelectedEndpoint: (endpoint: string) => void selectedEndpoint?: string path_to_folder: string + listRef?: React.RefObject } -export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, selectedEndpoint, path_to_folder }: APIListProps) => { +export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, selectedEndpoint, path_to_folder, listRef }: APIListProps) => { const [searchQuery, setSearchQuery] = useState('') const [requestTypeFilter, setRequestTypeFilter] = useState('All') @@ -33,7 +34,18 @@ export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, s }, [searchQuery, apiList, requestTypeFilter]) useEffect(() => { - setSelectedEndpoint(filterList[0]?.name ?? '') + const searchParams = new URLSearchParams(window.location.search) + const endpointFromURL = searchParams.get('api') + if (endpointFromURL) { + if (filterList.map((api) => api.name).includes(endpointFromURL)) { + setSelectedEndpoint(endpointFromURL) + } else { + setSelectedEndpoint(filterList[0]?.name ?? '') + } + } + else { + setSelectedEndpoint(filterList[0]?.name ?? '') + } }, [filterList, setSelectedEndpoint]) return ( @@ -83,16 +95,28 @@ export const APIList = ({ apiList, app_name, branch_name, setSelectedEndpoint, s
{/* fixed height container */}
- +
) } -export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint, searchQuery }: { list: APIData[], setSelectedEndpoint: (endpoint: string) => void, selectedEndpoint?: string, searchQuery?: string }) => { +export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint, searchQuery, listRef }: { list: APIData[], setSelectedEndpoint: (endpoint: string) => void, selectedEndpoint?: string, searchQuery?: string, listRef?: React.RefObject }) => { + + const itemRefs = useRef<(HTMLLIElement | null)[]>([]); + + useEffect(() => { + if (listRef?.current && selectedEndpoint) { + const selectedElement = itemRefs?.current?.find(item => item?.dataset.endpoint === selectedEndpoint); + if (selectedElement) { + selectedElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + }, []); + return ( -
-
    +
    +
      {list.length === 0 && (

      Sorry we couldn't find what you were looking for.

      @@ -100,7 +124,12 @@ export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint, searchQu
      )} {list.map((person: APIData, index: number) => ( -
    • setSelectedEndpoint(person.name)}> +
    • { + if (el) { + el.dataset.endpoint = person.name; + itemRefs.current[index] = el; + } + }} className={`flex justify-between gap-x-6 p-2 hover:bg-gray-100 cursor-pointer group ${selectedEndpoint === person.name ? 'bg-gray-100' : ''} `} onClick={() => setSelectedEndpoint(person.name)}>

      {person.name}

      @@ -127,7 +156,7 @@ export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint, searchQu ))}
    {/* create a div which is at fixed location and should be stick bottom which will show total list count at right corner of same w as above ul*/} - {list.length &&
    + {list.length &&

    {list.length} API's {searchQuery ? "found" : ''}

    }
    diff --git a/dashboard/src/pages/features/api_viewer/APIViewer.tsx b/dashboard/src/pages/features/api_viewer/APIViewer.tsx index a7a7bd5..65f78d1 100644 --- a/dashboard/src/pages/features/api_viewer/APIViewer.tsx +++ b/dashboard/src/pages/features/api_viewer/APIViewer.tsx @@ -1,9 +1,9 @@ import { APIDetails } from "@/components/features/api_viewer/APIDetails" import { APIList } from "@/components/features/api_viewer/APIList" -import { useState } from "react" +import { useEffect, useRef, useState } from "react" import { useFrappeGetCall } from "frappe-react-sdk" import { APIData } from "@/types/APIData" -import { useParams } from "react-router-dom" +import { useNavigate, useParams } from "react-router-dom" import { Header } from "@/components/common/Header" import { FullPageLoader } from "@/components/common/FullPageLoader/FullPageLoader" import { ErrorBanner } from "@/components/common/ErrorBanner/ErrorBanner" @@ -33,14 +33,46 @@ export const APIViewerContainer = () => { export const APIViewer = ({ projectBranch }: { projectBranch: string }) => { const [selectedendpoint, setSelectedEndpoint] = useState('') + const navigate = useNavigate() - const { data, isLoading, error } = useFrappeGetCall<{ message: GetAPIResponse }>('commit.api.api_explorer.get_apis_for_project', { - project_branch: projectBranch - }, undefined, { - revalidateOnFocus: false, - revalidateIfStale: false, - onSuccess: (d: { message: GetAPIResponse }) => setSelectedEndpoint(d.message.apis[0].name) - }) + const listRef = useRef(null); + + // Fetch the query parameters from the URL + useEffect(() => { + const searchParams = new URLSearchParams(window.location.search) + const endpointFromURL = searchParams.get('api') + + // Set selected endpoint from URL if available + if (endpointFromURL) { + setSelectedEndpoint(endpointFromURL) + } + }, []) + + // Update the URL search params when selectedEndpoint changes + useEffect(() => { + if (selectedendpoint) { + const searchParams = new URLSearchParams(window.location.search) + searchParams.set('api', selectedendpoint) + navigate({ search: searchParams.toString() }, { replace: true }) + } + }, [selectedendpoint, navigate]) + + const { data, isLoading, error } = useFrappeGetCall<{ message: GetAPIResponse }>( + 'commit.api.api_explorer.get_apis_for_project', + { + project_branch: projectBranch + }, + undefined, + { + revalidateOnFocus: false, + revalidateIfStale: false, + onSuccess: (d: { message: GetAPIResponse }) => { + if (!selectedendpoint) { + setSelectedEndpoint(d.message.apis[0].name) + } + } + } + ) if (isLoading) { return @@ -51,7 +83,7 @@ export const APIViewer = ({ projectBranch }: { projectBranch: string }) => {
    {error && } {data &&
    -
    +
    { setSelectedEndpoint={setSelectedEndpoint} selectedEndpoint={selectedendpoint} path_to_folder={data?.message.path_to_folder} + listRef={listRef} />
    {selectedendpoint && (
    { export const AppAPIViewer = ({ appName }: { appName: string }) => { const [selectedendpoint, setSelectedEndpoint] = useState('') + const navigate = useNavigate() + + const listRef = useRef(null); + + // Fetch the query parameters from the URL + useEffect(() => { + const searchParams = new URLSearchParams(window.location.search) + const endpointFromURL = searchParams.get('api') + + // Set selected endpoint from URL if available + if (endpointFromURL) { + setSelectedEndpoint(endpointFromURL) + } + }, []) + + // Update the URL search params when selectedEndpoint changes + useEffect(() => { + if (selectedendpoint) { + const searchParams = new URLSearchParams(window.location.search) + searchParams.set('api', selectedendpoint) + navigate({ search: searchParams.toString() }, { replace: true }) + } + }, [selectedendpoint, navigate]) const { data, isLoading, error } = useFrappeGetCall<{ message: GetAPIResponse }>('commit.api.meta_data.get_apis_for_app', { app_name: appName }, undefined, { revalidateIfStale: false, revalidateOnFocus: false, - onSuccess: (d: { message: GetAPIResponse }) => setSelectedEndpoint(d.message.apis?.[0]?.name) + onSuccess: (d: { message: GetAPIResponse }) => { + if (!selectedendpoint) { + setSelectedEndpoint(d.message.apis[0].name) + } + } }) if (isLoading) { @@ -44,7 +71,7 @@ export const AppAPIViewer = ({ appName }: { appName: string }) => {
    {error && } {data &&
    -
    +
    { setSelectedEndpoint={setSelectedEndpoint} selectedEndpoint={selectedendpoint} path_to_folder="" + listRef={listRef} />
    @@ -59,7 +87,7 @@ export const AppAPIViewer = ({ appName }: { appName: string }) => { {selectedendpoint && (
    From d374b4d6ce6aa09cadfead32dbadac6589065946 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Fri, 27 Sep 2024 19:01:42 +0530 Subject: [PATCH 4/9] fix:position from sticky to fixed --- dashboard/src/pages/features/erd/ERDViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/src/pages/features/erd/ERDViewer.tsx b/dashboard/src/pages/features/erd/ERDViewer.tsx index 000c5b6..9f4c542 100644 --- a/dashboard/src/pages/features/erd/ERDViewer.tsx +++ b/dashboard/src/pages/features/erd/ERDViewer.tsx @@ -178,7 +178,7 @@ export const ModuleDoctypeListDrawer = ({ open, setOpen, apps, setSelectedApps,
    -
    +
    From f2b332b7253ccfff36208ca14793d4a8ff7a5e26 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Fri, 27 Sep 2024 19:01:51 +0530 Subject: [PATCH 5/9] refactor: Simplify APIDocumentation component and extract AllButton component --- .../documentation/APIDocumentation.tsx | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/dashboard/src/components/features/documentation/APIDocumentation.tsx b/dashboard/src/components/features/documentation/APIDocumentation.tsx index 8d1bbd9..3da8d0d 100644 --- a/dashboard/src/components/features/documentation/APIDocumentation.tsx +++ b/dashboard/src/components/features/documentation/APIDocumentation.tsx @@ -94,39 +94,12 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path,
    {error && }
    -
    -
    Last Docs Updated - {convertFrappeTimestampToTimeAgo(apiData?.last_updated)}
    - {isCreateAccess &&
    - {!edit && - - - - - - Generate Documentation for this API - - - } - {edit && } - - - - - - - {edit ? 'Save Documentation' : 'Edit Documentation'} - - - -
    } -
    + {apiData?.last_updated ?
    +
    + Last Docs Updated - {convertFrappeTimestampToTimeAgo(apiData?.last_updated)} +
    + {isCreateAccess && } +
    : (isCreateAccess && )}
    ) +} + +export const AllButton = ({ generateDocumentation, loading, edit, setEdit, SaveEditButton }: { generateDocumentation: () => void, loading: boolean, edit: boolean, setEdit: (value: boolean) => void, SaveEditButton: () => void }) => { + + return ( +
    +
    + {!edit && + + + + + + Generate Documentation for this API + + + } + {edit && } + + + + + + + {edit ? 'Save Documentation' : 'Edit Documentation'} + + + +
    +
    + ) } \ No newline at end of file From 446c364a9adb0a759cb3dd136327fee8eb210453 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Sun, 29 Sep 2024 13:03:18 +0530 Subject: [PATCH 6/9] feat: Add Commit Branch Documentation doctype and related files --- .../commit_branch_documentation/__init__.py | 0 .../commit_branch_documentation.js | 8 +++ .../commit_branch_documentation.json | 52 +++++++++++++++++++ .../commit_branch_documentation.py | 9 ++++ .../test_commit_branch_documentation.py | 9 ++++ 5 files changed, 78 insertions(+) create mode 100644 commit/commit/doctype/commit_branch_documentation/__init__.py create mode 100644 commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.js create mode 100644 commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.json create mode 100644 commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.py create mode 100644 commit/commit/doctype/commit_branch_documentation/test_commit_branch_documentation.py diff --git a/commit/commit/doctype/commit_branch_documentation/__init__.py b/commit/commit/doctype/commit_branch_documentation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.js b/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.js new file mode 100644 index 0000000..79e7e2d --- /dev/null +++ b/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, The Commit Company and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Commit Branch Documentation", { +// refresh(frm) { + +// }, +// }); diff --git a/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.json b/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.json new file mode 100644 index 0000000..a09a825 --- /dev/null +++ b/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:app", + "creation": "2024-09-29 12:24:08.997955", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "app", + "documentation" + ], + "fields": [ + { + "fieldname": "app", + "fieldtype": "Data", + "in_list_view": 1, + "label": "App", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "documentation", + "fieldtype": "JSON", + "label": "Documentation" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-09-29 12:29:40.251915", + "modified_by": "Administrator", + "module": "commit", + "name": "Commit Branch Documentation", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.py b/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.py new file mode 100644 index 0000000..c84c245 --- /dev/null +++ b/commit/commit/doctype/commit_branch_documentation/commit_branch_documentation.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, The Commit Company and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CommitBranchDocumentation(Document): + pass diff --git a/commit/commit/doctype/commit_branch_documentation/test_commit_branch_documentation.py b/commit/commit/doctype/commit_branch_documentation/test_commit_branch_documentation.py new file mode 100644 index 0000000..cc8937a --- /dev/null +++ b/commit/commit/doctype/commit_branch_documentation/test_commit_branch_documentation.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, The Commit Company and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestCommitBranchDocumentation(FrappeTestCase): + pass From b2958acd813b0042de301da8362818a123e1c3b3 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Sun, 29 Sep 2024 13:04:19 +0530 Subject: [PATCH 7/9] chore: Update SpinnerLoader component to accept style prop and pass mutate to APIDetails --- .../src/components/common/FullPageLoader/SpinnerLoader.tsx | 7 +++++-- .../src/components/features/api_viewer/APIDetails.tsx | 4 ++-- dashboard/src/pages/features/api_viewer/APIViewer.tsx | 3 ++- dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/dashboard/src/components/common/FullPageLoader/SpinnerLoader.tsx b/dashboard/src/components/common/FullPageLoader/SpinnerLoader.tsx index f5088f3..56f000e 100644 --- a/dashboard/src/components/common/FullPageLoader/SpinnerLoader.tsx +++ b/dashboard/src/components/common/FullPageLoader/SpinnerLoader.tsx @@ -45,14 +45,17 @@ export const AsyncSpinnerLoader: React.FC = ({ export interface SpinnerLoaderProps { className?: string; + style?: React.CSSProperties; } -export const SpinnerLoader = ({ className }: SpinnerLoaderProps) => { +export const SpinnerLoader = ({ className, style }: SpinnerLoaderProps) => { return (
    + role="status" + style={style} + >
    ) } \ 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 46ab477..799f6cf 100644 --- a/dashboard/src/components/features/api_viewer/APIDetails.tsx +++ b/dashboard/src/components/features/api_viewer/APIDetails.tsx @@ -12,7 +12,7 @@ import { useMemo } from "react" import { MdOutlineFileDownload } from "react-icons/md" import { APIDocumentationOfSiteApp } from "../documentation/APIDocumentation" -export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, setSelectedEndpoint, viewerType }: { project_branch: string, endpointData: APIData[], selectedEndpoint: string, setSelectedEndpoint: React.Dispatch>, viewerType: string }) => { +export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, setSelectedEndpoint, viewerType, mutate }: { project_branch: string, endpointData: APIData[], selectedEndpoint: string, setSelectedEndpoint: React.Dispatch>, viewerType: string, mutate: () => void }) => { const data = useMemo(() => { return endpointData.find((endpoint: APIData) => endpoint.name === selectedEndpoint) }, [endpointData, selectedEndpoint]) @@ -119,7 +119,7 @@ export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, set - +
    diff --git a/dashboard/src/pages/features/api_viewer/APIViewer.tsx b/dashboard/src/pages/features/api_viewer/APIViewer.tsx index 65f78d1..288c462 100644 --- a/dashboard/src/pages/features/api_viewer/APIViewer.tsx +++ b/dashboard/src/pages/features/api_viewer/APIViewer.tsx @@ -57,7 +57,7 @@ export const APIViewer = ({ projectBranch }: { projectBranch: string }) => { } }, [selectedendpoint, navigate]) - const { data, isLoading, error } = useFrappeGetCall<{ message: GetAPIResponse }>( + const { data, isLoading, error, mutate } = useFrappeGetCall<{ message: GetAPIResponse }>( 'commit.api.api_explorer.get_apis_for_project', { project_branch: projectBranch @@ -106,6 +106,7 @@ export const APIViewer = ({ projectBranch }: { projectBranch: string }) => { selectedEndpoint={selectedendpoint} setSelectedEndpoint={setSelectedEndpoint} viewerType="project" + mutate={mutate} />
    )} diff --git a/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx b/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx index 127d4e2..e11bd33 100644 --- a/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx +++ b/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx @@ -49,7 +49,7 @@ export const AppAPIViewer = ({ appName }: { appName: string }) => { } }, [selectedendpoint, navigate]) - const { data, isLoading, error } = useFrappeGetCall<{ message: GetAPIResponse }>('commit.api.meta_data.get_apis_for_app', { + const { data, isLoading, error, mutate } = useFrappeGetCall<{ message: GetAPIResponse }>('commit.api.meta_data.get_apis_for_app', { app_name: appName }, undefined, { revalidateIfStale: false, @@ -89,7 +89,7 @@ export const AppAPIViewer = ({ appName }: { appName: string }) => { className={`fixed z-10 right-0 w-[80vw] h-full bg-white shadow-lg transition-transform transform ${selectedendpoint ? 'translate-x-0' : 'translate-x-full' } md:relative md:translate-x-0 sm:w-[55%]`} > - +
    )}
    } From 068e488e20b7b7ab52c71f36d55e66bb086ac88d Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Sun, 29 Sep 2024 13:07:37 +0530 Subject: [PATCH 8/9] feat:save documentation for project_branch or site_app --- commit/api/generate_documentation.py | 82 ++++++++++++++++++- .../documentation/APIDocumentation.tsx | 55 ++++++------- 2 files changed, 105 insertions(+), 32 deletions(-) diff --git a/commit/api/generate_documentation.py b/commit/api/generate_documentation.py index 3e0e68f..a11d08d 100644 --- a/commit/api/generate_documentation.py +++ b/commit/api/generate_documentation.py @@ -159,4 +159,84 @@ def generate_documentation_for_api_snippet(api_path:str,code_snippet:str): def get_documentation_for_api(project_branch: str, file_path: str,block_start: int, block_end: int,endpoint:str,viewer_type:str = 'app'): code_snippet = get_file_content_from_path(project_branch, file_path,block_start, block_end,viewer_type) api_path = endpoint - return generate_documentation_for_api_snippet(api_path, code_snippet) \ No newline at end of file + return generate_documentation_for_api_snippet(api_path, code_snippet) + +@frappe.whitelist() +def save_documentation(project_branch:str,endpoint:str,documentation:str,viewer_type:str = 'app'): + # Save the documentation to the project branch + # 1. Check for viewer_type app or project + # 2. If viewer_type is app, then check the document is already present in Commit Branch Documentation doctype + # 3. If document present then loop over documentation check if the function_name and path matches then update the documentation else create a new dict and append to the documentation + # 4. If document not present then create a new document and append the documentation + # 5. If viewer_type is project then check the document is already present in Commit Project Branch doctype + # 6. If document present then loop over documentation check if the function_name and path matches then update the documentation else create a new dict and append to the documentation + # 7. If document not present then create a new document and append the documentation + + if viewer_type == "app": + # Check if the document is already present in Commit Branch Documentation doctype + save_documentation_for_site_app(project_branch, endpoint, documentation) + else: + save_documentation_for_project_branch(project_branch, endpoint, documentation) + +def save_documentation_for_project_branch(project_branch:str,endpoint:str,documentation:str): + + doc = frappe.get_doc("Commit Project Branch", project_branch) + docs = json.loads(doc.documentation) if doc.documentation else {} + apis = docs.get("apis", []) + + # apis is list of dict with keys function_name, path, last_updated, documentation + # loop over apis and check if function_name and path matches then update the documentation else create a new dict and append to the documentation + found = False + for api in apis: + if api.get("function_name") == endpoint.split(".")[-1] and api.get("path") == endpoint: + api["documentation"] = documentation + api["last_updated"] = frappe.utils.now() + found = True + break + if not found: + apis.append({ + "function_name": endpoint.split(".")[-1], + "path": endpoint, + "last_updated": frappe.utils.now(), + "documentation": documentation + }) + + doc.documentation = json.dumps({"apis": apis}) + doc.save() + +def save_documentation_for_site_app(project_branch:str,endpoint:str,documentation:str): + + if frappe.db.exists("Commit Branch Documentation",project_branch): + doc = frappe.get_doc("Commit Branch Documentation", project_branch) + docs = json.loads(doc.documentation) if doc.documentation else {} + apis = docs.get("apis", []) + + # apis is list of dict with keys function_name, path, last_updated, documentation + # loop over apis and check if function_name and path matches then update the documentation else create a new dict and append to the documentation + found = False + for api in apis: + if api.get("function_name") == endpoint.split(".")[-1] and api.get("path") == endpoint: + api["documentation"] = documentation + api["last_updated"] = frappe.utils.now() + found = True + break + if not found: + apis.append({ + "function_name": endpoint.split(".")[-1], + "path": endpoint, + "last_updated": frappe.utils.now(), + "documentation": documentation + }) + doc.documentation = json.dumps({"apis": apis}) + doc.save() + else: + # Create a new document and append the documentation + doc = frappe.new_doc("Commit Branch Documentation") + doc.app = project_branch + doc.documentation = json.dumps({"apis": [{ + "function_name": endpoint.split(".")[-1], + "path": endpoint, + "last_updated": frappe.utils.now(), + "documentation": documentation + }]}) + doc.save() \ No newline at end of file diff --git a/dashboard/src/components/features/documentation/APIDocumentation.tsx b/dashboard/src/components/features/documentation/APIDocumentation.tsx index 3da8d0d..cb4ee62 100644 --- a/dashboard/src/components/features/documentation/APIDocumentation.tsx +++ b/dashboard/src/components/features/documentation/APIDocumentation.tsx @@ -4,41 +4,20 @@ import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { APIData } from "@/types/APIData"; import { useFrappePostCall } from "frappe-react-sdk"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { MdOutlineRocketLaunch } from "react-icons/md"; -import Markdown from "react-markdown"; import MDEditor from '@uiw/react-md-editor'; import { FiEdit, FiSave } from "react-icons/fi"; import { isSystemManager } from "@/utils/roles"; import { IoMdClose } from "react-icons/io"; import { convertFrappeTimestampToTimeAgo } from "@/components/utils/dateconversion"; - -export const Documentation = ({ documentation }: { documentation: string }) => { - - const renderContent = () => { - if (typeof documentation === 'string') { - return {documentation}; - } else if (typeof documentation === 'object' && documentation !== null && !Array.isArray(documentation)) { - return
    {JSON.stringify(documentation, null, 2)}
    ; - } else { - return
    Invalid documentation format
    ; - } - }; - - return ( -
    - {renderContent()} -
    - ) -} - export interface DocumentationResponse { function_name: string, path: string, documentation: string } -export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, endPoint, viewerType }: { apiData: APIData, project_branch: string, file_path: string, endPoint: string, viewerType: string }) => { +export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, endPoint, viewerType, mutate }: { apiData: APIData, project_branch: string, file_path: string, endPoint: string, viewerType: string, mutate: () => void }) => { const renderContent = () => { // return string by type checking @@ -79,14 +58,25 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path, return edit ? 'live' : 'preview' }, [edit]) - const SaveEditButton = () => { + const { call: saveCall } = useFrappePostCall('commit.api.generate_documentation.save_documentation') + + const SaveEditButton = useCallback(() => { if (edit) { // code for saving the documentation + saveCall({ + project_branch: project_branch, + endpoint: endPoint, + documentation: documentation ?? '', + viewer_type: viewerType + }).then(() => { + mutate() + setEdit(false) + }) } else { setEdit(true) } - } + }, [edit, documentation, project_branch, endPoint, viewerType]) const isCreateAccess = isSystemManager(); @@ -94,12 +84,12 @@ export const APIDocumentationOfSiteApp = ({ apiData, project_branch, file_path,
    {error && }
    - {apiData?.last_updated ?
    + {apiData?.last_updated ?
    Last Docs Updated - {convertFrappeTimestampToTimeAgo(apiData?.last_updated)}
    - {isCreateAccess && } -
    : (isCreateAccess && )} + {isCreateAccess && } +
    : (isCreateAccess && )} void, loading: boolean, edit: boolean, setEdit: (value: boolean) => void, SaveEditButton: () => void }) => { +export const AllButton = ({ generateDocumentation, loading, edit, setEdit, SaveEditButton, renderContent, setDocumentation }: { generateDocumentation: () => void, loading: boolean, edit: boolean, setEdit: (value: boolean) => void, SaveEditButton: () => void, renderContent: () => string, setDocumentation: (value: string | undefined) => void }) => { return (
    @@ -125,7 +115,7 @@ export const AllButton = ({ generateDocumentation, loading, edit, setEdit, SaveE @@ -134,7 +124,10 @@ export const AllButton = ({ generateDocumentation, loading, edit, setEdit, SaveE } - {edit && } From c98a8f0a5588436a7245f651010ba81d07aa48bf Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Sun, 29 Sep 2024 13:08:07 +0530 Subject: [PATCH 9/9] feat:get saved documentation for site app is exists --- commit/commit/code_analysis/apis.py | 36 +++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/commit/commit/code_analysis/apis.py b/commit/commit/code_analysis/apis.py index d8d44f5..2c1d3bd 100644 --- a/commit/commit/code_analysis/apis.py +++ b/commit/commit/code_analysis/apis.py @@ -1,5 +1,7 @@ import os import ast +import frappe +import json other_decorators = [ '@cache_source', @@ -33,7 +35,7 @@ def find_all_occurrences_of_whitelist(path: str, app_name: str): # if file.endswith('party.py'): indexes,line_nos,no_of_occurrences = find_indexes_of_whitelist(file_content, no_of_occurrences) api_count += no_of_occurrences - apis = get_api_details(file, file_content, indexes,line_nos, path) + apis = get_api_details(app_name, file, file_content, indexes,line_nos, path) api_details.extend(apis) return api_details @@ -109,7 +111,7 @@ def is_in_string_or_comment(file_content, index): return indexes, line_nos, actual_count -def get_api_details(file, file_content: str, indexes: list,line_nos:list, path: str): +def get_api_details(app_name, file, file_content: str, indexes: list,line_nos:list, path: str): ''' Get details of the API ''' @@ -118,7 +120,7 @@ def get_api_details(file, file_content: str, indexes: list,line_nos:list, path: whitelist_details = get_whitelist_details(file_content, index) api_details = get_api_name(file_content, index) other_decorators = get_other_decorators(file_content, index, api_details.get('def_index')) - apis.append({ + obj = { **api_details, **whitelist_details, 'other_decorators': other_decorators, @@ -127,7 +129,11 @@ def get_api_details(file, file_content: str, indexes: list,line_nos:list, path: 'block_end': find_function_end_lines(file_content,api_details.get('name','')), 'file': file, 'api_path': file.replace(path, '').replace('\\', '/').replace('.py', '').replace('/', '.')[1:] + '.' + api_details.get('name') - }) + } + documentation, last_updated = get_documentation_from_branch_documentation(app_name, obj.get('name'), obj.get('api_path')) + obj['documentation'] = documentation + obj['last_updated'] = last_updated + apis.append(obj) return apis @@ -286,4 +292,24 @@ def get_decorators(node): decorator_name = get_decorator_name(decorator) if decorator_name is not None: decorators.append(decorator_name) - return decorators \ No newline at end of file + return decorators + +def get_documentation_from_branch_documentation(app_name:str, name: str, api_path: str): + ''' + Get documentation from the Commit Branch Documentation + ''' + if frappe.db.exists('Commit Branch Documentation',app_name): + branch_documentation = frappe.get_doc('Commit Branch Documentation', app_name) + docs = json.loads(branch_documentation.documentation) if branch_documentation.documentation else {} + apis = docs.get("apis", []) + documentation = '' + last_updated = '' + for api in apis: + if api.get("function_name") == name and api.get("path") == api_path: + documentation = api.get("documentation") + last_updated = api.get("last_updated") + break + return documentation, last_updated + else: + return '', '' + \ No newline at end of file