From 986f3464a6b9604e72a35104cc3eb39ff8dd3178 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Tue, 11 Nov 2025 10:32:50 +1000 Subject: [PATCH 1/4] Advisor refine (#40293) * advisor panel title * refactor * use badge for critical * Update apps/studio/components/ui/AdvisorPanel/AdvisorPanel.utils.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ui/AdvisorPanel/AdvisorDetail.tsx | 2 +- .../ui/AdvisorPanel/AdvisorPanel.tsx | 3 +- .../ui/AdvisorPanel/AdvisorPanel.types.ts | 13 ++++ .../ui/AdvisorPanel/AdvisorPanel.utils.ts | 65 ++++++++++++++++ .../ui/AdvisorPanel/AdvisorPanelBody.tsx | 76 +++++++++++-------- .../ui/AdvisorPanel/AdvisorPanelHeader.tsx | 44 +++-------- 6 files changed, 136 insertions(+), 67 deletions(-) create mode 100644 apps/studio/components/ui/AdvisorPanel/AdvisorPanel.types.ts create mode 100644 apps/studio/components/ui/AdvisorPanel/AdvisorPanel.utils.ts diff --git a/apps/studio/components/ui/AdvisorPanel/AdvisorDetail.tsx b/apps/studio/components/ui/AdvisorPanel/AdvisorDetail.tsx index cc124a5c69c14..b6c2356631d72 100644 --- a/apps/studio/components/ui/AdvisorPanel/AdvisorDetail.tsx +++ b/apps/studio/components/ui/AdvisorPanel/AdvisorDetail.tsx @@ -2,7 +2,7 @@ import LintDetail from 'components/interfaces/Linter/LintDetail' import { Lint } from 'data/lint/lint-query' import { Notification } from 'data/notifications/notifications-v2-query' import { noop } from 'lodash' -import { AdvisorItem } from './AdvisorPanelHeader' +import type { AdvisorItem } from './AdvisorPanel.types' import { NotificationDetail } from './NotificationDetail' interface AdvisorDetailProps { diff --git a/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.tsx b/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.tsx index 23d798e7deaa1..cd57b2400be52 100644 --- a/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.tsx +++ b/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.tsx @@ -16,8 +16,9 @@ import { AdvisorSeverity, AdvisorTab, useAdvisorStateSnapshot } from 'state/advi import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state' import { AdvisorDetail } from './AdvisorDetail' import { AdvisorFilters } from './AdvisorFilters' +import type { AdvisorItem } from './AdvisorPanel.types' import { AdvisorPanelBody } from './AdvisorPanelBody' -import { AdvisorItem, AdvisorPanelHeader } from './AdvisorPanelHeader' +import { AdvisorPanelHeader } from './AdvisorPanelHeader' const severityOrder: Record = { critical: 0, diff --git a/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.types.ts b/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.types.ts new file mode 100644 index 0000000000000..3c24a57fe2957 --- /dev/null +++ b/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.types.ts @@ -0,0 +1,13 @@ +import { Lint } from 'data/lint/lint-query' +import { Notification } from 'data/notifications/notifications-v2-query' +import { AdvisorItemSource, AdvisorSeverity } from 'state/advisor-state' + +export type AdvisorItem = { + id: string + title: string + severity: AdvisorSeverity + createdAt?: number + tab: 'security' | 'performance' | 'messages' + source: AdvisorItemSource + original: Lint | Notification +} diff --git a/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.utils.ts b/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.utils.ts new file mode 100644 index 0000000000000..19a9bbe53f570 --- /dev/null +++ b/apps/studio/components/ui/AdvisorPanel/AdvisorPanel.utils.ts @@ -0,0 +1,65 @@ +import dayjs from 'dayjs' +import { Gauge, Inbox, Shield } from 'lucide-react' + +import { lintInfoMap } from 'components/interfaces/Linter/Linter.utils' +import { Lint } from 'data/lint/lint-query' +import { AdvisorSeverity, AdvisorTab } from 'state/advisor-state' +import type { AdvisorItem } from './AdvisorPanel.types' + +export const formatItemDate = (timestamp: number): string => { + const daysFromNow = dayjs().diff(dayjs(timestamp), 'day') + const formattedTimeFromNow = dayjs(timestamp).fromNow() + const formattedInsertedAt = dayjs(timestamp).format('MMM DD, YYYY') + return daysFromNow > 1 ? formattedInsertedAt : formattedTimeFromNow +} + +export const getAdvisorItemDisplayTitle = (item: AdvisorItem): string => { + if (item.source === 'lint') { + const lint = item.original as Lint + return ( + lintInfoMap.find((info) => info.name === lint.name)?.title || item.title.replace(/[`\\]/g, '') + ) + } + return item.title.replace(/[`\\]/g, '') +} + +export const tabIconMap: Record, React.ElementType> = { + security: Shield, + performance: Gauge, + messages: Inbox, +} + +export const severityColorClasses: Record = { + critical: 'text-destructive', + warning: 'text-warning', + info: 'text-foreground-light', +} + +export const severityBadgeVariants: Record = + { + critical: 'destructive', + warning: 'warning', + info: 'default', + } + +export const severityLabels: Record = { + critical: 'Critical', + warning: 'Warning', + info: 'Info', +} + +export const getLintEntityString = (lint: Lint | null): string | undefined => { + if (!lint?.metadata) { + return undefined + } + + if (lint.metadata.entity) { + return lint.metadata.entity + } + + if (lint.metadata.schema && lint.metadata.name) { + return `${lint.metadata.schema}.${lint.metadata.name}` + } + + return undefined +} diff --git a/apps/studio/components/ui/AdvisorPanel/AdvisorPanelBody.tsx b/apps/studio/components/ui/AdvisorPanel/AdvisorPanelBody.tsx index 7b00fce3e6aa3..a12326e244782 100644 --- a/apps/studio/components/ui/AdvisorPanel/AdvisorPanelBody.tsx +++ b/apps/studio/components/ui/AdvisorPanel/AdvisorPanelBody.tsx @@ -1,11 +1,20 @@ -import dayjs from 'dayjs' -import { AlertTriangle, ChevronRight, Gauge, Inbox, Shield } from 'lucide-react' +import { AlertTriangle, ChevronRight, Inbox } from 'lucide-react' +import { Lint } from 'data/lint/lint-query' import { Notification } from 'data/notifications/notifications-v2-query' import { AdvisorSeverity, AdvisorTab } from 'state/advisor-state' -import { Button, cn } from 'ui' +import { Badge, Button, cn } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' -import { AdvisorItem } from './AdvisorPanelHeader' +import type { AdvisorItem } from './AdvisorPanel.types' +import { + formatItemDate, + getAdvisorItemDisplayTitle, + getLintEntityString, + severityBadgeVariants, + severityColorClasses, + severityLabels, + tabIconMap, +} from './AdvisorPanel.utils' import { EmptyAdvisor } from './EmptyAdvisor' const NoProjectNotice = () => { @@ -22,18 +31,6 @@ const NoProjectNotice = () => { ) } -const tabIconMap: Record, React.ElementType> = { - security: Shield, - performance: Gauge, - messages: Inbox, -} - -const severityColorClasses: Record = { - critical: 'text-destructive', - warning: 'text-warning', - info: 'text-foreground-light', -} - interface AdvisorPanelBodyProps { isLoading: boolean isError: boolean @@ -96,11 +93,19 @@ export const AdvisorPanelBody = ({ <>
{filteredItems.map((item) => { - const SeverityIcon = tabIconMap[item.tab] + const SeverityIcon = tabIconMap[item.tab as Exclude] const severityClass = severityColorClasses[item.severity] const isNotification = item.source === 'notification' const notification = isNotification ? (item.original as Notification) : null const isUnread = notification?.status === 'new' + const lint = !isNotification ? (item.original as Lint) : null + + // Primary text: issue type for lint items, title for notifications + const primaryText = getAdvisorItemDisplayTitle(item) + + // Secondary text: entity for lint items when no date, date for notifications + const hasDate = !!item.createdAt + const entityString = getLintEntityString(lint) return (
@@ -113,32 +118,39 @@ export const AdvisorPanelBody = ({ onClick={() => onItemClick(item)} >
-
+
-
{item.title.replace(/[`\\]/g, '')}
- {item.createdAt && ( +
{primaryText}
+ {hasDate ? ( - {(() => { - const insertedAt = item.createdAt - const daysFromNow = dayjs().diff(dayjs(insertedAt), 'day') - const formattedTimeFromNow = dayjs(insertedAt).fromNow() - const formattedInsertedAt = dayjs(insertedAt).format('MMM DD, YYYY') - return daysFromNow > 1 ? formattedInsertedAt : formattedTimeFromNow - })()} + {formatItemDate(item.createdAt!)} + ) : ( + entityString && ( +
+ {entityString} +
+ ) )}
- +
+ {item.severity === 'critical' && ( + + {severityLabels[item.severity]} + + )} + +
diff --git a/apps/studio/components/ui/AdvisorPanel/AdvisorPanelHeader.tsx b/apps/studio/components/ui/AdvisorPanel/AdvisorPanelHeader.tsx index ae060dddb9355..cb15bcef32b2e 100644 --- a/apps/studio/components/ui/AdvisorPanel/AdvisorPanelHeader.tsx +++ b/apps/studio/components/ui/AdvisorPanel/AdvisorPanelHeader.tsx @@ -1,32 +1,14 @@ -import dayjs from 'dayjs' import { ChevronLeft, X } from 'lucide-react' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { AdvisorItemSource, AdvisorSeverity } from 'state/advisor-state' import { Badge } from 'ui' - -export type AdvisorItem = { - id: string - title: string - severity: AdvisorSeverity - createdAt?: number - tab: 'security' | 'performance' | 'messages' - source: AdvisorItemSource - original: any -} - -export const severityBadgeVariants: Record = - { - critical: 'destructive', - warning: 'warning', - info: 'default', - } - -export const severityLabels: Record = { - critical: 'Critical', - warning: 'Warning', - info: 'Info', -} +import type { AdvisorItem } from './AdvisorPanel.types' +import { + formatItemDate, + getAdvisorItemDisplayTitle, + severityBadgeVariants, + severityLabels, +} from './AdvisorPanel.utils' interface AdvisorPanelHeaderProps { selectedItem: AdvisorItem | undefined @@ -35,6 +17,8 @@ interface AdvisorPanelHeaderProps { } export const AdvisorPanelHeader = ({ selectedItem, onBack, onClose }: AdvisorPanelHeaderProps) => { + const displayTitle = selectedItem ? getAdvisorItemDisplayTitle(selectedItem) : undefined + return (
- {selectedItem?.title?.replace(/[`\\]/g, '')} + {displayTitle} {selectedItem?.createdAt && ( - {(() => { - const insertedAt = selectedItem.createdAt - const daysFromNow = dayjs().diff(dayjs(insertedAt), 'day') - const formattedTimeFromNow = dayjs(insertedAt).fromNow() - const formattedInsertedAt = dayjs(insertedAt).format('MMM DD, YYYY') - return daysFromNow > 1 ? formattedInsertedAt : formattedTimeFromNow - })()} + {formatItemDate(selectedItem.createdAt)} )}
From dee40fe1bfb5c57c705575cf63ed0099dfc22269 Mon Sep 17 00:00:00 2001 From: Raminder Singh Date: Tue, 11 Nov 2025 07:01:32 +0530 Subject: [PATCH 2/4] feat: add stopping pipeline status in ui (#40294) --- .../interfaces/Database/ETL/Pipeline.utils.ts | 16 +++++++++++++--- .../interfaces/Database/ETL/PipelineStatus.tsx | 7 +++++++ .../Database/ETL/Replication.constants.ts | 1 + apps/studio/data/etl/pipeline-status-query.ts | 3 +++ .../replication-pipeline-request-status.tsx | 9 ++++++--- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/studio/components/interfaces/Database/ETL/Pipeline.utils.ts b/apps/studio/components/interfaces/Database/ETL/Pipeline.utils.ts index 551243632f172..a62e685706016 100644 --- a/apps/studio/components/interfaces/Database/ETL/Pipeline.utils.ts +++ b/apps/studio/components/interfaces/Database/ETL/Pipeline.utils.ts @@ -1,4 +1,7 @@ -import { ReplicationPipelineStatusData } from 'data/etl/pipeline-status-query' +import { + ReplicationPipelineStatus, + ReplicationPipelineStatusData, +} from 'data/etl/pipeline-status-query' import { PipelineStatusRequestStatus } from 'state/replication-pipeline-request-status' import { PipelineStatusName } from './Replication.constants' @@ -58,6 +61,11 @@ const PIPELINE_STATE_MESSAGES = { message: 'Initializing replication. Table status will be available once running.', badge: 'Starting', }, + stopping: { + title: 'Pipeline stopping', + message: 'Stopping replication. Table replication will be paused once stopped', + badge: 'Stopping', + }, running: { title: 'Pipeline running', message: 'Replication is active and processing data', @@ -76,8 +84,8 @@ const PIPELINE_STATE_MESSAGES = { } as const export const getPipelineStateMessages = ( - requestStatus: PipelineStatusRequestStatus | undefined, - statusName: string | undefined + requestStatus?: PipelineStatusRequestStatus, + statusName?: ReplicationPipelineStatus ) => { // Reflect optimistic request intent immediately after click if (requestStatus === PipelineStatusRequestStatus.RestartRequested) { @@ -100,6 +108,8 @@ export const getPipelineStateMessages = ( return PIPELINE_STATE_MESSAGES.stopped case 'started': return PIPELINE_STATE_MESSAGES.running + case 'stopping': + return PIPELINE_STATE_MESSAGES.stopping case 'unknown': return PIPELINE_STATE_MESSAGES.unknown default: diff --git a/apps/studio/components/interfaces/Database/ETL/PipelineStatus.tsx b/apps/studio/components/interfaces/Database/ETL/PipelineStatus.tsx index 420f4142e9d96..fd13ea2a6a5c2 100644 --- a/apps/studio/components/interfaces/Database/ETL/PipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/ETL/PipelineStatus.tsx @@ -97,6 +97,13 @@ export const PipelineStatus = ({ color: 'text-foreground-light', tooltip: stateMessages.message, } + case PipelineStatusName.STOPPING: + return { + label: 'Stopping', + dot: , + color: 'text-warning', + tooltip: stateMessages.message, + } case PipelineStatusName.UNKNOWN: return { label: 'Unknown', diff --git a/apps/studio/components/interfaces/Database/ETL/Replication.constants.ts b/apps/studio/components/interfaces/Database/ETL/Replication.constants.ts index 687e7307483d6..16e0f73436c44 100644 --- a/apps/studio/components/interfaces/Database/ETL/Replication.constants.ts +++ b/apps/studio/components/interfaces/Database/ETL/Replication.constants.ts @@ -5,5 +5,6 @@ export enum PipelineStatusName { STARTING = 'starting', STARTED = 'started', STOPPED = 'stopped', + STOPPING = 'stopping', UNKNOWN = 'unknown', } diff --git a/apps/studio/data/etl/pipeline-status-query.ts b/apps/studio/data/etl/pipeline-status-query.ts index 1e6b2d9787d17..6367239c4f4f0 100644 --- a/apps/studio/data/etl/pipeline-status-query.ts +++ b/apps/studio/data/etl/pipeline-status-query.ts @@ -1,10 +1,13 @@ import { useQuery } from '@tanstack/react-query' +import { components } from 'api-types' import { get, handleError } from 'data/fetchers' import type { ResponseError, UseCustomQueryOptions } from 'types' import { replicationKeys } from './keys' type ReplicationPipelinesStatusParams = { projectRef?: string; pipelineId?: number } +type ReplicationPipelineStatusResponse = components['schemas']['ReplicationPipelineStatusResponse'] +export type ReplicationPipelineStatus = ReplicationPipelineStatusResponse['status']['name'] async function fetchReplicationPipelineStatus( { projectRef, pipelineId }: ReplicationPipelinesStatusParams, diff --git a/apps/studio/state/replication-pipeline-request-status.tsx b/apps/studio/state/replication-pipeline-request-status.tsx index 36561ab544440..b14c7864ebd1a 100644 --- a/apps/studio/state/replication-pipeline-request-status.tsx +++ b/apps/studio/state/replication-pipeline-request-status.tsx @@ -1,11 +1,11 @@ import { createContext, - useContext, - useState, ReactNode, useCallback, - useRef, + useContext, useEffect, + useRef, + useState, } from 'react' export enum PipelineStatusRequestStatus { @@ -35,6 +35,9 @@ const PipelineRequestStatusContext = createContext { const [requestStatus, setRequestStatusState] = useState< Record From 40f7abc2cd29db7700fc25fccd8e49d4b595adbd Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:13:06 +1100 Subject: [PATCH 3/4] docs(design-system): design system improvements (#40196) * font smoothing * remove sidebar * fix typo * data grid empty state * basic data grid * remove figma * empty state docs --- apps/design-system/README.md | 2 +- apps/design-system/__registry__/index.tsx | 30 ++++++- apps/design-system/config/docs.ts | 10 --- apps/design-system/content/docs/changelog.mdx | 84 ------------------- apps/design-system/content/docs/figma.mdx | 28 ------- .../content/docs/ui-patterns/empty-states.mdx | 47 ++++++++--- apps/design-system/package.json | 1 + ...mpty-state-initial-state-informational.tsx | 31 +++++++ ...ty-state-initial-state-presentational.tsx} | 6 +- .../example/empty-state-missing-route.tsx | 11 ++- .../empty-state-zero-items-data-grid.tsx | 47 +++++++++++ .../example/empty-state-zero-items-table.tsx | 8 +- apps/design-system/registry/examples.ts | 14 +++- apps/design-system/styles/globals.css | 2 + pnpm-lock.yaml | 3 + 15 files changed, 176 insertions(+), 148 deletions(-) delete mode 100644 apps/design-system/content/docs/changelog.mdx delete mode 100644 apps/design-system/content/docs/figma.mdx create mode 100644 apps/design-system/registry/default/example/empty-state-initial-state-informational.tsx rename apps/design-system/registry/default/example/{empty-state-initial-state.tsx => empty-state-initial-state-presentational.tsx} (92%) create mode 100644 apps/design-system/registry/default/example/empty-state-zero-items-data-grid.tsx diff --git a/apps/design-system/README.md b/apps/design-system/README.md index 959e363180250..f7d0347f0b5a4 100644 --- a/apps/design-system/README.md +++ b/apps/design-system/README.md @@ -64,7 +64,7 @@ With that out of the way, there are several parts of this design system that nee - `registry/charts.ts`: chart components - `registry/fragments.ts`: fragment components -You may need to rebuild the design system’s registry. You can do that via: +You will probably need to rebuild the design system’s registry after making new additions. You can do that via: ```bash cd apps/design-system diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index cead6b766f8e2..32ff2204d4f6e 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -2315,13 +2315,35 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, - "empty-state-initial-state": { - name: "empty-state-initial-state", + "empty-state-initial-state-presentational": { + name: "empty-state-initial-state-presentational", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/empty-state-initial-state")), + component: React.lazy(() => import("@/registry/default/example/empty-state-initial-state-presentational")), source: "", - files: ["registry/default/example/empty-state-initial-state.tsx"], + files: ["registry/default/example/empty-state-initial-state-presentational.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "empty-state-initial-state-informational": { + name: "empty-state-initial-state-informational", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-initial-state-informational")), + source: "", + files: ["registry/default/example/empty-state-initial-state-informational.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "empty-state-zero-items-data-grid": { + name: "empty-state-zero-items-data-grid", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-zero-items-data-grid")), + source: "", + files: ["registry/default/example/empty-state-zero-items-data-grid.tsx"], category: "undefined", subcategory: "undefined", chunks: [] diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index ec78bcd99caff..7e9e2b628b15a 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -35,16 +35,6 @@ export const docsConfig: DocsConfig = { href: '/docs/icons', items: [], }, - { - title: 'Figma', - href: '/docs/figma', - items: [], - }, - { - title: 'Changelog', - href: '/docs/changelog', - items: [], - }, ], }, { diff --git a/apps/design-system/content/docs/changelog.mdx b/apps/design-system/content/docs/changelog.mdx deleted file mode 100644 index a96a3e675f167..0000000000000 --- a/apps/design-system/content/docs/changelog.mdx +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Changelog -description: Latest updates and announcements. -toc: false ---- - -## 2nd Oct 2025 - Update button styles - -- UI Patterns section added for higher-level guidance -- [`Navigation`](/design-system/docs/ui-patterns/navigation) UI pattern added -- [`NavMenu`](/design-system/docs/components/nav-menu) component added - -[PR](https://github.com/supabase/supabase/pull/39188) - -## 1st Sep 2025 - Update button styles - -- [`Color usage`](/design-system/docs/color-usage) page updated -- Color tokens improved for accessibility - -[PR](https://github.com/supabase/supabase/pull/38343) - -## 18th Oct 2024 - Update multi-select - -Improved Multi Select with combobox, badge limit, and more examples. - -- [Multi Select](/design-system/components/multi-select) - -[PR](https://github.com/supabase/supabase/pull/29695) - -## 12th June 2024 - Button styles updated - -- [` + + + + + Table name + Date created + + + + + + +

No tables yet

+

Create a table to get started

+
+
+
+
+
+
+ ) +} diff --git a/apps/design-system/registry/default/example/empty-state-initial-state.tsx b/apps/design-system/registry/default/example/empty-state-initial-state-presentational.tsx similarity index 92% rename from apps/design-system/registry/default/example/empty-state-initial-state.tsx rename to apps/design-system/registry/default/example/empty-state-initial-state-presentational.tsx index 0c0db65716615..81ace8d4713a5 100644 --- a/apps/design-system/registry/default/example/empty-state-initial-state.tsx +++ b/apps/design-system/registry/default/example/empty-state-initial-state-presentational.tsx @@ -1,8 +1,8 @@ -import { Button } from 'ui' -import { Plus } from 'lucide-react' import { BucketAdd } from 'icons' +import { Plus } from 'lucide-react' +import { Button } from 'ui' -export default function EmptyStateInitialState() { +export default function EmptyStateInitialStatePresentational() { return (