ReplicationPipelineStatus.utils.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import dayjs from 'dayjs'
  2. import { Badge } from 'ui'
  3. import { getPipelineDisplayState, normalizePipelineStatusName } from '../Pipeline.utils'
  4. import { RetryPolicy, TableState } from './ReplicationPipelineStatus.types'
  5. import { ReplicationPipelineStatusData } from '@/data/replication/pipeline-status-query'
  6. import { formatBytes } from '@/lib/helpers'
  7. import { PipelineStatusRequestStatus } from '@/state/replication-pipeline-request-status'
  8. const numberFormatter = new Intl.NumberFormat()
  9. export const getStatusConfig = (state: TableState['state']) => {
  10. switch (state.name) {
  11. case 'queued':
  12. return {
  13. badge: <Badge variant="warning">Queued</Badge>,
  14. description: 'Table is waiting for ETL to pick it up for replication.',
  15. tooltip: 'Table is waiting for ETL to pick it up for replication.',
  16. color: 'text-warning',
  17. }
  18. case 'copying_table':
  19. return {
  20. badge: <Badge variant="success">Copying</Badge>,
  21. description: "Table's existing rows are being copied before live streaming begins.",
  22. tooltip: "Table's existing rows are being copied before live streaming begins.",
  23. color: 'text-brand-600',
  24. }
  25. case 'copied_table':
  26. return {
  27. badge: <Badge variant="success">Copied</Badge>,
  28. description: "Table copy is complete and it's preparing to follow WAL changes.",
  29. tooltip: "Table copy is complete and it's preparing to follow WAL changes.",
  30. color: 'text-success-600',
  31. }
  32. case 'following_wal':
  33. return {
  34. badge: <Badge variant="success">Live</Badge>,
  35. description: 'Table is streaming new changes in real time from the WAL.',
  36. tooltip: 'Table is streaming new changes in real time from the WAL.',
  37. color: 'text-success-600',
  38. }
  39. case 'error':
  40. return {
  41. badge: <Badge variant="destructive">Error</Badge>,
  42. description: 'Replication is paused because the table encountered an error.',
  43. tooltip: 'Replication is paused because the table encountered an error.',
  44. color: 'text-destructive-600',
  45. }
  46. default:
  47. return {
  48. badge: <Badge variant="warning">Unknown</Badge>,
  49. description: 'Table status is unavailable.',
  50. tooltip: 'Table status is unavailable.',
  51. color: 'text-warning',
  52. }
  53. }
  54. }
  55. export const getDisabledStateConfig = ({
  56. requestStatus,
  57. statusName,
  58. }: {
  59. requestStatus: PipelineStatusRequestStatus
  60. statusName?: ReplicationPipelineStatusData['status']['name']
  61. }) => {
  62. const normalizedStatusName = normalizePipelineStatusName(statusName)
  63. const displayState = getPipelineDisplayState(requestStatus, normalizedStatusName)
  64. const { title, message } = displayState
  65. return { title, message }
  66. }
  67. export const isValidRetryPolicy = (policy: any): policy is RetryPolicy => {
  68. if (!policy || typeof policy !== 'object' || !policy.policy) return false
  69. switch (policy.policy) {
  70. case 'no_retry':
  71. case 'manual_retry':
  72. return true
  73. case 'timed_retry':
  74. return typeof policy.next_retry === 'string'
  75. default:
  76. return false
  77. }
  78. }
  79. const formatLagBytesValue = (value?: number) => {
  80. if (typeof value !== 'number' || Number.isNaN(value)) {
  81. return { display: '—', detail: undefined }
  82. }
  83. const decimals = value < 1024 ? 0 : value < 1024 * 1024 ? 1 : 2
  84. const display = formatBytes(value, decimals)
  85. const detail = `${numberFormatter.format(value)} bytes`
  86. return { display, detail }
  87. }
  88. const formatLagDurationValue = (value?: number) => {
  89. if (typeof value !== 'number' || Number.isNaN(value)) {
  90. return { display: '—', detail: undefined }
  91. }
  92. const sign = value < 0 ? '-' : ''
  93. const absMilliseconds = Math.abs(value)
  94. const duration = dayjs.duration(absMilliseconds, 'milliseconds')
  95. if (absMilliseconds < 1000) {
  96. return { display: `${value} ms`, detail: undefined }
  97. }
  98. const seconds = duration.asSeconds()
  99. if (seconds < 60) {
  100. const decimals = seconds >= 10 ? 1 : 2
  101. return {
  102. display: `${sign}${seconds.toFixed(decimals)} s`,
  103. detail: `${numberFormatter.format(value)} ms`,
  104. }
  105. }
  106. const minutes = duration.asMinutes()
  107. if (minutes < 60) {
  108. const roundedSeconds = Math.round(seconds)
  109. return {
  110. display: `${sign}${minutes.toFixed(minutes >= 10 ? 1 : 2)} min`,
  111. detail: `${numberFormatter.format(roundedSeconds)} s`,
  112. }
  113. }
  114. const hours = duration.asHours()
  115. const roundedMinutes = Math.round(minutes)
  116. return {
  117. display: `${sign}${hours.toFixed(hours >= 10 ? 1 : 2)} h`,
  118. detail: `${numberFormatter.format(roundedMinutes)} min`,
  119. }
  120. }
  121. export const getFormattedLagValue = (type: 'bytes' | 'duration', value?: number) =>
  122. type === 'bytes' ? formatLagBytesValue(value) : formatLagDurationValue(value)