| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- import { Handle, Position } from '@xyflow/react'
- import { useParams } from 'common'
- import { AnalyticsBucket, BigQuery, Database } from 'icons'
- import { ComponentType, PropsWithChildren, useMemo } from 'react'
- import { AWS_REGIONS } from 'shared-data'
- import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
- import { getStatusName } from '../Pipeline.utils'
- import { getStatusLabel } from '../ReadReplicas/ReadReplicas.utils'
- import { STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants'
- import { getReplicationDestinationType, type ReplicationDestinationType } from './Nodes.utils'
- import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
- import { formatDatabaseID } from '@/data/read-replicas/replicas.utils'
- import { useReplicationDestinationsQuery } from '@/data/replication/destinations-query'
- import { useReplicationPipelineStatusQuery } from '@/data/replication/pipeline-status-query'
- import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { BASE_PATH } from '@/lib/constants'
- export const NODE_WIDTH = 480
- const destinationIconByType: Record<
- ReplicationDestinationType,
- ComponentType<{ className?: string; size?: string | number }>
- > = {
- BigQuery,
- 'Analytics Bucket': AnalyticsBucket,
- DuckLake: Database,
- }
- const NodeContainer = ({ className, children }: PropsWithChildren<{ className?: string }>) => {
- return (
- <div
- style={{ width: NODE_WIDTH / 2 + 55 }}
- className={cn(
- 'flex items-start justify-between p-3 rounded-sm bg-surface-100 border border-default',
- className
- )}
- >
- {children}
- </div>
- )
- }
- export const PrimaryDatabaseNode = () => {
- const { ref: projectRef } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const { data: databases = [] } = useReadReplicasQuery({ projectRef })
- const hasReadReplicas = databases.some((x) => x.identifier !== projectRef)
- const { data: destinationsData } = useReplicationDestinationsQuery({ projectRef })
- const hasDestinations = (destinationsData?.destinations ?? []).length > 0
- const region = Object.values(AWS_REGIONS).find((x) => x.code === project?.region)
- const hasReplication = hasReadReplicas || hasDestinations
- return (
- <NodeContainer>
- <div className="text-sm flex flex-col gap-y-0.5">
- <p>Primary Database</p>
- <p className="text-foreground-light">{region?.displayName}</p>
- <p className="text-foreground-light">{region?.code}</p>
- </div>
- {!!project && (
- <img
- alt="region icon"
- className="w-8 rounded-xs mt-0.5"
- src={`${BASE_PATH}/img/regions/${project?.region}.svg`}
- />
- )}
- <Handle
- type="source"
- position={Position.Right}
- className={hasReplication ? 'opacity-25' : 'opacity-0'}
- />
- </NodeContainer>
- )
- }
- export const ReplicationNode = ({ id }: { id: string }) => {
- const { ref: projectRef } = useParams()
- const { data: destinationsData } = useReplicationDestinationsQuery({ projectRef })
- const destination = (destinationsData?.destinations ?? []).find((x) => x.id.toString() === id)
- const { data: pipelinesData } = useReplicationPipelinesQuery({
- projectRef,
- })
- const pipeline = (pipelinesData?.pipelines ?? []).find((x) => x.destination_id.toString() === id)
- const { data: pipelineStatusData } = useReplicationPipelineStatusQuery(
- { projectRef, pipelineId: pipeline?.id },
- { refetchInterval: STATUS_REFRESH_FREQUENCY_MS }
- )
- const statusName = getStatusName(pipelineStatusData?.status)
- const type = getReplicationDestinationType(destination?.config)
- const DestinationIcon = type ? destinationIconByType[type] : undefined
- return (
- <NodeContainer className="justify-start gap-x-3">
- {DestinationIcon ? <DestinationIcon size={20} className="text-foreground-light" /> : null}
- <div className="text-sm flex flex-col gap-y-0.5">
- <div className="flex items-center">
- <p>{type}</p>
- <Tooltip>
- <TooltipTrigger>
- <div className="w-6 h-full flex items-center justify-center">
- <div
- className={cn(
- 'w-2 h-2 rounded-full',
- statusName === 'started'
- ? 'bg-brand'
- : statusName === 'failed'
- ? 'bg-destructive'
- : 'bg-selection'
- )}
- />
- </div>
- </TooltipTrigger>
- <TooltipContent side="bottom" className="capitalize">
- {statusName}
- </TooltipContent>
- </Tooltip>
- </div>
- <p className="text-foreground-light">{destination?.name}</p>
- <p className="text-foreground-light">ID: {destination?.id}</p>
- </div>
- <Handle type="target" position={Position.Left} className="opacity-25" />
- </NodeContainer>
- )
- }
- export const ReadReplicaNode = ({ id }: { id: string }) => {
- const { ref: projectRef } = useParams()
- const { data: databases = [] } = useReadReplicasQuery({ projectRef })
- const database = databases.find((x) => x.identifier === id)
- const region = Object.values(AWS_REGIONS).find((x) => x.code === database?.region)
- const formattedId = formatDatabaseID(database?.identifier ?? '')
- const statusLabel = useMemo(
- () => getStatusLabel({ status: database?.status }),
- [database?.status]
- )
- return (
- <NodeContainer className="justify-start gap-x-3">
- <Database size={20} className="text-foreground-light" />
- <div className="flex flex-col gap-y-0.5">
- <div className="flex items-center">
- <p className="text-sm">Read Replica</p>
- <Tooltip>
- <TooltipTrigger>
- <div className="w-6 h-full flex items-center justify-center">
- <div
- className={cn(
- 'w-2 h-2 rounded-full',
- database?.status === 'ACTIVE_HEALTHY' ? 'bg-brand' : 'bg-selection'
- )}
- />
- </div>
- </TooltipTrigger>
- <TooltipContent side="bottom">{statusLabel}</TooltipContent>
- </Tooltip>
- </div>
- <p className="text-sm text-foreground-light">{region?.displayName}</p>
- <div className="flex gap-x-2 items-center text-sm text-foreground-light">
- <span>ID: {formattedId}</span>
- <span>•</span>
- <span>{region?.code}</span>
- </div>
- </div>
- <Handle type="target" position={Position.Left} className="opacity-25" />
- </NodeContainer>
- )
- }
|