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 43a39df..a11d08d 100644 --- a/commit/api/generate_documentation.py +++ b/commit/api/generate_documentation.py @@ -61,24 +61,22 @@ 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"
+ "{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})
- # 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
@@ -120,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})
@@ -161,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/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
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
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 cf7651f..799f6cf 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"
-
-export const APIDetails = ({ project_branch, endpointData, selectedEndpoint, setSelectedEndpoint, viewerType }: { project_branch: string, endpointData: APIData[], selectedEndpoint: string, setSelectedEndpoint: React.Dispatch>, viewerType: string }) => {
+import { APIDocumentationOfSiteApp } from "../documentation/APIDocumentation"
+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])
-
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,16 +95,28 @@ 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, 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.
@@ -110,7 +124,12 @@ export const ListView = ({ list, setSelectedEndpoint, selectedEndpoint }: { list
)}
{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}
@@ -137,9 +156,9 @@ 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 &&
+ {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..cb4ee62 100644
--- a/dashboard/src/components/features/documentation/APIDocumentation.tsx
+++ b/dashboard/src/components/features/documentation/APIDocumentation.tsx
@@ -4,44 +4,37 @@ 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";
-
-
-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()}
-
- )
-}
+import { isSystemManager } from "@/utils/roles";
+import { IoMdClose } from "react-icons/io";
+import { convertFrappeTimestampToTimeAgo } from "@/components/utils/dateconversion";
export interface DocumentationResponse {
function_name: string,
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, mutate }: { apiData: APIData, project_branch: string, file_path: string, endPoint: string, viewerType: string, mutate: () => void }) => {
+
+ 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,74 +42,107 @@ 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(() => {
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();
return (
{error && }
-
-
-
-
-
-
-
- Generate Documentation for this API
-
-
-
-
-
-
-
-
-
- {edit ? 'Save Documentation' : 'Edit Documentation'}
-
-
-
-
+ {apiData?.last_updated ?
+
+ Last Docs Updated - {convertFrappeTimestampToTimeAgo(apiData?.last_updated)}
+
+ {isCreateAccess && }
+ : (isCreateAccess && )}
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
+ }}
/>
)
+}
+
+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 (
+
+
+ {!edit &&
+
+
+
+
+
+ Generate Documentation for this API
+
+
+ }
+ {edit && }
+
+
+
+
+
+
+ {edit ? 'Save Documentation' : 'Edit Documentation'}
+
+
+
+
+
+ )
}
\ No newline at end of file
diff --git a/dashboard/src/pages/features/api_viewer/APIViewer.tsx b/dashboard/src/pages/features/api_viewer/APIViewer.tsx
index a7a7bd5..288c462 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, mutate } = 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 && (
{
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 98b1543..e11bd33 100644
--- a/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx
+++ b/dashboard/src/pages/features/api_viewer/AppAPIViewer.tsx
@@ -5,8 +5,8 @@ import { APIDetails } from "@/components/features/api_viewer/APIDetails"
import { APIList } from "@/components/features/api_viewer/APIList"
import { APIData } from "@/types/APIData"
import { useFrappeGetCall } from "frappe-react-sdk"
-import { useState } from "react"
-import { useParams } from "react-router-dom"
+import { useEffect, useRef, useState } from "react"
+import { useNavigate, useParams } from "react-router-dom"
interface GetAPIResponse {
apis: APIData[]
@@ -25,13 +25,40 @@ export const AppAPIViewerContainer = () => {
export const AppAPIViewer = ({ appName }: { appName: string }) => {
const [selectedendpoint, setSelectedEndpoint] = useState('')
+ const navigate = useNavigate()
- const { data, isLoading, error } = useFrappeGetCall<{ message: GetAPIResponse }>('commit.api.meta_data.get_apis_for_app', {
+ 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, mutate } = 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,9 +87,9 @@ export const AppAPIViewer = ({ appName }: { appName: string }) => {
{selectedendpoint && (
-
+
)}
}
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,
-
+
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 {