Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export const RateLimits = () => {
},
})

const canUpdateEmailLimit = authConfig?.EXTERNAL_EMAIL_ENABLED && isSmtpEnabled(authConfig)
const canUpdateEmailLimit =
authConfig?.EXTERNAL_EMAIL_ENABLED &&
(isSmtpEnabled(authConfig) || authConfig?.HOOK_SEND_EMAIL_ENABLED)
const canUpdateSMSRateLimit = authConfig?.EXTERNAL_PHONE_ENABLED
const canUpdateAnonymousUsersRateLimit = authConfig?.EXTERNAL_ANONYMOUS_USERS_ENABLED
const canUpdateWeb3RateLimit = authConfig?.EXTERNAL_WEB3_SOLANA_ENABLED
Expand Down Expand Up @@ -271,19 +273,25 @@ export const RateLimits = () => {
) : (
<>
<p className="font-medium">
Custom SMTP provider is required to update this configuration
Custom SMTP or Send Email hook is required to update this
configuration
</p>
<p className="mt-1">
The built-in email service has a fixed rate limit. You will need
to set up your own custom SMTP provider to update your email
rate limit
The built-in email service has a fixed rate limit. Set up a
custom SMTP provider or enable the Send Email hook to update
your email rate limit
</p>
<div className="mt-3">
<div className="mt-3 flex gap-2">
<Button asChild type="default" size="tiny">
<Link href={`/project/${projectRef}/auth/smtp`}>
View SMTP settings
</Link>
</Button>
<Button asChild type="default" size="tiny">
<Link href={`/project/${projectRef}/auth/hooks`}>
View hooks
</Link>
</Button>
</div>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@ describe('connect.schema:steps resolution', () => {
const steps = resolveSteps(connectSchema, state)

expect(steps.find((s) => s.id === 'codex-add-server')).toBeDefined()
expect(steps.find((s) => s.id === 'codex-enable-remote')).toBeDefined()
expect(steps.find((s) => s.id === 'codex-authenticate')).toBeDefined()
expect(steps.find((s) => s.id === 'codex-verify')).toBeDefined()
})
Expand Down Expand Up @@ -445,9 +444,6 @@ describe('connect.schema:step content paths', () => {
expect(steps.find((s) => s.id === 'codex-add-server')?.content).toBe(
'steps/mcp/codex/add-server'
)
expect(steps.find((s) => s.id === 'codex-enable-remote')?.content).toBe(
'steps/mcp/codex/enable-remote'
)
expect(steps.find((s) => s.id === 'codex-authenticate')?.content).toBe(
'steps/mcp/codex/authenticate'
)
Expand Down
15 changes: 1 addition & 14 deletions apps/studio/components/interfaces/ConnectSheet/connect.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,6 @@ const codexAddServerStep: StepDefinition = {
content: 'steps/mcp/codex/add-server',
}

const codexEnableRemoteStep: StepDefinition = {
id: 'codex-enable-remote',
title: 'Enable remote MCP client support',
description: 'Add this to your ~/.codex/config.toml file.',
content: 'steps/mcp/codex/enable-remote',
}

const codexAuthenticateStep: StepDefinition = {
id: 'codex-authenticate',
title: 'Authenticate',
Expand Down Expand Up @@ -387,13 +380,7 @@ export const connectSchema: ConnectSchema = {
orm: [ormInstallStep, ormConfigureStep, skillsInstallStep],
mcp: {
mcpClient: {
codex: [
codexAddServerStep,
codexEnableRemoteStep,
codexAuthenticateStep,
codexVerifyStep,
skillsInstallStep,
],
codex: [codexAddServerStep, codexAuthenticateStep, codexVerifyStep, skillsInstallStep],
'claude-code': [claudeAddServerStep, claudeAuthenticateStep, skillsInstallStep],
DEFAULT: [mcpConfigureStep, skillsInstallStep],
},
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import {
useReactFlow,
} from '@xyflow/react'
import { ArrowLeft, ArrowRight } from 'lucide-react'
import { useState } from 'react'
import { memo, useState } from 'react'
import { Badge, cn } from 'ui'

import { useSchemaGraphContext } from './SchemaGraphContext'
import { EdgeData } from './Schemas.constants'
import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState'
import { useStaticEffectEvent } from '@/hooks/useStaticEffectEvent'

export const DefaultEdge = ({
const DefaultEdgeComponent = ({
id,
animated,
data,
Expand Down Expand Up @@ -73,6 +73,8 @@ export const DefaultEdge = ({
)
}

export const DefaultEdge = memo(DefaultEdgeComponent)

const EdgeRelationInfo = ({
data,
source,
Expand Down
31 changes: 13 additions & 18 deletions apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,17 @@ export const SchemaGraph = () => {
}

const selectedNodeIds = new Set(params.nodes.map((n) => n.id))
reactFlowInstance.setEdges(
reactFlowInstance.getEdges().map((edge) => ({
...edge,
animated:
selectedNodeIds.size > 0 &&
(selectedNodeIds.has(edge.source) || selectedNodeIds.has(edge.target)),
}))
)
const currentEdges = reactFlowInstance.getEdges()
let hasChanges = false
const nextEdges = currentEdges.map((edge) => {
const shouldAnimate =
selectedNodeIds.size > 0 &&
(selectedNodeIds.has(edge.source) || selectedNodeIds.has(edge.target))
if (edge.animated === shouldAnimate) return edge
hasChanges = true
return { ...edge, animated: shouldAnimate }
})
if (hasChanges) reactFlowInstance.setEdges(nextEdges)
}
)

Expand Down Expand Up @@ -253,16 +256,7 @@ export const SchemaGraph = () => {
}
})
}
}, [
isSuccessTables,
isSuccessSchemas,
tables,
reactFlowInstance,
ref,
resolvedTheme,
schemas,
selectedSchema,
])
}, [isSuccessTables, isSuccessSchemas, tables, reactFlowInstance, ref, schemas, selectedSchema])

const schemaGraphContext = useMemo<SchemaGraphContextType>(
() => ({
Expand Down Expand Up @@ -470,6 +464,7 @@ export const SchemaGraph = () => {
fitView
minZoom={0.8}
maxZoom={1.8}
onlyRenderVisibleElements
proOptions={{ hideAttribution: true }}
onNodeDragStop={saveNodePositions}
onSelectionChange={handleSelectionChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Table2,
} from 'lucide-react'
import { useRouter } from 'next/router'
import { memo } from 'react'
import { toast } from 'sonner'
import {
Button,
Expand Down Expand Up @@ -41,13 +42,15 @@ import { formatSql } from '@/lib/formatSql'
export const TABLE_NODE_WIDTH = 320
export const TABLE_NODE_ROW_HEIGHT = 40

export const TableNode = ({
type TableNodeOwnProps = NodeProps<Node<TableNodeData>> & { placeholder?: boolean }

const TableNodeComponent = ({
id,
data,
targetPosition,
sourcePosition,
placeholder,
}: NodeProps<Node<TableNodeData>> & { placeholder?: boolean }) => {
}: TableNodeOwnProps) => {
// Important styles is a nasty hack to use Handles (required for edges calculations), but do not show them in the UI.
// ref: https://github.com/wbkd/react-flow/discussions/2698
const hiddenNodeConnector = 'h-px! w-px! min-w-0! min-h-0! cursor-grab! border-0! opacity-0!'
Expand Down Expand Up @@ -358,3 +361,17 @@ export const TableNode = ({
</article>
)
}

// Custom comparator: xyflow re-renders nodes on selection/drag with new prop
// objects; only the listed props affect rendered output. Selection-driven styling
// (highlighted edges) is read from context inside the component, so we can safely
// ignore xyflow's `selected`/`dragging`/etc. here.
export const TableNode = memo(
TableNodeComponent,
(prev, next) =>
prev.id === next.id &&
prev.data === next.data &&
prev.targetPosition === next.targetPosition &&
prev.sourcePosition === next.sourcePosition &&
prev.placeholder === next.placeholder
)
55 changes: 22 additions & 33 deletions apps/studio/components/interfaces/Database/Schemas/Schemas.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ export async function getGraphDataFromTables(
'id'
)

// Precompute name → { tableId, columnsByName } lookup so each relationship
// resolves its source/target handles in O(1) instead of scanning every table+column.
const tablesByName = new Map<string, { tableId: number; columnsByName: Map<string, string> }>()
for (const table of tables) {
const columnsByName = new Map<string, string>()
for (const column of table.columns || []) {
columnsByName.set(column.name, column.id)
}
tablesByName.set(table.name, { tableId: table.id, columnsByName })
}

const findHandleIds = (tableName: string, columnName: string): [string?, string?] => {
const entry = tablesByName.get(tableName)
if (!entry) return []
const columnId = entry.columnsByName.get(columnName)
if (columnId === undefined) return []
return [String(entry.tableId), columnId]
}

for (const rel of uniqueRelationships) {
// TODO: Support [external->this] relationship?
if (rel.source_schema !== currentSchema) {
Expand Down Expand Up @@ -96,11 +115,7 @@ export async function getGraphDataFromTables(
})
}

const [source, sourceHandle] = findTablesHandleIds(
tables,
rel.source_table_name,
rel.source_column_name
)
const [source, sourceHandle] = findHandleIds(rel.source_table_name, rel.source_column_name)

if (source) {
edges.push({
Expand All @@ -124,16 +139,8 @@ export async function getGraphDataFromTables(
continue
}

const [source, sourceHandle] = findTablesHandleIds(
tables,
rel.source_table_name,
rel.source_column_name
)
const [target, targetHandle] = findTablesHandleIds(
tables,
rel.target_table_name,
rel.target_column_name
)
const [source, sourceHandle] = findHandleIds(rel.source_table_name, rel.source_column_name)
const [target, targetHandle] = findHandleIds(rel.target_table_name, rel.target_column_name)

// We do not support [external->this] flow currently.
if (source && target) {
Expand Down Expand Up @@ -165,24 +172,6 @@ export async function getGraphDataFromTables(
: getLayoutedElementsViaDagre(nodes, edges)
}

function findTablesHandleIds(
tables: PGTable[],
table_name: string,
column_name: string
): [string?, string?] {
for (const table of tables) {
if (table_name !== table.name) continue

for (const column of table.columns || []) {
if (column_name !== column.name) continue

return [String(table.id), column.id]
}
}

return []
}

export const getLayoutedElementsViaDagre = (nodes: Node<TableNodeData>[], edges: Edge[]) => {
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
Expand Down
Loading
Loading