diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx index b4c4193ff36ae..88d820c5e2f5c 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx @@ -137,7 +137,7 @@ const PolicyTableRowHeader = ({ }, }} > - + diff --git a/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx b/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx index 74b5b4919395b..3928bea2096ec 100644 --- a/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx +++ b/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx @@ -78,25 +78,7 @@ const BranchManagement = () => { isLoading: isLoadingBranches, isError: isErrorBranches, isSuccess: isSuccessBranches, - } = useBranchesQuery( - { projectRef }, - { - refetchInterval(data) { - if ( - data?.some( - (branch) => - branch.status === 'CREATING_PROJECT' || - branch.status === 'RUNNING_MIGRATIONS' || - branch.status === 'MIGRATIONS_FAILED' - ) - ) { - return 1000 * 3 // 3 seconds - } - - return false - }, - } - ) + } = useBranchesQuery({ projectRef }) const [[mainBranch], previewBranchesUnsorted] = partition(branches, (branch) => branch.is_default) const previewBranches = previewBranchesUnsorted.sort((a, b) => new Date(a.updated_at) < new Date(b.updated_at) ? 1 : -1 diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index 609998fd789dc..bc11bf2eccd7c 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -149,9 +149,7 @@ const FunctionsList = ({ type="default" disabled={!canCreateFunctions} className="px-1 pointer-events-auto" - icon={ - - } + icon={} onClick={() => setAiAssistantPanel({ open: true, diff --git a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx index fd2af38317ebe..bca14055c723e 100644 --- a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx +++ b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx @@ -131,9 +131,7 @@ const TriggersList = ({ type="default" disabled={!canCreateTriggers} className="px-1 pointer-events-auto" - icon={ - - } + icon={} onClick={() => setAiAssistantPanel({ open: true, diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx index e477d0c146c5a..5d4580608f3ef 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx @@ -43,8 +43,8 @@ import { } from './CronJobs.utils' import { CronJobScheduleSection } from './CronJobScheduleSection' import { EdgeFunctionSection } from './EdgeFunctionSection' +import { HttpBodyFieldSection } from './HttpBodyFieldSection' import { HTTPHeaderFieldsSection } from './HttpHeaderFieldsSection' -import { HTTPParameterFieldsSection } from './HttpParameterFieldsSection' import { HttpRequestSection } from './HttpRequestSection' import { SqlFunctionSection } from './SqlFunctionSection' import { SqlSnippetSection } from './SqlSnippetSection' @@ -63,7 +63,7 @@ const edgeFunctionSchema = z.object({ edgeFunctionName: z.string().trim().min(1, 'Please select one of the listed Edge Functions'), timeoutMs: z.coerce.number().int().gte(1000).lte(5000).default(1000), httpHeaders: z.array(z.object({ name: z.string(), value: z.string() })), - httpParameters: z.array(z.object({ name: z.string(), value: z.string() })), + httpBody: z.string().trim(), }) const httpRequestSchema = z.object({ @@ -77,7 +77,7 @@ const httpRequestSchema = z.object({ .refine((value) => value.startsWith('http'), 'Please include HTTP/HTTPs to your URL'), timeoutMs: z.coerce.number().int().gte(1000).lte(5000).default(1000), httpHeaders: z.array(z.object({ name: z.string(), value: z.string() })), - httpParameters: z.array(z.object({ name: z.string(), value: z.string() })), + httpBody: z.string().trim(), }) const sqlFunctionSchema = z.object({ @@ -187,7 +187,7 @@ export const CreateCronJobSheet = ({ values.method, values.edgeFunctionName, values.httpHeaders, - values.httpParameters, + values.httpBody, values.timeoutMs ) } else if (values.type === 'http_request') { @@ -195,7 +195,7 @@ export const CreateCronJobSheet = ({ values.method, values.endpoint, values.httpHeaders, - values.httpParameters, + values.httpBody, values.timeoutMs ) } else if (values.type === 'sql_function') { @@ -367,7 +367,7 @@ export const CreateCronJobSheet = ({ - + )} {cronType === 'edge_function' && ( @@ -376,7 +376,7 @@ export const CreateCronJobSheet = ({ - + )} {cronType === 'sql_function' && } diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx index d8bbd50102bc8..21f1c65abf8ad 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx @@ -1,13 +1,13 @@ import { toString as CronToString } from 'cronstrue' -import { Clock, Loader2, MoreVertical } from 'lucide-react' +import { Clock, History, Loader2, MoreVertical } from 'lucide-react' import Link from 'next/link' +import { useState } from 'react' import { useParams } from 'common' import { SQLCodeBlock } from 'components/interfaces/Auth/ThirdPartyAuthForm/SqlCodeBlock' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-query' import { useDatabaseCronJobToggleMutation } from 'data/database-cron-jobs/database-cron-jobs-toggle-mutation' -import { useState } from 'react' import { Button, DropdownMenu, @@ -68,6 +68,11 @@ export const CronJobCard = ({ job, onEditCronJob, onDeleteCronJob }: CronJobCard checked={job.active} onCheckedChange={() => showToggleConfirmationModal(true)} /> + + ) : (
@@ -82,18 +82,7 @@ export const CronjobsTab = () => { onChange={(e) => setSearchQuery(e.target.value)} /> - +
{filteredCronJobs.length === 0 ? (
{ setCreateCronJobSheetShown(job)} + onEditCronJob={(job) => { + setCronJobForEditing(job) + setCreateCronJobSheetShown(true) + }} onDeleteCronJob={(job) => setCronJobForDeletion(job)} /> )) @@ -136,11 +128,12 @@ export const CronjobsTab = () => { > { setIsClosingCreateCronJobSheet(false) - setCreateCronJobSheetShown(undefined) + setCronJobForEditing(undefined) + setCreateCronJobSheetShown(false) }} isClosing={isClosingCreateCronJobSheet} setIsClosing={setIsClosingCreateCronJobSheet} diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/HttpBodyFieldSection.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/HttpBodyFieldSection.tsx new file mode 100644 index 0000000000000..82798c330a23c --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/CronJobs/HttpBodyFieldSection.tsx @@ -0,0 +1,44 @@ +import { UseFormReturn } from 'react-hook-form' + +import { + FormControl_Shadcn_, + FormDescription_Shadcn_, + FormField_Shadcn_, + FormItem_Shadcn_, + FormLabel_Shadcn_, + FormMessage_Shadcn_, + SheetSection, + TextArea_Shadcn_, +} from 'ui' +import { CreateCronJobForm } from './CreateCronJobSheet' + +interface HttpBodyFieldSectionProps { + form: UseFormReturn +} + +export const HttpBodyFieldSection = ({ form }: HttpBodyFieldSectionProps) => { + return ( + + ( + + HTTP Request Body + + + + + The content should match the content-type header. + + + + )} + /> + + ) +} diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx index bfd46415d0b14..6a76aac83d752 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/HttpHeaderFieldsSection.tsx @@ -15,6 +15,7 @@ import { FormControl_Shadcn_, FormField_Shadcn_, FormItem_Shadcn_, + FormLabel_Shadcn_, FormMessage_Shadcn_, Input_Shadcn_, SheetSection, @@ -38,7 +39,7 @@ export const HTTPHeaderFieldsSection = ({ variant }: HTTPHeaderFieldsSectionProp return ( - HTTP Headers + HTTP Headers
{fields.map((field, index) => (
diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/HttpParameterFieldsSection.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/HttpParameterFieldsSection.tsx deleted file mode 100644 index d00cb14571dc2..0000000000000 --- a/apps/studio/components/interfaces/Integrations/CronJobs/HttpParameterFieldsSection.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Plus, Trash } from 'lucide-react' -import { useFieldArray } from 'react-hook-form' - -import { - Button, - FormControl_Shadcn_, - FormField_Shadcn_, - FormItem_Shadcn_, - FormMessage_Shadcn_, - Input_Shadcn_, - SheetSection, -} from 'ui' -import { CreateCronJobForm } from './CreateCronJobSheet' - -interface HTTPParameterFieldsSectionProps { - variant: 'edge_function' | 'http_request' -} - -export const HTTPParameterFieldsSection = ({ variant }: HTTPParameterFieldsSectionProps) => { - // gets the fields through form context - const { fields, append, remove } = useFieldArray({ - name: 'values.httpParameters', - }) - - return ( - - HTTP Parameters -
- {fields.map((field, index) => ( -
- ( - - - - - - - )} - /> - ( - - - - - - - )} - /> - -
- ))} -
- -
-
-
- ) -} diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/HttpRequestSection.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/HttpRequestSection.tsx index 13871ab4d7ace..03d522eb6263a 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/HttpRequestSection.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/HttpRequestSection.tsx @@ -51,7 +51,9 @@ export const HttpRequestSection = ({ form }: HttpRequestSectionProps) => { name="values.endpoint" render={({ field: { ref, ...rest } }) => ( - + + + )} /> diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx index c17d3158abb90..d86dbac261bfb 100644 --- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx @@ -59,11 +59,11 @@ export const IntegrationOverviewTab = ({ src={`${router.basePath}/img/supabase-logo.svg`} className=" h-2.5 cursor-pointer rounded" /> - Database Extension + Postgres Module `\`${x}\``).join(', ')} + content={`This integration uses the ${integration.requiredExtensions.map((x) => `\`${x}\``).join(', ')} extension${integration.requiredExtensions.length > 1 ? 's' : ''} directly in your Postgres database. ${hasMissingExtensions ? `Install ${integration.requiredExtensions.length > 1 ? 'these' : 'this'} database extension${integration.requiredExtensions.length > 1 ? 's' : ''} to use ${integration.name} in your project.` : ''} `} diff --git a/apps/studio/components/interfaces/Integrations/Landing/AvailableIntegrations.tsx b/apps/studio/components/interfaces/Integrations/Landing/AvailableIntegrations.tsx index d00fd8f68ffa8..2b4ce16f35467 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/AvailableIntegrations.tsx +++ b/apps/studio/components/interfaces/Integrations/Landing/AvailableIntegrations.tsx @@ -10,6 +10,11 @@ import { IntegrationCard, IntegrationLoadingCard } from './IntegrationCard' import { useInstalledIntegrations } from './useInstalledIntegrations' type IntegrationCategory = 'all' | 'wrapper' | 'postgres_extensions' | 'custom' +const CATEGORIES = [ + { key: 'all', label: 'All Integrations' }, + { key: 'wrapper', label: 'Wrappers' }, + { key: 'postgres_extension', label: 'Postgres Modules' }, +] as const export const AvailableIntegrations = () => { const [selectedCategory, setSelectedCategory] = useQueryState( @@ -29,10 +34,8 @@ export const AvailableIntegrations = () => { // available integrations for install const integrationsByCategory = selectedCategory === 'all' - ? availableIntegrations.filter((i) => !installedIds.includes(i.id)) - : availableIntegrations.filter( - (i) => !installedIds.includes(i.id) && i.type === selectedCategory - ) + ? availableIntegrations + : availableIntegrations.filter((i) => i.type === selectedCategory) const filteredIntegrations = ( search.length > 0 ? integrationsByCategory.filter((i) => i.name.toLowerCase().includes(search.toLowerCase())) @@ -47,23 +50,21 @@ export const AvailableIntegrations = () => { onValueChange={(value) => setSelectedCategory(value as IntegrationCategory)} > - {['all', 'wrapper', 'postgres_extension'].map((category) => ( + {CATEGORIES.map((category) => ( setSelectedCategory(category as IntegrationCategory)} + key={category.key} + value={category.key} + onClick={() => setSelectedCategory(category.key as IntegrationCategory)} className={cn( buttonVariants({ size: 'tiny', - type: selectedCategory === category ? 'default' : 'outline', + type: selectedCategory === category.key ? 'default' : 'outline', }), - selectedCategory === category ? 'text-foreground' : 'text-foreground-lighter', + selectedCategory === category.key ? 'text-foreground' : 'text-foreground-lighter', '!rounded-full px-3' )} > - {category === 'all' - ? 'All Integrations' - : category.replace('_', ' ').replace(/\b\w/g, (char) => char.toUpperCase())} + {category.label} ))} { error={error} /> )} - {isSuccess && filteredIntegrations.map((i) => )} + {isSuccess && + filteredIntegrations.map((i) => ( + + ))} {isSuccess && search.length > 0 && filteredIntegrations.length === 0 && ( { ) } -export const IntegrationCard = ({ id, beta, name, icon, description }: IntegrationCardProps) => { +export const IntegrationCard = ({ + id, + beta, + name, + icon, + description, + isInstalled, +}: IntegrationCardProps) => { const { project } = useProjectContext() return ( @@ -37,7 +47,7 @@ export const IntegrationCard = ({ id, beta, name, icon, description }: Integrati
{icon()}
-
+

{name}

@@ -49,9 +59,17 @@ export const IntegrationCard = ({ id, beta, name, icon, description }: Integrati

{description}

- - Official - +
+ + Official + + {isInstalled && ( +
+ + Installed +
+ )} +
diff --git a/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx b/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx index cad9403f5ea95..f016a84e281ab 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx +++ b/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx @@ -113,7 +113,7 @@ const supabaseIntegrations: IntegrationDefinition[] = [ id: 'cron-jobs', type: 'postgres_extension' as const, requiredExtensions: ['pg_cron'], - name: `Cron Jobs`, + name: `Cron`, icon: ({ className, ...props } = {}) => ( ), @@ -129,8 +129,8 @@ const supabaseIntegrations: IntegrationDefinition[] = [ label: 'Overview', }, { - route: 'cron-jobs', - label: 'Cron Jobs', + route: 'jobs', + label: 'Jobs', hasChild: true, childIcon: ( @@ -157,7 +157,7 @@ const supabaseIntegrations: IntegrationDefinition[] = [ loading: Loading, } ) - case 'cron-jobs': + case 'jobs': return dynamic(() => import('../CronJobs/CronJobsTab').then((mod) => mod.CronjobsTab), { loading: Loading, }) @@ -226,8 +226,8 @@ const supabaseIntegrations: IntegrationDefinition[] = [ }, { id: 'webhooks', - type: 'custom' as const, - name: `Webhooks`, + type: 'postgres_extension' as const, + name: `Database Webhooks`, icon: ({ className, ...props } = {}) => ( ), @@ -276,7 +276,7 @@ const supabaseIntegrations: IntegrationDefinition[] = [ id: 'graphiql', type: 'postgres_extension' as const, requiredExtensions: ['pg_graphql'], - name: `GraphiQL`, + name: `GraphQL`, icon: ({ className, ...props } = {}) => ( { return ( ) } diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx index 81f71e9bd35ad..c2a99c0bd76b4 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx @@ -1,7 +1,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { AnimatePresence, motion } from 'framer-motion' import { last } from 'lodash' -import { FileText } from 'lucide-react' +import { FileText, Info } from 'lucide-react' import { memo, useEffect, useMemo, useRef, useState } from 'react' import { toast } from 'sonner' @@ -43,6 +43,7 @@ import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import AIOnboarding from './AIOnboarding' import CollapsibleCodeBlock from './CollapsibleCodeBlock' import { Message } from './Message' +import DotGrid from '../DotGrid' const MemoizedMessage = memo( ({ message, isLoading }: { message: MessageType; isLoading: boolean }) => { @@ -298,15 +299,6 @@ export const AIAssistant = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, isInSQLEditor, snippetContent]) - if (isLoadingTables) { - return ( -
- {/* [Joshen] We could try play around with a custom loader for the assistant here */} - -
- ) - } - return ( <>
@@ -315,12 +307,24 @@ export const AIAssistant = ({ className={cn('flex-grow overflow-auto flex flex-col')} onScroll={handleScroll} > -
-
- +
+
+ + +
Assistant
+
+ + + + + + The Assistant is in Alpha and your prompts might be rate limited.{' '} + {includeSchemaMetadata + ? 'Project metadata is being shared to improve Assistant responses.' + : 'Project metadata is not being shared. Opt in to improve Assistant responses.'} + + -
{hasMessages ? 'New chat' : 'Assistant'}
-
{(hasMessages || suggestions || sqlSnippets) && (
+ {!hasMessages && ( +
+ +
+ )} {hasMessages ? ( - -
- {new Date(messages[0].createdAt || new Date()).toLocaleDateString('en-US', { - month: 'long', - day: 'numeric', - year: 'numeric', - hour: 'numeric', - minute: 'numeric', - })} -
+ {renderedMessages} {(last(messages)?.role === 'user' || last(messages)?.content?.length === 0) && ( - - Thinking -
- - . - - - . - - - . - -
-
+
+ + + Thinking +
+ + . + + + . + + + . + +
+
+
)}
@@ -417,48 +420,15 @@ export const AIAssistant = ({ ))}
+ ) : isLoadingTables ? ( +
+ {/* [Joshen] We could try play around with a custom loader for the assistant here */} + +
) : (tables ?? [])?.length > 0 ? ( ) : ( -
-
-
- -
- - - -
-
-
+

Welcome to Supabase!

This is the Supabase assistant which will help you create, debug and modify tables, @@ -590,11 +560,6 @@ export const AIAssistant = ({ } }} /> - {!hasMessages && ( -

- The Assistant is in Alpha and your prompts might be rate limited -

- )}
diff --git a/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx b/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx index 0d330085b60fe..cab0382de7d6e 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIOnboarding.tsx @@ -1,7 +1,6 @@ import { motion } from 'framer-motion' import { FileText, MessageCircleMore, WandSparkles } from 'lucide-react' -import DotGrid from 'components/ui/DotGrid' import { Button } from 'ui' import { InnerSideMenuCollapsible, @@ -20,10 +19,7 @@ export default function AIOnboarding({ setMessages, onSendMessage }: AIOnboardin } return ( -
-
- -
+
- +