mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
## Context This is just pulling out the relevant changes from https://github.com/supabase/supabase/pull/49028 as I might have messed up the stack while making changes down the PRs 🙏 Builds on the Query Tab in the Explorer UI, adds the source selector component to run either a database query or a logs query - will subsequently be looking into have the source selector component in the QueryCell as well (within notebooks) <img width="1095" height="594" alt="image" src="https://github.com/user-attachments/assets/ab6e8d91-aed3-4845-9777-acbd7a3a7cb1" /> <img width="1086" height="667" alt="image" src="https://github.com/user-attachments/assets/75d99063-755d-4ba6-a128-7ddcbbc9f161" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added query-source selection for Explorer and notebook queries. - Supports log queries, database selection, and read-replica connections. - Added validation for custom log time ranges and retention limits, with upgrade guidance when applicable. - Query source choices are saved and restored across sessions. - Changing sources clears previous results to prevent stale data. - **Bug Fixes** - Improved handling of unavailable log querying and missing database connections. - Legacy saved queries now fall back safely to the default database source. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
154 lines
5.1 KiB
TypeScript
154 lines
5.1 KiB
TypeScript
import { useFlag, useParams } from 'common'
|
|
import dayjs from 'dayjs'
|
|
import { Check, ChevronDown } from 'lucide-react'
|
|
import { useState } from 'react'
|
|
import {
|
|
Button,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from 'ui'
|
|
|
|
import { DatabaseParametersSubMenu } from '@/components/interfaces/QuerySources/DatabaseParametersSubMenu'
|
|
import { LogsCustomRangeDialog } from '@/components/interfaces/QuerySources/LogsCustomRangeDialog'
|
|
import { LogsTimeRangeSubMenu } from '@/components/interfaces/QuerySources/LogsTimeRangeSubMenu'
|
|
import { QuerySourceIcon } from '@/components/interfaces/QuerySources/QuerySourceIcon'
|
|
import { maybeShowUpgradePromptIfNotEntitled } from '@/components/interfaces/Settings/Logs/Logs.utils'
|
|
import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
|
|
import {
|
|
createDefaultCellSource,
|
|
QUERY_SOURCE_LABELS,
|
|
QUERY_SOURCES,
|
|
type CellSource,
|
|
} from '@/data/query-sources/query-source-registry'
|
|
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
|
|
|
export type ExplorerQuerySourceMenuProps = {
|
|
source: CellSource
|
|
onSourceChange: (source: CellSource) => void
|
|
}
|
|
|
|
/**
|
|
* Source binding and parameter controls shared by standalone Explorer queries
|
|
* and notebook query-cell toolbars. The consumer owns the binding; this menu
|
|
* only emits complete, validated-by-construction `CellSource` values.
|
|
*/
|
|
export const ExplorerQuerySourceMenu = ({
|
|
source,
|
|
onSourceChange,
|
|
}: ExplorerQuerySourceMenuProps) => {
|
|
const { ref } = useParams()
|
|
const isLogsSourceEnabled = useFlag('sqlEditorLogsSource')
|
|
const isOtelLogsEnabled = useFlag('otelLegacyLogs')
|
|
const [isCustomRangeOpen, setIsCustomRangeOpen] = useState(false)
|
|
const [showUpgradePrompt, setShowUpgradePrompt] = useState(false)
|
|
const { getEntitlementNumericValue } = useCheckEntitlements('log.retention_days')
|
|
const entitledToLogDays = getEntitlementNumericValue()
|
|
|
|
const availableSources = QUERY_SOURCES.filter(
|
|
(candidate) =>
|
|
candidate.type !== 'logs' ||
|
|
(isLogsSourceEnabled && isOtelLogsEnabled) ||
|
|
source.type === 'logs'
|
|
)
|
|
|
|
const applyCustomRange = ({ from, to }: { from: Date; to: Date }) => {
|
|
const fromIso = dayjs(from).startOf('day').toISOString()
|
|
if (maybeShowUpgradePromptIfNotEntitled(fromIso, entitledToLogDays)) {
|
|
setShowUpgradePrompt(true)
|
|
return
|
|
}
|
|
|
|
onSourceChange({
|
|
id: 'logs',
|
|
type: 'logs',
|
|
parameters: {
|
|
time_range: {
|
|
type: 'absolute',
|
|
from: fromIso,
|
|
to: dayjs(to).endOf('day').toISOString(),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="text"
|
|
size="tiny"
|
|
aria-label={`Query source: ${QUERY_SOURCE_LABELS[source.id]}`}
|
|
icon={<QuerySourceIcon source={source.id} className="text-foreground-light" />}
|
|
iconRight={<ChevronDown className="text-foreground-light" />}
|
|
>
|
|
{QUERY_SOURCE_LABELS[source.id]}
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-60">
|
|
{availableSources.map((candidate) => (
|
|
<DropdownMenuItem
|
|
key={candidate.id}
|
|
className="justify-between"
|
|
onSelect={(event) => {
|
|
event.preventDefault()
|
|
if (candidate.id !== source.id) {
|
|
onSourceChange(createDefaultCellSource(candidate.id))
|
|
}
|
|
}}
|
|
>
|
|
<span className="flex items-center gap-x-2">
|
|
<QuerySourceIcon source={candidate.id} className="text-foreground-light" />
|
|
{QUERY_SOURCE_LABELS[candidate.id]}
|
|
</span>
|
|
{source.id === candidate.id && <Check size={14} />}
|
|
</DropdownMenuItem>
|
|
))}
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
{source.type === 'database' ? (
|
|
<DatabaseParametersSubMenu
|
|
identifier={source.parameters.identifier ?? ref}
|
|
onIdentifierChange={(identifier) =>
|
|
onSourceChange({
|
|
id: 'database',
|
|
type: 'database',
|
|
parameters: { identifier },
|
|
})
|
|
}
|
|
/>
|
|
) : (
|
|
<LogsTimeRangeSubMenu
|
|
range={source.parameters.time_range}
|
|
onRangeChange={(timeRange) =>
|
|
onSourceChange({
|
|
id: 'logs',
|
|
type: 'logs',
|
|
parameters: { time_range: timeRange },
|
|
})
|
|
}
|
|
onOpenCustomRange={() => setIsCustomRangeOpen(true)}
|
|
onShowUpgrade={() => setShowUpgradePrompt(true)}
|
|
/>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
{source.type === 'logs' && (
|
|
<>
|
|
<LogsCustomRangeDialog
|
|
open={isCustomRangeOpen}
|
|
onOpenChange={setIsCustomRangeOpen}
|
|
onApply={applyCustomRange}
|
|
/>
|
|
<UpgradePrompt show={showUpgradePrompt} setShowUpgradePrompt={setShowUpgradePrompt} />
|
|
</>
|
|
)}
|
|
</>
|
|
)
|
|
}
|