mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
fix(studio): route vercel deploy-button params to create despite marketplace source (#48258)
## What kind of change does this PR introduce? Bug fix for the Vercel Deploy Button → Studio handoff. ## What is the current behavior? Vercel sometimes opens our install popup with `source=marketplace` while still sending Deploy Button params (`currentProjectId`, `external-id`). We trust `source` alone, so users are routed to choose-project (connect) instead of create — which is why create never gets reached in the Deploy Button flow. ## What is the new behavior? - When both Deploy Button signals (`currentProjectId` + `externalId`) are present, route to create even if Vercel sent `source=marketplace` / `external` - Hide Skip (and related empty-state copy) on choose-project when those signals are present, so Deploy Button users can't continue without linking ## Additional context Stacked on #48230. Test plan: - [ ] Unit tests for `resolveVercelInstallSource` / `hasVercelDeployButtonSignals` pass - [ ] Deploy Button flow with mislabeled `source=marketplace` + both params → lands on create after org install/continue - [ ] Genuine marketplace install (no `currentProjectId`/`external-id`) → still lands on choose-project with Skip available - [ ] If choose-project is opened with both Deploy Button params, Skip is hidden <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved Vercel installation handling for Deploy Button workflows, ensuring the correct setup path is selected. - Added clearer project-connection guidance when no projects are available (including conditional skip copy). - **Bug Fixes** - Prevented Deploy Button installations from incorrectly offering a skip option. - Preserved the skip-and-connect-later guidance for other Vercel installation flows. - Improved recognition of Deploy Button installations even when the reported Vercel source differs. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
1 parent
2f0a582198
commit
69570a357d
5 files changed
+93
-22
No files matched your search
@@ -117,7 +117,8 @@ export const ProjectLinker = ({
|
||||
) : showNoEntitiesState && (noSupabaseProjects || noForeignProjects) ? (
|
||||
<div className="text-sm text-foreground-lighter text-balance">
|
||||
No {missingEntity} projects found. Create a {missingEntity} project to link to a{' '}
|
||||
{oppositeMissingEntity} project, or skip and connect later.
|
||||
{oppositeMissingEntity} project
|
||||
{onSkip !== undefined ? ', or skip and connect later.' : '.'}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -192,8 +193,12 @@ export const ProjectLinker = ({
|
||||
<p className="text-foreground-light text-sm">
|
||||
You will need to create a {missingEntity} Project to link to a {oppositeMissingEntity}{' '}
|
||||
Project.
|
||||
<br />
|
||||
You can skip this and create a Project Connection later.
|
||||
{onSkip !== undefined && (
|
||||
<>
|
||||
<br />
|
||||
You can skip this and create a Project Connection later.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -13,9 +13,33 @@ export function getErrorMessage(error: unknown): string | undefined {
|
||||
|
||||
export type VercelInstallSource = 'deploy-button' | 'marketplace' | 'external'
|
||||
|
||||
export function getVercelInstallSource(
|
||||
/**
|
||||
* Vercel sometimes sends source=marketplace with Deploy Button params
|
||||
* (currentProjectId + external-id). Treat that as deploy-button for routing.
|
||||
*/
|
||||
export function hasVercelDeployButtonSignals({
|
||||
currentProjectId,
|
||||
externalId,
|
||||
}: {
|
||||
currentProjectId?: string
|
||||
externalId?: string
|
||||
}): boolean {
|
||||
return Boolean(currentProjectId && externalId)
|
||||
}
|
||||
|
||||
export function resolveVercelInstallSource({
|
||||
source,
|
||||
currentProjectId,
|
||||
externalId,
|
||||
}: {
|
||||
source: string | undefined
|
||||
): VercelInstallSource | undefined {
|
||||
currentProjectId?: string
|
||||
externalId?: string
|
||||
}): VercelInstallSource | undefined {
|
||||
if (hasVercelDeployButtonSignals({ currentProjectId, externalId })) {
|
||||
return 'deploy-button'
|
||||
}
|
||||
|
||||
switch (source) {
|
||||
case 'deploy-button':
|
||||
case 'marketplace':
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
|
||||
import { withAuth } from '@/hooks/misc/withAuth'
|
||||
import { BASE_PATH } from '@/lib/constants'
|
||||
import { getErrorMessage } from '@/lib/get-error-message'
|
||||
import { hasVercelDeployButtonSignals } from '@/lib/integrations/vercel-install.utils'
|
||||
import { buildStudioPageTitle } from '@/lib/page-title'
|
||||
import { useProfileNameAndPicture } from '@/lib/profile'
|
||||
import { EMPTY_ARR } from '@/lib/void'
|
||||
@@ -40,9 +41,10 @@ const PAGE_TITLE = buildStudioPageTitle({
|
||||
})
|
||||
|
||||
const VercelChooseProjectPage: NextPageWithLayout = () => {
|
||||
const { slug, configurationId, next, currentProjectId } = useParams()
|
||||
const { slug, configurationId, next, currentProjectId, externalId } = useParams()
|
||||
const { username, primaryEmail, avatarUrl } = useProfileNameAndPicture()
|
||||
const displayName = primaryEmail ?? username ?? ''
|
||||
const isDeployButtonFlow = hasVercelDeployButtonSignals({ currentProjectId, externalId })
|
||||
|
||||
const {
|
||||
data: integrationData,
|
||||
@@ -199,11 +201,15 @@ const VercelChooseProjectPage: NextPageWithLayout = () => {
|
||||
getForeignProjectIcon={getForeignProjectIcon}
|
||||
choosePrompt="Choose Vercel project"
|
||||
defaultForeignProjectId={currentProjectId}
|
||||
onSkip={() => {
|
||||
if (next && isVercelUrl(next)) {
|
||||
window.location.href = next
|
||||
}
|
||||
}}
|
||||
onSkip={
|
||||
isDeployButtonFlow
|
||||
? undefined
|
||||
: () => {
|
||||
if (next && isVercelUrl(next)) {
|
||||
window.location.href = next
|
||||
}
|
||||
}
|
||||
}
|
||||
loadingForeignProjects={isLoadingVercelProjectsData}
|
||||
mode="Vercel"
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@ import { withAuth } from '@/hooks/misc/withAuth'
|
||||
import {
|
||||
buildVercelInstallRouteQuery,
|
||||
getErrorMessage,
|
||||
getVercelInstallSource,
|
||||
resolveVercelInstallSource,
|
||||
} from '@/lib/integrations/vercel-install.utils'
|
||||
import { buildStudioPageTitle } from '@/lib/page-title'
|
||||
import { useProfileNameAndPicture } from '@/lib/profile'
|
||||
@@ -115,7 +115,11 @@ const VercelIntegration: NextPageWithLayout = () => {
|
||||
*/
|
||||
function handleRouteChange() {
|
||||
const orgSlug = selectedOrg?.slug
|
||||
const vercelInstallSource = getVercelInstallSource(source)
|
||||
const vercelInstallSource = resolveVercelInstallSource({
|
||||
source,
|
||||
currentProjectId,
|
||||
externalId,
|
||||
})
|
||||
const query = buildVercelInstallRouteQuery({
|
||||
source: vercelInstallSource,
|
||||
organizationSlug: orgSlug,
|
||||
|
||||
@@ -3,7 +3,8 @@ import { describe, expect, test } from 'vitest'
|
||||
import {
|
||||
buildVercelInstallRouteQuery,
|
||||
getErrorMessage,
|
||||
getVercelInstallSource,
|
||||
hasVercelDeployButtonSignals,
|
||||
resolveVercelInstallSource,
|
||||
} from '@/lib/integrations/vercel-install.utils'
|
||||
|
||||
describe('getErrorMessage', () => {
|
||||
@@ -89,15 +90,46 @@ describe('buildVercelInstallRouteQuery', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('getVercelInstallSource', () => {
|
||||
test('returns supported Vercel install sources', () => {
|
||||
expect(getVercelInstallSource('deploy-button')).toBe('deploy-button')
|
||||
expect(getVercelInstallSource('marketplace')).toBe('marketplace')
|
||||
expect(getVercelInstallSource('external')).toBe('external')
|
||||
describe('hasVercelDeployButtonSignals', () => {
|
||||
test('requires both currentProjectId and externalId', () => {
|
||||
expect(
|
||||
hasVercelDeployButtonSignals({
|
||||
currentProjectId: 'prj_123',
|
||||
externalId: 'https://github.com/org/repo',
|
||||
})
|
||||
).toBe(true)
|
||||
expect(hasVercelDeployButtonSignals({ currentProjectId: 'prj_123' })).toBe(false)
|
||||
expect(hasVercelDeployButtonSignals({ externalId: 'https://github.com/org/repo' })).toBe(false)
|
||||
expect(hasVercelDeployButtonSignals({})).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
test('returns undefined for unsupported sources', () => {
|
||||
expect(getVercelInstallSource('deploybutton')).toBeUndefined()
|
||||
expect(getVercelInstallSource(undefined)).toBeUndefined()
|
||||
describe('resolveVercelInstallSource', () => {
|
||||
test('overrides marketplace and external when deploy-button signals are present', () => {
|
||||
expect(
|
||||
resolveVercelInstallSource({
|
||||
source: 'marketplace',
|
||||
currentProjectId: 'prj_123',
|
||||
externalId: 'https://github.com/org/repo',
|
||||
})
|
||||
).toBe('deploy-button')
|
||||
expect(
|
||||
resolveVercelInstallSource({
|
||||
source: 'external',
|
||||
currentProjectId: 'prj_123',
|
||||
externalId: 'https://github.com/org/repo',
|
||||
})
|
||||
).toBe('deploy-button')
|
||||
})
|
||||
|
||||
test('keeps the declared source when deploy-button signals are incomplete', () => {
|
||||
expect(
|
||||
resolveVercelInstallSource({
|
||||
source: 'marketplace',
|
||||
currentProjectId: 'prj_123',
|
||||
})
|
||||
).toBe('marketplace')
|
||||
expect(resolveVercelInstallSource({ source: 'deploy-button' })).toBe('deploy-button')
|
||||
expect(resolveVercelInstallSource({ source: undefined })).toBeUndefined()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user