Pricing
@@ -159,45 +160,13 @@ export default function IndexPage() {
Start building for free, collaborate with a team, then scale to millions of users.
-
diff --git a/packages/shared-data/plans.ts b/packages/shared-data/plans.ts
index 5e40e4b5c51..d13c90cb463 100644
--- a/packages/shared-data/plans.ts
+++ b/packages/shared-data/plans.ts
@@ -94,7 +94,7 @@ export const plans: PricingInformation[] = [
'Daily backups stored for 14 days',
'Standardised Security Questionnaire',
'SOC2',
- 'HIPAA',
+ 'HIPAA available as paid add-on',
'SSO for Supabase Dashboard',
'Priority email support & SLAs',
'28-day log retention',
diff --git a/packages/shared-data/pricing.ts b/packages/shared-data/pricing.ts
index b955a8cfe2f..3bd10372288 100644
--- a/packages/shared-data/pricing.ts
+++ b/packages/shared-data/pricing.ts
@@ -469,8 +469,8 @@ export const pricing: Pricing = {
plans: {
free: false,
pro: false,
- team: true,
- enterprise: true,
+ team: 'Available as paid add-on',
+ enterprise: 'Available as paid add-on',
},
tooltips: {
main: 'Available as a paid add-on on Team plan and above.',
diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts
index 71b5f65324f..f4c4f69fc39 100644
--- a/studio/components/interfaces/Reports/Reports.constants.ts
+++ b/studio/components/interfaces/Reports/Reports.constants.ts
@@ -228,6 +228,52 @@ export const PRESET_CONFIG: Record
= {
title: '',
queries: {},
},
+ [Presets.STORAGE]: {
+ title: 'Storage',
+ queries: {
+ cacheHitRate: {
+ queryType: 'logs',
+ // storage report does not perform any filtering
+ sql: (_filters) => `
+-- cache-hit-rate
+SELECT
+ timestamp_trunc(timestamp, hour) as timestamp,
+ countif( h.cf_cache_status in ('HIT', 'STALE', 'REVALIDATED', 'UPDATING') ) as hit_count,
+ countif( h.cf_cache_status in ('MISS', 'NONE/UNKNOWN', 'EXPIRED', 'BYPASS', 'DYNAMIC') ) as miss_count
+from edge_logs f
+ cross join unnest(f.metadata) as m
+ cross join unnest(m.request) as r
+ cross join unnest(m.response) as res
+ cross join unnest(res.headers) as h
+where starts_with(r.path, '/storage/v1/object') and r.method = 'GET'
+group by timestamp
+order by timestamp desc
+`,
+ },
+ topCacheMisses: {
+ queryType: 'logs',
+ // storage report does not perform any filtering
+ sql: (_filters) => `
+-- top-cache-misses
+SELECT
+ r.path as path,
+ r.search as search,
+ count(id) as count
+from edge_logs f
+ cross join unnest(f.metadata) as m
+ cross join unnest(m.request) as r
+ cross join unnest(m.response) as res
+ cross join unnest(res.headers) as h
+where starts_with(r.path, '/storage/v1/object')
+ and r.method = 'GET'
+ and h.cf_cache_status in ('MISS', 'NONE/UNKNOWN', 'EXPIRED', 'BYPASS', 'DYNAMIC')
+group by path, search
+order by count desc
+limit 12
+ `,
+ },
+ },
+ },
[Presets.QUERY_PERFORMANCE]: {
title: 'Query performance',
queries: {
diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts
index a13240a5125..900a7c0c437 100644
--- a/studio/components/interfaces/Reports/Reports.types.ts
+++ b/studio/components/interfaces/Reports/Reports.types.ts
@@ -3,6 +3,7 @@ import { DEFAULT_QUERY_PARAMS } from './Reports.constants'
export enum Presets {
API = 'api',
+ STORAGE = 'storage',
AUTH = 'auth',
QUERY_PERFORMANCE = 'query_performance',
}
diff --git a/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx
new file mode 100644
index 00000000000..cdff467d303
--- /dev/null
+++ b/studio/components/interfaces/Reports/renderers/StorageRenderers.tsx
@@ -0,0 +1,86 @@
+import { TextFormatter } from 'components/interfaces/Settings/Logs/LogsFormatters'
+import Table from 'components/to-be-cleaned/Table'
+import StackedBarChart from 'components/ui/Charts/StackedBarChart'
+import { Fragment } from 'react'
+import { ReportWidgetProps, ReportWidgetRendererProps } from '../ReportWidget'
+
+export const CacheHitRateChartRenderer = (
+ props: ReportWidgetProps<{
+ timestamp: string
+ hit_count: number
+ miss_count: number
+ }>
+) => {
+ const stackedData = props.data.flatMap((datum) => [
+ {
+ timestamp: datum.timestamp,
+ count: datum.hit_count,
+ type: 'hit',
+ },
+ {
+ timestamp: datum.timestamp,
+ count: datum.miss_count,
+ type: 'miss',
+ },
+ ])
+
+ return (
+
+ )
+}
+
+export const TopCacheMissesRenderer = (
+ props: ReportWidgetRendererProps<{
+ path: string
+ search: string
+ count: number
+ }>
+) => {
+ if (props.data.length === 0) return null
+ const headerClasses = '!text-xs !py-2 p-0 font-bold !bg-scale-400'
+ const cellClasses = '!text-xs !py-2'
+
+ return (
+ <>
+ Top Cache Misses
+
+ Request
+ Count
+ >
+ }
+ body={
+ <>
+ {props.data.map((datum) => (
+
+
+
+
+
+
+
+
+
+ {datum.count}
+
+
+
+ ))}
+ >
+ }
+ />
+ >
+ )
+}
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
index 9a97b7eba14..477252ef4eb 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
@@ -2,7 +2,17 @@ import type { PostgresColumn, PostgresSchema, PostgresTable } from '@supabase/po
import { Dictionary } from 'components/grid'
import { find, get, isEmpty, sortBy } from 'lodash'
import { useEffect, useState } from 'react'
-import { IconDatabase, IconHelpCircle, Input, Listbox, SidePanel } from 'ui'
+import {
+ AlertDescription_Shadcn_,
+ AlertTitle_Shadcn_,
+ Alert_Shadcn_,
+ IconAlertTriangle,
+ IconDatabase,
+ IconHelpCircle,
+ Input,
+ Listbox,
+ SidePanel,
+} from 'ui'
import InformationBox from 'components/ui/InformationBox'
import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants'
@@ -153,6 +163,8 @@ const ForeignKeySelector = ({
resolve()
}
+ const matchingColumnTypes = selectedColumn?.format === column?.format
+
return (
)}
+ {!matchingColumnTypes && (
+
+
+ The column types don't match
+
+ The referenced column
+ {column?.name && {column.name}}
+ {column?.format ? (
+ <>
+ is of type
+ {column.format}
+ >
+ ) : (
+ has no type
+ )}
+ while the selected foreign column
+
+ {selectedTable?.name}.{selectedColumn?.name}
+
+ has
+ {selectedColumn?.data_type}type. These two
+ columns can't be referenced until they are of the same type.
+
+
+ )}
}
diff --git a/studio/components/layouts/ReportsLayout/ReportsLayout.tsx b/studio/components/layouts/ReportsLayout/ReportsLayout.tsx
index f1517357776..0bdd0813a26 100644
--- a/studio/components/layouts/ReportsLayout/ReportsLayout.tsx
+++ b/studio/components/layouts/ReportsLayout/ReportsLayout.tsx
@@ -4,7 +4,7 @@ import { PropsWithChildren } from 'react'
import ProductMenu from 'components/ui/ProductMenu'
import { useSelectedProject, withAuth } from 'hooks'
import ProjectLayout from '../'
-import { generateReportsMenu } from './ReportsMenu.utils'
+import { useFlag } from 'hooks'
interface ReportsLayoutProps {
title?: string
@@ -15,12 +15,61 @@ const ReportsLayout = ({ title, children }: PropsWithChildren}
+ productMenu={}
>
{children}
diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts
deleted file mode 100644
index 4e088fd0ae6..00000000000
--- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Project } from 'types'
-import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types'
-
-export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => {
- const ref = project?.ref ?? 'default'
-
- return [
- {
- title: '',
- items: [
- {
- name: 'Custom reports',
- key: '',
- url: `/project/${ref}/reports`,
- items: [],
- },
- ],
- },
- {
- items: [
- {
- name: 'API',
- key: 'api-overview',
- url: `/project/${ref}/reports/api-overview`,
- items: [],
- label: 'NEW',
- },
- {
- name: 'Database',
- key: 'database',
- url: `/project/${ref}/reports/database`,
- items: [],
- },
- {
- name: 'Query Performance',
- key: 'query-performance',
- url: `/project/${ref}/reports/query-performance`,
- items: [],
- label: 'NEW',
- },
- ],
- },
- ]
-}
diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx
index d20d137cdbc..82697ff1ce1 100644
--- a/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx
+++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/CustomExpiryModal.tsx
@@ -1,11 +1,21 @@
-import { Form, Modal, Input, Button } from 'ui'
+import dayjs from 'dayjs'
import { observer } from 'mobx-react-lite'
+import { Button, Form, Input, Listbox, Modal } from 'ui'
+
+import { DATETIME_FORMAT } from 'lib/constants'
import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore'
export interface CustomExpiryModalProps {
onCopyUrl: (name: string, url: string) => void
}
+const unitMap = {
+ days: 3600 * 24,
+ weeks: 3600 * 24 * 7,
+ months: 3600 * 24 * 30,
+ years: 3600 * 24 * 365,
+}
+
const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => {
const storageExplorerStore = useStorageStore()
const { getFileUrl, selectedFileCustomExpiry, setSelectedFileCustomExpiry } = storageExplorerStore
@@ -25,34 +35,57 @@ const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => {
>