From e401c88df62eba8cd76db9e43a286cf712fbf5ca Mon Sep 17 00:00:00 2001 From: JustinKol <144163838+JustinKol@users.noreply.github.com> Date: Wed, 21 May 2025 07:54:34 -0400 Subject: [PATCH] Add query results, export to json/csv button (#2143) * master pull * Added export to json button * Update .npmrc * Update settings.json * Update .npmrc * Update .npmrc * revert .npmrc file * Added export to csv * Prettier run * Disable react/prop-types ESLint check * Changed to download icon * Added titles * Switched to download icon already present * Fixed download title * Added check for all unique headers and added seperator header for excel only * Moved to inline dropdown under download button * Capitalized CSV and JSON * Fixed where format wasn't updating before exporting * removed testing console log * Removed unnecessary async * Added csv escaping * Removing unnecessary escape character * Separated into different functions for better organization and readability * Fixed any value --- src/Explorer/Tabs/QueryTab/ResultsView.tsx | 168 +++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/Explorer/Tabs/QueryTab/ResultsView.tsx b/src/Explorer/Tabs/QueryTab/ResultsView.tsx index 4bb02484c..64b987b69 100644 --- a/src/Explorer/Tabs/QueryTab/ResultsView.tsx +++ b/src/Explorer/Tabs/QueryTab/ResultsView.tsx @@ -32,6 +32,7 @@ enum ResultsTabs { const ResultsTab: React.FC = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => { const styles = useQueryTabStyles(); + /* eslint-disable react/prop-types */ const queryResultsString = queryResults ? isMongoDB ? MongoUtility.tojson(queryResults.documents, undefined, false) @@ -47,6 +48,172 @@ const ResultsTab: React.FC = ({ queryResults, isMongoDB, execu await executeQueryDocumentsPage(firstItemIndex + itemCount - 1); }; + const ExportResults: React.FC = () => { + const [showDropdown, setShowDropdown] = useState(false); + const dropdownRef = React.useRef(null); + + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setShowDropdown(false); + } + }; + + if (showDropdown) { + document.addEventListener("mousedown", handleClickOutside); + } + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [showDropdown]); + + const escapeCsvValue = (value: string): string => { + return `"${value.replace(/"/g, '""')}"`; + }; + + const formatValueForCsv = (value: string | object): string => { + if (value === null || value === undefined) { + return ""; + } + if (typeof value === "object") { + return escapeCsvValue(JSON.stringify(value)); + } + return escapeCsvValue(String(value)); + }; + + const exportToCsv = () => { + try { + const allHeadersSet = new Set(); + queryResults.documents.forEach((doc) => { + Object.keys(doc).forEach((key) => allHeadersSet.add(key)); + }); + + const allHeaders = Array.from(allHeadersSet); + const csvHeader = allHeaders.map(escapeCsvValue).join(","); + const csvData = queryResults.documents + .map((doc) => + allHeaders.map((header) => (doc[header] !== undefined ? formatValueForCsv(doc[header]) : "")).join(","), + ) + .join("\n"); + + const csvContent = `sep=,\n${csvHeader}\n${csvData}`; + downloadFile(csvContent, "query-results.csv", "text/csv"); + } catch (error) { + console.error("Failed to export CSV:", error); + } + }; + + const exportToJson = () => { + try { + downloadFile(queryResultsString, "query-results.json", "application/json"); + } catch (error) { + console.error("Failed to export JSON:", error); + } + }; + + const downloadFile = (content: string, fileName: string, contentType: string) => { + const blob = new Blob([content], { type: contentType }); + const url = URL.createObjectURL(blob); + const downloadLink = document.createElement("a"); + downloadLink.href = url; + downloadLink.download = fileName; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + setTimeout(() => URL.revokeObjectURL(url), 100); + }; + + const handleExport = (format: "CSV" | "JSON") => { + setShowDropdown(false); + if (format === "CSV") { + exportToCsv(); + } else { + exportToJson(); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent, format: "CSV" | "JSON") => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleExport(format); + } else if (e.key === "Escape") { + setShowDropdown(false); + } + }; + + return ( +
+ + +
+ )} + + ); + }; + return ( <>
@@ -67,6 +234,7 @@ const ResultsTab: React.FC = ({ queryResults, isMongoDB, execu aria-label="Copy" onClick={onClickCopyResults} /> +