From aa522bb50429d0340af12147dd0ded41c490f4fe Mon Sep 17 00:00:00 2001 From: nachi Date: Fri, 1 Nov 2024 15:09:34 -0700 Subject: [PATCH] feat(admin): Add trace log visualization to RPC endpoints (#6473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change enhances the RPC endpoints admin interface by adding trace log visualization capabilities. Key changes include: - Add new trace log formatter with syntax highlighting and improved readability - Introduce a toggle switch to alternate between query metadata and trace logs view - Add new types for better TypeScript support - Improve styling of response containers with proper scrolling - Reset debug mode when switching endpoints - Add proper monospace font styling for trace logs The trace log visualization includes: - Color-coded components (host, thread ID, query ID, log levels) - Text wrapping for better readability - Proper HTML escaping for security This enhancement will help to better understand query execution and debug issues more effectively on EAP endpoints. Screenshot 2024-10-29 at 11 04 12 AM --- .vscode/settings.json | 12 +- snuba/admin/static/rpc_endpoints/index.tsx | 455 +++++++++--------- snuba/admin/static/rpc_endpoints/styles.ts | 68 +++ .../static/rpc_endpoints/trace_formatter.tsx | 65 +++ snuba/admin/static/rpc_endpoints/types.tsx | 59 +++ snuba/admin/static/rpc_endpoints/utils.ts | 51 ++ .../rpc_enpoints/trace_formatter.test.tsx | 45 ++ 7 files changed, 526 insertions(+), 229 deletions(-) create mode 100644 snuba/admin/static/rpc_endpoints/styles.ts create mode 100644 snuba/admin/static/rpc_endpoints/trace_formatter.tsx create mode 100644 snuba/admin/static/rpc_endpoints/types.tsx create mode 100644 snuba/admin/static/rpc_endpoints/utils.ts create mode 100644 snuba/admin/static/tests/rpc_enpoints/trace_formatter.test.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index a2eb678fc1..e19bc2e8f4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,21 +26,25 @@ }, "[javascript]": { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "editor.tabSize": 2 }, "[javascriptreact]": { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "editor.tabSize": 2 }, "[typescript]": { "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.tabSize": 2 }, "[typescriptreact]": { "editor.formatOnSave": true, - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "vscode.typescript-language-features", + "editor.tabSize": 2 }, "[less]": { diff --git a/snuba/admin/static/rpc_endpoints/index.tsx b/snuba/admin/static/rpc_endpoints/index.tsx index 8705b753d6..e772e340a5 100644 --- a/snuba/admin/static/rpc_endpoints/index.tsx +++ b/snuba/admin/static/rpc_endpoints/index.tsx @@ -1,240 +1,245 @@ import React, { useState, useCallback, useEffect } from 'react'; -import { Select, Button, Code, Space, Textarea, Accordion, createStyles, Loader, Checkbox, Text, Table } from '@mantine/core'; +import { Select, Button, Code, Space, Textarea, Accordion, createStyles, Loader, Checkbox, Text, Table, Switch } from '@mantine/core'; import useApi from 'SnubaAdmin/api_client'; +import { TraceLog } from 'SnubaAdmin/rpc_endpoints/trace_formatter'; +import { ExampleRequestAccordionProps, QueryInfo } from 'SnubaAdmin/rpc_endpoints/types'; +import { useStyles } from 'SnubaAdmin/rpc_endpoints/styles'; +import { fetchEndpointsList, executeEndpoint, getEndpointData } from 'SnubaAdmin/rpc_endpoints/utils'; const DEBUG_SUPPORTED_VERSIONS = ['v1']; -function RpcEndpoints() { - const api = useApi(); - const [endpoints, setEndpoints] = useState>([]); - const [selectedEndpoint, setSelectedEndpoint] = useState(null); - const [selectedVersion, setSelectedVersion] = useState(null); - const [requestBody, setRequestBody] = useState(''); - const [response, setResponse] = useState(null); - const exampleRequestTemplates: Record> = require('SnubaAdmin/rpc_endpoints/exampleRequestTemplates.json'); - const [accordionOpened, setAccordionOpened] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [debugMode, setDebugMode] = useState(false); +function ExampleRequestAccordion({ + selectedEndpoint, + selectedVersion, + exampleRequestTemplates, + setRequestBody, + classes, +}: ExampleRequestAccordionProps) { + const [isOpened, setIsOpened] = useState(false); + + return ( + setIsOpened(value === 'example')} + > + + Example Request Payload + + +
+              {JSON.stringify(
+                selectedEndpoint && selectedVersion
+                  ? exampleRequestTemplates[selectedEndpoint]?.[selectedVersion] || exampleRequestTemplates.default
+                  : exampleRequestTemplates.default,
+                null,
+                2
+              )}
+            
+
+ +
+
+
+ ); +} - const fetchEndpoints = useCallback(async () => { - try { - const fetchedEndpoints: [string, string][] = await api.getRpcEndpoints(); - const formattedEndpoints = fetchedEndpoints.map((endpoint: [string, string]) => ({ - name: endpoint[0], - version: endpoint[1] - })); - setEndpoints(formattedEndpoints); - } catch (error) { - console.error("Error fetching endpoints:", error); - } - }, []); +function RpcEndpoints() { + const api = useApi(); + const [endpoints, setEndpoints] = useState>([]); + const [selectedEndpoint, setSelectedEndpoint] = useState(null); + const [selectedVersion, setSelectedVersion] = useState(null); + const [requestBody, setRequestBody] = useState(''); + const [response, setResponse] = useState(null); + const exampleRequestTemplates: Record> = require('SnubaAdmin/rpc_endpoints/exampleRequestTemplates.json'); + const [isLoading, setIsLoading] = useState(false); + const [debugMode, setDebugMode] = useState(false); + const [showTraceLogs, setShowTraceLogs] = useState(false); - useEffect(() => { - fetchEndpoints(); - }, []); + const { classes } = useStyles(); - const handleEndpointSelect = (value: string | null) => { - setSelectedEndpoint(value); - const selectedEndpointData = endpoints.find(e => e.name === value); - setSelectedVersion(selectedEndpointData?.version || null); - setRequestBody(''); - setResponse(null); - }; + const fetchEndpoints = useCallback(async () => { + const formattedEndpoints = await fetchEndpointsList(api); + setEndpoints(formattedEndpoints); + }, []); - const handleExecute = async () => { - if (!selectedEndpoint || !selectedVersion) return; - setIsLoading(true); - try { - const parsedBody = JSON.parse(requestBody); - if (debugMode && DEBUG_SUPPORTED_VERSIONS.includes(selectedVersion)) { - parsedBody.meta = parsedBody.meta || {}; - parsedBody.meta.debug = true; - } - const result = await api.executeRpcEndpoint(selectedEndpoint, selectedVersion, parsedBody); - setResponse(result); - } catch (error: any) { - alert(`Error: ${error.message}`); - setResponse({ error: error.message }); - } finally { - setIsLoading(false); - } - }; + useEffect(() => { + fetchEndpoints(); + }, []); - const useStyles = createStyles((theme) => ({ - accordion: { - '& .mantine-Accordion-control': { - backgroundColor: theme.colors.blue[1], - color: theme.colors.blue[7], - fontSize: theme.fontSizes.xs, - padding: '2px 4px', - lineHeight: 1.2, - borderBottom: `1px solid ${theme.colors.gray[3]}`, - cursor: 'pointer', - '&:hover': { - backgroundColor: theme.colors.blue[2], - }, - }, - }, - table: { - border: `1px solid ${theme.colors.gray[3]}`, - '& th, & td': { - border: `1px solid ${theme.colors.gray[3]}`, - padding: theme.spacing.xs, - }, - '& th': { - backgroundColor: theme.colors.gray[1], - fontWeight: 'bold', - }, - '& td:first-of-type': { - width: '20%', - fontWeight: 'bold', - }, - '& td:last-of-type': { - width: '80%', - }, - }, - debugCheckbox: { - marginBottom: theme.spacing.md, - }, - })); + const handleEndpointSelect = (value: string | null) => { + setSelectedEndpoint(value); + const selectedEndpointData = getEndpointData(endpoints, value || ''); + setSelectedVersion(selectedEndpointData?.version || null); + setRequestBody(''); + setResponse(null); + setDebugMode(false); + }; - const { classes } = useStyles(); + const handleExecute = async () => { + setIsLoading(true); + try { + const result = await executeEndpoint( + api, + selectedEndpoint, + selectedVersion, + requestBody, + debugMode + ); + setResponse(result); + } catch (error: any) { + alert(`Error: ${error.message}`); + setResponse({ error: error.message }); + } finally { + setIsLoading(false); + } + }; - return ( -
-

RPC Endpoints

-