From 9de6ee58b15c029b6d9bb94b40f888b146426bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 30 Jul 2024 14:37:54 +0800 Subject: [PATCH 01/13] feat(orb): support new usage stats (#28208) - Add new metric Disk Size GB-Hrs (only exposed to paid plans on Orb) - Adjusted some texts as preparation of new billing plans - Fixed some visual bugs with the usage summary grid - Project allocations in tooltips are now sorted by usage (descending) - Project allocations with too little usage (<0.01) now display "<0.01" instead of just "0" - Usage formatter supports usage that is broken down to hours - Stop showing percentages when the usage is 0 (previously showed "<1%" This change is backwards compatible and can be merged. Note that there will be full overhaul in the feature of the entire usage page, this is just the least invasive change to make the transition work. --- .../BillingBreakdown.constants.ts | 17 ++- .../BillingBreakdown/BillingMetric.tsx | 122 +++++++++++------- .../BillingBreakdown/ComputeMetric.tsx | 79 ++++++------ .../BillingBreakdown/UpcomingInvoice.tsx | 4 +- .../Organization/BillingSettings/helpers.ts | 27 +++- .../Organization/Usage/TotalUsage.tsx | 15 ++- .../Organization/Usage/Usage.constants.tsx | 22 +++- .../Organization/Usage/Usage.utils.ts | 4 + .../Organization/Usage/UsageBarChart.tsx | 2 +- .../Organization/Usage/UsageChartTooltips.tsx | 6 +- .../Usage/UsageSection/AttributeUsage.tsx | 65 +++++----- .../Usage/UsageSection/UsageSection.tsx | 4 +- .../data/analytics/org-daily-stats-query.ts | 1 + packages/api-types/types/api.d.ts | 6 +- 14 files changed, 225 insertions(+), 149 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts index 26eb514fece..ba64d28edf3 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.constants.ts @@ -6,9 +6,10 @@ export interface Metric { key: string name: string units: string - anchor: string + anchor?: string category: string - unitName: string + unitName?: string + tip?: string } export const BILLING_BREAKDOWN_METRICS: Metric[] = [ @@ -58,7 +59,6 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [ units: 'absolute', anchor: 'storageImageTransformations', category: 'Storage', - unitName: 'image', }, { key: PricingMetric.REALTIME_PEAK_CONNECTIONS, @@ -66,7 +66,6 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [ units: 'absolute', anchor: 'realtimePeakConnections', category: 'Realtime', - unitName: 'connection', }, { key: PricingMetric.REALTIME_MESSAGE_COUNT, @@ -74,7 +73,6 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [ units: 'absolute', anchor: 'realtimeMessageCount', category: 'Realtime', - unitName: 'message', }, { key: PricingMetric.FUNCTION_INVOCATIONS, @@ -82,6 +80,13 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [ units: 'absolute', anchor: 'funcInvocations', category: 'Edge Functions', - unitName: 'invocation', + }, + { + key: PricingMetric.DISK_SIZE_GB_HOURS, + name: 'Disk Size', + units: 'absolute', + unitName: 'GB-Hrs', + category: 'Database', + tip: 'Each project gets provisioned with 8 GB of disk. When you get close to the disk size limit, we autoscale your disk by 1.5x. The first 8 GB of disk is free for every single project.', }, ] diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx index 5943872e241..c95e5190d34 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx @@ -36,21 +36,30 @@ const BillingMetric = ({ } else if ( (usageMeta.cost && usageMeta.cost > 0) || !relativeToSubscription || - usageMeta.unlimited + usageMeta.unlimited || + usageMeta.pricing_free_units === 0 ) { - return metric.units === 'bytes' + return metric.units === 'bytes' || metric.units === 'gigabytes' ? `${usageMeta.usage.toLocaleString() ?? 0} GB` - : usageMeta.usage.toLocaleString() + : usageMeta.usage.toLocaleString() + (metric.unitName ? ` ${metric.unitName}` : '') } else { - return metric.units === 'bytes' + return metric.units === 'bytes' || metric.units === 'gigabytes' ? `${usageMeta.usage.toLocaleString() ?? 0} / ${usageMeta.pricing_free_units ?? 0} GB` - : `${usageMeta.usage.toLocaleString()} / ${usageMeta.pricing_free_units?.toLocaleString()}` + : `${usageMeta.usage.toLocaleString()} / ${usageMeta.pricing_free_units?.toLocaleString()}` + + (metric.unitName ? ` ${metric.unitName}` : '') } }, [usageMeta, relativeToSubscription, metric]) + const sortedProjectAllocations = useMemo(() => { + if (!usageMeta || !usageMeta.project_allocations) return [] + + return usageMeta.project_allocations.sort((a, b) => b.usage - a.usage) + }, [usageMeta]) + if (!usageMeta) return null - const usageRatio = usageMeta.usage / (usageMeta.pricing_free_units ?? 0) + const usageRatio = + usageMeta.usage === 0 ? 0 : usageMeta.usage / (usageMeta.pricing_free_units ?? 0) const isUsageBillingEnabled = subscription?.usage_billing_enabled === true @@ -61,24 +70,32 @@ const BillingMetric = ({ const unit = billingMetricUnit(usageMeta.metric as PricingMetric) const percentageLabel = - usageRatio < 0.01 ? '<1%' : `${(+(usageRatio * 100).toFixed(0)).toLocaleString()}%` + usageMeta.usage === 0 || usageMeta.pricing_free_units === 0 + ? '' + : usageRatio < 0.01 + ? '(<1%)' + : `(${(+(usageRatio * 100).toFixed(0)).toLocaleString()}%)` return (
- -
-

- {metric.name} -

- -
- + {metric.anchor ? ( + +
+

+ {metric.name} +

+ +
+ + ) : ( +

{metric.name}

+ )} {usageLabel}  - {usageMeta.cost && usageMeta.cost > 0 ? ( + {relativeToSubscription && usageMeta.cost && usageMeta.cost > 0 ? ( ({formatCurrency(usageMeta.cost)}) ) : usageMeta.available_in_plan && !usageMeta.unlimited && relativeToSubscription ? ( - ({percentageLabel}) + {percentageLabel} ) : null}
@@ -130,39 +147,10 @@ const BillingMetric = ({

{usageMeta.unit_price_desc}

- {usageMeta.project_allocations && usageMeta.project_allocations.length > 0 && ( - - - - - - - - - {usageMeta.project_allocations.map((allocation) => ( - - - - - ))} - - - - - - - - -
ProjectUsage
{allocation.name} - {formatUsage(usageMeta.metric as PricingMetric, allocation.usage)} -
- Total{unit && ({unit})} - - {formatUsage( - usageMeta.metric as PricingMetric, - usageMeta.usage_original - )}{' '} -
+ {metric.tip && ( +
+

{metric.tip}

+
)} {subscription.usage_billing_enabled === false && @@ -176,6 +164,40 @@ const BillingMetric = ({

)} + + {sortedProjectAllocations && sortedProjectAllocations.length > 0 && ( + + + + + + + + + {sortedProjectAllocations.map((allocation) => ( + + + + + ))} + + + + + + + + +
ProjectUsage
{allocation.name} + {formatUsage(usageMeta.metric as PricingMetric, allocation)} +
+ Total{unit && ({unit})} + + {formatUsage(usageMeta.metric as PricingMetric, { + usage: usageMeta.usage_original, + })}{' '} +
+ )}
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/ComputeMetric.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/ComputeMetric.tsx index 925015cc208..a319efb3056 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/ComputeMetric.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/ComputeMetric.tsx @@ -20,15 +20,21 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM const usageMeta = usage.usages.find((x) => x.metric === metric.key) const usageLabel = useMemo(() => { - if (usageMeta?.cost && usageMeta.cost > 0) { - return `${usageMeta?.usage?.toLocaleString() ?? 0} hours` - } else { + if (usageMeta?.pricing_free_units) { return `${usageMeta?.usage?.toLocaleString() ?? 0} / ${ usageMeta?.pricing_free_units ?? 0 } hours` + } else { + return `${usageMeta?.usage?.toLocaleString() ?? 0} hours` } }, [usageMeta]) + const sortedProjectAllocations = useMemo(() => { + if (!usageMeta || !usageMeta.project_allocations) return [] + + return usageMeta.project_allocations.sort((a, b) => b.usage - a.usage) + }, [usageMeta]) + return (
@@ -57,40 +63,7 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM

{usageMeta?.unit_price_desc}

- {usageMeta?.project_allocations && usageMeta.project_allocations.length > 0 && ( - - - - - - - - - {usageMeta.project_allocations.map((allocation) => ( - - - - - ))} - - - - - - - - -
ProjectUsage
{allocation.name} - {formatUsage(usageMeta.metric as PricingMetric, allocation.usage)} -
Total (Hours) - {formatUsage( - usageMeta.metric as PricingMetric, - usageMeta.usage_original - )} -
- )} - -
+

Every project is a dedicated server and database. For every hour your project is active, it incurs compute costs based on the compute size of your project. @@ -104,6 +77,38 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM

+ + {usageMeta && sortedProjectAllocations.length > 0 && ( + + + + + + + + + {sortedProjectAllocations.map((allocation) => ( + + + + + ))} + + + + + + + + +
ProjectUsage
{allocation.name} + {formatUsage(usageMeta.metric as PricingMetric, allocation)} +
Total (Hours) + {formatUsage(usageMeta.metric as PricingMetric, { + usage: usageMeta.usage_original, + })} +
+ )}
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/UpcomingInvoice.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/UpcomingInvoice.tsx index 9a9a2fe5cdf..d3fe1d030e1 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/UpcomingInvoice.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/UpcomingInvoice.tsx @@ -193,7 +193,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => { {fee.usage_original - ? `${formatUsage(fee.usage_metric!, fee.usage_original)}` + ? `${formatUsage(fee.usage_metric!, { usage: fee.usage_original })}` : fee.quantity ? fee.quantity : null} @@ -223,7 +223,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => { {breakdown.project_name} - {formatUsage(fee.usage_metric!, breakdown.usage)} + {formatUsage(fee.usage_metric!, breakdown)} diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/helpers.ts b/apps/studio/components/interfaces/Organization/BillingSettings/helpers.ts index 431fe037dd0..62677c4ba83 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/helpers.ts +++ b/apps/studio/components/interfaces/Organization/BillingSettings/helpers.ts @@ -6,11 +6,32 @@ const pricingMetricBytes = [ PricingMetric.STORAGE_SIZE, ] -export const formatUsage = (pricingMetric: PricingMetric, usage: number) => { +export const formatUsage = ( + pricingMetric: PricingMetric, + allocation: { usage: number; hours?: number } +) => { + if (allocation.hours) { + return ( + allocation.usage.toLocaleString() + + ' (' + + Math.round(allocation.usage / allocation.hours).toLocaleString() + + 'x' + + allocation.hours.toLocaleString() + + ' hours)' + ) + } + if (pricingMetricBytes.includes(pricingMetric)) { - return +(usage / 1e9).toFixed(2).toLocaleString() + const formattedUsage = +(allocation.usage / 1e9).toFixed(2).toLocaleString() + + // To avoid very low usage displaying as "0", we will show "<0.01" instead + if (allocation.usage > 0 && formattedUsage === 0) { + return '<0.01' + } else { + return formattedUsage + } } else { - return usage.toLocaleString() + return allocation.usage.toLocaleString() } } diff --git a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx index f56de7d4206..f664536c08a 100644 --- a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx @@ -58,9 +58,13 @@ const TotalUsage = ({ ) const sortedBillingMetrics = useMemo(() => { - if (!usage) return BILLING_BREAKDOWN_METRICS + if (!usage) return [] - return BILLING_BREAKDOWN_METRICS.slice().sort((a, b) => { + const breakdownMetrics = BILLING_BREAKDOWN_METRICS.filter((metric) => + usage.usages.some((usage) => !usage.available_in_plan || usage.metric === metric.key) + ) + + return breakdownMetrics.slice().sort((a, b) => { const usageMetaA = usage.usages.find((x) => x.metric === a.key) const usageRatioA = typeof usageMetaA !== 'number' @@ -92,8 +96,8 @@ const TotalUsage = ({ section={{ name: 'Usage Summary', description: isUsageBillingEnabled - ? 'Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, your subscription will be charged for the overages. It may take up to 24 hours for usage stats to update.' - : 'Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, you may experience restrictions, as you are currently not billed for overages. It may take up to 24 hours for usage stats to update.', + ? `Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, your subscription will be charged for the overages. It may take up to ${subscription?.usage_based_billing_project_addons ? '1 hour' : '24 hours'} for usage stats to update.` + : `Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, you may experience restrictions, as you are currently not billed for overages. It may take up to ${subscription?.usage_based_billing_project_addons ? '1 hour' : '24 hours'} for usage stats to update.`, links: [ { name: 'How billing works', @@ -184,7 +188,8 @@ const TotalUsage = ({
diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx index 60317e059eb..fbf88f42ebb 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx @@ -47,7 +47,7 @@ export interface CategoryAttribute { key: string // Property from organization usage attributes: Attribute[] // For querying against stats-daily / infra-monitoring name: string - unit: 'bytes' | 'absolute' | 'percentage' | 'hours' + unit: 'bytes' | 'absolute' | 'percentage' | 'hours' | 'gigabytes' links?: { name: string url: string @@ -56,7 +56,7 @@ export interface CategoryAttribute { chartPrefix?: 'Max' | 'Average' | 'Cumulative' chartSuffix?: string chartDescription: string - additionalInfo?: (subscription?: OrgSubscription, usage?: OrgUsageResponse) => JSX.Element | null + additionalInfo?: (usage?: OrgUsageResponse) => JSX.Element | null } export type CategoryMetaKey = 'bandwidth' | 'sizeCount' | 'activity' | 'compute' @@ -68,7 +68,9 @@ export interface CategoryMeta { attributes: CategoryAttribute[] } -export const USAGE_CATEGORIES: CategoryMeta[] = [ +export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[] = ( + subscription +) => [ { key: 'bandwidth', name: 'Bandwidth', @@ -106,15 +108,25 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [ chartPrefix: 'Average', unit: 'bytes', description: - 'Database size refers to the monthly average storage usage, as reported by Postgres. Paid Plans use auto-scaling disks.\nBilling is based on the average daily database size used in GB throughout the billing period. Billing is independent of the provisioned disk size.', + subscription?.usage_based_billing_project_addons === true + ? 'Database size refers to the monthly average database space usage, as reported by Postgres. Paid Plans use auto-scaling disks and are billed based on provisioned disk size, rather than database space used.' + : 'Database size refers to the monthly average database space usage, as reported by Postgres. Paid Plans use auto-scaling disks.\nBilling is based on the average daily database size used in GB throughout the billing period. Billing is independent of the provisioned disk size.', links: [ { name: 'Documentation', url: 'https://supabase.com/docs/guides/platform/database-size', }, + ...(subscription?.usage_based_billing_project_addons === true + ? [ + { + name: 'Disk Management', + url: 'https://supabase.com/docs/guides/platform/database-size#disk-management', + }, + ] + : []), ], chartDescription: 'The data refreshes every 24 hours.', - additionalInfo: (subscription?: OrgSubscription, usage?: OrgUsageResponse) => { + additionalInfo: (usage?: OrgUsageResponse) => { const usageMeta = usage?.usages.find((x) => x.metric === PricingMetric.DATABASE_SIZE) const usageRatio = typeof usageMeta !== 'number' diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts b/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts index 4e68b9e278a..62dcce33ae1 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts @@ -38,6 +38,8 @@ export const ChartYFormatterCompactNumber = (number: number | string, unit: stri const formattedBytes = formatBytesCompact(number).replace(/\s/g, '') return formattedBytes === '0bytes' ? '0' : formattedBytes + } else if (unit === 'gigabytes') { + return compactNumberFormatter.format(number) + 'GB' } else { return compactNumberFormatter.format(number) } @@ -53,6 +55,8 @@ export const ChartTooltipValueFormatter = (number: number | string, unit: string const formattedBytes = formatBytesPrecision(number).replace(/\s/g, '') return formattedBytes === '0bytes' ? '0' : formattedBytes + } else if (unit === 'gigabytes') { + return compactNumberFormatter.format(number) + 'GB' } else { return compactNumberFormatter.format(number) } diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageBarChart.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageBarChart.tsx index 003943ddfa5..937f36c09af 100644 --- a/apps/studio/components/interfaces/Organization/Usage/UsageBarChart.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/UsageBarChart.tsx @@ -21,7 +21,7 @@ export interface UsageBarChartProps { data: DataPoint[] name: string // Used within the tooltip attributes: Attribute[] - unit: 'bytes' | 'absolute' | 'percentage' | 'hours' + unit: 'bytes' | 'absolute' | 'percentage' | 'hours' | 'gigabytes' yLimit?: number yMin?: number yLeftMargin?: number diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageChartTooltips.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageChartTooltips.tsx index da6f20200c6..685c7afb46e 100644 --- a/apps/studio/components/interfaces/Organization/Usage/UsageChartTooltips.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/UsageChartTooltips.tsx @@ -4,7 +4,7 @@ import { Attribute, COLOR_MAP } from './Usage.constants' export interface SingleAttributeTooltipContentProps { name: string - unit: 'bytes' | 'percentage' | 'absolute' | 'hours' + unit: 'bytes' | 'percentage' | 'absolute' | 'hours' | 'gigabytes' value: any isAfterToday: boolean tooltipFormatter?: (value: any) => any @@ -37,7 +37,7 @@ export interface MultiAttributeTooltipContentProps { values: Payload[] isAfterToday: boolean tooltipFormatter?: (value: any) => any - unit: 'bytes' | 'percentage' | 'absolute' | 'hours' + unit: 'bytes' | 'percentage' | 'absolute' | 'hours' | 'gigabytes' } const AttributeContent = ({ @@ -51,7 +51,7 @@ const AttributeContent = ({ attributeMeta?: Payload sumValue: number tooltipFormatter?: (value: any) => any - unit: 'bytes' | 'percentage' | 'absolute' | 'hours' + unit: 'bytes' | 'percentage' | 'absolute' | 'hours' | 'gigabytes' }) => { const attrValue = Number(attributeMeta?.value ?? 0) const percentageContribution = ((attrValue / sumValue) * 100).toFixed(1) diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx index e0a01bd76c1..bebc034cb83 100644 --- a/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx @@ -93,7 +93,7 @@ const AttributeUsage = ({ {isSuccess && ( <> - {usageMeta?.available_in_plan ? ( + {!usageMeta || usageMeta?.available_in_plan ? ( <> {!projectRef && (
@@ -150,7 +150,7 @@ const AttributeUsage = ({
- {currentBillingCycleSelected && !usageMeta.unlimited && ( + {currentBillingCycleSelected && usageMeta && !usageMeta.unlimited && ( )} - { -
+
+ {usageMeta && (

Included in {subscription?.plan?.name.toLowerCase()} plan @@ -179,43 +179,42 @@ const AttributeUsage = ({

Unlimited

) : (

- {attribute.unit === 'bytes' + {attribute.unit === 'bytes' || attribute.unit === 'gigabytes' ? `${usageMeta.pricing_free_units ?? 0} GB` : (usageMeta.pricing_free_units ?? 0).toLocaleString()}

)}
- {currentBillingCycleSelected && ( -
-

- {attribute.chartPrefix || 'Used '} in period -

-

- {attribute.unit === 'bytes' - ? `${(usageMeta?.usage ?? 0).toFixed(2)} GB` - : (usageMeta?.usage ?? 0).toLocaleString()} -

-
- )} - {currentBillingCycleSelected && - (usageMeta?.pricing_free_units ?? 0) > 0 && ( -
-

Overage in period

-

- {(usageMeta?.pricing_free_units ?? 0) === -1 || usageExcess < 0 - ? `0${attribute.unit === 'bytes' ? ' GB' : ''}` - : attribute.unit === 'bytes' - ? `${usageExcess.toFixed(2)} GB` - : usageExcess.toLocaleString()} -

-
- )} -
- } + )} + {currentBillingCycleSelected && usageMeta && ( +
+

+ {attribute.chartPrefix || 'Used '} in period +

+

+ {attribute.unit === 'bytes' || attribute.unit === 'gigabytes' + ? `${(usageMeta?.usage ?? 0).toFixed(2)} GB` + : (usageMeta?.usage ?? 0).toLocaleString()} +

+
+ )} + {currentBillingCycleSelected && (usageMeta?.pricing_free_units ?? 0) > 0 && ( +
+

Overage in period

+

+ {(usageMeta?.pricing_free_units ?? 0) === -1 || usageExcess < 0 + ? `0${attribute.unit === 'bytes' || attribute.unit === 'gigabytes' ? ' GB' : ''}` + : attribute.unit === 'bytes' || attribute.unit === 'gigabytes' + ? `${usageExcess.toFixed(2)} GB` + : usageExcess.toLocaleString()} +

+
+ )} +
)} - {attribute.additionalInfo?.(subscription, usage)} + {attribute.additionalInfo?.(usage)}

diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx index f5a4fe47b8f..21f118c093f 100644 --- a/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx @@ -35,7 +35,9 @@ const UsageSection = ({ isSuccess: isSuccessUsage, } = useOrgUsageQuery({ orgSlug }) - const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === categoryKey) + const categoryMeta = USAGE_CATEGORIES(subscription).find( + (category) => category.key === categoryKey + ) if (!categoryMeta) return null return ( diff --git a/apps/studio/data/analytics/org-daily-stats-query.ts b/apps/studio/data/analytics/org-daily-stats-query.ts index 8b74d5e8d30..fa4e3797cc1 100644 --- a/apps/studio/data/analytics/org-daily-stats-query.ts +++ b/apps/studio/data/analytics/org-daily-stats-query.ts @@ -21,6 +21,7 @@ export enum PricingMetric { EGRESS = 'EGRESS', DATABASE_SIZE = 'DATABASE_SIZE', STORAGE_SIZE = 'STORAGE_SIZE', + DISK_SIZE_GB_HOURS = 'DISK_SIZE_GB_HOURS', MONTHLY_ACTIVE_USERS = 'MONTHLY_ACTIVE_USERS', MONTHLY_ACTIVE_SSO_USERS = 'MONTHLY_ACTIVE_SSO_USERS', FUNCTION_INVOCATIONS = 'FUNCTION_INVOCATIONS', diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts index f1ad7619ef2..a56e891dd08 100644 --- a/packages/api-types/types/api.d.ts +++ b/packages/api-types/types/api.d.ts @@ -3911,7 +3911,7 @@ export interface components { | 'STORAGE_IMAGES_TRANSFORMED' | 'REALTIME_MESSAGE_COUNT' | 'REALTIME_PEAK_CONNECTIONS' - | 'DISK_GB_HOURS' + | 'DISK_SIZE_GB_HOURS' | 'COMPUTE_HOURS_BRANCH' | 'COMPUTE_HOURS_XS' | 'COMPUTE_HOURS_SM' @@ -3941,7 +3941,6 @@ export interface components { usage_original: number } OrgUsageResponse: { - slugs: string[] usage_billing_enabled: boolean usages: components['schemas']['OrgMetricUsage'][] } @@ -4308,6 +4307,7 @@ export interface components { price_type: components['schemas']['ProjectAddonVariantPricingType'] } ProjectAllocation: { + hours?: number name: string ref: string usage: number @@ -7803,7 +7803,7 @@ export interface operations { | 'STORAGE_IMAGES_TRANSFORMED' | 'REALTIME_MESSAGE_COUNT' | 'REALTIME_PEAK_CONNECTIONS' - | 'DISK_GB_HOURS' + | 'DISK_SIZE_GB_HOURS' | 'COMPUTE_HOURS_BRANCH' | 'COMPUTE_HOURS_XS' | 'COMPUTE_HOURS_SM' From 46a7c73a82ed2c9acd8027b086924d517fcce403 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:53:06 +0200 Subject: [PATCH 02/13] chore: update self-hosted image versions (#28272) chore: update image versions for docker/docker-compose.yml --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e59cfb968a2..9edd76f50bc 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,7 +10,7 @@ version: "3.8" services: studio: container_name: supabase-studio - image: supabase/studio:20240722-c670708 + image: supabase/studio:20240729-ce42139 restart: unless-stopped healthcheck: test: From a389866b5d8e5bc5ec5b6fff49953d1a16dc4036 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:03:59 +0200 Subject: [PATCH 03/13] chore: update cli reference doc (#28245) * chore: update cli reference doc * chore: update cli reference doc --- apps/docs/spec/cli_v1_commands.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/spec/cli_v1_commands.yaml b/apps/docs/spec/cli_v1_commands.yaml index 27da229776d..c682e88a220 100644 --- a/apps/docs/spec/cli_v1_commands.yaml +++ b/apps/docs/spec/cli_v1_commands.yaml @@ -1,7 +1,7 @@ clispec: '001' info: id: cli - version: 1.187.3 + version: 1.187.10 title: Supabase CLI language: sh source: https://github.com/supabase/cli From d674ccbfae5e13e7ed9bc1de4366971a519cf724 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 30 Jul 2024 17:07:39 +0800 Subject: [PATCH 04/13] fix: improve github authorization calls (#28274) --- .../IntegrationSettings/SidePanelGitHubRepoLinker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker.tsx b/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker.tsx index cba30fb97df..1a3b423f313 100644 --- a/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker.tsx +++ b/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker.tsx @@ -36,7 +36,7 @@ const SidePanelGitHubRepoLinker = ({ projectRef }: SidePanelGitHubRepoLinkerProp const sidePanelStateSnapshot = useSidePanelsStateSnapshot() const { data: gitHubAuthorization, isLoading: isLoadingGitHubAuthorization } = - useGitHubAuthorizationQuery() + useGitHubAuthorizationQuery({ enabled: sidePanelStateSnapshot.githubConnectionsOpen }) // [Alaister]: temp override with until the typegen is fixed const { data: githubReposData, isLoading: isLoadingGitHubRepos } = useGitHubRepositoriesQuery< From b4adc8d6ba1cbe6f1c63775b0421d4b5ce5dddb3 Mon Sep 17 00:00:00 2001 From: jenny kibiri <46620256+jenniekibiri@users.noreply.github.com> Date: Tue, 30 Jul 2024 13:00:14 +0300 Subject: [PATCH 05/13] chore: add jenniekibiri to humans.txt (#28187) Co-authored-by: Jenny kibiri --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index ee2983ba130..25cb1920e0b 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -34,6 +34,7 @@ Guilherme Souza Haydn Maley Hieu Pham Inian P +Jenny Kibiri Jess Shears Joel Lee John Pena From 4b2c4acaab24cdbd586d38a593b56f099017db6e Mon Sep 17 00:00:00 2001 From: Thomas <31189692+ecktoteckto@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:01:45 +0200 Subject: [PATCH 06/13] Adjust texts shown when user doesn't have permission for org settings (#28210) --- .../BillingSettings/BillingBreakdown/BillingBreakdown.tsx | 2 +- .../components/interfaces/Organization/OAuthApps/OAuthApps.tsx | 2 +- apps/studio/components/ui/NoPermission.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx index 4292616e26f..b22f205965e 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx @@ -52,7 +52,7 @@ const BillingBreakdown = () => { {!canReadSubscriptions ? ( - + ) : ( <> {isLoadingSubscription && ( diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx index 8744880f1a8..512de2049f4 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx @@ -61,7 +61,7 @@ const OAuthApps = () => { if (!canReadOAuthApps) { return ( - + ) } diff --git a/apps/studio/components/ui/NoPermission.tsx b/apps/studio/components/ui/NoPermission.tsx index ab96619caca..3fb1a0b8554 100644 --- a/apps/studio/components/ui/NoPermission.tsx +++ b/apps/studio/components/ui/NoPermission.tsx @@ -22,7 +22,7 @@ const NoPermission = ({ resourceText, isFullPage = false }: NoPermissionProps) =

You need additional permissions to {resourceText}

- Contact your organization owner or adminstrator for assistance. + Contact your organization owner or administrator for assistance.

From c52fd215801fd0efe24087aaf6817cec5203b937 Mon Sep 17 00:00:00 2001 From: jingsam Date: Tue, 30 Jul 2024 19:38:11 +0800 Subject: [PATCH 07/13] fix logs title (#28266) --- apps/studio/pages/project/[ref]/logs/edge-logs.tsx | 2 +- apps/studio/pages/project/[ref]/logs/pooler-logs.tsx | 2 +- apps/studio/pages/project/[ref]/logs/postgres-logs.tsx | 2 +- apps/studio/pages/project/[ref]/logs/postgrest-logs.tsx | 2 +- apps/studio/pages/project/[ref]/logs/realtime-logs.tsx | 2 +- apps/studio/pages/project/[ref]/logs/storage-logs.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/studio/pages/project/[ref]/logs/edge-logs.tsx b/apps/studio/pages/project/[ref]/logs/edge-logs.tsx index 59055f4de34..923dca7a204 100644 --- a/apps/studio/pages/project/[ref]/logs/edge-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/edge-logs.tsx @@ -19,6 +19,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default LogPage diff --git a/apps/studio/pages/project/[ref]/logs/pooler-logs.tsx b/apps/studio/pages/project/[ref]/logs/pooler-logs.tsx index 8275ea21191..37c02c2e667 100644 --- a/apps/studio/pages/project/[ref]/logs/pooler-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/pooler-logs.tsx @@ -23,6 +23,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default LogPage diff --git a/apps/studio/pages/project/[ref]/logs/postgres-logs.tsx b/apps/studio/pages/project/[ref]/logs/postgres-logs.tsx index afddad783a4..b029ee105c3 100644 --- a/apps/studio/pages/project/[ref]/logs/postgres-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/postgres-logs.tsx @@ -19,6 +19,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default LogPage diff --git a/apps/studio/pages/project/[ref]/logs/postgrest-logs.tsx b/apps/studio/pages/project/[ref]/logs/postgrest-logs.tsx index 29acc7044db..d75a9fe7189 100644 --- a/apps/studio/pages/project/[ref]/logs/postgrest-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/postgrest-logs.tsx @@ -19,6 +19,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default LogPage diff --git a/apps/studio/pages/project/[ref]/logs/realtime-logs.tsx b/apps/studio/pages/project/[ref]/logs/realtime-logs.tsx index 6dfcdb2a72b..636ffa945da 100644 --- a/apps/studio/pages/project/[ref]/logs/realtime-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/realtime-logs.tsx @@ -19,6 +19,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default LogPage diff --git a/apps/studio/pages/project/[ref]/logs/storage-logs.tsx b/apps/studio/pages/project/[ref]/logs/storage-logs.tsx index e0f63ee0271..41a65f98281 100644 --- a/apps/studio/pages/project/[ref]/logs/storage-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/storage-logs.tsx @@ -19,6 +19,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default LogPage From bb967407dc77d2656873505473f80d9d821fb23a Mon Sep 17 00:00:00 2001 From: Mohammed Yasin Mulla <55092280+Mohammed-Yasin-Mulla@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:15:05 +0530 Subject: [PATCH 08/13] =?UTF-8?q?style:=20move=20the=20find-widget=20in=20?= =?UTF-8?q?the=20sql=20editor=20to=20prevent=20overlapping=20=E2=80=A6=20(?= =?UTF-8?q?#28228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit style: move the find-widget in the sql editor to prevent overlapping with the suggest ai button --- apps/studio/styles/monaco.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/studio/styles/monaco.scss b/apps/studio/styles/monaco.scss index 9abbd76e8db..7ebacfe0974 100644 --- a/apps/studio/styles/monaco.scss +++ b/apps/studio/styles/monaco.scss @@ -47,6 +47,10 @@ span.th.tr.td.textarea { cursor: inherit; } +.find-widget { + margin-right: 30px; +} + .grid-monaco-editor, .monaco-editor .suggest-widget { .main { From eb9ab2a7ab96b41def229f97d73a0cdcb55572bd Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 30 Jul 2024 20:15:19 +0800 Subject: [PATCH 09/13] chore: update table def (#28278) * chore: update table def * fix double not null * update test --- .../grid/components/footer/Footer.tsx | 23 +- .../database/database-table-definition.ts | 395 ++++++++++++------ .../tests/snapshot/spec/table-editor.spec.ts | 2 +- 3 files changed, 284 insertions(+), 136 deletions(-) diff --git a/apps/studio/components/grid/components/footer/Footer.tsx b/apps/studio/components/grid/components/footer/Footer.tsx index 57cb0019c9e..f3a4881622a 100644 --- a/apps/studio/components/grid/components/footer/Footer.tsx +++ b/apps/studio/components/grid/components/footer/Footer.tsx @@ -1,6 +1,8 @@ import { useParams } from 'common' import { GridFooter } from 'components/ui/GridFooter' import TwoOptionToggle from 'components/ui/TwoOptionToggle' +import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' +import useEntityType from 'hooks/misc/useEntityType' import useTable from 'hooks/misc/useTable' import { useUrlState } from 'hooks/ui/useUrlState' import RefreshButton from '../header/RefreshButton' @@ -14,6 +16,7 @@ const Footer = ({ isRefetching }: FooterProps) => { const { id: _id } = useParams() const id = _id ? Number(_id) : undefined const { data: selectedTable } = useTable(id) + const entityType = useEntityType(selectedTable?.id) const [{ view: selectedView = 'data' }, setUrlState] = useUrlState() @@ -25,6 +28,10 @@ const Footer = ({ isRefetching }: FooterProps) => { } } + const isViewSelected = + entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW + const isTableSelected = entityType?.type === ENTITY_TYPE.TABLE + return ( {selectedView === 'data' && } @@ -34,13 +41,15 @@ const Footer = ({ isRefetching }: FooterProps) => { )} - + {(isViewSelected || isTableSelected) && ( + + )}
) diff --git a/apps/studio/data/database/database-table-definition.ts b/apps/studio/data/database/database-table-definition.ts index bc968581287..0e09cb40507 100644 --- a/apps/studio/data/database/database-table-definition.ts +++ b/apps/studio/data/database/database-table-definition.ts @@ -2,38 +2,66 @@ import minify from 'pg-minify' // Derived from https://github.com/MichaelDBA/pg_get_tabledef // NOTE: when updating, \n must be replaced with \\n in the SQL below +// public.x is replaced with pg_temp.x + +// REMOVE (as it breaks the encoding for some reason): +// SELECT REPLACE(REPLACE(setting, '"$user"', '$user'), '$user', '"$user"') INTO search_path_old +// FROM pg_settings +// WHERE name = 'search_path'; export const CREATE_PG_GET_TABLEDEF_SQL = minify( /* SQL */ ` DROP TYPE IF EXISTS pg_temp.tabledefs CASCADE; - CREATE TYPE pg_temp.tabledefs AS ENUM ('PKEY_INTERNAL','PKEY_EXTERNAL','FKEYS_INTERNAL', 'FKEYS_EXTERNAL', 'FKEYS_COMMENTED', 'FKEYS_NONE', 'INCLUDE_TRIGGERS', 'NO_TRIGGERS'); + CREATE TYPE pg_temp.tabledefs AS ENUM ('PKEY_INTERNAL','PKEY_EXTERNAL','FKEYS_INTERNAL', 'FKEYS_EXTERNAL', 'COMMENTS', 'FKEYS_NONE', 'INCLUDE_TRIGGERS', 'NO_TRIGGERS'); + -- SELECT * FROM pg_temp.pg_get_coldef('sample','orders','id'); -- DROP FUNCTION pg_temp.pg_get_coldef(text,text,text,boolean); CREATE OR REPLACE FUNCTION pg_temp.pg_get_coldef( in_schema text, - in_table text, + in_table text, in_column text, - oldway boolean default False + oldway boolean default False ) RETURNS text LANGUAGE plpgsql VOLATILE AS $$ DECLARE - coldef text; + v_coldef text; + v_dt1 text; + v_dt2 text; + v_dt3 text; + v_nullable boolean; + v_position int; + v_identity text; + v_generated text; + v_hasdflt boolean; + v_dfltexpr text; + BEGIN IF oldway THEN - SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) INTO coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum; + -- RAISE NOTICE 'DEBUG: oldway=%',v_coldef; ELSE -- a.attrelid::regclass::text, a.attname SELECT CASE WHEN a.atttypid = ANY ('{int,int8,int2}'::regtype[]) AND EXISTS (SELECT FROM pg_attrdef ad WHERE ad.adrelid = a.attrelid AND ad.adnum = a.attnum AND pg_get_expr(ad.adbin, ad.adrelid) = 'nextval(''' || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass || '''::regclass)') THEN CASE a.atttypid WHEN 'int'::regtype THEN 'serial' WHEN 'int8'::regtype THEN 'bigserial' WHEN 'int2'::regtype THEN 'smallserial' END ELSE format_type(a.atttypid, a.atttypmod) END AS data_type - INTO coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t + INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum; + -- RAISE NOTICE 'DEBUG: newway=%',v_coldef; + + -- Issue#24: not implemented yet + -- might replace with this below to do more detailed parsing... + -- SELECT a.atttypid::regtype AS dt1, format_type(a.atttypid, a.atttypmod) as dt2, t.typname as dt3, CASE WHEN not(a.attnotnull) THEN True ELSE False END AS nullable, + -- a.attnum, a.attidentity, a.attgenerated, a.atthasdef, pg_get_expr(ad.adbin, ad.adrelid) dfltexpr + -- INTO v_dt1, v_dt2, v_dt3, v_nullable, v_position, v_identity, v_generated, v_hasdflt, v_dfltexpr + -- FROM pg_attribute a JOIN pg_class c ON (a.attrelid = c.oid) JOIN pg_type t ON (a.atttypid = t.oid) LEFT JOIN pg_attrdef ad ON (a.attrelid = ad.adrelid AND a.attnum = ad.adnum) + -- WHERE c.relkind in ('r','p') AND a.attnum > 0 AND NOT a.attisdropped AND c.relnamespace::regnamespace::text = in_schema AND c.relname = in_table AND a.attname = in_column; + -- RAISE NOTICE 'schema=% table=% column=% dt1=% dt2=% dt3=% nullable=% pos=% identity=% generated=% HasDefault=% DeftExpr=%', in_schema, in_table, in_column, v_dt1,v_dt2,v_dt3,v_nullable,v_position,v_identity,v_generated,v_hasdflt,v_dfltexpr; END IF; - RETURN coldef; + RETURN v_coldef; END; $$; @@ -57,11 +85,12 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( v_constraintrec record; v_trigrec record; v_indexrec record; - v_primary boolean := False; + v_rec record; v_constraint_name text; v_constraint_def text; v_pkey_def text := ''; - v_fkey_defs text; + v_fkey_def text := ''; + v_fkey_defs text := ''; v_trigger text := ''; v_partition_key text := ''; v_partbound text; @@ -69,6 +98,7 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( v_parent_schema text; v_persist text; v_temp text := ''; + v_temp2 text; v_relopts text; v_tablespace text; v_pgversion int; @@ -82,17 +112,22 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( bVerbose boolean := False; v_cnt1 integer; v_cnt2 integer; + search_path_old text := ''; + search_path_new text := ''; + v_partial boolean; + v_pos integer; -- assume defaults for ENUMs at the getgo pkcnt int := 0; fkcnt int := 0; trigcnt int := 0; - pktype tabledefs := 'PKEY_INTERNAL'; - fktype tabledefs := 'FKEYS_INTERNAL'; - trigtype tabledefs := 'NO_TRIGGERS'; + cmtcnt int := 0; + pktype pg_temp.tabledefs := 'PKEY_INTERNAL'; + fktype pg_temp.tabledefs := 'FKEYS_INTERNAL'; + trigtype pg_temp.tabledefs := 'NO_TRIGGERS'; arglen integer; vargs text; - avarg tabledefs; + avarg pg_temp.tabledefs; -- exception variables v_ret text; @@ -120,8 +155,8 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( -- select array_to_string($4, ',', '***') INTO vargs; IF bVerbose THEN RAISE NOTICE 'arguments=%', $4; END IF; FOREACH avarg IN ARRAY $4 LOOP - IF bVerbose THEN RAISE INFO 'arg=%', avarg; END IF; - IF avarg = 'FKEYS_INTERNAL' OR avarg = 'FKEYS_EXTERNAL' OR avarg = 'FKEYS_COMMENTED' THEN + IF bVerbose THEN RAISE NOTICE 'arg=%', avarg; END IF; + IF avarg = 'FKEYS_INTERNAL' OR avarg = 'FKEYS_EXTERNAL' OR avarg = 'FKEYS_NONE' THEN fkcnt = fkcnt + 1; fktype = avarg; ELSEIF avarg = 'INCLUDE_TRIGGERS' OR avarg = 'NO_TRIGGERS' THEN @@ -130,6 +165,9 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( ELSEIF avarg = 'PKEY_EXTERNAL' THEN pkcnt = pkcnt + 1; pktype = avarg; + ELSEIF avarg = 'COMMENTS' THEN + cmtcnt = cmtcnt + 1; + END IF; END LOOP; IF fkcnt > 1 THEN @@ -141,12 +179,24 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( ELSEIF pkcnt > 1 THEN RAISE WARNING 'Only one pkey option can be provided. You provided %', pkcnt; RETURN ''; + ELSEIF cmtcnt > 1 THEN + RAISE WARNING 'Only one comments option can be provided. You provided %', cmtcnt; + RETURN ''; + END IF; END IF; SELECT c.oid, (select setting from pg_settings where name = 'server_version_num') INTO v_table_oid, v_pgversion FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind in ('r','p') AND c.relname = in_table AND n.nspname = in_schema; - + + -- set search_path = public before we do anything to force explicit schema qualification but dont forget to set it back before exiting... + SELECT setting INTO search_path_old FROM pg_settings WHERE name = 'search_path'; + + -- RAISE NOTICE 'DEBUG tableddl: saving old search_path: ***%***', search_path_old; + EXECUTE 'SET search_path = "public"'; + SELECT setting INTO search_path_new FROM pg_settings WHERE name = 'search_path'; + -- RAISE NOTICE 'DEBUG tableddl: using new search path=***%***', search_path_new; + -- throw an error if table was not found IF (v_table_oid IS NULL) THEN RAISE EXCEPTION 'table does not exist'; @@ -206,7 +256,9 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = in_schema AND t.table_name = in_table AND t.table_type = 'BASE TABLE'); --Issue#19 put double-quotes around SQL keyword column names - SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = v_colrec.column_name AND catcode = 'R'; + -- Issue#121: fix keyword lookup for table name not column name that does not apply here + -- SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = v_colrec.column_name AND catcode = 'R'; + SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = in_table AND catcode = 'R'; IF bInheritance THEN -- inheritance-based @@ -235,7 +287,7 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( -- Jump to constraints and index section to add the check constraints and indexes and perhaps FKeys END IF; END IF; - IF bVerbose THEN RAISE INFO '(1)tabledef so far: %', v_table_ddl; END IF; + IF bVerbose THEN RAISE NOTICE '(1)tabledef so far: %', v_table_ddl; END IF; IF NOT bPartition THEN -- see if this is unlogged or temporary table @@ -262,14 +314,15 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '.' || in_table || ' (' || E'\\n'; END IF; END IF; - -- RAISE INFO 'DEBUG2: tabledef so far: %', v_table_ddl; + -- RAISE NOTICE 'DEBUG2: tabledef so far: %', v_table_ddl; -- define all of the columns in the table unless we are in progress creating an inheritance-based child table IF NOT bPartition THEN FOR v_colrec IN - SELECT c.column_name, c.data_type, c.udt_name, c.udt_schema, c.character_maximum_length, c.is_nullable, c.column_default, c.numeric_precision, c.numeric_scale, c.is_identity, c.identity_generation + SELECT c.column_name, c.data_type, c.udt_name, c.udt_schema, c.character_maximum_length, c.is_nullable, c.column_default, c.numeric_precision, c.numeric_scale, c.is_identity, c.identity_generation, c.is_generated, c.generation_expression FROM information_schema.columns c WHERE (table_schema, table_name) = (in_schema, in_table) ORDER BY ordinal_position LOOP - IF bVerbose THEN RAISE INFO '(col loop) name=% type=% udt_name=% udt_schema=%', v_colrec.column_name, v_colrec.data_type, v_colrec.udt_name, v_colrec.udt_schema; END IF; + IF bVerbose THEN RAISE NOTICE '(col loop) name=% type=% udt_name=% default=% is_generated=% gen_expr=%', v_colrec.column_name, v_colrec.data_type, v_colrec.udt_name, v_colrec.column_default, v_colrec.is_generated, v_colrec.generation_expression; END IF; + -- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling -- SELECT CASE WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial; SELECT CASE WHEN pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial; @@ -279,8 +332,8 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( SELECT pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) into v_temp; IF v_temp IS NULL THEN v_temp = 'NA'; END IF; SELECT pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name) INTO v_diag1; - --RAISE NOTICE 'DEBUG table: % Column: % datatype: % Serial=% serialval=% coldef=%', v_qualified, v_colrec.column_name, v_colrec.data_type, bSerial, v_temp, v_diag1; - --RAISE NOTICE 'DEBUG tabledef: %', v_table_ddl; + RAISE NOTICE 'DEBUG table: % Column: % datatype: % Serial=% serialval=% coldef=%', v_qualified, v_colrec.column_name, v_colrec.data_type, bSerial, v_temp, v_diag1; + RAISE NOTICE 'DEBUG tabledef: %', v_table_ddl; END IF; --Issue#17 put double-quotes around case-sensitive column names @@ -295,32 +348,64 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( ELSE v_table_ddl := v_table_ddl || ' ' || v_colrec.column_name || ' '; END IF; - - v_table_ddl := v_table_ddl || - CASE WHEN v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail') - THEN v_colrec.udt_name - WHEN v_colrec.data_type = 'USER-DEFINED' - THEN v_colrec.udt_schema || '.' || v_colrec.udt_name - WHEN v_colrec.data_type = 'ARRAY' - -- Issue#6 fix: handle arrays - THEN pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name) - -- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling - -- WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL - WHEN pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL - -- Issue#8 fix: handle serial. Note: NOT NULL is implied so no need to declare it explicitly - THEN pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name) - ELSE v_colrec.data_type END - || CASE WHEN v_colrec.is_identity = 'YES' THEN CASE WHEN v_colrec.identity_generation = 'ALWAYS' THEN ' GENERATED ALWAYS AS IDENTITY' ELSE ' GENERATED BY DEFAULT AS IDENTITY' END ELSE '' END - || CASE WHEN v_colrec.character_maximum_length IS NOT NULL THEN ('(' || v_colrec.character_maximum_length || ')') - WHEN v_colrec.numeric_precision > 0 AND v_colrec.numeric_scale > 0 THEN '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')' - ELSE '' END || ' ' - || CASE WHEN bSerial THEN '' ELSE CASE WHEN v_colrec.is_nullable = 'NO' THEN 'NOT NULL' ELSE 'NULL' END END - || CASE WHEN bSerial THEN '' ELSE CASE WHEN v_colrec.column_default IS NOT null THEN (' DEFAULT ' || v_colrec.column_default) ELSE '' END END - || ',' || E'\\n'; + + -- Issue#23: Handle autogenerated columns and rewrite as a simpler IF THEN ELSE branch instead of a much more complex embedded CASE STATEMENT + IF v_colrec.is_generated = 'ALWAYS' and v_colrec.generation_expression IS NOT NULL THEN + -- searchable tsvector GENERATED ALWAYS AS (to_tsvector('simple'::regconfig, COALESCE(translate(email, '@.-'::citext, ' '::text), ''::text)) ) STORED + v_temp = v_colrec.data_type || ' GENERATED ALWAYS AS (' || v_colrec.generation_expression || ') STORED '; + ELSEIF v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail') THEN + v_temp = v_colrec.udt_name; + ELSEIF v_colrec.data_type = 'USER-DEFINED' THEN + v_temp = v_colrec.udt_schema || '.' || v_colrec.udt_name; + ELSEIF v_colrec.data_type = 'ARRAY' THEN + -- Issue#6 fix: handle arrays + v_temp = pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name); + -- v17 fix: handle case-sensitive for pg_get_serial_sequence that requires SQL Identifier handling + -- WHEN pg_get_serial_sequence(v_qualified, v_colrec.column_name) IS NOT NULL + ELSEIF pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN + -- Issue#8 fix: handle serial. Note: NOT NULL is implied so no need to declare it explicitly + v_temp = pg_temp.pg_get_coldef(in_schema, in_table,v_colrec.column_name); + ELSE + v_temp = v_colrec.data_type; + END IF; + -- RAISE NOTICE 'column def1=%', v_temp; + + -- handle IDENTITY columns + IF v_colrec.is_identity = 'YES' THEN + IF v_colrec.identity_generation = 'ALWAYS' THEN + v_temp = v_temp || ' GENERATED ALWAYS AS IDENTITY'; + ELSE + v_temp = v_temp || ' GENERATED BY DEFAULT AS IDENTITY'; + END IF; + ELSEIF v_colrec.character_maximum_length IS NOT NULL THEN + v_temp = v_temp || ('(' || v_colrec.character_maximum_length || ')'); + ELSEIF v_colrec.numeric_precision > 0 AND v_colrec.numeric_scale > 0 THEN + v_temp = v_temp || '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')'; + END IF; + + -- Handle NULL/NOT NULL + IF bSerial THEN + v_temp = v_temp || ' NOT NULL'; + ELSEIF v_colrec.is_nullable = 'NO' THEN + v_temp = v_temp || ' NOT NULL'; + ELSEIF v_colrec.is_nullable = 'YES' THEN + v_temp = v_temp || ' NULL'; + END IF; + + -- Handle defaults + IF v_colrec.column_default IS NOT null AND NOT bSerial THEN + -- RAISE NOTICE 'Setting default for column, %', v_colrec.column_name; + v_temp = v_temp || (' DEFAULT ' || v_colrec.column_default); + END IF; + v_temp = v_temp || ',' || E'\\n'; + -- RAISE NOTICE 'column def2=%', v_temp; + v_table_ddl := v_table_ddl || v_temp; + -- RAISE NOTICE 'tabledef=%', v_table_ddl; + END LOOP; END IF; - IF bVerbose THEN RAISE INFO '(2)tabledef so far: %', v_table_ddl; END IF; - + IF bVerbose THEN RAISE NOTICE '(2)tabledef so far: %', v_table_ddl; END IF; + -- define all the constraints: conparentid does not exist pre PGv11 IF v_pgversion < 110000 THEN FOR v_constraintrec IN @@ -335,42 +420,61 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( pg_get_constraintdef(con.oid) as constraint_definition FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace WHERE nsp.nspname = in_schema AND rel.relname = in_table ORDER BY type_rank - LOOP + LOOP + v_constraint_name := v_constraintrec.constraint_name; + v_constraint_def := v_constraintrec.constraint_definition; IF v_constraintrec.type_rank = 1 THEN - v_primary := True; - IF pkcnt = 0 THEN + IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN + -- internal def v_constraint_name := v_constraintrec.constraint_name; v_constraint_def := v_constraintrec.constraint_definition; + v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column + || 'CONSTRAINT' || ' ' + || v_constraint_name || ' ' + || v_constraint_def + || ',' || E'\\n'; ELSE -- Issue#16 handle external PG def - v_constraint_name := v_constraintrec.constraint_name; SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def - FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table; + FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table and r.conname = v_constraint_name; END IF; IF bPartition THEN continue; END IF; + ELSIF v_constraintrec.type_rank = 3 THEN + -- handle foreign key constraints + --Issue#22 fix: added FKEY_NONE check + IF fktype = 'FKEYS_NONE' THEN + -- skip + continue; + ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN + -- internal def + v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column + || 'CONSTRAINT' || ' ' + || v_constraint_name || ' ' + || v_constraint_def + || ',' || E'\\n'; + ELSE + -- external def + SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def + FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table; + v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n'; + END IF; ELSE - v_constraint_name := v_constraintrec.constraint_name; - v_constraint_def := v_constraintrec.constraint_definition; + -- handle all other constraints besides PKEY and FKEYS as internal defs by default + v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column + || 'CONSTRAINT' || ' ' + || v_constraint_name || ' ' + || v_constraint_def + || ',' || E'\\n'; END IF; - if bVerbose THEN RAISE INFO 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF; + if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF; constraintarr := constraintarr || v_constraintrec.constraint_name:: text; - IF fktype <> 'FKEYS_INTERNAL' AND v_constraintrec.constraint_type = 'f' THEN - continue; - END IF; - - IF pkcnt = 0 THEN - v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column - || 'CONSTRAINT' || ' ' - || v_constraint_name || ' ' - || v_constraint_def - || ',' || E'\\n'; - END IF; END LOOP; - ELSE + -- handle PG versions 11 and up + -- Issue#20: Fix logic for external PKEY and FKEYS FOR v_constraintrec IN SELECT con.conname as constraint_name, con.contype as constraint_type, CASE @@ -386,51 +490,73 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( --Issue#13 added this condition: AND con.conparentid = 0 ORDER BY type_rank - LOOP + LOOP + v_constraint_name := v_constraintrec.constraint_name; + v_constraint_def := v_constraintrec.constraint_definition; IF v_constraintrec.type_rank = 1 THEN - v_primary := True; - IF pkcnt = 0 THEN + IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN + -- internal def v_constraint_name := v_constraintrec.constraint_name; v_constraint_def := v_constraintrec.constraint_definition; + v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column + || 'CONSTRAINT' || ' ' + || v_constraint_name || ' ' + || v_constraint_def + || ',' || E'\\n'; ELSE -- Issue#16 handle external PG def - v_constraint_name := v_constraintrec.constraint_name; SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table; END IF; IF bPartition THEN continue; - END IF; + END IF; + ELSIF v_constraintrec.type_rank = 3 THEN + -- handle foreign key constraints + --Issue#22 fix: added FKEY_NONE check + IF fktype = 'FKEYS_NONE' THEN + -- skip + continue; + ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN + -- internal def + v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column + || 'CONSTRAINT' || ' ' + || v_constraint_name || ' ' + || v_constraint_def + || ',' || E'\\n'; + ELSE + -- external def + SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def + FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table and + r.conname = v_constraint_name and r.conparentid = 0; + v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n'; + END IF; ELSE - v_constraint_name := v_constraintrec.constraint_name; - v_constraint_def := v_constraintrec.constraint_definition; + -- handle all other constraints besides PKEY and FKEYS as internal defs by default + v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column + || 'CONSTRAINT' || ' ' + || v_constraint_name || ' ' + || v_constraint_def + || ',' || E'\\n'; END IF; - -- SELECT 'ALTER TABLE ONLY ' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' as pkeyddl FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = 'sample' AND c.relname = 'extensions_table'; - if bVerbose THEN RAISE INFO 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF; + if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF; constraintarr := constraintarr || v_constraintrec.constraint_name:: text; - IF fktype <> 'FKEYS_INTERNAL' AND v_constraintrec.constraint_type = 'f' THEN - continue; - END IF; - - IF pkcnt = 0 THEN - v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column - || 'CONSTRAINT' || ' ' - || v_constraint_name || ' ' - || v_constraint_def - || ',' || E'\\n'; - END IF; END LOOP; END IF; - IF bVerbose THEN RAISE INFO '(3)tabledef so far: %', v_table_ddl; END IF; - -- drop the last comma before ending the create statement - v_table_ddl = substr(v_table_ddl, 0, length(v_table_ddl) - 1) || E'\\n'; + -- drop the last comma before ending the create statement, which should be right before the carriage return character + -- Issue#24: make sure the comma is there before removing it + select substring(v_table_ddl, length(v_table_ddl) - 1, 1) INTO v_temp; + IF v_temp = ',' THEN + v_table_ddl = substr(v_table_ddl, 0, length(v_table_ddl) - 1) || E'\\n'; + END IF; + IF bVerbose THEN RAISE NOTICE '(3)tabledef so far: %', trim(v_table_ddl); END IF; -- --------------------------------------------------------------------------- -- at this point we have everything up to the last table-enclosing parenthesis -- --------------------------------------------------------------------------- - IF bVerbose THEN RAISE INFO '(4)tabledef so far: %', v_table_ddl; END IF; + IF bVerbose THEN RAISE NOTICE '(4)tabledef so far: %', v_table_ddl; END IF; -- See if this is an inheritance-based child table and finish up the table create. IF bPartition and bInheritance THEN @@ -458,7 +584,7 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( END IF; END IF; - IF bVerbose THEN RAISE INFO '(5)tabledef so far: %', v_table_ddl; END IF; + IF bVerbose THEN RAISE NOTICE '(5)tabledef so far: %', v_table_ddl; END IF; -- Add closing paren for regular tables -- IF NOT bPartition THEN @@ -471,19 +597,24 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( v_table_ddl := v_table_ddl || v_pkey_def || E'\\n'; END IF; - IF bVerbose THEN RAISE INFO '(6)tabledef so far: %', v_table_ddl; END IF; + -- Issue#20 + IF v_fkey_defs <> '' THEN + v_table_ddl := v_table_ddl || v_fkey_defs || E'\\n'; + END IF; + + IF bVerbose THEN RAISE NOTICE '(6)tabledef so far: %', v_table_ddl; END IF; -- create indexes FOR v_indexrec IN SELECT indexdef, COALESCE(tablespace, 'pg_default') as tablespace, indexname FROM pg_indexes WHERE (schemaname, tablename) = (in_schema, in_table) LOOP - -- RAISE INFO 'DEBUG6: indexname=%', v_indexrec.indexname; + -- RAISE NOTICE 'DEBUG6: indexname=% indexdef=%', v_indexrec.indexname, v_indexrec.indexdef; -- loop through constraints and skip ones already defined bSkip = False; FOREACH constraintelement IN ARRAY constraintarr LOOP IF constraintelement = v_indexrec.indexname THEN - -- RAISE INFO 'DEBUG7: skipping index, %', v_indexrec.indexname; + -- RAISE NOTICE 'DEBUG7: skipping index, %', v_indexrec.indexname; bSkip = True; EXIT; END IF; @@ -492,48 +623,47 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( -- Add IF NOT EXISTS clause so partition index additions will not be created if declarative partition in effect and index already created on parent v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE INDEX', 'CREATE INDEX IF NOT EXISTS'); - -- RAISE INFO 'DEBUG8: adding index, %', v_indexrec.indexname; + -- Fix Issue#26: do it for unique/primary key indexes as well + v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE UNIQUE INDEX', 'CREATE UNIQUE INDEX IF NOT EXISTS'); + -- RAISE NOTICE 'DEBUG8: adding index, %', v_indexrec.indexname; -- NOTE: cannot specify default tablespace for partitioned relations IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN v_table_ddl := v_table_ddl || v_indexrec.indexdef || ';' || E'\\n'; ELSE - v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || v_indexrec.tablespace || ';' || E'\\n'; + -- Issue#25: see if partial index or not + select CASE WHEN i.indpred IS NOT NULL THEN True ELSE False END INTO v_partial + FROM pg_index i JOIN pg_class c1 ON (i.indexrelid = c1.oid) JOIN pg_class c2 ON (i.indrelid = c2.oid) + WHERE c1.relnamespace::regnamespace::text = in_schema AND c2.relnamespace::regnamespace::text = in_schema AND c2.relname = in_table AND c1.relname = v_indexrec.indexname; + IF v_partial THEN + -- Put tablespace def before WHERE CLAUSE + v_temp = v_indexrec.indexdef; + v_pos = POSITION(' WHERE ' IN v_temp); + v_temp2 = SUBSTRING(v_temp, v_pos); + v_temp = SUBSTRING(v_temp, 1, v_pos); + v_table_ddl := v_table_ddl || v_temp || ' TABLESPACE ' || v_indexrec.tablespace || v_temp2 || ';' || E'\\n'; + ELSE + v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || v_indexrec.tablespace || ';' || E'\\n'; + END IF; END IF; END LOOP; - IF bVerbose THEN RAISE INFO '(7)tabledef so far: %', v_table_ddl; END IF; - - -- Handle external foreign key defs here if applicable. - IF fktype = 'FKEYS_EXTERNAL' THEN - -- Issue#13 fix here too for conparentid = 0. and had to change to a loop to handle multiple return set, not a select into variable syntax. - -- Also had to account for PG V10 where there is no conparentid - IF v_pgversion < 110000 THEN - FOR v_constraintrec IN - SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' as fkeydef - FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table + IF bVerbose THEN RAISE NOTICE '(7)tabledef so far: %', v_table_ddl; END IF; + + -- Issue#20: added logic for table and column comments + IF cmtcnt > 0 THEN + FOR v_rec IN + SELECT c.relname, 'COMMENT ON ' || CASE WHEN c.relkind in ('r','p') AND a.attname IS NULL THEN 'TABLE ' WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN 'COLUMN ' WHEN c.relkind = 'f' THEN 'FOREIGN TABLE ' + WHEN c.relkind = 'm' THEN 'MATERIALIZED VIEW ' WHEN c.relkind = 'v' THEN 'VIEW ' WHEN c.relkind = 'i' THEN 'INDEX ' WHEN c.relkind = 'S' THEN 'SEQUENCE ' ELSE 'XX' END || n.nspname || '.' || + CASE WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN quote_ident(c.relname) || '.' || a.attname ELSE quote_ident(c.relname) END || ' IS ' || quote_literal(d.description) || ';' as ddl + FROM pg_class c JOIN pg_namespace n ON (n.oid = c.relnamespace) LEFT JOIN pg_description d ON (c.oid = d.objoid) LEFT JOIN pg_attribute a ON (c.oid = a.attrelid AND a.attnum > 0 and a.attnum = d.objsubid) + WHERE d.description IS NOT NULL AND n.nspname = in_schema AND c.relname = in_table ORDER BY 2 desc, ddl LOOP - v_table_ddl := v_table_ddl || v_constraintrec.fkeydef || ';' || E'\\n'; - IF bVerbose THEN RAISE INFO 'keydef = %', v_constraintrec.fkeydef; END IF; - END LOOP; - ELSE - FOR v_constraintrec IN - SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' as fkeydef - FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table and r.conparentid = 0 - LOOP - v_table_ddl := v_table_ddl || v_constraintrec.fkeydef || E'\\n'; - IF bVerbose THEN RAISE INFO 'keydef = %', v_constraintrec.fkeydef; END IF; - END LOOP; - END IF; - - ELSIF fktype = 'FKEYS_COMMENTED' THEN - SELECT '-- ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' into v_fkey_defs - FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table; - IF v_fkey_defs IS NOT NULL THEN - v_table_ddl := v_table_ddl || v_fkey_defs; - END IF; + --RAISE NOTICE 'comments:%', v_rec.ddl; + v_table_ddl = v_table_ddl || v_rec.ddl || E'\\n'; + END LOOP; END IF; - IF bVerbose THEN RAISE INFO '(8)tabledef so far: %', v_table_ddl; END IF; + IF bVerbose THEN RAISE NOTICE '(8)tabledef so far: %', v_table_ddl; END IF; IF trigtype = 'INCLUDE_TRIGGERS' THEN -- Issue#14: handle multiple triggers for a table @@ -543,12 +673,21 @@ export const CREATE_PG_GET_TABLEDEF_SQL = minify( LOOP v_table_ddl := v_table_ddl || v_trigrec.triggerdef; v_table_ddl := v_table_ddl || E'\\n'; - IF bVerbose THEN RAISE INFO 'triggerdef = %', v_trigrec.triggerdef; END IF; + IF bVerbose THEN RAISE NOTICE 'triggerdef = %', v_trigrec.triggerdef; END IF; END LOOP; END IF; + IF bVerbose THEN RAISE NOTICE '(9)tabledef so far: %', v_table_ddl; END IF; -- add empty line v_table_ddl := v_table_ddl || E'\\n'; + IF bVerbose THEN RAISE NOTICE '(10)tabledef so far: %', v_table_ddl; END IF; + + -- reset search_path back to what it was + IF search_path_old = '' THEN + SELECT set_config('search_path', '', false) into v_temp; + ELSE + EXECUTE 'SET search_path = ' || search_path_old; + END IF; RETURN v_table_ddl; diff --git a/playwright-tests/tests/snapshot/spec/table-editor.spec.ts b/playwright-tests/tests/snapshot/spec/table-editor.spec.ts index 994784a3b0e..046e4b7f1a4 100644 --- a/playwright-tests/tests/snapshot/spec/table-editor.spec.ts +++ b/playwright-tests/tests/snapshot/spec/table-editor.spec.ts @@ -49,7 +49,7 @@ test.describe('Table Editor page', () => { // view its definition await page.getByText('definition').click() await expect(page.locator('div.view-lines')).toContainText( - `CREATE TABLE public.${tableName} ( id bigint GENERATED BY DEFAULT AS IDENTITY , created_at timestamp with time zone NULL DEFAULT now(), \"defaultValueColumn\" smallint NULL DEFAULT '2'::smallint, CONSTRAINT ${tableName}_pkey PRIMARY KEY (id)) TABLESPACE pg_default;` + `CREATE TABLE public.${tableName} ( id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, created_at timestamp with time zone NULL DEFAULT now(), \"defaultValueColumn\" smallint NULL DEFAULT '2'::smallint, CONSTRAINT ${tableName}_pkey PRIMARY KEY (id)) TABLESPACE pg_default;` ) // add a new row From 70ef8ecc6f40d99d3441b2720ca8b55cb8926a97 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 30 Jul 2024 21:04:15 +0800 Subject: [PATCH 10/13] fix: sign up redirect (#28279) --- apps/studio/components/layouts/SignInLayout/SignInLayout.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx b/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx index cf65385a68b..056ecd46d17 100644 --- a/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx +++ b/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx @@ -63,11 +63,6 @@ const SignInLayout = ({ await queryClient.resetQueries() router.push(getReturnToPath()) - } else { - // if the user doesn't have a token, he needs to go back to the sign-in page - const redirectTo = buildPathWithParams('/sign-in') - router.replace(redirectTo) - return } }) .catch(() => {}) // catch all errors thrown by auth methods From 6cc83d985764ebfb0177b7a6dadd19e52aca3501 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Tue, 30 Jul 2024 21:25:05 +0800 Subject: [PATCH 11/13] [www] Chore: logos added (#28258) * logos added * Update logos.tsx * Update package-lock.json * Update logos.tsx * Update logos.tsx * Update logos.tsx * Update logos.tsx * Update logos.tsx * replace --- apps/www/components/Hero/Hero.tsx | 3 - apps/www/components/Hero/HeroFrameworks.tsx | 4 +- apps/www/components/logos.tsx | 100 ++++++++++++++++++ apps/www/pages/index.tsx | 4 + .../images/logos/publicity/1password.svg | 12 +++ .../images/logos/publicity/betashares.svg | 11 ++ .../images/logos/publicity/chatbase.svg | 11 ++ .../public/images/logos/publicity/gopuff.svg | 10 ++ .../public/images/logos/publicity/humata.svg | 11 ++ .../public/images/logos/publicity/krea.svg | 10 ++ .../images/logos/publicity/langchain.svg | 21 ++++ .../public/images/logos/publicity/loops.svg | 4 + .../public/images/logos/publicity/mobbin.svg | 4 + .../public/images/logos/publicity/mozilla.svg | 3 + .../public/images/logos/publicity/pika.svg | 3 + .../www/public/images/logos/publicity/pwc.svg | 30 ++++++ .../public/images/logos/publicity/resend.svg | 15 +++ .../public/images/logos/publicity/udio.svg | 7 ++ 18 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 apps/www/components/logos.tsx create mode 100644 apps/www/public/images/logos/publicity/1password.svg create mode 100644 apps/www/public/images/logos/publicity/betashares.svg create mode 100644 apps/www/public/images/logos/publicity/chatbase.svg create mode 100644 apps/www/public/images/logos/publicity/gopuff.svg create mode 100644 apps/www/public/images/logos/publicity/humata.svg create mode 100644 apps/www/public/images/logos/publicity/krea.svg create mode 100644 apps/www/public/images/logos/publicity/langchain.svg create mode 100644 apps/www/public/images/logos/publicity/loops.svg create mode 100644 apps/www/public/images/logos/publicity/mobbin.svg create mode 100644 apps/www/public/images/logos/publicity/mozilla.svg create mode 100644 apps/www/public/images/logos/publicity/pika.svg create mode 100644 apps/www/public/images/logos/publicity/pwc.svg create mode 100644 apps/www/public/images/logos/publicity/resend.svg create mode 100644 apps/www/public/images/logos/publicity/udio.svg diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index e7bdbfe1d26..4aeb48315bc 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -5,7 +5,6 @@ import { useTelemetryProps } from 'common/hooks/useTelemetryProps' import gaEvents from '~/lib/gaEvents' import { Button, IconBookOpen } from 'ui' import SectionContainer from '~/components/Layouts/SectionContainer' -import HeroFrameworks from './HeroFrameworks' const Hero = () => { const router = useRouter() @@ -57,8 +56,6 @@ const Hero = () => { - - diff --git a/apps/www/components/Hero/HeroFrameworks.tsx b/apps/www/components/Hero/HeroFrameworks.tsx index f6940e84687..f58ce06848e 100644 --- a/apps/www/components/Hero/HeroFrameworks.tsx +++ b/apps/www/components/Hero/HeroFrameworks.tsx @@ -63,8 +63,8 @@ const HeroFrameworks = ({ className }: { className?: string }) => { return (
- Works seamlessly with 20+ frameworks -
+

Works seamlessly with 20+ frameworks

+
{frameworks.map((framework) => ( +
+
+ {logos.map((logo) => ( +
+ {logo.alt} +
+ ))} +
+
+
+ ) +} diff --git a/apps/www/pages/index.tsx b/apps/www/pages/index.tsx index 090afb8ff7f..8c694c43bca 100644 --- a/apps/www/pages/index.tsx +++ b/apps/www/pages/index.tsx @@ -2,8 +2,10 @@ import dynamic from 'next/dynamic' import content from '~/data/home/content' import Layout from '~/components/Layouts/Default' import Hero from '~/components/Hero/Hero' +import HeroFrameworks from '~/components/Hero/HeroFrameworks' const Products = dynamic(() => import('~/components/Products/index')) +const Logos = dynamic(() => import('~/components/logos')) const BuiltExamples = dynamic(() => import('components/BuiltWithSupabase/index')) const MadeForDevelopers = dynamic(() => import('components/MadeForDevelopers/index')) const AdminAccess = dynamic(() => import('components/AdminAccess/index')) @@ -16,7 +18,9 @@ const Index = () => { return ( + + diff --git a/apps/www/public/images/logos/publicity/1password.svg b/apps/www/public/images/logos/publicity/1password.svg new file mode 100644 index 00000000000..ea7109bbe11 --- /dev/null +++ b/apps/www/public/images/logos/publicity/1password.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/betashares.svg b/apps/www/public/images/logos/publicity/betashares.svg new file mode 100644 index 00000000000..d6f7ece4bb6 --- /dev/null +++ b/apps/www/public/images/logos/publicity/betashares.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/chatbase.svg b/apps/www/public/images/logos/publicity/chatbase.svg new file mode 100644 index 00000000000..42fca2edd72 --- /dev/null +++ b/apps/www/public/images/logos/publicity/chatbase.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/gopuff.svg b/apps/www/public/images/logos/publicity/gopuff.svg new file mode 100644 index 00000000000..13c48199fb8 --- /dev/null +++ b/apps/www/public/images/logos/publicity/gopuff.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/humata.svg b/apps/www/public/images/logos/publicity/humata.svg new file mode 100644 index 00000000000..5984a1dc846 --- /dev/null +++ b/apps/www/public/images/logos/publicity/humata.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/krea.svg b/apps/www/public/images/logos/publicity/krea.svg new file mode 100644 index 00000000000..d4e0489b0f3 --- /dev/null +++ b/apps/www/public/images/logos/publicity/krea.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/langchain.svg b/apps/www/public/images/logos/publicity/langchain.svg new file mode 100644 index 00000000000..9f11a7c0442 --- /dev/null +++ b/apps/www/public/images/logos/publicity/langchain.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/loops.svg b/apps/www/public/images/logos/publicity/loops.svg new file mode 100644 index 00000000000..0cd43772b3e --- /dev/null +++ b/apps/www/public/images/logos/publicity/loops.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/www/public/images/logos/publicity/mobbin.svg b/apps/www/public/images/logos/publicity/mobbin.svg new file mode 100644 index 00000000000..87502f4dec6 --- /dev/null +++ b/apps/www/public/images/logos/publicity/mobbin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/www/public/images/logos/publicity/mozilla.svg b/apps/www/public/images/logos/publicity/mozilla.svg new file mode 100644 index 00000000000..44e1bf59751 --- /dev/null +++ b/apps/www/public/images/logos/publicity/mozilla.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/www/public/images/logos/publicity/pika.svg b/apps/www/public/images/logos/publicity/pika.svg new file mode 100644 index 00000000000..dcaf1ec21c5 --- /dev/null +++ b/apps/www/public/images/logos/publicity/pika.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/www/public/images/logos/publicity/pwc.svg b/apps/www/public/images/logos/publicity/pwc.svg new file mode 100644 index 00000000000..2a670b51caf --- /dev/null +++ b/apps/www/public/images/logos/publicity/pwc.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/resend.svg b/apps/www/public/images/logos/publicity/resend.svg new file mode 100644 index 00000000000..2d2d982fb1e --- /dev/null +++ b/apps/www/public/images/logos/publicity/resend.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/www/public/images/logos/publicity/udio.svg b/apps/www/public/images/logos/publicity/udio.svg new file mode 100644 index 00000000000..6e597fd12f9 --- /dev/null +++ b/apps/www/public/images/logos/publicity/udio.svg @@ -0,0 +1,7 @@ + + + + + + + From 6d760a1a995a042ae7b8b8e809da20228b65b70a Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Tue, 30 Jul 2024 21:34:28 +0800 Subject: [PATCH 12/13] Feat/deep dark (#27026) * chore: move to deeper dark * tidy up boxes on www * update deep-dark colors * fix homepage product cards * toned down pricing page a little * update * lots of color changes * Update grid.scss * Update EntityListItem.tsx * Update EntityListItem.tsx * fix partners page icon * fix partners page colors * update theme * Update global.css * combined dark theme so classic-dark does not break on other sites * Update package-lock.json * updated color tokens pipeline to support combined dark theme * smol updates * Update FeaturedThumb.tsx * Update BlogFilters.tsx * Update BlogListItem.tsx * Update BlogListItem.tsx * Update UserSettingsDropdown.tsx * Update NavigationBar.tsx --------- Co-authored-by: Francesco Sansalvadore --- apps/design-system/styles/globals.css | 2 +- apps/docs/styles/main.scss | 3 +- .../grid/components/header/Header.tsx | 2 +- .../header/filter/FilterPopover.tsx | 5 +- .../components/header/sort/SortPopover.tsx | 5 +- .../Account/Preferences/ThemeSettings.tsx | 2 +- .../Realtime/Inspector/MessageSelection.tsx | 4 +- .../Realtime/Inspector/MessagesTable.tsx | 2 +- .../RoleImpersonationPopover.tsx | 6 +- .../RoleImpersonationRadio.tsx | 9 +- .../layouts/AccountLayout/WithSidebar.tsx | 2 +- .../layouts/AppLayout/BranchDropdown.tsx | 11 +- .../AppLayout/OrganizationDropdown.tsx | 10 +- .../layouts/AppLayout/ProjectDropdown.tsx | 9 +- .../AppLayout/UserSettingsDropdown.tsx | 14 +- .../LayoutHeader/LayoutHeader.tsx | 2 +- .../NavigationBar/NavigationBar.tsx | 20 +-- .../NavigationBar/NavigationIconButton.tsx | 2 +- .../NavigationBar/NavigationIconLink.tsx | 9 +- .../layouts/ProjectLayout/ProductMenuBar.tsx | 2 +- .../TableEditorLayout/EntityListItem.tsx | 20 ++- apps/studio/components/ui/Panel.tsx | 4 +- .../studio/public/img/themes/classic-dark.svg | 39 +++++ apps/studio/public/img/themes/dark.svg | 64 +++---- apps/studio/public/img/themes/deep-dark.svg | 39 ----- apps/studio/styles/grid.scss | 34 ++-- apps/studio/styles/main.scss | 2 +- apps/www/components/Blog/BlogFilters.tsx | 5 +- apps/www/components/Blog/BlogGridItem.tsx | 2 +- apps/www/components/Blog/BlogListItem.tsx | 6 +- apps/www/components/Blog/FeaturedThumb.tsx | 2 +- apps/www/components/CTABanner/index.tsx | 6 +- apps/www/components/ExampleCard.tsx | 4 +- .../www/components/Nav/DevelopersDropdown.tsx | 2 +- apps/www/components/Nav/ProductDropdown.tsx | 2 +- apps/www/components/Panel/Panel.tsx | 4 +- apps/www/components/Pricing/PricingPlans.tsx | 10 +- .../Products/Functions/NpmEcosystem.tsx | 2 +- .../Products/Functions/QueryLogs.tsx | 2 +- .../Products/Functions/RealtimeLogs.tsx | 2 +- apps/www/components/Products/ProductCard.tsx | 6 +- .../components/Products/RealtimeVisual.tsx | 3 - apps/www/data/partners/index.tsx | 4 +- apps/www/pages/index.tsx | 2 +- apps/www/pages/partners/index.tsx | 22 ++- .../www/public/images/index/products/auth.svg | 77 ++++----- .../images/index/products/realtime-bg.svg | 88 +++++----- apps/www/styles/index.css | 5 +- .../with-react-query-nextjs-14/app/page.tsx | 2 +- .../nextjs-todo-list/components/TodoList.tsx | 2 +- .../nextjs-todo-list/pages/index.tsx | 2 +- .../todo-list/nextjs-todo-list/styles/app.css | 150 ++++++++++++++--- .../sveltejs-todo-list/src/App.svelte | 2 +- .../sveltejs-todo-list/src/assets/app.css | 156 +++++++++++++++--- .../sveltejs-todo-list/src/lib/Todo.svelte | 2 +- packages/ui-patterns/GlassPanel/index.tsx | 10 +- packages/ui-patterns/TweetCard/index.tsx | 2 +- packages/ui/build/css/themes/classic-dark.css | 65 ++++++++ packages/ui/build/css/themes/concept-two.css | 65 ++++++++ .../{deep-dark.css => dark-combined.css} | 14 +- packages/ui/build/css/themes/dark.css | 82 ++++----- packages/ui/build/css/themes/light.css | 2 + packages/ui/build/css/tw-extend/color.js | 14 +- .../internals/tokens/extract-design-tokens.js | 7 + .../components/ThemeProvider/singleThemes.ts | 2 +- .../src/components/Toggle/Toggle.module.css | 2 +- .../ui/src/components/shadcn/ui/alert.tsx | 2 +- .../ui/src/components/shadcn/ui/dialog.tsx | 2 +- packages/ui/src/lib/tailwind-demo-classes.ts | 4 +- packages/ui/src/lib/theme/defaultTheme.ts | 18 +- packages/ui/transformTokens.js | 20 ++- 71 files changed, 788 insertions(+), 418 deletions(-) create mode 100644 apps/studio/public/img/themes/classic-dark.svg delete mode 100644 apps/studio/public/img/themes/deep-dark.svg create mode 100644 packages/ui/build/css/themes/classic-dark.css create mode 100644 packages/ui/build/css/themes/concept-two.css rename packages/ui/build/css/themes/{deep-dark.css => dark-combined.css} (89%) diff --git a/apps/design-system/styles/globals.css b/apps/design-system/styles/globals.css index 9ef3d8f9229..6e015819cfb 100644 --- a/apps/design-system/styles/globals.css +++ b/apps/design-system/styles/globals.css @@ -4,7 +4,7 @@ @import './../../../packages/ui/build/css/source/global.css'; @import './../../../packages/ui/build/css/themes/dark.css'; -@import './../../../packages/ui/build/css/themes/deep-dark.css'; +@import './../../../packages/ui/build/css/themes/classic-dark.css'; @import './../../../packages/ui/build/css/themes/light.css'; /* @layer base { diff --git a/apps/docs/styles/main.scss b/apps/docs/styles/main.scss index 2ea5486c6ee..e1dde0f01ec 100644 --- a/apps/docs/styles/main.scss +++ b/apps/docs/styles/main.scss @@ -3,8 +3,7 @@ @tailwind utilities; @import './../../../packages/ui/build/css/source/global.css'; -@import './../../../packages/ui/build/css/themes/dark.css'; -@import './../../../packages/ui/build/css/themes/deep-dark.css'; +@import './../../../packages/ui/build/css/themes/dark-combined.css'; @import './../../../packages/ui/build/css/themes/light.css'; @font-face { diff --git a/apps/studio/components/grid/components/header/Header.tsx b/apps/studio/components/grid/components/header/Header.tsx index 77a12051692..8e6a88a3c93 100644 --- a/apps/studio/components/grid/components/header/Header.tsx +++ b/apps/studio/components/grid/components/header/Header.tsx @@ -61,7 +61,7 @@ const Header = ({ return (
-
+
{customHeader ? ( <>{customHeader} ) : ( diff --git a/apps/studio/components/grid/components/header/filter/FilterPopover.tsx b/apps/studio/components/grid/components/header/filter/FilterPopover.tsx index 7b87e65d27c..b83a6ef54ae 100644 --- a/apps/studio/components/grid/components/header/filter/FilterPopover.tsx +++ b/apps/studio/components/grid/components/header/filter/FilterPopover.tsx @@ -51,10 +51,7 @@ const FilterPopover = ({ table, filters, setParams }: FilterPopoverProps) => { return ( - diff --git a/apps/studio/components/grid/components/header/sort/SortPopover.tsx b/apps/studio/components/grid/components/header/sort/SortPopover.tsx index b73860bfa6e..39e1811aa51 100644 --- a/apps/studio/components/grid/components/header/sort/SortPopover.tsx +++ b/apps/studio/components/grid/components/header/sort/SortPopover.tsx @@ -42,10 +42,7 @@ const SortPopover = ({ table, sorts, setParams }: SortPopoverProps) => { return ( - diff --git a/apps/studio/components/interfaces/Account/Preferences/ThemeSettings.tsx b/apps/studio/components/interfaces/Account/Preferences/ThemeSettings.tsx index 9e4e9676728..e90964a05fd 100644 --- a/apps/studio/components/interfaces/Account/Preferences/ThemeSettings.tsx +++ b/apps/studio/components/interfaces/Account/Preferences/ThemeSettings.tsx @@ -41,7 +41,7 @@ const ThemeSettings = () => { > {singleThemes.map((theme: Theme) => ( - + ))} diff --git a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx index db121fd1bb3..23dc53820e8 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx @@ -24,12 +24,12 @@ const MessageSelection = ({ log, onClose }: MessageSelectionProps) => { return (
diff --git a/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx b/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx index 11e493aebf4..976980787da 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx @@ -187,7 +187,7 @@ const MessagesTable = ({ 'font-mono tracking-tight', isEqual(row, focusedLog) ? 'bg-scale-800 rdg-row--focused' - : ' bg-scale-200 hover:bg-scale-300 cursor-pointer', + : ' bg-200 hover:bg-scale-300 cursor-pointer', isErrorLog(row) && '!bg-warning-300', ]) }} diff --git a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx index 216963c7a3d..2cdf1ec43a6 100644 --- a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx +++ b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover.tsx @@ -47,11 +47,7 @@ const RoleImpersonationPopover = ({
- + diff --git a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationRadio.tsx b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationRadio.tsx index 25895706f72..49a9afaac34 100644 --- a/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationRadio.tsx +++ b/apps/studio/components/interfaces/RoleImpersonationSelector/RoleImpersonationRadio.tsx @@ -1,4 +1,5 @@ -import { IconCheck, IconMinus, cn } from 'ui' +import { Check, Minus } from 'lucide-react' +import { cn } from 'ui' export interface RoleImpersonationRadioProps { label?: string @@ -19,7 +20,7 @@ function RoleImpersonationRadio({