Address circular dependencies across multiple files (#39231)

* Address circular dependencies across multiple files

* Fix TS
This commit is contained in:
Joshen Lim authored and GitHub committed 2025-10-06 11:01:56 +08:00
1 parent 54ad0b7495
commit 426fda2ebc
38 files changed
+253 -264

No files matched your search

@@ -2,8 +2,8 @@ import type { Key } from 'react'
import { TriggerEvent, useContextMenu } from 'react-contexify'
import { RenderRowProps, Row } from 'react-data-grid'
import { ROW_CONTEXT_MENU_ID } from 'components/grid/constants'
import { SupaRow } from 'components/grid/types'
import { ROW_CONTEXT_MENU_ID } from '../menu'
export default function RowRenderer(key: Key, props: RenderRowProps<SupaRow>) {
const { show: showContextMenu } = useContextMenu()
@@ -3,11 +3,11 @@ import { useCallback } from 'react'
import { Item, ItemParams, Menu } from 'react-contexify'
import { toast } from 'sonner'
import { ROW_CONTEXT_MENU_ID } from 'components/grid/constants'
import type { SupaRow } from 'components/grid/types'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import { copyToClipboard, DialogSectionSeparator } from 'ui'
import { ROW_CONTEXT_MENU_ID } from '.'
import { formatClipboardValue } from '../../utils/common'
export type RowContextMenuProps = {
@@ -1,4 +1,2 @@
export { default as ColumnMenu } from './ColumnMenu'
export { default as RowContextMenu } from './RowContextMenu'
export const ROW_CONTEXT_MENU_ID = 'row-context-menu-id'
+2
View File
@@ -12,3 +12,5 @@ const RLS_ACKNOWLEDGED_KEY = 'supabase-acknowledge-rls-warning'
export const rlsAcknowledgedKey = (tableID?: string | number) =>
`${RLS_ACKNOWLEDGED_KEY}-${String(tableID)}`
export const ROW_CONTEXT_MENU_ID = 'row-context-menu-id'
@@ -22,8 +22,8 @@ import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
import { DeleteDestination } from './DeleteDestination'
import { DestinationPanel } from './DestinationPanel'
import { getStatusName, PIPELINE_ERROR_MESSAGES } from './Pipeline.utils'
import { PipelineStatus, PipelineStatusName } from './PipelineStatus'
import { STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants'
import { PipelineStatus } from './PipelineStatus'
import { PipelineStatusName, STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants'
import { RowMenu } from './RowMenu'
import { UpdateVersionModal } from './UpdateVersionModal'
@@ -1,6 +1,6 @@
import { ReplicationPipelineStatusData } from 'data/replication/pipeline-status-query'
import { PipelineStatusRequestStatus } from 'state/replication-pipeline-request-status'
import { PipelineStatusName } from './PipelineStatus'
import { PipelineStatusName } from './Replication.constants'
export const PIPELINE_ERROR_MESSAGES = {
RETRIEVE_PIPELINE: 'Failed to retrieve pipeline information',
@@ -8,14 +8,7 @@ import { ResponseError } from 'types'
import { cn, Tooltip, TooltipContent, TooltipTrigger, WarningIcon } from 'ui'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
import { getPipelineStateMessages } from './Pipeline.utils'
export enum PipelineStatusName {
FAILED = 'failed',
STARTING = 'starting',
STARTED = 'started',
STOPPED = 'stopped',
UNKNOWN = 'unknown',
}
import { PipelineStatusName } from './Replication.constants'
interface PipelineStatusProps {
pipelineStatus: ReplicationPipelineStatusData['status'] | undefined
@@ -1 +1,9 @@
export const STATUS_REFRESH_FREQUENCY_MS: number = 5000
export enum PipelineStatusName {
FAILED = 'failed',
STARTING = 'starting',
STARTED = 'started',
STOPPED = 'stopped',
UNKNOWN = 'unknown',
}
@@ -39,8 +39,8 @@ import {
PIPELINE_ERROR_MESSAGES,
getStatusName,
} from '../Pipeline.utils'
import { PipelineStatus, PipelineStatusName } from '../PipelineStatus'
import { STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants'
import { PipelineStatus } from '../PipelineStatus'
import { PipelineStatusName, STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants'
import { UpdateVersionModal } from '../UpdateVersionModal'
import { SlotLagMetrics, TableState } from './ReplicationPipelineStatus.types'
import { getDisabledStateConfig, getStatusConfig } from './ReplicationPipelineStatus.utils'
@@ -1,4 +1,4 @@
import { Edit, MoreVertical, Pause, Play, RotateCcw, Trash, ArrowUpCircle } from 'lucide-react'
import { ArrowUpCircle, Edit, MoreVertical, Pause, Play, RotateCcw, Trash } from 'lucide-react'
import { toast } from 'sonner'
import { useParams } from 'common'
@@ -28,7 +28,7 @@ import {
PIPELINE_ERROR_MESSAGES,
getStatusName,
} from './Pipeline.utils'
import { PipelineStatusName } from './PipelineStatus'
import { PipelineStatusName } from './Replication.constants'
interface RowMenuProps {
pipeline: Pipeline | undefined
@@ -12,8 +12,7 @@ import {
} from 'state/replication-pipeline-request-status'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { getStatusName } from './Pipeline.utils'
import { PipelineStatusName } from './PipelineStatus'
import { STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants'
import { PipelineStatusName, STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants'
interface UpdateVersionModalProps {
visible: boolean
@@ -26,8 +25,6 @@ interface UpdateVersionModalProps {
export const UpdateVersionModal = ({
visible,
pipeline,
// currentVersionName,
// newVersionName,
confirmLabel = 'Update and restart',
confirmLabelLoading = 'Updating',
onClose,
@@ -0,0 +1,122 @@
import { toString as CronToString } from 'cronstrue'
import z from 'zod'
import { urlRegex } from 'components/interfaces/Auth/Auth.constants'
import { cronPattern, secondsPattern } from '../CronJobs.constants'
const convertCronToString = (schedule: string) => {
// pg_cron can also use "30 seconds" format for schedule. Cronstrue doesn't understand that format so just use the
// original schedule when cronstrue throws
try {
return CronToString(schedule)
} catch (error) {
return schedule
}
}
const edgeFunctionSchema = z.object({
type: z.literal('edge_function'),
method: z.enum(['GET', 'POST']),
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() })),
httpBody: z
.string()
.trim()
.optional()
.refine((value) => {
if (!value) return true
try {
JSON.parse(value)
return true
} catch {
return false
}
}, 'Input must be valid JSON'),
// When editing a cron job, we want to keep the original command as a snippet in case the user wants to manually edit it
snippet: z.string().trim(),
})
const httpRequestSchema = z.object({
type: z.literal('http_request'),
method: z.enum(['GET', 'POST']),
endpoint: z
.string()
.trim()
.min(1, 'Please provide a URL')
.regex(urlRegex(), 'Please provide a valid URL')
.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() })),
httpBody: z
.string()
.trim()
.optional()
.refine((value) => {
if (!value) return true
try {
JSON.parse(value)
return true
} catch {
return false
}
}, 'Input must be valid JSON'),
// When editing a cron job, we want to keep the original command as a snippet in case the user wants to manually edit it
snippet: z.string().trim(),
})
const sqlFunctionSchema = z.object({
type: z.literal('sql_function'),
schema: z.string().trim().min(1, 'Please select one of the listed database schemas'),
functionName: z.string().trim().min(1, 'Please select one of the listed database functions'),
// When editing a cron job, we want to keep the original command as a snippet in case the user wants to manually edit it
snippet: z.string().trim(),
})
const sqlSnippetSchema = z.object({
type: z.literal('sql_snippet'),
snippet: z.string().trim().min(1),
})
export const FormSchema = z
.object({
name: z.string().trim().min(1, 'Please provide a name for your cron job'),
supportsSeconds: z.boolean(),
schedule: z
.string()
.trim()
.min(1)
.refine((value) => {
if (cronPattern.test(value)) {
try {
convertCronToString(value)
return true
} catch {
return false
}
} else if (secondsPattern.test(value)) {
return true
}
return false
}, 'Invalid Cron format'),
values: z.discriminatedUnion('type', [
edgeFunctionSchema,
httpRequestSchema,
sqlFunctionSchema,
sqlSnippetSchema,
]),
})
.superRefine((data, ctx) => {
if (!cronPattern.test(data.schedule)) {
if (!(data.supportsSeconds && secondsPattern.test(data.schedule))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Seconds are supported only in pg_cron v1.5.0+. Please use a valid Cron format.',
path: ['schedule'],
})
}
}
})
export type CreateCronJobForm = z.infer<typeof FormSchema>
export type CronJobType = CreateCronJobForm['values']
@@ -1,15 +1,12 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { toString as CronToString } from 'cronstrue'
import { parseAsString, useQueryState } from 'nuqs'
import { useEffect, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import z from 'zod'
import { useWatch } from '@ui/components/shadcn/ui/form'
import { useParams } from 'common'
import { urlRegex } from 'components/interfaces/Auth/Auth.constants'
import EnableExtensionModal from 'components/interfaces/Database/Extensions/EnableExtensionModal'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { getDatabaseCronJob } from 'data/database-cron-jobs/database-cron-job-query'
@@ -38,23 +35,22 @@ import {
import { Admonition } from 'ui-patterns/admonition'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CRONJOB_DEFINITIONS } from './CronJobs.constants'
import { CRONJOB_DEFINITIONS } from '../CronJobs.constants'
import { buildCronQuery, buildHttpRequestCommand, parseCronJobCommand } from '../CronJobs.utils'
import { EdgeFunctionSection } from '../EdgeFunctionSection'
import { HttpBodyFieldSection } from '../HttpBodyFieldSection'
import { HTTPHeaderFieldsSection } from '../HttpHeaderFieldsSection'
import { HttpRequestSection } from '../HttpRequestSection'
import { SqlFunctionSection } from '../SqlFunctionSection'
import { SqlSnippetSection } from '../SqlSnippetSection'
import {
buildCronQuery,
buildHttpRequestCommand,
cronPattern,
parseCronJobCommand,
secondsPattern,
} from './CronJobs.utils'
FormSchema,
type CreateCronJobForm,
type CronJobType,
} from './CreateCronJobSheet.constants'
import { CronJobScheduleSection } from './CronJobScheduleSection'
import { EdgeFunctionSection } from './EdgeFunctionSection'
import { HttpBodyFieldSection } from './HttpBodyFieldSection'
import { HTTPHeaderFieldsSection } from './HttpHeaderFieldsSection'
import { HttpRequestSection } from './HttpRequestSection'
import { SqlFunctionSection } from './SqlFunctionSection'
import { SqlSnippetSection } from './SqlSnippetSection'
export interface CreateCronJobSheetProps {
interface CreateCronJobSheetProps {
selectedCronJob?: Pick<CronJob, 'jobname' | 'schedule' | 'active' | 'command'>
supportsSeconds: boolean
isClosing: boolean
@@ -62,113 +58,6 @@ export interface CreateCronJobSheetProps {
onClose: () => void
}
const edgeFunctionSchema = z.object({
type: z.literal('edge_function'),
method: z.enum(['GET', 'POST']),
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() })),
httpBody: z
.string()
.trim()
.optional()
.refine((value) => {
if (!value) return true
try {
JSON.parse(value)
return true
} catch {
return false
}
}, 'Input must be valid JSON'),
// When editing a cron job, we want to keep the original command as a snippet in case the user wants to manually edit it
snippet: z.string().trim(),
})
const httpRequestSchema = z.object({
type: z.literal('http_request'),
method: z.enum(['GET', 'POST']),
endpoint: z
.string()
.trim()
.min(1, 'Please provide a URL')
.regex(urlRegex(), 'Please provide a valid URL')
.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() })),
httpBody: z
.string()
.trim()
.optional()
.refine((value) => {
if (!value) return true
try {
JSON.parse(value)
return true
} catch {
return false
}
}, 'Input must be valid JSON'),
// When editing a cron job, we want to keep the original command as a snippet in case the user wants to manually edit it
snippet: z.string().trim(),
})
const sqlFunctionSchema = z.object({
type: z.literal('sql_function'),
schema: z.string().trim().min(1, 'Please select one of the listed database schemas'),
functionName: z.string().trim().min(1, 'Please select one of the listed database functions'),
// When editing a cron job, we want to keep the original command as a snippet in case the user wants to manually edit it
snippet: z.string().trim(),
})
const sqlSnippetSchema = z.object({
type: z.literal('sql_snippet'),
snippet: z.string().trim().min(1),
})
const FormSchema = z
.object({
name: z.string().trim().min(1, 'Please provide a name for your cron job'),
supportsSeconds: z.boolean(),
schedule: z
.string()
.trim()
.min(1)
.refine((value) => {
if (cronPattern.test(value)) {
try {
CronToString(value)
return true
} catch {
return false
}
} else if (secondsPattern.test(value)) {
return true
}
return false
}, 'Invalid Cron format'),
values: z.discriminatedUnion('type', [
edgeFunctionSchema,
httpRequestSchema,
sqlFunctionSchema,
sqlSnippetSchema,
]),
})
.superRefine((data, ctx) => {
if (!cronPattern.test(data.schedule)) {
if (!(data.supportsSeconds && secondsPattern.test(data.schedule))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Seconds are supported only in pg_cron v1.5.0+. Please use a valid Cron format.',
path: ['schedule'],
})
}
}
})
export type CreateCronJobForm = z.infer<typeof FormSchema>
export type CronJobType = CreateCronJobForm['values']
const FORM_ID = 'create-cron-job-sidepanel'
const buildCommand = (values: CronJobType) => {
@@ -23,9 +23,9 @@ import {
Switch,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { CreateCronJobForm } from './CreateCronJobSheet'
import { formatScheduleString, getScheduleMessage } from './CronJobs.utils'
import CronSyntaxChart from './CronSyntaxChart'
import { formatScheduleString, getScheduleMessage } from '../CronJobs.utils'
import CronSyntaxChart from '../CronSyntaxChart'
import { type CreateCronJobForm } from './CreateCronJobSheet.constants'
interface CronJobScheduleSectionProps {
form: UseFormReturn<CreateCronJobForm>
@@ -20,7 +20,7 @@ import {
TooltipTrigger,
} from 'ui'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
import { CreateCronJobSheet } from './CreateCronJobSheet'
import { CreateCronJobSheet } from './CreateCronJobSheet/CreateCronJobSheet'
import { isSecondsFormat, parseCronJobCommand } from './CronJobs.utils'
import { PreviousRunsTab } from './PreviousRunsTab'
@@ -1,3 +1,4 @@
import parser from 'cron-parser'
import dayjs from 'dayjs'
import { Clipboard, Edit, MoreVertical, Play, Trash } from 'lucide-react'
import { parseAsString, useQueryState } from 'nuqs'
@@ -41,7 +42,34 @@ import {
TooltipTrigger,
} from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { getNextRun } from './CronJobs.utils'
const getNextRun = (schedule: string, lastRun?: string) => {
// cron-parser can only deal with the traditional cron syntax but technically users can also
// use strings like "30 seconds" now, For the latter case, we try our best to parse the next run
// (can't guarantee as scope is quite big)
if (schedule.includes('*')) {
try {
const interval = parser.parseExpression(schedule, { tz: 'UTC' })
return interval.next().getTime()
} catch (error) {
return undefined
}
} else {
// [Joshen] Only going to attempt to parse if the schedule is as simple as "n second" or "n seconds"
// Returned undefined otherwise - we can revisit this perhaps if we get feedback about this
const [value, unit] = schedule.toLocaleLowerCase().split(' ')
if (
['second', 'seconds'].includes(unit) &&
!Number.isNaN(Number(value)) &&
lastRun !== undefined
) {
const parsedLastRun = dayjs(lastRun).add(Number(value), unit as dayjs.ManipulateType)
return parsedLastRun.valueOf()
} else {
return undefined
}
}
}
interface CronJobTableCellProps {
col: any
@@ -1,6 +1,12 @@
import { EdgeFunctions, RESTApi, SqlEditor } from 'icons'
import { ScrollText } from 'lucide-react'
export const cronPattern =
/^(\*|(\d+|\*\/\d+)|\d+\/\d+|\d+-\d+|\d+(,\d+)*)(\s+(\*|(\d+|\*\/\d+)|\d+\/\d+|\d+-\d+|\d+(,\d+)*)){4}$/
// detect seconds like "10 seconds" or normal cron syntax like "*/5 * * * *"
export const secondsPattern = /^\d+\s+seconds*$/
export const CRONJOB_TYPES = [
'http_request',
'edge_function',
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest'
import { cronPattern, parseCronJobCommand, secondsPattern } from './CronJobs.utils'
import { cronPattern, secondsPattern } from './CronJobs.constants'
import { parseCronJobCommand } from './CronJobs.utils'
describe('parseCronJobCommand', () => {
it('should return a default object when the command is null', () => {
@@ -1,12 +1,10 @@
import parser from 'cron-parser'
import { toString as CronToString } from 'cronstrue'
import dayjs from 'dayjs'
import { Column } from 'react-data-grid'
import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-infinite-query'
import { cn } from 'ui'
import { CronJobType } from './CreateCronJobSheet'
import { CRON_TABLE_COLUMNS, HTTPHeader } from './CronJobs.constants'
import { CronJobType } from './CreateCronJobSheet/CreateCronJobSheet.constants'
import { CRON_TABLE_COLUMNS, HTTPHeader, secondsPattern } from './CronJobs.constants'
import { CronJobTableCell } from './CronJobTableCell'
export function buildCronQuery(name: string, schedule: string, command: string) {
@@ -186,12 +184,6 @@ export function formatDate(dateString: string): string {
return date.toLocaleString(undefined, options)
}
export const cronPattern =
/^(\*|(\d+|\*\/\d+)|\d+\/\d+|\d+-\d+|\d+(,\d+)*)(\s+(\*|(\d+|\*\/\d+)|\d+\/\d+|\d+-\d+|\d+(,\d+)*)){4}$/
// detect seconds like "10 seconds" or normal cron syntax like "*/5 * * * *"
export const secondsPattern = /^\d+\s+seconds*$/
export function isSecondsFormat(schedule: string): boolean {
return secondsPattern.test(schedule.trim().toLocaleLowerCase())
}
@@ -230,44 +222,6 @@ export const formatScheduleString = (value: string) => {
}
}
export const convertCronToString = (schedule: string) => {
// pg_cron can also use "30 seconds" format for schedule. Cronstrue doesn't understand that format so just use the
// original schedule when cronstrue throws
try {
return CronToString(schedule)
} catch (error) {
return schedule
}
}
export const getNextRun = (schedule: string, lastRun?: string) => {
// cron-parser can only deal with the traditional cron syntax but technically users can also
// use strings like "30 seconds" now, For the latter case, we try our best to parse the next run
// (can't guarantee as scope is quite big)
if (schedule.includes('*')) {
try {
const interval = parser.parseExpression(schedule, { tz: 'UTC' })
return interval.next().getTime()
} catch (error) {
return undefined
}
} else {
// [Joshen] Only going to attempt to parse if the schedule is as simple as "n second" or "n seconds"
// Returned undefined otherwise - we can revisit this perhaps if we get feedback about this
const [value, unit] = schedule.toLocaleLowerCase().split(' ')
if (
['second', 'seconds'].includes(unit) &&
!Number.isNaN(Number(value)) &&
lastRun !== undefined
) {
const parsedLastRun = dayjs(lastRun).add(Number(value), unit as dayjs.ManipulateType)
return parsedLastRun.valueOf()
} else {
return undefined
}
}
}
export const formatCronJobColumns = ({
onSelectEdit,
onSelectDelete,
@@ -5,7 +5,7 @@ import { UIEvent, useMemo, useRef, useState } from 'react'
import DataGrid, { DataGridHandle, Row } from 'react-data-grid'
import { useParams } from 'common'
import { CreateCronJobSheet } from 'components/interfaces/Integrations/CronJobs/CreateCronJobSheet'
import { CreateCronJobSheet } from 'components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CreateCronJobSheet'
import AlertError from 'components/ui/AlertError'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useCronJobsCountQuery } from 'data/database-cron-jobs/database-cron-jobs-count-query'
@@ -21,7 +21,7 @@ import {
SheetSection,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateCronJobForm } from './CreateCronJobSheet'
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
interface HTTPRequestFieldsProps {
form: UseFormReturn<CreateCronJobForm>
@@ -2,7 +2,6 @@ import { UseFormReturn } from 'react-hook-form'
import {
FormControl_Shadcn_,
FormDescription_Shadcn_,
FormField_Shadcn_,
FormItem_Shadcn_,
FormLabel_Shadcn_,
@@ -10,7 +9,7 @@ import {
SheetSection,
TextArea_Shadcn_,
} from 'ui'
import { CreateCronJobForm } from './CreateCronJobSheet'
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
interface HttpBodyFieldSectionProps {
form: UseFormReturn<CreateCronJobForm>
@@ -19,7 +19,7 @@ import {
Input_Shadcn_,
SheetSection,
} from 'ui'
import { CreateCronJobForm } from './CreateCronJobSheet'
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
interface HTTPHeaderFieldsSectionProps {
variant: 'edge_function' | 'http_request'
@@ -15,7 +15,7 @@ import {
SheetSection,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateCronJobForm } from './CreateCronJobSheet'
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
interface HttpRequestSectionProps {
form: UseFormReturn<CreateCronJobForm>
@@ -4,7 +4,7 @@ import FunctionSelector from 'components/ui/FunctionSelector'
import SchemaSelector from 'components/ui/SchemaSelector'
import { FormField_Shadcn_, SheetSection } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateCronJobForm } from './CreateCronJobSheet'
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
interface SqlFunctionSectionProps {
form: UseFormReturn<CreateCronJobForm>
@@ -3,7 +3,7 @@ import { UseFormReturn } from 'react-hook-form'
import CodeEditor from 'components/ui/CodeEditor/CodeEditor'
import { FormField_Shadcn_, SheetSection } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateCronJobForm } from './CreateCronJobSheet'
import { CreateCronJobForm } from './CreateCronJobSheet/CreateCronJobSheet.constants'
interface SqlSnippetSectionProps {
form: UseFormReturn<CreateCronJobForm>
@@ -18,7 +18,7 @@ import { Edit3, Eye, EyeOff, Key, Loader, MoreVertical, Trash } from 'lucide-rea
import type { VaultSecret } from 'types'
import { Input } from 'ui-patterns/DataInputs/Input'
import EditSecretModal from './EditSecretModal'
import type { SecretTableColumn } from './Secrets.utils'
import { SecretTableColumn } from './Secrets.types'
interface SecretRowProps {
row: VaultSecret
@@ -0,0 +1,7 @@
export interface SecretTableColumn {
id: 'secret' | 'id' | 'secret_value' | 'updated_at' | 'actions'
name: string
minWidth?: number
width?: number
maxWidth?: number
}
@@ -3,16 +3,7 @@ import type { Column } from 'react-data-grid'
import type { VaultSecret } from 'types'
import { cn } from 'ui'
import SecretRow from './SecretRow'
export type SecretColumnId = 'secret' | 'id' | 'secret_value' | 'updated_at' | 'actions'
export interface SecretTableColumn {
id: SecretColumnId
name: string
minWidth?: number
width?: number
maxWidth?: number
}
import { SecretTableColumn } from './Secrets.types'
export const SECRET_TABLE_COLUMNS: SecretTableColumn[] = [
{ id: 'secret', name: 'Secret', minWidth: 300, width: 360 },
@@ -30,6 +30,8 @@ import { WrapperMeta } from './Wrappers.types'
import { makeValidateRequired } from './Wrappers.utils'
import WrapperTableEditor from './WrapperTableEditor'
const FORM_ID = 'create-wrapper-form'
export interface CreateWrapperSheetProps {
isClosing: boolean
wrapperMeta: WrapperMeta
@@ -37,8 +39,6 @@ export interface CreateWrapperSheetProps {
onClose: () => void
}
const FORM_ID = 'create-wrapper-form'
export const CreateWrapperSheet = ({
wrapperMeta,
isClosing,
@@ -18,6 +18,7 @@ import {
WarningIcon,
} from 'ui'
import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
import { CreateIcebergWrapperSheet } from './CreateIcebergWrapperSheet'
import { CreateWrapperSheet } from './CreateWrapperSheet'
import { WRAPPERS } from './Wrappers.constants'
import { WrapperTable } from './WrapperTable'
@@ -53,7 +54,13 @@ export const WrapperOverviewTab = () => {
const databaseNeedsUpgrading =
wrappersExtension?.installed_version === wrappersExtension?.default_version
const CreateWrapperSheetComponent = wrapperMeta.createComponent || CreateWrapperSheet
// [Joshen] Opting to declare custom wrapper sheets here instead of within Wrappers.constants.ts
// as we'll easily run into circular dependencies doing so unfortunately
const CreateWrapperSheetComponent = wrapperMeta.customComponent
? wrapperMeta.name === 'iceberg_wrapper'
? CreateIcebergWrapperSheet
: ({}) => null
: CreateWrapperSheet
return (
<IntegrationOverviewTab
@@ -1,5 +1,4 @@
import { BASE_PATH, DOCS_URL } from 'lib/constants'
import { CreateIcebergWrapperSheet } from './CreateIcebergWrapperSheet'
import type { ServerOption, WrapperMeta } from './Wrappers.types'
export const WRAPPER_HANDLERS = {
@@ -2175,7 +2174,7 @@ export const WRAPPERS: WrapperMeta[] = [
label: 'Iceberg',
docsUrl: `${DOCS_URL}/guides/database/extensions/wrappers/iceberg`,
minimumExtensionVersion: '0.5.3',
createComponent: CreateIcebergWrapperSheet,
customComponent: true,
server: {
// The fields are intentionally not required. The required flag is enforced in the create iceberg wrapper sheet.
// In the edit wrapper sheet, all fields are shown and not required.
@@ -1,5 +1,3 @@
import { CreateWrapperSheetProps } from './CreateWrapperSheet'
export type WrapperMeta = {
name: string
handlerName: string
@@ -12,7 +10,7 @@ export type WrapperMeta = {
server: Server
docsUrl: string
minimumExtensionVersion?: string
createComponent?: React.ComponentType<CreateWrapperSheetProps>
customComponent?: boolean
// If true, the wrapper can target a schema which will be populated with tables specified by the wrapper..
canTargetSchema?: boolean
sourceSchemaOption?: ServerOption
@@ -1,5 +1,4 @@
import type { ResponseError } from 'types'
import { DEFAULT_QUERY_PARAMS } from './Reports.constants'
export enum Presets {
API = 'api',
@@ -11,7 +10,9 @@ export enum Presets {
export type MetaQueryResponse = any & { error: ResponseError }
export type BaseReportParams = typeof DEFAULT_QUERY_PARAMS & { sql?: string } & unknown
export type BaseReportParams = { iso_timestamp_start: string; iso_timestamp_end: string } & {
sql?: string
} & unknown
export interface PresetConfig {
title: string
queries: BaseQueries<string>
@@ -15,23 +15,6 @@ import {
SLIDER_DELIMITER,
SORT_DELIMITER,
} from 'components/ui/DataTable/DataTable.constants'
import { ChartConfig } from 'ui'
import { TooltipLabel } from './components/TooltipLabel'
export const CHART_CONFIG = {
success: {
label: <TooltipLabel level="success" />,
color: 'hsl(var(--foreground-muted))',
},
warning: {
label: <TooltipLabel level="warning" />,
color: 'hsl(var(--warning-default))',
},
error: {
label: <TooltipLabel level="error" />,
color: 'hsl(var(--destructive-default))',
},
} satisfies ChartConfig
export const REGIONS = ['ams', 'fra', 'gru', 'hkg', 'iad', 'syd'] as const
export const METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] as const
@@ -44,13 +44,29 @@ import { RefreshButton } from '../../ui/DataTable/RefreshButton'
import { generateDynamicColumns, UNIFIED_LOGS_COLUMNS } from './components/Columns'
import { DownloadLogsButton } from './components/DownloadLogsButton'
import { LogsListPanel } from './components/LogsListPanel'
import { TooltipLabel } from './components/TooltipLabel'
import { ServiceFlowPanel } from './ServiceFlowPanel'
import { CHART_CONFIG, SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants'
import { SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants'
import { filterFields as defaultFilterFields } from './UnifiedLogs.fields'
import { useLiveMode, useResetFocus } from './UnifiedLogs.hooks'
import { QuerySearchParamsType } from './UnifiedLogs.types'
import { getFacetedUniqueValues, getLevelRowClassName } from './UnifiedLogs.utils'
export const CHART_CONFIG = {
success: {
label: <TooltipLabel level="success" />,
color: 'hsl(var(--foreground-muted))',
},
warning: {
label: <TooltipLabel level="warning" />,
color: 'hsl(var(--warning-default))',
},
error: {
label: <TooltipLabel level="error" />,
color: 'hsl(var(--destructive-default))',
},
} satisfies ChartConfig
export const UnifiedLogs = () => {
useResetFocus()
+4 -7
View File
@@ -1,12 +1,9 @@
import type { ContentType } from './content-query'
import type { SqlSnippet } from './sql-snippets-query'
export const contentKeys = {
allContentLists: (projectRef: string | undefined) => ['projects', projectRef, 'content'] as const,
infiniteList: (
projectRef: string | undefined,
options?: {
type: ContentType | undefined
type: string
name: string | undefined
limit?: number
sort?: string
@@ -14,14 +11,14 @@ export const contentKeys = {
) => ['projects', projectRef, 'content-infinite', options].filter(Boolean),
list: (
projectRef: string | undefined,
options: { type?: ContentType; name?: string; limit?: number }
options: { type?: string; name?: string; limit?: number }
) => ['projects', projectRef, 'content', options] as const,
sqlSnippets: (
projectRef: string | undefined,
options?: {
sort?: 'inserted_at' | 'name'
name?: string
visibility?: SqlSnippet['visibility']
visibility?: string
favorite?: boolean
}
) => ['projects', projectRef, 'content', 'sql', options].filter(Boolean),
@@ -41,7 +38,7 @@ export const contentKeys = {
type?: string,
options?: {
cumulative?: boolean
visibility?: SqlSnippet['visibility']
visibility?: string
favorite?: boolean
name?: string
}
+2 -9
View File
@@ -1,12 +1,5 @@
import type { GetTableRowsArgs } from './table-rows-query'
type TableRowKeyArgs = Omit<GetTableRowsArgs, 'table'> & { table?: { id?: number } }
export const tableRowKeys = {
tableRows: (
projectRef?: string,
{ table, roleImpersonationState, ...args }: TableRowKeyArgs = {}
) =>
tableRows: (projectRef?: string, { table, roleImpersonationState, ...args }: any = {}) =>
[
'projects',
projectRef,
@@ -15,7 +8,7 @@ export const tableRowKeys = {
'rows',
{ roleImpersonation: roleImpersonationState?.role, ...args },
] as const,
tableRowsCount: (projectRef?: string, { table, ...args }: TableRowKeyArgs = {}) =>
tableRowsCount: (projectRef?: string, { table, ...args }: any = {}) =>
['projects', projectRef, 'table-rows', table?.id, 'count', args] as const,
tableRowsAndCount: (projectRef?: string, tableId?: number) =>
['projects', projectRef, 'table-rows', tableId] as const,