) : (
diff --git a/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
index a601e79481669..3d984779e4e50 100644
--- a/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
+++ b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx
@@ -181,9 +181,7 @@ const EdgeFunctionDetails = () => {
Import maps are{' '}
-
+
{hasImportMap ? 'used' : 'not used'}
{' '}
for this function
diff --git a/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx
index bc2ae90243257..e8ec20799909f 100644
--- a/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx
+++ b/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx
@@ -10,7 +10,7 @@ export const generateCLICommands = (
jsx: () => {
return (
<>
- supabase functions deploy{' '}
+ supabase functions deploy{' '}
{selectedFunction?.slug}
>
)
@@ -23,7 +23,7 @@ export const generateCLICommands = (
jsx: () => {
return (
<>
- supabase functions delete{' '}
+ supabase functions delete{' '}
{selectedFunction?.slug}
>
)
@@ -39,7 +39,7 @@ export const generateCLICommands = (
jsx: () => {
return (
<>
- supabase secrets list
+ supabase secrets list
>
)
},
@@ -51,7 +51,7 @@ export const generateCLICommands = (
jsx: () => {
return (
<>
- supabase secrets set NAME1=VALUE1 NAME2=VALUE2
+ supabase secrets set NAME1=VALUE1 NAME2=VALUE2
>
)
},
@@ -63,7 +63,7 @@ export const generateCLICommands = (
jsx: () => {
return (
<>
- supabase secrets unset NAME1 NAME2
+ supabase secrets unset NAME1 NAME2
>
)
},
@@ -80,7 +80,7 @@ export const generateCLICommands = (
jsx: () => {
return (
<>
- curl -L -X POST '{functionUrl}' -H
+ curl -L -X POST '{functionUrl}' -H
'Authorization: Bearer [YOUR ANON KEY]' {`--data '{"name":"Functions"}'`}
>
)
diff --git a/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
index 4237781074c87..e968c6e115e01 100644
--- a/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
+++ b/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
@@ -55,7 +55,7 @@ const EdgeFunctionsListItem: FC = ({ function: item }) => {
}}
>
{isCopied ? (
-
+
) : (
diff --git a/studio/components/interfaces/Functions/TerminalInstructions.tsx b/studio/components/interfaces/Functions/TerminalInstructions.tsx
index 3862be68f513f..71cd7cb22e1bb 100644
--- a/studio/components/interfaces/Functions/TerminalInstructions.tsx
+++ b/studio/components/interfaces/Functions/TerminalInstructions.tsx
@@ -39,7 +39,7 @@ const TerminalInstructions: FC
= ({ closable = false, removeBorder = fals
jsx: () => {
return (
<>
- supabase functions new hello-world
+ supabase functions new hello-world
>
)
},
@@ -51,7 +51,7 @@ const TerminalInstructions: FC = ({ closable = false, removeBorder = fals
jsx: () => {
return (
<>
- supabase functions deploy hello-world
+ supabase functions deploy hello-world
--project-ref {projectRef}
>
)
@@ -66,7 +66,7 @@ const TerminalInstructions: FC = ({ closable = false, removeBorder = fals
jsx: () => {
return (
<>
- curl -L -X POST 'https://{functionsEndpoint}
+ curl -L -X POST 'https://{functionsEndpoint}
/hello-world' -H 'Authorization: Bearer [YOUR ANON KEY]'{' '}
{`--data '{"name":"Functions"}'`}
>
diff --git a/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx b/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx
index 815ac8cec05a2..c0847e3788d6e 100644
--- a/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx
+++ b/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx
@@ -139,7 +139,7 @@ const APIKeys = () => {
for your tables and configured policies. You may also use the service key which
can be found{' '}
- here
+ here
{' '}
to bypass RLS.
diff --git a/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx b/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx
index 090b720fb29b0..d8a76c8ef5415 100644
--- a/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx
+++ b/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx
@@ -242,7 +242,7 @@ const NewProjectPanel: FC = ({}) => {
Interact with your database through the{' '}
- Supabase client libraries
+ Supabase client libraries
{' '}
with your API keys.
diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
index ad3689be4f908..0b1801f51a2d0 100644
--- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
+++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
@@ -1,20 +1,40 @@
-import { Badge, IconAlertTriangle, IconLoader, IconPauseCircle } from 'ui'
+import {
+ Badge,
+ IconAlertTriangle,
+ IconGitBranch,
+ IconGitHub,
+ IconLoader,
+ IconPauseCircle,
+ IconTriangle,
+} from 'ui'
import CardButton from 'components/ui/CardButton'
import { useProjectReadOnlyStatus } from 'hooks/misc/useProjectReadOnlyStatus'
-import { PROJECT_STATUS } from 'lib/constants'
+import { BASE_PATH, PROJECT_STATUS } from 'lib/constants'
import { Project } from 'types'
+import { IntegrationProjectConnection } from 'data/integrations/integrations.types'
export interface ProjectCardProps {
project: Project
rewriteHref?: string
+ githubIntegration?: IntegrationProjectConnection
+ vercelIntegration?: IntegrationProjectConnection
}
-const ProjectCard = ({ project, rewriteHref }: ProjectCardProps) => {
+const ProjectCard = ({
+ project,
+ rewriteHref,
+ githubIntegration,
+ vercelIntegration,
+}: ProjectCardProps) => {
const { name, ref: projectRef } = project
const desc = `${project.cloud_provider} | ${project.region}`
const isReadonly = useProjectReadOnlyStatus(projectRef)
+ const isBranchingEnabled = project.preview_branch_refs.length > 0
+ const isGithubIntegrated = githubIntegration !== undefined
+ const isVercelIntegrated = vercelIntegration !== undefined
+ const githubRepository = githubIntegration?.metadata.name ?? undefined
// Project status should supersede is read only status
const isHealthy = project.status === PROJECT_STATUS.ACTIVE_HEALTHY
@@ -28,8 +48,32 @@ const ProjectCard = ({ project, rewriteHref }: ProjectCardProps) => {
- {name}
+
+
{name}
+
+ {isVercelIntegrated && (
+
+
+
+ )}
+ {isBranchingEnabled && (
+
+
+
+ )}
+ {isGithubIntegrated && (
+ <>
+
+
+
+
{githubRepository}
+ >
+ )}
+
}
footer={
diff --git a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx
index 1868b9c2689f6..d24aa81b1d02b 100644
--- a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx
+++ b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx
@@ -18,6 +18,7 @@ import { makeRandomString } from 'lib/helpers'
import { Organization, Project, ResponseError } from 'types'
import ProjectCard from './ProjectCard'
import ShimmeringCard from './ShimmeringCard'
+import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
export interface ProjectListProps {
rewriteHref?: (projectRef: string) => string
diff --git a/studio/components/interfaces/Integrations/IntegrationPanels.tsx b/studio/components/interfaces/Integrations/IntegrationPanels.tsx
index ad89a42d0ab6a..26028c0a79f10 100644
--- a/studio/components/interfaces/Integrations/IntegrationPanels.tsx
+++ b/studio/components/interfaces/Integrations/IntegrationPanels.tsx
@@ -6,8 +6,10 @@ import { Markdown } from 'components/interfaces/Markdown'
import { Integration, IntegrationProjectConnection } from 'data/integrations/integrations.types'
import { useProjectsQuery } from 'data/projects/projects-query'
import { BASE_PATH } from 'lib/constants'
-import { getVercelConfigurationUrl } from 'lib/integration-utils'
import { Badge, Button, IconArrowRight, IconExternalLink, IconGitHub, IconSquare, cn } from 'ui'
+import { getIntegrationConfigurationUrl } from 'lib/integration-utils'
+import Link from 'next/link'
+import { BooleanFormatter } from 'components/grid/components/formatter'
const ICON_STROKE_WIDTH = 2
const ICON_SIZE = 14
@@ -24,9 +26,9 @@ const HandleIcon = ({ type, className }: { type: HandleIconType; className?: str
case 'GitHub':
return
break
- case 'Netlify':
- return
- break
+ // case 'Netlify':
+ // return
+ // break
case 'Vercel':
return (
- }>
- {/* hard coded to vercel for now, TODO: move to a prop */}
-
- Manage
-
-
+
+ }>
+ Manage
+
+
)
}
@@ -130,10 +134,15 @@ export interface IntegrationConnectionProps extends React.HTMLAttributes(
- ({ className, connection, type, actions, ...props }, ref) => {
+ (
+ { className, connection, type, actions, showNode = true, orientation = 'horizontal', ...props },
+ ref
+ ) => {
const { data: projects } = useProjectsQuery()
const project = projects?.find((project) => project.ref === connection.supabase_project_ref)
@@ -142,18 +151,31 @@ const IntegrationConnection = React.forwardRef
-
-
-
+ {showNode && (
+
+ )}
+
+
-
{project?.name}
+
{project?.name}
{!connection?.metadata?.framework ? (
-
+
) : (
)}
-
{connection.metadata?.name}
+
{connection.metadata?.name}
-
+
Connected {dayjs(connection?.inserted_at).fromNow()}
-
+
Added by {connection?.added_by?.primary_email}
@@ -206,7 +228,7 @@ const IntegrationConnectionOption = React.forwardRef
{connection.metadata.name}
-
+
Connected {dayjs(connection.inserted_at).fromNow()}
@@ -219,15 +241,23 @@ const IntegrationConnectionOption = React.forwardRef
->(({ className, ...props }, ref) => {
+ React.HTMLAttributes & { showNode?: boolean }
+>(({ className, showNode = true, ...props }, ref) => {
return (
-
+ {showNode && (
+
+ )}
void
+}
+
+const OrganizationPicker = ({
+ integrationName,
+ configurationId,
+ selectedOrg,
+ onSelectedOrgChange,
+}: OrganizationPickerProps) => {
+ const [open, setOpen] = useState(false)
+ const ref = useRef
(null)
+
+ const { data: integrationData } = useIntegrationsQuery()
+ const { data: organizationsData, isLoading: isLoadingOrganization } = useOrganizationsQuery()
+
+ const installed = useMemo(
+ () =>
+ integrationData && organizationsData
+ ? getHasInstalledObject({
+ integrationName,
+ integrationData,
+ organizationsData,
+ installationId: configurationId,
+ })
+ : {},
+ [configurationId, integrationData, integrationName, organizationsData]
+ )
+
+ return (
+ <>
+
+
+ }
+ loading={isLoadingOrganization}
+ disabled={isLoadingOrganization}
+ iconRight={
+
+
+
+ }
+ >
+
+ {selectedOrg?.name}
+ {selectedOrg && configurationId && installed[selectedOrg.slug] && (
+ Integration Installed
+ )}
+
+
+
+
+
+
+
+ No results found.
+
+ {organizationsData?.map((org) => {
+ return (
+ {
+ const org = organizationsData?.find(
+ (org) => org.slug.toLowerCase() === slug.toLowerCase()
+ )
+ if (org) {
+ onSelectedOrgChange(org)
+ }
+
+ setOpen(false)
+ }}
+ >
+
+ {org.name}{' '}
+ {configurationId && installed[org.slug] && (
+
+ Integration Installed
+
+ )}
+
+ )
+ })}
+
+
+
+
+
+ >
+ )
+}
+
+export default OrganizationPicker
diff --git a/studio/components/interfaces/Integrations/ProjectLinker.tsx b/studio/components/interfaces/Integrations/ProjectLinker.tsx
index e220c54f1d291..2bebe5af4b789 100644
--- a/studio/components/interfaces/Integrations/ProjectLinker.tsx
+++ b/studio/components/interfaces/Integrations/ProjectLinker.tsx
@@ -1,13 +1,9 @@
-import { ENV_VAR_RAW_KEYS } from 'components/interfaces/Integrations/Integrations-Vercel.constants'
-import { Markdown } from 'components/interfaces/Markdown'
-import { vercelIcon } from 'components/to-be-cleaned/ListIcons'
-import { useIntegrationConnectionsCreateMutation } from 'data/integrations/integration-connections-create-mutation'
-import { useIntegrationsVercelConnectionSyncEnvsMutation } from 'data/integrations/integrations-vercel-connection-sync-envs-mutation'
-import { VercelProjectsResponse } from 'data/integrations/integrations-vercel-projects-query'
+import { ReactNode, useRef, useState } from 'react'
+
import { IntegrationProjectConnection } from 'data/integrations/integrations.types'
+import { IntegrationConnectionsCreateVariables } from 'data/integrations/types'
import { useSelectedOrganization } from 'hooks'
import { BASE_PATH } from 'lib/constants'
-import { useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import {
Button,
@@ -24,22 +20,33 @@ import {
cn,
} from 'ui'
-interface Project {
+export interface Project {
id: string
name: string
ref: string
}
+export interface ForeignProject {
+ id: string
+ name: string
+}
+
export interface ProjectLinkerProps {
organizationIntegrationId: string | undefined
- foreignProjects: VercelProjectsResponse[]
+ foreignProjects: ForeignProject[]
supabaseProjects: Project[]
- onCreateConnections?: () => void
+ onCreateConnections: (variables: IntegrationConnectionsCreateVariables) => void
installedConnections: IntegrationProjectConnection[] | undefined
- setLoading?: (x: boolean) => void
- showSkip?: boolean
+ isLoading?: boolean
+ integrationIcon: ReactNode
+ getForeignProjectIcon?: (project: ForeignProject) => ReactNode
+ choosePrompt?: string
+ onSkip?: () => void
loadingForeignProjects?: boolean
loadingSupabaseProjects?: boolean
+
+ defaultSupabaseProjectRef?: string
+ defaultForeignProjectId?: string
}
const ProjectLinker = ({
@@ -48,77 +55,63 @@ const ProjectLinker = ({
supabaseProjects,
onCreateConnections: _onCreateConnections,
installedConnections = [],
- setLoading,
- showSkip = false,
+ isLoading,
+ integrationIcon,
+ getForeignProjectIcon,
+ choosePrompt = 'Choose a project',
+ onSkip,
loadingForeignProjects,
loadingSupabaseProjects,
+
+ defaultSupabaseProjectRef,
+ defaultForeignProjectId,
}: ProjectLinkerProps) => {
const [supabaseProjectsComboBoxOpen, setSupabaseProjectsComboboxOpen] = useState(false)
- const [vercelProjectsComboBoxOpen, setVercelProjectsComboboxOpen] = useState(false)
+ const [foreignProjectsComboBoxOpen, setForeignProjectsComboboxOpen] = useState(false)
const supabaseProjectsComboBoxRef = useRef(null)
- const vercelProjectsComboBoxRef = useRef(null)
+ const foreignProjectsComboBoxRef = useRef(null)
const selectedOrganization = useSelectedOrganization()
- const [supabaseProjectRef, setSupabaseProjectRef] = useState(undefined)
- const [vercelProjectId, setVercelProjectId] = useState(undefined)
-
- const { mutateAsync: syncEnvs } = useIntegrationsVercelConnectionSyncEnvsMutation()
- const { mutate: createConnections, isLoading } = useIntegrationConnectionsCreateMutation({
- async onSuccess({ id }) {
- try {
- await syncEnvs({ connectionId: id })
- } catch (error: any) {
- toast.error('Failed to sync environment variables: ', error.message)
- }
-
- if (setLoading) setLoading(false)
- _onCreateConnections?.()
- },
- onError() {
- if (setLoading) setLoading(false)
- },
- })
+ const [supabaseProjectRef, setSupabaseProjectRef] = useState(
+ defaultSupabaseProjectRef
+ )
+ const [foreignProjectId, setForeignProjectId] = useState(
+ defaultForeignProjectId
+ )
// create a flat array of foreign project ids. ie, ["prj_MlkO6AiLG5ofS9ojKrkS3PhhlY3f", ..]
const flatInstalledConnectionsIds = new Set(installedConnections.map((x) => x.foreign_project_id))
- // check that vercel project is not already installed
- const filteredForeignProjects: VercelProjectsResponse[] = foreignProjects.filter(
- (foreignProject) => {
- return !flatInstalledConnectionsIds.has(foreignProject.id)
- }
- )
-
const selectedSupabaseProject = supabaseProjectRef
? supabaseProjects.find((x) => x.ref?.toLowerCase() === supabaseProjectRef?.toLowerCase())
: undefined
- const selectedVercelProject = vercelProjectId
- ? filteredForeignProjects.find((x) => x.id?.toLowerCase() === vercelProjectId?.toLowerCase())
+ const selectedForeignProject = foreignProjectId
+ ? foreignProjects.find((x) => x.id?.toLowerCase() === foreignProjectId?.toLowerCase())
: undefined
function onCreateConnections() {
- const projectDetails = selectedVercelProject
+ const projectDetails = selectedForeignProject
if (!organizationIntegrationId) return console.error('No integration ID set')
- if (!selectedVercelProject?.id) return console.error('No Vercel project ID set')
+ if (!selectedForeignProject?.id) return console.error('No Foreign project ID set')
if (!selectedSupabaseProject?.ref) return console.error('No Supabase project ref set')
- if (setLoading) setLoading(true)
+ const alreadyInstalled = flatInstalledConnectionsIds.has(foreignProjectId ?? '')
+ if (alreadyInstalled) {
+ return toast.error(
+ `Unable to connect to ${selectedForeignProject.name}: Selected repository already has an installed connection to a project`
+ )
+ }
- createConnections({
+ _onCreateConnections({
organizationIntegrationId,
connection: {
- foreign_project_id: selectedVercelProject?.id,
+ foreign_project_id: selectedForeignProject?.id,
supabase_project_ref: selectedSupabaseProject?.ref,
metadata: {
...projectDetails,
- supabaseConfig: {
- projectEnvVars: {
- write: true,
- },
- },
},
},
orgSlug: selectedOrganization?.slug,
@@ -162,7 +155,7 @@ const ProjectLinker = ({
type="default"
size="medium"
block
- disabled={loadingSupabaseProjects}
+ disabled={defaultSupabaseProjectRef !== undefined || loadingSupabaseProjects}
loading={loadingSupabaseProjects}
className="justify-start"
icon={
@@ -175,9 +168,11 @@ const ProjectLinker = ({
}
iconRight={
-
-
-
+ defaultSupabaseProjectRef === undefined ? (
+
+
+
+ ) : null
}
>
{selectedSupabaseProject ? selectedSupabaseProject.name : 'Choose Project'}
@@ -194,14 +189,14 @@ const ProjectLinker = ({
No results found.
- {supabaseProjects.map((project) => {
+ {supabaseProjects.map((project, i) => {
return (
{
- if (ref) setSupabaseProjectRef(ref)
+ onSelect={() => {
+ if (project.ref) setSupabaseProjectRef(project.ref)
setSupabaseProjectsComboboxOpen(false)
}}
>
@@ -225,23 +220,16 @@ const ProjectLinker = ({
-
+ {integrationIcon}
- )
- ) : (
- <>>
- )
+ selectedForeignProject
+ ? getForeignProjectIcon?.(selectedForeignProject)
+ : integrationIcon
}
iconRight={
@@ -270,42 +247,32 @@ const ProjectLinker = ({
}
>
- {(selectedVercelProject && selectedVercelProject.name) ??
- 'Choose a Vercel Project'}
+ {(selectedForeignProject && selectedForeignProject.name) ?? choosePrompt}
No results found.
- {filteredForeignProjects.map((project) => {
+ {foreignProjects.map((project, i) => {
return (
{
- if (id) setVercelProjectId(id)
- setVercelProjectsComboboxOpen(false)
+ onSelect={() => {
+ if (project.id) setForeignProjectId(project.id)
+ setForeignProjectsComboboxOpen(false)
}}
>
- {!project?.framework ? (
- vercelIcon
- ) : (
-
- )}
+ {getForeignProjectIcon?.(project) ?? integrationIcon}
{project.name}
)
@@ -319,12 +286,12 @@ const ProjectLinker = ({
- {showSkip && (
+ {onSkip !== undefined && (
-
{
- return `
- \n
- - \`${x}\`
-`
-})}
-`}
- />
)
}
diff --git a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
index c463ce98397f5..7a864aef62627 100644
--- a/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
+++ b/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
@@ -132,7 +132,7 @@ const AuditLogs = () => {
return (
Your organization has a log retention period of{' '}
-
+
{retentionPeriod} day
{retentionPeriod > 1 ? 's' : ''}
diff --git a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/EnterpriseCard.tsx b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/EnterpriseCard.tsx
index af9608f0dd0b0..cbf139f924ba6 100644
--- a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/EnterpriseCard.tsx
+++ b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/EnterpriseCard.tsx
@@ -18,13 +18,13 @@ const EnterpriseCard = ({ plan, isCurrentPlan }: EnterpriseCardProps) => {
>
-
{plan.name}
+
{plan.name}
{isCurrentPlan ? (
Current plan
) : plan.nameBadge ? (
-
+
{plan.nameBadge}
) : null}
@@ -43,7 +43,7 @@ const EnterpriseCard = ({ plan, isCurrentPlan }: EnterpriseCardProps) => {
{plan.features.map((feature) => (
-
-
+
{feature}
))}
diff --git a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx
index fd50323a60eb7..9ec255913b81a 100644
--- a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx
+++ b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/PlanUpdateSidePanel.tsx
@@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useOrgPlansQuery } from 'data/subscriptions/org-plans-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
-import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
+import { SubscriptionTier, useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
import Telemetry from 'lib/telemetry'
@@ -110,9 +110,14 @@ const PlanUpdateSidePanel = () => {
return ui.setNotification({ category: 'error', message: 'Please select a payment method' })
}
- updateOrgSubscription({ slug, tier: selectedTier, paymentMethod: selectedPaymentMethod })
+ // If the user is downgrading from team, should have spend cap disabled by default
+ const tier = subscription?.plan?.id === 'team' && selectedTier === PRICING_TIER_PRODUCT_IDS.PRO ? PRICING_TIER_PRODUCT_IDS.PAYG as SubscriptionTier : selectedTier
+
+ updateOrgSubscription({ slug, tier, paymentMethod: selectedPaymentMethod })
}
+ const planMeta = selectedTier ? availablePlans.find((p) => p.id === selectedTier.split('tier_')[1]) : null
+
return (
<>
{
>
-
{plan.name}
+
{plan.name}
{isCurrentPlan ? (
Current plan
) : plan.nameBadge ? (
-
+
{plan.nameBadge}
) : (
@@ -182,7 +187,7 @@ const PlanUpdateSidePanel = () => {
{tierMeta?.costUnitOrg}
-
@@ -250,7 +255,7 @@ const PlanUpdateSidePanel = () => {
-
@@ -289,7 +294,7 @@ const PlanUpdateSidePanel = () => {
onCancel={() => setSelectedTier(undefined)}
onConfirm={onUpdateSubscription}
overlayClassName="pointer-events-none"
- header={`Confirm to upgrade to ${subscriptionPlanMeta?.name}`}
+ header={`Confirm ${planMeta?.change_type === 'downgrade' ? 'downgrade' : 'upgrade'} to ${subscriptionPlanMeta?.name}`}
>
diff --git a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/Subscription.tsx b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/Subscription.tsx
index c5b258bcca0c3..c5c07a0f72f78 100644
--- a/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/Subscription.tsx
+++ b/studio/components/interfaces/Organization/BillingSettingsV2/Subscription/Subscription.tsx
@@ -75,9 +75,7 @@ const Subscription = () => {
This organization is currently on the plan:
-
- {currentPlan?.name ?? 'Unknown'}
-
+
{currentPlan?.name ?? 'Unknown'}
diff --git a/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx b/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx
index 00489485bea88..adba0bbc19942 100644
--- a/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx
+++ b/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx
@@ -172,7 +172,7 @@ const GeneralSettings = () => {
privacy policy
diff --git a/studio/components/interfaces/Organization/IntegrationSettings/Integration.tsx b/studio/components/interfaces/Organization/IntegrationSettings/Integration.tsx
index c0a351aff61f6..08ee496acadaa 100644
--- a/studio/components/interfaces/Organization/IntegrationSettings/Integration.tsx
+++ b/studio/components/interfaces/Organization/IntegrationSettings/Integration.tsx
@@ -11,42 +11,38 @@ import {
ScaffoldSectionContent,
ScaffoldSectionDetail,
} from 'components/layouts/Scaffold'
-import { useIntegrationsVercelInstalledConnectionDeleteMutation } from 'data/integrations/integrations-vercel-installed-connection-delete-mutation'
+import ConfirmationModal from 'components/ui/ConfirmationModal'
+import { useIntegrationsVercelConnectionSyncEnvsMutation } from 'data/integrations/integrations-vercel-connection-sync-envs-mutation'
import {
+ IntegrationName,
IntegrationProjectConnection,
Integration as TIntegration,
} from 'data/integrations/integrations.types'
-import { useSelectedOrganization } from 'hooks'
import { BASE_PATH } from 'lib/constants'
import { pluralize } from 'lib/helpers'
import { EMPTY_ARR } from 'lib/void'
-import { useGithubConnectionConfigPanelSnapshot } from 'state/github-connection-config-panel'
-import { Button, Dropdown, IconChevronDown, IconTrash } from 'ui'
+import { useCallback, useState } from 'react'
+import { Button, Dropdown, IconChevronDown, IconLoader, IconRefreshCw, IconTrash, Modal } from 'ui'
export interface IntegrationProps {
title: string
- orgName?: string
description?: string
note?: string
detail?: string
integrations?: TIntegration[]
+ onAddConnection: (integrationId: string) => void
+ onDeleteConnection: (connection: IntegrationProjectConnection) => void | Promise
}
const Integration = ({
title,
- orgName,
description,
note,
detail,
integrations = EMPTY_ARR,
+ onAddConnection,
+ onDeleteConnection,
}: IntegrationProps) => {
- const snapshot = useGithubConnectionConfigPanelSnapshot()
-
- const selectedOrganization = useSelectedOrganization()
-
- const { mutate: deleteMutate, isLoading: isDeleteLoading } =
- useIntegrationsVercelInstalledConnectionDeleteMutation()
-
const ConnectionHeading = ({ integration }: { integration: TIntegration }) => {
return (
{
- const variables = {
- organization_integration_id: organizationIntegrationId,
- id: projectIntegrationId,
- orgSlug: selectedOrganization?.slug,
- }
-
- deleteMutate(variables)
- }
-
return (
<>
@@ -89,60 +74,28 @@ Repository connections for ${title?.toLowerCase()}
{integrations.length > 0 &&
integrations.map((integration, i) => {
return (
-
-
+
+
+
{integration.connections.length > 0 ? (
<>
- {integration.connections.map(
- (connection: IntegrationProjectConnection, i) => (
-
- }
- onSelect={() =>
- handleDelete(integration.id, connection.id)
- }
- >
- Delete
-
- >
- }
- >
- }
- type="default"
- >
- Manage
-
-
- }
- />
- )
- )}
+ {integration.connections.map((connection) => (
+
+ ))}
>
) : (
)}
-
{
- snapshot.setOrganizationIntegrationId(integration.id)
- snapshot.setOpen(true)
- }}
- >
+ onAddConnection(integration.id)}>
Add new project connection
@@ -158,3 +111,107 @@ Repository connections for ${title?.toLowerCase()}
}
export default Integration
+
+type IntegrationConnectionItemProps = {
+ connection: IntegrationProjectConnection
+ type: IntegrationName
+ onDeleteConnection: (connection: IntegrationProjectConnection) => void | Promise
+}
+
+const IntegrationConnectionItem = ({
+ connection,
+ type,
+ onDeleteConnection,
+}: IntegrationConnectionItemProps) => {
+ const [isOpen, setIsOpen] = useState(false)
+ const [dropdownVisible, setDropdownVisible] = useState(false)
+
+ const onConfirm = useCallback(async () => {
+ try {
+ await onDeleteConnection(connection)
+ } finally {
+ setIsOpen(false)
+ }
+ }, [connection, onDeleteConnection])
+
+ const onCancel = useCallback(() => {
+ setIsOpen(false)
+ }, [])
+
+ const { mutateAsync: syncEnvs, isLoading: isSyncEnvLoading } =
+ useIntegrationsVercelConnectionSyncEnvsMutation()
+
+ const onReSyncEnvVars = useCallback(async () => {
+ try {
+ await syncEnvs({ connectionId: connection.id })
+ } finally {
+ setDropdownVisible(false)
+ }
+ }, [connection, syncEnvs])
+
+ return (
+ <>
+ setDropdownVisible(!dropdownVisible)}
+ modal={false}
+ side="bottom"
+ align="end"
+ size="medium"
+ overlay={
+ <>
+ {type === 'Vercel' && (
+ <>
+
+ ) : (
+
+ )
+ }
+ onSelect={(event) => {
+ event.preventDefault()
+ onReSyncEnvVars()
+ }}
+ disabled={isSyncEnvLoading}
+ >
+ Resync environment variables
+
+
+ >
+ )}
+ } onSelect={() => setIsOpen(true)}>
+ Delete connection
+
+ >
+ }
+ >
+ } type="default">
+ Manage
+
+
+ }
+ />
+
+
+
+
+ {`This action cannot be undone. Are you sure you want to delete this connection?`}
+
+
+
+ >
+ )
+}
diff --git a/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx b/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx
index b80cd93b40a84..a0be0c4b15c5d 100644
--- a/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx
+++ b/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx
@@ -1,19 +1,25 @@
+import { useCallback, useState } from 'react'
+
+import { useParams } from 'common'
import { ScaffoldDivider } from 'components/layouts/Scaffold'
+import { useIntegrationsGitHubInstalledConnectionDeleteMutation } from 'data/integrations/integrations-github-connection-delete-mutation'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
+import { useIntegrationsVercelInstalledConnectionDeleteMutation } from 'data/integrations/integrations-vercel-installed-connection-delete-mutation'
import { useVercelProjectsQuery } from 'data/integrations/integrations-vercel-projects-query'
-import { useSelectedOrganization } from 'hooks'
-import { getVercelConfigurationUrl } from 'lib/integration-utils'
+import { IntegrationProjectConnection } from 'data/integrations/integrations.types'
+import { getIntegrationConfigurationUrl } from 'lib/integration-utils'
import Integration from './Integration'
+import SidePanelGitHubRepoLinker from './SidePanelGitHubRepoLinker'
import SidePanelVercelProjectLinker from './SidePanelVercelProjectLinker'
const IntegrationSettings = () => {
- const org = useSelectedOrganization()
- const { data } = useOrgIntegrationsQuery({ orgSlug: org?.slug })
+ const { slug } = useParams()
+ const { data } = useOrgIntegrationsQuery({ orgSlug: slug })
const vercelIntegrations = data
?.filter((integration) => integration.integration.name === 'Vercel')
.map((integration) => {
- if (integration.metadata) {
+ if (integration.metadata && integration.integration.name === 'Vercel') {
const avatarSrc =
!integration.metadata.account.avatar && integration.metadata.account.type === 'Team'
? `https://vercel.com/api/www/avatar?teamId=${integration.metadata.account.team_id}&s=48`
@@ -39,11 +45,56 @@ const IntegrationSettings = () => {
)
const vercelProjectCount = vercelProjectsData?.length ?? 0
+ const [addPanelOpen, setAddPanelOpen] = useState(undefined)
+ const [addPanelIntegrationId, setAddPanelIntegrationId] = useState(undefined)
+
+ const onAddVercelConnection = useCallback((integrationId: string) => {
+ setAddPanelIntegrationId(integrationId)
+ setAddPanelOpen('VERCEL')
+ }, [])
+
+ const onAddGitHubConnection = useCallback((integrationId: string) => {
+ setAddPanelIntegrationId(integrationId)
+ setAddPanelOpen('GITHUB')
+ }, [])
+
+ const onCloseAddPanel = useCallback(() => {
+ setAddPanelOpen(undefined)
+ setAddPanelIntegrationId(undefined)
+ }, [])
+
+ const { mutateAsync: deleteVercelConnection } =
+ useIntegrationsVercelInstalledConnectionDeleteMutation()
+
+ const onDeleteVercelConnection = useCallback(
+ async (connection: IntegrationProjectConnection) => {
+ await deleteVercelConnection({
+ id: connection.id,
+ organization_integration_id: connection.organization_integration_id,
+ orgSlug: slug,
+ })
+ },
+ [deleteVercelConnection, slug]
+ )
+
+ const { mutateAsync: deleteGitHubConnection } =
+ useIntegrationsGitHubInstalledConnectionDeleteMutation()
+
+ const onDeleteGitHubConnection = useCallback(
+ async (connection: IntegrationProjectConnection) => {
+ await deleteGitHubConnection({
+ connectionId: connection.id,
+ integrationId: connection.organization_integration_id,
+ orgSlug: slug,
+ })
+ },
+ [deleteGitHubConnection, slug]
+ )
+
return (
<>
0 && vercelIntegration !== undefined
? `
Your Vercel connection has access to ${vercelProjectCount} Vercel Projects.
-You can change the scope of the access for Supabase by configuring [here](${getVercelConfigurationUrl(
+You can change the scope of the access for Supabase by configuring [here](${getIntegrationConfigurationUrl(
vercelIntegration
)}).
`
: undefined
}
integrations={vercelIntegrations}
+ onAddConnection={onAddVercelConnection}
+ onDeleteConnection={onDeleteVercelConnection}
/>
+
+
-
>
)
}
diff --git a/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker.tsx b/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker.tsx
new file mode 100644
index 0000000000000..82a5dd7cb255d
--- /dev/null
+++ b/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker.tsx
@@ -0,0 +1,151 @@
+import { useMemo } from 'react'
+
+import ProjectLinker from 'components/interfaces/Integrations/ProjectLinker'
+import { Markdown } from 'components/interfaces/Markdown'
+import { useIntegrationGitHubConnectionsCreateMutation } from 'data/integrations/integrations-github-connections-create-mutation'
+import { useGitHubReposQuery } from 'data/integrations/integrations-github-repos-query'
+import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
+import { useProjectsQuery } from 'data/projects/projects-query'
+import { useSelectedOrganization } from 'hooks'
+import { EMPTY_ARR } from 'lib/void'
+import { SidePanel } from 'ui'
+import { useIntegrationsGitHubInstalledConnectionDeleteMutation } from 'data/integrations/integrations-github-connection-delete-mutation'
+import { IntegrationConnectionsCreateVariables } from 'data/integrations/types'
+
+const GITHUB_ICON = (
+
+)
+
+export type SidePanelGitHubRepoLinkerProps = {
+ isOpen?: boolean
+ onClose?: () => void
+ organizationIntegrationId?: string
+ projectRef?: string
+}
+
+const SidePanelGitHubRepoLinker = ({
+ isOpen = false,
+ onClose,
+ organizationIntegrationId,
+ projectRef,
+}: SidePanelGitHubRepoLinkerProps) => {
+ const selectedOrganization = useSelectedOrganization()
+
+ const { data: integrationData } = useOrgIntegrationsQuery({
+ orgSlug: selectedOrganization?.slug,
+ })
+ const githubIntegrations = integrationData?.filter(
+ (integration) => integration.integration.name === 'GitHub'
+ ) // github
+ const existingProjectGithubConnection = githubIntegrations?.[0]?.connections.find(
+ (connection) => connection.supabase_project_ref === projectRef
+ )
+
+ /**
+ * Find the right integration
+ *
+ * we use the snapshot.organizationIntegrationId which should be set whenever this sidepanel is opened
+ */
+ const selectedIntegration = githubIntegrations?.find((x) => x.id === organizationIntegrationId)
+
+ /**
+ * Supabase projects available
+ */
+ const { data: supabaseProjectsData } = useProjectsQuery({
+ enabled: organizationIntegrationId !== undefined,
+ })
+
+ const supabaseProjects = useMemo(
+ () =>
+ supabaseProjectsData
+ ?.filter((project) => project.organization_id === selectedOrganization?.id)
+ .map((project) => ({ id: project.id.toString(), name: project.name, ref: project.ref })) ??
+ EMPTY_ARR,
+ [selectedOrganization?.id, supabaseProjectsData]
+ )
+
+ const { data: githubProjectsData } = useGitHubReposQuery(
+ {
+ integrationId: organizationIntegrationId,
+ },
+ { enabled: organizationIntegrationId !== undefined }
+ )
+
+ const githubRepos = useMemo(
+ () =>
+ githubProjectsData?.map((repo) => ({
+ id: repo.id.toString(),
+ name: repo.full_name,
+ })) ?? EMPTY_ARR,
+ [githubProjectsData]
+ )
+
+ const { mutate: createConnections, isLoading: isCreatingConnection } =
+ useIntegrationGitHubConnectionsCreateMutation({
+ onSuccess() {
+ onClose?.()
+ },
+ })
+
+ const { mutate: deleteGitHubConnection } =
+ useIntegrationsGitHubInstalledConnectionDeleteMutation()
+
+ const createGithubConnection = (variables: IntegrationConnectionsCreateVariables) => {
+ const existingProjectGithubConnection = githubIntegrations?.[0].connections.find(
+ (connection) => connection.supabase_project_ref === variables.connection.supabase_project_ref
+ )
+ if (existingProjectGithubConnection !== undefined && selectedOrganization !== undefined) {
+ deleteGitHubConnection({
+ connectionId: existingProjectGithubConnection.id,
+ integrationId: existingProjectGithubConnection.organization_integration_id,
+ orgSlug: selectedOrganization.slug,
+ })
+ }
+ createConnections(variables)
+ }
+
+ return (
+ onClose?.()}
+ >
+
+
+ )
+}
+
+export default SidePanelGitHubRepoLinker
diff --git a/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoSelection.tsx b/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoSelection.tsx
index 4b978dd0ae291..53a4bb42ed8b2 100644
--- a/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoSelection.tsx
+++ b/studio/components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoSelection.tsx
@@ -3,7 +3,6 @@ import { Markdown } from 'components/interfaces/Markdown'
// import { useIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import { useStore } from 'hooks'
import { observer } from 'mobx-react-lite'
-import { useGithubConnectionConfigPanelSnapshot } from 'state/github-connection-config-panel'
import { IconGitHub, IconSearch, Input, Select, SidePanel } from 'ui'
// TO DO
@@ -13,8 +12,6 @@ const GitHubRepoSelection = () => {
const { slug } = useParams()
// const { data } = useIntegrationsQuery({ orgSlug: slug })
- const githubConnectionConfigPanelSnapshot = useGithubConnectionConfigPanelSnapshot()
-
return (
{
+const VERCEL_ICON = (
+
+)
+
+export type SidePanelVercelProjectLinkerProps = {
+ isOpen?: boolean
+ onClose?: () => void
+ organizationIntegrationId?: string
+}
+
+const SidePanelVercelProjectLinker = ({
+ isOpen = false,
+ onClose,
+ organizationIntegrationId,
+}: SidePanelVercelProjectLinkerProps) => {
const selectedOrganization = useSelectedOrganization()
const { data: integrationData } = useOrgIntegrationsQuery({
@@ -20,19 +40,12 @@ const SidePanelVercelProjectLinker = () => {
(integration) => integration.integration.name === 'Vercel'
) // vercel
- const snapshot = useGithubConnectionConfigPanelSnapshot()
-
/**
* Find the right integration
*
* we use the snapshot.organizationIntegrationId which should be set whenever this sidepanel is opened
*/
- const selectedIntegration = vercelIntegrations?.find(
- (x) => x.id === snapshot.organizationIntegrationId
- )
-
- const organizationIntegrationId = snapshot.organizationIntegrationId
- const open = snapshot.open
+ const selectedIntegration = vercelIntegrations?.find((x) => x.id === organizationIntegrationId)
/**
* Supabase projects available
@@ -58,14 +71,60 @@ const SidePanelVercelProjectLinker = () => {
)
const vercelProjects = useMemo(() => vercelProjectsData ?? EMPTY_ARR, [vercelProjectsData])
+ const vercelProjectsById = useMemo(() => keyBy(vercelProjects, 'id'), [vercelProjects])
+
+ const getForeignProjectIcon = useCallback(
+ (_project: ForeignProject) => {
+ const project = vercelProjectsById[_project.id]
+
+ return !project?.framework ? (
+ vercelIcon
+ ) : (
+
+ )
+ },
+ [vercelProjectsById]
+ )
+
+ const { mutate: createConnections, isLoading: isCreatingConnection } =
+ useIntegrationVercelConnectionsCreateMutation({
+ onSuccess() {
+ onClose?.()
+ },
+ })
+
+ const onCreateConnections = useCallback(
+ (vars) => {
+ createConnections({
+ ...vars,
+ connection: {
+ ...vars.connection,
+ metadata: {
+ ...vars.connection.metadata,
+ supabaseConfig: {
+ projectEnvVars: {
+ write: true,
+ },
+ },
+ },
+ },
+ })
+ },
+ [createConnections]
+ )
return (
snapshot.setOpen(false)}
+ onCancel={() => onClose?.()}
>
@@ -82,10 +141,24 @@ Check the details below before proceeding
organizationIntegrationId={selectedIntegration?.id}
foreignProjects={vercelProjects}
supabaseProjects={supabaseProjects}
- onCreateConnections={() => {
- snapshot.setOpen(false)
- }}
+ onCreateConnections={onCreateConnections}
installedConnections={selectedIntegration?.connections}
+ isLoading={isCreatingConnection}
+ integrationIcon={VERCEL_ICON}
+ getForeignProjectIcon={getForeignProjectIcon}
+ choosePrompt="Choose Vercel Project"
+ />
+ {
+ return `
+ \n
+ - \`${x}\`
+`
+})}
+`}
/>
diff --git a/studio/components/interfaces/Organization/OAuthApps/AuthorizedAppRow.tsx b/studio/components/interfaces/Organization/OAuthApps/AuthorizedAppRow.tsx
index d8b4360ef6633..6f45bc8ca34f2 100644
--- a/studio/components/interfaces/Organization/OAuthApps/AuthorizedAppRow.tsx
+++ b/studio/components/interfaces/Organization/OAuthApps/AuthorizedAppRow.tsx
@@ -28,9 +28,7 @@ const AuthorizedAppRow = ({ app, onSelectRevoke }: AuthorizedAppRowProps) => {
{app.id}
:
- }
+ icon={isCopied ? : }
className="ml-2 px-1"
onClick={() => {
copyToClipboard(app.id)
diff --git a/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx b/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx
index 6157cf4748d58..44d4a7851f1a3 100644
--- a/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx
+++ b/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx
@@ -45,11 +45,7 @@ const OAuthAppRow = ({ app, onSelectEdit, onSelectDelete }: OAuthAppRowProps) =>
- ) : (
-
- )
+ isCopied ? :
}
className="ml-2 px-1"
onClick={() => {
diff --git a/studio/components/interfaces/Organization/Usage/Usage.constants.tsx b/studio/components/interfaces/Organization/Usage/Usage.constants.tsx
index 0743b1ac3d89f..d38ad187a07ac 100644
--- a/studio/components/interfaces/Organization/Usage/Usage.constants.tsx
+++ b/studio/components/interfaces/Organization/Usage/Usage.constants.tsx
@@ -103,10 +103,11 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
const isApproachingLimit = hasLimit && usageRatio >= USAGE_APPROACHING_THRESHOLD
const isExceededLimit = hasLimit && usageRatio >= 1
+ const isCapped = usageMeta?.capped
return (
- {(isApproachingLimit || isExceededLimit) && (
+ {(isApproachingLimit || isExceededLimit) && isCapped && (
{
}
export const createSqlSnippetSkeleton = ({
+ id,
name,
sql,
owner_id,
+ project_id,
}: {
+ id?: string
name?: string
sql?: string
owner_id?: number
+ project_id?: number
} = {}): UserContent => {
return {
...NEW_SQL_SNIPPET_SKELETON,
+ id,
...(name && { name }),
...(owner_id && { owner_id }),
+ ...(project_id && { project_id }),
content: {
...NEW_SQL_SNIPPET_SKELETON.content,
- content_id: '',
+ content_id: id ?? '',
sql: sql ?? '',
},
}
diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx
index 3c729c690d289..62c3cfb01acf9 100644
--- a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx
+++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react-lite'
import { useParams, useTelemetryProps } from 'common'
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.constants'
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { SqlSnippet } from 'data/content/sql-snippets-query'
import { useCheckPermissions, useStore } from 'hooks'
import { uuidv4 } from 'lib/helpers'
@@ -19,6 +20,7 @@ const SQLTemplates = observer(() => {
const { ref } = useParams()
const router = useRouter()
const { profile } = useProfile()
+ const { project } = useProjectContext()
const [sql, quickStart] = partition(SQL_TEMPLATES, { type: 'template' })
const telemetryProps = useTelemetryProps()
@@ -28,10 +30,9 @@ const SQLTemplates = observer(() => {
subject: { id: profile?.id },
})
- // [Joshen TODO] Removed optimistic query creation logic for now, need to figure out
- // how to do that after using ids as part of the URL
const handleNewQuery = async (sql: string, name: string) => {
- if (!ref) return console.error('Project ref is required')
+ if (!project) return console.error('Project is required')
+ if (!profile) return console.error('Profile is required')
if (!canCreateSQLSnippet) {
return ui.setNotification({
category: 'info',
@@ -40,10 +41,16 @@ const SQLTemplates = observer(() => {
}
try {
- const snippet = createSqlSnippetSkeleton({ name, sql, owner_id: profile?.id })
- const data = { ...snippet, id: uuidv4() }
- snap.addSnippet(data as SqlSnippet, ref, true)
- router.push(`/project/${ref}/sql/${data.id}`)
+ const snippet = createSqlSnippetSkeleton({
+ id: uuidv4(),
+ name,
+ sql,
+ owner_id: profile.id,
+ project_id: project.id,
+ })
+
+ snap.addSnippet(snippet as SqlSnippet, project.ref)
+ router.push(`/project/${project.ref}/sql/${snippet.id}`)
} catch (error: any) {
ui.setNotification({
category: 'error',
diff --git a/studio/components/interfaces/SQLEditor/UtilityPanel/SavingIndicator.tsx b/studio/components/interfaces/SQLEditor/UtilityPanel/SavingIndicator.tsx
index a39a293c3c7ac..05bc9c02c20c0 100644
--- a/studio/components/interfaces/SQLEditor/UtilityPanel/SavingIndicator.tsx
+++ b/studio/components/interfaces/SQLEditor/UtilityPanel/SavingIndicator.tsx
@@ -47,7 +47,7 @@ const SavingIndicator = ({ id }: SavingIndicatorProps) => {
{showSavedText ? (
-
+
diff --git a/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx b/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx
index ed3a3240a09ce..9104db59821a1 100644
--- a/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx
+++ b/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx
@@ -145,7 +145,7 @@ const CustomDomainSidePanel = () => {
Custom domains allow you to present a branded experience to your users. You may set up
your custom domain in the{' '}
- General Settings
+ General Settings
{' '}
page after enabling the add-on.
diff --git a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx
index 6792ab6557e7c..cfb85ffcccb5d 100644
--- a/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx
+++ b/studio/components/interfaces/Settings/Database/ConnectionPooling.tsx
@@ -34,6 +34,8 @@ const ConnectionPooling = () => {
'ignore_startup_parameters',
'pool_mode',
'pgbouncer_enabled',
+ 'max_client_conn',
+ 'connectionString',
]
const bouncerInfo = isSuccess ? pluckObjectFields(formModel, BOUNCER_FIELDS) : {}
@@ -105,6 +107,8 @@ interface ConfigProps {
ignore_startup_parameters: 'string'
pool_mode: string
pgbouncer_enabled: boolean
+ max_client_conn: number
+ connectionString: string
}
connectionInfo: {
db_host: string
@@ -123,11 +127,12 @@ export const PgbouncerConfig: FC = ({ projectRef, bouncerInfo, conn
'projects'
)
- const [updates, setUpdates] = useState({
+ const [updates, setUpdates] = useState({
pool_mode: bouncerInfo.pool_mode || 'transaction',
default_pool_size: bouncerInfo.default_pool_size || undefined,
ignore_startup_parameters: bouncerInfo.ignore_startup_parameters || '',
pgbouncer_enabled: bouncerInfo.pgbouncer_enabled,
+ max_client_conn: bouncerInfo.max_client_conn || undefined,
})
const updateConfig = async (updatedConfig: any) => {
@@ -174,6 +179,16 @@ export const PgbouncerConfig: FC = ({ projectRef, bouncerInfo, conn
type: 'string',
help: 'Defaults are either blank or "extra_float_digits"',
},
+ max_client_conn: {
+ title: 'Max Client Connections',
+ oneOf: [{ type: 'integer' }, { type: 'null' }],
+ help: 'The maximum number of concurrent client connections allowed. Overrides default optimizations; refer to https://supabase.com/docs/guides/platform/custom-postgres-config#pooler-config',
+ },
+ default_pool_size: {
+ title: 'Default Pool Size',
+ oneOf: [{ type: 'integer' }, { type: 'null' }],
+ help: 'The maximum number of connections made to the underlying Postgres cluster, per user+db combination. Overrides default optimizations; refer to https://supabase.com/docs/guides/platform/custom-postgres-config#pooler-config',
+ },
},
required: ['pool_mode'],
type: 'object',
@@ -182,7 +197,7 @@ export const PgbouncerConfig: FC = ({ projectRef, bouncerInfo, conn
return (
= ({ projectRef, bouncerInfo, conn
+
+
+
+
>
)}
@@ -239,11 +258,7 @@ export const PgbouncerConfig: FC = ({ projectRef, bouncerInfo, conn
copy
disabled
label="Connection string"
- value={
- `postgres://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
- `${connectionInfo.db_host}:${connectionInfo.db_port}` +
- `/${connectionInfo.db_name}`
- }
+ value={bouncerInfo.connectionString}
/>
diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx
index abb91c8a96eae..d2e68280594ec 100644
--- a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx
+++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig.tsx
@@ -69,6 +69,7 @@ const CustomDomainConfig = () => {
? 'To configure a custom domain for your project, please upgrade to the Pro plan with the custom domains add-on selected'
: 'To configure a custom domain for your project, please enable the add-on'
}
+ addon="customDomain"
/>
) : (
diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainDelete.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainDelete.tsx
index f1512f480287b..ebeb95f35c892 100644
--- a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainDelete.tsx
+++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainDelete.tsx
@@ -36,7 +36,7 @@ const CustomDomainDelete = ({ projectRef, customDomain }: CustomDomainDeleteProp
Active custom domain:
-
+
{customDomain.hostname}
diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx
index ebf5ff5e3ecd5..3cce8d4f0622a 100644
--- a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx
+++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainVerify.tsx
@@ -84,7 +84,7 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain
You may also visit{' '}
- here
+ here
{' '}
to check if your DNS has been propagated successfully before clicking verify.
@@ -93,7 +93,7 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain
You may also visit{' '}
- here
+ here
{' '}
to check if your DNS has been propagated successfully before clicking verify.
diff --git a/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx
index b4ba127f6c1e2..c0d00b54c24ca 100644
--- a/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx
+++ b/studio/components/interfaces/Settings/General/Infrastructure/ProjectUpgradeAlert/ProjectUpgradeAlert.tsx
@@ -67,7 +67,7 @@ const ProjectUpgradeAlert = () => {
return (
<>
}
+ icon={}
variant="success"
title="Your project can be upgraded to the latest version of Postgres"
>
@@ -114,7 +114,7 @@ const ProjectUpgradeAlert = () => {
This update has breaking changes. Read more
-
+
here
diff --git a/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx
index 896cb36b6bfdd..d599cb2cc96c5 100644
--- a/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx
+++ b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx
@@ -262,7 +262,7 @@ const TransferProjectButton = () => {
{' '}
Your current organization will be granted{' '}
-
+
${transferPreviewData.credits_on_source_organization}
{' '}
in credits as proration.
@@ -277,7 +277,7 @@ const TransferProjectButton = () => {
{' '}
The target organization will be billed{' '}
-
+
${transferPreviewData.costs_on_target_organization}
{' '}
immediately to prorate for the remainder of the billing period.
diff --git a/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx b/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx
index 6ea93cd06f834..9a31cbe0b2d1c 100644
--- a/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogsFormatters.tsx
@@ -276,7 +276,7 @@ export function jsonSyntaxHighlight(input: Object) {
if (/:$/.test(match)) {
cls = 'key text-scale-1200'
} else {
- cls = 'string text-brand-1100'
+ cls = 'string text-brand-600'
}
} else if (/true|false/.test(match)) {
cls = 'boolean text-blue-900'
diff --git a/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx b/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx
index c0a9419c7e8fa..530286775ea78 100644
--- a/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx
+++ b/studio/components/interfaces/Settings/Vault/Secrets/SecretRow.tsx
@@ -66,11 +66,11 @@ const SecretRow: FC = ({ secret, onSelectEdit, onSelectRemove }) => {
{secret.key_id}
diff --git a/studio/components/interfaces/Storage/CreateBucketModal.tsx b/studio/components/interfaces/Storage/CreateBucketModal.tsx
index 3d098675683d7..abb5cb455967e 100644
--- a/studio/components/interfaces/Storage/CreateBucketModal.tsx
+++ b/studio/components/interfaces/Storage/CreateBucketModal.tsx
@@ -209,7 +209,7 @@ const CreateBucketModal = ({ visible, onClose }: CreateBucketModalProps) => {
Note: The{' '}
-
+
global upload limit
{' '}
diff --git a/studio/components/interfaces/Storage/EditBucketModal.tsx b/studio/components/interfaces/Storage/EditBucketModal.tsx
index 650c68f28283a..59b691e743590 100644
--- a/studio/components/interfaces/Storage/EditBucketModal.tsx
+++ b/studio/components/interfaces/Storage/EditBucketModal.tsx
@@ -221,7 +221,7 @@ const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalProps) =>
Note: The{' '}
-
+
global upload limit
{' '}
diff --git a/studio/components/interfaces/Support/Success.tsx b/studio/components/interfaces/Support/Success.tsx
index 1fcd9d937e38d..7fad08f177043 100644
--- a/studio/components/interfaces/Support/Success.tsx
+++ b/studio/components/interfaces/Support/Success.tsx
@@ -18,8 +18,8 @@ const Success: FC = ({ sentCategory = '' }) => {
return (
-
-
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
index fb4c5d6f35014..c98ba5e41fae4 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
@@ -367,7 +367,7 @@ const ColumnEditor = ({
You will need to{' '}
- install
+ install
{' '}
the extension pgsodium
first before being
able to encrypt your column.
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx
index df57a2cf8f2fb..9c9dc4f4d0813 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx
@@ -134,8 +134,7 @@ const ColumnForeignKeyAdded: FC<{
- The following foreign key relation will be{' '}
- added:
+ The following foreign key relation will be added:
- The foreign key relation will be updated as
- such:
+ The foreign key relation will be updated as such:
{columnName}
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
index d40c56df3ee73..3a6fb96cc5597 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
@@ -330,7 +330,7 @@ const ForeignKeySelector: FC
= ({ column, visible = false, closePanel, sa
href="https://supabase.com/docs/guides/database/postgres/cascade-deletes"
target="_blank"
rel="noreferrer"
- className="text-brand-900 opacity-75"
+ className="text-brand opacity-75"
>
Learn more about cascade deletes
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorGrid.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorGrid.tsx
index a77e38a903979..a878ab46238c4 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorGrid.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/SelectorGrid.tsx
@@ -20,7 +20,7 @@ const columnRender = (name: string, isPrimaryKey = false) => {
{isPrimaryKey && (
-
+
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts
index 585853061eeaf..ddee6d5ee0c8f 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants.ts
@@ -2,7 +2,15 @@ import { sortBy, concat } from 'lodash'
import { PostgresDataTypeOption } from './SidePanelEditor.types'
export const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ'
-export const NUMERICAL_TYPES = ['int2', 'int4', 'int8', 'float4', 'float8', 'numeric']
+export const NUMERICAL_TYPES = [
+ 'int2',
+ 'int4',
+ 'int8',
+ 'float4',
+ 'float8',
+ 'numeric',
+ 'double precision',
+]
export const JSON_TYPES = ['json', 'jsonb']
export const TEXT_TYPES = ['text', 'varchar']
@@ -11,7 +19,7 @@ export const DATE_TYPES = ['date']
export const TIME_TYPES = ['time', 'timetz']
export const DATETIME_TYPES = concat(TIMESTAMP_TYPES, DATE_TYPES, TIME_TYPES)
-export const OTHER_DATA_TYPES = ['uuid', 'bool']
+export const OTHER_DATA_TYPES = ['uuid', 'bool', 'vector']
export const POSTGRES_DATA_TYPES = sortBy(
concat(NUMERICAL_TYPES, JSON_TYPES, TEXT_TYPES, DATETIME_TYPES, OTHER_DATA_TYPES)
)
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx
index 286d80c26654b..73506ebc68ece 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx
@@ -57,7 +57,7 @@ const SpreadSheetFileUpload: FC
= ({
onClick={() => (uploadButtonRef.current as any)?.click()}
>
- Drag and drop, or browse your files
+ Drag and drop, or browse your files
) : (
diff --git a/studio/components/layouts/AppLayout/AppHeader.tsx b/studio/components/layouts/AppLayout/AppHeader.tsx
index e6bcbe9bca92b..d845b167b32f0 100644
--- a/studio/components/layouts/AppLayout/AppHeader.tsx
+++ b/studio/components/layouts/AppLayout/AppHeader.tsx
@@ -2,7 +2,7 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useParams } from 'common'
-import { useFlag, useSelectedOrganization } from 'hooks'
+import { useFlag, useSelectedOrganization, useSelectedProject } from 'hooks'
import FeedbackDropdown from '../ProjectLayout/LayoutHeader/FeedbackDropdown'
import HelpPopover from '../ProjectLayout/LayoutHeader/HelpPopover'
import NotificationsPopover from '../ProjectLayout/LayoutHeader/NotificationsPopover'
@@ -11,18 +11,24 @@ import ProjectDropdown from './ProjectDropdown'
import SettingsButton from './SettingsButton'
import UserSettingsDropdown from './UserSettingsDropdown'
import BranchDropdown from './BranchDropdown'
+import EnableBranchingButton from './EnableBranchingButton/EnableBranchingButton'
const AppHeader = () => {
const router = useRouter()
const { ref } = useParams()
+ const project = useSelectedProject()
const organization = useSelectedOrganization()
const enableBranchManagement = useFlag('branchManagement')
+ const isBranchingSupported = project?.cloud_provider === 'FLY'
+ const isBranchingEnabled =
+ project?.is_branch_enabled === true || project?.parent_project_ref !== undefined
+
return (
-
+
diff --git a/studio/components/layouts/AppLayout/BranchDropdown.tsx b/studio/components/layouts/AppLayout/BranchDropdown.tsx
index 4e7e3378cb11a..6eb09304f71b0 100644
--- a/studio/components/layouts/AppLayout/BranchDropdown.tsx
+++ b/studio/components/layouts/AppLayout/BranchDropdown.tsx
@@ -1,10 +1,11 @@
+import Link from 'next/link'
import { useRouter } from 'next/router'
import { useRef, useState } from 'react'
-import Link from 'next/link'
import { useParams } from 'common'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { Branch, useBranchesQuery } from 'data/branches/branches-query'
+import { useSelectedProject } from 'hooks'
import {
Badge,
Button,
@@ -18,12 +19,11 @@ import {
IconCheck,
IconCode,
IconGitBranch,
- IconPlus,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
Popover_Shadcn_,
+ ScrollArea,
} from 'ui'
-import { useSelectedProject } from 'hooks'
import { sanitizeRoute } from './ProjectDropdown'
const BranchLink = ({
@@ -126,14 +126,16 @@ const BranchDropdown = () => {
No branches found
- {branches?.map((branch) => (
-