diff --git a/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx b/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx index 2cacce34d5b06..bfb804279b442 100644 --- a/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx +++ b/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx @@ -4,6 +4,7 @@ github_url = "https://github.com/orgs/supabase/discussions/19391" date_created = "2023-12-03T17:05:20+00:00" topics = ["realtime", "self-hosting"] keywords = ["quota", "connections"] +database_id = "01e0dd67-5208-4e65-b489-2afd5a0b2af3" --- The "Concurrent Peak Connections" quota refers to the maximum number of simultaneous connections to Supabase Realtime. diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx index c238436218382..e8c224d68e361 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx @@ -17,6 +17,8 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import AddNewSecretForm from './AddNewSecretForm' import EdgeFunctionSecret from './EdgeFunctionSecret' import { EditSecretSheet } from './EditSecretSheet' +import { InlineLink } from '@/components/ui/InlineLink' +import { DOCS_URL } from '@/lib/constants' export const EdgeFunctionSecrets = () => { const { ref: projectRef } = useParams() @@ -158,8 +160,17 @@ export const EdgeFunctionSecrets = () => {

No secrets created

-

- There are no secrets associated with your project yet +

+ This project has no custom secrets yet.{' '} + + SUPABASE_* + {' '} + + default secrets + {' '} + are still available.

diff --git a/apps/studio/components/layouts/AppLayout/StatusPageBanner.tsx b/apps/studio/components/layouts/AppLayout/StatusPageBanner.tsx index b5ffe723f8cbc..1254546603c62 100644 --- a/apps/studio/components/layouts/AppLayout/StatusPageBanner.tsx +++ b/apps/studio/components/layouts/AppLayout/StatusPageBanner.tsx @@ -1,9 +1,7 @@ -import { LOCAL_STORAGE_KEYS, useFlag } from 'common' -import { HeaderBanner } from 'components/interfaces/Organization/HeaderBanner' -import { InlineLink } from 'components/ui/InlineLink' +import { useFlag } from 'common' -import { useIncidentStatusQuery } from '@/data/platform/incident-status-query' -import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' +import { HeaderBanner } from '@/components/interfaces/Organization/HeaderBanner' +import { InlineLink } from '@/components/ui/InlineLink' const BANNER_DESCRIPTION = ( <> @@ -12,54 +10,18 @@ const BANNER_DESCRIPTION = ( ) /** - * Used to display ongoing incidents or maintenances + * Used to display ongoing incidents */ export const StatusPageBanner = () => { - const { data: allStatusPageEvents } = useIncidentStatusQuery() - const { incidents = [], maintenanceEvents = [] } = allStatusPageEvents ?? {} - - // Only show incident banner for incidents with real impact (not "none") - const highImpactIncident = incidents.find((incident) => incident.impact !== 'none') - const incidentEventId = highImpactIncident?.id ?? '' - - const showIncidentBannerOverride = + const showIncidentBanner = useFlag('ongoingIncident') || process.env.NEXT_PUBLIC_ONGOING_INCIDENT === 'true' - const ongoingMaintenance = maintenanceEvents.length > 0 - const maintenanceEventId = maintenanceEvents[0]?.id ?? '' - - const [dismissedIncident, setDismissedIncident] = useLocalStorageQuery( - LOCAL_STORAGE_KEYS.INCIDENT_BANNER_DISMISSED(incidentEventId), - false - ) - - const [dismissedMaintenance, setDismissedMaintenance] = useLocalStorageQuery( - LOCAL_STORAGE_KEYS.MAINTENANCE_BANNER_DISMISSED(maintenanceEventId), - false - ) - - if (showIncidentBannerOverride || (highImpactIncident && !dismissedIncident)) { + if (showIncidentBanner) { return ( setDismissedIncident(true) - } - /> - ) - } - - if (ongoingMaintenance && !dismissedMaintenance) { - return ( - setDismissedMaintenance(true)} /> ) } diff --git a/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx b/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx index 895917be4f992..394d0b6654628 100644 --- a/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx +++ b/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx @@ -299,27 +299,25 @@ const EdgeFunctionDetailsLayout = ({ - {IS_PLATFORM && ( - <> - {!!functionSlug && ( - - )} - + {!!functionSlug && ( + )} diff --git a/apps/studio/lib/api/edgeFunctions.test.ts b/apps/studio/lib/api/edgeFunctions.test.ts index 068879834111b..54e0af1f63e72 100644 --- a/apps/studio/lib/api/edgeFunctions.test.ts +++ b/apps/studio/lib/api/edgeFunctions.test.ts @@ -1,4 +1,5 @@ -import { expect, describe, it } from 'vitest' +import { describe, expect, it } from 'vitest' + import { isValidEdgeFunctionURL } from './edgeFunctions' describe('isValidEdgeFunctionURL', () => { @@ -9,7 +10,16 @@ describe('isValidEdgeFunctionURL', () => { 'https://projectref.supabase.red/functions/v3/hello-world', ] - const invalidEdgeFunctionUrls = [ + const validLocalEdgeFunctionsUrls = [ + 'https://projectref.notsupabase.com/functions/v1/test', + 'https://notsupabase.com/functions/v1/test', + 'http://localhost:54321/functions/v1/test-2', + 'http://kong:8000/functions/v1/hello-world', + 'https://127.0.0.1:54321/functions/v1/test-3', + 'https://127.0.0.1:54321/functions/v1/test-5', + ] + + const invalidPlatformEdgeFunctionUrls = [ 'https://notsupabase.com/functions/v1/test', 'https://projectref.notsupabase.com/functions/v1/test', 'https://localhost?https://aaaa.supabase.co/functions/v1/xxx', @@ -17,15 +27,41 @@ describe('isValidEdgeFunctionURL', () => { 'http://localhost:3000/?https://aaaa.supabase.co/functions/v1/xxx', ] - it('should match valid edge function URLs', () => { + const invalidEdgeFunctionUrls = [ + 'https://localhost?https://aaaa.supabase.co/functions/v1/xxx', + 'https://localhost:3000/?https://aaaa.supabase.co/functions/v1/xxx', + 'http://localhost:3000/?https://aaaa.supabase.co/functions/v1/xxx', + ] + + it('should match valid edge function URLs on platform', () => { for (const url of validEdgeFunctionUrls) { - expect(isValidEdgeFunctionURL(url), `Expected ${url} to be valid`).toBe(true) + expect(isValidEdgeFunctionURL(url, true), `Expected ${url} to be valid`).toBe(true) + } + }) + + it('should not match local URLs on platform', () => { + for (const url of validLocalEdgeFunctionsUrls) { + expect(isValidEdgeFunctionURL(url, true), `Expected ${url} to be invalid on platform`).toBe( + false + ) + } + }) + + it('should match valid local edge function URLs off platform', () => { + for (const url of validLocalEdgeFunctionsUrls) { + expect(isValidEdgeFunctionURL(url, false), `Expected ${url} to be valid`).toBe(true) + } + }) + + it('should not match invalid edge function URLs on platform', () => { + for (const url of invalidPlatformEdgeFunctionUrls) { + expect(isValidEdgeFunctionURL(url, true), `Expected ${url} to be invalid`).toBe(false) } }) - it('should not match invalid edge function URLs', () => { + it('should not match invalid edge function URLs off platform', () => { for (const url of invalidEdgeFunctionUrls) { - expect(isValidEdgeFunctionURL(url), `Expected ${url} to be invalid`).toBe(false) + expect(isValidEdgeFunctionURL(url, false), `Expected ${url} to be invalid`).toBe(false) } }) }) diff --git a/apps/studio/lib/api/edgeFunctions.ts b/apps/studio/lib/api/edgeFunctions.ts index a62fadc91eaa8..15b0138328ac2 100644 --- a/apps/studio/lib/api/edgeFunctions.ts +++ b/apps/studio/lib/api/edgeFunctions.ts @@ -1,12 +1,20 @@ const NIMBUS_PROD_PROJECTS_URL = process.env.NIMBUS_PROD_PROJECTS_URL -export const isValidEdgeFunctionURL = (url: string) => { +export const isValidEdgeFunctionURL = (url: string, isPlatform: boolean) => { if (NIMBUS_PROD_PROJECTS_URL !== undefined) { const apexDomain = NIMBUS_PROD_PROJECTS_URL.replace('https://*.', '').replace(/\./g, '\\.') const nimbusRegex = new RegExp('^https://[a-z]*\\.' + apexDomain + '/functions/v[0-9]{1}/.*$') return nimbusRegex.test(url) } + if (!isPlatform) { + const regexValidLocalEdgeFunctionURL = new RegExp( + '^https?://[^\\s/?#]+/functions/v[0-9]{1}/.*$' + ) + + return regexValidLocalEdgeFunctionURL.test(url) + } + const regexValidEdgeFunctionURL = new RegExp( '^https://[a-z]*.supabase.(red|co)/functions/v[0-9]{1}/.*$' ) diff --git a/apps/studio/pages/api/edge-functions/test.ts b/apps/studio/pages/api/edge-functions/test.ts index 044dfe70cdb0c..442d4f1be4f06 100644 --- a/apps/studio/pages/api/edge-functions/test.ts +++ b/apps/studio/pages/api/edge-functions/test.ts @@ -1,3 +1,4 @@ +import { IS_PLATFORM } from 'common' import { isValidEdgeFunctionURL } from 'lib/api/edgeFunctions' import { NextApiRequest, NextApiResponse } from 'next' @@ -20,9 +21,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) async function handlePost(req: NextApiRequest, res: NextApiResponse) { try { - const { url, method, body: requestBody, headers: customHeaders } = req.body + const { url: requestUrl, method, body: requestBody, headers: customHeaders } = req.body + const url = IS_PLATFORM + ? requestUrl + : requestUrl.replace(process.env.SUPABASE_PUBLIC_URL, process.env.SUPABASE_URL) - const validEdgeFnUrl = isValidEdgeFunctionURL(url) + const validEdgeFnUrl = isValidEdgeFunctionURL(url, IS_PLATFORM) if (!validEdgeFnUrl) { return res.status(400).json({ diff --git a/apps/studio/pages/project/[ref]/observability/index.tsx b/apps/studio/pages/project/[ref]/observability/index.tsx index 087c2791fb12a..24e081daa5def 100644 --- a/apps/studio/pages/project/[ref]/observability/index.tsx +++ b/apps/studio/pages/project/[ref]/observability/index.tsx @@ -1,25 +1,25 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' - -import { useParams, useFlag } from 'common' -import { CreateReportModal } from 'components/interfaces/Reports/CreateReportModal' +import { useFeatureFlags, useFlag, useParams } from 'common' import { ObservabilityOverview } from 'components/interfaces/Observability/ObservabilityOverview' +import { CreateReportModal } from 'components/interfaces/Reports/CreateReportModal' import DefaultLayout from 'components/layouts/DefaultLayout' import ObservabilityLayout from 'components/layouts/ObservabilityLayout/ObservabilityLayout' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import { useContentQuery } from 'data/content/content-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useProfile } from 'lib/profile' +import { useRouter } from 'next/router' +import { parseAsBoolean, useQueryState } from 'nuqs' +import { useEffect, useState } from 'react' import type { NextPageWithLayout } from 'types' import { LogoLoader } from 'ui' -import { parseAsBoolean, useQueryState } from 'nuqs' export const UserReportPage: NextPageWithLayout = () => { const router = useRouter() const { ref } = useParams() const { profile } = useProfile() + const { hasLoaded: flagsLoaded } = useFeatureFlags() const showOverview = useFlag('observabilityOverview') const [showCreateReportModal, setShowCreateReportModal] = useQueryState( 'newReport', @@ -37,6 +37,7 @@ export const UserReportPage: NextPageWithLayout = () => { useEffect(() => { if (!isSuccess) return + if (!flagsLoaded) return // Wait for flags to load before checking redirect if (showOverview) return // Don't redirect if overview is enabled const reports = data.content @@ -44,7 +45,7 @@ export const UserReportPage: NextPageWithLayout = () => { .sort((a, b) => a.name.localeCompare(b.name)) if (reports.length >= 1) router.push(`/project/${ref}/observability/${reports[0].id}`) if (reports.length === 0) router.push(`/project/${ref}/observability/api-overview`) - }, [isSuccess, data, router, ref, showOverview]) + }, [isSuccess, data, router, ref, showOverview, flagsLoaded]) const { can: canCreateReport } = useAsyncCheckPermissions( PermissionAction.CREATE, @@ -55,6 +56,11 @@ export const UserReportPage: NextPageWithLayout = () => { } ) + // Wait for flags to load before rendering to avoid flashing wrong page + if (!flagsLoaded || isLoading) { + return + } + // Show overview page if feature flag is enabled if (showOverview) { return @@ -62,34 +68,28 @@ export const UserReportPage: NextPageWithLayout = () => { return (
- {isLoading ? ( - - ) : ( - <> - { - setShowCreateReportModal(true) - }} - disabled={!canCreateReport} - disabledMessage="You need additional permissions to create a report" - > -

- Create custom reports for your projects. -

-

- Get a high level overview of your network traffic, user actions, and infrastructure - health. -

-
- setShowCreateReportModal(false)} - afterSubmit={() => setShowCreateReportModal(false)} - /> - - )} + <> + { + setShowCreateReportModal(true) + }} + disabled={!canCreateReport} + disabledMessage="You need additional permissions to create a report" + > +

Create custom reports for your projects.

+

+ Get a high level overview of your network traffic, user actions, and infrastructure + health. +

+
+ setShowCreateReportModal(false)} + afterSubmit={() => setShowCreateReportModal(false)} + /> +
) } diff --git a/apps/www/_blog/2026-02-04-hydra-joins-supabase.mdx b/apps/www/_blog/2026-02-04-hydra-joins-supabase.mdx new file mode 100644 index 0000000000000..042e06a9a3b4b --- /dev/null +++ b/apps/www/_blog/2026-02-04-hydra-joins-supabase.mdx @@ -0,0 +1,47 @@ +--- +title: 'Hydra joins Supabase' +description: 'The Hydra team, maintainers of pg_duckdb, is joining Supabase to focus on Postgres + Analytics and Open Warehouse Architecture.' +author: paul_copplestone +imgSocial: https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=partnerships&layout=icon-only©=BKND+is+joining+Supabase&icon=1770827339509-Hydra.png&icon2=supabase.svg +imgThumb: https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=partnerships&layout=icon-only©=BKND+is+joining+Supabase&icon=1770827339509-Hydra.png&icon2=supabase.svg +categories: + - company +date: '2026-02-10:10:00:00' +toc_depth: 2 +--- + +Today we're welcoming [Joe](https://x.com/JoeSciarrino), the co-creator of Hydra, to the Supabase team. He is joining us to build Supabase Warehouse: an open data warehouse architecture for developers. + +## Hydra + pg_duckdb + +Hydra co-developed [`pg_duckdb`](https://github.com/duckdb/pg_duckdb), a popular open source (MIT-licensed) extension that accelerates analytics queries on Postgres by over 600x. + +![Hydra benchmarks chart](/images/blog/hydra-joins-supabase/benchmark.png) + +### Analytics on Postgres + +Joe's work on Hydra pioneered the modern lakebase architecture before most even considered it possible. + +Last year, Supabase started looking at warehousing, announcing Analytics Buckets to store data in Iceberg format in S3. This year, Hydra and `pg_duckdb` will make it easier than ever to get fast analytics on large data volumes in Postgres. + +Joe will oversee our Open Warehouse Architecture initiative and help push our Postgres + Analytics roadmap forward. + +### Open Warehouse Architecture + +We're building an open warehouse architecture that keeps Postgres at the center while unlocking modern analytics workflows. + +![Open Warehouse Architecture diagram](/images/blog/hydra-joins-supabase/open-warehouse-architecture.png) + +This effort includes: + +- A tighter integration between Postgres and object storage. +- Serverless analytics workflows that feel native to Supabase. +- A foundation for open table formats and analytics-first tooling. + +### Next Steps for Hydra + +Everything will stay open source. We'll take over maintenance of pg_duckdb and work in the open as we develop the Open Warehouse Architecture and the broader Postgres + Analytics vision. + +## Join us to build the Open Warehouse + +We are assembling a team to build the Open Warehouse Architecture on Supabase. If you are a C++ programmer or storage engineer and want to get involved, [apply online](/careers). diff --git a/apps/www/public/images/blog/hydra-joins-supabase/benchmark.png b/apps/www/public/images/blog/hydra-joins-supabase/benchmark.png new file mode 100644 index 0000000000000..23af9b2c519cc Binary files /dev/null and b/apps/www/public/images/blog/hydra-joins-supabase/benchmark.png differ diff --git a/apps/www/public/images/blog/hydra-joins-supabase/og.png b/apps/www/public/images/blog/hydra-joins-supabase/og.png new file mode 100644 index 0000000000000..221e3816b140a Binary files /dev/null and b/apps/www/public/images/blog/hydra-joins-supabase/og.png differ diff --git a/apps/www/public/images/blog/hydra-joins-supabase/open-warehouse-architecture.png b/apps/www/public/images/blog/hydra-joins-supabase/open-warehouse-architecture.png new file mode 100644 index 0000000000000..69f8fd30d9a05 Binary files /dev/null and b/apps/www/public/images/blog/hydra-joins-supabase/open-warehouse-architecture.png differ diff --git a/apps/www/public/images/blog/hydra-joins-supabase/thumb.png b/apps/www/public/images/blog/hydra-joins-supabase/thumb.png new file mode 100644 index 0000000000000..221e3816b140a Binary files /dev/null and b/apps/www/public/images/blog/hydra-joins-supabase/thumb.png differ diff --git a/e2e/studio/features/status-page-banner.spec.ts b/e2e/studio/features/status-page-banner.spec.ts index a1c504e1dfd1c..28a3ac4d5483f 100644 --- a/e2e/studio/features/status-page-banner.spec.ts +++ b/e2e/studio/features/status-page-banner.spec.ts @@ -3,172 +3,15 @@ import { expect } from '@playwright/test' import { test } from '../utils/test.js' import { toUrl } from '../utils/to-url.js' -// Mock incident data - impact is "major" (not "none" or "maintenance") -const mockIncident = { - id: 'incident-123', - name: 'Test Incident', - status: 'investigating', - impact: 'major', - active_since: new Date().toISOString(), -} - -// Mock incident with "none" impact - should NOT trigger the incident banner -const mockIncidentNoneImpact = { - id: 'incident-none-456', - name: 'No Impact Incident', - status: 'investigating', - impact: 'none', - active_since: new Date().toISOString(), -} - -// Mock maintenance event - impact is "maintenance" -const mockMaintenance = { - id: 'maintenance-789', - name: 'Scheduled Maintenance', - status: 'in_progress', - impact: 'maintenance', - active_since: new Date().toISOString(), -} - test.describe('StatusPageBanner', () => { - test.describe.configure({ mode: 'serial' }) - - test.beforeEach(async ({ page }) => { - // Clear any dismissed banner state from localStorage before each test - await page.addInitScript(() => { - // Clear all incident/maintenance banner dismissals and test overrides - for (const key of Object.keys(localStorage)) { - if ( - key.startsWith('incident-banner-dismissed-') || - key.startsWith('maintenance-banner-dismissed-') - ) { - localStorage.removeItem(key) - } - } - }) - }) - - test('incident banner shows for incidents with impact not equal to "none"', async ({ - page, - ref, - }) => { - // Mock the incident status API to return an incident with major impact - await page.route('**/api/incident-status', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([mockIncident]), - }) - }) - - await page.goto(toUrl(`/project/${ref}`)) - - // Wait for the incident banner to be visible - const incidentBanner = page.getByText('We are investigating a technical issue') - await expect(incidentBanner).toBeVisible({ timeout: 15000 }) - - // Verify the dismiss button IS present for real incidents - const dismissButton = page.getByRole('button', { name: 'Dismiss banner' }) - await expect(dismissButton).toBeVisible() - }) - - test('incident banner does NOT show for incidents with impact "none"', async ({ page, ref }) => { - // Mock the incident status API to return an incident with "none" impact - await page.route('**/api/incident-status', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([mockIncidentNoneImpact]), - }) - }) - + test('incident banner does not show when no incident flag is set', async ({ page, ref }) => { await page.goto(toUrl(`/project/${ref}`)) // Wait for page to load await expect(page.getByRole('heading', { level: 1 })).toBeVisible({ timeout: 15000 }) - // Verify the incident banner is NOT visible + // Verify the incident banner is NOT visible (no flag is set) const incidentBanner = page.getByText('We are investigating a technical issue') await expect(incidentBanner).not.toBeVisible() }) - - test('incident banner takes precedence over maintenance banner', async ({ page, ref }) => { - // Mock the incident status API to return both an incident and a maintenance event - await page.route('**/api/incident-status', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([mockIncident, mockMaintenance]), - }) - }) - - await page.goto(toUrl(`/project/${ref}`)) - - // Wait for the incident banner to be visible - const incidentBanner = page.getByText('We are investigating a technical issue') - await expect(incidentBanner).toBeVisible({ timeout: 15000 }) - - // Verify the maintenance banner is NOT visible (incident takes precedence) - const maintenanceBanner = page.getByText('Scheduled maintenance is in progress') - await expect(maintenanceBanner).not.toBeVisible() - }) - - test('maintenance banner shows after dismissing incident banner when both exist', async ({ - page, - ref, - }) => { - // Mock the incident status API to return both an incident and a maintenance event - await page.route('**/api/incident-status', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([mockIncident, mockMaintenance]), - }) - }) - - await page.goto(toUrl(`/project/${ref}`)) - - // Wait for the incident banner to be visible - const incidentBanner = page.getByText('We are investigating a technical issue') - await expect(incidentBanner).toBeVisible({ timeout: 15000 }) - - // Click the dismiss button to dismiss the incident banner - const dismissButton = page.getByRole('button', { name: 'Dismiss banner' }) - await expect(dismissButton).toBeVisible() - await dismissButton.click() - - // Wait for the incident banner to disappear - await expect(incidentBanner).not.toBeVisible({ timeout: 5000 }) - - // Verify the maintenance banner is now visible - const maintenanceBanner = page.getByText('Scheduled maintenance is in progress') - await expect(maintenanceBanner).toBeVisible({ timeout: 5000 }) - }) - - test('maintenance banner is dismissible', async ({ page, ref }) => { - // Mock the incident status API to return only a maintenance event - await page.route('**/api/incident-status', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([mockMaintenance]), - }) - }) - - await page.goto(toUrl(`/project/${ref}`)) - - // Wait for the maintenance banner to be visible - const maintenanceBanner = page.getByText('Scheduled maintenance is in progress') - await expect(maintenanceBanner).toBeVisible({ timeout: 15000 }) - - // Verify the dismiss button is present - const dismissButton = page.getByRole('button', { name: 'Dismiss banner' }) - await expect(dismissButton).toBeVisible() - - // Dismiss the maintenance banner - await dismissButton.click() - - // Verify the maintenance banner is no longer visible - await expect(maintenanceBanner).not.toBeVisible({ timeout: 5000 }) - }) }) diff --git a/packages/ui-patterns/src/FilterBar/CommandListItem.tsx b/packages/ui-patterns/src/FilterBar/CommandListItem.tsx index c420640469aac..8c05ebb985b85 100644 --- a/packages/ui-patterns/src/FilterBar/CommandListItem.tsx +++ b/packages/ui-patterns/src/FilterBar/CommandListItem.tsx @@ -1,4 +1,3 @@ -import { Check } from 'lucide-react' import { cn } from 'ui' import { OperatorSymbolBadge } from './OperatorSymbolBadge' @@ -7,7 +6,6 @@ import { MenuItem } from './types' export type CommandListItemProps = { item: MenuItem isHighlighted: boolean - isSelected: boolean includeIcon: boolean onSelect: (item: MenuItem) => void setRef: (el: HTMLDivElement | null) => void @@ -16,7 +14,6 @@ export type CommandListItemProps = { export function CommandListItem({ item, isHighlighted, - isSelected, includeIcon, onSelect, setRef, @@ -25,7 +22,6 @@ export function CommandListItem({
onSelect(item)} className={cn( 'relative flex items-center justify-between gap-2 px-2 py-1.5 text-xs cursor-pointer select-none outline-none text-foreground-light', @@ -37,10 +33,7 @@ export function CommandListItem({ {includeIcon && item.icon} {item.label} - - {item.operatorSymbol && } - {isSelected && } - + {item.operatorSymbol && }
) } diff --git a/packages/ui-patterns/src/FilterBar/DefaultCommandList.helpers.tsx b/packages/ui-patterns/src/FilterBar/DefaultCommandList.helpers.tsx index 7f75ff009cacd..7a507adc69fff 100644 --- a/packages/ui-patterns/src/FilterBar/DefaultCommandList.helpers.tsx +++ b/packages/ui-patterns/src/FilterBar/DefaultCommandList.helpers.tsx @@ -4,7 +4,7 @@ export function EmptyState() { export function GroupHeader({ label }: { label: string }) { return ( -
+
{label}
) diff --git a/packages/ui-patterns/src/FilterBar/DefaultCommandList.tsx b/packages/ui-patterns/src/FilterBar/DefaultCommandList.tsx index 09d5065ac6d51..761019dfc2589 100644 --- a/packages/ui-patterns/src/FilterBar/DefaultCommandList.tsx +++ b/packages/ui-patterns/src/FilterBar/DefaultCommandList.tsx @@ -13,7 +13,6 @@ export type DefaultCommandListProps = { onSelect: (item: MenuItem) => void includeIcon?: boolean grouped?: boolean - selectedValue?: string } export function DefaultCommandList({ @@ -22,7 +21,6 @@ export function DefaultCommandList({ onSelect, includeIcon = true, grouped = false, - selectedValue, }: DefaultCommandListProps) { const listRef = useRef(null) const itemRefs = useRef>(new Map()) @@ -78,7 +76,6 @@ export function DefaultCommandList({ key={`${item.value}-${item.label}`} item={item} isHighlighted={index === highlightedIndex} - isSelected={selectedValue === item.value} includeIcon={includeIcon} onSelect={onSelect} setRef={setItemRef(index)} diff --git a/packages/ui-patterns/src/FilterBar/FilterCondition.tsx b/packages/ui-patterns/src/FilterBar/FilterCondition.tsx index 27e7637b59025..1c10369aae184 100644 --- a/packages/ui-patterns/src/FilterBar/FilterCondition.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterCondition.tsx @@ -260,7 +260,6 @@ export function FilterCondition({ highlightedIndex={opHighlightedIndex} onSelect={handleSelectMenuItem} includeIcon={false} - selectedValue={condition.operator} grouped /> diff --git a/packages/ui-patterns/src/FilterBar/OperatorSymbolBadge.tsx b/packages/ui-patterns/src/FilterBar/OperatorSymbolBadge.tsx index d0788fd7b28d2..1870be99c0b44 100644 --- a/packages/ui-patterns/src/FilterBar/OperatorSymbolBadge.tsx +++ b/packages/ui-patterns/src/FilterBar/OperatorSymbolBadge.tsx @@ -4,7 +4,7 @@ export type OperatorSymbolBadgeProps = { export function OperatorSymbolBadge({ symbol }: OperatorSymbolBadgeProps) { return ( - + {symbol} )