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
2 changes: 1 addition & 1 deletion apps/docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ If you're a library maintainer, follow these steps when updating function parame

## Content reuse

If you copy the same content multiple times across different files, create a **partial** for content reuse instead. Partials are MDX files contained in [`apps/docs/components/MDX`](https://github.com/supabase/supabase/tree/master/apps/docs/components/MDX). They contain reusable snippets that can be inserted in multiple pages. For example, you can create a partial to define a common setup step for a group of tutorials.
If you copy the same content multiple times across different files, create a **partial** for content reuse instead. Partials are MDX files contained in [`apps/docs/content/_partials`](https://github.com/supabase/supabase/tree/master/apps/docs/content/_partials). They contain reusable snippets that can be inserted in multiple pages. For example, you can create a partial to define a common setup step for a group of tutorials.

To use a partial, import it into your MDX file. You can also set up a partial to automatically import by including it in the `components` within [`apps/docs/components/index.tsx`](https://github.com/supabase/supabase/blob/master/apps/docs/components/index.tsx).

Expand Down
8 changes: 6 additions & 2 deletions apps/docs/components/HomePageCover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import { ChevronRight, Play, Sparkles } from 'lucide-react'
import Link from 'next/link'
// End of third-party imports

import { isFeatureEnabled, useBreakpoint } from 'common'
import { cn, IconBackground } from 'ui'
import { IconPanel } from 'ui-patterns/IconPanel'

import { useCustomContent } from '../hooks/custom-content/useCustomContent'
import DocsCoverLogo from './DocsCoverLogo'

const { sdkDart: sdkDartEnabled, sdkKotlin: sdkKotlinEnabled } = isFeatureEnabled([
Expand Down Expand Up @@ -36,6 +37,7 @@ function AiPrompt({ className }: { className?: string }) {
const HomePageCover = (props) => {
const isXs = useBreakpoint(639)
const iconSize = isXs ? 'sm' : 'lg'
const { homepageHeading } = useCustomContent(['homepage:heading'])

const frameworks = [
{
Expand Down Expand Up @@ -141,7 +143,9 @@ const HomePageCover = (props) => {
<div className="flex flex-col sm:flex-row gap-4 sm:gap-8 items-start sm:items-center w-full max-w-xl xl:max-w-[33rem]">
<DocsCoverLogo aria-hidden="true" />
<div className="flex flex-col">
<h1 className="m-0 mb-3 text-2xl sm:text-3xl text-foreground">{props.title}</h1>
<h1 className="m-0 mb-3 text-2xl sm:text-3xl text-foreground">
{homepageHeading || props.title}
</h1>
<p className="m-0 text-foreground-light">
Learn how to get up and running with Supabase through tutorials, APIs and platform
resources.
Expand Down
18 changes: 11 additions & 7 deletions apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { memo, useState } from 'react'
import { Command, Menu, Search } from 'lucide-react'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import Link from 'next/link'
import { Command, Search, Menu } from 'lucide-react'
import type { FC } from 'react'
import { memo, useState } from 'react'
// End of third-party imports

import { useIsLoggedIn, useIsUserLoading, useUser } from 'common'
import { Button, buttonVariants, cn } from 'ui'
import { AuthenticatedDropdownMenu, CommandMenuTrigger } from 'ui-patterns'
import { useCustomContent } from '../../../hooks/custom-content/useCustomContent'
import GlobalNavigationMenu from './GlobalNavigationMenu'
import useDropdownMenu from './useDropdownMenu'

import type { FC } from 'react'

const GlobalMobileMenu = dynamic(() => import('./GlobalMobileMenu'))
const TopNavDropdown = dynamic(() => import('./TopNavDropdown'))

Expand Down Expand Up @@ -55,7 +56,8 @@ const TopNavBar: FC = () => {
<div className="flex items-center space-x-2 text-foreground-muted">
<Search size={18} strokeWidth={2} />
<p className="flex text-sm pr-2">
Search<span className="hidden xl:inline ml-1"> docs...</span>
Search
<span className="hidden xl:inline ml-1"> docs...</span>
</p>
</div>
<div className="hidden md:flex items-center space-x-1">
Expand Down Expand Up @@ -108,6 +110,8 @@ const TopNavBar: FC = () => {
}

const HeaderLogo = memo(() => {
const { navigationLogoUrl } = useCustomContent(['navigation:logo_url'])

return (
<Link
href="/"
Expand All @@ -118,7 +122,7 @@ const HeaderLogo = memo(() => {
>
<Image
className="hidden dark:block !m-0"
src="/docs/supabase-dark.svg"
src={navigationLogoUrl?.dark ?? '/docs/supabase-dark.svg'}
priority={true}
loading="eager"
width={96}
Expand All @@ -127,7 +131,7 @@ const HeaderLogo = memo(() => {
/>
<Image
className="block dark:hidden !m-0"
src="/docs/supabase-light.svg"
src={navigationLogoUrl?.light ?? '/docs/supabase-light.svg'}
priority={true}
loading="eager"
width={96}
Expand Down
7 changes: 7 additions & 0 deletions apps/docs/hooks/custom-content/CustomContent.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type CustomContentTypes = {
homepageHeading: string
navigationLogoUrl: {
light: string
dark: string
}
}
7 changes: 7 additions & 0 deletions apps/docs/hooks/custom-content/custom-content.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"homepage:heading": "Supabase Documentation",
"navigation:logo_url": {
"light": "/docs/supabase-light.svg",
"dark": "/docs/supabase-dark.svg"
}
}
48 changes: 48 additions & 0 deletions apps/docs/hooks/custom-content/useCustomContent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* @vitest-environment jsdom
*/

import { cleanup, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'

beforeEach(() => {
vi.clearAllMocks()
vi.resetModules()
cleanup()
})

describe('useCustomContent', () => {
it('should return null if content is not found in the custom-content.json file', async () => {
vi.doMock('./custom-content.json', () => ({
default: {
'navigation:logo_url': null,
},
}))

const { useCustomContent } = await import('./useCustomContent')
const { result } = renderHook(() => useCustomContent(['navigation:logo_url']))
expect(result.current.navigationLogoUrl).toEqual(null)
})

it('should return the content for the key passed in if it exists in the custom-content.json file', async () => {
vi.doMock('./custom-content.json', () => ({
default: {
'navigation:logo_url': {
light: 'https://example.com/logo-light.svg',
dark: 'https://example.com/logo-dark.svg',
},
'homepage:heading': 'Custom Heading',
},
}))

const { useCustomContent } = await import('./useCustomContent')
const { result } = renderHook(() =>
useCustomContent(['navigation:logo_url', 'homepage:heading'])
)
expect(result.current.navigationLogoUrl).toEqual({
light: 'https://example.com/logo-light.svg',
dark: 'https://example.com/logo-dark.svg',
})
expect(result.current.homepageHeading).toEqual('Custom Heading')
})
})
49 changes: 49 additions & 0 deletions apps/docs/hooks/custom-content/useCustomContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* [Charis 2025-09-29] This file is a duplicate of studio's useCustomContent.ts
* for now.
*
* We should probably consolidate these two files in the future, but we want to
* get this change shipped quickly without worrying about smoke testing all the
* components affected by a refactor.
*/

import type { CustomContentTypes } from './CustomContent.types'
import customContentRaw from './custom-content.json'

const customContentStaticObj = customContentRaw as Omit<typeof customContentRaw, '$schema'>
type CustomContent = keyof typeof customContentStaticObj

type SnakeToCamelCase<S extends string> = S extends `${infer First}_${infer Rest}`
? `${First}${SnakeToCamelCase<Capitalize<Rest>>}`
: S

type CustomContentToCamelCase<S extends CustomContent> = S extends `${infer P}:${infer R}`
? `${SnakeToCamelCase<P>}${Capitalize<SnakeToCamelCase<R>>}`
: SnakeToCamelCase<S>

function contentToCamelCase(feature: CustomContent) {
return feature
.replace(/:/g, '_')
.split('_')
.map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1)))
.join('') as CustomContentToCamelCase<typeof feature>
}

const useCustomContent = <T extends CustomContent[]>(
contents: T
): {
[key in CustomContentToCamelCase<T[number]>]:
| CustomContentTypes[CustomContentToCamelCase<T[number]>]
| null
} => {
// [Joshen] Running into some TS errors without the `as` here - must be overlooking something super simple
return Object.fromEntries(
contents.map((content) => [contentToCamelCase(content), customContentStaticObj[content]])
) as {
[key in CustomContentToCamelCase<T[number]>]:
| CustomContentTypes[CustomContentToCamelCase<T[number]>]
| null
}
}

export { useCustomContent }
1 change: 1 addition & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"@graphql-codegen/typescript": "4.1.6",
"@graphql-codegen/typescript-resolvers": "4.5.0",
"@supabase/supa-mdx-lint": "0.3.1",
"@testing-library/react": "^16.0.0",
"@types/common-tags": "^1.8.4",
"@types/estree": "1.0.5",
"@types/graphql-validation-complexity": "^0.4.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type { ForeignKey } from './ForeignKeySelector/ForeignKeySelector.types'
import type { ColumnField, CreateColumnPayload, UpdateColumnPayload } from './SidePanelEditor.types'
import { checkIfRelationChanged } from './TableEditor/ForeignKeysManagement/ForeignKeysManagement.utils'
import type { ImportContent } from './TableEditor/TableEditor.types'
import { executeWithRetry } from 'data/table-rows/table-rows-query'

const BATCH_SIZE = 1000
const CHUNK_SIZE = 1024 * 1024 * 0.1 // 0.1MB
Expand Down Expand Up @@ -892,7 +893,9 @@ export const insertRowsViaSpreadsheet = async (

const insertQuery = new Query().from(table.name, table.schema).insert(formattedData).toSql()
try {
await executeSql({ projectRef, connectionString, sql: insertQuery })
await executeWithRetry(() =>
executeSql({ projectRef, connectionString, sql: insertQuery })
)
} catch (error) {
console.warn(error)
insertError = error
Expand Down
3 changes: 2 additions & 1 deletion apps/studio/data/table-rows/table-rows-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ export async function executeWithRetry<T>(
try {
return await fn()
} catch (error: any) {
if (error?.status === 429 && attempt < maxRetries) {
// Our custom ResponseError's use 'code' instead of 'status'
if ((error?.status ?? error?.code) === 429 && attempt < maxRetries) {
// Get retry delay from headers or use exponential backoff (1s, then 2s, then 4s)
const retryAfter = error.headers?.get('retry-after')
const delayMs = retryAfter ? parseInt(retryAfter) * 1000 : baseDelay * Math.pow(2, attempt)
Expand Down
3 changes: 2 additions & 1 deletion apps/studio/lib/ai/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,13 @@ export const GENERAL_PROMPT = `
Act as a Supabase Postgres expert to assist users in efficiently managing their Supabase projects.
## Instructions
Support the user by:
- Gathering context from the database using the \`list_tables\`, \`list_extensions\`, and \`list_edge_functions\` tools
- Writing SQL queries
- Creating Edge Functions
- Debugging issues
- Monitoring project status
## Tools
- Use available context gathering tools such as \`list_tables\`, \`list_extensions\`, and \`list_edge_functions\` whenever relevant for context.
- Always use available context gathering tools such as \`list_tables\`, \`list_extensions\`, and \`list_edge_functions\`
- Tools are for assistant use only; do not imply user access to them.
- Only use the tools listed above. For read-only or information-gathering operations, call tools automatically; for potentially destructive actions, obtain explicit user confirmation before proceeding.
- Tool access may be limited by organizational settings. If required permissions for a task are unavailable, inform the user of this limitation and propose alternatives if possible.
Expand Down
Loading
Loading