diff --git a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
index b5f0276ac2d51..7e8a38450e911 100644
--- a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
+++ b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
@@ -77,6 +77,14 @@ SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key
```
+
+
+
+```bash .env
+SUPABASE_URL=your_supabase_project_url
+SUPABASE_ANON_KEY=your_supabase_anon_key
+```
+
@@ -660,6 +668,105 @@ export default function Index() {
+
+
+
+
+
+```ts _index.tsx
+import { LoaderFunctionArgs } from 'react-router'
+import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ const headers = new Headers()
+
+ const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
+ cookies: {
+ getAll() {
+ return parseCookieHeader(request.headers.get('Cookie') ?? '')
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
+ )
+ },
+ },
+ })
+
+ return new Response('...', {
+ headers,
+ })
+}
+```
+
+
+
+
+
+```ts _index.tsx
+import { type ActionFunctionArgs } from '@react-router'
+import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
+
+export async function action({ request }: ActionFunctionArgs) {
+ const headers = new Headers()
+
+ const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
+ cookies: {
+ getAll() {
+ return parseCookieHeader(request.headers.get('Cookie') ?? '')
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
+ )
+ },
+ },
+ })
+
+ return new Response('...', {
+ headers,
+ })
+}
+```
+
+
+
+
+
+```ts _index.tsx
+import { type LoaderFunctionArgs } from "react-router";
+import { useLoaderData } from "react-router";
+import { createBrowserClient } from "@supabase/ssr";
+
+export async function loader({}: LoaderFunctionArgs) {
+ return {
+ env: {
+ SUPABASE_URL: process.env.SUPABASE_URL!,
+ SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!,
+ },
+ };
+}
+
+export default function Index() {
+ const { env } = useLoaderData();
+
+ const supabase = createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY);
+
+ return ...
+}
+```
+
+
+
+
+
+
void
}
-export const ExportDialog = ({ table, open, onOpenChange }: ExportDialogProps) => {
+export const ExportDialog = ({
+ table,
+ filters = [],
+ sorts = [],
+ ignoreRoleImpersonation = false,
+ open,
+ onOpenChange,
+}: ExportDialogProps) => {
const { ref: projectRef } = useParams()
+ const roleImpersonationState = useRoleImpersonationStateSnapshot()
const [selectedTab, setSelectedTab] = useState('csv')
@@ -48,10 +65,18 @@ export const ExportDialog = ({ table, open, onOpenChange }: ExportDialogProps) =
})
const outputName = `${table?.name}_rows`
+ const queryChains = !table ? undefined : getAllTableRowsSql({ table, sorts, filters })
+ const query = !!queryChains
+ ? ignoreRoleImpersonation
+ ? queryChains.toSql()
+ : wrapWithRoleImpersonation(
+ queryChains.toSql(),
+ roleImpersonationState as RoleImpersonationState
+ )
+ : ''
const csvExportCommand = `
-${connectionStrings.direct.psql} -c "COPY (SELECT * FROM "${table?.schema}"."${table?.name}") TO STDOUT WITH CSV HEADER DELIMITER ',';" > ${outputName}.csv
-`.trim()
+${connectionStrings.direct.psql} -c "COPY (${query}) TO STDOUT WITH CSV HEADER DELIMITER ',';" > ${outputName}.csv`.trim()
const sqlExportCommand = `
pg_dump -h ${db_host} -p ${db_port} -d ${db_name} -U ${db_user} --table="${table?.schema}.${table?.name}" --data-only --column-inserts > ${outputName}.sql
@@ -95,6 +120,12 @@ pg_dump -h ${db_host} -p ${db_port} -d ${db_name} -U ${db_user} --table="${table
value={sqlExportCommand}
className="[&_code]:text-[12px] [&_code]:text-foreground"
/>
+
@@ -107,15 +138,11 @@ pg_dump -h ${db_host} -p ${db_port} -d ${db_name} -U ${db_user} --table="${table
{selectedTab === 'sql' && (
-
-
- 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 CSV
Export 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/interfaces/Home/ProjectList/ProjectCard.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
index bab4d0c394f30..0f2283b1b973a 100644
--- a/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
+++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
@@ -65,7 +65,7 @@ const ProjectCard = ({
- {githubRepository}
+ {githubRepository}
>
)}
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/CronJobTableCell.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobTableCell.tsx
index 8ea872bb67a55..ea33c32f9285e 100644
--- a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobTableCell.tsx
+++ b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobTableCell.tsx
@@ -1,9 +1,10 @@
import dayjs from 'dayjs'
-import { Clipboard, Edit, MoreVertical, Trash } from 'lucide-react'
+import { Clipboard, Edit, MoreVertical, Play, Trash } from 'lucide-react'
import { parseAsString, useQueryState } from 'nuqs'
import { useState } from 'react'
import { toast } from 'sonner'
+import { useDatabaseCronJobRunCommandMutation } from 'data/database-cron-jobs/database-cron-job-run-mutation'
import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-infinite-query'
import { useDatabaseCronJobToggleMutation } from 'data/database-cron-jobs/database-cron-jobs-toggle-mutation'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
@@ -74,15 +75,29 @@ export const CronJobTableCell = ({
? getNextRun(schedule, latest_run)
: value
+ const { mutate: runCronJob, isLoading: isRunning } = useDatabaseCronJobRunCommandMutation({
+ onSuccess: () => {
+ toast.success(`Command from "${jobname}" ran successfully`)
+ },
+ })
+
const { mutate: toggleDatabaseCronJob, isLoading: isToggling } = useDatabaseCronJobToggleMutation(
{
- onSuccess: () => {
- toast.success('Successfully asdasd')
+ onSuccess: (_, vars) => {
+ toast.success(`Successfully ${vars.active ? 'enabled' : 'disabled'} "${jobname}"`)
setShowToggleModal(false)
},
}
)
+ const onRunCronJob = () => {
+ runCronJob({
+ projectRef: project?.ref!,
+ connectionString: project?.connectionString,
+ jobId: jobid,
+ })
+ }
+
const onConfirmToggle = () => {
toggleDatabaseCronJob({
projectRef: project?.ref!,
@@ -100,12 +115,23 @@ export const CronJobTableCell = ({
}
onClick={(e) => e.stopPropagation()}
/>
+ {
+ e.stopPropagation()
+ onRunCronJob()
+ }}
+ >
+
+ Run command
+
{
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 (
+
+
+
+ )
+ }
+
return (
{
connectionString: project?.connectionString,
jobId: jobId,
},
- { enabled: !!jobId, staleTime: 30 }
+ { enabled: !!jobId, staleTime: 30000 }
)
- useEffect(() => {
- // Refetch only the first page
- const timerId = setInterval(() => {
- refetch({ refetchPage: (_page, index) => index === 0 })
- }, 30000)
+ const { data: edgeFunctions = [] } = useEdgeFunctionsQuery({ projectRef: project?.ref })
- return () => clearInterval(timerId)
- }, [refetch])
+ const cronJobRuns = useMemo(() => data?.pages.flatMap((p) => p) || [], [data?.pages])
+ const cronJobValues = parseCronJobCommand(job?.command || '', project?.ref!)
+ const edgeFunction =
+ cronJobValues.type === 'edge_function' ? cronJobValues.edgeFunctionName : undefined
+ const edgeFunctionSlug = edgeFunction?.split('/functions/v1/').pop()
+ const isValidEdgeFunction = edgeFunctions.some((x) => x.slug === edgeFunctionSlug)
const handleScroll = useCallback(
(event: UIEvent
) => {
@@ -178,8 +199,6 @@ export const PreviousRunsTab = () => {
[fetchNextPage, isLoadingCronJobRuns]
)
- const cronJobRuns = useMemo(() => data?.pages.flatMap((p) => p) || [], [data?.pages])
-
return (
@@ -251,11 +270,16 @@ export const PreviousRunsTab = () => {
-
+
+ Command
{job?.command}
@@ -265,12 +289,29 @@ export const PreviousRunsTab = () => {
Explore
-
}>
- {/* [Terry] need to link to the exact jobid, but not currently supported */}
-
- View logs
-
-
+
+ }>
+ {/* [Terry] need to link to the exact jobid, but not currently supported */}
+
+ View Cron logs
+
+
+ {isValidEdgeFunction && (
+
+
+ View Edge Function logs
+
+
+ )}
+
>
)}
diff --git a/apps/studio/components/interfaces/Storage/BucketRow.tsx b/apps/studio/components/interfaces/Storage/BucketRow.tsx
index f272a89c53dea..78ef08e09c0b0 100644
--- a/apps/studio/components/interfaces/Storage/BucketRow.tsx
+++ b/apps/studio/components/interfaces/Storage/BucketRow.tsx
@@ -1,6 +1,6 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop } from 'lodash'
-import { ChevronDown, Columns3, Edit2, Trash, XCircle } from 'lucide-react'
+import { Columns3, Edit2, MoreVertical, Trash, XCircle } from 'lucide-react'
import Link from 'next/link'
import type { Bucket } from 'data/storage/buckets-query'
@@ -76,7 +76,7 @@ const BucketRow = ({
{canUpdateBuckets && isSelected ? (
- } className="mr-1 p-1.5 w-7" />
+ } className="mr-1 h-6 w-6" />
{bucket.type !== 'ANALYTICS' && (
diff --git a/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx b/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx
index e9b1ab5ef550a..002d48acbd82f 100644
--- a/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx
+++ b/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx
@@ -1,46 +1,21 @@
-import Link from 'next/link'
import { useRouter } from 'next/router'
import { PropsWithChildren } from 'react'
import { useParams } from 'common'
-import { useIsColumnLevelPrivilegesEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { ProductMenu } from 'components/ui/ProductMenu'
import { useAuthConfigPrefetch } from 'data/auth/auth-config-query'
import { withAuth } from 'hooks/misc/withAuth'
-import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui'
import ProjectLayout from '../ProjectLayout/ProjectLayout'
import { generateAuthMenu } from './AuthLayout.utils'
const AuthProductMenu = () => {
const router = useRouter()
const { ref: projectRef = 'default' } = useParams()
- const columnLevelPrivileges = useIsColumnLevelPrivilegesEnabled()
useAuthConfigPrefetch({ projectRef })
const page = router.pathname.split('/')[4]
- return (
- <>
-
- {columnLevelPrivileges && (
-
-
-
- Column Privileges has been shifted
-
-
- It can now be found in the menu under the database section.
-
-
- Head over to Database
-
-
-
-
-
- )}
- >
- )
+ return
}
const AuthLayout = ({ children }: PropsWithChildren<{}>) => {
diff --git a/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx b/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx
index b579a00c0786f..3895c1115a44a 100644
--- a/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx
@@ -1,21 +1,22 @@
import { PropsWithChildren } from 'react'
+import { cn } from 'ui'
interface ProductMenuBarProps {
title: string
+ className?: string
}
-const ProductMenuBar = ({ title, children }: PropsWithChildren) => {
+const ProductMenuBar = ({ title, children, className }: PropsWithChildren) => {
return (
{title}
-
{children}
+
{children}
)
}
diff --git a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
index 1e2a94dc71956..f183b8a9bb52e 100644
--- a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
@@ -10,8 +10,8 @@ import { AIAssistant } from 'components/ui/AIAssistantPanel/AIAssistant'
import { EditorPanel } from 'components/ui/EditorPanel/EditorPanel'
import { Loading } from 'components/ui/Loading'
import { ResourceExhaustionWarningBanner } from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner'
-import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
-import { useSelectedProject } from 'hooks/misc/useSelectedProject'
+import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
+import { useSelectedProject, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { withAuth } from 'hooks/misc/withAuth'
import { PROJECT_STATUS } from 'lib/constants'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
@@ -66,6 +66,7 @@ export interface ProjectLayoutProps {
productMenu?: ReactNode
selectedTable?: string
resizableSidebar?: boolean
+ productMenuClassName?: string
}
const ProjectLayout = forwardRef>(
@@ -79,13 +80,14 @@ const ProjectLayout = forwardRef {
const router = useRouter()
const [isClient, setIsClient] = useState(false)
- const selectedOrganization = useSelectedOrganization()
- const selectedProject = useSelectedProject()
+ const { data: selectedOrganization } = useSelectedOrganizationQuery()
+ const { data: selectedProject } = useSelectedProjectQuery()
const {
editorPanel,
@@ -190,7 +192,9 @@ const ProjectLayout = forwardRef
- {productMenu}
+
+ {productMenu}
+
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()}
+ />
) => {
@@ -16,7 +17,7 @@ const TableEditorLayout = ({ children }: PropsWithChildren<{}>) => {
)
}
- return <>{children}>
+ return children
}
export default TableEditorLayout
diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx
index 27ea4e9ea551d..bc97706c6271f 100644
--- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx
+++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx
@@ -5,6 +5,8 @@ import { useEffect, useMemo, useState } from 'react'
import { useParams } from 'common'
import { useBreakpoint } from 'common/hooks/useBreakpoint'
import { ExportDialog } from 'components/grid/components/header/ExportDialog'
+import { parseSupaTable } from 'components/grid/SupabaseGrid.utils'
+import { SupaTable } from 'components/grid/types'
import { ProtectedSchemaWarning } from 'components/interfaces/Database/ProtectedSchemaWarning'
import EditorMenuListSkeleton from 'components/layouts/TableEditorLayout/EditorMenuListSkeleton'
import AlertError from 'components/ui/AlertError'
@@ -13,7 +15,7 @@ import InfiniteList from 'components/ui/InfiniteList'
import SchemaSelector from 'components/ui/SchemaSelector'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
-import { useTableEditorQuery } from 'data/table-editor/table-editor-query'
+import { getTableEditor, useTableEditorQuery } from 'data/table-editor/table-editor-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
@@ -40,14 +42,14 @@ import EntityListItem from './EntityListItem'
import { TableMenuEmptyState } from './TableMenuEmptyState'
export const TableEditorMenu = () => {
- 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/components/layouts/editors/EditorBaseLayout.tsx b/apps/studio/components/layouts/editors/EditorBaseLayout.tsx
index 9f0da404d7fb4..27c4b4a41bede 100644
--- a/apps/studio/components/layouts/editors/EditorBaseLayout.tsx
+++ b/apps/studio/components/layouts/editors/EditorBaseLayout.tsx
@@ -11,12 +11,18 @@ import { useEditorType } from './EditorsLayout.hooks'
export interface ExplorerLayoutProps extends ComponentProps {
children: ReactNode
- hideTabs?: boolean
title?: string
product?: string
+ productMenuClassName?: string
}
-export const EditorBaseLayout = ({ children, title, product, ...props }: ExplorerLayoutProps) => {
+export const EditorBaseLayout = ({
+ children,
+ title,
+ product,
+ productMenuClassName,
+ productMenu,
+}: ExplorerLayoutProps) => {
const { ref } = useParams()
const pathname = usePathname()
const editor = useEditorType()
@@ -28,7 +34,13 @@ export const EditorBaseLayout = ({ children, title, product, ...props }: Explore
pathname === `/project/${ref}/editor` || pathname === `/project/${ref}/sql` || hasNoOpenTabs
return (
-
+
{
className="w-fit mt-4"
onClick={() => setIsConfirmOptInModalOpen(true)}
>
- Update AI settings
+ Permission settings
)}
diff --git a/apps/studio/components/ui/ProductMenu/ProductMenu.tsx b/apps/studio/components/ui/ProductMenu/ProductMenu.tsx
index 67cc2c49e39c6..d55a15a053b17 100644
--- a/apps/studio/components/ui/ProductMenu/ProductMenu.tsx
+++ b/apps/studio/components/ui/ProductMenu/ProductMenu.tsx
@@ -10,7 +10,7 @@ interface ProductMenuProps {
const ProductMenu = ({ page, menu }: ProductMenuProps) => {
return (
-
+
{menu.map((group, idx) => (
diff --git a/apps/studio/data/database-cron-jobs/database-cron-job-run-mutation.ts b/apps/studio/data/database-cron-jobs/database-cron-job-run-mutation.ts
new file mode 100644
index 0000000000000..2e88d0ed8a02c
--- /dev/null
+++ b/apps/studio/data/database-cron-jobs/database-cron-job-run-mutation.ts
@@ -0,0 +1,66 @@
+import { useMutation, UseMutationOptions } from '@tanstack/react-query'
+import { toast } from 'sonner'
+
+import { executeSql } from 'data/sql/execute-sql-query'
+import type { ResponseError } from 'types'
+import { databaseCronJobsKeys } from './keys'
+
+export type DatabaseCronJobRunVariables = {
+ projectRef: string
+ connectionString?: string | null
+ jobId: number
+}
+
+// [Joshen] JFYI pg_cron doesn't support a run job function OOB just yet
+// So this is just merely running the command from within the cron job, will not reset the cron job's timer
+// https://github.com/citusdata/pg_cron/issues/226
+export async function runDatabaseCronJobCommand({
+ projectRef,
+ connectionString,
+ jobId,
+}: DatabaseCronJobRunVariables) {
+ const { result } = await executeSql({
+ projectRef,
+ connectionString,
+ sql: `
+DO $$
+DECLARE
+ job_command text;
+BEGIN
+ select command into job_command from cron.job where jobid = ${jobId};
+ EXECUTE job_command;
+END $$;
+`.trim(),
+ queryKey: databaseCronJobsKeys.create(),
+ })
+
+ return result
+}
+
+type DatabaseCronJobRunData = Awaited
>
+
+export const useDatabaseCronJobRunCommandMutation = ({
+ onSuccess,
+ onError,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ return useMutation(
+ (vars) => runDatabaseCronJobCommand(vars),
+ {
+ async onSuccess(data, variables, context) {
+ await onSuccess?.(data, variables, context)
+ },
+ async onError(data, variables, context) {
+ if (onError === undefined) {
+ toast.error(`Failed to run cron job command: ${data.message}`)
+ } else {
+ onError(data, variables, context)
+ }
+ },
+ ...options,
+ }
+ )
+}
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..6f11123aa8fde 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'
@@ -56,7 +56,11 @@ const TableEditorPage: NextPageWithLayout = () => {
TableEditorPage.getLayout = (page) => (
- } product="Table Editor">
+ }
+ product="Table Editor"
+ productMenuClassName="overflow-y-hidden"
+ >
{page}
diff --git a/apps/studio/pages/project/[ref]/editor/index.tsx b/apps/studio/pages/project/[ref]/editor/index.tsx
index 55797c6e53def..6ba1073515fb6 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'
@@ -47,7 +47,11 @@ const TableEditorPage: NextPageWithLayout = () => {
TableEditorPage.getLayout = (page) => (
- } product="Table Editor">
+ }
+ product="Table Editor"
+ productMenuClassName="overflow-y-hidden"
+ >
{page}
diff --git a/apps/studio/pages/project/[ref]/editor/new.tsx b/apps/studio/pages/project/[ref]/editor/new.tsx
index 3a2e12a9e3b26..ec76bdc6a4982 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'
@@ -27,7 +27,11 @@ const EditorNewPage: NextPageWithLayout = () => {
EditorNewPage.getLayout = (page) => (
- } product="Table Editor">
+ }
+ product="Table Editor"
+ productMenuClassName="overflow-y-hidden"
+ >
{page}
diff --git a/apps/studio/styles/main.scss b/apps/studio/styles/main.scss
index 1fab429887ce1..9a0898e8eac42 100644
--- a/apps/studio/styles/main.scss
+++ b/apps/studio/styles/main.scss
@@ -2,7 +2,6 @@
@tailwind components;
@tailwind utilities;
-@import './../../../packages/ui/build/css/source/global.css';
@import './../../../packages/ui/build/css/source/global.css';
@import './../../../packages/ui/build/css/themes/dark.css';
@import './../../../packages/ui/build/css/themes/light.css';
diff --git a/packages/ui/src/components/Accordion/Accordion.tsx b/packages/ui/src/components/Accordion/Accordion.tsx
index 26a1629099c74..4b793744c12ca 100644
--- a/packages/ui/src/components/Accordion/Accordion.tsx
+++ b/packages/ui/src/components/Accordion/Accordion.tsx
@@ -39,7 +39,7 @@ export interface AccordionProps {
}
/**
- * @deprecated Use ./Accordion_Shadcn_ instead
+ * @deprecated Use `import { Accordion_Shadcn_ } from "ui"` instead
*/
function Accordion({
children,
diff --git a/packages/ui/src/components/Alert/Alert.tsx b/packages/ui/src/components/Alert/Alert.tsx
index 34f40e9d2f06a..1c49c76c46059 100644
--- a/packages/ui/src/components/Alert/Alert.tsx
+++ b/packages/ui/src/components/Alert/Alert.tsx
@@ -27,7 +27,7 @@ const icons: Record = {
}
/**
- * @deprecated Use Alert_Shadcn_. For studio use Admonition
+ * @deprecated Use `import { Alert_Shadcn_ } from "ui"` instead. For studio use `Admonition`
*/
export function Alert({
variant = 'neutral',
diff --git a/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx b/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx
index 35a06b852213c..7de226f7170bd 100644
--- a/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/packages/ui/src/components/Breadcrumb/Breadcrumb.tsx
@@ -10,7 +10,7 @@ interface Props {
}
/**
- * @deprecated Use ./Breadcrumb_shadcn_ instead
+ * @deprecated Use `import { Breadcrumb_shadcn_ } from "ui"` instead
*/
const Breadcrumb = ({ className, style, children, spacing = 'small' }: Props) => {
let classes = [BreadcrumbStyle['sbui-breadcrumb--container']]
diff --git a/packages/ui/src/components/Checkbox/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx
index 0f37e7022c294..f98fafb97ceb5 100644
--- a/packages/ui/src/components/Checkbox/Checkbox.tsx
+++ b/packages/ui/src/components/Checkbox/Checkbox.tsx
@@ -36,7 +36,7 @@ interface GroupProps {
}
/**
- * @deprecated Use ./Checkbox_shadcn_ instead
+ * @deprecated Use `import { Checkbox_shadcn_ } from "ui"` instead
*/
function Group({
id,
diff --git a/packages/ui/src/components/Collapsible/Collapsible.tsx b/packages/ui/src/components/Collapsible/Collapsible.tsx
index 88da35cb68937..cbc4941a8215a 100644
--- a/packages/ui/src/components/Collapsible/Collapsible.tsx
+++ b/packages/ui/src/components/Collapsible/Collapsible.tsx
@@ -9,7 +9,7 @@ export interface CollapsibleProps extends RadixCollapsible.CollapsibleProps {
}
/**
- * @deprecated Use ./Collapsible_shadcn_ instead
+ * @deprecated Use `import { Collapsible_shadcn_ } from "ui"` instead
*/
export const Collapsible = ({
open = undefined,
diff --git a/packages/ui/src/components/Form/Form.tsx b/packages/ui/src/components/Form/Form.tsx
index 781d3da032fe7..47bd2c8202cfe 100644
--- a/packages/ui/src/components/Form/Form.tsx
+++ b/packages/ui/src/components/Form/Form.tsx
@@ -32,7 +32,7 @@ function errorReducer(state: any, action: any) {
}
/**
- * @deprecated Use ./Form_shadcn_ instead
+ * @deprecated Use `import { Form_shadcn_ } from "ui"` instead
*/
export default function Form({ validate, ...props }: Props) {
const [fieldLevelErrors, dispatchErrors] = useReducer(errorReducer, null)
diff --git a/packages/ui/src/components/Input/Input.tsx b/packages/ui/src/components/Input/Input.tsx
index d827478f87ef0..35f21c2aa8aa3 100644
--- a/packages/ui/src/components/Input/Input.tsx
+++ b/packages/ui/src/components/Input/Input.tsx
@@ -38,7 +38,7 @@ export interface Props
}
/**
- * @deprecated Use ./Input_shadcn_ instead or ./ui-patterns/data-inputs/input
+ * @deprecated Use `import { Input_shadcn_ } from "ui"` instead or ./ui-patterns/data-inputs/input
*/
function Input({
autoComplete,
diff --git a/packages/ui/src/components/InputNumber/InputNumber.tsx b/packages/ui/src/components/InputNumber/InputNumber.tsx
index 91501de58ae3f..e4aad0c0f756c 100644
--- a/packages/ui/src/components/InputNumber/InputNumber.tsx
+++ b/packages/ui/src/components/InputNumber/InputNumber.tsx
@@ -27,7 +27,7 @@ export interface Props extends Omit,
}
/**
- * @deprecated Use ./Input_shadcn_ with type="number" instead or ./ui-patterns/data-inputs/input with type="number"
+ * @deprecated Use `import { Input_shadcn_ } from "ui"` with `type="number"` instead or ./ui-patterns/data-inputs/input with `type="number"`
*/
function InputNumber({
defaultValue,
diff --git a/packages/ui/src/components/InputOld/Input.js b/packages/ui/src/components/InputOld/Input.js
index 8ccf6b8bd46a3..00c287a4d6452 100644
--- a/packages/ui/src/components/InputOld/Input.js
+++ b/packages/ui/src/components/InputOld/Input.js
@@ -5,7 +5,7 @@ import './Input.css'
export const SIZES = ['small', 'medium']
/**
- * @deprecated Use ./Input_shadcn_ with type="number" instead or ./ui-patterns/data-inputs/input with type="number"
+ * @deprecated Use `import { Input_shadcn_ } from "ui"` with `type="number"` instead or ./ui-patterns/data-inputs/input with `type="number"`
*/
const Input = ({
className = '',
diff --git a/packages/ui/src/components/Listbox/Listbox2.tsx b/packages/ui/src/components/Listbox/Listbox2.tsx
index f6e90ad0dcb54..2b4aa34a3282e 100644
--- a/packages/ui/src/components/Listbox/Listbox2.tsx
+++ b/packages/ui/src/components/Listbox/Listbox2.tsx
@@ -38,7 +38,7 @@ export interface Props extends Omit
}
/**
- * @deprecated Use ./Select_shadcn_ or follow ComboBox convention or use ./ui-patterns/multi-select
+ * @deprecated Use `import { Select_shadcn_ } from "ui"` or follow ComboBox convention or use ./ui-patterns/multi-select
*/
function Listbox({
children,
diff --git a/packages/ui/src/components/Modal/Modal.tsx b/packages/ui/src/components/Modal/Modal.tsx
index ebdf78b5e2207..df2b7adfd9d24 100644
--- a/packages/ui/src/components/Modal/Modal.tsx
+++ b/packages/ui/src/components/Modal/Modal.tsx
@@ -62,7 +62,7 @@ interface ModalType
Separator: React.ComponentType
}
-/** @deprecated use instead */
+/** @deprecated Use `import { Dialog } from "ui"` instead */
const Modal = forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & ModalProps
diff --git a/packages/ui/src/components/Popover/Popover.tsx b/packages/ui/src/components/Popover/Popover.tsx
index faea74be735d1..59a3cf4aa2d28 100644
--- a/packages/ui/src/components/Popover/Popover.tsx
+++ b/packages/ui/src/components/Popover/Popover.tsx
@@ -30,7 +30,7 @@ interface RootProps {
}
/**
- * @deprecated Use ./Popover_shadcn_ instead
+ * @deprecated Use `import { Popover_shadcn_ } from "ui"` instead
*/
function Popover({
align = 'center',
diff --git a/packages/ui/src/components/Radio/Radio.tsx b/packages/ui/src/components/Radio/Radio.tsx
index 7fa77a8a23bb2..19ecc83375c89 100644
--- a/packages/ui/src/components/Radio/Radio.tsx
+++ b/packages/ui/src/components/Radio/Radio.tsx
@@ -34,7 +34,7 @@ interface GroupProps {
}
/**
- * @deprecated Use ./RadioGroup_Shadcn_ instead
+ * @deprecated Use `import { RadioGroup_Shadcn_ } from "ui"` instead
*/
function RadioGroup({
id,
diff --git a/packages/ui/src/components/RadioOld/Radio.js b/packages/ui/src/components/RadioOld/Radio.js
index bab2deab77818..8601699b2f42a 100644
--- a/packages/ui/src/components/RadioOld/Radio.js
+++ b/packages/ui/src/components/RadioOld/Radio.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import './Radio.css'
/**
- * @deprecated Use ./RadioGroupItem_Shadcn_ and ./RadioGroup_Shadcn_ instead
+ * @deprecated Use `import { RadioGroup_Shadcn_, RadioGroupItem_Shadcn_ } from "ui"` instead
*/
const Radio = ({ className = '', label = '', containerClassName = '', children, ...props }) => {
return (
diff --git a/packages/ui/src/components/Select/Select.tsx b/packages/ui/src/components/Select/Select.tsx
index d6d40e73e1977..5794abf5c010b 100644
--- a/packages/ui/src/components/Select/Select.tsx
+++ b/packages/ui/src/components/Select/Select.tsx
@@ -41,7 +41,7 @@ export interface Props extends Omit
export const ColLayout = (props: any) => {props.children}
/**
- * @deprecated Use ./Select_shadcn_ instead
+ * @deprecated Use `import { Select_shadcn_ } from "ui"` instead
*/
function Select({
autoComplete,
diff --git a/packages/ui/src/components/Tabs/Tabs.tsx b/packages/ui/src/components/Tabs/Tabs.tsx
index 7d09ba8440fab..eb96b4bfa7d32 100644
--- a/packages/ui/src/components/Tabs/Tabs.tsx
+++ b/packages/ui/src/components/Tabs/Tabs.tsx
@@ -38,7 +38,7 @@ interface TabsSubComponents {
}
/**
- * @deprecated Use ./Tabs_shadcn_ instead
+ * @deprecated Use `import { Tabs_shadcn_ } from "ui"` instead
*/
const Tabs: React.FC> & TabsSubComponents = ({
defaultActiveId,
diff --git a/packages/ui/src/components/Toggle/Toggle.tsx b/packages/ui/src/components/Toggle/Toggle.tsx
index 658e9e567d432..3093b267436ff 100644
--- a/packages/ui/src/components/Toggle/Toggle.tsx
+++ b/packages/ui/src/components/Toggle/Toggle.tsx
@@ -27,7 +27,7 @@ interface Props extends Omit, 'size'> {
}
/**
- * @deprecated Use ./Switch instead
+ * @deprecated Use `import { Switch } from "ui"` instead
*/
function Toggle({
disabled,