Files
Danny White d451bdc264 fix(studio): place pipeline arrows on horizontal edges (#50965)
## Problem

In branched Pipelines diagrams, status chips sit on the shared vertical
bend, making flow direction unclear.

- Resolves
[DEPR-689](https://linear.app/supabase/issue/DEPR-689/move-pipeline-arrows-to-horizontal-edge-segments)
- Resolves duplicate
[PIPE-1079](https://linear.app/supabase/issue/PIPE-1079/move-destination-chart-arrows-to-horizontal-edges)

## Solution

Place each chip on its destination's final horizontal edge segment. Keep
the single-destination chip centred on its straight edge.

| 1× Destination (No Change) |
| --- |
| <img width="1352" height="678" alt="CleanShot 2026-09-29 at 14 03
22@2x"
src="https://github.com/user-attachments/assets/c495318c-811b-4817-9b25-af32d12d8e08"
/> |
| _Before_ |
| <img width="1350" height="682" alt="CleanShot 2026-09-29 at 14 02
37@2x"
src="https://github.com/user-attachments/assets/da862b82-4605-48df-93cd-c035443acc9d"
/> |
| _After_ |

| 2× Destinations |
| --- |
| <img width="1354" height="680" alt="CleanShot 2026-09-29 at 14 01
15@2x"
src="https://github.com/user-attachments/assets/970aa562-1ab9-4ff1-90e0-3c0fe95d01dc"
/> |
| _Before_ |
| <img width="1354" height="674" alt="CleanShot 2026-09-29 at 14 00
50@2x"
src="https://github.com/user-attachments/assets/5501657b-32a2-485d-907f-8a0a8fae595d"
/> |
| _After_ |

## Review instructions

1. Open Database > Pipelines with one destination, then with several.
Check the chip position and arrow direction at desktop and phone widths.

## Checklist

- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved label placement in the database replication diagram when
edges are shifted, making the connections easier to read.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-30 09:12:45 +10:00

109 lines
3.3 KiB
TypeScript

import { BaseEdge, EdgeLabelRenderer, getSmoothStepPath, type EdgeProps } from '@xyflow/react'
import { useParams, useReducedMotion } from 'common'
import { useMemo } from 'react'
import { getStatusName } from '../Pipeline.utils'
import {
EdgeVisualChip,
getEdgeVisual,
type ReplicationState,
} from '@/components/ui/ReactFlow/EdgeVisual'
import { useReplicationPipelineStatusQuery } from '@/data/replication/pipeline-status-query'
import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query'
import {
PipelineStatusRequestStatus,
usePipelineRequestStatus,
} from '@/state/replication-pipeline-request-status'
type EdgeData = {
type: string
identifier: string
shiftEdgeEnd: boolean
}
export const SmoothstepEdge = ({
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
style = {},
markerEnd,
data,
}: EdgeProps) => {
const { ref: projectRef = 'default' } = useParams()
const prefersReducedMotion = useReducedMotion()
const { identifier, shiftEdgeEnd } = (data || {}) as EdgeData
const { data: pipelinesData } = useReplicationPipelinesQuery({ projectRef })
const pipeline = (pipelinesData?.pipelines ?? []).find(
(p) => p.destination_id.toString() === identifier
)
const { data: pipelineStatusData } = useReplicationPipelineStatusQuery(
{ projectRef, pipelineId: pipeline?.id },
{ enabled: !!pipeline?.id }
)
const { getRequestStatus } = usePipelineRequestStatus()
const requestStatus = pipeline?.id
? getRequestStatus(pipeline.id)
: PipelineStatusRequestStatus.None
const replicationState = useMemo<ReplicationState>(() => {
const isTransitioning = requestStatus !== PipelineStatusRequestStatus.None
const statusName = getStatusName(pipelineStatusData?.status)
return {
isReplicating: statusName === 'started' && !isTransitioning,
isComingUp: isTransitioning || statusName === 'starting' || statusName === 'stopping',
isFailed: statusName === 'failed',
}
}, [pipelineStatusData?.status, requestStatus])
const edgeTargetX = shiftEdgeEnd ? targetX - 8 : targetX
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX,
sourceY,
sourcePosition,
targetX: edgeTargetX,
targetY,
targetPosition,
})
// Branched paths label their shared vertical bend, so move the chip onto each final segment.
const chipX = shiftEdgeEnd ? (labelX + edgeTargetX) / 2 : labelX
const chipY = shiftEdgeEnd ? targetY : labelY
const visual = getEdgeVisual(replicationState)
return (
<>
<BaseEdge
path={edgePath}
markerEnd={markerEnd}
style={{
...style,
stroke: visual.color,
strokeWidth: visual.strokeWidth,
opacity: visual.opacity,
strokeDasharray: visual.dashArray,
animation:
visual.shouldAnimate && !prefersReducedMotion
? 'dashdraw 0.5s linear infinite'
: undefined,
}}
/>
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${chipX}px,${chipY}px)`,
pointerEvents: 'all',
}}
className="nodrag nopan"
>
<EdgeVisualChip visual={visual} />
</div>
</EdgeLabelRenderer>
</>
)
}