From 310d2d575fc96f735bf10d7eb88569e92fa7fcd4 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Sat, 2 Aug 2025 01:30:42 +0800 Subject: [PATCH 1/2] Chore/cron fixes (#37626) * Fix query to fetch cron jobs resulting in duplicate if job previous run succeeded, but latest failed * Fix integration tabs child label * Add a key to the rows in the data grid. * Fix the small loading state when clicking the create cron job button. * Wrap try catch around getDatabaseCronJob when validating cron job name --------- Co-authored-by: Ivan Vasilov --- .../CronJobs/CreateCronJobSheet.tsx | 34 ++++++++++++------- .../Integrations/CronJobs/CronJobsTab.tsx | 3 +- .../layouts/Integrations/layout.tsx | 11 +++--- .../components/layouts/Integrations/tabs.tsx | 4 ++- .../database-cron-jobs-infinite-query.ts | 20 ++++++++--- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx index 547aa46879aaa..4bc501ffe0b7b 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx @@ -203,6 +203,7 @@ export const CreateCronJobSheet = ({ const { project } = useProjectContext() const { data: org } = useSelectedOrganizationQuery() const [searchQuery] = useQueryState('search', parseAsString.withDefault('')) + const [isLoadingGetCronJob, setIsLoadingGetCronJob] = useState(false) const isEditing = !!selectedCronJob?.jobname const [showEnableExtensionModal, setShowEnableExtensionModal] = useState(false) @@ -215,7 +216,8 @@ export const CreateCronJobSheet = ({ const pgNetExtensionInstalled = pgNetExtension?.installed_version != undefined const { mutate: sendEvent } = useSendEventMutation() - const { mutate: upsertCronJob, isLoading } = useDatabaseCronJobCreateMutation() + const { mutate: upsertCronJob, isLoading: isUpserting } = useDatabaseCronJobCreateMutation() + const isLoading = isLoadingGetCronJob || isUpserting const canToggleExtensions = useCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, @@ -306,18 +308,25 @@ export const CreateCronJobSheet = ({ if (!project) return console.error('Project is required') if (!isEditing) { - const checkExistingJob = await getDatabaseCronJob({ - projectRef: project.ref, - connectionString: project.connectionString, - name, - }) - const nameExists = !!checkExistingJob - - if (nameExists) { - return form.setError('name', { - type: 'manual', - message: 'A cron job with this name already exists', + try { + setIsLoadingGetCronJob(true) + const checkExistingJob = await getDatabaseCronJob({ + projectRef: project.ref, + connectionString: project.connectionString, + name, }) + const nameExists = !!checkExistingJob + + if (nameExists) { + return form.setError('name', { + type: 'manual', + message: 'A cron job with this name already exists', + }) + } + } catch (error: any) { + toast.error(`Failed to validate cron job name: ${error.message}`) + } finally { + setIsLoadingGetCronJob(false) } } @@ -369,6 +378,7 @@ export const CreateCronJobSheet = ({ }, } ) + setIsLoadingGetCronJob(false) } return ( diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx index b128ae77bbe96..aa26e6410958c 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx @@ -193,9 +193,10 @@ export const CronjobsTab = () => { }} onScroll={handleScroll} renderers={{ - renderRow(_, props) { + renderRow(key, props) { return ( { const { jobid, jobname } = props.row diff --git a/apps/studio/components/layouts/Integrations/layout.tsx b/apps/studio/components/layouts/Integrations/layout.tsx index e7a14ed257b85..1d7691dda014f 100644 --- a/apps/studio/components/layouts/Integrations/layout.tsx +++ b/apps/studio/components/layouts/Integrations/layout.tsx @@ -1,21 +1,20 @@ import { useRouter } from 'next/router' import { PropsWithChildren, useEffect, useRef, useState } from 'react' -import { IntegrationDefinition } from 'components/interfaces/Integrations/Landing/Integrations.constants' import { useInstalledIntegrations } from 'components/interfaces/Integrations/Landing/useInstalledIntegrations' import { Header } from 'components/layouts/Integrations/header' import ProjectLayout from 'components/layouts/ProjectLayout/ProjectLayout' +import AlertError from 'components/ui/AlertError' import { ProductMenu } from 'components/ui/ProductMenu' import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' +import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' import { useScroll } from 'framer-motion' -import { useSelectedProject } from 'hooks/misc/useSelectedProject' +import { useSelectedProject, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { withAuth } from 'hooks/misc/withAuth' import { useFlag } from 'hooks/ui/useFlag' -import { IntegrationTabs } from './tabs' import { Menu, Separator } from 'ui' -import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' import { GenericSkeletonLoader } from 'ui-patterns' -import AlertError from 'components/ui/AlertError' +import { IntegrationTabs } from './tabs' /** * Layout component for the Integrations section @@ -156,7 +155,7 @@ const IntegrationTopHeaderLayout = ({ ...props }: PropsWithChildren) => { const IntegrationsLayoutSide = ({ ...props }: PropsWithChildren) => { const router = useRouter() const page = router.pathname.split('/')[4] - const project = useSelectedProject() + const { data: project } = useSelectedProjectQuery() const { installedIntegrations: integrations, diff --git a/apps/studio/components/layouts/Integrations/tabs.tsx b/apps/studio/components/layouts/Integrations/tabs.tsx index ed8fbb47dcbe6..00f5adb90b01d 100644 --- a/apps/studio/components/layouts/Integrations/tabs.tsx +++ b/apps/studio/components/layouts/Integrations/tabs.tsx @@ -97,7 +97,9 @@ export const IntegrationTabs = ({ scroll, isSticky }: IntegrationTabsProps) => { > {tab.childIcon} - + {childLabel ? childLabel : childId} diff --git a/apps/studio/data/database-cron-jobs/database-cron-jobs-infinite-query.ts b/apps/studio/data/database-cron-jobs/database-cron-jobs-infinite-query.ts index 861bdde9d1d15..f54b37e3886b2 100644 --- a/apps/studio/data/database-cron-jobs/database-cron-jobs-infinite-query.ts +++ b/apps/studio/data/database-cron-jobs/database-cron-jobs-infinite-query.ts @@ -22,6 +22,7 @@ export type CronJob = { status: string } +// [Joshen] Just to call out that I had AI help me with this, so please let me know if this can be optimized const getCronJobSql = ({ searchTerm, page }: { searchTerm?: string; page: number }) => ` WITH latest_runs AS ( @@ -31,6 +32,17 @@ WITH latest_runs AS ( MAX(start_time) AS latest_run FROM cron.job_run_details GROUP BY jobid, status +), most_recent_runs AS ( + SELECT + jobid, + status, + latest_run + FROM latest_runs lr1 + WHERE latest_run = ( + SELECT MAX(latest_run) + FROM latest_runs lr2 + WHERE lr2.jobid = lr1.jobid + ) ) SELECT job.jobid, @@ -38,13 +50,13 @@ SELECT job.schedule, job.command, job.active, - lr.latest_run, - lr.status + mr.latest_run, + mr.status FROM cron.job job -LEFT JOIN latest_runs lr ON job.jobid = lr.jobid -${!!searchTerm ? `WHERE job.jobname ILIKE '%${searchTerm}%'` : ''} +LEFT JOIN most_recent_runs mr ON job.jobid = mr.jobid ORDER BY job.jobid +${!!searchTerm ? `WHERE job.jobname ILIKE '%${searchTerm}%'` : ''} LIMIT ${CRON_JOBS_PAGE_LIMIT} OFFSET ${page * CRON_JOBS_PAGE_LIMIT}; `.trim() From d83ef08ea1aa661ead1af970a094c512319863f8 Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Sat, 2 Aug 2025 01:42:05 +0800 Subject: [PATCH 2/2] docs: revive section on preparing github repository (#37627) * docs: revive section on preparing github repository * chore: remove unnecessary steps --- .../branching/github-integration.mdx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/apps/docs/content/guides/deployment/branching/github-integration.mdx b/apps/docs/content/guides/deployment/branching/github-integration.mdx index 661730e0521c0..41869d4fcf8d2 100644 --- a/apps/docs/content/guides/deployment/branching/github-integration.mdx +++ b/apps/docs/content/guides/deployment/branching/github-integration.mdx @@ -17,6 +17,60 @@ In the Supabase Dashboard: 6. Configure the other options as needed to automate your GitHub connection. 7. Click **Enable integration**. +## Preparing your Git repository + +You will be using the [Supabase CLI](/docs/guides/cli) to initialise your local `./supabase` directory: + + + + + + If you don't have a `./supabase` directory, you can create one: + + ```markdown + supabase init + ``` + + + + + + + + Pull your database changes using `supabase db pull`. To get your database connection string, go to your project dashboard, click [Connect](https://supabase.com/dashboard/project/_?showConnect=true) and look for the Session pooler connection string. + + ```markdown + supabase db pull --db-url + + # Your Database connection string will look like this: + # postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres + ``` + + + If you're in an [IPv6 environment](https://github.com/orgs/supabase/discussions/27034) or have the IPv4 Add-On, you can use the direct connection string instead of Supavisor in Session mode. + + + + + + + + + + Commit the `supabase` directory to Git, and push your changes to your remote repository. + + ```bash + git add supabase + git commit -m "Initial migration" + git push + ``` + + + + + + + ## Syncing GitHub branches Enable the **Automatic branching** option in your GitHub Integration configuration to automatically sync GitHub branches with Supabase branches.