mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## Summary * Adds a second branch to the assistant's `get_active_incidents` tool (`apps/studio/lib/ai/tools/incident-tools.ts`) that reads `/api/status-page` when the global ConfigCat flag `incidentIoStatusPage` is on, instead of the legacy `/api/incident-status` endpoint. * Filters on `visible`, ignores `show_banner` (only the global banner respects it), and concatenates ongoing incidents + in-progress maintenances (not scheduled maintenances). * Adds `isServerFlagEnabled` to `lib/server/configcat.ts` for reading global, non-user-targeted ConfigCat flags server-side, and threads the flag through `getTools` from `pages/api/ai/sql/generate-v4.ts`. This is PR 3 of 6 in the [FE-4057](https://linear.app/supabase/issue/FE-4057/frontend-bannerbot-reconfigured) stack — stacked on `charis/fe-4057-pr2-support-form`. Behind the `incidentIoStatusPage` flag (default off), so this ships no user-visible change on its own. See Linear [FE-4057](https://linear.app/supabase/issue/FE-4057/frontend-bannerbot-reconfigured) for full context. ## Test plan - [X] `pnpm --filter studio run typecheck` - [X] `pnpm --filter studio run lint:ratchet` - [X] `pnpm knip --workspace apps/studio` - [X] `pnpm test:prettier` - [X] `pnpm --filter studio exec vitest run lib/ai/tools/incident-tools.test.ts` (19/19 passing, including 7 new tests for the status-page branch)
82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
import { getClient, PollingMode, User } from '@configcat/sdk/node'
|
|
import { IS_PLATFORM } from 'common'
|
|
|
|
let serverClient: ReturnType<typeof getClient>
|
|
|
|
export type TrustedUserEmail = string & { readonly __trustedUserEmailBrand: never }
|
|
|
|
// Promotes an email to TrustedUserEmail. Only call this with an email whose origin is already
|
|
// verified — e.g. `claims.email` from a JWT that apiAuthenticate has confirmed — never with a
|
|
// raw request body/query param.
|
|
export function trustedUserEmail(email: string | undefined): TrustedUserEmail | undefined {
|
|
return email as TrustedUserEmail | undefined
|
|
}
|
|
|
|
const STAFF_EMAIL_DOMAINS = ['supabase.com', 'supabase.io']
|
|
|
|
function isStaffEmail(email: string): boolean {
|
|
const domain = email.slice(email.lastIndexOf('@') + 1).toLowerCase()
|
|
return STAFF_EMAIL_DOMAINS.includes(domain)
|
|
}
|
|
|
|
function buildUser(userEmail?: TrustedUserEmail, customAttributes?: Record<string, string>) {
|
|
const _customAttributes = {
|
|
...customAttributes,
|
|
is_staff: (!!userEmail && isStaffEmail(userEmail)).toString(),
|
|
}
|
|
|
|
return new User(userEmail ?? 'anonymous', undefined, undefined, _customAttributes)
|
|
}
|
|
|
|
function getServerClient() {
|
|
if (serverClient) return serverClient
|
|
|
|
const proxyUrl = process.env.NEXT_PUBLIC_CONFIGCAT_PROXY_URL
|
|
const sdkKey = process.env.NEXT_PUBLIC_CONFIGCAT_SDK_KEY
|
|
|
|
if (!proxyUrl && !sdkKey) {
|
|
console.log('Skipping server ConfigCat set up as env vars are not present')
|
|
return undefined
|
|
}
|
|
|
|
try {
|
|
if (proxyUrl) {
|
|
serverClient = getClient('configcat-proxy/frontend-v2', PollingMode.LazyLoad, {
|
|
baseUrl: proxyUrl,
|
|
})
|
|
return serverClient
|
|
}
|
|
|
|
if (sdkKey) {
|
|
serverClient = getClient(sdkKey, PollingMode.LazyLoad)
|
|
return serverClient
|
|
}
|
|
|
|
return undefined
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
console.error(`Failed to get server ConfigCat client: ${message}`)
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
export async function getServerFlags(
|
|
userEmail?: TrustedUserEmail,
|
|
customAttributes?: Record<string, string>
|
|
) {
|
|
const client = getServerClient()
|
|
|
|
if (!client) {
|
|
return []
|
|
}
|
|
|
|
return client.getAllValuesAsync(buildUser(userEmail, customAttributes))
|
|
}
|
|
|
|
export async function isServerFlagEnabled(name: string): Promise<boolean> {
|
|
if (!IS_PLATFORM) return false
|
|
|
|
const flags = await getServerFlags()
|
|
return flags.some((flag) => flag.settingKey === name && flag.settingValue === true)
|
|
}
|