mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(studio): mcp secrets interstitial polish (#50351)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? - We made the Next step copy a bit more generic so it doesn't read like it's pointing you back to Inspector UI. - Added CTA on key stored screen to send you to Edge Function Secrets directly. - Tidies up footer area to always be centrally aligned across all states. ### 1. Enable the feature flag ### 2. Preview states via URL Mock mode is enabled automatically in local/staging. Navigate to `/mcp/secrets` with a `state` query param: http://localhost:8082/mcp/secrets?state=<state> States that need no other params: | `state` value | What it shows | | ----------------- | --------------------------------------- | | `loading` | Loading skeleton | | `expired` | Link expired | | `cancelled` | Request cancelled | | `paused` | Storing keys paused | | `wrong-account` | Signed in as the wrong account | | `error` | Generic failure | States that need a real project —ame=<KEY_NAME>`: | `state` value | What it sh | | -------------------- | ---------------------- | | `form` | The "st | | `stored` | Success | | `stored-timeout` | Successopped waiting | | `already-stored` | Key was already stored, nothing to do | Example: http://localhost:8082/mcp/secretsJECT_REF&name=OPENAI_API_KEY ### 3. What to check - [ ] `stored` / `already-stored`tions secrets"** button linking to `/project/<ref>/functions/secrets - [ ] States without a project re `paused`, `error`) don't show that button - [ ] Footer text is centered on - [ ] `wrong-account` → **Switch its footer is centered - [ ] The provider-dashboard link` state, use a `name` like`OPENAI_API_KEY` or `RESEND_API_Khint) is centered too <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a project-specific link to Edge Functions secrets from the MCP setup screen when a project is available. * Added a separator to distinguish the secrets link from the remaining setup guidance. * **Improvements** * Updated completion guidance to tell users to return to their agent and confirm the setup is finished. * Standardized interstitial footer content with centered guidance and consistent provider dashboard instructions. * **Tests** * Added coverage for displaying the project-specific secrets link and hiding it when no project is associated. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ali Waseem <waseema393@gmail.com>
This commit is contained in:
1 parent
b0de9dd7a6
commit
4aa34f556a
8 files changed
+56
-27
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { cn, Skeleton } from 'ui'
|
||||
import { Skeleton } from 'ui'
|
||||
|
||||
import { InterstitialLayout, SupabaseLogo } from '@/components/layouts/InterstitialLayout'
|
||||
|
||||
@@ -36,19 +36,6 @@ export const InterstitialShellSkeleton = ({ children }: { children: ReactNode })
|
||||
</InterstitialShell>
|
||||
)
|
||||
|
||||
export const InterstitialFooter = ({
|
||||
children,
|
||||
align = 'center',
|
||||
}: {
|
||||
children: ReactNode
|
||||
align?: 'center' | 'start'
|
||||
}) => (
|
||||
<p
|
||||
className={cn(
|
||||
'text-xs text-foreground-light',
|
||||
align === 'center' ? 'text-center' : 'text-left'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
export const InterstitialFooter = ({ children }: { children: ReactNode }) => (
|
||||
<p className="text-center text-xs text-foreground-light">{children}</p>
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
import { screen } from '@testing-library/react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { InterstitialTerminalScreen } from './InterstitialTerminalScreen'
|
||||
import { customRender } from '@/tests/lib/custom-render'
|
||||
|
||||
const baseCopy = {
|
||||
title: 'Key stored',
|
||||
subtitle: 'RESEND_API_KEY is saved for billing-staging.',
|
||||
calloutTitle: 'Next step',
|
||||
calloutBody: 'Head back to your agent and let it know you finished.',
|
||||
footer: 'You can close this tab.',
|
||||
}
|
||||
|
||||
describe('InterstitialTerminalScreen', () => {
|
||||
it('links to the project Edge Functions secrets page when a project ref is given', () => {
|
||||
customRender(<InterstitialTerminalScreen {...baseCopy} projectRef="abcdefghijklmnopqrst" />)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'Go to Edge Functions secrets' })).toHaveAttribute(
|
||||
'href',
|
||||
'/project/abcdefghijklmnopqrst/functions/secrets'
|
||||
)
|
||||
})
|
||||
|
||||
it('omits the Edge Functions secrets link when there is no project to point at', () => {
|
||||
customRender(<InterstitialTerminalScreen {...baseCopy} />)
|
||||
|
||||
expect(
|
||||
screen.queryByRole('link', { name: 'Go to Edge Functions secrets' })
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Button, Separator } from 'ui'
|
||||
import { Admonition } from 'ui-patterns/Admonition'
|
||||
|
||||
import { InterstitialFooter, InterstitialShell } from './InterstitialShell'
|
||||
@@ -8,6 +9,7 @@ export type InterstitialTerminalCopy = {
|
||||
calloutTitle: string
|
||||
calloutBody: string
|
||||
footer: string
|
||||
projectRef?: string
|
||||
}
|
||||
|
||||
export const InterstitialTerminalScreen = ({
|
||||
@@ -16,9 +18,19 @@ export const InterstitialTerminalScreen = ({
|
||||
calloutTitle,
|
||||
calloutBody,
|
||||
footer,
|
||||
projectRef,
|
||||
}: InterstitialTerminalCopy) => (
|
||||
<InterstitialShell title={title} subtitle={subtitle}>
|
||||
<Admonition type="note" title={calloutTitle} description={calloutBody} className="mb-0" />
|
||||
|
||||
{projectRef && (
|
||||
<Button block variant="default" asChild>
|
||||
<a href={`/project/${projectRef}/functions/secrets`}>Go to Edge Functions secrets</a>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
<InterstitialFooter>{footer}</InterstitialFooter>
|
||||
</InterstitialShell>
|
||||
)
|
||||
@@ -2,8 +2,6 @@ export const MCP_SECRETS_ROUTE = '/mcp/secrets'
|
||||
|
||||
export const MCP_SECRETS_FLAG = 'McpElicitURLMode'
|
||||
|
||||
export const UNKNOWN_CLIENT_LABEL = 'your AI client'
|
||||
|
||||
export const SECRETS_TOOL_NAME = 'create_edge_function_secret'
|
||||
|
||||
export const MAX_SECRET_NAME_LENGTH = 256
|
||||
|
||||
@@ -35,9 +35,7 @@ describe('getSecretsCopy', () => {
|
||||
it('names the generic client, because v1 never learns which one sent the user', () => {
|
||||
const copy = getSecretsCopy({ status: 'stored', request, timedOut: false })
|
||||
|
||||
expect(copy.calloutBody).toBe(
|
||||
`Go back to your AI client and choose "I've completed it" to finish the tool call.`
|
||||
)
|
||||
expect(copy.calloutBody).toBe(`Head back to your agent and let it know you're finished.`)
|
||||
})
|
||||
|
||||
it('sends the user back to the agent instead of the client on timeout', () => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
|
||||
import type { InterstitialTerminalCopy } from '../InterstitialTerminalScreen'
|
||||
import { UNKNOWN_CLIENT_LABEL } from './McpSecrets.constants'
|
||||
import type {
|
||||
SecretProviderHint,
|
||||
SecretRequest,
|
||||
@@ -14,7 +13,7 @@ import type {
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const RETURN_TO_CLIENT_STEP = `Go back to ${UNKNOWN_CLIENT_LABEL} and choose "I've completed it" to finish the tool call.`
|
||||
const RETURN_TO_CLIENT_STEP = `Head back to your agent and let it know you're finished.`
|
||||
|
||||
const CLOSE_TAB_FOOTER = 'You can close this tab.'
|
||||
|
||||
@@ -35,6 +34,7 @@ export function getSecretsCopy(state: SecretsOutcomeState): InterstitialTerminal
|
||||
calloutBody:
|
||||
'Ask your agent to store the key again. It will find the saved key and finish without sending you back here.',
|
||||
footer: UNVERIFIED_KEY_FOOTER,
|
||||
projectRef: state.request.ref,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export function getSecretsCopy(state: SecretsOutcomeState): InterstitialTerminal
|
||||
calloutTitle: 'Next step',
|
||||
calloutBody: RETURN_TO_CLIENT_STEP,
|
||||
footer: UNVERIFIED_KEY_FOOTER,
|
||||
projectRef: state.request.ref,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +55,7 @@ export function getSecretsCopy(state: SecretsOutcomeState): InterstitialTerminal
|
||||
calloutTitle: 'Next step',
|
||||
calloutBody: RETURN_TO_CLIENT_STEP,
|
||||
footer: CLOSE_TAB_FOOTER,
|
||||
projectRef: state.request.ref,
|
||||
}
|
||||
|
||||
case 'expired':
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Admonition } from 'ui-patterns/Admonition'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import z from 'zod'
|
||||
|
||||
import { InterstitialShell } from '../InterstitialShell'
|
||||
import { InterstitialFooter, InterstitialShell } from '../InterstitialShell'
|
||||
import type { SecretRequest } from './McpSecrets.types'
|
||||
import {
|
||||
getOverwriteWarning,
|
||||
@@ -163,7 +163,7 @@ export const McpSecretsForm = ({
|
||||
{providerHint && (
|
||||
<>
|
||||
<Separator />
|
||||
<p className="text-xs text-foreground-light">
|
||||
<InterstitialFooter>
|
||||
{`Don't have your key? Create one in the `}
|
||||
{providerHint.dashboardUrl ? (
|
||||
<a
|
||||
@@ -178,7 +178,7 @@ export const McpSecretsForm = ({
|
||||
`${providerHint.name} dashboard`
|
||||
)}
|
||||
.
|
||||
</p>
|
||||
</InterstitialFooter>
|
||||
</>
|
||||
)}
|
||||
</InterstitialShell>
|
||||
|
||||
@@ -27,7 +27,7 @@ export const McpSecretsWrongAccount = ({
|
||||
|
||||
<Separator />
|
||||
|
||||
<InterstitialFooter align="start">
|
||||
<InterstitialFooter>
|
||||
After switching, ask your agent to run the tool again. Nothing has been stored.
|
||||
</InterstitialFooter>
|
||||
</InterstitialShell>
|
||||
|
||||
Reference in new issue
Block a user