diff --git a/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx b/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx index 6f898e9b490..c19f5dde1b0 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx @@ -1,32 +1,20 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { isEqual } from 'lodash' -import { ChevronDown, Clipboard, Download, Eye, EyeOff, Play } from 'lucide-react' -import Papa from 'papaparse' -import { Key, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Clipboard, Eye, EyeOff, Play } from 'lucide-react' +import { Key, ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { Item, Menu, useContextMenu } from 'react-contexify' import DataGrid, { Column, RenderRowProps, Row } from 'react-data-grid' import { createPortal } from 'react-dom' -import { toast } from 'sonner' -import { IS_PLATFORM } from 'common' +import { IS_PLATFORM, useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import CSVButton from 'components/ui/CSVButton' +import { DownloadResultsButton } from 'components/ui/DownloadResultsButton' import { useSelectedLog } from 'hooks/analytics/useSelectedLog' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { copyToClipboard } from 'lib/helpers' import { useProfile } from 'lib/profile' import { ResponseError } from 'types' -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - ResizableHandle, - ResizablePanel, - ResizablePanelGroup, - cn, -} from 'ui' +import { Button, ResizableHandle, ResizablePanel, ResizablePanelGroup, cn } from 'ui' import AuthColumnRenderer from './LogColumnRenderers/AuthColumnRenderer' import DatabaseApiColumnRender from './LogColumnRenderers/DatabaseApiColumnRender' import DatabasePostgresColumnRender from './LogColumnRenderers/DatabasePostgresColumnRender' @@ -93,11 +81,11 @@ const LogTable = ({ selectedLogError, onSelectedLogChange, }: Props) => { + const { ref } = useParams() const { profile } = useProfile() const [selectedLogId] = useSelectedLog() const { show: showContextMenu } = useContextMenu() - const downloadCsvRef = useRef(null) const [cellPosition, setCellPosition] = useState() const [selectionOpen, setSelectionOpen] = useState(false) const [selectedRow, setSelectedRow] = useState(null) @@ -188,7 +176,6 @@ const LogTable = ({ } } - const stringData = useMemo(() => JSON.stringify(data), [data]) const [dedupedData, logMap] = useMemo<[LogData[], LogMap]>(() => { const deduped = [...new Set(data)] as LogData[] @@ -245,54 +232,12 @@ const LogTable = ({ )} >
- - - - - - { - downloadCsvRef.current?.click() - }} - className="space-x-2" - > - -
Download CSV
-
- { - const csvData = Papa.unparse(data) - copyToClipboard(csvData, () => { - toast.success('Results copied to clipboard') - }) - }} - className="space-x-2" - > - -
Copy as CSV
-
- { - copyToClipboard(stringData, () => { - toast.success('Results copied to clipboard') - }) - }} - className="space-x-2" - > - -
Copy as JSON
-
-
-
-
- - {/* Using .hidden with a ref so I don't have to duplicate the code to download the CSV - Jordi */} -
- -
Download CSV
-
+
{showHistogramToggle && ( diff --git a/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx b/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx index 53e0ff6f0e2..06aec55d186 100644 --- a/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx @@ -5,8 +5,8 @@ import { useEffect, useState } from 'react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import CSVButton from 'components/ui/CSVButton' import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DownloadResultsButton } from 'components/ui/DownloadResultsButton' import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' import { IS_PLATFORM } from 'lib/constants' import { Button, Input, Tooltip, TooltipContent, TooltipTrigger, cn } from 'ui' @@ -70,6 +70,8 @@ const PreviewFilterPanel = ({ const { ref } = useParams() const [search, setSearch] = useState('') + const logName = router.pathname.split('/').pop() + const { data: loadBalancers } = useLoadBalancersQuery({ projectRef: ref }) // [Joshen] These are the routes tested that can show replica logs @@ -233,7 +235,15 @@ const PreviewFilterPanel = ({ Chart - + {Boolean(csvData) && ( + + )} {showDatabaseSelector ? ( diff --git a/apps/studio/components/ui/CSVButton.tsx b/apps/studio/components/ui/CSVButton.tsx deleted file mode 100644 index 137d0929c03..00000000000 --- a/apps/studio/components/ui/CSVButton.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Download } from 'lucide-react' -import React, { PropsWithChildren, useMemo, useRef } from 'react' -import { CSVLink } from 'react-csv' -import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui' -import { ButtonProps } from 'ui/src/components/Button/Button' - -interface CSVButtonProps { - buttonType?: ButtonProps['type'] - onClick?: ButtonProps['onClick'] - disabled?: ButtonProps['disabled'] - icon?: React.ReactNode - data?: unknown[] - title?: string -} - -const CSVButton = ({ - onClick, - buttonType = 'default', - icon, - children, - disabled, - data, - title, -}: PropsWithChildren) => { - const csvRef = useRef(null) - const handleDownload = () => { - ;(csvRef.current as any)?.link.click() - } - const formattedData = useMemo(() => { - const first = data?.[0] - if (!first || !data) return - const keys = Object.keys(first as any) - return data.map((datum: any) => { - return keys.reduce((acc: any, key) => { - if (typeof datum[key] === 'object') { - acc[key] = JSON.stringify(datum[key]) as string - } else { - acc[key] = String(datum[key]) - } - return acc - }, {}) - }) - // retrieve dot notation of all keys - }, [JSON.stringify(data)]) - - return ( - <> - - - - - - - Download logs as CSV - - - - ) -} - -export default CSVButton diff --git a/apps/studio/components/ui/DownloadResultsButton.tsx b/apps/studio/components/ui/DownloadResultsButton.tsx index eea79d08d1c..ba2dee032bd 100644 --- a/apps/studio/components/ui/DownloadResultsButton.tsx +++ b/apps/studio/components/ui/DownloadResultsButton.tsx @@ -1,7 +1,8 @@ +import saveAs from 'file-saver' import { ChevronDown, Clipboard, Download } from 'lucide-react' import { markdownTable } from 'markdown-table' -import { useMemo, useRef } from 'react' -import { CSVLink } from 'react-csv' +import Papa from 'papaparse' +import { useMemo } from 'react' import { toast } from 'sonner' import { copyToClipboard } from 'lib/helpers' @@ -14,7 +15,9 @@ import { } from 'ui' interface DownloadResultsButtonProps { + iconOnly?: boolean type?: 'text' | 'default' + text?: string align?: 'start' | 'center' | 'end' results: any[] fileName: string @@ -24,7 +27,9 @@ interface DownloadResultsButtonProps { } export const DownloadResultsButton = ({ + iconOnly = false, type = 'default', + text = 'Export', align = 'start', results, fileName, @@ -32,8 +37,6 @@ export const DownloadResultsButton = ({ onCopyAsMarkdown, onCopyAsJSON, }: DownloadResultsButtonProps) => { - const csvRef = useRef(null) - // [Joshen] Ensure JSON values are stringified for CSV and Markdown const formattedResults = results.map((row) => { const r = { ...row } @@ -51,6 +54,14 @@ export const DownloadResultsButton = ({ return undefined }, [results]) + const downloadAsCSV = () => { + const csv = Papa.unparse(formattedResults, { columns: headers }) + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) + saveAs(blob, `${fileName}.csv`) + toast.success('Downloading results as CSV') + onDownloadAsCSV?.() + } + const copyAsMarkdown = () => { if (navigator) { if (formattedResults.length == 0) toast('Results are empty') @@ -82,42 +93,32 @@ export const DownloadResultsButton = ({ } return ( - <> - - - - - - { - csvRef.current?.link.click() - toast.success('Downloading results as CSV') - onDownloadAsCSV?.() - }} - > - -

Download CSV

-
- - -

Copy as markdown

-
- - -

Copy as JSON

-
-
-
- - + + + + + + downloadAsCSV()}> + +

Download CSV

+
+ + +

Copy as markdown

+
+ + +

Copy as JSON

+
+
+
) } diff --git a/apps/studio/package.json b/apps/studio/package.json index 883fffe7d24..8832ead17b1 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -95,7 +95,6 @@ "react-beautiful-dnd": "^13.1.0", "react-contexify": "^5.0.0", "react-copy-to-clipboard": "^5.1.0", - "react-csv": "^2.0.3", "react-data-grid": "7.0.0-beta.41", "react-datepicker": "^4.18.0", "react-dnd": "^16.0.1", @@ -150,7 +149,6 @@ "@types/react": "^18.2.24", "@types/react-beautiful-dnd": "^13.1.2", "@types/react-copy-to-clipboard": "^5.0.4", - "@types/react-csv": "^1.1.2", "@types/react-datepicker": "^4.3.4", "@types/react-dom": "^18.2.8", "@types/react-grid-layout": "^1.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ed39c1d17f..ad85e0a6f26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -745,9 +745,6 @@ importers: react-copy-to-clipboard: specifier: ^5.1.0 version: 5.1.0(react@18.2.0) - react-csv: - specifier: ^2.0.3 - version: 2.2.2 react-data-grid: specifier: 7.0.0-beta.41 version: 7.0.0-beta.41(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -905,9 +902,6 @@ importers: '@types/react-copy-to-clipboard': specifier: ^5.0.4 version: 5.0.5 - '@types/react-csv': - specifier: ^1.1.2 - version: 1.1.6 '@types/react-datepicker': specifier: ^4.3.4 version: 4.15.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -6504,9 +6498,6 @@ packages: '@types/react-copy-to-clipboard@5.0.5': resolution: {integrity: sha512-en3JGqPA4RX4aUlo6q6uUbnqLp31Dhm2E/thiMvFTIvU+dUDG249jBG2MJ0rPMXE/MbKVrpmi/1r1G4QLhIHKQ==} - '@types/react-csv@1.1.6': - resolution: {integrity: sha512-D8MmuwIrqVr1ilgCEhB5U7uyMeojPC+Ffr/Zg/galVEFMDy/dbmegj38Bs5hWwgC3VVjCyo9GR+YHrC1fnFm1w==} - '@types/react-datepicker@4.15.1': resolution: {integrity: sha512-6/LthK0pTDBKjjVJqA2ygY3jJsHH7uZXIk8WPQcGHUiFOQLOKIv3krOxFZ2mt3BB3guMB6jVTc6ansQAd0r7xQ==} @@ -9004,9 +8995,6 @@ packages: resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==} engines: {node: '>=12.0.0'} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} @@ -9901,9 +9889,6 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} - is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -12394,9 +12379,6 @@ packages: react: '>= 15' react-dom: '>= 15' - react-csv@2.2.2: - resolution: {integrity: sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw==} - react-data-grid@7.0.0-beta.41: resolution: {integrity: sha512-WmTP/PV+vtVjIaGVLgyG6WAhqvuPBM8I54bsR7oJZl6w43+mIasZM9rEBWjQ52XHJEy41/tjcMBIMNiWqoEbrQ==} peerDependencies: @@ -20914,10 +20896,6 @@ snapshots: dependencies: '@types/react': 18.3.3 - '@types/react-csv@1.1.6': - dependencies: - '@types/react': 18.3.3 - '@types/react-datepicker@4.15.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@popperjs/core': 2.11.8 @@ -23556,8 +23534,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -23904,12 +23882,10 @@ snapshots: flat-cache@3.1.0: dependencies: - flatted: 3.3.1 + flatted: 3.3.2 keyv: 4.5.3 rimraf: 3.0.2 - flatted@3.3.1: {} - flatted@3.3.2: {} follow-redirects@1.15.9: {} @@ -25011,10 +24987,6 @@ snapshots: dependencies: '@types/estree': 1.0.5 - is-reference@3.0.2: - dependencies: - '@types/estree': 1.0.5 - is-reference@3.0.3: dependencies: '@types/estree': 1.0.7 @@ -28084,9 +28056,9 @@ snapshots: periscopic@3.1.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.7 estree-walker: 3.0.3 - is-reference: 3.0.2 + is-reference: 3.0.3 pg-cloudflare@1.1.1: optional: true @@ -28588,8 +28560,6 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-csv@2.2.2: {} - react-data-grid@7.0.0-beta.41(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: clsx: 2.1.1 @@ -29491,9 +29461,9 @@ snapshots: sass@1.72.0: dependencies: - chokidar: 3.5.3 + chokidar: 3.6.0 immutable: 4.3.4 - source-map-js: 1.2.0 + source-map-js: 1.2.1 satori@0.10.9: dependencies: