diff --git a/apps/docs/content/guides/platform/billing-on-supabase.mdx b/apps/docs/content/guides/platform/billing-on-supabase.mdx
index 54293ddd95284..868a0f87059d2 100644
--- a/apps/docs/content/guides/platform/billing-on-supabase.mdx
+++ b/apps/docs/content/guides/platform/billing-on-supabase.mdx
@@ -64,9 +64,9 @@ The quota is applied to your entire organization, independent of how many projec
| Usage Item | Free | Pro/Team | Enterprise |
| -------------------------------- | ------------------------ | ------------------------------------------------------------------- | ---------- |
| Egress | 5 GB | 250 GB included, then per GB | Custom |
-| Database Size | 500 MB | 8 GB disk per project included, then per GB | Custom |
+| Database Size | 500 MB per project | 8 GB disk per project included, then per GB | Custom |
| Monthly Active Users | 50,000 MAU | 100,000 MAU included, then per MAU | Custom |
-| Monthly Active Third-Party Users | 50 MAU | 50 MAU included, then per MAU | Custom |
+| Monthly Active Third-Party Users | 50,000 MAU | 100,000 MAU included, then per MAU | Custom |
| Monthly Active SSO Users | Unavailable on Free Plan | 50 MAU included, then per MAU | Custom |
| Storage Size | 1 GB | 100 GB included, then per GB | Custom |
| Storage Images Transformed | Unavailable on Free Plan | 100 included, then per 1000 | Custom |
diff --git a/apps/docs/content/guides/platform/manage-your-usage/edge-function-invocations.mdx b/apps/docs/content/guides/platform/manage-your-usage/edge-function-invocations.mdx
index db31cb0d45693..9109b2d3261a0 100644
--- a/apps/docs/content/guides/platform/manage-your-usage/edge-function-invocations.mdx
+++ b/apps/docs/content/guides/platform/manage-your-usage/edge-function-invocations.mdx
@@ -5,7 +5,7 @@ title: 'Manage Edge Function Invocations usage'
## What you are charged for
-You are charged for the number of times your functions get invoked, regardless of the response status code.
+You are charged for the number of times your functions get invoked, regardless of the response status code. Preflight (OPTIONS) requests are not billed.
## How charges are calculated
diff --git a/apps/docs/lib/userAuth.ts b/apps/docs/lib/userAuth.ts
index 12fec885fe721..973c5eb8bb177 100644
--- a/apps/docs/lib/userAuth.ts
+++ b/apps/docs/lib/userAuth.ts
@@ -1,6 +1,11 @@
-import { gotrueClient } from 'common'
+import * as Sentry from '@sentry/nextjs'
+import { gotrueClient, setCaptureException } from 'common'
import { useEffect } from 'react'
+setCaptureException((e: any) => {
+ Sentry.captureException(e)
+})
+
export const auth = gotrueClient
export async function getAccessToken() {
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx
index 9a56fd87b21e4..03d791ca8914e 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx
@@ -1,6 +1,13 @@
import { LOCAL_STORAGE_KEYS } from 'common'
export const FEATURE_PREVIEWS = [
+ {
+ key: LOCAL_STORAGE_KEYS.UI_PREVIEW_SECURITY_NOTIFICATIONS,
+ name: 'Security notification templates',
+ discussionsUrl: undefined,
+ isNew: true,
+ isPlatformOnly: true,
+ },
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_NEW_STORAGE_UI,
name: 'New Storage interface',
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
index 885724aca13c0..bb2b449c4d512 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
@@ -115,6 +115,11 @@ export const useIsNewStorageUIEnabled = () => {
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_NEW_STORAGE_UI]
}
+export const useIsSecurityNotificationsEnabled = () => {
+ const { flags } = useFeaturePreviewContext()
+ return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_SECURITY_NOTIFICATIONS]
+}
+
export const useFeaturePreviewModal = () => {
const [featurePreviewModal, setFeaturePreviewModal] = useQueryState('featurePreviewModal')
@@ -122,6 +127,7 @@ export const useFeaturePreviewModal = () => {
const advisorRulesEnabled = useFlag('advisorRules')
const isUnifiedLogsPreviewAvailable = useFlag('unifiedLogs')
const isNewStorageUIAvailable = useFlag('storageAnalyticsVector')
+ const isSecurityNotificationsAvailable = useFlag('securityNotifications')
const selectedFeatureKeyFromQuery = featurePreviewModal?.trim() ?? null
const showFeaturePreviewModal = selectedFeatureKeyFromQuery !== null
@@ -138,11 +144,19 @@ export const useFeaturePreviewModal = () => {
return isUnifiedLogsPreviewAvailable
case 'new-storage-ui':
return isNewStorageUIAvailable
+ case 'security-notifications':
+ return isSecurityNotificationsAvailable
default:
return true
}
},
- [gitlessBranchingEnabled, advisorRulesEnabled, isUnifiedLogsPreviewAvailable]
+ [
+ gitlessBranchingEnabled,
+ advisorRulesEnabled,
+ isUnifiedLogsPreviewAvailable,
+ isNewStorageUIAvailable,
+ isSecurityNotificationsAvailable,
+ ]
)
const selectedFeatureKey = (
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
index ec463be8a6477..3fa262e4b123e 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
@@ -16,6 +16,7 @@ import { useFeaturePreviewContext, useFeaturePreviewModal } from './FeaturePrevi
import { InlineEditorPreview } from './InlineEditorPreview'
import { NewStorageUIPreview } from './NewStorageUIPreview'
import { UnifiedLogsPreview } from './UnifiedLogsPreview'
+import { SecurityNotificationsPreview } from './SecurityNotificationsPreview'
const FEATURE_PREVIEW_KEY_TO_CONTENT: {
[key: string]: ReactNode
@@ -27,6 +28,7 @@ const FEATURE_PREVIEW_KEY_TO_CONTENT: {
[LOCAL_STORAGE_KEYS.UI_PREVIEW_CLS]: ,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS]: ,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_NEW_STORAGE_UI]: ,
+ [LOCAL_STORAGE_KEYS.UI_PREVIEW_SECURITY_NOTIFICATIONS]: ,
}
const FeaturePreviewModal = () => {
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/SecurityNotificationsPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/SecurityNotificationsPreview.tsx
new file mode 100644
index 0000000000000..8857881dfa4fb
--- /dev/null
+++ b/apps/studio/components/interfaces/App/FeaturePreview/SecurityNotificationsPreview.tsx
@@ -0,0 +1,46 @@
+import Image from 'next/image'
+
+import { useParams } from 'common'
+import { InlineLink } from 'components/ui/InlineLink'
+import { BASE_PATH } from 'lib/constants'
+import { useIsSecurityNotificationsEnabled } from './FeaturePreviewContext'
+
+export const SecurityNotificationsPreview = () => {
+ const { ref } = useParams()
+ const isSecurityNotificationsEnabled = useIsSecurityNotificationsEnabled()
+
+ return (
+
+
+
+
+ Try out our expanded set of{' '}
+
+ email templates
+ {' '}
+ with support for security-related notifications.
+
+
Enabling this preview will:
+
+ - Add a dedicated sidebar section for contact methods like email and SMS
+ - Add new email templates for security-related notifications
+ - Move each (existing and new) template into its own dynamic route
+
+
+ These changes are necessary to support incoming security-related notification templates.
+ Given that the list of our email templates is doubling in size, this change requires some
+ wider interface changes. Ones that we think make for a clearer experience overall. Win
+ win!
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx
index 52f5444c228a4..58b820518666c 100644
--- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx
+++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx
@@ -705,6 +705,11 @@ export const SQLEditor = () => {
padding: { top: 4 },
lineNumbersMinChars: 3,
}}
+ // [Joshen] These ones are meant to solve a UI issue that seems to only be happening locally
+ // Happens when you use the inline assistant in the SQL Editor and accept the suggestion
+ // Error: TextModel got disposed before DiffEditorWidget model got reset
+ keepCurrentModifiedModel={true}
+ keepCurrentOriginalModel={true}
/>
{showWidget && (
{
- const where = genWhereStatement(table, filters)
+ let where = genWhereStatement(table, filters)
+ // pg_cron logs are a subset of postgres logs
+ // to calculate the chart, we need to query postgres logs
+ if (table === LogsTableName.PG_CRON) {
+ table = LogsTableName.POSTGRES
+ where = basePgCronWhere
+ }
const joins = genCrossJoinUnnests(table)
return `SELECT count(*) as count FROM ${table} ${joins} ${where}`
}
@@ -320,7 +325,10 @@ const calcChartStart = (params: Partial): [Dayjs, string] =>
return [its.add(-extendValue, trunc), trunc]
}
-const basePgCronWhere = `where ( parsed.application_name = 'pg_cron' or regexp_contains(event_message, 'cron job') )`
+// TODO(qiao): workaround for self-hosted cron logs error until logflare is fixed
+const basePgCronWhere = IS_PLATFORM
+ ? `where ( parsed.application_name = 'pg_cron' or regexp_contains(event_message, 'cron job') )`
+ : `where ( parsed.application_name = 'pg_cron' or event_message::text LIKE '%cron job%' )`
/**
*
* generates log event chart query
diff --git a/apps/studio/components/interfaces/Support/CategoryAndSeverityInfo.tsx b/apps/studio/components/interfaces/Support/CategoryAndSeverityInfo.tsx
index 7d612c2d650fb..ae10f3997b09f 100644
--- a/apps/studio/components/interfaces/Support/CategoryAndSeverityInfo.tsx
+++ b/apps/studio/components/interfaces/Support/CategoryAndSeverityInfo.tsx
@@ -83,7 +83,7 @@ function CategorySelector({ form }: CategorySelectorProps) {
- {CATEGORY_OPTIONS.map((option) => (
+ {CATEGORY_OPTIONS.filter((option) => !option.hidden).map((option) => (
{option.label}
diff --git a/apps/studio/components/interfaces/Support/Support.constants.ts b/apps/studio/components/interfaces/Support/Support.constants.ts
index 777d187c6507b..f7ddf76f92005 100644
--- a/apps/studio/components/interfaces/Support/Support.constants.ts
+++ b/apps/studio/components/interfaces/Support/Support.constants.ts
@@ -3,13 +3,14 @@ import { isFeatureEnabled } from 'common'
const billingEnabled = isFeatureEnabled('billing:all')
-export type ExtendedSupportCategories = SupportCategories | 'Plan_upgrade'
+export type ExtendedSupportCategories = SupportCategories | 'Plan_upgrade' | 'Others'
export const CATEGORY_OPTIONS: {
value: ExtendedSupportCategories
label: string
description: string
query?: string
+ hidden?: boolean
}[] = [
{
value: SupportCategories.PROBLEM,
@@ -77,6 +78,13 @@ export const CATEGORY_OPTIONS: {
query: undefined,
},
]),
+ {
+ value: 'Others' as const,
+ label: 'Others',
+ description: 'Issues that are not related to any of the other categories',
+ query: undefined,
+ hidden: true,
+ },
]
export const SEVERITY_OPTIONS = [
diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx
index e431ddd52115c..04b62436b1409 100644
--- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx
+++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx
@@ -1,8 +1,9 @@
-import { type Dispatch, type MouseEventHandler } from 'react'
+import { useEffect, type Dispatch, type MouseEventHandler } from 'react'
import type { SubmitHandler, UseFormReturn } from 'react-hook-form'
// End of third-party imports
import { SupportCategories } from '@supabase/shared-types/out/constants'
+import { useFlag } from 'common'
import { CLIENT_LIBRARIES } from 'common/constants'
import { getProjectAuthConfig } from 'data/auth/auth-config-query'
import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send'
@@ -33,6 +34,15 @@ import {
NO_PROJECT_MARKER,
} from './SupportForm.utils'
+const useIsSimplifiedForm = (slug: string) => {
+ const simplifiedSupportForm = useFlag('simplifiedSupportForm')
+ if (typeof simplifiedSupportForm === 'string') {
+ const slugs = (simplifiedSupportForm as string).split(',').map((x) => x.trim())
+ return slugs.includes(slug)
+ }
+ return false
+}
+
interface SupportFormV2Props {
form: UseFormReturn
initialError: string | null
@@ -45,6 +55,7 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
const respondToEmail = profile?.primary_email ?? 'your email'
const { organizationSlug, projectRef, category, severity, subject, library } = form.watch()
+ const simplifiedSupportForm = useIsSimplifiedForm(organizationSlug)
const selectedOrgSlug = organizationSlug === NO_ORG_MARKER ? null : organizationSlug
const selectedProjectRef = projectRef === NO_PROJECT_MARKER ? null : projectRef
@@ -85,6 +96,7 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
const payload = {
...values,
+ category,
organizationSlug: values.organizationSlug ?? NO_ORG_MARKER,
projectRef: values.projectRef ?? NO_PROJECT_MARKER,
allowSupportAccess: SUPPORT_ACCESS_CATEGORIES.includes(values.category)
@@ -134,6 +146,15 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
handleFormSubmit(event)
}
+ useEffect(() => {
+ if (simplifiedSupportForm) {
+ form.setValue('category', 'Others')
+ } else {
+ form.setValue('category', '' as any)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [simplifiedSupportForm])
+
return (