From 4892a792802e3798f4bd7e99cb9e69582b05dd05 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 27 Feb 2024 10:31:18 +0800 Subject: [PATCH 01/17] fix: remove unused code to fix creating policies via assistant (#21584) Remove unused code to fix creating policies via assistant --- .../interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx index e525696f33c..f42e025bc44 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx @@ -130,9 +130,6 @@ export const AIPolicyEditorPanel = memo(function ({ const { mutateAsync: debugSql, isLoading: isDebugSqlLoading } = useSqlDebugMutation() - const errorLines = - error?.formattedError.split('\n').filter((x: string) => x.length > 0).length ?? 0 - const onExecuteSQL = useCallback(() => { // clean up the sql before sending const policy = editorRef.current?.getValue().replaceAll(' ', ' ') From 569f7fcd144d1e0bfde5966ed95820e75096d57d Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Mon, 26 Feb 2024 23:44:31 -0330 Subject: [PATCH 02/17] Feat/schema visualizer legend (#21575) Add legend --- .../Database/Schemas/SchemaGraph.tsx | 2 + .../Database/Schemas/SchemaGraphLegend.tsx | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 apps/studio/components/interfaces/Database/Schemas/SchemaGraphLegend.tsx diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx index 19587afcb72..ea316a695ac 100644 --- a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx +++ b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx @@ -20,6 +20,7 @@ import { useTheme } from 'next-themes' import 'reactflow/dist/style.css' import { IconLoader } from 'ui' import { TABLE_NODE_ROW_HEIGHT, TABLE_NODE_WIDTH, TableNode } from 'ui-patterns/SchemaTableNode' +import SchemaGraphLegend from './SchemaGraphLegend' type TableNodeData = { name: string @@ -259,6 +260,7 @@ const TablesGraph = ({ tables }: { tables: PostgresTable[] }) => { maskColor={miniMapMaskColor} className="border rounded-md shadow-sm" /> + diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaGraphLegend.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaGraphLegend.tsx new file mode 100644 index 00000000000..acd94d2380a --- /dev/null +++ b/apps/studio/components/interfaces/Database/Schemas/SchemaGraphLegend.tsx @@ -0,0 +1,38 @@ +import { DiamondIcon, Fingerprint, Hash, Key } from 'lucide-react' + +const SchemaGraphLegend = () => { + return ( +
+ {/*

Legend

*/} +
    +
  • + + Primary key +
  • +
  • + + Identity +
  • +
  • + + Unique +
  • +
  • + + Nullable +
  • +
  • + + Non-Nullable +
  • +
+
+ ) +} + +export default SchemaGraphLegend From d8a2052a9455fb6f1105d7674215439614076ec0 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Tue, 27 Feb 2024 11:17:38 +0800 Subject: [PATCH 03/17] bug: Update EntityListItem.tsx (#21570) * Update EntityListItem.tsx * Fix padding on org dropdown --------- Co-authored-by: Terry Sutton --- .../components/layouts/AppLayout/OrganizationDropdown.tsx | 2 +- .../components/layouts/TableEditorLayout/EntityListItem.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx index 51ae83afd01..beca9292de8 100644 --- a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx @@ -47,7 +47,7 @@ const OrganizationDropdown = ({ isNewNav = false }: OrganizationDropdownProps) = } return ( -
+
diff --git a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx index a84f73ef3e8..f72e396098a 100644 --- a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx @@ -135,7 +135,7 @@ const EntityListItem: ItemRenderer = ({ )} > - + {entity.type === ENTITY_TYPE.TABLE ? ( ) : entity.type === ENTITY_TYPE.VIEW ? ( @@ -173,7 +173,7 @@ const EntityListItem: ItemRenderer = ({
@@ -191,7 +191,7 @@ const EntityListItem: ItemRenderer = ({ )}
From f8b21c5e2d843459c70bb1e91598774b3d75a868 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 27 Feb 2024 14:41:08 +1100 Subject: [PATCH 04/17] fix: supabase project ws csp issue (#21586) --- apps/studio/next.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/studio/next.config.js b/apps/studio/next.config.js index 97e2d661a6b..dd066e538aa 100644 --- a/apps/studio/next.config.js +++ b/apps/studio/next.config.js @@ -14,6 +14,7 @@ const GOTRUE_URL = process.env.NEXT_PUBLIC_GOTRUE_URL ? new URL(process.env.NEXT_PUBLIC_GOTRUE_URL).origin : '' const SUPABASE_PROJECTS_URL = 'https://*.supabase.co' +const SUPABASE_PROJECTS_URL_WS = 'wss://*.supabase.co' // construct the URL for the Websocket Local URLs let SUPABASE_LOCAL_PROJECTS_URL_WS = '' @@ -45,7 +46,7 @@ const VERCEL_LIVE_URL = 'https://vercel.live' const PUSHER_URL = 'https://*.pusher.com' const PUSHER_URL_WS = 'wss://*.pusher.com' -const DEFAULT_SRC_URLS = `${API_URL} ${SUPABASE_URL} ${GOTRUE_URL} ${SUPABASE_PROJECTS_URL} ${SUPABASE_LOCAL_PROJECTS_URL_WS} ${HCAPTCHA_SUBDOMAINS_URL} ${CONFIGCAT_URL} ${STRIPE_SUBDOMAINS_URL} ${STRIPE_NETWORK_URL} ${CLOUDFLARE_URL} ${ONE_ONE_ONE_ONE_URL} ${VERCEL_INSIGHTS_URL} ${GITHUB_API_URL} ${GITHUB_USER_CONTENT_URL}` +const DEFAULT_SRC_URLS = `${API_URL} ${SUPABASE_URL} ${GOTRUE_URL} ${SUPABASE_LOCAL_PROJECTS_URL_WS} ${SUPABASE_PROJECTS_URL} ${SUPABASE_PROJECTS_URL_WS} ${HCAPTCHA_SUBDOMAINS_URL} ${CONFIGCAT_URL} ${STRIPE_SUBDOMAINS_URL} ${STRIPE_NETWORK_URL} ${CLOUDFLARE_URL} ${ONE_ONE_ONE_ONE_URL} ${VERCEL_INSIGHTS_URL} ${GITHUB_API_URL} ${GITHUB_USER_CONTENT_URL}` const SCRIPT_SRC_URLS = `${CLOUDFLARE_CDN_URL} ${HCAPTCHA_JS_URL} ${STRIPE_JS_URL}` const FRAME_SRC_URLS = `${HCAPTCHA_ASSET_URL} ${STRIPE_JS_URL}` const IMG_SRC_URLS = `${SUPABASE_URL} ${SUPABASE_COM_URL} ${SUPABASE_PROJECTS_URL}` From 3e3017294643202780f3eaf800a931f693bb5cc5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 27 Feb 2024 11:52:12 +0800 Subject: [PATCH 05/17] Chore/read replica fixes 260224 (#21547) * Staging: only allow apse1 as selection for replica * Fix modal stuck in loading state if dropping replica fails for some reason * Add warnings for RR based on cloud provider and pg version, and fix support page not automatically selecting project ref from query param into form * Add warnings on add ons side panels for compute and pitr RE replicas * Add notice text RE changing compute size if read replicas are preent * Add options to change compute size and delete all replicas from settings/infrastructure * Lint --- .../Database/Backups/PITR/PITRStatus.tsx | 40 ++++---- .../Addons/ComputeInstanceSidePanel.tsx | 96 +++++++++++++------ .../Settings/Addons/PITRSidePanel.tsx | 58 ++++++++--- .../DeployNewReplicaPanel.tsx | 49 +++++++++- .../DropAllReplicasConfirmationModal.tsx | 91 ++++++++++++++++++ .../DropReplicaConfirmationModal.tsx | 3 +- .../InstanceConfiguration.tsx | 65 +++++++++---- .../InstanceConfiguration.utils.ts | 3 - .../InstanceNode.tsx | 7 +- .../InfrastructureConfiguration/MapView.tsx | 25 +---- .../interfaces/Support/SupportForm.tsx | 43 +++++---- .../read-replicas/replica-remove-mutation.ts | 21 ++-- 12 files changed, 372 insertions(+), 129 deletions(-) create mode 100644 apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropAllReplicasConfirmationModal.tsx diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx index 4b083ad80f0..5d13dee4588 100644 --- a/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx +++ b/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx @@ -61,6 +61,7 @@ const PITRStatus = ({
+ {/* [Joshen TODO] Double check if this is intentional */} - {!canTriggerPhysicalBackup && ( - - - -
- - You need additional permissions to trigger a PITR recovery - -
-
-
- )} + {hasReadReplicas || + (!canTriggerPhysicalBackup && ( + + + +
+ + {hasReadReplicas + ? 'You will need to remove all read replicas first to trigger a PITR recovery' + : !canTriggerPhysicalBackup + ? 'You need additional permissions to trigger a PITR recovery' + : null} + +
+
+
+ ))}
} diff --git a/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx index 5771f56abc6..991f0f5477d 100644 --- a/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx @@ -35,6 +35,8 @@ import { import * as Tooltip from '@radix-ui/react-tooltip' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { AddonVariantId, ProjectAddonVariantMeta } from 'data/subscriptions/types' +import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' +import { WarningIcon } from 'components/ui/Icons' const ComputeInstanceSidePanel = () => { const queryClient = useQueryClient() @@ -61,6 +63,7 @@ const ComputeInstanceSidePanel = () => { snap.setPanelKey(undefined) } + const { data: databases } = useReadReplicasQuery({ projectRef }) const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({ @@ -105,7 +108,6 @@ const ComputeInstanceSidePanel = () => { const [selectedOption, setSelectedOption] = useState('') const isSubmitting = isUpdating || isRemoving - const projectId = selectedProject?.id const cpuArchitecture = getCloudProviderArchitecture(selectedProject?.cloud_provider) const selectedAddons = addons?.selected_addons ?? [] @@ -162,9 +164,12 @@ const ComputeInstanceSidePanel = () => { const selectedCompute = availableOptions.find((option) => option.identifier === selectedOption) const hasChanges = selectedOption !== (subscriptionCompute?.variant.identifier ?? defaultInstanceSize) + const hasReadReplicas = (databases ?? []).length > 1 const blockDowngradeDueToPitr = pitrAddon !== undefined && ['ci_micro', 'ci_nano'].includes(selectedOption) && hasChanges + const blockDowngradeDueToReadReplicas = + hasChanges && hasReadReplicas && ['ci_micro', 'ci_nano'].includes(selectedOption) useEffect(() => { if (visible) { @@ -217,7 +222,12 @@ const ComputeInstanceSidePanel = () => { onConfirm={() => setShowConfirmationModal(true)} loading={isLoading} disabled={ - isFreePlan || isLoading || !hasChanges || blockDowngradeDueToPitr || !canUpdateCompute + isFreePlan || + isLoading || + !hasChanges || + blockDowngradeDueToPitr || + blockDowngradeDueToReadReplicas || + !canUpdateCompute } tooltip={ isFreePlan @@ -382,38 +392,66 @@ const ComputeInstanceSidePanel = () => {

)} - {hasChanges && !blockDowngradeDueToPitr && ( - - Your project will be unavailable for up to 2 minutes while the changes take place. - + {hasChanges && !blockDowngradeDueToPitr && !blockDowngradeDueToReadReplicas && ( + + + + Your project will need to be restarted when changing it's compute size + + + Your project will be unavailable for up to 2 minutes while the changes take place. + + {hasReadReplicas && ( + + The compute sizes for all read replicas{' '} + on your project will also be changed to the {selectedCompute?.name} size as + well. + + )} + )} - {blockDowngradeDueToPitr && ( - + + + Unable to downgrade as project has active read replicas + + + The minimum compute instance size for using read replicas is the Small Compute. + You need to remove all read replicas before downgrading Compute as it requires at + least a Small compute instance. + + + + + + ) : blockDowngradeDueToPitr ? ( + + + Disable PITR before downgrading + + You currently have PITR enabled. The minimum compute instance size for using PITR + is the Small Compute. + + + You need to disable PITR before downgrading Compute as it requires at least a + Small compute instance. + + - } - > -

- You currently have PITR enabled. The minimum compute instance size for using PITR - is the Small Compute. -

-

- You need to disable PITR before downgrading Compute as it requires at least a - Small compute instance. -

-
- )} + + + ) : null} {hasChanges && subscription?.billing_via_partner && diff --git a/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx index e539aa53a37..70a052fa4d0 100644 --- a/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx @@ -5,14 +5,20 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { useParams } from 'common' -import { useTheme } from 'next-themes' import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation' import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useCheckPermissions, useSelectedOrganization, useSelectedProject, useStore } from 'hooks' import { BASE_PATH } from 'lib/constants' import Telemetry from 'lib/telemetry' +import { useTheme } from 'next-themes' +import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils' +import { CriticalIcon, WarningIcon } from 'components/ui/Icons' +import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' +import { AddonVariantId } from 'data/subscriptions/types' +import { formatCurrency } from 'lib/helpers' import { useSubscriptionPageStateSnapshot } from 'state/subscription-page' import { Alert, @@ -20,16 +26,11 @@ import { AlertTitle_Shadcn_, Alert_Shadcn_, Button, + IconAlertTriangle, IconExternalLink, Radio, SidePanel, - IconAlertTriangle, } from 'ui' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { AlertTriangleIcon } from 'lucide-react' -import { AddonVariantId } from 'data/subscriptions/types' -import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils' -import { formatCurrency } from 'lib/helpers' const PITR_CATEGORY_OPTIONS: { id: 'off' | 'on' @@ -76,6 +77,7 @@ const PITRSidePanel = () => { snap.setPanelKey(undefined) } + const { data: databases } = useReadReplicasQuery({ projectRef }) const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const hasHipaaAddon = subscriptionHasHipaaAddon(subscription) @@ -121,9 +123,12 @@ const PITRSidePanel = () => { const subscriptionPitr = selectedAddons.find((addon) => addon.type === 'pitr') const availableOptions = availableAddons.find((addon) => addon.type === 'pitr')?.variants ?? [] + const hasReadReplicas = (databases ?? []).length > 1 const hasChanges = selectedOption !== (subscriptionPitr?.variant.identifier ?? 'pitr_0') const selectedPitr = availableOptions.find((option) => option.identifier === selectedOption) const isFreePlan = subscription?.plan?.id === 'free' + const blockDowngradeDueToReadReplicas = + hasChanges && hasReadReplicas && selectedCategory === 'off' && selectedOption === 'pitr_0' useEffect(() => { if (visible) { @@ -167,7 +172,13 @@ const PITRSidePanel = () => { onConfirm={onConfirm} loading={isLoading || isSubmitting} disabled={ - isFreePlan || isLoading || !hasChanges || isSubmitting || !canUpdatePitr || hasHipaaAddon + isFreePlan || + isLoading || + !hasChanges || + isSubmitting || + !canUpdatePitr || + hasHipaaAddon || + blockDowngradeDueToReadReplicas } tooltip={ hasHipaaAddon @@ -225,7 +236,13 @@ const PITRSidePanel = () => { className={clsx('col-span-3 group space-y-1', isFreePlan && 'opacity-75')} onClick={() => { setSelectedCategory(option.id) - if (option.id === 'off') setSelectedOption('pitr_0') + if (option.id === 'off') { + setSelectedOption('pitr_0') + } else if (subscriptionPitr?.variant.identifier !== undefined) { + setSelectedOption(subscriptionPitr.variant.identifier) + } else { + setSelectedOption('pitr_7') + } Telemetry.sendActivity( { activity: 'Option Selected', @@ -270,7 +287,7 @@ const PITRSidePanel = () => { {selectedCategory === 'off' && subscriptionPitr !== undefined && isBranchingEnabled && ( - + Are you sure you want to disable this while using Branching? @@ -281,6 +298,25 @@ const PITRSidePanel = () => { )} + {blockDowngradeDueToReadReplicas && ( + + + Remove all read replicas before downgrading + + You currently have active read replicas. The minimum compute instance size for using + read replicas is the Small Compute. You need to remove all read replicas before + downgrading Compute as it requires at least a Small compute instance. + + + + + + )} + {selectedCategory === 'on' && (
{isFreePlan ? ( @@ -358,7 +394,7 @@ const PITRSidePanel = () => {
)} - {hasChanges && ( + {hasChanges && !blockDowngradeDueToReadReplicas && ( <> {selectedOption === 'pitr_0' || (selectedPitr?.price ?? 0) < (subscriptionPitr?.variant.price ?? 0) ? ( diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx index 0faf37c0f5c..34d5447fe2b 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DeployNewReplicaPanel.tsx @@ -6,7 +6,6 @@ import { AlertTitle_Shadcn_, Alert_Shadcn_, Button, - IconAlertCircle, Listbox, SidePanel, } from 'ui' @@ -20,6 +19,7 @@ import Link from 'next/link' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization, useSelectedProject } from 'hooks' import { WarningIcon } from 'components/ui/Icons' +import { getSemanticVersion } from 'lib/helpers' // [Joshen] FYI this is purely for AWS only, need to update to support Fly eventually @@ -53,6 +53,10 @@ const DeployNewReplicaPanel = ({ }, }) + const currentPgVersion = Number( + (project?.dbVersion ?? '').split('supabase-postgres-')[1]?.split('.')[0] + ) + const reachedMaxReplicas = (data ?? []).filter((db) => db.identifier !== projectRef).length >= 2 const isFreePlan = subscription?.plan.id === 'free' const currentComputeAddon = addons?.selected_addons.find( @@ -61,6 +65,7 @@ const DeployNewReplicaPanel = ({ const currentPitrAddon = addons?.selected_addons.find((addon) => addon.type === 'pitr') const canDeployReplica = !reachedMaxReplicas && + currentPgVersion >= 15 && project?.cloud_provider === 'AWS' && !isFreePlan && currentComputeAddon !== undefined && @@ -82,6 +87,11 @@ const DeployNewReplicaPanel = ({ const [selectedCompute, setSelectedCompute] = useState(defaultCompute) const selectedComputeMeta = computeAddons.find((addon) => addon.identifier === selectedCompute) + const availableRegions = + process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging' + ? AVAILABLE_REPLICA_REGIONS.filter((x) => x.key === 'SOUTHEAST_ASIA') + : AVAILABLE_REPLICA_REGIONS + const onSubmit = async () => { const regionKey = AWS_REGIONS_VALUES[selectedRegion] if (!projectRef) return console.error('Project is required') @@ -162,6 +172,41 @@ const DeployNewReplicaPanel = ({ )} + {/* [Joshen] Not particular about this warning as all users on prod are on AWS */} + {project?.cloud_provider !== 'AWS' && ( + + + + Read replicas can only be deployed with projects on AWS + + + If you'd like to use read replicas, please migrate your project to AWS by creating a + new one. + + + )} + {currentPgVersion < 15 && ( + + + + Read replicas can only be deployed with projects on Postgres version 15 and above + + + If you'd like to use read replicas, please contact us via support + + + + + + )} - {AVAILABLE_REPLICA_REGIONS.map((region) => ( + {availableRegions.map((region) => ( void + onCancel: () => void +} + +const DropAllReplicasConfirmationModal = ({ + visible, + onSuccess, + onCancel, +}: DropAllReplicasConfirmationModalProps) => { + const { ref: projectRef } = useParams() + const queryClient = useQueryClient() + const { data: databases } = useReadReplicasQuery({ projectRef }) + const { mutateAsync: removeReadReplica, isLoading: isRemoving } = useReadReplicaRemoveMutation() + + const onConfirmRemove = async () => { + if (!projectRef) return console.error('Project is required') + if (databases === undefined) return console.error('Unable to retrieve replicas') + if (databases.length === 1) toast('Your project has no read replicas') + + const replicas = databases.filter((db) => db.identifier !== projectRef) + await Promise.all( + replicas.map((db) => + removeReadReplica({ projectRef, identifier: db.identifier, skipInvalidateOnSuccess: true }) + ) + ) + toast.success(`Tearing down all read replicas`) + + queryClient.setQueriesData(replicaKeys.list(projectRef), (old: Database[]) => { + return old.filter((db: Database) => db.identifier === projectRef) + }) + queryClient.setQueriesData(replicaKeys.loadBalancers(projectRef), (old: Database[]) => []) + + setTimeout(async () => { + await queryClient.invalidateQueries(replicaKeys.list(projectRef)) + await queryClient.invalidateQueries(replicaKeys.loadBalancers(projectRef)) + }, 5000) + + onSuccess() + onCancel() + } + + return ( + onCancel()} + onSelectConfirm={() => onConfirmRemove()} + > + + + + This action cannot be undone + + You may still deploy new replicas in this region thereafter + + +
+

Before deleting all replicas, consider:

+
    +
  • Network traffic from this region may slow down
  • +
+
+
+
+ ) +} + +export default DropAllReplicasConfirmationModal diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx index ae311a2ef92..319b7f661c8 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx @@ -26,7 +26,7 @@ const DropReplicaConfirmationModal = ({ }: DropReplicaConfirmationModalProps) => { const { ref: projectRef } = useParams() const formattedId = formatDatabaseID(selectedReplica?.identifier ?? '') - const { mutateAsync: removeReadReplica } = useReadReplicaRemoveMutation({ + const { mutateAsync: removeReadReplica, isLoading: isRemoving } = useReadReplicaRemoveMutation({ onSuccess: () => { toast.success(`Tearing down read replica (ID: ${formattedId})`) onSuccess() @@ -45,6 +45,7 @@ const DropReplicaConfirmationModal = ({ { const { resolvedTheme } = useTheme() const { ref: projectRef } = useParams() const numComingUp = useRef() + const snap = useSubscriptionPageStateSnapshot() const [view, setView] = useState<'flow' | 'map'>('flow') + const [showDeleteAllModal, setShowDeleteAllModal] = useState(false) const [showNewReplicaPanel, setShowNewReplicaPanel] = useState(false) const [refetchInterval, setRefetchInterval] = useState(10000) const [newReplicaRegion, setNewReplicaRegion] = useState() - const [selectedReplicaToResize, setSelectedReplicaToResize] = useState() const [selectedReplicaToDrop, setSelectedReplicaToDrop] = useState() const [selectedReplicaToRestart, setSelectedReplicaToRestart] = useState() @@ -86,7 +98,6 @@ const InstanceConfigurationUI = () => { isSuccessReplicas && isSuccessLoadBalancers ? generateNodes(primary, replicas, loadBalancers ?? [], { onSelectRestartReplica: setSelectedReplicaToRestart, - onSelectResizeReplica: setSelectedReplicaToResize, onSelectDropReplica: setSelectedReplicaToDrop, }) : [], @@ -134,10 +145,6 @@ const InstanceConfigurationUI = () => { [] ) - const onConfirmRestartReplica = () => { - console.log('Restart replica', selectedReplicaToRestart) - } - const setReactFlow = async () => { const graph = getDagreGraphLayout(nodes, edges) const { nodes: updatedNodes } = addRegionNodes(graph.nodes, graph.edges) @@ -168,9 +175,33 @@ const InstanceConfigurationUI = () => { {isSuccessReplicas && ( <>
- +
+ + + +
-
- -
@@ -453,8 +428,8 @@ const Addons = () => {

Current option:

{ipv4 !== undefined - ? 'IPv4 address is enabled' - : 'IPv4 address is not enabled'} + ? 'Dedicated IPv4 address is enabled' + : 'Dedicated IPv4 address is not enabled'}

@@ -470,7 +445,7 @@ const Addons = () => { !(canUpdateIPv4 || ipv4) } > - Change IPv4 address + Change dedicated IPv4 address
@@ -494,19 +469,6 @@ const Addons = () => { )} - {/* - - */}
diff --git a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx index 27f518c6d13..3286477e8e3 100644 --- a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx @@ -130,14 +130,14 @@ const IPv4SidePanel = () => { } header={
-

IPv4 address

+

Dedicated IPv4 address

@@ -147,10 +147,12 @@ const IPv4SidePanel = () => {

Direct connections to the database only work if your client is able to resolve IPv6 - addresses from January 26th. Enabling the IPv4 add-on allows you to directly connect to - your database via a IPv4 address. If you are connecting via our connection pooler, you - do not need this add-on as our pooler resolves to IPv4 addresses. You can check your - connection info in your{' '} + addresses. Enabling the dedicated IPv4 add-on allows you to directly connect to your + database via a IPv4 address. +

+

+ If you are connecting via our connection pooler, you do not need this add-on as our + pooler resolves to IPv4 addresses. You can check your connection info in your{' '} project database settings @@ -213,7 +215,7 @@ const IPv4SidePanel = () => { >

-

IPv4 address

+

Dedicated IPv4 address

diff --git a/apps/studio/public/img/ipv4-off--light.svg b/apps/studio/public/img/ipv4-off--light.svg index 21436d4df33..f4176a836ef 100644 --- a/apps/studio/public/img/ipv4-off--light.svg +++ b/apps/studio/public/img/ipv4-off--light.svg @@ -1,15 +1,17 @@ - + + + - - - + + + - - - + + + diff --git a/apps/studio/public/img/ipv4-off.svg b/apps/studio/public/img/ipv4-off.svg index faa01508c51..cf2cd5098d9 100644 --- a/apps/studio/public/img/ipv4-off.svg +++ b/apps/studio/public/img/ipv4-off.svg @@ -1,15 +1,19 @@ - + + + + - - - + + + - - - + + + + diff --git a/apps/studio/public/img/ipv4-on--light.svg b/apps/studio/public/img/ipv4-on--light.svg index 8c9906336fd..62090b8f6fe 100644 --- a/apps/studio/public/img/ipv4-on--light.svg +++ b/apps/studio/public/img/ipv4-on--light.svg @@ -1,16 +1,17 @@ - + + + - - - + + + + - - - + diff --git a/apps/studio/public/img/ipv4-on.svg b/apps/studio/public/img/ipv4-on.svg index ec02c70d9e4..ee1ccd47cb3 100644 --- a/apps/studio/public/img/ipv4-on.svg +++ b/apps/studio/public/img/ipv4-on.svg @@ -1,17 +1,17 @@ - + + - - - - - - + + + + + + + - - - + From 1e03efd8b50b4a498358ca79938de9da2e068165 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 27 Feb 2024 13:35:46 +0900 Subject: [PATCH 07/17] docs: Update Google sign-in docs for Android(Kotlin) to use credential manager and raw gotrue-kt (#21337) * docs: Focus Google sign-in guide for Kotlin on Android * refine the Kotlin code and add instructions to add dependencies * Add Kotlin multiplatform instructions on Kotlin tab * minor style tiding up * refine instructions for creating client IDs for Android apps * Update apps/docs/content/guides/auth/social-login/auth-google.mdx * Update apps/docs/content/guides/auth/social-login/auth-google.mdx Co-authored-by: Jan Tennert * ci: Autofix updates from GitHub workflow --------- Co-authored-by: Jan Tennert Co-authored-by: github-tidy-bot --- .../guides/auth/social-login/auth-google.mdx | 115 ++++++++++++++++-- 1 file changed, 108 insertions(+), 7 deletions(-) diff --git a/apps/docs/content/guides/auth/social-login/auth-google.mdx b/apps/docs/content/guides/auth/social-login/auth-google.mdx index 38f6936b717..6279925e6f3 100644 --- a/apps/docs/content/guides/auth/social-login/auth-google.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-google.mdx @@ -16,7 +16,7 @@ There are three general ways to use Sign in with Google, depending on the applic - Sign in on the web or in web-based apps - Using an OAuth flow initiated by Supabase Auth using [Google Identity Authorization with OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/oauth2/web-server). - Using a [personalized sign-in button](https://developers.google.com/identity/gsi/web/guides/personalized-button) or [One Tap](https://developers.google.com/identity/gsi/web/guides/display-google-one-tap) and [automatic sign-in](https://developers.google.com/identity/gsi/web/guides/automatic-sign-in-sign-out) for users already logged in to their Google account. -- Sign in in native Android apps and Chrome extensions +- Sign in in native Android apps and Chrome extensions. In some cases you're able to use the OAuth flow within web-based native apps such as with React Native, Expo or other similar frameworks. It is best practice to use native Sign in with Google capabilities whenever possible. @@ -232,7 +232,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf - ## Using native sign in with Google in Flutter + ## Using native sign-in with Google on Flutter Native Google sign in with Supabase is done through the [google_sign_in](https://pub.dev/packages/google_sign_in) package for iOS and Android. Follow the instructions on the [google_sign_in](https://pub.dev/packages/google_sign_in) package for plugin specific settings. @@ -301,11 +301,114 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf - Unlike the OAuth flow which requires the use of a web browser, the native Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developers.google.com/android/reference/com/google/android/gms/auth/api/identity/package-summary) to prompt the user for consent. Note that native sign-in has been rebranded as _One Tap sign-in on Android_ by Google, which you should not confuse with _One Tap sign in for web_, as mentioned below. + ## Using Google sign-in on Android + + The Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developer.android.com/training/sign-in/credential-manager) to prompt the user for consent. When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth. - When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`. + **Note:** You have to create OAuth client IDs for both a Web and Android application. The Web client ID is the one used in your Android app. + + Add the following dependencies to your app. You can find the latest version of `credentials` [here](https://developer.android.com/jetpack/androidx/releases/credentials) and `googleid` [here](https://developers.google.com/identity/android-credential-manager/releases). + + ```kotlin + implementation("androidx.credentials:credentials:") + implementation ("com.google.android.libraries.identity.googleid:googleid:") + + // optional - needed for credentials support from play services, for devices running + // Android 13 and below. + implementation("androidx.credentials:credentials-play-services-auth:") + ``` + + Add the following proguard rules to your `proguard-rules.pro` file: + + ```proguard + -if class androidx.credentials.CredentialManager + -keep class androidx.credentials.playservices.** { + *; + } + ``` + + Follow the code sample below to implement native Google sign-in with Supabase using Credential Manager in your Android app. + + ```kotlin + @Composable + fun GoogleSignInButton() { + val coroutineScope = rememberCoroutineScope() + val context = LocalContext.current + + val onClick: () -> Unit = { + val credentialManager = CredentialManager.create(context) + + // Generate a nonce and hash it with sha-256 + // Providing a nonce is optional but recommended + val rawNonce = UUID.randomUUID().toString() // Generate a random String. UUID should be sufficient, but can also be any other random string. + val bytes = rawNonce.toString().toByteArray() + val md = MessageDigest.getInstance("SHA-256") + val digest = md.digest(bytes) + val hashedNonce = digest.fold("") { str, it -> str + "%02x".format(it) } // Hashed nonce to be passed to Google sign-in + + + val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(false) + .setServerClientId("WEB_GOOGLE_CLIENT_ID") + .setNonce(hashedNonce) // Provide the nonce if you have one + .build() + + val request: GetCredentialRequest = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + + coroutineScope.launch { + try { + val result = credentialManager.getCredential( + request = request, + context = context, + ) + + val googleIdTokenCredential = GoogleIdTokenCredential + .createFrom(result.credential.data) + + val googleIdToken = googleIdTokenCredential.idToken + + supabase.auth.signInWith(IDToken) { + idToken = googleIdToken + provider = Google + nonce = rawNonce + } + + // Handle successful sign-in + } catch (e: GetCredentialException) { + // Handle GetCredentialException thrown by `credentialManager.getCredential()` + } catch (e: GoogleIdTokenParsingException) { + // Handle GoogleIdTokenParsingException thrown by `GoogleIdTokenCredential.createFrom()` + } catch (e: RestException) { + // Handle RestException thrown by Supabase + } catch (e: Exception) { + // Handle unknown exceptions + } + } + } + + Button( + onClick = onClick, + ) { + Text("Sign in with Google") + } + } + ``` + + ### Configuration [#android-configuration] + + 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. You need two client IDs, a web client ID and Android client ID. When creating a new Android OAuth client ID, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app. + 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. + 3. Finally, add the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. + + Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in. + + ## Using Google sign-in with Kotlin Multiplatform + + When using [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`. Alternatively, you can have a look at [Google One Tap](https://developers.google.com/identity/one-tap/android/overview), just login with the IdToken after you receive it. **Initialize the Supabase Client** @@ -343,7 +446,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf } ``` - ### Configuration [#kotlin-configuration-native-app] + ### Configuration [#kotlin-multiplatform-configuration] 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for. @@ -353,8 +456,6 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. - Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in. - From 4e039c9bf5d4b17d3cc9c8f03a0e3c98c3ad2a5f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 27 Feb 2024 13:14:21 +0800 Subject: [PATCH 08/17] Remove config cat flag for read replicas (#21587) * Remove config cat flag for read replicas * Remove --- .../interfaces/SQLEditor/SQLEditor.tsx | 55 ++++++++----------- .../SQLEditor/UtilityPanel/UtilityActions.tsx | 6 +- .../interfaces/Settings/API/ServiceList.tsx | 13 ++--- .../DatabaseConnectionString.tsx | 3 +- .../DatabaseSettings/DatabaseSettings.tsx | 19 +++---- .../Settings/Database/PoolingModesModal.tsx | 3 +- .../Settings/Database/UsePoolerCheckbox.tsx | 3 +- .../Infrastructure/InfrastructureInfo.tsx | 5 +- .../Settings/Logs/LogsPreviewer.tsx | 12 ++-- .../Settings/Logs/PreviewFilterPanel.tsx | 3 - .../read-replicas/load-balancers-query.ts | 8 +-- .../data/read-replicas/replicas-query.ts | 13 ++--- .../read-replicas/replicas-status-query.ts | 10 +--- apps/studio/pages/projects.tsx | 2 +- 14 files changed, 60 insertions(+), 95 deletions(-) diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx index d71235efc0a..8a1bcb68464 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -4,36 +4,8 @@ import { AnimatePresence, motion } from 'framer-motion' import dynamic from 'next/dynamic' import { useRouter } from 'next/router' import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' -import { format } from 'sql-formatter' - -import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' -import { useSqlEditMutation } from 'data/ai/sql-edit-mutation' -import { useSqlGenerateMutation } from 'data/ai/sql-generate-mutation' -import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation' -import { SqlSnippet } from 'data/content/sql-snippets-query' -import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query' -import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' -import { useFormatQueryMutation } from 'data/sql/format-sql-query' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { isError } from 'data/utils/error-check' -import { - useFlag, - useLocalStorageQuery, - useSelectedOrganization, - useSelectedProject, - useStore, -} from 'hooks' -import { IS_PLATFORM, LOCAL_STORAGE_KEYS, OPT_IN_TAGS } from 'lib/constants' -import { uuidv4 } from 'lib/helpers' -import { useProfile } from 'lib/profile' -import { wrapWithRoleImpersonation } from 'lib/role-impersonation' -import Telemetry from 'lib/telemetry' import toast from 'react-hot-toast' -import { useAppStateSnapshot } from 'state/app-state' -import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' -import { isRoleImpersonationEnabled, useGetImpersonatedRole } from 'state/role-impersonation-state' -import { getSqlEditorStateSnapshot, useSqlEditorStateSnapshot } from 'state/sql-editor' +import { format } from 'sql-formatter' import { AiIconAnimation, Button, @@ -53,6 +25,28 @@ import { ResizablePanelGroup, cn, } from 'ui' + +import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' +import { useSqlEditMutation } from 'data/ai/sql-edit-mutation' +import { useSqlGenerateMutation } from 'data/ai/sql-generate-mutation' +import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation' +import { SqlSnippet } from 'data/content/sql-snippets-query' +import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query' +import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' +import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' +import { useFormatQueryMutation } from 'data/sql/format-sql-query' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' +import { isError } from 'data/utils/error-check' +import { useLocalStorageQuery, useSelectedOrganization, useSelectedProject, useStore } from 'hooks' +import { IS_PLATFORM, LOCAL_STORAGE_KEYS, OPT_IN_TAGS } from 'lib/constants' +import { uuidv4 } from 'lib/helpers' +import { useProfile } from 'lib/profile' +import { wrapWithRoleImpersonation } from 'lib/role-impersonation' +import Telemetry from 'lib/telemetry' +import { useAppStateSnapshot } from 'state/app-state' +import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' +import { isRoleImpersonationEnabled, useGetImpersonatedRole } from 'state/role-impersonation-state' +import { getSqlEditorStateSnapshot, useSqlEditorStateSnapshot } from 'state/sql-editor' import { subscriptionHasHipaaAddon } from '../Billing/Subscription/Subscription.utils' import AISchemaSuggestionPopover from './AISchemaSuggestionPopover' import { sqlAiDisclaimerComment, untitledSnippetTitle } from './SQLEditor.constants' @@ -122,8 +116,7 @@ const SQLEditor = () => { const [hasSelection, setHasSelection] = useState(false) const inputRef = useRef(null) - const readReplicasEnabled = useFlag('readReplicas') - const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled + const showReadReplicasUI = project?.is_read_replicas_enabled const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { data: databases, isSuccess: isSuccessReadReplicas } = useReadReplicasQuery({ diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx index 74387b88bda..490f135e622 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx @@ -5,7 +5,7 @@ import { Button, IconAlignLeft, IconCommand, IconCornerDownLeft } from 'ui' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector' import DatabaseSelector from 'components/ui/DatabaseSelector' -import { useFlag, useSelectedProject } from 'hooks' +import { useSelectedProject } from 'hooks' import FavoriteButton from './FavoriteButton' import SavingIndicator from './SavingIndicator' @@ -28,9 +28,7 @@ const UtilityActions = ({ }: UtilityActionsProps) => { const os = detectOS() const project = useSelectedProject() - const readReplicasEnabled = useFlag('readReplicas') - - const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled + const showReadReplicasUI = project?.is_read_replicas_enabled return ( <> diff --git a/apps/studio/components/interfaces/Settings/API/ServiceList.tsx b/apps/studio/components/interfaces/Settings/API/ServiceList.tsx index 37ba646a136..72c50f94124 100644 --- a/apps/studio/components/interfaces/Settings/API/ServiceList.tsx +++ b/apps/studio/components/interfaces/Settings/API/ServiceList.tsx @@ -8,19 +8,19 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte import DatabaseSelector from 'components/ui/DatabaseSelector' import Panel from 'components/ui/Panel' import { DisplayApiSettings } from 'components/ui/ProjectSettings' +import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { configKeys } from 'data/config/keys' import { useProjectApiQuery } from 'data/config/project-api-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' +import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useFlag, useStore } from 'hooks' +import { useStore } from 'hooks' +import { PROJECT_STATUS } from 'lib/constants' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from './API.constants' import JWTSettings from './JWTSettings' import PostgrestConfig from './PostgrestConfig' -import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { PROJECT_STATUS } from 'lib/constants' -import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' const ServiceList = () => { const { ui } = useStore() @@ -29,8 +29,7 @@ const ServiceList = () => { const { ref: projectRef, source } = useParams() const state = useDatabaseSelectorStateSnapshot() - const readReplicasEnabled = useFlag('readReplicas') - const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled + const showReadReplicasUI = project?.is_read_replicas_enabled const { data: settings, isError } = useProjectApiQuery({ projectRef, @@ -91,7 +90,7 @@ const ServiceList = () => { }, [jwtSecretUpdateStatus]) useEffect(() => { - if (readReplicasEnabled && source !== undefined) { + if (showReadReplicasUI && source !== undefined) { state.setSelectedDatabaseId('load-balancer') } }, [source]) diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx index a8373e8849e..78e327fd269 100644 --- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx @@ -23,7 +23,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectSettingsQuery } from 'data/config/project-settings-query' import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useFlag } from 'hooks' import { pluckObjectFields } from 'lib/helpers' import Telemetry from 'lib/telemetry' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' @@ -58,7 +57,7 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin const snap = useDatabaseSettingsStateSnapshot() const state = useDatabaseSelectorStateSnapshot() const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext() - const readReplicasEnabled = useFlag('readReplicas') && projectDetails?.is_read_replicas_enabled + const readReplicasEnabled = projectDetails?.is_read_replicas_enabled const connectionStringsRef = useRef(null) const [poolingMode, setPoolingMode] = useState<'transaction' | 'session' | 'statement'>('session') diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx index 5eebfc8fbad..cccd2e741f3 100644 --- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx @@ -1,6 +1,13 @@ import { useParams, useTelemetryProps } from 'common' import { useRouter } from 'next/router' import { useEffect, useRef, useState } from 'react' +import { + AlertDescription_Shadcn_, + AlertTitle_Shadcn_, + Alert_Shadcn_, + IconAlertCircle, + Input, +} from 'ui' import AlertError from 'components/ui/AlertError' import DatabaseSelector from 'components/ui/DatabaseSelector' @@ -9,18 +16,11 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectSettingsQuery } from 'data/config/project-settings-query' import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useFlag, useSelectedProject } from 'hooks' +import { useSelectedProject } from 'hooks' import { pluckObjectFields } from 'lib/helpers' import Telemetry from 'lib/telemetry' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { useDatabaseSettingsStateSnapshot } from 'state/database-settings' -import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, - IconAlertCircle, - Input, -} from 'ui' import { IPv4DeprecationNotice } from '../IPv4DeprecationNotice' import { UsePoolerCheckbox } from '../UsePoolerCheckbox' import ResetDbPassword from './ResetDbPassword' @@ -33,8 +33,7 @@ const DatabaseSettings = () => { const state = useDatabaseSelectorStateSnapshot() const selectedProject = useSelectedProject() - const readReplicasEnabled = useFlag('readReplicas') - const showReadReplicasUI = readReplicasEnabled && selectedProject?.is_read_replicas_enabled + const showReadReplicasUI = selectedProject?.is_read_replicas_enabled const connectionStringsRef = useRef(null) const [poolingMode, setPoolingMode] = useState<'transaction' | 'session' | 'statement'>('session') diff --git a/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx b/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx index ed08d417d25..c6b9c395d7c 100644 --- a/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx +++ b/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx @@ -18,7 +18,6 @@ import { import { Markdown } from 'components/interfaces/Markdown' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query' -import { useFlag } from 'hooks' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { useDatabaseSettingsStateSnapshot } from 'state/database-settings' @@ -27,7 +26,7 @@ export const PoolingModesModal = () => { const { project } = useProjectContext() const snap = useDatabaseSettingsStateSnapshot() const state = useDatabaseSelectorStateSnapshot() - const readReplicasEnabled = useFlag('readReplicas') && project?.is_read_replicas_enabled + const readReplicasEnabled = project?.is_read_replicas_enabled const { data } = usePoolingConfigurationQuery({ projectRef: projectRef }) const primaryConfig = readReplicasEnabled diff --git a/apps/studio/components/interfaces/Settings/Database/UsePoolerCheckbox.tsx b/apps/studio/components/interfaces/Settings/Database/UsePoolerCheckbox.tsx index 7b7a7533142..33592a1d8ed 100644 --- a/apps/studio/components/interfaces/Settings/Database/UsePoolerCheckbox.tsx +++ b/apps/studio/components/interfaces/Settings/Database/UsePoolerCheckbox.tsx @@ -18,7 +18,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { usePgBouncerStatus } from 'data/config/pgbouncer-enabled-query' import { useProjectSettingsQuery } from 'data/config/project-settings-query' import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query' -import { useFlag } from 'hooks' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { useDatabaseSettingsStateSnapshot } from 'state/database-settings' @@ -47,7 +46,7 @@ export const UsePoolerCheckbox = ({ const { data: settings, isSuccess: isSuccessSettings } = useProjectSettingsQuery({ projectRef }) const { data: pgBouncerStatus } = usePgBouncerStatus({ projectRef: projectRef }) - const readReplicasEnabled = useFlag('readReplicas') && project?.is_read_replicas_enabled + const readReplicasEnabled = project?.is_read_replicas_enabled const poolingConfiguration = readReplicasEnabled ? data?.find((x) => x.identifier === state.selectedDatabaseId) : data?.find((x) => x.database_type === 'PRIMARY') diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx index 766de0d1e44..ac36896a3fd 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx @@ -23,7 +23,7 @@ import AlertError from 'components/ui/AlertError' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useProjectUpgradeEligibilityQuery } from 'data/config/project-upgrade-eligibility-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useFlag, useIsFeatureEnabled } from 'hooks' +import { useIsFeatureEnabled } from 'hooks' import { AWS_REGIONS, FLY_REGIONS } from 'lib/constants' import ProjectUpgradeAlert from '../General/Infrastructure/ProjectUpgradeAlert' import InstanceConfiguration from './InfrastructureConfiguration/InstanceConfiguration' @@ -54,8 +54,7 @@ const InfrastructureInfo = () => { const currentPgVersion = (current_app_version ?? '').split('supabase-postgres-')[1] const latestPgVersion = (latest_app_version ?? '').split('supabase-postgres-')[1] - const readReplicasEnabled = useFlag('readReplicas') - const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled + const showReadReplicasUI = project?.is_read_replicas_enabled const hasReadReplicas = (databases ?? []).length > 1 const subject = 'Request%20for%20Postgres%20upgrade%20for%20project' const message = `Upgrade information:%0A• Manual intervention reason: ${requires_manual_intervention}` diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx index 92b94adb206..34cea58a523 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx @@ -15,17 +15,17 @@ import { maybeShowUpgradePrompt, } from 'components/interfaces/Settings/Logs' import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilterPanel' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import LoadingOpacity from 'components/ui/LoadingOpacity' import ShimmerLine from 'components/ui/ShimmerLine' +import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' +import { useSelectedOrganization } from 'hooks' import useLogsPreview from 'hooks/analytics/useLogsPreview' import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt' +import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { LOGS_TABLES } from './Logs.constants' import UpgradePrompt from './UpgradePrompt' -import { useFlag, useSelectedOrganization } from 'hooks' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' -import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' /** * Acts as a container component for the entire log display @@ -58,7 +58,7 @@ export const LogsPreviewer = ({ const { project } = useProjectContext() const organization = useSelectedOrganization() const state = useDatabaseSelectorStateSnapshot() - const readReplicasEnabled = useFlag('readReplicas') && project?.is_read_replicas_enabled + const readReplicasEnabled = project?.is_read_replicas_enabled const { data: databases, isSuccess } = useReadReplicasQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) diff --git a/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx b/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx index 193375a0ef0..845d27b9c10 100644 --- a/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx @@ -17,7 +17,6 @@ import { import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import CSVButton from 'components/ui/CSVButton' import DatabaseSelector from 'components/ui/DatabaseSelector' -import { useFlag } from 'hooks' import { Filters, LogSearchCallback, LogTemplate, PREVIEWER_DATEPICKER_HELPERS } from '.' import DatePickers from './Logs.DatePickers' import { FILTER_OPTIONS, LogsTableName } from './Logs.constants' @@ -69,12 +68,10 @@ const PreviewFilterPanel = ({ }: PreviewFilterPanelProps) => { const router = useRouter() const [search, setSearch] = useState('') - const readReplicasEnabled = useFlag('readReplicas') const { project } = useProjectContext() // [Joshen] These are the routes tested that can show replica logs const showDatabaseSelector = - readReplicasEnabled && project?.is_read_replicas_enabled && ['/project/[ref]/logs/edge-logs', '/project/[ref]/logs/pooler-logs'].includes(router.pathname) diff --git a/apps/studio/data/read-replicas/load-balancers-query.ts b/apps/studio/data/read-replicas/load-balancers-query.ts index e0fb4fff4d5..e80df9f15ed 100644 --- a/apps/studio/data/read-replicas/load-balancers-query.ts +++ b/apps/studio/data/read-replicas/load-balancers-query.ts @@ -3,7 +3,6 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { components } from 'data/api' import { get, handleError } from 'data/fetchers' import { useProjectDetailQuery } from 'data/projects/project-detail-query' -import { useFlag } from 'hooks' import { ResponseError } from 'types' import { replicaKeys } from './keys' @@ -35,18 +34,13 @@ export const useLoadBalancersQuery = ( { projectRef }: LoadBalancersVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => { - const readReplicasEnabled = useFlag('readReplicas') const { data } = useProjectDetailQuery({ ref: projectRef }) return useQuery( replicaKeys.loadBalancers(projectRef), ({ signal }) => getLoadBalancers({ projectRef }, signal), { - enabled: - enabled && - data?.is_read_replicas_enabled && - readReplicasEnabled && - typeof projectRef !== 'undefined', + enabled: enabled && data?.is_read_replicas_enabled && typeof projectRef !== 'undefined', ...options, } ) diff --git a/apps/studio/data/read-replicas/replicas-query.ts b/apps/studio/data/read-replicas/replicas-query.ts index dc95d6c9ae8..a4ad7f648db 100644 --- a/apps/studio/data/read-replicas/replicas-query.ts +++ b/apps/studio/data/read-replicas/replicas-query.ts @@ -1,10 +1,10 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' + +import { components } from 'data/api' import { get } from 'data/fetchers' +import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { ResponseError } from 'types' import { replicaKeys } from './keys' -import { components } from 'data/api' -import { useFlag } from 'hooks' -import { useProjectDetailQuery } from 'data/projects/project-detail-query' export type ReadReplicasVariables = { projectRef?: string @@ -31,18 +31,13 @@ export const useReadReplicasQuery = ( { projectRef }: ReadReplicasVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => { - const readReplicasEnabled = useFlag('readReplicas') const { data } = useProjectDetailQuery({ ref: projectRef }) return useQuery( replicaKeys.list(projectRef), ({ signal }) => getReadReplicas({ projectRef }, signal), { - enabled: - enabled && - data?.is_read_replicas_enabled && - readReplicasEnabled && - typeof projectRef !== 'undefined', + enabled: enabled && data?.is_read_replicas_enabled && typeof projectRef !== 'undefined', ...options, } ) diff --git a/apps/studio/data/read-replicas/replicas-status-query.ts b/apps/studio/data/read-replicas/replicas-status-query.ts index 0276ca0e827..a0430316a42 100644 --- a/apps/studio/data/read-replicas/replicas-status-query.ts +++ b/apps/studio/data/read-replicas/replicas-status-query.ts @@ -1,9 +1,8 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { get } from 'data/fetchers' +import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { ResponseError } from 'types' import { replicaKeys } from './keys' -import { useFlag } from 'hooks' -import { useProjectDetailQuery } from 'data/projects/project-detail-query' export type ReadReplicasStatusesVariables = { projectRef?: string @@ -34,18 +33,13 @@ export const useReadReplicasStatusesQuery = ( ...options }: UseQueryOptions = {} ) => { - const readReplicasEnabled = useFlag('readReplicas') const { data } = useProjectDetailQuery({ ref: projectRef }) return useQuery( replicaKeys.statuses(projectRef), ({ signal }) => getReadReplicasStatuses({ projectRef }, signal), { - enabled: - enabled && - data?.is_read_replicas_enabled && - readReplicasEnabled && - typeof projectRef !== 'undefined', + enabled: enabled && data?.is_read_replicas_enabled && typeof projectRef !== 'undefined', ...options, } ) diff --git a/apps/studio/pages/projects.tsx b/apps/studio/pages/projects.tsx index cf8bda6dba0..4ca933cd0a9 100644 --- a/apps/studio/pages/projects.tsx +++ b/apps/studio/pages/projects.tsx @@ -2,8 +2,8 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import ProjectList from 'components/interfaces/Home/ProjectList' -import { AccountLayout } from 'components/layouts' import HomePageActions from 'components/interfaces/HomePageActions' +import { AccountLayout } from 'components/layouts' import AlertError from 'components/ui/AlertError' import Connecting from 'components/ui/Loading/Loading' import { useOrganizationsQuery } from 'data/organizations/organizations-query' From e444d337f1509a6eb1f67e19a1b9a6aab80c697d Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Tue, 27 Feb 2024 01:38:41 -0500 Subject: [PATCH 09/17] chore: remove ipv4 deprecation banners (#21589) --- .../docs/components/IPv4DeprecationBanner.tsx | 45 ------------------- apps/docs/pages/_app.tsx | 2 - apps/www/components/Nav/index.tsx | 5 +-- .../src/layout/banners/IPV4Banner/index.tsx | 28 ------------ packages/ui/src/layout/banners/index.tsx | 1 - 5 files changed, 1 insertion(+), 80 deletions(-) delete mode 100644 apps/docs/components/IPv4DeprecationBanner.tsx delete mode 100644 packages/ui/src/layout/banners/IPV4Banner/index.tsx diff --git a/apps/docs/components/IPv4DeprecationBanner.tsx b/apps/docs/components/IPv4DeprecationBanner.tsx deleted file mode 100644 index f44662e2069..00000000000 --- a/apps/docs/components/IPv4DeprecationBanner.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import Link from 'next/link' -import { memo } from 'react' - -import { Button, IconExternalLink, cn } from 'ui' - -export const IPv4DeprecationBanner = memo(() => { - return ( -

-

- Prepare for the PgBouncer and IPv4 deprecations on 26th January 2024{' '} - - Learn more - -

-
- -
-
- ) -}) -IPv4DeprecationBanner.displayName = 'IPv4DeprecationBanner' diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index b1c048b72a4..c75ece1b38b 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -14,7 +14,6 @@ import { CommandMenuProvider, PortalToast, TabsProvider } from 'ui' import { useConsent } from 'ui-patterns/ConsentToast' import Favicons from '~/components/Favicons' -import { IPv4DeprecationBanner } from '~/components/IPv4DeprecationBanner' import SiteLayout from '~/layouts/SiteLayout' import { IS_PLATFORM, IS_PREVIEW } from '~/lib/constants' import { unauthedAllowedPost } from '~/lib/fetch/fetchWrappers' @@ -223,7 +222,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
- diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index cfd6655978b..22d3baf20a2 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -5,7 +5,7 @@ import { useRouter } from 'next/router' import { useTheme } from 'next-themes' import { useWindowSize } from 'react-use' -import { Announcement, Button, IPV4Banner, cn } from 'ui' +import { Button, cn } from 'ui' import { NavigationMenu, NavigationMenuContent, @@ -64,9 +64,6 @@ const Nav = (props: Props) => { return ( <> - - -
{ - return ( -
-
-
-
-

Prepare for the PgBouncer and IPv4 deprecations on 26th January 2024

-
- -
-
-
- ) -}) - -export default index diff --git a/packages/ui/src/layout/banners/index.tsx b/packages/ui/src/layout/banners/index.tsx index 8ba69528077..49a7d6ec582 100644 --- a/packages/ui/src/layout/banners/index.tsx +++ b/packages/ui/src/layout/banners/index.tsx @@ -1,3 +1,2 @@ export { default as Announcement } from './Announcement' -export { default as IPV4Banner } from './IPV4Banner' export { default as LW8CountdownBanner } from './LW8CountdownBanner/LW8CountdownBanner' From 95991e313509707420bd73597740d60e2f289970 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 27 Feb 2024 15:18:07 +0800 Subject: [PATCH 10/17] Fix view definition query (#21590) --- apps/studio/data/database/view-definition-query.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/studio/data/database/view-definition-query.ts b/apps/studio/data/database/view-definition-query.ts index 21cbd47ae2a..39e3b96317b 100644 --- a/apps/studio/data/database/view-definition-query.ts +++ b/apps/studio/data/database/view-definition-query.ts @@ -7,10 +7,8 @@ type GetViewDefinition = { } export const getViewDefinitionQuery = ({ schema, name }: GetViewDefinition) => { - const fullName = [schema, name].filter(Boolean).join('.') - const sql = /* SQL */ ` - select pg_get_viewdef('${fullName}', true) as definition + select pg_get_viewdef(to_regclass('"${schema}"."${name}"'), true) as definition `.trim() return sql @@ -39,6 +37,7 @@ export const useViewDefinitionQuery = Date: Tue, 27 Feb 2024 10:13:07 +0100 Subject: [PATCH 11/17] fix: Make the playwright tests always succeed (#21591) * Make the playwright tests always succeed. * Push a change to trigger the tests. * Try moving the continue-on-error on the failed step. --- .github/workflows/playwright.yml | 2 ++ .../studio/components/interfaces/Auth/Users/AddUserDropdown.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 6e3c4d377e4..fde83f5826d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -39,6 +39,8 @@ jobs: run: npx playwright install --with-deps - name: Run Playwright tests run: npm run test:playwright + # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable. + continue-on-error: true # - uses: actions/upload-artifact@v3 # if: always() # with: diff --git a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx index 5813fef3ad8..1bf095ff73a 100644 --- a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx @@ -15,8 +15,8 @@ import { import { useCheckPermissions } from 'hooks' import { IS_PLATFORM } from 'lib/constants' -import CreateUserModal from './CreateUserModal' import InviteUserModal from './InviteUserModal' +import CreateUserModal from './CreateUserModal' export type AddUserDropdownProps = { projectKpsVersion?: string From a122e926e96e6f6e0d17aa1ec1ff475c0bcf2abf Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 27 Feb 2024 17:49:09 +0800 Subject: [PATCH 12/17] RR node show restoring badge (#21603) --- .../Infrastructure/InfrastructureConfiguration/InstanceNode.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx index 9c901748547..e4287339065 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx @@ -194,6 +194,8 @@ export const ReplicaNode = ({ data }: NodeProps) => { Healthy ) : status === PROJECT_STATUS.COMING_UP ? ( Coming up + ) : status === PROJECT_STATUS.RESTORING ? ( + Restoring ) : ( Unhealthy )} From f4f5a8b7e9482117b77d819376badf0777650a64 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Tue, 27 Feb 2024 10:57:43 +0100 Subject: [PATCH 13/17] chore: Add telemetry events for the realtime inspector (#20672) * Added telemetry events for the realtime inspector. * Add some fixes. * Add headers to the realtime inspector. * Minor fixes. --- .../Inspector/ChooseChannelPopover/index.tsx | 14 +++++++++++ .../interfaces/Realtime/Inspector/Header.tsx | 22 ++++++++++++++++- .../Realtime/Inspector/MessageSelection.tsx | 23 +++++++++++++++++- .../Realtime/Inspector/MessagesTable.tsx | 20 +++++++++++++--- .../Inspector/RealtimeFilterPopover/index.tsx | 16 ++++++++++++- .../Inspector/RealtimeTokensPopover/index.tsx | 24 ++++++++++++++++++- .../interfaces/Realtime/Inspector/index.tsx | 15 +++++++++++- .../Realtime/Inspector/useRealtimeMessages.ts | 9 +++++-- apps/studio/lib/telemetry.ts | 13 ++++++++-- 9 files changed, 144 insertions(+), 12 deletions(-) diff --git a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx index e1d9be354c6..9719b931570 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx @@ -1,5 +1,7 @@ import { zodResolver } from '@hookform/resolvers/zod' +import { useTelemetryProps } from 'common' import Link from 'next/link' +import { useRouter } from 'next/router' import { Dispatch, SetStateAction, useState } from 'react' import { useForm } from 'react-hook-form' import { @@ -16,6 +18,7 @@ import { } from 'ui' import * as z from 'zod' +import Telemetry from 'lib/telemetry' import { RealtimeConfig } from '../useRealtimeMessages' interface ChooseChannelPopoverProps { @@ -25,6 +28,8 @@ interface ChooseChannelPopoverProps { export const ChooseChannelPopover = ({ config, onChangeConfig }: ChooseChannelPopoverProps) => { const [open, setOpen] = useState(false) + const telemetryProps = useTelemetryProps() + const router = useRouter() const FormSchema = z.object({ channel: z.string() }) const form = useForm>({ @@ -44,6 +49,15 @@ export const ChooseChannelPopover = ({ config, onChangeConfig }: ChooseChannelPo const onSubmit = () => { setOpen(false) + Telemetry.sendEvent( + { + category: 'realtime_inspector', + action: 'started_listening_to_channel_in_input_channel_popover', + label: 'realtime_inspector_config', + }, + telemetryProps, + router + ) onChangeConfig({ ...config, channelName: form.getValues('channel'), enabled: true }) } diff --git a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx index 11c7e1bdd55..d400408ac8b 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx @@ -3,6 +3,9 @@ import { PlayCircle, StopCircle } from 'lucide-react' import { Dispatch, SetStateAction } from 'react' import { Button } from 'ui' +import { useTelemetryProps } from 'common' +import Telemetry from 'lib/telemetry' +import { useRouter } from 'next/router' import { ChooseChannelPopover } from './ChooseChannelPopover' import { RealtimeFilterPopover } from './RealtimeFilterPopover' import { RealtimeTokensPopover } from './RealtimeTokensPopover' @@ -14,6 +17,9 @@ interface HeaderProps { } export const Header = ({ config, onChangeConfig }: HeaderProps) => { + const telemetryProps = useTelemetryProps() + const router = useRouter() + return (
@@ -27,7 +33,21 @@ export const Header = ({ config, onChangeConfig }: HeaderProps) => { className="rounded-l-none border-l-0" disabled={config.channelName.length === 0} icon={config.enabled ? : } - onClick={() => onChangeConfig({ ...config, enabled: !config.enabled })} + onClick={() => { + onChangeConfig({ ...config, enabled: !config.enabled }) + if (!config.enabled) { + // the user has clicked to start listening + Telemetry.sendEvent( + { + category: 'realtime_inspector', + action: 'started_listening', + label: 'realtime_inspector_config', + }, + telemetryProps, + router + ) + } + }} > {config.enabled ? `Stop listening` : `Start listening`} diff --git a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx index ad437a22520..dde4f3d36ad 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx @@ -1,7 +1,10 @@ +import { useTelemetryProps } from 'common' +import { useRouter } from 'next/router' import { useMemo } from 'react' import { Button, IconX, cn } from 'ui' import CopyButton from 'components/ui/CopyButton' +import Telemetry from 'lib/telemetry' import { LogData } from './Messages.types' import { SelectedRealtimeMessagePanel } from './SelectedRealtimeMessagePanel' @@ -11,6 +14,9 @@ export interface MessageSelectionProps { } const MessageSelection = ({ log, onClose }: MessageSelectionProps) => { + const telemetryProps = useTelemetryProps() + const router = useRouter() + const selectionText = useMemo(() => { return JSON.stringify(log, null, 2) }, [log]) @@ -64,7 +70,22 @@ const MessageSelection = ({ log, onClose }: MessageSelectionProps) => {
- + { + Telemetry.sendEvent( + { + category: 'realtime_inspector', + action: 'copied_message', + label: 'realtime_inspector_results', + }, + telemetryProps, + router + ) + }} + />