| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- import { useQueryClient } from '@tanstack/react-query'
- import { Background, ColorMode, ReactFlow, ReactFlowProvider, useReactFlow } from '@xyflow/react'
- import { useParams } from 'common'
- import { useTheme } from 'next-themes'
- import { useEffect, useMemo } from 'react'
- import { getStatusName } from '../Pipeline.utils'
- import { PrimaryDatabaseNode, ReadReplicaNode, ReplicationNode } from './Nodes'
- import { getDagreGraphLayout } from './ReplicationDiagram.utils'
- import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
- import { useReplicationDestinationsQuery } from '@/data/replication/destinations-query'
- import { replicationKeys } from '@/data/replication/keys'
- import { ReplicationPipelineStatusResponse } from '@/data/replication/pipeline-status-query'
- import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query'
- import { timeout } from '@/lib/helpers'
- import '@xyflow/react/dist/style.css'
- import { SmoothstepEdge } from './Edges'
- import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
- export const ReplicationDiagram = () => {
- return (
- <ReactFlowProvider>
- <ReplicationDiagramContent />
- </ReactFlowProvider>
- )
- }
- const nodeTypes = {
- primary: PrimaryDatabaseNode,
- replication: ReplicationNode,
- readReplica: ReadReplicaNode,
- }
- const edgeTypes = { smoothstep: SmoothstepEdge }
- const ReplicationDiagramContent = () => {
- const reactFlow = useReactFlow()
- const { resolvedTheme } = useTheme()
- const queryClient = useQueryClient()
- const { ref: projectRef = 'default' } = useParams()
- const { data: databases = [], isSuccess: isSuccessReplicas } = useReadReplicasQuery({
- projectRef,
- })
- const readReplicas = useMemo(
- () => databases.filter((x) => x.identifier !== projectRef),
- [databases, projectRef]
- )
- const { data, isSuccess: isSuccessDestinations } = useReplicationDestinationsQuery({
- projectRef,
- })
- const destinations = useMemo(() => data?.destinations ?? [], [data])
- const { data: pipelinesData } = useReplicationPipelinesQuery({ projectRef })
- const nodes = useMemo(() => {
- return [
- { id: projectRef, type: 'primary', data: {}, position: { x: 0, y: 5 } },
- ...readReplicas.map((x) => ({
- id: x.identifier,
- type: 'readReplica',
- data: {},
- position: { x: 0, y: 0 },
- })),
- ...destinations.map((x) => ({
- id: x.id.toString(),
- type: 'replication',
- data: {},
- position: { x: 0, y: 0 },
- })),
- ]
- }, [destinations, projectRef, readReplicas])
- const edges = useMemo(() => {
- return [
- ...readReplicas.map((x) => {
- const isReplicating = x.status === 'ACTIVE_HEALTHY'
- return {
- id: `${projectRef}-${x.identifier}`,
- source: projectRef,
- target: x.identifier,
- type: 'smoothstep',
- className: 'cursor-default!',
- animated: isReplicating,
- style: {
- opacity: isReplicating ? 1 : 0.4,
- strokeDasharray: isReplicating ? undefined : '5 5',
- },
- data: {
- type: 'replica',
- identifier: x.identifier,
- shiftEdgeEnd: readReplicas.length + destinations.length > 1,
- isReplicating,
- isComingUp: [
- REPLICA_STATUS.COMING_UP,
- REPLICA_STATUS.INIT_READ_REPLICA,
- REPLICA_STATUS.UNKNOWN,
- ].includes(x.status),
- isFailed: [REPLICA_STATUS.ACTIVE_UNHEALTHY, REPLICA_STATUS.INIT_FAILED].includes(
- x.status
- ),
- },
- }
- }),
- ...destinations.map((x) => {
- const pipeline = (pipelinesData?.pipelines ?? []).find((p) => p.destination_id === x.id)
- const pipelineStatus = queryClient.getQueryData(
- replicationKeys.pipelinesStatus(projectRef, pipeline?.id)
- ) as ReplicationPipelineStatusResponse
- const statusName = getStatusName(pipelineStatus?.status)
- const isReplicating = statusName === 'started'
- return {
- id: `${projectRef}-${x.id}`,
- source: projectRef,
- target: x.id.toString(),
- type: 'smoothstep',
- className: 'cursor-default!',
- animated: isReplicating,
- style: {
- opacity: isReplicating ? 1 : 0.4,
- strokeDasharray: isReplicating ? undefined : '5 5',
- },
- data: {
- type: 'etl',
- identifier: x.id,
- shiftEdgeEnd: readReplicas.length + destinations.length > 1,
- isReplicating,
- isComingUp: ['starting'].includes(statusName ?? ''),
- isFailed: ['failed'].includes(statusName ?? ''),
- },
- }
- }),
- ]
- }, [destinations, pipelinesData?.pipelines, projectRef, queryClient, readReplicas])
- const backgroundPatternColor =
- resolvedTheme === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.4)'
- const setReactFlow = async () => {
- const graph = getDagreGraphLayout(nodes, edges)
- reactFlow.setNodes(graph.nodes)
- reactFlow.setEdges(graph.edges)
- // [Joshen] Odd fix to ensure that react flow snaps back to center when adding nodes
- await timeout(1)
- reactFlow.fitView({ minZoom: 0.8, maxZoom: 0.9 })
- }
- useEffect(() => {
- if (nodes.length > 0 && isSuccessDestinations && isSuccessReplicas) {
- setReactFlow()
- }
- }, [nodes, isSuccessDestinations, isSuccessReplicas])
- return (
- <div className="nowheel relative min-h-[350px]">
- <ReactFlow
- // FIXME: https://github.com/xyflow/xyflow/issues/4876
- colorMode={'' as unknown as ColorMode}
- fitView
- fitViewOptions={{ minZoom: 0.8, maxZoom: 0.9 }}
- className="bg"
- zoomOnPinch={false}
- zoomOnScroll={false}
- nodesDraggable={false}
- nodesConnectable={false}
- zoomOnDoubleClick={false}
- edgesFocusable={false}
- edgesReconnectable={false}
- defaultNodes={[]}
- defaultEdges={[]}
- nodeTypes={nodeTypes}
- edgeTypes={edgeTypes}
- proOptions={{ hideAttribution: true }}
- >
- <Background color={backgroundPatternColor} />
- </ReactFlow>
- </div>
- )
- }
|