From d75b65b473ea34ba0bca194053a4fdfeb5de957f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 30 Mar 2023 15:28:11 +0800 Subject: [PATCH 01/12] Mid way import csv existing --- studio/components/grid/SupabaseGrid.tsx | 26 ++++---- .../grid/components/header/Header.tsx | 59 +++++++++++++++++-- studio/components/grid/types/grid.ts | 5 ++ .../TableGridEditor/TableGridEditor.tsx | 1 + 4 files changed, 76 insertions(+), 15 deletions(-) diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index 5318d088f19..3a1f1fbaffb 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -66,6 +66,11 @@ const SupabaseGridLayout = forwardRef((props showCustomChildren, customHeader, children, + onAddRow, + onAddColumn, + updateTableRow, + onEditForeignKeyColumnValue, + onImportData, } = props const dispatch = useDispatch() const state = useTrackedState() @@ -79,15 +84,13 @@ const SupabaseGridLayout = forwardRef((props const sorts = formatSortURLParams(sort as string[]) const filters = formatFilterURLParams(filter as string[]) - const table = props.table - const { project } = useProjectContext() const { data, isLoading, isRefetching } = useTableRowsQuery( { - queryKey: [table.schema, table.name], + queryKey: [props.table.schema, props.table.name], projectRef: project?.ref, connectionString: project?.connectionString, - table, + table: props.table, sorts, filters, page: state.page, @@ -194,17 +197,18 @@ const SupabaseGridLayout = forwardRef((props if (!state.table || refreshView || refreshTable) { initializeData() } - }, [state.metaService, state.table, table, props.schema]) + }, [state.metaService, state.table, props.table, props.schema]) return (
@@ -216,8 +220,8 @@ const SupabaseGridLayout = forwardRef((props ref={gridRef} {...gridProps} rows={data?.rows ?? []} - updateRow={props.updateTableRow} - onEditForeignKeyColumnValue={props.onEditForeignKeyColumnValue} + updateRow={updateTableRow} + onEditForeignKeyColumnValue={onEditForeignKeyColumnValue} />
@@ -225,7 +229,7 @@ const SupabaseGridLayout = forwardRef((props )} {mounted && - createPortal(, document.body)} + createPortal(, document.body)}
) }) diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index 61efa5c7b37..9b513d889f9 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -1,6 +1,15 @@ import { saveAs } from 'file-saver' import { useState, ReactNode } from 'react' -import { Button, IconDownload, IconX, IconTrash, Dropdown, IconChevronDown } from 'ui' +import { + Button, + IconDownload, + IconX, + IconTrash, + Dropdown, + IconChevronDown, + IconFileText, + IconArrowUp, +} from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' import { checkPermissions, useStore, useUrlState } from 'hooks' @@ -31,6 +40,7 @@ export type HeaderProps = { isRefetching: boolean onAddColumn?: () => void onAddRow?: () => void + onImportData?: () => void headerActions?: ReactNode customHeader: ReactNode } @@ -41,6 +51,7 @@ const Header = ({ filters, onAddColumn, onAddRow, + onImportData, headerActions, customHeader, isRefetching, @@ -63,6 +74,7 @@ const Header = ({ isRefetching={isRefetching} onAddColumn={onAddColumn} onAddRow={onAddRow} + onImportData={onImportData} /> )} @@ -81,8 +93,15 @@ type DefaultHeaderProps = { isRefetching: boolean onAddColumn?: () => void onAddRow?: () => void + onImportData?: () => void } -const DefaultHeader = ({ table, isRefetching, onAddColumn, onAddRow }: DefaultHeaderProps) => { +const DefaultHeader = ({ + table, + isRefetching, + onAddColumn, + onAddRow, + onImportData, +}: DefaultHeaderProps) => { const canAddNew = onAddRow !== undefined || onAddColumn !== undefined // [Joshen] Using this logic to block both column and row creation/update/delete @@ -115,7 +134,6 @@ const DefaultHeader = ({ table, isRefetching, onAddColumn, onAddRow }: DefaultHe key="add-row" className="group" onClick={onAddRow} - disabled={onAddRow === undefined} icon={
@@ -142,7 +160,6 @@ const DefaultHeader = ({ table, isRefetching, onAddColumn, onAddRow }: DefaultHe key="add-column" className="group" onClick={onAddColumn} - disabled={onAddColumn === undefined} icon={
@@ -163,6 +180,40 @@ const DefaultHeader = ({ table, isRefetching, onAddColumn, onAddRow }: DefaultHe , ] : []), + ...(onImportData !== undefined + ? [ + + //
+ //
+ //
+ //
+
+ + +
+ } + > +
+

Import data from CSV

+

Insert new rows from a CSV

+
+ , + ] + : []), ]} >
- - {spreadsheetData.headers.length > 0 && ( -
-
-
-

Content Preview

-

- Your table will have {spreadsheetData.rowCount.toLocaleString()} rows and the - following {spreadsheetData.headers.length} columns. -

-

- Here is a preview of your table (up to the first 20 columns and first 20 rows). -

-
- -
- {errors.length > 0 && ( -
+ + {spreadsheetData.headers.length > 0 && ( + <> +
+ +
+ +
+
-

Issues found in spreadsheet

-

- Your table can still be created nonetheless despite issues in the following - rows. -

+

Preview data to be imported

+
+

+ {selectedTable === undefined + ? `Your table will have ${spreadsheetData.rowCount.toLocaleString()} rows and the + following ${spreadsheetData.headers.length} columns.` + : `A total of ${spreadsheetData.rowCount.toLocaleString()} rows will be added to the table "${ + selectedTable.name + }"`} +

+

+ Here is a preview of the data that will be added (up to the first 20 columns + and first 20 rows). +

+
+ +
+ {errors.length > 0 && (
- {errors.map((error: any, idx: number) => { - const key = `import-error-${idx}` - const isExpanded = expandedErrors.includes(key) - return ( -
-
- onSelectExpandError(key)} - /> -

Row: {error.row}

-

{error.message}

- {error.data?.__parsed_extra && ( - <> - -

Extra field(s):

- {error.data?.__parsed_extra.map((value: any, i: number) => ( - - {value} - - ))} - +
+

Issues found in spreadsheet

+

+ Your table can still be created nonetheless despite issues in the following + rows. +

+
+
+ {errors.map((error: any, idx: number) => { + const key = `import-error-${idx}` + const isExpanded = expandedErrors.includes(key) + return ( +
+
+ onSelectExpandError(key)} + /> +

Row: {error.row}

+

{error.message}

+ {error.data?.__parsed_extra && ( + <> + +

Extra field(s):

+ {error.data?.__parsed_extra.map((value: any, i: number) => ( + + {value} + + ))} + + )} +
+ {isExpanded && ( + )}
- {isExpanded && ( - - )} -
- ) - })} + ) + })} +
-
- )} -
- )} -
+ )} +
+ + + )} ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreview.tsx index dc8a98ead37..3869815d1fd 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreview.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreview.tsx @@ -1,6 +1,7 @@ import { FC } from 'react' import { isUndefined } from 'lodash' import DataGrid from '@supabase/react-data-grid' +import clsx from 'clsx' interface Props { headers: string[] @@ -16,26 +17,39 @@ const SpreadsheetPreview: FC = ({ headers = [], rows = [] }) => { return ( { + const columnValues = previewRows.map((row) => row[header].toString()) + const maxLength = Math.max(...columnValues.map((el) => el.length)) + const maxWidth = maxLength > 20 ? 200 : maxLength * 10 + return { key: header, name: header, - width: header.length * 10, + width: maxWidth, resizable: true, headerRenderer: () => (
-

{header}

+

{header}

), - formatter: ({ row }: { row: any }) => ( - - {isUndefined(row[header]) ? 'NULL' : row[header]} - - ), + formatter: ({ row }: { row: any }) => { + const isEmpty = !row[header] + return ( + + {isEmpty ? 'NULL' : row[header]} + + ) + }, } })} rows={previewRows} className="!border-l !border-r" - style={{ height: `${34 + 34 * (previewRows.length || 1)}px` }} + // style={{ height: `${34 + 34 * (previewRows.length || 1)}px` }} + style={{ height: '250px' }} /> ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts index da2fc543765..c3b8784850e 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts @@ -16,4 +16,5 @@ export interface ImportContent { rowCount: number rows: object[] columnTypeMap: Dictionary + resolve: () => void } diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index fbe99620f64..a7745e5039d 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -129,7 +129,11 @@ export interface IMetaStore { columns: ColumnField[], isRealtimeEnabled: boolean ) => any - + insertRowsViaSpreadsheet: ( + file: any, + table: PostgresTable, + onProgressUpdate: (progress: number) => void + ) => void setProjectDetails: (details: { ref: string; connectionString?: string }) => void } export default class MetaStore implements IMetaStore { From 1c846ddce061158f051ce6ee61fa7527501395ce Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 4 Apr 2023 01:34:11 +0800 Subject: [PATCH 04/12] Support omitting of columns from imported CSV --- .../SidePanelEditor/SidePanelEditor.tsx | 3 +- .../SpreadSheetImportConfiguration.tsx | 73 ++++++++ .../SpreadsheetImport/SpreadsheetImport.tsx | 123 ++++--------- .../SpreadsheetImportPreview.tsx | 161 ++++++++++++++++++ ...Preview.tsx => SpreadsheetPreviewGrid.tsx} | 36 ++-- .../TableEditor/ColumnManagement.tsx | 10 +- .../TableEditor/TableEditor.types.ts | 1 + studio/stores/pgmeta/MetaStore.ts | 11 +- 8 files changed, 302 insertions(+), 116 deletions(-) create mode 100644 studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx create mode 100644 studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx rename studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/{SpreadsheetPreview.tsx => SpreadsheetPreviewGrid.tsx} (65%) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 704abef218f..5fca440ca65 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -397,7 +397,7 @@ const SidePanelEditor = ({ return console.error('no project or table selected') } - const { file, rowCount, resolve } = importContent + const { file, rowCount, selectedHeaders, resolve } = importContent const toastId = ui.setNotification({ category: 'loading', message: `Adding ${rowCount.toLocaleString()} rows to ${selectedTable.name}`, @@ -405,6 +405,7 @@ const SidePanelEditor = ({ const { error }: any = await meta.insertRowsViaSpreadsheet( file, selectedTable, + selectedHeaders, (progress: number) => { ui.setNotification({ id: toastId, diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx new file mode 100644 index 00000000000..61c40fa6866 --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx @@ -0,0 +1,73 @@ +import clsx from 'clsx' +import { useState } from 'react' +import { SidePanel, Button, IconChevronDown } from 'ui' +import { SpreadsheetData } from './SpreadsheetImport.types' + +interface SpreadSheetImportConfigurationProps { + spreadsheetData: SpreadsheetData + selectedHeaders: string[] + onToggleHeader: (header: string) => void +} + +const SpreadsheetImportConfiguration = ({ + spreadsheetData, + selectedHeaders, + onToggleHeader, +}: SpreadSheetImportConfigurationProps) => { + const [expandConfiguration, setExpandConfiguration] = useState(false) + + return ( + +
+
+

Configure import data

+
+
+
+
+
+
+

Select which columns to import

+

+ By default, all columns are selected to be imported from your CSV +

+
+
+ {spreadsheetData.headers.map((header) => { + const isSelected = selectedHeaders.includes(header) + return ( + + ) + })} +
+
+ {/* Setting null perhaps idk */} +
+
+
+ ) +} + +export default SpreadsheetImportConfiguration diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx index 50e82e3f310..18f4df1777a 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx @@ -1,12 +1,11 @@ import { useCallback, useState, FC, useEffect } from 'react' import { debounce, includes } from 'lodash' -import { SidePanel, Tabs, IconArrowRight, IconChevronRight } from 'ui' +import { SidePanel, Tabs } from 'ui' import { useStore } from 'hooks' import ActionBar from '../ActionBar' import SpreadSheetTextInput from './SpreadSheetTextInput' import SpreadSheetFileUpload from './SpreadSheetFileUpload' -import SpreadsheetPreview from './SpreadsheetPreview' import { SpreadsheetData } from './SpreadsheetImport.types' import { acceptedFileExtension, @@ -15,6 +14,8 @@ import { } from './SpreadsheetImport.utils' import { UPLOAD_FILE_TYPES, EMPTY_SPREADSHEET_DATA } from './SpreadsheetImport.constants' import { ImportContent } from '../TableEditor/TableEditor.types' +import SpreadsheetImportConfiguration from './SpreadSheetImportConfiguration' +import SpreadsheetImportPreview from './SpreadsheetImportPreview' interface Props { debounceDuration?: number @@ -27,13 +28,13 @@ interface Props { } const SpreadsheetImport: FC = ({ + visible = false, debounceDuration = 250, headers = [], rows = [], selectedTable, saveContent, closePanel, - visible = false, }) => { const { ui } = useStore() @@ -53,7 +54,7 @@ const SpreadsheetImport: FC = ({ columnTypeMap: {}, }) const [errors, setErrors] = useState([]) - const [expandedErrors, setExpandedErrors] = useState([]) + const [selectedHeaders, setSelectedHeaders] = useState([]) const onProgressUpdate = (progress: number) => { setParseProgress(progress) @@ -84,6 +85,7 @@ const SpreadsheetImport: FC = ({ } setErrors(errors) + setSelectedHeaders(headers) setSpreadsheetData({ headers, rows: previewRows, rowCount, columnTypeMap }) } event.target.value = '' @@ -94,7 +96,6 @@ const SpreadsheetImport: FC = ({ setSpreadsheetData(EMPTY_SPREADSHEET_DATA) setUploadedFile(null) setErrors([]) - setExpandedErrors([]) } const readSpreadsheetText = async (text: string) => { @@ -121,11 +122,21 @@ const SpreadsheetImport: FC = ({ handler(event.target.value) } - const onSelectExpandError = (key: string) => { - if (expandedErrors.includes(key)) { - setExpandedErrors(expandedErrors.filter((error) => error !== key)) + const onToggleHeader = (header: string) => { + const updatedSelectedHeaders = selectedHeaders.includes(header) + ? selectedHeaders.filter((h) => h !== header) + : selectedHeaders.concat([header]) + setSelectedHeaders(updatedSelectedHeaders) + } + + const onConfirm = (resolve: () => void) => { + if (selectedHeaders.length === 0) { + return ui.setNotification({ + category: 'error', + message: 'Please select at least one header from your CSV', + }) } else { - setExpandedErrors(expandedErrors.concat([key])) + saveContent({ file: uploadedFile, ...spreadsheetData, selectedHeaders, resolve }) } } @@ -152,13 +163,7 @@ const SpreadsheetImport: FC = ({ backButtonLabel="Cancel" applyButtonLabel={selectedTable === undefined ? 'Save' : 'Import data'} closePanel={closePanel} - applyFunction={(resolve: () => void) => { - saveContent({ - file: uploadedFile, - ...spreadsheetData, - resolve, - }) - }} + applyFunction={onConfirm} /> } > @@ -184,79 +189,19 @@ const SpreadsheetImport: FC = ({
- -
-
-
-

Preview data to be imported

-
-

- {selectedTable === undefined - ? `Your table will have ${spreadsheetData.rowCount.toLocaleString()} rows and the - following ${spreadsheetData.headers.length} columns.` - : `A total of ${spreadsheetData.rowCount.toLocaleString()} rows will be added to the table "${ - selectedTable.name - }"`} -

-

- Here is a preview of the data that will be added (up to the first 20 columns - and first 20 rows). -

-
-
- -
- {errors.length > 0 && ( -
-
-

Issues found in spreadsheet

-

- Your table can still be created nonetheless despite issues in the following - rows. -

-
-
- {errors.map((error: any, idx: number) => { - const key = `import-error-${idx}` - const isExpanded = expandedErrors.includes(key) - return ( -
-
- onSelectExpandError(key)} - /> -

Row: {error.row}

-

{error.message}

- {error.data?.__parsed_extra && ( - <> - -

Extra field(s):

- {error.data?.__parsed_extra.map((value: any, i: number) => ( - - {value} - - ))} - - )} -
- {isExpanded && ( - - )} -
- ) - })} -
-
- )} -
-
+ + + + )} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx new file mode 100644 index 00000000000..9acc61d247e --- /dev/null +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx @@ -0,0 +1,161 @@ +import clsx from 'clsx' +import { useEffect, useState } from 'react' +import { + SidePanel, + Badge, + Button, + IconChevronDown, + IconChevronRight, + IconArrowRight, + IconAlertCircle, +} from 'ui' + +import { SpreadsheetData } from './SpreadsheetImport.types' +import SpreadsheetPreviewGrid from './SpreadsheetPreviewGrid' +import { PostgresTable } from '@supabase/postgres-meta' + +const MAX_ROWS = 20 +const MAX_HEADERS = 20 + +interface SpreadsheetImportPreviewProps { + selectedTable: PostgresTable + selectedHeaders: string[] + spreadsheetData: SpreadsheetData + errors?: any[] +} + +const SpreadsheetImportPreview = ({ + selectedTable, + selectedHeaders, + spreadsheetData, + errors = [], +}: SpreadsheetImportPreviewProps) => { + const [expandPreview, setExpandPreview] = useState(false) + const [expandedErrors, setExpandedErrors] = useState([]) + + const { headers, rows } = spreadsheetData + const previewHeaders = headers + .filter((header) => selectedHeaders.includes(header)) + .slice(0, MAX_HEADERS) + const previewRows = rows.slice(0, MAX_ROWS) + + useEffect(() => { + setExpandPreview(true) + }, [spreadsheetData]) + + const onSelectExpandError = (key: string) => { + if (expandedErrors.includes(key)) { + setExpandedErrors(expandedErrors.filter((error) => error !== key)) + } else { + setExpandedErrors(expandedErrors.concat([key])) + } + } + + return ( + +
+
+
+

Preview data to be imported

+ {errors.length > 0 && {errors.length} errors present} +
+
+
+
+
+

+ {selectedTable === undefined + ? `Your table will have ${spreadsheetData.rowCount.toLocaleString()} rows and the + following ${spreadsheetData.headers.length} columns.` + : `A total of ${spreadsheetData.rowCount.toLocaleString()} rows will be added to the table "${ + selectedTable.name + }"`} +

+

+ Here is a preview of the data that will be added (up to the first 20 columns and first + 20 rows). +

+
+
+ {previewHeaders.length > 0 && previewRows.length > 0 ? ( + + ) : ( +
+ +

+ {previewHeaders.length === 0 + ? 'No headers have been selected' + : previewRows.length === 0 + ? 'Your CSV contains no data' + : ''} +

+
+ )} +
+ {errors.length > 0 && ( +
+
+

Issues found in spreadsheet

+

+ Your table can still be created nonetheless despite issues in the following rows. +

+
+
+ {errors.map((error: any, idx: number) => { + const key = `import-error-${idx}` + const isExpanded = expandedErrors.includes(key) + return ( +
+
+ onSelectExpandError(key)} + /> +

Row: {error.row}

+

{error.message}

+ {error.data?.__parsed_extra && ( + <> + +

Extra field(s):

+ {error.data?.__parsed_extra.map((value: any, i: number) => ( + + {value} + + ))} + + )} +
+ {isExpanded && ( + + )} +
+ ) + })} +
+
+ )} +
+
+ ) +} + +export default SpreadsheetImportPreview diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreviewGrid.tsx similarity index 65% rename from studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreview.tsx rename to studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreviewGrid.tsx index 3869815d1fd..f98bc945cf2 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreview.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreviewGrid.tsx @@ -1,19 +1,23 @@ -import { FC } from 'react' -import { isUndefined } from 'lodash' -import DataGrid from '@supabase/react-data-grid' import clsx from 'clsx' - -interface Props { - headers: string[] - rows?: any[] -} +import DataGrid from '@supabase/react-data-grid' const MAX_ROWS = 20 const MAX_HEADERS = 20 -const SpreadsheetPreview: FC = ({ headers = [], rows = [] }) => { +interface SpreadsheetPreviewGridProps { + headers: string[] + rows?: any[] + height?: number +} + +const SpreadsheetPreviewGrid = ({ + headers = [], + rows = [], + height, +}: SpreadsheetPreviewGridProps) => { const previewHeaders = headers.slice(0, MAX_HEADERS) const previewRows = rows.slice(0, MAX_ROWS) + return ( { @@ -27,19 +31,14 @@ const SpreadsheetPreview: FC = ({ headers = [], rows = [] }) => { width: maxWidth, resizable: true, headerRenderer: () => ( -
+

{header}

), formatter: ({ row }: { row: any }) => { const isEmpty = !row[header] return ( - + {isEmpty ? 'NULL' : row[header]} ) @@ -48,10 +47,9 @@ const SpreadsheetPreview: FC = ({ headers = [], rows = [] }) => { })} rows={previewRows} className="!border-l !border-r" - // style={{ height: `${34 + 34 * (previewRows.length || 1)}px` }} - style={{ height: '250px' }} + style={{ height: height || `${34 + 34 * (previewRows.length || 1)}px` }} /> ) } -export default SpreadsheetPreview +export default SpreadsheetPreviewGrid diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx index d01b213b3ab..5c55f64b17e 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx @@ -163,12 +163,10 @@ const ColumnManagement: FC = ({
{hasImportContent && ( -
-

- Your table will be created with {importContent?.rowCount?.toLocaleString()} rows and - the following {columns.length} columns. -

-
+

+ Your table will be created with {importContent?.rowCount?.toLocaleString()} rows and the + following {columns.length} columns. +

)} {primaryKeyColumns.length === 0 && ( diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts index c3b8784850e..eca1af27df7 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts @@ -16,5 +16,6 @@ export interface ImportContent { rowCount: number rows: object[] columnTypeMap: Dictionary + selectedHeaders: string[] resolve: () => void } diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index a7745e5039d..b2b2267e2c8 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -132,6 +132,7 @@ export interface IMetaStore { insertRowsViaSpreadsheet: ( file: any, table: PostgresTable, + selectedHeaders: string[], onProgressUpdate: (progress: number) => void ) => void setProjectDetails: (details: { ref: string; connectionString?: string }) => void @@ -656,6 +657,7 @@ export default class MetaStore implements IMetaStore { const { error }: any = await this.insertRowsViaSpreadsheet( importContent.file, table, + importContent.selectedHeaders, (progress: number) => { this.rootStore.ui.setNotification({ id: toastId, @@ -838,6 +840,7 @@ export default class MetaStore implements IMetaStore { async insertRowsViaSpreadsheet( file: any, table: PostgresTable, + selectedHeaders: string[], onProgressUpdate: (progress: number) => void ) { let chunkNumber = 0 @@ -853,9 +856,15 @@ export default class MetaStore implements IMetaStore { chunk: async (results: any, parser: any) => { parser.pause() + const formattedData = results.data.map((row: any) => { + const formattedRow: any = {} + selectedHeaders.forEach((header) => (formattedRow[header] = row[header])) + return formattedRow + }) + const insertQuery = new Query() .from(table.name, table.schema) - .insert(results.data) + .insert(formattedData) .toSql() const res = await this.query(insertQuery) From 9732e9cc46646cb73b0e2355d912344678fa97e4 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 4 Apr 2023 01:34:19 +0800 Subject: [PATCH 05/12] Fix --- .../SidePanelEditor/TableEditor/TableEditor.utils.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts index e58420703c6..c79570968ce 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts @@ -55,9 +55,12 @@ export const generateTableFieldFromPostgresTable = ( } export const formatImportedContentToColumnFields = (importContent: ImportContent) => { - const columnFields = importContent.headers.map((header: string) => { - const columnType = importContent.columnTypeMap[header] - return generateColumnField({ name: header, format: columnType }) - }) + const { headers, selectedHeaders, columnTypeMap } = importContent + const columnFields = headers + .filter((header) => selectedHeaders.includes(header)) + .map((header) => { + const columnType = columnTypeMap[header] + return generateColumnField({ name: header, format: columnType }) + }) return columnFields } From 215fa3ccda480719c81c33f104e49b0130e24643 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 4 Apr 2023 01:51:53 +0800 Subject: [PATCH 06/12] Small fixes --- .../SpreadsheetImport/SpreadSheetFileUpload.tsx | 6 +++--- .../SpreadsheetImport/SpreadSheetTextInput.tsx | 2 +- .../SpreadsheetImportPreview.tsx | 16 ++++++++-------- .../SpreadsheetImport/SpreadsheetPreviewGrid.tsx | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx index cbbeea7f9cc..286d80c2665 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx @@ -76,9 +76,9 @@ const SpreadSheetFileUpload: FC = ({
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx index a5641296883..54b30ce6c94 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx @@ -15,7 +15,7 @@ const SpreadSheetTextInput: FC = ({ input, onInputChange }) => ( include any special characters other than hyphens (-) or underscores ( _).

-

+

Tip: Datetime columns should be formatted as YYYY-MM-DD HH:mm:ss

diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx index 9acc61d247e..d29699457e3 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx @@ -57,7 +57,7 @@ const SpreadsheetImportPreview = ({

Preview data to be imported

- {errors.length > 0 && {errors.length} errors present} + {errors.length > 0 && {errors.length} issues found}
- {errors.length > 0 && ( + {(!isCompatible || errors.length > 0) && (

Issues found in spreadsheet

-

- Your table can still be created nonetheless despite issues in the following rows. -

+ {isCompatible && ( +

+ {selectedTable !== undefined + ? 'This CSV can still be imported into your table despite issues in the following rows.' + : 'Your table can still be created nonetheless despite issues in the following rows.'} +

+ )}
+ {!isCompatible && ( +
+
+
+
+
+

+ This CSV cannot be imported into your + table due to incompatible headers: {incompatibleHeaders.join(', ')} +

+
+
+ )} {errors.map((error: any, idx: number) => { const key = `import-error-${idx}` const isExpanded = expandedErrors.includes(key) + return (
-
- onSelectExpandError(key)} - /> -

Row: {error.row}

+
onSelectExpandError(key)} + > + {error.data !== undefined ? ( + + ) : ( +
+
+
+ )} + {error.data !== undefined &&

Row: {error.row}

}

{error.message}

{error.data?.__parsed_extra && ( <> @@ -141,7 +172,7 @@ const SpreadsheetImportPreview = ({ )}
- {isExpanded && ( + {error.data !== undefined && isExpanded && ( Date: Tue, 4 Apr 2023 13:24:39 +0800 Subject: [PATCH 09/12] Change to use Collapsible component --- .../SpreadSheetImportConfiguration.tsx | 56 ++-- .../SpreadsheetImportPreview.tsx | 256 +++++++++--------- 2 files changed, 157 insertions(+), 155 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx index 61c40fa6866..f77a9d7194d 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetImportConfiguration.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx' import { useState } from 'react' -import { SidePanel, Button, IconChevronDown } from 'ui' +import { SidePanel, Button, IconChevronDown, Collapsible } from 'ui' import { SpreadsheetData } from './SpreadsheetImport.types' interface SpreadSheetImportConfigurationProps { @@ -17,30 +17,29 @@ const SpreadsheetImportConfiguration = ({ const [expandConfiguration, setExpandConfiguration] = useState(false) return ( - -
-
-

Configure import data

-
-
-
-
-
+ + + +
+

Configure import data

+
+
+
+ + +

Select which columns to import

@@ -63,10 +62,9 @@ const SpreadsheetImportConfiguration = ({ })}

- {/* Setting null perhaps idk */} -
-
- + + + ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx index 571a046b793..8e7a56e04fe 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx @@ -4,6 +4,7 @@ import { SidePanel, Badge, Button, + Collapsible, IconChevronDown, IconChevronRight, IconArrowRight, @@ -56,136 +57,139 @@ const SpreadsheetImportPreview = ({ } return ( - -
-
-
-

Preview data to be imported

- {!isCompatible && Data incompatible} - {errors.length > 0 && {errors.length} issues found} + + + +
+
+

Preview data to be imported

+ {!isCompatible && Data incompatible} + {errors.length > 0 && {errors.length} issues found} +
+
-
-
-
-
-

- {selectedTable === undefined - ? `Your table will have ${spreadsheetData.rowCount.toLocaleString()} rows and the + + + + +

+

+ {selectedTable === undefined + ? `Your table will have ${spreadsheetData.rowCount.toLocaleString()} rows and the following ${spreadsheetData.headers.length} columns.` - : `A total of ${spreadsheetData.rowCount.toLocaleString()} rows will be added to the table "${ - selectedTable.name - }"`} -

-

- Here is a preview of the data that will be added (up to the first 20 columns and first - 20 rows). -

-
-
- {previewHeaders.length > 0 && previewRows.length > 0 ? ( - - ) : ( -
- -

- {previewHeaders.length === 0 - ? 'No headers have been selected' - : previewRows.length === 0 - ? 'Your CSV contains no data' - : ''} -

+ : `A total of ${spreadsheetData.rowCount.toLocaleString()} rows will be added to the table "${ + selectedTable.name + }"`} +

+

+ Here is a preview of the data that will be added (up to the first 20 columns and first + 20 rows). +

+
+
+ {previewHeaders.length > 0 && previewRows.length > 0 ? ( + + ) : ( +
+ +

+ {previewHeaders.length === 0 + ? 'No headers have been selected' + : previewRows.length === 0 + ? 'Your CSV contains no data' + : ''} +

+
+ )} +
+ {(!isCompatible || errors.length > 0) && ( +
+
+

Issues found in spreadsheet

+ {isCompatible && ( +

+ {selectedTable !== undefined + ? 'This CSV can still be imported into your table despite issues in the following rows.' + : 'Your table can still be created nonetheless despite issues in the following rows.'} +

+ )} +
+
+ {!isCompatible && ( +
+
+
+
+
+

+ This CSV cannot be imported into your + table due to incompatible headers: {incompatibleHeaders.join(', ')} +

+
+
+ )} + {errors.map((error: any, idx: number) => { + const key = `import-error-${idx}` + const isExpanded = expandedErrors.includes(key) + + return ( +
+
onSelectExpandError(key)} + > + {error.data !== undefined ? ( + + ) : ( +
+
+
+ )} + {error.data !== undefined && ( +

Row: {error.row}

+ )} +

{error.message}

+ {error.data?.__parsed_extra && ( + <> + +

Extra field(s):

+ {error.data?.__parsed_extra.map((value: any, i: number) => ( + + {value} + + ))} + + )} +
+ {error.data !== undefined && isExpanded && ( + + )} +
+ ) + })} +
)} -
- {(!isCompatible || errors.length > 0) && ( -
-
-

Issues found in spreadsheet

- {isCompatible && ( -

- {selectedTable !== undefined - ? 'This CSV can still be imported into your table despite issues in the following rows.' - : 'Your table can still be created nonetheless despite issues in the following rows.'} -

- )} -
-
- {!isCompatible && ( -
-
-
-
-
-

- This CSV cannot be imported into your - table due to incompatible headers: {incompatibleHeaders.join(', ')} -

-
-
- )} - {errors.map((error: any, idx: number) => { - const key = `import-error-${idx}` - const isExpanded = expandedErrors.includes(key) - - return ( -
-
onSelectExpandError(key)} - > - {error.data !== undefined ? ( - - ) : ( -
-
-
- )} - {error.data !== undefined &&

Row: {error.row}

} -

{error.message}

- {error.data?.__parsed_extra && ( - <> - -

Extra field(s):

- {error.data?.__parsed_extra.map((value: any, i: number) => ( - - {value} - - ))} - - )} -
- {error.data !== undefined && isExpanded && ( - - )} -
- ) - })} -
-
- )} -
- + + + ) } From 438a93909139e549594d3e0942034670f9c31c4e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 4 Apr 2023 13:34:37 +0800 Subject: [PATCH 10/12] fix typo --- .../LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx index 7565f658aea..08b1be53ec6 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx +++ b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx @@ -18,7 +18,7 @@ interface Props { export function TicketBrickWallSlider({ users, reverse, speed = 10000 }: Props) { const ref = useRef(null) - const [swiperInstace, setSwiperInstance] = useState(null) + const [swiperInstance, setSwiperInstance] = useState(null) const { width } = useWindowSize() const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw7' const BUCKET_FOLDER_VERSION = 'v3' @@ -29,7 +29,7 @@ export function TicketBrickWallSlider({ users, reverse, speed = 10000 }: Props) useEffect(() => { // trigger autplay if viewport resize - if (swiperInstace) swiperInstace?.autoplay?.run() + if (swiperInstance) swiperInstance?.autoplay?.run() }, [width]) return ( From eb127e89ea199641a0b95ec97f27957d178be893 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 4 Apr 2023 14:50:01 +0800 Subject: [PATCH 11/12] Fix paste text --- .../LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx | 2 +- .../SidePanelEditor/SidePanelEditor.tsx | 10 +++++----- .../SpreadsheetImport/SpreadsheetImport.tsx | 1 + .../SpreadsheetImport/SpreadsheetImportPreview.tsx | 6 +++++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx index 08b1be53ec6..59271c61e43 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx +++ b/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx @@ -28,7 +28,7 @@ export function TicketBrickWallSlider({ users, reverse, speed = 10000 }: Props) }/${BUCKET_FOLDER_VERSION}/${username}.png` useEffect(() => { - // trigger autplay if viewport resize + // trigger autoplay if viewport resize if (swiperInstance) swiperInstance?.autoplay?.run() }, [width]) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 09a5ee71690..9a27979629b 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -425,16 +425,16 @@ const SidePanelEditor = ({ }) resolve() } else { - ui.setNotification({ - id: toastId, - category: 'success', - message: `Successfully imported ${rowCount} rows of data into ${selectedTable.name}`, - }) await Promise.all([ queryClient.invalidateQueries( sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name]) ), ]) + ui.setNotification({ + id: toastId, + category: 'success', + message: `Successfully imported ${rowCount} rows of data into ${selectedTable.name}`, + }) resolve() closePanel() } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx index 27467d6e518..4a2c7954a3c 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.tsx @@ -121,6 +121,7 @@ const SpreadsheetImport: FC = ({ }) } setErrors(errors) + setSelectedHeaders(headers) setSpreadsheetData({ headers, rows, rowCount: rows.length, columnTypeMap }) } else { setSpreadsheetData(EMPTY_SPREADSHEET_DATA) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx index 8e7a56e04fe..5e967af0f64 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx @@ -134,7 +134,11 @@ const SpreadsheetImportPreview = ({

This CSV cannot be imported into your - table due to incompatible headers: {incompatibleHeaders.join(', ')} + table due to incompatible headers: +
+ The column{incompatibleHeaders.length > 1 ? 's' : ''}{' '} + {incompatibleHeaders.map((x) => `"${x}"`).join(', ')}{' '} + {incompatibleHeaders.length > 1 ? 'are' : 'is'} not present in your table

From b4f763f8ea137b6cdf5c16623a6a5ecfee386ae3 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 4 Apr 2023 15:16:50 +0800 Subject: [PATCH 12/12] Remove conflicting max w style in side panel --- packages/ui/src/lib/theme/defaultTheme.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/lib/theme/defaultTheme.ts b/packages/ui/src/lib/theme/defaultTheme.ts index c33ae2a99da..359c09fe6a9 100644 --- a/packages/ui/src/lib/theme/defaultTheme.ts +++ b/packages/ui/src/lib/theme/defaultTheme.ts @@ -978,7 +978,6 @@ export default { flex flex-col fixed inset-y-0 - max-w-full h-screen border-l border-overlay-border shadow-xl