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
67 changes: 60 additions & 7 deletions .github/workflows/docs-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ on:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
branches: ['master']
paths:
- 'apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx'
- 'apps/docs/content/_partials/quickstart_db_setup.mdx'
- 'apps/docs/content/_partials/api_settings.mdx'
- 'apps/docs/content/guides/**/*.mdx'
- 'apps/docs/content/troubleshooting/**/*.mdx'
- 'apps/docs/content/_partials/**'
- 'e2e/docs/features/**'
- 'e2e/docs/utils/**'
- 'e2e/docs/scripts/**'
- 'e2e/docs/playwright.config.ts'
- 'e2e/docs/package.json'
- 'e2e/docs/tsconfig.json'
Expand All @@ -22,6 +23,11 @@ on:
required: false
default: 'https://supabase.com'
type: string
page_paths:
description: 'Comma-separated /docs/... paths to test (required for manual runs)'
required: false
default: ''
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -46,17 +52,59 @@ jobs:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# Need full history on PRs so we can diff against the base branch.
# Use string '0' — numeric 0 is falsy in GitHub Actions expressions.
fetch-depth: ${{ github.event_name == 'pull_request' && '0' || '1' }}
sparse-checkout: |
e2e/docs
scripts
patches
apps/docs/content/guides
apps/docs/content/troubleshooting
apps/docs/content/_partials
apps/docs/scripts/federated-content/sources

- name: Use Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: '.nvmrc'

# Map changed owned content (guides, troubleshooting, partials) to page
# URLs. Harness-only PRs resolve to skip=true and exit before Playwright.
- name: Resolve docs E2E scope
id: scope
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
PAGE_PATHS_INPUT: ${{ inputs.page_paths }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
if [ -z "$PAGE_PATHS_INPUT" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "paths=" >> "$GITHUB_OUTPUT"
echo "Manual run requires the page_paths input."
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
printf 'paths=%s\n' "$PAGE_PATHS_INPUT" >> "$GITHUB_OUTPUT"
exit 0
fi

git diff --name-only --diff-filter=ACMR "origin/$BASE_REF"...HEAD \
| node --experimental-strip-types e2e/docs/scripts/resolve-docs-scope.ts

- name: Skip Playwright (no in-scope pages)
if: steps.scope.outputs.skip == 'true'
run: echo "No in-scope docs pages changed; skipping Playwright suite."

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
if: steps.scope.outputs.skip != 'true'
name: Install pnpm
with:
run_install: false

- name: Use Node.js
- name: Enable pnpm store cache
if: steps.scope.outputs.skip != 'true'
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: '.nvmrc'
Expand All @@ -65,7 +113,7 @@ jobs:
# Vercel skips the docs preview when a PR only changes the harness
# (e2e/docs, workflow). Wait for a preview only when apps/docs changed.
- name: Detect docs app changes
if: github.event_name == 'pull_request'
if: steps.scope.outputs.skip != 'true' && github.event_name == 'pull_request'
id: filter
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
with:
Expand All @@ -80,7 +128,7 @@ jobs:
# those — then resolve the deployment it points to via Vercel's own API
# to get the actual preview URL. See scripts/waitForVercelDocsPreview.js.
- name: Wait for Vercel docs preview
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.filter.outputs.docs_app == 'true'
if: steps.scope.outputs.skip != 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.filter.outputs.docs_app == 'true'
id: deployment
run: node scripts/waitForVercelDocsPreview.js
env:
Expand All @@ -90,6 +138,7 @@ jobs:
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}

- name: Resolve base URL
if: steps.scope.outputs.skip != 'true'
id: base-url
env:
EVENT_NAME: ${{ github.event_name }}
Expand All @@ -110,20 +159,24 @@ jobs:
fi

- name: Install dependencies
if: steps.scope.outputs.skip != 'true'
run: pnpm install --frozen-lockfile --filter=e2e-docs...

- name: Install Playwright Chromium
if: steps.scope.outputs.skip != 'true'
run: pnpm -C e2e/docs exec playwright install chromium --with-deps --only-shell

- name: Run docs E2E
if: steps.scope.outputs.skip != 'true'
working-directory: e2e/docs
run: pnpm run e2e:docs
env:
PLAYWRIGHT_BASE_URL: ${{ steps.base-url.outputs.url }}
DOCS_E2E_PAGE_PATHS: ${{ steps.scope.outputs.paths }}
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ steps.base-url.outputs.use_bypass == 'true' && secrets.VERCEL_AUTOMATION_BYPASS_DOCS || '' }}

- name: Upload Playwright report
if: failure()
if: failure() && steps.scope.outputs.skip != 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: docs-playwright-report
Expand Down
1 change: 1 addition & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ Sugu Sougoumarane
Supun Sudaraka Kalidasa
Taha Le Bras
Tanun Chalermsinsuwan
Tarun Khandelwal
Taryn King
Terry Sutton
Terry Wilcox
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, test } from 'vitest'

import {
getValidVercelReturnUrl,
isVercelUrl,
} from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'

describe('isVercelUrl', () => {
test('accepts https vercel.com urls', () => {
expect(isVercelUrl('https://vercel.com/callback')).toBe(true)
})

test('rejects non-vercel and invalid urls', () => {
expect(isVercelUrl('https://example.com')).toBe(false)
expect(isVercelUrl('http://vercel.com')).toBe(false)
expect(isVercelUrl('not-a-url')).toBe(false)
})
})

describe('getValidVercelReturnUrl', () => {
test('returns the url when it is a valid vercel return url', () => {
expect(getValidVercelReturnUrl('https://vercel.com/callback')).toBe(
'https://vercel.com/callback'
)
})

test('returns undefined for missing or invalid next values', () => {
expect(getValidVercelReturnUrl(undefined)).toBeUndefined()
expect(getValidVercelReturnUrl('https://example.com')).toBeUndefined()
expect(getValidVercelReturnUrl('not-a-url')).toBeUndefined()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export function isVercelUrl(url: string): boolean {
}
}

/** Returns `next` when it is a safe Vercel return URL; otherwise undefined. */
export function getValidVercelReturnUrl(next: string | undefined): string | undefined {
if (typeof next === 'string' && isVercelUrl(next)) return next
return undefined
}

export function findVercelIntegrationByConfigurationId(
integrations: Integration[] | undefined,
configurationId: string | undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import type { MemberWithFreeProjectLimit } from '@/data/organizations/free-proje

interface FreeProjectLimitWarningProps {
membersExceededLimit: MemberWithFreeProjectLimit[]
showVercelReturnHint?: boolean
}

export const FreeProjectLimitWarning = ({ membersExceededLimit }: FreeProjectLimitWarningProps) => {
export const FreeProjectLimitWarning = ({
membersExceededLimit,
showVercelReturnHint = false,
}: FreeProjectLimitWarningProps) => {
return (
<Panel.Content>
<Admonition
Expand All @@ -32,6 +36,11 @@ export const FreeProjectLimitWarning = ({ membersExceededLimit }: FreeProjectLim
These members will need to either delete, pause, or upgrade one or more of these
projects before you're able to create a free project within this organization.
</p>
{showVercelReturnHint && (
<p className="text-sm leading-normal">
Or return to Vercel and restart with a different organization.
</p>
)}

<UpgradePlanButton
source="freeProjectLimitWarning"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useFlag } from 'common'
import { useFlag, useParams } from 'common'
import { useRouter } from 'next/router'
import { UseFormReturn } from 'react-hook-form'
import {
Expand All @@ -16,20 +16,23 @@ import { InfoTooltip } from 'ui-patterns/info-tooltip'

import { CreateProjectForm } from './ProjectCreation.schema'
import { instanceLabel, monthlyInstancePrice } from './ProjectCreation.utils'
import { getValidVercelReturnUrl } from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'
import { InlineLink } from '@/components/ui/InlineLink'
import { OrgProject } from '@/data/projects/org-projects-infinite-query'
import { useLastVisitedOrganization } from '@/hooks/misc/useLastVisitedOrganization'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { DOCS_URL } from '@/lib/constants'

export type ProjectCreationCancelAction = 'studio' | 'vercel' | 'hidden'

interface ProjectCreationFooterProps {
form: UseFormReturn<CreateProjectForm>
canCreateProject: boolean
instanceSize?: string
organizationProjects: OrgProject[]
isCreatingNewProject: boolean
isSuccessNewProject: boolean
hideCancelButton: boolean
cancelAction?: ProjectCreationCancelAction
}

export const ProjectCreationFooter = ({
Expand All @@ -39,9 +42,10 @@ export const ProjectCreationFooter = ({
organizationProjects,
isCreatingNewProject,
isSuccessNewProject,
hideCancelButton,
cancelAction = 'studio',
}: ProjectCreationFooterProps) => {
const router = useRouter()
const { next } = useParams()
const { data: currentOrg } = useSelectedOrganizationQuery()
const isFreePlan = currentOrg?.plan?.id === 'free'
const { lastVisitedOrganization } = useLastVisitedOrganization()
Expand All @@ -53,6 +57,9 @@ export const ProjectCreationFooter = ({
? 0
: monthlyInstancePrice(instanceSize) - availableComputeCredits

const vercelReturnUrl = getValidVercelReturnUrl(next)
const canReturnToVercel = cancelAction === 'vercel' && vercelReturnUrl !== undefined

// [kevin] This will eventually all be provided by a new API endpoint to preview and validate project creation, this is just for kaizen now
const monthlyComputeCosts =
// current project costs
Expand All @@ -66,6 +73,17 @@ export const ProjectCreationFooter = ({
// compute credits
10

const onCancel = () => {
if (canReturnToVercel && vercelReturnUrl) {
window.location.href = vercelReturnUrl
return
}

// Fall back to Studio when cancelAction is studio, or when vercel next is missing/invalid
if (!!lastVisitedOrganization) router.push(`/org/${lastVisitedOrganization}`)
else router.push('/organizations')
}

return (
<div key="panel-footer" className="grid grid-cols-12 w-full gap-4 items-center">
<div className="col-span-4">
Expand Down Expand Up @@ -169,16 +187,13 @@ export const ProjectCreationFooter = ({
</div>

<div className="flex items-end col-span-8 space-x-2 ml-auto">
{!hideCancelButton && (
{cancelAction !== 'hidden' && (
<Button
variant="default"
disabled={isCreatingNewProject || isSuccessNewProject}
onClick={() => {
if (!!lastVisitedOrganization) router.push(`/org/${lastVisitedOrganization}`)
else router.push('/organizations')
}}
onClick={onCancel}
>
Cancel
{canReturnToVercel ? 'Return to Vercel' : 'Cancel'}
</Button>
)}
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ProjectNameInput } from './ProjectNameInput'
import { RegionSelector } from './RegionSelector'
import { SecurityOptions } from './SecurityOptions'
import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Database/Triggers/EventTriggersList/EventTriggers.constants'
import { getValidVercelReturnUrl } from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'
import {
GitHubRepositoryField,
useGitHubRepositoryOptions,
Expand Down Expand Up @@ -90,7 +91,7 @@ interface ProjectCreationFormProps {
* - "Internal configuration" section
* - "GitHub repository" field
* - "Free project info" at the bottom
* - "Cancel" button
* - Shows Cancel as "Return to Vercel" (via `next`) instead of navigating into Studio
* - Shows the following:
* - "Data seeding" section
* - When embedded in the Vercel interstitial, flattens Panel chrome so the shared
Expand All @@ -105,7 +106,8 @@ export const ProjectCreationForm = ({
const track = useTrack()
const router = useRouter()
const { profile } = useProfile()
const { slug, projectName, externalId } = useParams()
const { slug, projectName, externalId, next } = useParams()
const canReturnToVercel = getValidVercelReturnUrl(next) !== undefined
const trackFunnelError = useTrackFunnelError()
const defaultProvider = useDefaultProvider()

Expand Down Expand Up @@ -607,7 +609,7 @@ export const ProjectCreationForm = ({
organizationProjects={organizationProjects}
isCreatingNewProject={isCreatingNewProject}
isSuccessNewProject={isSuccessNewProject}
hideCancelButton={isVercelIntegrationFlow}
cancelAction={isVercelIntegrationFlow ? 'vercel' : 'studio'}
/>
}
>
Expand Down Expand Up @@ -700,7 +702,10 @@ export const ProjectCreationForm = ({
{freePlanWithExceedingLimits ? (
isAdmin &&
slug && (
<FreeProjectLimitWarning membersExceededLimit={membersExceededLimit || []} />
<FreeProjectLimitWarning
membersExceededLimit={membersExceededLimit || []}
showVercelReturnHint={isVercelIntegrationFlow && canReturnToVercel}
/>
)
) : hasOutstandingInvoices ? (
<Panel.Content>
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/pages/integrations/vercel/install.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ const VercelIntegration: NextPageWithLayout = () => {

<InterstitialLayout
logo={<VercelIntegrationLogo />}
title="Install Vercel Integration"
title="Install Vercel integration"
description="Choose the Supabase organization Vercel can connect to"
footer={<VercelIntegrationFooter />}
>
Expand Down
Loading
Loading