Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/docs/content/guides/database/postgres/triggers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ You can delete a trigger using the `drop trigger` command:
drop trigger "trigger_name" on "table_name";
```

If your trigger is inside a restricted schema, you won't be able to drop it due to permission restrictions. In those cases, you can drop the function it depends on instead using the CASCADE clause to automatically remove all triggers that call it:

```sql
drop function if exists restricted_schema.function_name() cascade;
```

Make sure you take a backup of the function before removing it in case you're planning to recreate it later.

## Resources

- Official Postgres Docs: [Triggers](https://www.postgresql.org/docs/current/triggers.html)
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/content/guides/integrations/vercel-marketplace.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ Note: Supabase Organization billing cycle is separate from Vercel's. Plan change

When using Vercel Marketplace, the following limitations apply:

- Projects can only be created or removed via the Vercel dashboard.
- Projects can only be created via the Vercel dashboard.
- Organizations cannot be removed manually; they are removed only if you uninstall the Vercel Marketplace Integration.
- Owners cannot be added manually within the Supabase dashboard.
- Invoices and payments must be managed through the Vercel dashboard, not the Supabase dashboard.
- [Custom Domains](/docs/guides/platform/custom-domains) are not supported, and we always use the base `SUPABASE_URL` for the Vercel environment variables.
2 changes: 1 addition & 1 deletion apps/docs/content/guides/platform/backups.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Database backups can be categorized into two types: **logical** and **physical**
To enable physical backups, you have three options:

- Enable [Point-in-Time Recovery (PITR)](#point-in-time-recovery)
- [Increase your disk size](/docs/guides/platform/database-size) to greater than 15GB
- [Increase your database size](/docs/guides/platform/database-size) to greater than 15GB
- [Create a read replica](/docs/guides/platform/read-replicas)

Once a project satisfies at least one of the requirements for physical backups then logical backups are no longer made. However, your project may revert back to logical backups if you remove add-ons.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const getDateRanges = () => {
}

export const AUTH_COMBINED_QUERY = () => `
-- auth-overview
with base as (
select
json_value(event_message, "$.auth_event.action") as action,
Expand Down
27 changes: 25 additions & 2 deletions apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from './OverviewUsage.constants'
import { useQuery } from '@tanstack/react-query'
import dayjs from 'dayjs'
import AlertError from 'components/ui/AlertError'

export const StatCard = ({
title,
Expand Down Expand Up @@ -95,20 +96,33 @@ export const StatCard = ({
export const OverviewUsage = () => {
const { ref } = useParams()

const { data: currentData, isLoading: currentLoading } = useQuery({
const {
data: currentData,
isLoading: currentLoading,
error: currentError,
} = useQuery({
queryKey: ['auth-metrics', ref, 'current'],
queryFn: () => fetchAllAuthMetrics(ref as string, 'current'),
enabled: !!ref,
})

const { data: previousData, isLoading: previousLoading } = useQuery({
const {
data: previousData,
isLoading: previousLoading,
error: previousError,
} = useQuery({
queryKey: ['auth-metrics', ref, 'previous'],
queryFn: () => fetchAllAuthMetrics(ref as string, 'previous'),
enabled: !!ref,
})

const metrics = processAllAuthMetrics(currentData?.result || [], previousData?.result || [])
const isLoading = currentLoading || previousLoading
const isError = !!previousError || !!currentError
const errorMessage =
(previousError as any)?.message ||
(currentError as any)?.message ||
'There was an error fetching the auth metrics.'

const activeUsersChange = calculatePercentageChange(
metrics.current.activeUsers,
Expand All @@ -124,6 +138,15 @@ export const OverviewUsage = () => {

return (
<ScaffoldSection isFullWidth>
{isError && (
<AlertError
className="mb-4"
subject="Error fetching auth metrics"
error={{
message: errorMessage,
}}
/>
)}
<div className="flex items-center justify-between mb-4">
<ScaffoldSectionTitle>Usage</ScaffoldSectionTitle>
<Link
Expand Down
12 changes: 10 additions & 2 deletions apps/studio/components/interfaces/HomeNew/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dayjs from 'dayjs'
import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
import { useEffect, useRef } from 'react'
Expand Down Expand Up @@ -26,6 +27,8 @@ export const HomeV2 = () => {
const { data: organization } = useSelectedOrganizationQuery()
const { mutate: sendEvent } = useSendEventMutation()

const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day'))

const hasShownEnableBranchingModalRef = useRef(false)
const isPaused = project?.status === PROJECT_STATUS.INACTIVE

Expand Down Expand Up @@ -103,8 +106,13 @@ export const HomeV2 = () => {
</SortableSection>
)
}
if (id === 'getting-started') {
return gettingStartedState === 'hidden' ? null : (
if (
id === 'getting-started' &&
!isMatureProject &&
project &&
gettingStartedState !== 'hidden'
) {
return (
<SortableSection key={id} id={id}>
<GettingStartedSection
value={gettingStartedState}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Image from 'next/image'
import { PropsWithChildren, ReactNode } from 'react'

import { UserDropdown } from 'components/interfaces/UserDropdown'
import { FeedbackDropdown } from 'components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown'
import { FeedbackDropdown } from 'components/layouts/ProjectLayout/LayoutHeader/FeedbackDropdown/FeedbackDropdown'
import { BASE_PATH } from 'lib/constants'
import { cn, Separator } from 'ui'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const CustomDomainVerify = () => {
<Link
target="_blank"
rel="noreferrer"
href={`https://whatsmydns.net/#TXT/${customDomain?.hostname}`}
href={`https://whatsmydns.net/#TXT/${customDomain?.ssl.txt_name}`}
className="text-brand"
>
here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,7 @@ export const ReplicaNode = ({ data }: NodeProps<ReplicaNodeData>) => {
<Badge>Restarting</Badge>
) : status === REPLICA_STATUS.RESIZING ? (
<Badge>Resizing</Badge>
) : initStatus === ReplicaInitializationStatus.Completed &&
status === REPLICA_STATUS.ACTIVE_HEALTHY ? (
) : status === REPLICA_STATUS.ACTIVE_HEALTHY ? (
<Badge variant="brand">Healthy</Badge>
) : (
<Badge variant="warning">Unhealthy</Badge>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import { createSupportStorageClient } from './support-storage-client'
const MAX_ATTACHMENTS = 5

const uploadAttachments = async ({ userId, files }: { userId: string; files: File[] }) => {
if (files.length === 0) return []

const supportSupabaseClient = createSupportStorageClient()

const filesToUpload = Array.from(files)
Expand Down Expand Up @@ -104,11 +102,13 @@ export function useAttachmentUpload() {
return []
}

if (uploadedFiles.length === 0) return

const filenames = await uploadAttachments({ userId: profile.gotrue_id, files: uploadedFiles })
const urls = await generateAttachmentURLs({ filenames })
const urls = await generateAttachmentURLs({ bucket: 'support-attachments', filenames })
return urls
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [uploadedFiles])
}, [profile, uploadedFiles])

return useMemo(
() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export const NO_ORG_MARKER = 'no-org'

export const formatMessage = ({
message,
attachments,
attachments = [],
error,
commit,
}: {
message: string
attachments: string[]
attachments?: string[]
error: string | null | undefined
commit: string | undefined
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { useState } from 'react'
import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_ } from 'ui'
import { FeedbackWidget } from './FeedbackWidget'

const FeedbackDropdown = ({ className }: { className?: string }) => {
export const FeedbackDropdown = ({ className }: { className?: string }) => {
const [isOpen, setIsOpen] = useState(false)
const [feedback, setFeedback] = useState('')
const [screenshot, setScreenshot] = useState<string>()
const [stage, setStage] = useState<'select' | 'widget'>('select')

return (
Expand All @@ -17,7 +15,6 @@ const FeedbackDropdown = ({ className }: { className?: string }) => {
open={isOpen}
onOpenChange={(e) => {
setIsOpen(e)
if (!e) setScreenshot(undefined)
if (!e) setStage('select')
}}
>
Expand Down Expand Up @@ -63,15 +60,7 @@ const FeedbackDropdown = ({ className }: { className?: string }) => {
</div>
</div>
)}
{stage === 'widget' && (
<FeedbackWidget
onClose={() => setIsOpen(false)}
feedback={feedback}
setFeedback={setFeedback}
screenshot={screenshot}
setScreenshot={setScreenshot}
/>
)}
{stage === 'widget' && <FeedbackWidget onClose={() => setIsOpen(false)} />}
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { createClient } from '@supabase/supabase-js'
import { createSupportStorageClient } from 'components/interfaces/Support/support-storage-client'
import { generateAttachmentURLs } from 'data/support/generate-attachment-urls-mutation'
import { uuidv4 } from 'lib/helpers'

const SUPPORT_API_URL = process.env.NEXT_PUBLIC_SUPPORT_API_URL || ''
const SUPPORT_API_KEY = process.env.NEXT_PUBLIC_SUPPORT_ANON_KEY || ''

export const convertB64toBlob = (image: string) => {
const contentType = 'image/png'
const byteCharacters = atob(image.substr(`data:${contentType};base64,`.length))
Expand All @@ -25,40 +23,37 @@ export const convertB64toBlob = (image: string) => {
return blob
}

export const uploadAttachment = async (ref: string, image: string) => {
const supabaseClient = createClient(SUPPORT_API_URL, SUPPORT_API_KEY, {
auth: {
persistSession: false,
autoRefreshToken: false,
// @ts-ignore
multiTab: false,
detectSessionInUrl: false,
localStorage: {
getItem: (key: string) => undefined,
setItem: (key: string, value: string) => {},
removeItem: (key: string) => {},
},
},
})
type UploadAttachmentArgs = {
image: string
userId?: string
}

export const uploadAttachment = async ({ image, userId }: UploadAttachmentArgs) => {
if (!userId) {
console.error(
'[FeedbackWidget > uploadAttachment] Unable to upload screenshot, missing user ID'
)
return undefined
}

const supabaseClient = createSupportStorageClient()

const blob = convertB64toBlob(image)
const name = `${ref || 'no-project'}/${uuidv4()}.png`
const filename = `${userId}/${uuidv4()}.png`
const options = { cacheControl: '3600' }

const { data: file, error: uploadError } = await supabaseClient.storage
.from('feedback-attachments')
.upload(name, blob, options)
.upload(filename, blob, options)

if (uploadError) {
console.error('Failed to upload:', uploadError)
if (uploadError || !file) {
console.error('Failed to upload screenshot attachment:', uploadError)
return undefined
}

if (file) {
const { data } = await supabaseClient.storage
.from('feedback-attachments')
.createSignedUrls([file.path], 10 * 365 * 24 * 60 * 60)
return data?.[0].signedUrl
}

return undefined
const signedUrls = await generateAttachmentURLs({
bucket: 'feedback-attachments',
filenames: [file.path],
})
return signedUrls[0]
}
Loading
Loading