diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx index 547aa46879a..4bc501ffe0b 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 b128ae77bbe..aa26e641095 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 e7a14ed257b..1d7691dda01 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 ed8fbb47dcb..00f5adb90b0 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 861bdde9d1d..f54b37e3886 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()