- If you run into a server version mismatch error, you will need to update{' '}
- pg_dump before running the command.
-
-
+
+ Note: pg_dump needs to match your project's Postgres version. If you run
+ into a server version mismatch error, you will need to update pg_dump{' '}
+ before running the command.
+
)}
diff --git a/apps/studio/components/grid/components/header/Header.tsx b/apps/studio/components/grid/components/header/Header.tsx
index baeeecc662fcf..bb401db90e8ac 100644
--- a/apps/studio/components/grid/components/header/Header.tsx
+++ b/apps/studio/components/grid/components/header/Header.tsx
@@ -504,11 +504,9 @@ const RowHeader = () => {
Export
-
+ Export as CSVExport as SQL
- {/* [Joshen] Should make this available for all cases, but that'll involve updating
- the Dialog's SQL output to be dynamic based on any filters applied */}
{snap.allRowsSelected ? (
setShowExportModal(true)}>
@@ -535,7 +533,9 @@ const RowHeader = () => {
setShowExportModal(false)}
/>
diff --git a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx
index e693516ccb869..c8ee912c07f37 100644
--- a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx
+++ b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx
@@ -1,5 +1,5 @@
import saveAs from 'file-saver'
-import { Clipboard, Copy, Download, Edit, Lock, MoreHorizontal, Trash } from 'lucide-react'
+import { Clipboard, Copy, Download, Edit, Lock, MoreVertical, Trash } from 'lucide-react'
import Link from 'next/link'
import Papa from 'papaparse'
import { toast } from 'sonner'
@@ -30,6 +30,7 @@ import { useTableEditorStateSnapshot } from 'state/table-editor'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import {
Badge,
+ Button,
cn,
copyToClipboard,
DropdownMenu,
@@ -242,7 +243,7 @@ const EntityListItem: ItemRenderer = ({
isOpened: isOpened && !isPreview,
isPreview,
}),
- 'px-4'
+ 'pl-4 pr-1'
)}
onDoubleClick={(e) => {
e.preventDefault()
@@ -285,8 +286,16 @@ const EntityListItem: ItemRenderer = ({
{canEdit && (
-
-
+
+ }
+ onClick={(e) => e.preventDefault()}
+ />
{
- const { id: _id } = useParams()
+ const { id: _id, ref: projectRef } = useParams()
const id = _id ? Number(_id) : undefined
const snap = useTableEditorStateSnapshot()
const { selectedSchema, setSelectedSchema } = useQuerySchemaState()
const isMobile = useBreakpoint()
const [searchText, setSearchText] = useState('')
- const [tableToExport, setTableToExport] = useState<{ name: string; schema: string }>()
+ const [tableToExport, setTableToExport] = useState()
const [visibleTypes, setVisibleTypes] = useState(Object.values(ENTITY_TYPE))
const [sort, setSort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>(
'table-editor-sort',
@@ -93,13 +95,24 @@ export const TableEditorMenu = () => {
id,
})
+ const tableEditorTabsCleanUp = useTableEditorTabsCleanUp()
+
+ const onSelectExportCLI = async (id: number) => {
+ const table = await getTableEditor({
+ id: id,
+ projectRef,
+ connectionString: project?.connectionString,
+ })
+ const supaTable = table && parseSupaTable(table)
+ setTableToExport(supaTable)
+ }
+
useEffect(() => {
if (selectedTable?.schema) {
setSelectedSchema(selectedTable.schema)
}
}, [selectedTable?.schema])
- const tableEditorTabsCleanUp = useTableEditorTabsCleanUp()
useEffect(() => {
// Clean up tabs + recent items for any tables that might have been removed outside of the dashboard session
if (entityTypes && !searchText) {
@@ -255,11 +268,7 @@ export const TableEditorMenu = () => {
projectRef: project?.ref!,
id: Number(id),
isSchemaLocked,
- onExportCLI: () => {
- const entity = entityTypes?.find((x) => x.id === id)
- if (!entity) return
- setTableToExport({ name: entity.name, schema: entity.schema })
- },
+ onExportCLI: () => onSelectExportCLI(Number(id)),
}}
getItemSize={() => 28}
hasNextPage={hasNextPage}
@@ -274,6 +283,7 @@ export const TableEditorMenu = () => {
{
@@ -283,5 +293,3 @@ export const TableEditorMenu = () => {
>
)
}
-
-export default TableEditorMenu
diff --git a/apps/studio/data/table-rows/table-rows-query.ts b/apps/studio/data/table-rows/table-rows-query.ts
index 5c8ef994fb6af..3e56d290f6f09 100644
--- a/apps/studio/data/table-rows/table-rows-query.ts
+++ b/apps/studio/data/table-rows/table-rows-query.ts
@@ -67,35 +67,15 @@ export async function executeWithRetry(
throw new Error('Max retries reached without success')
}
-// TODO: fetchAllTableRows is used for CSV export, but since it doesn't actually truncate anything, (compare to getTableRows)
-// this is not suitable and will cause crashes on the pg-meta side given big tables
-// (either when the number of rows exceeds Blob size or if the columns in the rows are too large).
-// We should handle those errors gracefully, maybe adding a hint to the user about how to extract
-// the CSV to their machine via a direct command line connection (e.g., pg_dump), which will be much more
-// reliable for large data extraction.
-export const fetchAllTableRows = async ({
- projectRef,
- connectionString,
+export const getAllTableRowsSql = ({
table,
filters = [],
sorts = [],
- roleImpersonationState,
- progressCallback,
}: {
- projectRef: string
- connectionString?: string | null
table: SupaTable
filters?: Filter[]
sorts?: Sort[]
- roleImpersonationState?: RoleImpersonationState
- progressCallback?: (value: number) => void
}) => {
- if (IS_PLATFORM && !connectionString) {
- console.error('Connection string is required')
- return []
- }
-
- const rows: any[] = []
const query = new Query()
const arrayBasedColumns = table.columns
@@ -129,6 +109,40 @@ export const fetchAllTableRows = async ({
})
}
+ return queryChains
+}
+
+// TODO: fetchAllTableRows is used for CSV export, but since it doesn't actually truncate anything, (compare to getTableRows)
+// this is not suitable and will cause crashes on the pg-meta side given big tables
+// (either when the number of rows exceeds Blob size or if the columns in the rows are too large).
+// We should handle those errors gracefully, maybe adding a hint to the user about how to extract
+// the CSV to their machine via a direct command line connection (e.g., pg_dump), which will be much more
+// reliable for large data extraction.
+export const fetchAllTableRows = async ({
+ projectRef,
+ connectionString,
+ table,
+ filters = [],
+ sorts = [],
+ roleImpersonationState,
+ progressCallback,
+}: {
+ projectRef: string
+ connectionString?: string | null
+ table: SupaTable
+ filters?: Filter[]
+ sorts?: Sort[]
+ roleImpersonationState?: RoleImpersonationState
+ progressCallback?: (value: number) => void
+}) => {
+ if (IS_PLATFORM && !connectionString) {
+ console.error('Connection string is required')
+ return []
+ }
+
+ const rows: any[] = []
+ const queryChains = getAllTableRowsSql({ table, sorts, filters })
+
const rowsPerPage = 500
const THROTTLE_DELAY = 500
diff --git a/apps/studio/pages/project/[ref]/editor/[id].tsx b/apps/studio/pages/project/[ref]/editor/[id].tsx
index 7409c843e69f8..3a1f9c8697ae2 100644
--- a/apps/studio/pages/project/[ref]/editor/[id].tsx
+++ b/apps/studio/pages/project/[ref]/editor/[id].tsx
@@ -6,7 +6,7 @@ import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import TableEditorLayout from 'components/layouts/TableEditorLayout/TableEditorLayout'
-import TableEditorMenu from 'components/layouts/TableEditorLayout/TableEditorMenu'
+import { TableEditorMenu } from 'components/layouts/TableEditorLayout/TableEditorMenu'
import { useTableEditorQuery } from 'data/table-editor/table-editor-query'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import type { NextPageWithLayout } from 'types'
diff --git a/apps/studio/pages/project/[ref]/editor/index.tsx b/apps/studio/pages/project/[ref]/editor/index.tsx
index 55797c6e53def..71e4686868b24 100644
--- a/apps/studio/pages/project/[ref]/editor/index.tsx
+++ b/apps/studio/pages/project/[ref]/editor/index.tsx
@@ -6,7 +6,7 @@ import SidePanelEditor from 'components/interfaces/TableGridEditor/SidePanelEdit
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
import TableEditorLayout from 'components/layouts/TableEditorLayout/TableEditorLayout'
-import TableEditorMenu from 'components/layouts/TableEditorLayout/TableEditorMenu'
+import { TableEditorMenu } from 'components/layouts/TableEditorLayout/TableEditorMenu'
import { NewTab } from 'components/layouts/Tabs/NewTab'
import { useAppStateSnapshot } from 'state/app-state'
import { editorEntityTypes, useTabsStateSnapshot } from 'state/tabs'
diff --git a/apps/studio/pages/project/[ref]/editor/new.tsx b/apps/studio/pages/project/[ref]/editor/new.tsx
index 3a2e12a9e3b26..48546300b9268 100644
--- a/apps/studio/pages/project/[ref]/editor/new.tsx
+++ b/apps/studio/pages/project/[ref]/editor/new.tsx
@@ -5,7 +5,7 @@ import { SidePanelEditor } from 'components/interfaces/TableGridEditor'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
import TableEditorLayout from 'components/layouts/TableEditorLayout/TableEditorLayout'
-import TableEditorMenu from 'components/layouts/TableEditorLayout/TableEditorMenu'
+import { TableEditorMenu } from 'components/layouts/TableEditorLayout/TableEditorMenu'
import { NewTab } from 'components/layouts/Tabs/NewTab'
import type { NextPageWithLayout } from 'types'
From 81dda5b371292da014ecd3be93891b03d4fcf3c5 Mon Sep 17 00:00:00 2001
From: Joshen Lim
Date: Tue, 5 Aug 2025 14:02:41 +0700
Subject: [PATCH 2/7] Add link to edge function logs for cron history if cron
is using edge function (#37531)
* Add link to edge function logs for cron history if cron is using edge function
* Patch
---
.../CronJobs/CreateCronJobSheet.tsx | 1 +
.../Integrations/CronJobs/CronJobsTab.tsx | 2 +-
.../CronJobs/EdgeFunctionSection.tsx | 3 +-
.../Integrations/CronJobs/PreviousRunsTab.tsx | 81 ++++++++++++++-----
4 files changed, 65 insertions(+), 22 deletions(-)
diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx
index 4bc501ffe0b7b..14337533f7a54 100644
--- a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx
+++ b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx
@@ -119,6 +119,7 @@ const sqlFunctionSchema = z.object({
// When editing a cron job, we want to keep the original command as a snippet in case the user wants to manually edit it
snippet: z.string().trim(),
})
+
const sqlSnippetSchema = z.object({
type: z.literal('sql_snippet'),
snippet: z.string().trim().min(1),
diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx
index aa26e6410958c..b8c55cfdddeb1 100644
--- a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx
@@ -193,7 +193,7 @@ export const CronjobsTab = () => {
}}
onScroll={handleScroll}
renderers={{
- renderRow(key, props) {
+ renderRow(_, props) {
return (
}
export const EdgeFunctionSection = ({ form }: HTTPRequestFieldsProps) => {
- const { project: selectedProject } = useProjectContext()
const { ref } = useParams()
+ const { project: selectedProject } = useProjectContext()
const { data: functions, isSuccess, isLoading } = useEdgeFunctionsQuery({ projectRef: ref })
const edgeFunctions = useMemo(() => functions ?? [], [functions])
@@ -52,6 +52,7 @@ export const EdgeFunctionSection = ({ form }: HTTPRequestFieldsProps) => {
form.setValue('values.edgeFunctionName', functionUrl)
}
}, [edgeFunctions, form, isSuccess, selectedProject?.ref, selectedProject?.restUrl])
+
return (
{
renderCell: (props) => {
const value = col.value(props.row)
+ if (['start_time', 'end_time'].includes(col.id)) {
+ const formattedValue = dayjs((props.row as any)[(col as any).id]).valueOf()
+ return (
+