diff --git a/.github/playwright/impact-map.generated.json b/.github/playwright/impact-map.generated.json index d2a6dab48ee5..65a86538ff99 100644 --- a/.github/playwright/impact-map.generated.json +++ b/.github/playwright/impact-map.generated.json @@ -143,6 +143,7 @@ "playwright/e2e/Features/DataQuality/DataQualityDashboard.spec.ts", "playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts", "playwright/e2e/Features/DataQuality/Dimensionality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerDateFilter.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerSqlQuery.spec.ts", "playwright/e2e/Features/DataQuality/Profiler.spec.ts", @@ -749,6 +750,7 @@ "playwright/e2e/Features/AppMode/AppModeUrlHandling.spec.ts", "playwright/e2e/Features/ApplicationsSettingsModal.spec.ts", "playwright/e2e/Features/CustomMetric.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerDateFilter.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerSqlQuery.spec.ts", "playwright/e2e/Features/DataQuality/RunTestCase.spec.ts", @@ -802,6 +804,7 @@ "playwright/e2e/Features/DataQuality/AddTestCaseNewFlow.spec.ts", "playwright/e2e/Features/DataQuality/BundleSuiteBulkOperations.spec.ts", "playwright/e2e/Features/DataQuality/DataQuality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerDateFilter.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerSqlQuery.spec.ts", "playwright/e2e/Features/DataQuality/Profiler.spec.ts", @@ -1685,6 +1688,7 @@ "playwright/e2e/Features/DataQuality/DataQualityListAfterReindex.spec.ts", "playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts", "playwright/e2e/Features/DataQuality/Dimensionality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterOwnerChange.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterReindex.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterSoftDelete.spec.ts", @@ -3347,6 +3351,7 @@ "openmetadata-ui/src/main/resources/ui/playwright/utils/codeEditor.ts" ], "specs": [ + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/Profiler.spec.ts", "playwright/e2e/Features/DataQuality/TableLevelTests.spec.ts", "playwright/e2e/Features/Permission.spec.ts", @@ -3448,6 +3453,7 @@ "playwright/e2e/Features/DataQuality/DataQualityListAfterReindex.spec.ts", "playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts", "playwright/e2e/Features/DataQuality/Dimensionality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterOwnerChange.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterReindex.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterSoftDelete.spec.ts", @@ -3880,6 +3886,7 @@ "playwright/e2e/Features/DataQuality/DataQualityDashboard.spec.ts", "playwright/e2e/Features/DataQuality/DataQualityDashboardAfterReindex.spec.ts", "playwright/e2e/Features/DataQuality/Dimensionality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/TableLevelTests.spec.ts", "playwright/e2e/Features/IncidentManager.spec.ts", "playwright/e2e/Features/TestSuiteMultiPipeline.spec.ts", @@ -5120,6 +5127,7 @@ "playwright/e2e/Features/DataQuality/DataQualityDashboard.spec.ts", "playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts", "playwright/e2e/Features/DataQuality/Dimensionality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/Profiler.spec.ts", "playwright/e2e/Features/DataQuality/RunTestCase.spec.ts", "playwright/e2e/Features/DataQuality/TableLevelTests.spec.ts", @@ -6384,6 +6392,7 @@ "playwright/e2e/Features/DataQuality/ColumnLevelTests.spec.ts", "playwright/e2e/Features/DataQuality/DataQuality.spec.ts", "playwright/e2e/Features/DataQuality/Dimensionality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/TableLevelTests.spec.ts", "playwright/e2e/Pages/DataContracts.spec.ts", "playwright/e2e/VersionPages/TestCaseVersionPage.spec.ts" @@ -6524,6 +6533,7 @@ "openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/RunDetailsCard/RunDetailsCard.tsx" ], "specs": [ + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/TestCaseDetailsPage.spec.ts" ] }, @@ -6798,6 +6808,7 @@ "openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaEditor/CodeEditor.tsx" ], "specs": [ + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/TableLevelTests.spec.ts", "playwright/e2e/Features/QueryEntity.spec.ts", "playwright/e2e/Pages/DataContracts.spec.ts" @@ -6808,6 +6819,7 @@ "openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaEditor/SchemaEditor.tsx" ], "specs": [ + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/TableLevelTests.spec.ts", "playwright/e2e/Features/QueryEntity.spec.ts", "playwright/e2e/Pages/DataContracts.spec.ts" @@ -11320,6 +11332,14 @@ "playwright/e2e/Features/DataQuality/DataQualityDashboard.spec.ts" ] }, + { + "sources": [ + "openmetadata-ui/src/main/resources/ui/src/components/observability/data-quality/ThresholdPreview/ThresholdPreview.tsx" + ], + "specs": [ + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts" + ] + }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/AINotFoundPage/AINotFoundPage.tsx" @@ -11876,6 +11896,7 @@ "playwright/e2e/Features/DataQuality/DataQualityListAfterReindex.spec.ts", "playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts", "playwright/e2e/Features/DataQuality/Dimensionality.spec.ts", + "playwright/e2e/Features/DataQuality/FailureThreshold.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterOwnerChange.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterReindex.spec.ts", "playwright/e2e/Features/DataQuality/IncidentManagerAfterSoftDelete.spec.ts", diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-item.test.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-item.test.tsx new file mode 100644 index 000000000000..60242778ebb6 --- /dev/null +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-item.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2026 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { NavItemBase, type NavItemSize } from './nav-item'; + +const Icon = () => ; + +describe('NavItemBase', () => { + it.each<[NavItemSize, string]>([ + ['sm', 'tw:pl-9'], + ['md', 'tw:pl-10'], + ])( + 'indents a %s collapsible child to line up with its parent label', + (size, indent) => { + render( + + Child + + ); + + expect(screen.getByRole('link', { name: 'Child' })).toHaveClass(indent); + } + ); + + it.each<[NavItemSize, number]>([ + ['md', 0], + ['lg', 1], + ])('shows a %s collapsible child icon %i time(s)', (size, count) => { + render( + + Child + + ); + + expect(screen.queryAllByTestId('nav-icon')).toHaveLength(count); + }); + + it('fills the current item with the brand colour in the solid variant', () => { + render( + <> + + Current + + + Default + + + Resting + + + ); + + expect(screen.getByRole('link', { name: 'Resting' })).toHaveClass( + 'tw:bg-transparent' + ); + expect(screen.getByText('Resting')).toHaveClass('tw:text-tertiary'); + expect(screen.getByText('Current')).toHaveClass('tw:text-primary_on-brand'); + + expect(screen.getByRole('link', { name: 'Current' })).toHaveClass( + 'tw:bg-brand-solid' + ); + expect(screen.getByRole('link', { name: 'Default' })).toHaveClass( + 'tw:bg-brand-primary_alt' + ); + }); +}); diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-item.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-item.tsx index 21805ae12670..a948c7d0ef08 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-item.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-item.tsx @@ -14,26 +14,64 @@ const styles = sortCx({ rootSelected: 'tw:bg-brand-primary_alt tw:hover:bg-brand-primary_alt', }); -export type NavItemSize = 'sm' | 'md'; +export type NavItemVariant = 'default' | 'solid'; + +// `solid` fills the current item with the brand colour and opens a collapsible +// group under a filled header; hover tints rest items brand instead of gray. +const solidStyles = sortCx({ + // Rest items sit on the sidebar surface instead of painting white boxes. + root: 'tw:bg-transparent tw:hover:bg-brand-primary_alt tw:dark:hover:bg-brand-primary_alt', + rootSelected: + 'tw:bg-brand-solid tw:hover:bg-brand-solid tw:dark:bg-brand-solid tw:dark:hover:bg-brand-solid', + groupSelected: + 'tw:bg-brand-solid_hover tw:hover:bg-brand-solid_hover tw:dark:bg-brand-solid_hover tw:dark:hover:bg-brand-solid_hover', + groupOpen: + 'tw:in-open:rounded-b-none tw:in-open:bg-brand-solid_hover tw:in-open:hover:bg-brand-solid_hover tw:dark:in-open:bg-brand-solid_hover', + // Rest label and icon share one mid-gray, as in the pre-migration classic sidebar. + label: 'tw:text-tertiary tw:group-hover:text-brand-secondary', + labelSelected: + 'tw:text-primary_on-brand tw:group-hover:text-primary_on-brand', + labelOpen: + 'tw:in-open:text-primary_on-brand tw:in-open:group-hover:text-primary_on-brand', + icon: 'tw:text-fg-tertiary tw:group-hover:text-fg-brand-secondary_alt', + iconSelected: 'tw:text-fg-white tw:group-hover:text-fg-white', + iconOpen: 'tw:in-open:text-fg-white tw:in-open:group-hover:text-fg-white', +}); + +export type NavItemSize = 'sm' | 'md' | 'lg'; const sizeStyles = sortCx({ sm: { - padding: 'tw:py-1', - icon: 'tw:size-4', + root: 'tw:px-3 tw:py-1', + // Lines child labels up with the parent label: px-3 + icon + mr-2. + child: 'tw:py-1 tw:pr-3 tw:pl-9', + icon: 'tw:mr-2 tw:size-4', label: 'tw:text-sm tw:font-normal', labelCurrent: 'tw:font-semibold', }, md: { - padding: 'tw:py-2', - icon: 'tw:size-5', + root: 'tw:px-3 tw:py-2', + child: 'tw:py-2 tw:pr-3 tw:pl-10', + icon: 'tw:mr-2 tw:size-5', label: 'tw:text-md tw:font-semibold', labelCurrent: '', }, + // Roomy primary navigation: 56px rows with 28px icons. Children keep their + // icon and sit flush inside the open group. + lg: { + root: 'tw:rounded-xl tw:p-3.5', + child: 'tw:rounded-none tw:px-4 tw:py-2.5', + icon: 'tw:mr-2.5 tw:size-7', + label: 'tw:text-sm tw:font-medium', + labelCurrent: 'tw:font-semibold', + }, }); interface NavItemBaseProps { - /** Text and icon size. `sm` is for dense secondary lists. Defaults to `md`. */ + /** Text and icon size. `sm` is for dense secondary lists, `lg` for roomy primary navigation. Defaults to `md`. */ size?: NavItemSize; + /** `solid` fills the current item and an open group's header with the brand colour. Defaults to `default`. */ + variant?: NavItemVariant; /** Whether the nav item shows only an icon. */ iconOnly?: boolean; /** Whether the collapsible nav item is open. */ @@ -67,20 +105,42 @@ export const NavItemBase = ({ children, truncate = true, size = 'md', + variant = 'default', dataTestId, onClick, }: NavItemBaseProps) => { - const iconElement = Icon && ( + const isSolid = variant === 'solid'; + const isGroup = type === 'collapsible'; + // Children only show their icon at `lg`; smaller sizes indent instead. + const showIcon = type !== 'collapsible-child' || size === 'lg'; + + const iconElement = Icon && showIcon && (