From 1be9edd3a15c9dd96f6580ba6690769ed7209790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nat=C3=A1lia=20Cond=C3=AA?= Date: Thu, 2 Oct 2025 08:05:08 -0300 Subject: [PATCH 1/2] =?UTF-8?q?Database=20Profiler=20=E2=80=93=20Implement?= =?UTF-8?q?=20Design=20Updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DatabaseProfiler.react.js | 6 +- .../DatabaseProfiler/DatabaseProfiler.scss | 108 ++++--- .../DatabaseProfilerDetail.react.js | 298 +++++++++--------- 3 files changed, 213 insertions(+), 199 deletions(-) diff --git a/src/dashboard/DatabaseProfiler/DatabaseProfiler.react.js b/src/dashboard/DatabaseProfiler/DatabaseProfiler.react.js index 350e2b019..bbe96374a 100644 --- a/src/dashboard/DatabaseProfiler/DatabaseProfiler.react.js +++ b/src/dashboard/DatabaseProfiler/DatabaseProfiler.react.js @@ -113,16 +113,16 @@ class DatabaseProfile extends DashboardView { renderHeaders() { return [ - + Operation Type , Class , - + Execution Time (ms) , - + Executed At (UTC) ]; diff --git a/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss b/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss index e26ee17d0..0321e3eb3 100644 --- a/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss +++ b/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss @@ -2,13 +2,27 @@ z-index: 1000 !important; } +code[class*="language-"], pre[class*="language-"] { + background: #111214 !important; +} + +.pre-wrap { + max-height: auto; +} + .pageContainer { position: relative; height: 100vh; display: flex; flex-direction: column; - padding-top: 64px; /* Account for header */ - overflow: hidden; /* Prevent double scrollbars */ + padding-top: 64px; + /* Account for header */ + overflow: hidden; + /* Prevent double scrollbars */ +} + +.marginTopContent { + margin-top: 80px; } .mainContent { @@ -21,18 +35,19 @@ margin-left: 10px; cursor: pointer; color: #ffffff; - + &:hover { opacity: 0.8; } svg { - fill: currentColor; // Make SVG icon inherit the text color + fill: currentColor; // Make SVG icon inherit the text color } } .row { transition: all 0.3s ease; + &:hover { cursor: pointer; box-shadow: 0px 1px 0px 0px rgba(193, 226, 255, 0.16); @@ -43,7 +58,8 @@ .detailView { padding: 20px; background: #0c1e35; - margin-top: 0px; /* Account for toolbar height */ + margin-top: 0px; + /* Account for toolbar height */ box-sizing: border-box; } @@ -93,10 +109,14 @@ .detailSection { margin-bottom: 24px; + border-radius: 7px; + border: 0.1px solid #ffffff29; + padding: 20px 20px 10px 20px; + background-color: #10203a; h3 { color: #ffffff; - font-size: 14px; + font-size: 17px; margin-bottom: 12px; font-weight: 600; } @@ -105,7 +125,7 @@ .detailGrid { display: grid; grid-template-columns: 1fr; - gap: 16px; + gap: 5px; margin-bottom: 12px; &.twoColumns { @@ -121,12 +141,11 @@ .detailRow { display: flex; + padding: 5px; align-items: center; - padding: 12px 16px; - background: rgba(255, 255, 255, 0.05); border-radius: 6px; + font-size: 15px; transition: all 0.2s ease; - min-height: 48px; box-sizing: border-box; &:hover { @@ -135,12 +154,13 @@ } .detailLabel { - color: rgba(255, 255, 255, 0.7); - font-weight: 500; - margin-right: 24px; - width: 160px; + color: #ffffff9c; + margin-right: 10px; flex-shrink: 0; - font-size: 13px; + + &::after { + content: ":"; + } } .detailValue { @@ -151,17 +171,17 @@ } .queryContainer { - background: rgba(255, 255, 255, 0.05); - padding: 12px; border-radius: 6px; font-size: 13px; width: 100%; line-height: 1.5; box-sizing: border-box; - max-height: 200px; + min-height: 200px; display: flex; flex-direction: column; - + margin-bottom: 20px; + position: relative; + h4 { margin: 0 0 8px 0; font-size: 13px; @@ -171,54 +191,40 @@ pre { width: 100%; flex: 1; - overflow: auto; margin: 0; - padding: 8px; - background: rgba(0, 0, 0, 0.2); + background: #111214 !important; + margin: 0 !important; border-radius: 4px; font-size: 12px; white-space: pre-wrap; - word-break: break-all; - max-height: 150px; - - &::-webkit-scrollbar { - width: 6px; - height: 6px; - } - - &::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.05); - border-radius: 3px; - } - - &::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); - border-radius: 3px; - - &:hover { - background: rgba(255, 255, 255, 0.3); - } - } + min-height: 200px; + border-radius: 4px; + padding: 0rem 1.5rem 1.5rem 0 !important; + overflow-x: auto; + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; + font-family: 'Roboto Mono', monospace !important; + padding-left: 3.5em !important; } } -.title { +.contentTitle { + background: #345070; color: #ffffff; font-size: 14px; font-weight: 500; - margin-bottom: 12px; -} + padding: 8px 30px; + border-radius: 5px 5px 0px 0px; -.code { - background: rgba(255, 255, 255, 0.02); - border-radius: 4px; - padding: 12px; + button { + float: right; + } } .booleanTag { display: inline-flex; align-items: center; - padding: 4px 8px; + padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: 500; diff --git a/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js b/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js index f98ac6271..4d68ac2df 100644 --- a/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js +++ b/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js @@ -5,9 +5,42 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; + +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import ReactMarkdown from 'react-markdown'; import styles from './DatabaseProfiler.scss'; +import Prism from 'prismjs'; +import 'prismjs/plugins/line-numbers/prism-line-numbers'; +import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; +import 'prismjs/components/prism-json'; +// eslint-disable-next-line no-unused-vars +import 'stylesheets/b4a-prisma.css'; + +const handleCopy = async (value) => { + try { + await navigator.clipboard.writeText(value.trim()); + } catch (err) { + console.error('Failed to copy text: ', err); + } +}; + +const CodeBlock = ({ language, value }) => { + useEffect(() => { + if (typeof Prism !== 'undefined') { + Prism.highlightAll(); + } + }, [value, language]); + + return ( +
+
{value.trim()}
+
+ ); +}; + + const DatabaseProfilerDetail = ({ data }) => { if (!data) { return
No data available
; @@ -47,7 +80,7 @@ const DatabaseProfilerDetail = ({ data }) => { }; const renderDetailRow = (label, value) => ( -
+
{label} {value}
@@ -59,199 +92,174 @@ const DatabaseProfilerDetail = ({ data }) => { ); + const JsonCodeBlock = React.memo(({ title, data }) => { + const memoizedJson = React.useMemo(() => JSON.stringify(data, null, 2), [data]); + return ( +
+ {title && + <> +
+ {title} + +
+ + } +
+ {`~~~json +${JSON.stringify(data, null, 2)} +~~~`} +
+
+ ); +}); + +JsonCodeBlock.propTypes = { + title: PropTypes.string, + data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired +}; + +const DetailSection = React.memo(({ title, children }) => ( +
+

{title}

+
+ {children} +
+
+ )); + +DetailSection.propTypes = { + title: PropTypes.string.isRequired, + children: PropTypes.node.isRequired +}; + const renderOperationSpecificDetails = () => { if (!command || command === 'unknown') { return ( -
-

Operation Details

-
-
{JSON.stringify(data, null, 2)}
-
-
+ + + ); } switch (command) { case 'aggregate': return ( -
-

Aggregation Pipeline

-
-
{JSON.stringify(pipeline, null, 2)}
-
-
+ + + ); case 'count': return ( -
-

Count Query

-
-
{JSON.stringify(query, null, 2)}
-
-
+ + + ); case 'delete': return ( -
-

Delete Operation

-
- {renderDetailRow('Documents Removed', nRemoved || 0)} -
-

Delete Query

-
{JSON.stringify(query, null, 2)}
-
-
-
+ + {renderDetailRow('Documents Removed', nRemoved || 0)} + + ); case 'distinct': return ( -
-

Distinct Operation

-
- {renderDetailRow('Distinct Key', distinct?.key || '')} -
-

Distinct Query

-
{JSON.stringify(query, null, 2)}
-
-
-
+ + {renderDetailRow('Distinct Key', distinct?.key || '')} + + ); case 'find': case 'query': return ( -
-

Query Details

-
0 ? 'flex' : 'flow', gap: '20px' }}> -
-
Find Query
-
-
{JSON.stringify(query, null, 2)}
-
-
- + +
+ {Object.keys(sort).length > 0 && ( -
-
Sort Criteria
-
-
{JSON.stringify(sort, null, 2)}
-
-
+ )}
-
+ ); case 'findAndModify': return ( -
-

Find and Modify Operation

-
- {limit > 0 && renderDetailRow('Limit', limit)} -
-

Query Document

-
{JSON.stringify(query, null, 2)}
-
- {Object.keys(sort).length > 0 && ( -
-

Sort Criteria

-
{JSON.stringify(sort, null, 2)}
-
- )} - {update && ( -
-

Update Operations

-
{JSON.stringify(update, null, 2)}
-
- )} -
-
+ + {limit > 0 && renderDetailRow('Limit', limit)} + + {Object.keys(sort).length > 0 && ( + + )} + {update && ( + + )} + ); case 'getMore': return ( -
-

Get More Operation

-
- {renderDetailRow('Documents Returned', docsReturned)} -
-
+ + {renderDetailRow('Documents Returned', docsReturned)} + ); case 'insert': return ( -
-

Insert Operation

-
- {renderDetailRow('Documents Inserted', nInserted || 0)} -
-
+ + {renderDetailRow('Documents Inserted', nInserted || 0)} + ); case 'mapReduce': return ( -
-

Map-Reduce Operation

-
- {mapReduce && ( - <> -
-

Map Function

-
{mapReduce.map}
-
-
-

Reduce Function

-
{mapReduce.reduce}
-
- {mapReduce.finalize && ( -
-

Finalize Function

-
{mapReduce.finalize}
-
- )} - - )} -
-
+ + {mapReduce && ( + <> + + + {mapReduce.finalize && ( + + )} + + )} + ); case 'update': return ( -
-

Update Operation

-
- {renderDetailRow('Documents Modified', nModified || 0)} -
-

Query

-
{JSON.stringify(query, null, 2)}
-
-
-

Update

-
{JSON.stringify(update, null, 2)}
-
-
-
+ + {renderDetailRow('Documents Modified', nModified || 0)} + + + ); case 'remove': return ( <> -
-

Remove Operation

-
- {renderDetailRow('Documents Removed', nRemoved || 0)} - {renderDetailRow('Limit', limit || 0)} -
-
-
-

Query Details

-
-
{JSON.stringify(query, null, 2)}
-
-
+ + {renderDetailRow('Documents Removed', nRemoved || 0)} + {renderDetailRow('Limit', limit || 0)} + + + + ); default: @@ -266,20 +274,20 @@ const DatabaseProfilerDetail = ({ data }) => { }; const renderOverview = () => ( -
+

Operation Overview

{renderDetailRow('Command Type', command)} {renderDetailRow('Class', className)} {(command === 'find' || command === 'query') && renderDetailRow('Limit', limit || 0)} - {renderDetailRow('Total Execution Time', `${duration}ms`)} - {renderDetailRow('Last Execution Time', formatDate(ts))} + {renderDetailRow('Duration', `${duration}ms`)} + {renderDetailRow('Timestamp', formatDate(ts))}
); return ( -
+
{renderOverview()} {shouldShowPerformanceMetrics() && ( From 9ffb52b5039284e7843489981314786127e2af64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nat=C3=A1lia=20Cond=C3=AA?= Date: Thu, 2 Oct 2025 20:22:50 -0300 Subject: [PATCH 2/2] publish structure changes --- .../DatabaseProfiler/DatabaseProfiler.scss | 13 +- .../DatabaseProfilerDetail.react.js | 206 ++++++++---------- 2 files changed, 92 insertions(+), 127 deletions(-) diff --git a/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss b/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss index 0321e3eb3..ac3a8f745 100644 --- a/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss +++ b/src/dashboard/DatabaseProfiler/DatabaseProfiler.scss @@ -117,7 +117,7 @@ code[class*="language-"], pre[class*="language-"] { h3 { color: #ffffff; font-size: 17px; - margin-bottom: 12px; + margin-bottom: 20px; font-weight: 600; } } @@ -127,16 +127,6 @@ code[class*="language-"], pre[class*="language-"] { grid-template-columns: 1fr; gap: 5px; margin-bottom: 12px; - - &.twoColumns { - grid-template-columns: repeat(2, 1fr); - } - - @media (max-width: 1200px) { - &.twoColumns { - grid-template-columns: 1fr; - } - } } .detailRow { @@ -171,6 +161,7 @@ code[class*="language-"], pre[class*="language-"] { } .queryContainer { + margin-top: 20px; border-radius: 6px; font-size: 13px; width: 100%; diff --git a/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js b/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js index 4d68ac2df..687cc85f8 100644 --- a/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js +++ b/src/dashboard/DatabaseProfiler/DatabaseProfilerDetail.react.js @@ -60,6 +60,7 @@ const DatabaseProfilerDetail = ({ data }) => { responseLength = 0, hasSort = false, hasIndex = false, + executionStats, command = op || 'unknown', // Fallback to op if command is not present limit = 0, update, @@ -146,125 +147,69 @@ DetailSection.propTypes = { }; const renderOperationSpecificDetails = () => { - if (!command || command === 'unknown') { - return ( - - - - ); - } + let sectionTitle = 'Operation Details', subTitle = '', json, sortJson, updateJson; switch (command) { + case 'unknown': + subTitle = 'Command Details'; + json = data; + break; case 'aggregate': - return ( - - - - ); - + subTitle = 'Pipeline'; + json = pipeline; + break; case 'count': - return ( - - - - ); - + subTitle = 'Query'; + json = query; + sortJson = sort; + break; case 'delete': - return ( - - {renderDetailRow('Documents Removed', nRemoved || 0)} - - - ); - + case 'remove': + subTitle = 'Query'; + json = query; + break; case 'distinct': - return ( - - {renderDetailRow('Distinct Key', distinct?.key || '')} - - - ); - + subTitle = 'Query'; + json = query; + break; case 'find': case 'query': - return ( - -
- - {Object.keys(sort).length > 0 && ( - - )} -
-
- ); - + subTitle = 'Query'; + json = query; + sortJson = sort; + break; case 'findAndModify': - return ( - - {limit > 0 && renderDetailRow('Limit', limit)} - - {Object.keys(sort).length > 0 && ( - - )} - {update && ( - - )} - - ); - - case 'getMore': - return ( - - {renderDetailRow('Documents Returned', docsReturned)} - - ); - - case 'insert': - return ( - - {renderDetailRow('Documents Inserted', nInserted || 0)} - - ); - + subTitle = 'Query'; + json = query; + sortJson = sort; + updateJson = update; + break; case 'mapReduce': - return ( - - {mapReduce && ( - <> - - - {mapReduce.finalize && ( - - )} - - )} - - ); - + subTitle = 'Map-Reduce Operation'; + json = { + map: mapReduce?.map, + reduce: mapReduce?.reduce, + finalize: mapReduce?.finalize + }; + break; case 'update': - return ( - - {renderDetailRow('Documents Modified', nModified || 0)} - - - - ); - - case 'remove': - return ( - <> - - {renderDetailRow('Documents Removed', nRemoved || 0)} - {renderDetailRow('Limit', limit || 0)} - - - - - - ); + subTitle = 'Query'; + json = query; + sortJson = sort; + updateJson = update; + break; default: return null; } + + return ( + + {json && Object.keys(json).length > 0 && } + {sortJson && Object.keys(sortJson).length > 0 && } + {updateJson && Object.keys(updateJson).length > 0 && } + {distinct && Object.keys(distinct).length > 0 && } + + ); }; const shouldShowPerformanceMetrics = () => { @@ -273,18 +218,46 @@ DetailSection.propTypes = { return commandsWithMetrics.includes(command); }; - const renderOverview = () => ( -
-

Operation Overview

-
- {renderDetailRow('Command Type', command)} - {renderDetailRow('Class', className)} - {(command === 'find' || command === 'query') && renderDetailRow('Limit', limit || 0)} - {renderDetailRow('Duration', `${duration}ms`)} - {renderDetailRow('Timestamp', formatDate(ts))} + const getOperationDetails = () => { + switch (command) { + case 'find': + case 'query': + const queryTotal = executionStats?.nReturned || docsReturned; + return queryTotal !== undefined ? { title: 'Documents Returned', value: queryTotal } : null; + case 'count': + const countTotal = docsReturned || executionStats?.nReturned; + return countTotal !== undefined ? { title: 'Documents Counted', value: countTotal } : null; + case 'delete': + case 'remove': + return nRemoved ? { title: 'Documents Removed', value: nRemoved } : null; + case 'insert': + return nInserted ? { title: 'Documents Inserted', value: nInserted } : null; + case 'update': + return nModified ? { title: 'Documents Modified', value: nModified } : null; + case 'distinct': + return distinct?.key ? { title: 'Distinct Key', value: distinct.key } : null; + default: + return null; + } + }; + + const renderOverview = () => { + + return ( +
+

Operation Overview

+
+ {renderDetailRow('Command Type', command)} + {renderDetailRow('Class', className)} + {(command === 'find' || command === 'query') && renderDetailRow('Limit', limit || 0)} + {renderDetailRow('Duration', `${duration}ms`)} + {renderDetailRow('Timestamp', formatDate(ts))} +
-
- ); + ); + }; + + const operationDetails = getOperationDetails(); return (
@@ -297,6 +270,7 @@ DetailSection.propTypes = { {renderDetailRow('Keys Examined', keysExamined)} {renderDetailRow('Docs Examined', docsExamined)} {renderDetailRow('Response Length', responseLength)} + {operationDetails && renderDetailRow(operationDetails.title, operationDetails.value)}
)}