From 03f381cffa7db202e9d407706a5b70dee05f80be Mon Sep 17 00:00:00 2001 From: Sean Oliver <882952+seanoliver@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:48:56 -0700 Subject: [PATCH 1/3] fix: remove PostHog wildcard domain from CSP (#38759) Remove the overly permissive *.posthog.com wildcard from CSP configuration. The PostHog reverse proxy at ph.supabase.com/ph.supabase.green handles all necessary PostHog endpoints including /flags for feature flags. This improves security by only allowing our controlled reverse proxy domains rather than the entire PostHog infrastructure. --- apps/studio/csp.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/studio/csp.js b/apps/studio/csp.js index 1f39cd02cd806..5c94f4093e19f 100644 --- a/apps/studio/csp.js +++ b/apps/studio/csp.js @@ -70,8 +70,6 @@ const SUPABASE_ASSETS_URL = ? 'https://frontend-assets.supabase.green' : 'https://frontend-assets.supabase.com' const POSTHOG_URL = isDevOrStaging ? 'https://ph.supabase.green' : 'https://ph.supabase.com' -// Required for feature flags and other PostHog features -const POSTHOG_EXTERNAL_URL = 'https://*.posthog.com' const USERCENTRICS_URLS = 'https://*.usercentrics.eu' const USERCENTRICS_APP_URL = 'https://app.usercentrics.eu' @@ -104,7 +102,6 @@ module.exports.getCSP = function getCSP() { STAPE_URL, GOOGLE_MAPS_API_URL, POSTHOG_URL, - POSTHOG_EXTERNAL_URL, ...(!!NIMBUS_PROD_PROJECTS_URL ? [NIMBUS_PROD_PROJECTS_URL, NIMBUS_PROD_PROJECTS_URL_WS] : []), ] const SCRIPT_SRC_URLS = [ @@ -114,7 +111,6 @@ module.exports.getCSP = function getCSP() { SUPABASE_ASSETS_URL, STAPE_URL, POSTHOG_URL, - POSTHOG_EXTERNAL_URL, ] const FRAME_SRC_URLS = [HCAPTCHA_ASSET_URL, STRIPE_JS_URL, STAPE_URL] const IMG_SRC_URLS = [ From 77561e8e097d83a6bf7ea97b23aadba83e4ea96b Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Wed, 17 Sep 2025 10:31:01 +0800 Subject: [PATCH 2/3] chore: replace branch id with branch ref (#38735) * chore: remove unused mutation to disable branching * chore: use branch ref in delete mutation * chore: use branch ref in diff query * chore: use branch ref in push mutation * chore: use branch ref in detail query * chore: use branch ref in update mutation * Update apps/studio/data/branches/branch-query.ts Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> --- .../BranchManagement/EditBranchModal.tsx | 6 +- .../interfaces/BranchManagement/Overview.tsx | 10 +- .../interfaces/BranchManagement/ReviewRow.tsx | 12 +- .../GitHubIntegrationConnectionForm.tsx | 8 +- .../LayoutHeader/MergeRequestButton.tsx | 4 +- .../data/branches/branch-delete-mutation.ts | 10 +- .../studio/data/branches/branch-diff-query.ts | 16 +- .../data/branches/branch-merge-mutation.ts | 11 +- .../data/branches/branch-push-mutation.ts | 6 +- apps/studio/data/branches/branch-query.ts | 16 +- .../data/branches/branch-reset-mutation.ts | 6 +- .../data/branches/branch-update-mutation.ts | 6 +- .../branches/branches-disable-mutation.ts | 69 -------- apps/studio/data/branches/branches-query.ts | 2 +- apps/studio/data/branches/keys.ts | 8 +- .../hooks/branches/useBranchMergeDiff.ts | 6 +- .../pages/project/[ref]/branches/index.tsx | 8 +- .../project/[ref]/branches/merge-requests.tsx | 167 +++++++++--------- apps/studio/pages/project/[ref]/merge.tsx | 26 ++- 19 files changed, 159 insertions(+), 238 deletions(-) delete mode 100644 apps/studio/data/branches/branches-disable-mutation.ts diff --git a/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx index 9fa327912c2d5..0f2439e5238c9 100644 --- a/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx +++ b/apps/studio/components/interfaces/BranchManagement/EditBranchModal.tsx @@ -147,16 +147,16 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro const onSubmit = (data: z.infer) => { if (!projectRef) return console.error('Project ref is required') - if (!branch?.id) return console.error('Branch ID is required') + if (!ref) return console.error('Branch ref is required') const payload: { + branchRef: string projectRef: string - id: string branchName: string gitBranch?: string } = { + branchRef: ref, projectRef, - id: branch.id, branchName: data.branchName, } diff --git a/apps/studio/components/interfaces/BranchManagement/Overview.tsx b/apps/studio/components/interfaces/BranchManagement/Overview.tsx index 887ada063ae7d..666cf7445c62e 100644 --- a/apps/studio/components/interfaces/BranchManagement/Overview.tsx +++ b/apps/studio/components/interfaces/BranchManagement/Overview.tsx @@ -167,7 +167,7 @@ const PreviewBranchActions = ({ }) => { const gitlessBranching = useIsBranching2Enabled() const queryClient = useQueryClient() - const projectRef = branch.parent_project_ref ?? branch.project_ref + const { project_ref: branchRef, parent_project_ref: projectRef } = branch const { can: canDeleteBranches } = useAsyncCheckPermissions( PermissionAction.DELETE, @@ -178,7 +178,7 @@ const PreviewBranchActions = ({ 'preview_branches' ) - const { data } = useBranchQuery({ projectRef, id: branch.id }) + const { data } = useBranchQuery({ projectRef, branchRef }) const isBranchActiveHealthy = data?.status === 'ACTIVE_HEALTHY' const [showConfirmResetModal, setShowConfirmResetModal] = useState(false) @@ -203,13 +203,11 @@ const PreviewBranchActions = ({ }) const onConfirmReset = () => { - if (!projectRef) return - resetBranch({ id: branch.id, projectRef }) + resetBranch({ branchRef, projectRef }) } const onTogglePersistent = () => { - if (!projectRef) return - updateBranch({ id: branch.id, projectRef, persistent: !branch.persistent }) + updateBranch({ branchRef, projectRef, persistent: !branch.persistent }) } return ( diff --git a/apps/studio/components/interfaces/BranchManagement/ReviewRow.tsx b/apps/studio/components/interfaces/BranchManagement/ReviewRow.tsx index 4d1e1409dde0f..4e932850701cb 100644 --- a/apps/studio/components/interfaces/BranchManagement/ReviewRow.tsx +++ b/apps/studio/components/interfaces/BranchManagement/ReviewRow.tsx @@ -3,11 +3,9 @@ import { MoreVertical, X } from 'lucide-react' import { useRouter } from 'next/router' import { useQueryClient } from '@tanstack/react-query' -import { useParams } from 'common' import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import type { Branch } from 'data/branches/branches-query' import { branchKeys } from 'data/branches/keys' -import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { toast } from 'sonner' import { Button, @@ -23,15 +21,14 @@ interface ReviewRowProps { export const ReviewRow = ({ branch }: ReviewRowProps) => { const router = useRouter() - const { data: project } = useSelectedProjectQuery() - const { ref: projectRef } = useParams() const queryClient = useQueryClient() + const { project_ref: branchRef, parent_project_ref: projectRef } = branch const { mutate: updateBranch, isLoading: isUpdating } = useBranchUpdateMutation({ onSuccess: () => { toast.success('Branch marked as not ready for review') queryClient.invalidateQueries({ - queryKey: branchKeys.list(project?.parent_project_ref || projectRef), + queryKey: branchKeys.list(projectRef), }) }, onError: (error) => { @@ -40,16 +37,15 @@ export const ReviewRow = ({ branch }: ReviewRowProps) => { }) const handleRowClick = () => { - router.push(`/project/${branch.project_ref}/merge`) + router.push(`/project/${branchRef}/merge`) } const handleNotReadyForReview = (e?: Event) => { e?.preventDefault() e?.stopPropagation() - if (!projectRef) return updateBranch({ - id: branch.id, + branchRef, projectRef, requestReview: false, }) diff --git a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx index e97061a5869a8..5c69fa0dc38d0 100644 --- a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx +++ b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx @@ -242,7 +242,7 @@ const GitHubIntegrationConnectionForm = ({ }, }) - if (!prodBranch?.id) { + if (!prodBranch) { createBranch({ projectRef: selectedProject.ref, branchName: 'main', @@ -251,7 +251,7 @@ const GitHubIntegrationConnectionForm = ({ }) } else { updateBranch({ - id: prodBranch.id, + branchRef: prodBranch.project_ref, projectRef: selectedProject.ref, gitBranch: data.branchName, }) @@ -291,9 +291,9 @@ const GitHubIntegrationConnectionForm = ({ }, }) - if (prodBranch?.id) { + if (prodBranch) { updateBranch({ - id: prodBranch.id, + branchRef: prodBranch.project_ref, projectRef: selectedProject.ref, gitBranch: data.enableProductionSync ? data.branchName : '', branchName: data.branchName || 'main', diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/MergeRequestButton.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/MergeRequestButton.tsx index 5e1adb2c95a13..6f4e8f92afcde 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/MergeRequestButton.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/MergeRequestButton.tsx @@ -33,7 +33,7 @@ export const MergeRequestButton = () => { if (!projectRef || !selectedBranch || selectedBranch.is_default || selectedBranch.git_branch) return null - const hasReviewRequested = !!selectedBranch?.review_requested_at + const hasReviewRequested = !!selectedBranch.review_requested_at const buttonLabel = hasReviewRequested ? 'Review merge request' : 'Open merge request' const handleClick = () => { @@ -42,7 +42,7 @@ export const MergeRequestButton = () => { } else { updateBranch( { - id: selectedBranch.id, + branchRef: selectedBranch.project_ref, projectRef, requestReview: true, }, diff --git a/apps/studio/data/branches/branch-delete-mutation.ts b/apps/studio/data/branches/branch-delete-mutation.ts index de0ffa242cb88..fe0adb6d8d38e 100644 --- a/apps/studio/data/branches/branch-delete-mutation.ts +++ b/apps/studio/data/branches/branch-delete-mutation.ts @@ -7,13 +7,13 @@ import { BranchesData } from './branches-query' import { branchKeys } from './keys' export type BranchDeleteVariables = { - id: string + branchRef: string projectRef: string } -export async function deleteBranch({ id }: Pick) { +export async function deleteBranch({ branchRef }: Pick) { const { data, error } = await del('/v1/branches/{branch_id_or_ref}', { - params: { path: { branch_id_or_ref: id } }, + params: { path: { branch_id_or_ref: branchRef } }, }) if (error) handleError(error) @@ -35,7 +35,7 @@ export const useBranchDeleteMutation = ({ (vars) => deleteBranch(vars), { async onSuccess(data, variables, context) { - const { projectRef } = variables + const { branchRef, projectRef } = variables setTimeout(() => { queryClient.invalidateQueries(branchKeys.list(projectRef)) }, 5000) @@ -44,7 +44,7 @@ export const useBranchDeleteMutation = ({ branchKeys.list(projectRef) ) if (branches) { - const updatedBranches = branches.filter((branch) => branch.id !== variables.id) + const updatedBranches = branches.filter((branch) => branch.project_ref !== branchRef) queryClient.setQueryData(branchKeys.list(projectRef), updatedBranches) } diff --git a/apps/studio/data/branches/branch-diff-query.ts b/apps/studio/data/branches/branch-diff-query.ts index d4f20a60eb53f..bdc83572fee3e 100644 --- a/apps/studio/data/branches/branch-diff-query.ts +++ b/apps/studio/data/branches/branch-diff-query.ts @@ -6,18 +6,18 @@ import type { ResponseError } from 'types' import { branchKeys } from './keys' export type BranchDiffVariables = { - branchId: string + branchRef: string projectRef: string includedSchemas?: string } export async function getBranchDiff({ - branchId, + branchRef, includedSchemas, -}: Pick) { +}: Pick) { const { data: diffData, error } = await get('/v1/branches/{branch_id_or_ref}/diff', { params: { - path: { branch_id_or_ref: branchId }, + path: { branch_id_or_ref: branchRef }, query: includedSchemas ? { included_schemas: includedSchemas } : undefined, }, headers: { @@ -41,17 +41,17 @@ export async function getBranchDiff({ type BranchDiffData = Awaited> export const useBranchDiffQuery = ( - { branchId, projectRef, includedSchemas }: BranchDiffVariables, + { branchRef, projectRef, includedSchemas }: BranchDiffVariables, { enabled = true, ...options }: Omit, 'queryKey' | 'queryFn'> = {} ) => useQuery( - branchKeys.diff(projectRef, branchId), - () => getBranchDiff({ branchId, includedSchemas }), + branchKeys.diff(projectRef, branchRef), + () => getBranchDiff({ branchRef, includedSchemas }), { - enabled: IS_PLATFORM && enabled && typeof branchId !== 'undefined' && branchId !== '', + enabled: IS_PLATFORM && enabled && Boolean(branchRef), ...options, } ) diff --git a/apps/studio/data/branches/branch-merge-mutation.ts b/apps/studio/data/branches/branch-merge-mutation.ts index 9413f5834941a..b6a1fd5691819 100644 --- a/apps/studio/data/branches/branch-merge-mutation.ts +++ b/apps/studio/data/branches/branch-merge-mutation.ts @@ -8,19 +8,14 @@ import { getBranchDiff } from './branch-diff-query' import { upsertMigration } from '../database/migration-upsert-mutation' export type BranchMergeVariables = { - id: string branchProjectRef: string baseProjectRef: string migration_version?: string } -export async function mergeBranch({ - id, - branchProjectRef, - migration_version, -}: BranchMergeVariables) { +export async function mergeBranch({ branchProjectRef, migration_version }: BranchMergeVariables) { // Step 1: Get the diff output from the branch - const diffContent = await getBranchDiff({ branchId: id }) + const diffContent = await getBranchDiff({ branchRef: branchProjectRef }) let migrationCreated = false @@ -41,7 +36,7 @@ export async function mergeBranch({ // Step 3: Call POST /v1/branches/id/merge to merge the branch const { data, error } = await post('/v1/branches/{branch_id_or_ref}/merge', { - params: { path: { branch_id_or_ref: id } }, + params: { path: { branch_id_or_ref: branchProjectRef } }, body: { migration_version }, }) diff --git a/apps/studio/data/branches/branch-push-mutation.ts b/apps/studio/data/branches/branch-push-mutation.ts index 81b92052d10bc..1ffb4ac909a80 100644 --- a/apps/studio/data/branches/branch-push-mutation.ts +++ b/apps/studio/data/branches/branch-push-mutation.ts @@ -6,13 +6,13 @@ import type { ResponseError } from 'types' import { branchKeys } from './keys' export type BranchPushVariables = { - id: string + branchRef: string projectRef: string } -export async function pushBranch({ id }: Pick) { +export async function pushBranch({ branchRef }: Pick) { const { data, error } = await post('/v1/branches/{branch_id_or_ref}/push', { - params: { path: { branch_id_or_ref: id } }, + params: { path: { branch_id_or_ref: branchRef } }, body: {}, }) diff --git a/apps/studio/data/branches/branch-query.ts b/apps/studio/data/branches/branch-query.ts index 9ea59496b1fc5..81fee2ba79bf7 100644 --- a/apps/studio/data/branches/branch-query.ts +++ b/apps/studio/data/branches/branch-query.ts @@ -6,15 +6,15 @@ import type { ResponseError } from 'types' import { branchKeys } from './keys' export type BranchVariables = { + branchRef?: string projectRef?: string - id?: string } -export async function getBranch({ id }: BranchVariables, signal?: AbortSignal) { - if (!id) throw new Error('id is required') +export async function getBranch({ branchRef }: BranchVariables, signal?: AbortSignal) { + if (!branchRef) throw new Error('branchRef is required') const { data, error } = await get(`/v1/branches/{branch_id_or_ref}`, { - params: { path: { branch_id_or_ref: id } }, + params: { path: { branch_id_or_ref: branchRef } }, signal, }) @@ -26,14 +26,14 @@ export type BranchData = Awaited> export type BranchError = ResponseError export const useBranchQuery = ( - { projectRef, id }: BranchVariables, + { projectRef, branchRef }: BranchVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => useQuery( - branchKeys.detail(projectRef, id), - ({ signal }) => getBranch({ id }, signal), + branchKeys.detail(projectRef, branchRef), + ({ signal }) => getBranch({ branchRef }, signal), { - enabled: IS_PLATFORM && enabled && typeof id !== 'undefined', + enabled: IS_PLATFORM && enabled && Boolean(branchRef), ...options, } ) diff --git a/apps/studio/data/branches/branch-reset-mutation.ts b/apps/studio/data/branches/branch-reset-mutation.ts index 623da430c31c5..f54e61a35c6a5 100644 --- a/apps/studio/data/branches/branch-reset-mutation.ts +++ b/apps/studio/data/branches/branch-reset-mutation.ts @@ -6,13 +6,13 @@ import type { ResponseError } from 'types' import { branchKeys } from './keys' export type BranchResetVariables = { - id: string + branchRef: string projectRef: string } -export async function resetBranch({ id }: Pick) { +export async function resetBranch({ branchRef }: Pick) { const { data, error } = await post('/v1/branches/{branch_id_or_ref}/reset', { - params: { path: { branch_id_or_ref: id } }, + params: { path: { branch_id_or_ref: branchRef } }, body: {}, }) diff --git a/apps/studio/data/branches/branch-update-mutation.ts b/apps/studio/data/branches/branch-update-mutation.ts index f18aedde13414..2ea12830fd08b 100644 --- a/apps/studio/data/branches/branch-update-mutation.ts +++ b/apps/studio/data/branches/branch-update-mutation.ts @@ -6,7 +6,7 @@ import type { ResponseError } from 'types' import { branchKeys } from './keys' export type BranchUpdateVariables = { - id: string + branchRef: string projectRef: string branchName?: string gitBranch?: string @@ -15,7 +15,7 @@ export type BranchUpdateVariables = { } export async function updateBranch({ - id, + branchRef, branchName, gitBranch, persistent, @@ -23,7 +23,7 @@ export async function updateBranch({ }: BranchUpdateVariables) { const { data, error } = await patch('/v1/branches/{branch_id_or_ref}', { params: { - path: { branch_id_or_ref: id }, + path: { branch_id_or_ref: branchRef }, }, body: { branch_name: branchName, diff --git a/apps/studio/data/branches/branches-disable-mutation.ts b/apps/studio/data/branches/branches-disable-mutation.ts deleted file mode 100644 index 6b550437812e5..0000000000000 --- a/apps/studio/data/branches/branches-disable-mutation.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' -import { toast } from 'sonner' - -import { del, handleError } from 'data/fetchers' -import { projectKeys } from 'data/projects/keys' -import type { ResponseError } from 'types' -import { deleteBranch } from './branch-delete-mutation' -import { branchKeys } from './keys' - -export type BranchesDisableVariables = { - branchIds: string[] - projectRef: string -} - -export async function disableBranching({ branchIds, projectRef }: BranchesDisableVariables) { - // Delete all preview branches first - if (branchIds.length > 0) { - const res = await Promise.all( - branchIds.map(async (id) => { - try { - return await deleteBranch({ id }) - } catch (error) { - return error - } - }) - ) - const hasError = res.some((item: any) => item.message !== 'ok') - if (hasError) throw new Error('Failed to disable branching: Unable to delete all branches') - } - - // Then disable branching - const { data, error } = await del('/v1/projects/{ref}/branches', { - params: { path: { ref: projectRef } }, - }) - if (error) handleError(error) - return data -} - -type BranchesDisableData = Awaited> - -export const useBranchesDisableMutation = ({ - onSuccess, - onError, - ...options -}: Omit< - UseMutationOptions, - 'mutationFn' -> = {}) => { - const queryClient = useQueryClient() - return useMutation( - (vars) => disableBranching(vars), - { - async onSuccess(data, variables, context) { - const { projectRef } = variables - await queryClient.invalidateQueries(branchKeys.list(projectRef)) - await queryClient.invalidateQueries(projectKeys.detail(projectRef)) - await onSuccess?.(data, variables, context) - }, - async onError(data, variables, context) { - if (onError === undefined) { - toast.error(`Failed to disable branching: ${data.message}`) - } else { - onError(data, variables, context) - } - }, - ...options, - } - ) -} diff --git a/apps/studio/data/branches/branches-query.ts b/apps/studio/data/branches/branches-query.ts index c6c82abd22fbc..69aad2f835a90 100644 --- a/apps/studio/data/branches/branches-query.ts +++ b/apps/studio/data/branches/branches-query.ts @@ -41,5 +41,5 @@ export const useBranchesQuery = ( useQuery( branchKeys.list(projectRef), ({ signal }) => getBranches({ projectRef }, signal), - { enabled: IS_PLATFORM && enabled && typeof projectRef !== 'undefined', ...options } + { enabled: IS_PLATFORM && enabled && Boolean(projectRef), ...options } ) diff --git a/apps/studio/data/branches/keys.ts b/apps/studio/data/branches/keys.ts index 078b6cdb343ab..91950e8fa8381 100644 --- a/apps/studio/data/branches/keys.ts +++ b/apps/studio/data/branches/keys.ts @@ -1,7 +1,7 @@ export const branchKeys = { list: (projectRef: string | undefined) => ['projects', projectRef, 'branches'] as const, - detail: (projectRef: string | undefined, id: string | undefined) => - ['projects', projectRef, 'branches', id] as const, - diff: (projectRef: string | undefined, branchId: string | undefined) => - ['projects', projectRef, 'branch', branchId, 'diff'] as const, + detail: (projectRef: string | undefined, branchRef: string | undefined) => + ['projects', projectRef, 'branches', branchRef] as const, + diff: (projectRef: string | undefined, branchRef: string | undefined) => + ['projects', projectRef, 'branch', branchRef, 'diff'] as const, } diff --git a/apps/studio/hooks/branches/useBranchMergeDiff.ts b/apps/studio/hooks/branches/useBranchMergeDiff.ts index a71789e903758..63f4fdbe4b8fd 100644 --- a/apps/studio/hooks/branches/useBranchMergeDiff.ts +++ b/apps/studio/hooks/branches/useBranchMergeDiff.ts @@ -4,7 +4,6 @@ import { useMigrationsQuery } from 'data/database/migrations-query' import { useEdgeFunctionsDiff, type EdgeFunctionsDiffResult } from './useEdgeFunctionsDiff' interface UseBranchMergeDiffProps { - branchId?: string currentBranchRef?: string parentProjectRef?: string currentBranchConnectionString?: string @@ -46,7 +45,6 @@ export interface BranchMergeDiffResult { } export const useBranchMergeDiff = ({ - branchId, currentBranchRef, parentProjectRef, currentBranchConnectionString, @@ -62,11 +60,11 @@ export const useBranchMergeDiff = ({ refetch: refetchDatabaseDiff, } = useBranchDiffQuery( { - branchId: branchId || '', + branchRef: currentBranchRef || '', projectRef: parentProjectRef || '', }, { - enabled: !!branchId && !!parentProjectRef, + enabled: !!currentBranchRef && !!parentProjectRef, refetchOnMount: 'always', refetchOnWindowFocus: false, staleTime: 0, diff --git a/apps/studio/pages/project/[ref]/branches/index.tsx b/apps/studio/pages/project/[ref]/branches/index.tsx index 30cb0df93356d..ab2a15e25af73 100644 --- a/apps/studio/pages/project/[ref]/branches/index.tsx +++ b/apps/studio/pages/project/[ref]/branches/index.tsx @@ -93,13 +93,13 @@ const BranchesPage: NextPageWithLayout = () => { const onConfirmDeleteBranch = () => { if (selectedBranchToDelete == undefined) return console.error('No branch selected') - if (projectRef == undefined) return console.error('Project ref is required') + const { project_ref: branchRef, parent_project_ref: projectRef } = selectedBranchToDelete deleteBranch( - { id: selectedBranchToDelete?.id, projectRef }, + { branchRef, projectRef }, { onSuccess: () => { - if (selectedBranchToDelete.project_ref === ref) { - router.push(`/project/${selectedBranchToDelete.parent_project_ref}/branches`) + if (branchRef === ref) { + router.push(`/project/${projectRef}/branches`) } // Track delete button click sendEvent({ diff --git a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx index d66359dc3796b..536e5f4a21dc2 100644 --- a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx +++ b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx @@ -95,62 +95,65 @@ const MergeRequestsPage: NextPageWithLayout = () => { }, }) - const handleMarkBranchForReview = (branch: Branch) => { - if (branch.id && projectRef) { - updateBranch( - { - id: branch.id, - projectRef, - requestReview: true, + const handleMarkBranchForReview = ({ + project_ref: branchRef, + parent_project_ref: projectRef, + persistent, + }: Branch) => { + updateBranch( + { + branchRef, + projectRef, + requestReview: true, + }, + { + onSuccess: () => { + toast.success('Merge request created') + + // Track merge request creation + sendEvent({ + action: 'branch_create_merge_request_button_clicked', + properties: { + branchType: persistent ? 'persistent' : 'preview', + origin: 'merge_page', + }, + groups: { + project: projectRef ?? 'Unknown', + organization: selectedOrg?.slug ?? 'Unknown', + }, + }) + + router.push(`/project/${branchRef}/merge`) }, - { - onSuccess: () => { - toast.success('Merge request created') - - // Track merge request creation - sendEvent({ - action: 'branch_create_merge_request_button_clicked', - properties: { - branchType: branch.persistent ? 'persistent' : 'preview', - origin: 'merge_page', - }, - groups: { - project: projectRef ?? 'Unknown', - organization: selectedOrg?.slug ?? 'Unknown', - }, - }) - - router.push(`/project/${branch.project_ref}/merge`) - }, - } - ) - } + } + ) } - const handleCloseMergeRequest = (branch: Branch) => { - if (branch.id && projectRef) { - updateBranch( - { - id: branch.id, - projectRef, - requestReview: false, + const handleCloseMergeRequest = ({ + project_ref: branchRef, + parent_project_ref: projectRef, + }: Branch) => { + updateBranch( + { + branchRef, + projectRef, + requestReview: false, + }, + { + onSuccess: () => { + toast.success('Merge request closed') + + // Track merge request closed + sendEvent({ + action: 'branch_close_merge_request_button_clicked', + groups: { + project: projectRef ?? 'Unknown', + organization: selectedOrg?.slug ?? 'Unknown', + }, + }) }, - { - onSuccess: () => { - toast.success('Merge request closed') - - // Track merge request closed - sendEvent({ - action: 'branch_close_merge_request_button_clicked', - groups: { - project: projectRef ?? 'Unknown', - organization: selectedOrg?.slug ?? 'Unknown', - }, - }) - }, - } - ) - } + } + ) } const generateCreatePullRequestURL = (branch?: string) => { @@ -329,36 +332,38 @@ const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => { }, }) - const handleMarkBranchForReview = (branch: Branch) => { - if (branch.id && projectRef) { - updateBranch( - { - id: branch.id, - projectRef, - requestReview: true, + const handleMarkBranchForReview = ({ + project_ref: branchRef, + parent_project_ref: projectRef, + persistent, + }: Branch) => { + updateBranch( + { + branchRef, + projectRef, + requestReview: true, + }, + { + onSuccess: () => { + toast.success('Merge request created') + + // Track merge request creation + sendEvent({ + action: 'branch_create_merge_request_button_clicked', + properties: { + branchType: persistent ? 'persistent' : 'preview', + origin: 'branch_selector', + }, + groups: { + project: projectRef ?? 'Unknown', + organization: selectedOrg?.slug ?? 'Unknown', + }, + }) + + router.push(`/project/${branchRef}/merge`) }, - { - onSuccess: () => { - toast.success('Merge request created') - - // Track merge request creation - sendEvent({ - action: 'branch_create_merge_request_button_clicked', - properties: { - branchType: branch.persistent ? 'persistent' : 'preview', - origin: 'branch_selector', - }, - groups: { - project: projectRef ?? 'Unknown', - organization: selectedOrg?.slug ?? 'Unknown', - }, - }) - - router.push(`/project/${branch.project_ref}/merge`) - }, - } - ) - } + } + ) } const primaryActions = gitlessBranching ? ( diff --git a/apps/studio/pages/project/[ref]/merge.tsx b/apps/studio/pages/project/[ref]/merge.tsx index 298fc676a0b15..21e946bf6d845 100644 --- a/apps/studio/pages/project/[ref]/merge.tsx +++ b/apps/studio/pages/project/[ref]/merge.tsx @@ -86,7 +86,6 @@ const MergePage: NextPageWithLayout = () => { isLoading: isCombinedDiffLoading, hasChanges: combinedHasChanges, } = useBranchMergeDiff({ - branchId: currentBranch?.id, currentBranchRef: ref, parentProjectRef, currentBranchConnectionString: project?.connectionString || undefined, @@ -118,10 +117,10 @@ const MergePage: NextPageWithLayout = () => { setWorkflowFinalStatus(status) refetchDiff() clearDiffsOptimistically() - if (parentProjectRef && currentBranch?.id && currentBranch.review_requested_at) { + if (ref && parentProjectRef && currentBranch?.review_requested_at) { updateBranch( { - id: currentBranch.id, + branchRef: ref, projectRef: parentProjectRef, requestReview: false, }, @@ -135,7 +134,7 @@ const MergePage: NextPageWithLayout = () => { refetchDiff, clearDiffsOptimistically, parentProjectRef, - currentBranch?.id, + ref, updateBranch, currentBranch?.review_requested_at, ] @@ -278,23 +277,23 @@ const MergePage: NextPageWithLayout = () => { }) const handlePush = () => { - if (!currentBranch?.id || !parentProjectRef) return + if (!ref || !parentProjectRef) return pushBranch({ - id: currentBranch.id, + branchRef: ref, projectRef: parentProjectRef, }) } const handleCloseBranch = () => { - if (!currentBranch?.id || !parentProjectRef) return + if (!ref || !parentProjectRef) return deleteBranch({ - id: currentBranch.id, + branchRef: ref, projectRef: parentProjectRef, }) } const handleMerge = () => { - if (!currentBranch?.id || !parentProjectRef || !ref) return + if (!ref || !parentProjectRef) return setIsSubmitting(true) // Track merge attempt @@ -307,7 +306,6 @@ const MergePage: NextPageWithLayout = () => { }) mergeBranch({ - id: currentBranch.id, branchProjectRef: ref, baseProjectRef: parentProjectRef, migration_version: undefined, @@ -315,10 +313,10 @@ const MergePage: NextPageWithLayout = () => { } const handleReadyForReview = () => { - if (!currentBranch?.id || !parentProjectRef) return + if (!ref || !parentProjectRef) return updateBranch( { - id: currentBranch.id, + branchRef: ref, projectRef: parentProjectRef, requestReview: true, }, @@ -433,10 +431,10 @@ const MergePage: NextPageWithLayout = () => { { - if (!currentBranch?.id || !parentProjectRef) return + if (!ref || !parentProjectRef) return updateBranch( { - id: currentBranch.id, + branchRef: ref, projectRef: parentProjectRef, requestReview: false, }, From 872a37211959e9c58d7cbf69af7ae256ea03604a Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 17 Sep 2025 11:45:08 +0800 Subject: [PATCH 3/3] fix: Debounce filter application in UnifiedLogs (#37275) Debounce filter application in UnifiedLogs Introduced debounced filter application using useDebounce to reduce excessive API calls when users quickly change filters in UnifiedLogs. This improves performance and user experience by batching rapid filter changes. --- .../interfaces/UnifiedLogs/UnifiedLogs.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx index 739270c7ec712..20050a0693e87 100644 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx @@ -16,7 +16,7 @@ import { import { useQueryStates } from 'nuqs' import { useEffect, useMemo, useState } from 'react' -import { useParams } from 'common' +import { useDebounce, useParams } from 'common' import { arrSome, inDateRange } from 'components/ui/DataTable/DataTable.utils' import { DataTableFilterCommand } from 'components/ui/DataTable/DataTableFilters/DataTableFilterCommand' import { DataTableHeaderLayout } from 'components/ui/DataTable/DataTableHeaderLayout' @@ -245,7 +245,8 @@ export const UnifiedLogs = () => { }) }, [facets]) - useEffect(() => { + // Debounced filter application to avoid too many API calls when user clicks multiple filters quickly + const applyFilterSearch = () => { const columnFiltersWithNullable = filterFields.map((field) => { const filterValue = columnFilters.find((filter) => filter.id === field.value) if (!filterValue) return { id: field.value, value: null } @@ -262,9 +263,14 @@ export const UnifiedLogs = () => { ) setSearch(search) + } + const debouncedApplyFilterSearch = useDebounce(applyFilterSearch, 1000) + + useEffect(() => { + debouncedApplyFilterSearch() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [columnFilters]) + }, [columnFilters, debouncedApplyFilterSearch]) useEffect(() => { setSearch({ sort: sorting?.[0] || null })