Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions apps/docs/content/guides/auth/server-side/creating-a-client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key
```

</TabPanel>
<TabPanel id="react-router" label="React Router">

```bash .env
SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key
```

</TabPanel>
<TabPanel id="express" label="Express">

Expand Down Expand Up @@ -660,6 +668,105 @@ export default function Index() {

</TabPanel>

<TabPanel id="react-router" label="React Router">

<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="react-router-loader"
queryGroup="environment"
>
<TabPanel id="react-router-loader" label="Loader">

```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,
})
}
```

</TabPanel>

<TabPanel id="react-router-action" label="Action">

```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,
})
}
```

</TabPanel>

<TabPanel id="react-router-component" label="Component">

```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<typeof loader>();

const supabase = createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY);

return ...
}
```

</TabPanel>
</Tabs>

</TabPanel>

<TabPanel id="express" label="Express">

<Tabs
Expand Down
55 changes: 41 additions & 14 deletions apps/studio/components/grid/components/header/ExportDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { useState } from 'react'

import { useParams } from 'common'
import { Filter, Sort, SupaTable } from 'components/grid/types'
import { getConnectionStrings } from 'components/interfaces/Connect/DatabaseSettings.utils'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { getAllTableRowsSql } from 'data/table-rows/table-rows-query'
import { pluckObjectFields } from 'lib/helpers'
import { useState } from 'react'
import { RoleImpersonationState, wrapWithRoleImpersonation } from 'lib/role-impersonation'
import { useRoleImpersonationStateSnapshot } from 'state/role-impersonation-state'
import {
Button,
cn,
Expand All @@ -22,13 +27,25 @@ import {
import { Admonition } from 'ui-patterns'

interface ExportDialogProps {
table?: { name: string; schema: string }
table?: SupaTable
filters?: Filter[]
sorts?: Sort[]
ignoreRoleImpersonation?: boolean

open: boolean
onOpenChange: (open: boolean) => 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<string>('csv')

Expand All @@ -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
Expand Down Expand Up @@ -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"
/>
<Admonition
type="note"
className="mt-2"
title="Filters are not supported when exporting as SQL via pg_dump"
description="If you'd like to export as SQL, we recommend creating a view first then exporting the data from there via pg_dump instead"
/>
</TabsContent_Shadcn_>
</Tabs_Shadcn_>

Expand All @@ -107,15 +138,11 @@ pg_dump -h ${db_host} -p ${db_port} -d ${db_name} -U ${db_user} --table="${table
</p>

{selectedTab === 'sql' && (
<Admonition
type="note"
title="The pg_dump version needs to match your Postgres version"
>
<p className="!leading-normal">
If you run into a server version mismatch error, you will need to update{' '}
<code>pg_dump</code> before running the command.
</p>
</Admonition>
<p className="text-sm text-foreground-light">
Note: <code>pg_dump</code> needs to match your project's Postgres version. If you run
into a server version mismatch error, you will need to update <code>pg_dump</code>{' '}
before running the command.
</p>
)}
</DialogSection>
<DialogFooter>
Expand Down
8 changes: 4 additions & 4 deletions apps/studio/components/grid/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -504,11 +504,9 @@ const RowHeader = () => {
Export
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className={snap.allRowsSelected ? 'w-52' : 'w-40'}>
<DropdownMenuContent align="start" className={snap.allRowsSelected ? 'w-52' : 'w-40'}>
<DropdownMenuItem onClick={onRowsExportCSV}>Export as CSV</DropdownMenuItem>
<DropdownMenuItem onClick={onRowsExportSQL}>Export as SQL</DropdownMenuItem>
{/* [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 ? (
<DropdownMenuItem className="group" onClick={() => setShowExportModal(true)}>
<div>
Expand All @@ -535,7 +533,9 @@ const RowHeader = () => {
</div>

<ExportDialog
table={{ name: snap.table.name, schema: snap.table.schema ?? '' }}
table={snap.table}
filters={filters}
sorts={sorts}
open={showExportModal}
onOpenChange={() => setShowExportModal(false)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const ProjectCard = ({
<div className="w-fit p-1 border rounded-md flex items-center">
<Github size={12} strokeWidth={1.5} />
</div>
<p className="text-xs !ml-2 text-foreground-light">{githubRepository}</p>
<p className="text-xs !ml-2 text-foreground-light truncate">{githubRepository}</p>
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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!,
Expand All @@ -100,12 +115,23 @@ export const CronJobTableCell = ({
<DropdownMenuTrigger asChild>
<Button
type="text"
loading={isRunning}
className="h-6 w-6"
icon={<MoreVertical />}
onClick={(e) => e.stopPropagation()}
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-42">
<DropdownMenuItem
className="gap-x-2"
onClick={(e) => {
e.stopPropagation()
onRunCronJob()
}}
>
<Play size={12} />
Run command
</DropdownMenuItem>
<DropdownMenuItem
className="gap-x-2"
onClick={(e) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export const CronjobsTab = () => {
}}
onScroll={handleScroll}
renderers={{
renderRow(key, props) {
renderRow(_, props) {
return (
<Row
key={props.row.jobid}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const buildFunctionUrl = (slug: string, projectRef: string, restUrl?: string) =>
}

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])
Expand All @@ -52,6 +52,7 @@ export const EdgeFunctionSection = ({ form }: HTTPRequestFieldsProps) => {
form.setValue('values.edgeFunctionName', functionUrl)
}
}, [edgeFunctions, form, isSuccess, selectedProject?.ref, selectedProject?.restUrl])

return (
<SheetSection className="flex flex-col gap-6">
<FormField_Shadcn_
Expand Down
Loading
Loading