From 8f8bd6e70dfac6fce6cef6ea89ba4649f9d00320 Mon Sep 17 00:00:00 2001 From: gitstart Date: Thu, 13 Oct 2022 16:16:27 +0000 Subject: [PATCH 01/13] fix: make SQL editor preview table cells copyable --- .../to-be-cleaned/SqlEditor/TabSqlQuery.js | 24 +++++++++++++++---- studio/styles/grid.scss | 18 +++++++++++++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js index dd0cdba43d2..c2aedc4800e 100644 --- a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js +++ b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js @@ -5,7 +5,7 @@ import { CSVLink } from 'react-csv' import { debounce } from 'lodash' import { observer } from 'mobx-react-lite' import { useEffect, useRef, useState } from 'react' -import { Button, Dropdown, IconChevronDown } from 'ui' +import { Button, Dropdown, IconChevronDown, IconClipboard } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useKeyboardShortcuts, useStore, useWindowDimensions, checkPermissions } from 'hooks' @@ -311,11 +311,11 @@ const Results = ({ results }) => { { 'Command+c': (event) => { event.stopPropagation() - onCopyCell() + onCopySelectedCell() }, 'Control+c': (event) => { event.stopPropagation() - onCopyCell() + onCopySelectedCell() }, }, ['INPUT', 'TEXTAREA'] @@ -337,7 +337,21 @@ const Results = ({ results }) => { } const formatter = (column, row) => { - return {JSON.stringify(row[column])} + return ( +
+ {JSON.stringify(row[column])} + + {row[column] && ( +
+ ) } const columnRender = (name) => { return
{name}
@@ -355,7 +369,7 @@ const Results = ({ results }) => { setCellPosition(position) } - function onCopyCell() { + function onCopySelectedCell() { if (columns && cellPosition) { const { idx, rowIdx } = cellPosition const column = columns[idx] diff --git a/studio/styles/grid.scss b/studio/styles/grid.scss index 7da30942b20..20c6634572b 100644 --- a/studio/styles/grid.scss +++ b/studio/styles/grid.scss @@ -388,7 +388,7 @@ .sb-grid-select-editor { button { @apply border-none rounded-none shadow-none; - box-shadow: none !important + box-shadow: none !important; } } @@ -527,6 +527,22 @@ @apply border-scale-300; } +/* + Cell copy action +*/ + +.sb-grid-select-cell__copy-action { + @apply hidden mr-1; +} + +.sb-grid-select-cell__formatter:hover .sb-grid-select-cell__copy-action { + @apply block opacity-50; +} + +.sb-grid-select-cell__copy-action:hover { + @apply opacity-100; +} + /* Header */ From a66e5028ad1d587a0cd4d6eecf5ff9cc6c246c45 Mon Sep 17 00:00:00 2001 From: gitstart Date: Fri, 14 Oct 2022 04:05:03 +0000 Subject: [PATCH 02/13] refactor: styling with pure Tailwindcss classes --- .../to-be-cleaned/SqlEditor/TabSqlQuery.js | 4 ++-- studio/styles/grid.scss | 18 +----------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js index c2aedc4800e..fc42aef8ec1 100644 --- a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js +++ b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js @@ -338,7 +338,7 @@ const Results = ({ results }) => { const formatter = (column, row) => { return ( -
+
{JSON.stringify(row[column])} {row[column] && ( @@ -346,7 +346,7 @@ const Results = ({ results }) => { type="outline" icon={} onClick={() => copyToClipboard(formatClipboardValue(row[column]))} - className="sb-grid-select-cell__copy-action" + className="hidden mr-1 group-hover:block group-hover:opacity-50 hover:opacity-100" title="Copy" /> )} diff --git a/studio/styles/grid.scss b/studio/styles/grid.scss index 20c6634572b..7da30942b20 100644 --- a/studio/styles/grid.scss +++ b/studio/styles/grid.scss @@ -388,7 +388,7 @@ .sb-grid-select-editor { button { @apply border-none rounded-none shadow-none; - box-shadow: none !important; + box-shadow: none !important } } @@ -527,22 +527,6 @@ @apply border-scale-300; } -/* - Cell copy action -*/ - -.sb-grid-select-cell__copy-action { - @apply hidden mr-1; -} - -.sb-grid-select-cell__formatter:hover .sb-grid-select-cell__copy-action { - @apply block opacity-50; -} - -.sb-grid-select-cell__copy-action:hover { - @apply opacity-100; -} - /* Header */ From e525edd70dcfd9c75bd2853d0debb009f2ab3928 Mon Sep 17 00:00:00 2001 From: gitstart Date: Sun, 16 Oct 2022 06:57:51 +0000 Subject: [PATCH 03/13] chore: implements suggested improvements --- .../to-be-cleaned/SqlEditor/TabSqlQuery.js | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js index fc42aef8ec1..82973f2cfee 100644 --- a/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js +++ b/studio/components/to-be-cleaned/SqlEditor/TabSqlQuery.js @@ -1,11 +1,12 @@ import Split from 'react-split' import Editor from '@monaco-editor/react' import DataGrid from '@supabase/react-data-grid' +import classNames from 'classnames' import { CSVLink } from 'react-csv' import { debounce } from 'lodash' import { observer } from 'mobx-react-lite' import { useEffect, useRef, useState } from 'react' -import { Button, Dropdown, IconChevronDown, IconClipboard } from 'ui' +import { Button, Dropdown, IconCheck, IconChevronDown, IconClipboard } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useKeyboardShortcuts, useStore, useWindowDimensions, checkPermissions } from 'hooks' @@ -306,6 +307,7 @@ const UtilityTabResults = observer(() => { const Results = ({ results }) => { const [cellPosition, setCellPosition] = useState(undefined) + const [copiedCell, setCopiedCell] = useState(undefined) useKeyboardShortcuts( { @@ -336,7 +338,29 @@ const Results = ({ results }) => { ) } - const formatter = (column, row) => { + useEffect(() => { + let timeoutId = 0 + + if (copiedCell) { + timeoutId = setTimeout(() => { + setCopiedCell(undefined) + }, 1000) + } + + return () => { + timeoutId && clearTimeout(timeoutId) + } + }, [copiedCell]) + + const handleCopyClick = (column, row, rowIndex) => { + copyToClipboard(formatClipboardValue(row[column]), () => { + setCopiedCell(`${column},${rowIndex}`) + }) + } + + const formatter = (column, row, rowIndex) => { + const isCopied = copiedCell === `${column},${rowIndex}` + return (
{JSON.stringify(row[column])} @@ -344,9 +368,12 @@ const Results = ({ results }) => { {row[column] && (
-
-
- +
+
+
+ + +
+
{isLoading && } -
+
{!isLoading && }
diff --git a/studio/components/ui/CopyButton.tsx b/studio/components/ui/CopyButton.tsx new file mode 100644 index 00000000000..294a0cdb179 --- /dev/null +++ b/studio/components/ui/CopyButton.tsx @@ -0,0 +1,32 @@ +import { Button, ButtonProps } from 'ui' +import { copyToClipboard } from 'lib/helpers' +import { IconClipboard } from 'ui' +import { useEffect, useState } from 'react' + +export interface CopyButtonProps extends ButtonProps { + text: string +} +const CopyButton: React.FC = ({ text, children, onClick, ...props }) => { + const [showCopied, setShowCopied] = useState(false) + + useEffect(() => { + if (!showCopied) return + const timer = setTimeout(() => setShowCopied(false), 2000) + return () => clearTimeout(timer) + }, [showCopied]) + + return ( + + ) +} +export default CopyButton diff --git a/studio/tests/components/CopyButton.test.tsx b/studio/tests/components/CopyButton.test.tsx new file mode 100644 index 00000000000..d8db3fc667e --- /dev/null +++ b/studio/tests/components/CopyButton.test.tsx @@ -0,0 +1,12 @@ +import { screen } from '@testing-library/dom' +import userEvent from '@testing-library/user-event' +import CopyButton from 'components/ui/CopyButton' +import { render } from 'tests/helpers' + +test('shows copied text', async () => { + const callback = jest.fn() + render() + userEvent.click(await screen.findByText('Copy')) + await screen.findByText('Copied') + expect(callback).toBeCalled() +}) diff --git a/studio/tests/pages/projects/LogTable.test.js b/studio/tests/pages/projects/LogTable.test.js index 804bfdbe685..94624e0b891 100644 --- a/studio/tests/pages/projects/LogTable.test.js +++ b/studio/tests/pages/projects/LogTable.test.js @@ -23,6 +23,10 @@ test('can display log data', async () => { userEvent.click(row) await screen.findByText(/my_key/) await screen.findByText(/something_value/) + + // render copy button + userEvent.click(await screen.findByText(/Copy/)) + await screen.findByText(/Copied/) }) test('dedupes log lines with exact id', async () => { From a432267ebab7273294299666444c4583102042a0 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 21 Oct 2022 04:00:39 +0800 Subject: [PATCH 07/13] feat: add log selection detailed row value copying --- .../DatabaseApiSelectionRender.tsx | 27 ++----- .../DatabasePostgresSelectionRender.tsx | 24 +++--- .../DefaultExplorerSelectionRenderer.tsx | 27 +------ .../DefaultPreviewSelectionRenderer.tsx | 73 ++++++------------- .../FunctionInvocationSelectionRender.tsx | 45 ++++-------- .../FunctionLogsSelectionRender.tsx | 39 +++------- .../Settings/Logs/LogsFormatters.tsx | 30 ++++++++ studio/components/ui/CopyButton.tsx | 19 ++++- 8 files changed, 115 insertions(+), 169 deletions(-) diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabaseApiSelectionRender.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabaseApiSelectionRender.tsx index 16402c0e2e1..1c786f068ae 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabaseApiSelectionRender.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabaseApiSelectionRender.tsx @@ -1,7 +1,7 @@ import React from 'react' import { LOGS_TAILWIND_CLASSES } from '../Logs.constants' import LogsDivider from '../Logs.Divider' -import { jsonSyntaxHighlight, ResponseCodeFormatter } from '../LogsFormatters' +import { jsonSyntaxHighlight, ResponseCodeFormatter, SelectionDetailedRow } from '../LogsFormatters' const DatabaseApiSelectionRender = ({ log }: any) => { const request = log?.metadata[0]?.request?.[0] @@ -13,27 +13,16 @@ const DatabaseApiSelectionRender = ({ log }: any) => { const clientInfo = request?.headers?.[0]?.x_client_info const referer = request?.headers?.[0]?.referer - const DetailedRow = ({ label, value }: { label: string; value: string | React.ReactNode }) => { - return ( -
- {label} - - {value} - -
- ) - } - return ( <>
- } /> - - - - - {clientInfo && } - {referer && } + } /> + + + + + {clientInfo && } + {referer && }
diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabasePostgresSelectionRender.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabasePostgresSelectionRender.tsx index 1f341742ccc..c583f131055 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabasePostgresSelectionRender.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DatabasePostgresSelectionRender.tsx @@ -2,21 +2,13 @@ import { Alert } from 'ui' import React from 'react' import { LOGS_TAILWIND_CLASSES } from '../Logs.constants' import LogsDivider from '../Logs.Divider' -import { jsonSyntaxHighlight, SeverityFormatter } from '../LogsFormatters' +import { jsonSyntaxHighlight, SelectionDetailedRow, SeverityFormatter } from '../LogsFormatters' const DatabasePostgresSelectionRender = ({ log }: any) => { const postgresUsername = log?.metadata[0]?.parsed[0]?.user_name const sessionId = log?.metadata[0]?.parsed[0]?.session_id const hint = log?.metadata[0]?.parsed[0]?.hint - - const DetailedRow = ({ label, value }: { label: string; value: string | React.ReactNode }) => { - return ( -
-

{label}

-

{value}

-
- ) - } + const errorSeverity = log?.metadata[0]?.parsed[0]?.error_severity return ( <> @@ -29,13 +21,17 @@ const DatabasePostgresSelectionRender = ({ log }: any) => {
- } /> - - + } + /> + +
{hint && (
- +
)} diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultExplorerSelectionRenderer.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultExplorerSelectionRenderer.tsx index 479c27ceed6..a4be3324635 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultExplorerSelectionRenderer.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultExplorerSelectionRenderer.tsx @@ -1,30 +1,7 @@ import { LOGS_TAILWIND_CLASSES } from '../Logs.constants' -import { jsonSyntaxHighlight } from '../LogsFormatters' +import { jsonSyntaxHighlight, SelectionDetailedRow } from '../LogsFormatters' const DefaultExplorerSelectionRenderer = ({ log }: any) => { - const DetailedRow = ({ - label, - value, - code, - }: { - label: string - value: string | React.ReactNode - code?: boolean - }) => { - return ( -
- {label} - - {value} - -
- ) - } - const DetailedJsonRow = ({ label, value, @@ -63,7 +40,7 @@ const DefaultExplorerSelectionRenderer = ({ log }: any) => { {value && typeof value === 'object' ? ( ) : ( - + )}
) diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx index 35d8510e98e..4f2bfd73b5a 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx @@ -1,55 +1,30 @@ -import { isUnixMicro, unixMicroToIsoTimestamp } from '..' +import { isUnixMicro, PreviewLogData, unixMicroToIsoTimestamp } from '..' import { LOGS_TAILWIND_CLASSES } from '../Logs.constants' -import { jsonSyntaxHighlight } from '../LogsFormatters' +import { jsonSyntaxHighlight, SelectionDetailedRow } from '../LogsFormatters' -const DefaultPreviewSelectionRenderer = ({ log }: any) => { - const DetailedRow = ({ - label, - value, - code, - }: { - label: string - value: string | React.ReactNode - code?: boolean - }) => { - return ( -
- {label} - - {value} - -
- ) - } - - return ( -
-
-

Event Message

-
- {log.event_message} -
-
- - -
-

Metadata

-
+const DefaultPreviewSelectionRenderer = ({ log }: { log: PreviewLogData }) => (
+  
+
+

Event Message

+
+ {log.event_message}
- ) -} + + +
+

Metadata

+
+    
+
+) export default DefaultPreviewSelectionRenderer diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionInvocationSelectionRender.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionInvocationSelectionRender.tsx index 51cfe6bb247..cb772f6964f 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionInvocationSelectionRender.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionInvocationSelectionRender.tsx @@ -2,7 +2,7 @@ import dayjs from 'dayjs' import { filterFunctionsRequestResponse } from 'lib/logs' import { PreviewLogData } from '..' import { LOGS_TAILWIND_CLASSES } from '../Logs.constants' -import { jsonSyntaxHighlight, ResponseCodeFormatter } from '../LogsFormatters' +import { jsonSyntaxHighlight, ResponseCodeFormatter, SelectionDetailedRow } from '../LogsFormatters' const FunctionInvocationSelectionRender = ({ log }: { log: PreviewLogData }) => { const metadata = log.metadata?.[0] @@ -15,39 +15,22 @@ const FunctionInvocationSelectionRender = ({ log }: { log: PreviewLogData }) => const deploymentId = metadata.deployment_id const timestamp = dayjs(log.timestamp / 1000) - const DetailedRow = ({ - label, - value, - code, - }: { - label: string - value: string | React.ReactNode - code?: boolean - }) => { - return ( -
- {label} - - {value} - -
- ) - } - return ( <>
- } /> - - - - - - + } /> + + + + + +

Request body

diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionLogsSelectionRender.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionLogsSelectionRender.tsx index 9d371f162d7..fb5a925767b 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionLogsSelectionRender.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/FunctionLogsSelectionRender.tsx @@ -1,34 +1,11 @@ import dayjs from 'dayjs' import { LOGS_TAILWIND_CLASSES } from '../Logs.constants' -import { jsonSyntaxHighlight, SeverityFormatter } from '../LogsFormatters' +import { jsonSyntaxHighlight, SelectionDetailedRow, SeverityFormatter } from '../LogsFormatters' const FunctionLogsSelectionRender = ({ log }: any) => { const timestamp = dayjs(log.timestamp / 1000) const metadata = log.metadata[0] - const DetailedRow = ({ - label, - value, - code, - }: { - label: string - value: string | React.ReactNode - code?: boolean - }) => { - return ( -
- {label} - - {value} - -
- ) - } - return ( <>
@@ -39,11 +16,15 @@ const FunctionLogsSelectionRender = ({ log }: any) => {
- } /> - - - - + } + /> + + + +

Metadata

diff --git a/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx b/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx index 82156ba5284..87ed2f6f855 100644 --- a/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx +++ b/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx @@ -8,11 +8,41 @@ import { IconAlertCircle, IconInfo } from 'ui' import dayjs from 'dayjs' import React from 'react' import { isUnixMicro, unixMicroToIsoTimestamp } from '.' +import CopyButton from 'components/ui/CopyButton' export const RowLayout: React.FC = ({ children }) => (
{children}
) +export const SelectionDetailedRow = ({ + label, + value, + valueRender, +}: { + label: string + value: string + valueRender?: React.ReactNode +}) => { + return ( +
+ {label} + + {valueRender ?? value} + + + {''} + +
+ ) +} + +// used for column renderers export const TextFormatter: React.FC<{ className?: string; value: string }> = ({ value, className, diff --git a/studio/components/ui/CopyButton.tsx b/studio/components/ui/CopyButton.tsx index 294a0cdb179..690f3399d7e 100644 --- a/studio/components/ui/CopyButton.tsx +++ b/studio/components/ui/CopyButton.tsx @@ -5,8 +5,16 @@ import { useEffect, useState } from 'react' export interface CopyButtonProps extends ButtonProps { text: string + // used for text-less feedback + bounceIconOnCopy?: boolean } -const CopyButton: React.FC = ({ text, children, onClick, ...props }) => { +const CopyButton: React.FC = ({ + text, + children, + onClick, + bounceIconOnCopy, + ...props +}) => { const [showCopied, setShowCopied] = useState(false) useEffect(() => { @@ -22,7 +30,14 @@ const CopyButton: React.FC = ({ text, children, onClick, ...pro copyToClipboard(text) onClick?.(e) }} - icon={} + icon={ + + } {...props} > {children ?? (showCopied ? 'Copied' : 'Copy')} From 48781daa7cc0ec5ce1e60a56230de7b89cc614e7 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 21 Oct 2022 04:02:27 +0800 Subject: [PATCH 08/13] feat: update copy button title --- studio/components/interfaces/Settings/Logs/LogSelection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Settings/Logs/LogSelection.tsx b/studio/components/interfaces/Settings/Logs/LogSelection.tsx index b814789d926..d6e16e194ae 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelection.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelection.tsx @@ -149,7 +149,7 @@ ${JSON.stringify(fullLog.metadata, null, 2)}
- +