diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 829cfbb138811..8a16b89d8aa10 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -176,6 +176,7 @@ Sean Oliver Sean Thompson Sergio Cioban Filho Shane E +Shreekar Shetty Sreyas Udayavarman Stanislav M Stephen Morgan diff --git a/apps/studio/components/interfaces/Auth/Users/UsersSearch.tsx b/apps/studio/components/interfaces/Auth/Users/UsersSearch.tsx index 927fc640d2a44..524f6caa2c7e7 100644 --- a/apps/studio/components/interfaces/Auth/Users/UsersSearch.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UsersSearch.tsx @@ -1,7 +1,29 @@ +import { AuthUsersSearchSubmittedEvent } from 'common/telemetry-constants' import { Search, X } from 'lucide-react' -import { SetStateAction } from 'react' +import { parseAsString, parseAsStringEnum, useQueryState } from 'nuqs' +import { useState } from 'react' +import { + Button, + SelectContent_Shadcn_, + SelectGroup_Shadcn_, + SelectItem_Shadcn_, + SelectSeparator_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, + Select_Shadcn_, + Tooltip, + TooltipContent, + TooltipTrigger, + cn, +} from 'ui' +import { Input } from 'ui-patterns/DataInputs/Input' -import { SpecificFilterColumn } from './Users.constants' +import { + PHONE_NUMBER_LEFT_PREFIX_REGEX, + SpecificFilterColumn, + UUIDV4_LEFT_PREFIX_REGEX, +} from './Users.constants' +import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' const getSearchPlaceholder = (column: SpecificFilterColumn): string => { switch (column) { @@ -20,41 +42,63 @@ const getSearchPlaceholder = (column: SpecificFilterColumn): string => { } } -import { - Button, - cn, - Select_Shadcn_, - SelectContent_Shadcn_, - SelectGroup_Shadcn_, - SelectItem_Shadcn_, - SelectSeparator_Shadcn_, - SelectTrigger_Shadcn_, - SelectValue_Shadcn_, - Tooltip, - TooltipContent, - TooltipTrigger, -} from 'ui' -import { Input } from 'ui-patterns/DataInputs/Input' - interface UsersSearchProps { - search: string - searchInvalid: boolean - specificFilterColumn: SpecificFilterColumn - setSearch: (value: SetStateAction) => void - setFilterKeywords: (value: string) => void - setSpecificFilterColumn: (value: SpecificFilterColumn) => void improvedSearchEnabled?: boolean + telemetryProps: Omit + telemetryGroups: AuthUsersSearchSubmittedEvent['groups'] + onSelectFilterColumn: (value: SpecificFilterColumn) => void } export const UsersSearch = ({ - search, - searchInvalid, - specificFilterColumn, - setSearch, - setFilterKeywords, - setSpecificFilterColumn, improvedSearchEnabled = false, + telemetryProps, + telemetryGroups, + onSelectFilterColumn, }: UsersSearchProps) => { + const [_, setSelectedId] = useQueryState( + 'show', + parseAsString.withOptions({ history: 'push', clearOnDefault: true }) + ) + const [filterKeywords, setFilterKeywords] = useQueryState('keywords', { defaultValue: '' }) + const [specificFilterColumn] = useQueryState( + 'filter', + parseAsStringEnum([ + 'id', + 'email', + 'phone', + 'name', + 'freeform', + ]).withDefault('email') + ) + + const [search, setSearch] = useState(filterKeywords) + const { mutate: sendEvent } = useSendEventMutation() + + const searchInvalid = + !search || + specificFilterColumn === 'freeform' || + specificFilterColumn === 'email' || + specificFilterColumn === 'name' + ? false + : specificFilterColumn === 'id' + ? !search.match(UUIDV4_LEFT_PREFIX_REGEX) + : !search.match(PHONE_NUMBER_LEFT_PREFIX_REGEX) + + const onSubmitSearch = () => { + const s = search.trim().toLocaleLowerCase() + setFilterKeywords(s) + setSelectedId(null) + sendEvent({ + action: 'auth_users_search_submitted', + properties: { + trigger: 'search_input', + ...telemetryProps, + keywords: s, + }, + groups: telemetryGroups, + }) + } + return (
@@ -63,7 +107,7 @@ export const UsersSearch = ({ setSpecificFilterColumn(v as typeof specificFilterColumn)} + onValueChange={(v) => onSelectFilterColumn(v as typeof specificFilterColumn)} > { - const value = e.target.value.replace(/\s+/g, '').toLowerCase() - setSearch(value) - }} + onChange={(e) => setSearch(e.target.value)} onKeyDown={(e) => { if (e.code === 'Enter' || e.code === 'NumpadEnter') { - if (!searchInvalid) setFilterKeywords(search.trim().toLocaleLowerCase()) + if (!searchInvalid) onSubmitSearch() } }} actions={ diff --git a/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx b/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx index 162d4a8c2e17a..5156ed1590016 100644 --- a/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx @@ -2,25 +2,10 @@ import pgMeta from '@supabase/pg-meta' import type { OptimizedSearchColumns } from '@supabase/pg-meta/src/sql/studio/get-users-types' import { keepPreviousData, useQueryClient } from '@tanstack/react-query' import AwesomeDebouncePromise from 'awesome-debounce-promise' -import { - ExternalLinkIcon, - InfoIcon, - RefreshCw, - Trash, - Users, - WandSparklesIcon, - X, -} from 'lucide-react' -import Link from 'next/link' -import { parseAsArrayOf, parseAsString, parseAsStringEnum, useQueryState } from 'nuqs' -import { UIEvent, useEffect, useMemo, useRef, useState } from 'react' -import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid' -import { toast } from 'sonner' - import { LOCAL_STORAGE_KEYS, useFlag, useParams } from 'common' import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' -import { AlertError } from 'components/ui/AlertError' import { APIDocsButton } from 'components/ui/APIDocsButton' +import { AlertError } from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { FilterPopover } from 'components/ui/FilterPopover' import { FormHeader } from 'components/ui/Forms/FormHeader' @@ -40,26 +25,41 @@ import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { cleanPointerEventsNoneOnBody, isAtBottom } from 'lib/helpers' import { - Alert_Shadcn_, + ExternalLinkIcon, + InfoIcon, + RefreshCw, + Trash, + Users, + WandSparklesIcon, + X, +} from 'lucide-react' +import Link from 'next/link' +import { parseAsArrayOf, parseAsString, parseAsStringEnum, useQueryState } from 'nuqs' +import { UIEvent, useEffect, useMemo, useRef, useState } from 'react' +import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid' +import { toast } from 'sonner' +import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, + Alert_Shadcn_, Button, - cn, LoadingLine, ResizablePanel, ResizablePanelGroup, - Select_Shadcn_, SelectContent_Shadcn_, SelectGroup_Shadcn_, SelectItem_Shadcn_, SelectTrigger_Shadcn_, SelectValue_Shadcn_, + Select_Shadcn_, Tooltip, TooltipContent, TooltipTrigger, + cn, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' + import { AddUserDropdown } from './AddUserDropdown' import { DeleteUserModal } from './DeleteUserModal' import { SortDropdown } from './SortDropdown' @@ -69,10 +69,8 @@ import { ColumnConfiguration, Filter, MAX_BULK_DELETE, - PHONE_NUMBER_LEFT_PREFIX_REGEX, PROVIDER_FILTER_OPTIONS, USERS_TABLE_COLUMNS, - UUIDV4_LEFT_PREFIX_REGEX, } from './Users.constants' import { formatUserColumns, formatUsersData } from './Users.utils' import { UsersFooter } from './UsersFooter' @@ -137,7 +135,7 @@ export const UsersV2 = () => { 'userType', parseAsStringEnum(['all', 'verified', 'unverified', 'anonymous']).withDefault('all') ) - const [filterKeywords, setFilterKeywords] = useQueryState('keywords', { defaultValue: '' }) + const [filterKeywords] = useQueryState('keywords', { defaultValue: '' }) const [sortByValue, setSortByValue] = useQueryState('sortBy', { defaultValue: 'created_at:desc' }) const [sortColumn, sortOrder] = sortByValue.split(':') const [selectedColumns, setSelectedColumns] = useQueryState( @@ -185,7 +183,6 @@ export const UsersV2 = () => { ) const [columns, setColumns] = useState[]>([]) - const [search, setSearch] = useState(filterKeywords) const [selectedUsers, setSelectedUsers] = useState>(new Set([])) const [selectedUserToDelete, setSelectedUserToDelete] = useState() const [showDeleteModal, setShowDeleteModal] = useState(false) @@ -330,16 +327,6 @@ export const UsersV2 = () => { // [Joshen] Only relevant for when selecting one user only const selectedUserFromCheckbox = users.find((u) => u.id === [...selectedUsers][0]) - const searchInvalid = - !search || - specificFilterColumn === 'freeform' || - specificFilterColumn === 'email' || - specificFilterColumn === 'name' - ? false - : specificFilterColumn === 'id' - ? !search.match(UUIDV4_LEFT_PREFIX_REGEX) - : !search.match(PHONE_NUMBER_LEFT_PREFIX_REGEX) - const telemetryProps = { sort_column: sortColumn, sort_order: sortOrder, @@ -566,24 +553,10 @@ export const UsersV2 = () => { <>
{ - setFilterKeywords(s) - setSelectedId(null) - sendEvent({ - action: 'auth_users_search_submitted', - properties: { - trigger: 'search_input', - ...telemetryProps, - keywords: s, - }, - groups: telemetryGroups, - }) - }} - setSpecificFilterColumn={(value) => { + improvedSearchEnabled={improvedSearchEnabled} + telemetryProps={telemetryProps} + telemetryGroups={telemetryGroups} + onSelectFilterColumn={(value) => { if (value === 'freeform') { if (isCountWithinThresholdForSortBy) { updateStorageFilter(value) @@ -594,7 +567,6 @@ export const UsersV2 = () => { updateStorageFilter(value) } }} - improvedSearchEnabled={improvedSearchEnabled} /> {showUserTypeFilter && diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx index f841a23492ece..5449a1982dfcf 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx @@ -1,12 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { AnimatePresence, motion } from 'framer-motion' -import { snakeCase } from 'lodash' -import { useEffect, useMemo, useRef, useState } from 'react' -import { useForm } from 'react-hook-form' -import { toast } from 'sonner' -import * as z from 'zod' - import { useFlag, useParams } from 'common' import { CreateAnalyticsBucketSheet } from 'components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketSheet' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' @@ -26,19 +19,26 @@ import { useReplicationSourcesQuery } from 'data/replication/sources-query' import { useStartPipelineMutation } from 'data/replication/start-pipeline-mutation' import { useUpdateDestinationPipelineMutation } from 'data/replication/update-destination-pipeline-mutation' import { - useValidateDestinationMutation, type ValidationFailure, + useValidateDestinationMutation, } from 'data/replication/validate-destination-mutation' import { useValidatePipelineMutation } from 'data/replication/validate-pipeline-mutation' import { useIcebergNamespaceCreateMutation } from 'data/storage/iceberg-namespace-create-mutation' import { useS3AccessKeyCreateMutation } from 'data/storage/s3-access-key-create-mutation' +import { AnimatePresence, motion } from 'framer-motion' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { snakeCase } from 'lodash' import { Loader2 } from 'lucide-react' +import { useEffect, useMemo, useRef, useState } from 'react' +import { useForm } from 'react-hook-form' +import { toast } from 'sonner' import { PipelineStatusRequestStatus, usePipelineRequestStatus, } from 'state/replication-pipeline-request-status' import { Button, DialogSectionSeparator, Form_Shadcn_, SheetFooter, SheetSection } from 'ui' +import * as z from 'zod' + import { DestinationType } from '../DestinationPanel.types' import { AdvancedSettings } from './AdvancedSettings' import { CREATE_NEW_KEY, CREATE_NEW_NAMESPACE } from './DestinationForm.constants' diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx index 8360c7f3457c4..f5fd1d81197b9 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx @@ -1,10 +1,10 @@ -import { useState } from 'react' - -import { useReplicationSourcesQuery } from '@/data/replication/sources-query' -import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' import { useFlag, useParams } from 'common' +import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' +import { ArrowUpRight } from 'lucide-react' +import Link from 'next/link' +import { useState } from 'react' import { - cn, + Button, DialogSectionSeparator, Sheet, SheetContent, @@ -12,12 +12,18 @@ import { SheetHeader, SheetSection, SheetTitle, + cn, } from 'ui' + import { EnableReplicationCallout } from '../EnableReplicationCallout' +import { useIsETLPrivateAlpha } from '../useIsETLPrivateAlpha' import { DestinationForm } from './DestinationForm' import { DestinationType } from './DestinationPanel.types' import { DestinationTypeSelection } from './DestinationTypeSelection' import { ReadReplicaForm } from './ReadReplicaForm' +import { DocsButton } from '@/components/ui/DocsButton' +import { useReplicationSourcesQuery } from '@/data/replication/sources-query' +import { DOCS_URL } from '@/lib/constants' interface DestinationPanelProps { visible: boolean @@ -41,6 +47,7 @@ export const DestinationPanel = ({ onSuccessCreateReadReplica, }: DestinationPanelProps) => { const { ref: projectRef } = useParams() + const enablePgReplicate = useIsETLPrivateAlpha() const unifiedReplication = useFlag('unifiedReplication') const { hasAccess: hasETLReplicationAccess } = useCheckEntitlements('replication.etl') @@ -84,6 +91,35 @@ export const DestinationPanel = ({ {selectedType === 'Read Replica' ? ( onSuccessCreateReadReplica?.()} /> + ) : unifiedReplication && !enablePgReplicate ? ( + +
+
+

Replicate data to external destinations in real-time

+

+ We are currently in private alpha and + slowly onboarding new customers to ensure stable data pipelines. Request + access below to join the waitlist. Read replicas are available now. +

+
+
+ + +
+
+
) : unifiedReplication && replicationNotEnabled ? ( { + const enablePgReplicate = useIsETLPrivateAlpha() const unifiedReplication = useFlag('unifiedReplication') const etlEnableBigQuery = useFlag('etlEnableBigQuery') const etlEnableIceberg = useFlag('etlEnableIceberg') @@ -101,6 +105,15 @@ export const DestinationTypeSelection = ({ )} + + {selectedType !== 'Read Replica' && enablePgReplicate && ( +

+ Replication is in alpha. Expect rapid changes and possible breaking updates.{' '} + + Leave feedback + +

+ )}
) } diff --git a/apps/studio/components/interfaces/Database/Replication/EnableReplicationCallout.tsx b/apps/studio/components/interfaces/Database/Replication/EnableReplicationCallout.tsx index fdcdb6208d270..3517938ee0ead 100644 --- a/apps/studio/components/interfaces/Database/Replication/EnableReplicationCallout.tsx +++ b/apps/studio/components/interfaces/Database/Replication/EnableReplicationCallout.tsx @@ -1,14 +1,9 @@ -import { useState } from 'react' -import { toast } from 'sonner' - -import { DocsButton } from '@/components/ui/DocsButton' -import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton' -import { DOCS_URL } from '@/lib/constants' import { useParams } from 'common' import { useCreateTenantSourceMutation } from 'data/replication/create-tenant-source-mutation' +import { useState } from 'react' +import { toast } from 'sonner' import { Button, - cn, Dialog, DialogContent, DialogFooter, @@ -17,9 +12,14 @@ import { DialogSectionSeparator, DialogTitle, DialogTrigger, + cn, } from 'ui' import { Admonition } from 'ui-patterns' +import { DocsButton } from '@/components/ui/DocsButton' +import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton' +import { DOCS_URL } from '@/lib/constants' + const EnableReplicationModal = () => { const { ref: projectRef } = useParams() const [open, setOpen] = useState(false) @@ -93,7 +93,7 @@ export const EnableReplicationCallout = ({ return (
-

Replicate data to external destinations in real-time

+

Replicate data to external destinations in real-time

{hasAccess ? 'Enable replication' : 'Upgrade to the Pro plan'} to start replicating your database changes to {type ?? 'data warehouses and analytics platforms'} diff --git a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx index 4ce4e316976b8..5178b6daa0278 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx @@ -28,6 +28,8 @@ import { import { Input } from 'ui-patterns/DataInputs/Input' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +const attribute = 'physical_replication_lag_physical_replication_lag_seconds' + export const ReadReplicaDetails = () => { const { ref: projectRef, replicaId } = useParams() const reportGranularityV2 = useFlag('reportGranularityV2') @@ -64,7 +66,7 @@ export const ReadReplicaDetails = () => { useInfraMonitoringAttributesQuery( { projectRef, - attributes: ['physical_replication_lag_physical_replica_lag_seconds'], + attributes: [attribute], databaseIdentifier: identifier, startDate: selectedDateRange.period_start.date, endDate: selectedDateRange.period_end.date, @@ -73,26 +75,24 @@ export const ReadReplicaDetails = () => { { enabled: !!replica } ) - // [Joshen] Temporarily hardcoding the data as the query to retrieve replication lag doesn't seem to be working - // https://linear.app/supabase/issue/INDATA-325/investigate-infra-monitoring-for-physical-replication-lag-physical - const chartData = useMemo(() => { - return Array.from({ length: 46 }, (_, i) => { - const date = new Date() - date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart - - return { - timestamp: date.toISOString(), - replication_lag: Math.floor(Math.random() * 100), - } - }).reverse() - }, []) + const chartData = useMemo( + () => + infraMonitoringData?.data.map((x) => ({ + timestamp: x.period_start, + [attribute]: (x as Record)[attribute], + })) ?? [], + [infraMonitoringData?.data] + ) return ( <> - + { description="It may take up to 24 hours for data to refresh" /> } - loadingState={} + loadingState={} >

`${value}s`, width: 80, }} - isFullHeight={true} + config={{ + [attribute]: { label: 'Replication lag' }, + }} />
diff --git a/apps/studio/components/ui/Charts/BarChart.tsx b/apps/studio/components/ui/Charts/BarChart.tsx index 23a1dc79222d9..99fbde8642607 100644 --- a/apps/studio/components/ui/Charts/BarChart.tsx +++ b/apps/studio/components/ui/Charts/BarChart.tsx @@ -191,7 +191,7 @@ const BarChart = ({ animationDuration={300} maxBarSize={48} > - {data?.map((_entry: Datum, index: any) => ( + {data?.map((_entry: Datum, index: number) => ( { - const { hoveredIndex, isHovered, isCurrentChart, setHover, clearHover } = useChartHoverState( - syncId || 'default' - ) + const { ref } = useParams() + const { hoveredIndex, isHovered } = useChartHoverState(syncId || 'default') const [localHighlightedValue, setLocalHighlightedValue] = useState(highlightedValue) const [localHighlightedLabel, setLocalHighlightedLabel] = useState(highlightedLabel) - const { ref } = useParams() - const router = useRouter() const formatHighlightedValue = (value: any) => { if (typeof value !== 'number') { @@ -109,7 +105,7 @@ export const ChartHeader = ({ const formattedValue = numberFormatter(value, valuePrecision) if (typeof format === 'string' && format) { - return `${formattedValue}${format}` + return `${formattedValue} ${format}` } return formattedValue diff --git a/apps/studio/data/analytics/infra-monitoring-query.ts b/apps/studio/data/analytics/infra-monitoring-query.ts index 9a30306425be9..65f7faad28235 100644 --- a/apps/studio/data/analytics/infra-monitoring-query.ts +++ b/apps/studio/data/analytics/infra-monitoring-query.ts @@ -1,29 +1,14 @@ import { useQuery } from '@tanstack/react-query' +import { paths } from 'api-types' import { get, handleError } from 'data/fetchers' import { UseCustomQueryOptions } from 'types' import type { AnalyticsInterval } from './constants' import { analyticsKeys } from './keys' -export type InfraMonitoringAttribute = - | 'max_cpu_usage' - | 'avg_cpu_usage' - | 'disk_io_budget' - | 'ram_usage' - | 'disk_io_consumption' - | 'pg_stat_database_num_backends' - | 'supavisor_connections_active' - | 'realtime_connections_connected' - | 'realtime_channel_events' - | 'realtime_channel_db_events' - | 'realtime_channel_presence_events' - | 'realtime_channel_joins' - | 'realtime_read_authorization_rls_execution_time' - | 'realtime_write_authorization_rls_execution_time' - | 'realtime_payload_size' - | 'realtime_sum_connections_connected' - | 'realtime_replication_connection_lag' - | 'physical_replication_lag_physical_replica_lag_seconds' +export type InfraMonitoringAttribute = NonNullable< + paths['/platform/projects/{ref}/infra-monitoring']['get']['parameters']['query']['attributes'] +>[number] export type InfraMonitoringSeriesMetadata = { yAxisLimit: number diff --git a/apps/studio/pages/project/[ref]/database/replication/index.tsx b/apps/studio/pages/project/[ref]/database/replication/index.tsx index 0181a144722a2..c706953df853f 100644 --- a/apps/studio/pages/project/[ref]/database/replication/index.tsx +++ b/apps/studio/pages/project/[ref]/database/replication/index.tsx @@ -1,22 +1,23 @@ -import { ReplicationDiagram } from '@/components/interfaces/Database/Replication/ReplicationDiagram' -import { useParams } from 'common' +import { useFlag, useParams } from 'common' import { ReplicationComingSoon } from 'components/interfaces/Database/Replication/ComingSoon' import { Destinations } from 'components/interfaces/Database/Replication/Destinations' import { useIsETLPrivateAlpha } from 'components/interfaces/Database/Replication/useIsETLPrivateAlpha' import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout' import { DefaultLayout } from 'components/layouts/DefaultLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' -import { AlphaNotice } from 'components/ui/AlphaNotice' import { FormHeader } from 'components/ui/Forms/FormHeader' import { UnknownInterface } from 'components/ui/UnknownInterface' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { PipelineRequestStatusProvider } from 'state/replication-pipeline-request-status' import type { NextPageWithLayout } from 'types' +import { ReplicationDiagram } from '@/components/interfaces/Database/Replication/ReplicationDiagram' + const DatabaseReplicationPage: NextPageWithLayout = () => { const { ref } = useParams() const enablePgReplicate = useIsETLPrivateAlpha() const showPgReplicate = useIsFeatureEnabled('database:replication') + const unifiedReplication = useFlag('unifiedReplication') if (!showPgReplicate) { return @@ -24,7 +25,7 @@ const DatabaseReplicationPage: NextPageWithLayout = () => { return ( <> - {enablePgReplicate ? ( + {unifiedReplication || enablePgReplicate ? ( @@ -37,10 +38,6 @@ const DatabaseReplicationPage: NextPageWithLayout = () => { analytics platforms in real-time

- diff --git a/apps/studio/pages/project/[ref]/observability/database.tsx b/apps/studio/pages/project/[ref]/observability/database.tsx index 48760a5591064..cdbeac91ac105 100644 --- a/apps/studio/pages/project/[ref]/observability/database.tsx +++ b/apps/studio/pages/project/[ref]/observability/database.tsx @@ -168,7 +168,7 @@ const DatabaseUsage = () => { if (isReplicaSelected) { queryClient.invalidateQueries({ queryKey: analyticsKeys.infraMonitoring(ref, { - attribute: 'physical_replication_lag_physical_replica_lag_seconds', + attribute: 'physical_replication_lag_physical_replication_lag_seconds', startDate: period_start.date, endDate: period_end.date, interval, @@ -286,7 +286,7 @@ const DatabaseUsage = () => { ( ChartEmptyState.displayName = 'ChartEmptyState' /* Chart Loading State */ -const ChartLoadingState = () => { +const ChartLoadingState = ({ className }: { className?: string }) => { return ( -
+
) @@ -592,21 +597,21 @@ const ChartSparkline = React.forwardRef( ChartSparkline.displayName = 'ChartSparkline' /* Exports */ +export { ChartBar, type ChartBarProps, type ChartBarTick } from './charts/chart-bar' +export { ChartLine, type ChartLineProps, type ChartLineTick } from './charts/chart-line' export { Chart, - ChartCard, - ChartHeader, - ChartTitle, ChartActions, - ChartMetric, + ChartCard, ChartContent, - ChartEmptyState, - ChartLoadingState, ChartDisabledState, + ChartEmptyState, ChartFooter, - ChartValueDifferential, - ChartSparklineTooltip, + ChartHeader, + ChartLoadingState, + ChartMetric, ChartSparkline, + ChartSparklineTooltip, + ChartTitle, + ChartValueDifferential, } -export { ChartBar, type ChartBarTick, type ChartBarProps } from './charts/chart-bar' -export { ChartLine, type ChartLineTick, type ChartLineProps } from './charts/chart-line'