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.
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()