diff --git a/apps/studio/components/interfaces/BranchManagement/Overview.tsx b/apps/studio/components/interfaces/BranchManagement/Overview.tsx
index 666cf7445c62e..4f9f6f047b976 100644
--- a/apps/studio/components/interfaces/BranchManagement/Overview.tsx
+++ b/apps/studio/components/interfaces/BranchManagement/Overview.tsx
@@ -180,9 +180,14 @@ const PreviewBranchActions = ({
const { data } = useBranchQuery({ projectRef, branchRef })
const isBranchActiveHealthy = data?.status === 'ACTIVE_HEALTHY'
+ const isPersistentBranch = branch.persistent
const [showConfirmResetModal, setShowConfirmResetModal] = useState(false)
const [showBranchModeSwitch, setShowBranchModeSwitch] = useState(false)
+ const [
+ showPersistentBranchDeleteConfirmationModal,
+ setShowPersistentBranchDeleteConfirmationModal,
+ ] = useState(false)
const [showEditBranchModal, setShowEditBranchModal] = useState(false)
const { mutate: resetBranch, isLoading: isResetting } = useBranchResetMutation({
@@ -210,6 +215,15 @@ const PreviewBranchActions = ({
updateBranch({ branchRef, projectRef, persistent: !branch.persistent })
}
+ const onDeleteBranch = (e: Event | React.MouseEvent) => {
+ if (isPersistentBranch) {
+ setShowPersistentBranchDeleteConfirmationModal(true)
+ } else {
+ e.stopPropagation()
+ onSelectDeleteBranch()
+ }
+ }
+
return (
<>
@@ -307,14 +321,8 @@ const PreviewBranchActions = ({
{
- e.stopPropagation()
- onSelectDeleteBranch()
- }}
- onClick={(e) => {
- e.stopPropagation()
- onSelectDeleteBranch()
- }}
+ onSelect={onDeleteBranch}
+ onClick={onDeleteBranch}
tooltip={{
content: {
side: 'left',
@@ -373,6 +381,20 @@ const PreviewBranchActions = ({
+ setShowPersistentBranchDeleteConfirmationModal(false)}
+ onConfirm={onTogglePersistent}
+ >
+
+ You must switch the branch "{branch.name}" to preview before deleting it.
+
+
+
= {
interface TriggerSheetProps {
selectedTrigger?: PostgresTrigger
+ isDuplicatingTrigger?: boolean
open: boolean
- setOpen: (val: boolean) => void
+ onClose: () => void
}
-export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetProps) => {
+export const TriggerSheet = ({
+ selectedTrigger,
+ isDuplicatingTrigger,
+ open,
+ onClose,
+}: TriggerSheetProps) => {
const { data: project } = useSelectedProjectQuery()
+ const [isClosingPanel, setIsClosingPanel] = useState(false)
const [showFunctionSelector, setShowFunctionSelector] = useState(false)
const { mutate: createDatabaseTrigger, isLoading: isCreating } = useDatabaseTriggerCreateMutation(
{
- onSuccess: (res) => {
- toast.success(`Successfully created trigger ${res.name}`)
- setOpen(false)
+ onSuccess: () => {
+ toast.success(`Successfully created trigger`)
+ onClose()
},
onError: (error) => {
toast.error(`Failed to create trigger: ${error.message}`)
@@ -97,9 +105,9 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
)
const { mutate: updateDatabaseTrigger, isLoading: isUpdating } = useDatabaseTriggerUpdateMutation(
{
- onSuccess: (res) => {
- toast.success(`Successfully updated trigger ${res.name}`)
- setOpen(false)
+ onSuccess: () => {
+ toast.success(`Successfully updated trigger`)
+ onClose()
},
onError: (error) => {
toast.error(`Failed to update trigger: ${error.message}`)
@@ -117,7 +125,7 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
const tables = data
.sort((a, b) => a.schema.localeCompare(b.schema))
.filter((a) => !protectedSchemas.find((s) => s.name === a.schema))
- const isEditing = !!selectedTrigger
+ const isEditing = !isDuplicatingTrigger && !!selectedTrigger
const form = useForm>({
mode: 'onSubmit',
@@ -127,6 +135,10 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
})
const { function_name, function_schema } = form.watch()
+ function isClosingSidePanel() {
+ form.formState.isDirty ? setIsClosingPanel(true) : onClose()
+ }
+
const onSubmit: SubmitHandler> = async (values) => {
if (!project) return console.error('Project is required')
const { tableId, ...payload } = values
@@ -151,7 +163,16 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
if (open && isSuccess) {
form.clearErrors()
- if (isEditing) {
+ if (isDuplicatingTrigger && selectedTrigger) {
+ const initalSelectedTable = tables.find((t) => t.name === selectedTrigger.table)
+
+ form.reset({
+ ...selectedTrigger,
+ tableId: initalSelectedTable?.id.toString(),
+ table: initalSelectedTable?.name,
+ schema: initalSelectedTable?.schema,
+ })
+ } else if (isEditing) {
form.reset(selectedTrigger)
} else if (tables.length > 0) {
form.reset({
@@ -167,13 +188,15 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
return (
<>
-
+
- {isEditing
- ? `Edit database trigger: ${selectedTrigger.name}`
- : 'Create a new database trigger'}
+ {isDuplicatingTrigger
+ ? 'Duplicate trigger'
+ : isEditing
+ ? `Edit database trigger: ${selectedTrigger.name}`
+ : 'Create a new database trigger'}
@@ -250,10 +273,12 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
{
+ // mark table ID as dirty to trigger validation
+ field.onChange(val)
const table = tables.find((x) => x.id.toString() === val)
if (table) {
- form.setValue('table', table.name)
- form.setValue('schema', table.schema)
+ form.setValue('table', table.name, { shouldDirty: true })
+ form.setValue('schema', table.schema, { shouldDirty: true })
}
}}
>
@@ -446,7 +471,7 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
type="default"
htmlType="reset"
disabled={isCreating || isUpdating}
- onClick={() => setOpen(false)}
+ onClick={onClose}
>
Cancel
@@ -454,6 +479,22 @@ export const TriggerSheet = ({ selectedTrigger, open, setOpen }: TriggerSheetPro
{isEditing ? 'Save' : 'Create'} trigger
+
+ setIsClosingPanel(false)}
+ onConfirm={() => {
+ setIsClosingPanel(false)
+ onClose()
+ }}
+ >
+
+ There are unsaved changes. Are you sure you want to close the panel? Your changes will
+ be lost.
+
+
diff --git a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx
index 9e0862014fea8..2690209c90437 100644
--- a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx
+++ b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx
@@ -1,6 +1,6 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { includes, sortBy } from 'lodash'
-import { Check, Edit, Edit2, MoreVertical, Trash, X } from 'lucide-react'
+import { Check, Copy, Edit, Edit2, MoreVertical, Trash, X } from 'lucide-react'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useDatabaseTriggersQuery } from 'data/database-triggers/database-triggers-query'
@@ -13,6 +13,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
+ DropdownMenuSeparator,
DropdownMenuTrigger,
TableCell,
TableRow,
@@ -21,13 +22,15 @@ import {
TooltipTrigger,
} from 'ui'
import { generateTriggerCreateSQL } from './TriggerList.utils'
+import { PostgresTrigger } from '@supabase/postgres-meta'
interface TriggerListProps {
schema: string
filterString: string
isLocked: boolean
- editTrigger: (trigger: any) => void
- deleteTrigger: (trigger: any) => void
+ editTrigger: (trigger: PostgresTrigger) => void
+ duplicateTrigger: (trigger: PostgresTrigger) => void
+ deleteTrigger: (trigger: PostgresTrigger) => void
}
const TriggerList = ({
@@ -35,6 +38,7 @@ const TriggerList = ({
filterString,
isLocked,
editTrigger,
+ duplicateTrigger,
deleteTrigger,
}: TriggerListProps) => {
const { data: project } = useSelectedProjectQuery()
@@ -197,6 +201,11 @@ const TriggerList = ({
Edit with Assistant
+ duplicateTrigger(x)}>
+
+ Duplicate trigger
+
+
deleteTrigger(x)}>
Delete trigger
diff --git a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx
index 30b651ba93887..1f5d10fe750a9 100644
--- a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx
+++ b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx
@@ -37,12 +37,14 @@ import Link from 'next/link'
interface TriggersListProps {
createTrigger: () => void
editTrigger: (trigger: PostgresTrigger) => void
+ duplicateTrigger: (trigger: PostgresTrigger) => void
deleteTrigger: (trigger: PostgresTrigger) => void
}
const TriggersList = ({
createTrigger = noop,
editTrigger = noop,
+ duplicateTrigger = noop,
deleteTrigger = noop,
}: TriggersListProps) => {
const { data: project } = useSelectedProjectQuery()
@@ -232,6 +234,7 @@ const TriggersList = ({
filterString={filterString}
isLocked={isSchemaLocked}
editTrigger={editTrigger}
+ duplicateTrigger={duplicateTrigger}
deleteTrigger={deleteTrigger}
/>
diff --git a/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx b/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx
index deaf56126fdb4..2e2678050f44b 100644
--- a/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx
+++ b/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx
@@ -4,8 +4,8 @@ import { useEffect, useState } from 'react'
import { Markdown } from 'components/interfaces/Markdown'
import { onErrorChat } from 'components/ui/AIAssistantPanel/AIAssistant.utils'
-import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { BASE_PATH } from 'lib/constants'
+import { useTrack } from 'lib/telemetry/track'
import { AiIconAnimation, Button, Label_Shadcn_, Textarea } from 'ui'
interface SupabaseService {
@@ -34,7 +34,7 @@ export const SchemaGenerator = ({
const [hasSql, setHasSql] = useState(false)
const [promptIntendSent, setPromptIntendSent] = useState(false)
- const { mutate: sendEvent } = useSendEventMutation()
+ const track = useTrack()
const { messages, setMessages, sendMessage, status, addToolResult } = useChat({
id: 'schema-generator',
@@ -193,18 +193,12 @@ export const SchemaGenerator = ({
const isNewPrompt = messages.length == 0
// distinguish between initial step or second step
if (step === 'initial') {
- sendEvent({
- action: 'project_creation_initial_step_prompt_intended',
- properties: {
- isNewPrompt,
- },
+ track('project_creation_initial_step_prompt_intended', {
+ isNewPrompt,
})
} else {
- sendEvent({
- action: 'project_creation_second_step_prompt_intended',
- properties: {
- isNewPrompt,
- },
+ track('project_creation_second_step_prompt_intended', {
+ isNewPrompt,
})
}
setPromptIntendSent(true)
diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx
index c5e7247aa4abe..41290142bad4e 100644
--- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx
+++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx
@@ -1,6 +1,6 @@
import type { PostgresTable } from '@supabase/postgres-meta'
import { isEmpty, isUndefined, noop } from 'lodash'
-import { useEffect, useState } from 'react'
+import { useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { DocsButton } from 'components/ui/DocsButton'
@@ -147,8 +147,12 @@ export const TableEditor = ({
connectionString: project?.connectionString,
schema: table?.schema,
})
- const foreignKeys = (foreignKeyMeta ?? []).filter(
- (fk) => fk.source_schema === table?.schema && fk.source_table === table?.name
+ const foreignKeys = useMemo(
+ () =>
+ (foreignKeyMeta ?? []).filter(
+ (fk) => fk.source_schema === table?.schema && fk.source_table === table?.name
+ ),
+ [foreignKeyMeta, table]
)
const onUpdateField = (changes: Partial) => {
@@ -245,7 +249,7 @@ export const TableEditor = ({
} else {
const tableFields = generateTableFieldFromPostgresTable(
table,
- foreignKeyMeta || [],
+ foreignKeyMeta ?? [],
isDuplicating,
isRealtimeEnabled
)
diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts
index 8fbe022d0ee5e..331b163d31ea8 100644
--- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts
+++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/types.ts
@@ -37,6 +37,7 @@ export enum QuickstartVariant {
CONTROL = 'control',
AI = 'ai',
TEMPLATES = 'templates',
+ ASSISTANT = 'assistant',
}
export type TableSuggestion = {
diff --git a/apps/studio/components/layouts/Tabs/NewTab.tsx b/apps/studio/components/layouts/Tabs/NewTab.tsx
index aea59d0500ae9..1eb98dc29ebf6 100644
--- a/apps/studio/components/layouts/Tabs/NewTab.tsx
+++ b/apps/studio/components/layouts/Tabs/NewTab.tsx
@@ -4,7 +4,7 @@ import { partition } from 'lodash'
import { Table2 } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
-import { useMemo } from 'react'
+import { useMemo, useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
@@ -20,10 +20,12 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { usePHFlag } from 'hooks/ui/useFlag'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
+import { useAiAssistantStateSnapshot, AssistantMessageType } from 'state/ai-assistant-state'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import {
+ AiIconAnimation,
Button,
cn,
SQL_ICON,
@@ -39,6 +41,12 @@ import { RecentItems } from './RecentItems'
const NEW_PROJECT_THRESHOLD_DAYS = 7
const TABLE_QUICKSTART_FLAG = 'tableQuickstart'
+const ASSISTANT_QUICKSTART_MESSAGES = {
+ user: 'Help me create a new database table for my project',
+ assistant:
+ "I'll help you create a database table. Please tell me:\n\n1. What does your application do?\n2. What kind of data do you want to store?\n\nI'll suggest a table structure that fits your requirements and help you create it directly in your database.",
+}
+
export function NewTab() {
const router = useRouter()
const { ref } = useParams()
@@ -50,7 +58,9 @@ export function NewTab() {
const snap = useTableEditorStateSnapshot()
const snapV2 = useSqlEditorV2StateSnapshot()
const tabs = useTabsStateSnapshot()
+ const aiSnap = useAiAssistantStateSnapshot()
+ const [isCreatingChat, setIsCreatingChat] = useState(false)
const [templates] = partition(SQL_TEMPLATES, { type: 'template' })
const [quickstarts] = partition(SQL_TEMPLATES, { type: 'quickstart' })
@@ -87,6 +97,43 @@ export function NewTab() {
? tableQuickstartVariant
: null
+ const handleOpenAssistant = () => {
+ if (isCreatingChat) return
+
+ setIsCreatingChat(true)
+
+ try {
+ const chatId = aiSnap.newChat({
+ name: 'Create a database table',
+ open: true,
+ })
+
+ if (!chatId) {
+ throw new Error('Failed to create chat')
+ }
+
+ const userMessage: AssistantMessageType = {
+ id: uuidv4(),
+ role: 'user',
+ parts: [{ type: 'text', text: ASSISTANT_QUICKSTART_MESSAGES.user }],
+ }
+
+ const assistantMessage: AssistantMessageType = {
+ id: uuidv4(),
+ role: 'assistant',
+ parts: [{ type: 'text', text: ASSISTANT_QUICKSTART_MESSAGES.assistant }],
+ }
+
+ aiSnap.saveMessage([userMessage, assistantMessage])
+ } catch (error) {
+ console.error('Failed to open AI assistant:', error)
+ const message = error instanceof Error ? error.message : 'Unknown error'
+ toast.error(`Unable to open AI assistant: ${message}`)
+ } finally {
+ setIsCreatingChat(false)
+ }
+ }
+
const tableEditorActions = [
{
icon: ,
@@ -153,6 +200,15 @@ export function NewTab() {
{actions.map((item, i) => (
))}
+ {activeQuickstartVariant === QuickstartVariant.ASSISTANT && (
+ }
+ title="Create with Assistant"
+ description="Use AI to design your database table"
+ bgColor="bg-brand-200"
+ onClick={handleOpenAssistant}
+ />
+ )}
{activeQuickstartVariant === QuickstartVariant.AI && (
snap.onAddTable(tableData)} />
diff --git a/apps/studio/lib/telemetry/track.ts b/apps/studio/lib/telemetry/track.ts
new file mode 100644
index 0000000000000..ec1755bc9a304
--- /dev/null
+++ b/apps/studio/lib/telemetry/track.ts
@@ -0,0 +1,61 @@
+import { sendTelemetryEvent } from 'common'
+import { TelemetryEvent, TelemetryGroups } from 'common/telemetry-constants'
+import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
+import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { API_URL } from 'lib/constants'
+import { useRouter } from 'next/router'
+import { useCallback } from 'react'
+
+type EventMap = {
+ [E in TelemetryEvent as E['action']]: E
+}
+
+type PropertiesForAction = EventMap[A] extends { properties: infer P }
+ ? P
+ : never
+
+type HasProperties = EventMap[A] extends { properties: any }
+ ? true
+ : false
+
+/**
+ * Hook for type-safe telemetry event tracking with automatic project/org context injection.
+ *
+ * @example
+ * const track = useTrack()
+ * track('table_created', { method: 'sql_editor', schema_name: 'public' })
+ * track('help_button_clicked')
+ */
+export const useTrack = () => {
+ const { data: project } = useSelectedProjectQuery()
+ const { data: org } = useSelectedOrganizationQuery()
+ const router = useRouter()
+
+ const track = useCallback(
+ (
+ action: A,
+ ...args: HasProperties extends true
+ ? [properties: PropertiesForAction, groupOverrides?: Partial]
+ : [properties?: undefined, groupOverrides?: Partial]
+ ) => {
+ const [properties, groupOverrides] = args
+
+ const groups = {
+ ...(project?.ref && { project: project.ref }),
+ ...(org?.slug && { organization: org.slug }),
+ ...groupOverrides,
+ }
+
+ const event = {
+ action,
+ ...(properties && { properties }),
+ ...(groups && { groups }),
+ } as EventMap[A]
+
+ sendTelemetryEvent(API_URL, event, router.pathname)
+ },
+ [project?.ref, org?.slug, router.pathname]
+ )
+
+ return track
+}
diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx
index d0448411dbdfb..5d1782ce87b58 100644
--- a/apps/studio/pages/new/[slug].tsx
+++ b/apps/studio/pages/new/[slug].tsx
@@ -43,7 +43,7 @@ import {
ProjectCreateVariables,
useProjectCreateMutation,
} from 'data/projects/project-create-mutation'
-import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
+import { useTrack } from 'lib/telemetry/track'
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
@@ -171,7 +171,7 @@ const Wizard: NextPageWithLayout = () => {
const [isComputeCostsConfirmationModalVisible, setIsComputeCostsConfirmationModalVisible] =
useState(false)
- const { mutate: sendEvent } = useSendEventMutation()
+ const track = useTrack()
FormSchema.superRefine(({ dbPassStrength }, refinementContext) => {
if (dbPassStrength < DEFAULT_MINIMUM_PASSWORD_STRENGTH) {
@@ -247,16 +247,16 @@ const Wizard: NextPageWithLayout = () => {
isSuccess: isSuccessNewProject,
} = useProjectCreateMutation({
onSuccess: (res) => {
- sendEvent({
- action: 'project_creation_simple_version_submitted',
- properties: {
+ track(
+ 'project_creation_simple_version_submitted',
+ {
instanceSize: form.getValues('instanceSize'),
},
- groups: {
+ {
project: res.ref,
organization: res.organization_slug,
- },
- })
+ }
+ )
router.push(isHomeNew ? `/project/${res.ref}` : `/project/${res.ref}/building`)
},
})
@@ -372,14 +372,8 @@ const Wizard: NextPageWithLayout = () => {
!sizesWithNoCostConfirmationRequired.includes(values.instanceSize as DesiredInstanceSize)
if (additionalMonthlySpend > 0 && (hasOAuthApps || launchingLargerInstance)) {
- sendEvent({
- action: 'project_creation_simple_version_confirm_modal_opened',
- properties: {
- instanceSize: values.instanceSize,
- },
- groups: {
- organization: currentOrg?.slug ?? 'Unknown',
- },
+ track('project_creation_simple_version_confirm_modal_opened', {
+ instanceSize: values.instanceSize,
})
setIsComputeCostsConfirmationModalVisible(true)
} else {
diff --git a/apps/studio/pages/project/[ref]/database/triggers.tsx b/apps/studio/pages/project/[ref]/database/triggers.tsx
index f08ad5a0f32ea..6830818d9d9e8 100644
--- a/apps/studio/pages/project/[ref]/database/triggers.tsx
+++ b/apps/studio/pages/project/[ref]/database/triggers.tsx
@@ -21,6 +21,8 @@ const TriggersPage: NextPageWithLayout = () => {
const isInlineEditorEnabled = useIsInlineEditorEnabled()
const [selectedTrigger, setSelectedTrigger] = useState()
+ const [isDuplicatingTrigger, setIsDuplicatingTrigger] = useState(false)
+
const [showCreateTriggerForm, setShowCreateTriggerForm] = useState(false)
const [showDeleteTriggerForm, setShowDeleteTriggerForm] = useState(false)
@@ -53,11 +55,34 @@ const TriggersPage: NextPageWithLayout = () => {
}
}
+ const duplicateTrigger = (trigger: PostgresTrigger) => {
+ setIsDuplicatingTrigger(true)
+
+ const dupTrigger = {
+ ...trigger,
+ name: `${trigger.name}_duplicate`,
+ }
+
+ if (isInlineEditorEnabled) {
+ setSelectedTriggerForEditor(dupTrigger)
+ setEditorPanelOpen(true)
+ } else {
+ setSelectedTrigger(dupTrigger)
+ setShowCreateTriggerForm(true)
+ }
+ }
+
const deleteTrigger = (trigger: PostgresTrigger) => {
setSelectedTrigger(trigger)
setShowDeleteTriggerForm(true)
}
+ const resetEditorPanel = () => {
+ setIsDuplicatingTrigger(false)
+ setEditorPanelOpen(false)
+ setSelectedTriggerForEditor(undefined)
+ }
+
if (isPermissionsLoaded && !canReadTriggers) {
return
}
@@ -75,6 +100,7 @@ const TriggersPage: NextPageWithLayout = () => {
@@ -83,7 +109,11 @@ const TriggersPage: NextPageWithLayout = () => {
{
+ setIsDuplicatingTrigger(false)
+ setShowCreateTriggerForm(false)
+ }}
+ isDuplicatingTrigger={isDuplicatingTrigger}
/>
{
{
- setEditorPanelOpen(false)
- setSelectedTriggerForEditor(undefined)
- }}
- onClose={() => {
- setEditorPanelOpen(false)
- setSelectedTriggerForEditor(undefined)
- }}
+ onRunSuccess={resetEditorPanel}
+ onClose={resetEditorPanel}
initialValue={
selectedTriggerForEditor
? generateTriggerCreateSQL(selectedTriggerForEditor)
@@ -111,12 +135,16 @@ execute function function_name();`
}
label={
selectedTriggerForEditor
- ? `Edit trigger "${selectedTriggerForEditor.name}"`
+ ? isDuplicatingTrigger
+ ? `Duplicate trigger "${selectedTriggerForEditor.name}"`
+ : `Edit trigger "${selectedTriggerForEditor.name}"`
: 'Create new database trigger'
}
initialPrompt={
selectedTriggerForEditor
- ? `Update the database trigger "${selectedTriggerForEditor.name}" to...`
+ ? isDuplicatingTrigger
+ ? `Duplicate the database trigger "${selectedTriggerForEditor.name}" to...`
+ : `Update the database trigger "${selectedTriggerForEditor.name}" to...`
: 'Create a new database trigger that...'
}
/>
diff --git a/apps/studio/state/storage-explorer.tsx b/apps/studio/state/storage-explorer.tsx
index fbeb9a547bee8..751080cd58c23 100644
--- a/apps/studio/state/storage-explorer.tsx
+++ b/apps/studio/state/storage-explorer.tsx
@@ -1198,6 +1198,9 @@ function createStorageExplorerState({
try {
const data = await getTemporaryAPIKey({ projectRef: state.projectRef })
req.setHeader('apikey', data.api_key)
+ if (!IS_PLATFORM) {
+ req.setHeader('Authorization', `Bearer ${data.api_key}`)
+ }
} catch (error) {
throw error
}
diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts
index 0d370b71ea45d..300f25a189e78 100644
--- a/packages/common/telemetry-constants.ts
+++ b/packages/common/telemetry-constants.ts
@@ -9,7 +9,7 @@
* @module telemetry-frontend
*/
-type TelemetryGroups = {
+export type TelemetryGroups = {
project: string
organization: string
}