Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/playwright/impact-map.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
]
},
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = () => <svg data-testid="nav-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(
<NavItemBase href="/child" size={size} type="collapsible-child">
Child
</NavItemBase>
);

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(
<NavItemBase
href="/child"
icon={Icon}
size={size}
type="collapsible-child">
Child
</NavItemBase>
);

expect(screen.queryAllByTestId('nav-icon')).toHaveLength(count);
});

it('fills the current item with the brand colour in the solid variant', () => {
render(
<>
<NavItemBase current href="/a" type="link" variant="solid">
Current
</NavItemBase>
<NavItemBase current href="/b" type="link">
Default
</NavItemBase>
<NavItemBase href="/c" type="link" variant="solid">
Resting
</NavItemBase>
</>
);

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'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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 && (
<Icon
aria-hidden="true"
className={cx(
'tw:mr-2 tw:shrink-0 tw:text-fg-quaternary tw:transition-inherit-all',
'tw:shrink-0 tw:text-fg-quaternary tw:transition-inherit-all',
sizeStyles[size].icon,
current && 'tw:text-fg-brand-secondary_alt'
current && 'tw:text-fg-brand-secondary_alt',
isSolid && solidStyles.icon,
isSolid && current && solidStyles.iconSelected,
isSolid && isGroup && solidStyles.iconOpen
)}
/>
);

const rootClassName = cx(
styles.root,
type === 'collapsible-child'
? sizeStyles[size].child
: sizeStyles[size].root,
current && styles.rootSelected,
isSolid && solidStyles.root,
isSolid &&
current &&
(isGroup ? solidStyles.groupSelected : solidStyles.rootSelected),
isSolid && isGroup && solidStyles.groupOpen
);

const badgeElement =
badge && (typeof badge === 'string' || typeof badge === 'number') ? (
<Badge className="tw:ml-3" color="gray" size="sm" type="pill-color">
Expand All @@ -97,7 +157,11 @@ export const NavItemBase = ({
sizeStyles[size].label,
current && sizeStyles[size].labelCurrent,
truncate && 'tw:truncate',
current && 'tw:text-brand-secondary tw:group-hover:text-brand-secondary'
current &&
'tw:text-brand-secondary tw:group-hover:text-brand-secondary',
isSolid && solidStyles.label,
isSolid && current && solidStyles.labelSelected,
isSolid && isGroup && solidStyles.labelOpen
)}>
{children}
</span>
Expand All @@ -110,14 +174,7 @@ export const NavItemBase = ({

if (type === 'collapsible') {
return (
<summary
className={cx(
'tw:px-3',
sizeStyles[size].padding,
styles.root,
current && styles.rootSelected
)}
onClick={onClick}>
<summary className={rootClassName} onClick={onClick}>
{iconElement}

{labelElement}
Expand All @@ -126,7 +183,11 @@ export const NavItemBase = ({

<ChevronDown
aria-hidden="true"
className="tw:ml-3 tw:size-4 tw:shrink-0 tw:stroke-[2.5px] tw:text-fg-quaternary tw:in-open:-scale-y-100"
className={cx(
'tw:ml-3 tw:size-4 tw:shrink-0 tw:stroke-[2.5px] tw:text-fg-quaternary tw:in-open:-scale-y-100',
isSolid && current && solidStyles.iconSelected,
isSolid && solidStyles.iconOpen
)}
/>
</summary>
);
Expand All @@ -136,17 +197,13 @@ export const NavItemBase = ({
return (
<AriaLink
aria-current={current ? 'page' : undefined}
className={cx(
'tw:pr-3 tw:pl-10',
sizeStyles[size].padding,
styles.root,
current && styles.rootSelected
)}
className={rootClassName}
data-testid={dataTestId}
href={href!}
rel="noopener noreferrer"
target={isExternal ? '_blank' : '_self'}
onClick={onClick}>
{iconElement}
{labelElement}
{externalIcon}
{badgeElement}
Expand All @@ -157,12 +214,7 @@ export const NavItemBase = ({
return (
<AriaLink
aria-current={current ? 'page' : undefined}
className={cx(
'tw:px-3',
sizeStyles[size].padding,
styles.root,
current && styles.rootSelected
)}
className={rootClassName}
data-testid={dataTestId}
href={href!}
rel="noopener noreferrer"
Expand Down
Loading
Loading