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 }