From 1b246b82d8a825cd6699047abf7dd026446cd286 Mon Sep 17 00:00:00 2001 From: Miki Date: Mon, 5 Aug 2024 13:02:47 -0700 Subject: [PATCH 01/35] Bump OUI to 1.9.0 Signed-off-by: Miki --- package.json | 2 +- packages/osd-ui-framework/package.json | 2 +- packages/osd-ui-shared-deps/package.json | 2 +- .../__snapshots__/region_map_options.test.tsx.snap | 12 ++++++------ .../plugins/osd_tp_run_pipeline/package.json | 2 +- .../plugins/osd_sample_panel_action/package.json | 2 +- .../osd_tp_custom_visualizations/package.json | 2 +- yarn.lock | 8 ++++---- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 3392b44d0072..1934c949fefe 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "dependencies": { "@aws-crypto/client-node": "^3.1.1", "@elastic/datemath": "5.0.3", - "@elastic/eui": "npm:@opensearch-project/oui@1.8.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.9.0", "@elastic/good": "^9.0.1-kibana3", "@elastic/numeral": "npm:@amoo-miki/numeral@2.6.0", "@elastic/request-crypto": "2.0.0", diff --git a/packages/osd-ui-framework/package.json b/packages/osd-ui-framework/package.json index f1decec5302c..7cfe87f7e2fd 100644 --- a/packages/osd-ui-framework/package.json +++ b/packages/osd-ui-framework/package.json @@ -23,7 +23,7 @@ "enzyme-adapter-react-16": "^1.9.1" }, "devDependencies": { - "@elastic/eui": "npm:@opensearch-project/oui@1.8.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.9.0", "@osd/babel-preset": "1.0.0", "@osd/optimizer": "1.0.0", "comment-stripper": "^0.0.4", diff --git a/packages/osd-ui-shared-deps/package.json b/packages/osd-ui-shared-deps/package.json index 45611a198bc9..05ccf6b50255 100644 --- a/packages/osd-ui-shared-deps/package.json +++ b/packages/osd-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@elastic/charts": "31.1.0", - "@elastic/eui": "npm:@opensearch-project/oui@1.8.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.9.0", "@elastic/numeral": "npm:@amoo-miki/numeral@2.6.0", "@opensearch/datemath": "5.0.3", "@osd/i18n": "1.0.0", diff --git a/src/plugins/region_map/public/components/__snapshots__/region_map_options.test.tsx.snap b/src/plugins/region_map/public/components/__snapshots__/region_map_options.test.tsx.snap index 447d5876fa10..8f16f7a650f0 100644 --- a/src/plugins/region_map/public/components/__snapshots__/region_map_options.test.tsx.snap +++ b/src/plugins/region_map/public/components/__snapshots__/region_map_options.test.tsx.snap @@ -271,7 +271,7 @@ exports[`region_map_options renders the RegionMapOptions with custom option if c onMouseOver={[Function]} >
, - } - } - className="euiHeaderSectionItemButton" - color="text" - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - > - - - - - +
@@ -7223,6 +7334,60 @@ exports[`Header renders condensed header 1`] = ` "thrownError": null, }, }, + "currentBadgeControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentBottomControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentCenterControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentDescriptionControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentLeftControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentRightControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, "getComponent": [MockFunction], "getUrlForApp": [MockFunction], "history": Object { @@ -7247,6 +7412,12 @@ exports[`Header renders condensed header 1`] = ` "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } badge$={ @@ -7359,6 +7530,43 @@ exports[`Header renders condensed header 1`] = ` "syncErrorThrown": false, "syncErrorValue": null, }, + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, ], "thrownError": null, } @@ -7521,6 +7729,55 @@ exports[`Header renders condensed header 1`] = ` "thrownError": null, } } + headerVariant$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } helpExtension$={ BehaviorSubject { "_isScalar": false, @@ -9877,6 +10134,43 @@ exports[`Header renders condensed header 1`] = ` "syncErrorThrown": false, "syncErrorValue": null, }, + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, ], "thrownError": null, } @@ -9940,6 +10234,7 @@ exports[`Header renders condensed header 1`] = ` }, ] } + className="" data-test-subj="breadcrumbs" max={10} > @@ -10051,9 +10346,10 @@ exports[`Header renders condensed header 1`] = ` >
@@ -10940,6 +11237,7 @@ exports[`Header renders condensed header 1`] = ` opensearchDashboardsVersion="1.0.0" surveyLink="/" useDefaultContent={true} + useUpdatedAppearance={true} > - - - - + } closePopover={[Function]} data-test-subj="helpMenuButton" @@ -11779,117 +12070,48 @@ exports[`Header renders condensed header 1`] = ` repositionOnScroll={true} >
- - - - - +
@@ -12475,6 +12697,60 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` "thrownError": null, }, }, + "currentBadgeControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentBottomControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentCenterControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentDescriptionControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentLeftControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentRightControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, "getComponent": [MockFunction], "getUrlForApp": [MockFunction], "history": Object { @@ -12499,6 +12775,12 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } badge$={ @@ -12611,6 +12893,43 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` "syncErrorThrown": false, "syncErrorValue": null, }, + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, ], "thrownError": null, } @@ -12773,6 +13092,55 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` "thrownError": null, } } + headerVariant$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } helpExtension$={ BehaviorSubject { "_isScalar": false, @@ -15129,6 +15497,43 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` "syncErrorThrown": false, "syncErrorValue": null, }, + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, ], "thrownError": null, } @@ -15192,6 +15597,7 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` }, ] } + className="" data-test-subj="breadcrumbs" max={10} > @@ -15303,9 +15709,10 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` >
@@ -16192,6 +16600,7 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` opensearchDashboardsVersion="1.0.0" surveyLink="/" useDefaultContent={true} + useUpdatedAppearance={true} > - - - - + } closePopover={[Function]} data-test-subj="helpMenuButton" @@ -17031,117 +17433,48 @@ exports[`Header toggles primary navigation menu when clicked 1`] = ` repositionOnScroll={true} >
- - - - - +
diff --git a/src/core/public/chrome/ui/header/header.scss b/src/core/public/chrome/ui/header/header.scss index cf5b3271a2a2..bd5d20975d3f 100644 --- a/src/core/public/chrome/ui/header/header.scss +++ b/src/core/public/chrome/ui/header/header.scss @@ -28,7 +28,7 @@ &.primaryApplicationHeader { height: auto; - padding-top: calc($euiSizeXS / 2); + padding-top: $euiSizeM - $euiSizeXS; } &:has(.headerDescriptionControl, .headerBottomControl) { @@ -36,6 +36,21 @@ } } +.primaryApplicationHeader { + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + .euiHeaderSection > .euiHeaderSectionItem { + align-items: stretch; + } + + .headerAppActionMenuSection { + flex-grow: 1; + } + + .headerAppActionMenu { + width: 100%; + } +} + .newTopNavApplicationTitle { padding: 0 $euiSizeM; } @@ -43,7 +58,7 @@ .newAppTopNavExpander { position: fixed; left: 0; - top: $euiSizeS - calc($euiSizeXS / 2); + top: $euiSizeL - $euiSizeXS; } .newPageTopNavExpander { @@ -55,7 +70,3 @@ .stretchedActionMenu { width: 100%; } - -[data-test-subj="headerAppActionMenu"] { - width: 100%; -} diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 288d7edd7ecf..3d63881e6499 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -311,10 +311,7 @@ export function Header({ ); const renderActionMenu = () => ( - + ); @@ -359,6 +356,7 @@ export function Header({ basePath={basePath} headerVariant={headerVariant} renderBreadcrumbs={renderBreadcrumbs()} + buttonSize={useApplicationHeader ? 's' : 'xs'} /> ); @@ -425,7 +423,6 @@ export function Header({ {renderCenterControls()} {renderActionMenu()} {renderRightControls()} - {renderHelp()} @@ -455,10 +452,7 @@ export function Header({ {renderRecentItems()} {renderActionMenu()} - - {renderRightControls()} - {renderHelp()} - + {renderRightControls()}
); diff --git a/src/core/public/chrome/ui/header/header_action_menu.test.tsx b/src/core/public/chrome/ui/header/header_action_menu.test.tsx index cf7c1f0f89d1..1adfbc02d6c5 100644 --- a/src/core/public/chrome/ui/header/header_action_menu.test.tsx +++ b/src/core/public/chrome/ui/header/header_action_menu.test.tsx @@ -79,7 +79,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
FOO
"` + `"
FOO
"` ); }); @@ -92,7 +92,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
FOO
"` + `"
FOO
"` ); act(() => { @@ -101,7 +101,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
"` + `"
"` ); }); @@ -114,7 +114,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
FOO
"` + `"
FOO
"` ); act(() => { @@ -123,7 +123,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
BAR
"` + `"
BAR
"` ); }); diff --git a/src/core/public/chrome/ui/header/header_action_menu.tsx b/src/core/public/chrome/ui/header/header_action_menu.tsx index f45a2ac39e1e..92ae9ffcd030 100644 --- a/src/core/public/chrome/ui/header/header_action_menu.tsx +++ b/src/core/public/chrome/ui/header/header_action_menu.tsx @@ -71,5 +71,7 @@ export const HeaderActionMenu: FC = ({ actionMenu$ }) => } }, [mounter]); - return
; + return ( +
+ ); }; diff --git a/src/core/public/chrome/ui/header/recent_items.scss b/src/core/public/chrome/ui/header/recent_items.scss index a3d9ab1fef72..f8ba65393149 100644 --- a/src/core/public/chrome/ui/header/recent_items.scss +++ b/src/core/public/chrome/ui/header/recent_items.scss @@ -7,6 +7,7 @@ margin-left: $euiSizeM; margin-right: $euiSizeS; min-width: $euiSizeL; + font-size: 0 !important; // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors .euiHeaderSectionItemButton__content, diff --git a/src/core/public/chrome/ui/header/recent_items.tsx b/src/core/public/chrome/ui/header/recent_items.tsx index ee4de6e04eca..6cd7b5c9706b 100644 --- a/src/core/public/chrome/ui/header/recent_items.tsx +++ b/src/core/public/chrome/ui/header/recent_items.tsx @@ -14,6 +14,7 @@ import { EuiIcon, EuiText, EuiSpacer, + EuiHeaderSectionItemButtonProps, } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; import { ChromeRecentlyAccessedHistoryItem, HeaderVariant } from '../..'; @@ -31,6 +32,7 @@ export interface Props { navLinks$: Rx.Observable; headerVariant?: HeaderVariant; renderBreadcrumbs: React.JSX.Element; + buttonSize?: EuiHeaderSectionItemButtonProps['size']; } export const RecentItems = ({ @@ -41,6 +43,7 @@ export const RecentItems = ({ basePath, headerVariant, renderBreadcrumbs, + buttonSize = 's', }: Props) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -76,7 +79,7 @@ export const RecentItems = ({ setIsPopoverOpen((prev) => !prev); }} data-test-subj="recentItemsSectionButton" - size="xs" + size={buttonSize} className="headerRecentItemsButton" > {/* TODO: replace this icon once there is a new icon added to OUI https://github.com/opensearch-project/OpenSearch-Dashboards/issues/7354 */} diff --git a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss index f44be6910ec6..b3bf36830ab7 100644 --- a/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss +++ b/src/plugins/data/public/ui/filter_bar/_global_filter_group.scss @@ -1,14 +1,24 @@ // SASSTODO: Probably not the right file for this selector, but temporary until the files get re-organized -.globalQueryBar { +.globalQueryBar, +.globalDatePicker { padding: 0 $euiSizeS $euiSizeS $euiSizeS; + + &:first-child { + padding-top: $euiSizeS; + } + + &:not(:empty) { + padding-bottom: $euiSizeS; + } } -.globalQueryBar:first-child { - padding-top: $euiSizeS; +.globalDatePicker { + text-align: right; } -.globalQueryBar:not(:empty) { - padding-bottom: $euiSizeS; +.headerAppActionMenu .globalQueryBar, +.headerAppActionMenu .globalDatePicker { + padding-top: 0; } .globalFilterGroup__filterBar { diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index 758c0f39b644..f2679a6f5fc1 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -387,6 +387,11 @@ export default function QueryBarTopRow(props: QueryBarTopRowProps) { 'osdQueryBar--withDatePicker': props.showDatePicker, }); + const shouldUseDatePickerRef = + props?.datePickerRef?.current && + (uiSettings.get(UI_SETTINGS.QUERY_ENHANCEMENTS_ENABLED) || + uiSettings.get('home:useNewHomePage')); + return ( <> - {props?.datePickerRef?.current && uiSettings.get(UI_SETTINGS.QUERY_ENHANCEMENTS_ENABLED) - ? createPortal(renderUpdateButton(), props.datePickerRef.current) + {shouldUseDatePickerRef + ? createPortal(renderUpdateButton(), props.datePickerRef!.current!) : renderUpdateButton()} diff --git a/src/plugins/navigation/public/index.ts b/src/plugins/navigation/public/index.ts index c409f066730e..2393505e1fed 100644 --- a/src/plugins/navigation/public/index.ts +++ b/src/plugins/navigation/public/index.ts @@ -35,7 +35,16 @@ export function plugin(initializerContext: PluginInitializerContext) { return new NavigationPublicPlugin(initializerContext); } -export { TopNavMenuData, TopNavMenu } from './top_nav_menu'; +export { + TopNavMenu, + TopNavMenuData, + TopNavMenuButtonData, + TopNavMenuSwitchData, + TopNavMenuIconData, + TopNavMenuLegacyData, + TopNavMenuItemRenderType, + TopNavControlData, +} from './top_nav_menu'; export { NavigationPublicPluginSetup, NavigationPublicPluginStart } from './types'; diff --git a/src/plugins/navigation/public/plugin.ts b/src/plugins/navigation/public/plugin.ts index 00bdb5f361d4..a626f67210d1 100644 --- a/src/plugins/navigation/public/plugin.ts +++ b/src/plugins/navigation/public/plugin.ts @@ -58,7 +58,7 @@ export class NavigationPublicPlugin return { ui: { - TopNavMenu: createTopNav(data, chrome.navGroup.getNavGroupEnabled(), extensions, i18n), + TopNavMenu: createTopNav(data, extensions, i18n, chrome.navGroup.getNavGroupEnabled()), HeaderControl: createTopNavControl(i18n), }, }; diff --git a/src/plugins/navigation/public/top_nav_menu/_index.scss b/src/plugins/navigation/public/top_nav_menu/_index.scss index 988153a1c6af..0325a6fb80ab 100644 --- a/src/plugins/navigation/public/top_nav_menu/_index.scss +++ b/src/plugins/navigation/public/top_nav_menu/_index.scss @@ -1,3 +1,67 @@ +@use "sass:map"; + .osdTopNavMenu { margin-right: $euiSizeXS; } + +.osdTopNavMenuGroupedActions { + background-color: $ouiColorEmptyShade; + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > .euiSwitch, + & > .euiButton, + & > .euiButtonIcon, + & > .euiToolTipAnchor > .euiSwitch, + & > .euiToolTipAnchor > .euiButton, + & > .euiToolTipAnchor > .euiButtonIcon { + border-radius: 0; + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > :not(:first-child), + & > .euiToolTipAnchor:not(:first-child) > .euiSwitch, + & > .euiToolTipAnchor:not(:first-child) > .euiButton, + & > .euiToolTipAnchor:not(:first-child) > .euiButtonIcon { + border-left-width: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > :not(:last-child), + & > .euiToolTipAnchor:not(:last-child) > .euiSwitch, + & > .euiToolTipAnchor:not(:last-child) > .euiButton, + & > .euiToolTipAnchor:not(:last-child) > .euiButtonIcon { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-right-color: map.get($euiSwitchColors, "text") !important; + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > .osdTopNavGroup-isDisabled:not(:last-child):has(+ .osdTopNavGroup-isDisabled), + & > .osdTopNavGroup-isDisabled.euiToolTipAnchor:not(:last-child):has(+ .osdTopNavGroup-isDisabled) > .euiButton, + & > .osdTopNavGroup-isDisabled.euiToolTipAnchor:not(:last-child):has(+ .osdTopNavGroup-isDisabled) > .euiButtonIcon { + border-right-color: $euiButtonColorDisabled !important; + } +} + +.osdTopNavGroup { + &--button { + min-width: auto; + } +} + +.osdTopNavGroup-isDisabled { + cursor: not-allowed; +} + +.osdTopNavMenuScreenTitle { + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + .euiText { + line-height: $euiFormControlCompressedHeight; + white-space: nowrap; + max-width: 18ch; + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx index 512e03d2a665..7af0bc49c0b2 100644 --- a/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx @@ -37,9 +37,9 @@ import { RegisteredTopNavMenuData } from './top_nav_menu_data'; export function createTopNav( data: DataPublicPluginStart, - navGroupEnabled: boolean, extraConfig: RegisteredTopNavMenuData[], - i18n: I18nStart + i18n: I18nStart, + groupActions?: boolean ) { return (props: TopNavMenuProps) => { const relevantConfig = extraConfig.filter( @@ -49,7 +49,7 @@ export function createTopNav( return ( - + ); }; diff --git a/src/plugins/navigation/public/top_nav_menu/index.ts b/src/plugins/navigation/public/top_nav_menu/index.ts index ee0325d268ea..7a0feb764826 100644 --- a/src/plugins/navigation/public/top_nav_menu/index.ts +++ b/src/plugins/navigation/public/top_nav_menu/index.ts @@ -29,9 +29,19 @@ */ export { createTopNav, createTopNavControl } from './create_top_nav_menu'; -export { TopNavMenu, TopNavMenuProps } from './top_nav_menu'; +export { TopNavMenu, TopNavMenuProps, TopNavMenuItemRenderType } from './top_nav_menu'; export { TopNavControls, TopNavControlsProps } from './top_nav_controls'; -export { TopNavMenuData } from './top_nav_menu_data'; +export { TopNavControlData } from './top_nav_control_data'; +export { + TopNavMenuData, + TopNavMenuButtonData, + TopNavMenuSwitchData, + TopNavMenuIconData, + TopNavMenuLegacyData, + TopNavMenuSwitchAction, + TopNavMenuClickAction, + TopNavMenuAction, +} from './top_nav_menu_data'; export { TopNavMenuExtensionsRegistrySetup, TopNavMenuExtensionsRegistry, diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx index eb61b8f14900..65b8b211b522 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx @@ -76,6 +76,7 @@ type TopNavControlIconData = TopNavControlButtonOrLinkOrIconData & { iconType: EuiIconType; ariaLabel: string; color?: EuiButtonIconProps['color']; + display?: EuiButtonIconProps['display']; run: TopNavControlAction; type: 'icon'; }; diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx index 40165f90e336..c8daad296e24 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx @@ -96,7 +96,7 @@ export function TopNavControlItem(props: TopNavControlData) { switch (props.type) { case 'icon': delete elementProps.iconSide; - component = ; + component = ; break; case 'link': diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index 1d7ec4c5bb56..e8e3489fe285 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -28,9 +28,9 @@ * under the License. */ -import { EuiFlexGroup, EuiFlexItem, EuiHeaderLinks } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiHeaderLinks, EuiText } from '@elastic/eui'; import classNames from 'classnames'; -import React, { ReactElement } from 'react'; +import React, { ReactElement, useRef } from 'react'; import { MountPoint } from '../../../../core/public'; import { @@ -43,18 +43,24 @@ import { MountPointPortal } from '../../../opensearch_dashboards_react/public'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; -export type TopNavMenuProps = StatefulSearchBarProps & - Omit & { +export enum TopNavMenuItemRenderType { + IN_PORTAL = 'in_portal', + IN_PLACE = 'in_place', + OMITTED = 'omitted', +} + +export type TopNavMenuProps = Omit & + Omit & { config?: TopNavMenuData[]; dataSourceMenuConfig?: DataSourceMenuProps; - showSearchBar?: boolean; + showSearchBar?: boolean | TopNavMenuItemRenderType; showQueryBar?: boolean; showQueryInput?: boolean; - showDatePicker?: boolean; + showDatePicker?: boolean | TopNavMenuItemRenderType; showFilterBar?: boolean; showDataSourceMenu?: boolean; data?: DataPublicPluginStart; - navGroupEnabled?: boolean; + groupActions?: boolean; className?: string; datePickerRef?: any; /** @@ -91,11 +97,16 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { const { config, showSearchBar, + showDatePicker, showDataSourceMenu, dataSourceMenuConfig, + groupActions, + screenTitle, ...searchBarProps } = props; + const datePickerRef = useRef(null); + if ( (!config || config.length === 0) && (!showSearchBar || !props.data) && @@ -104,11 +115,17 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { return null; } - function renderItems(): ReactElement[] | null { + function renderItems(): ReactElement | ReactElement[] | null { if (!config || config.length === 0) return null; - return config.map((menuItem: TopNavMenuData, i: number) => { + const renderedItems = config.map((menuItem: TopNavMenuData, i: number) => { return ; }); + + return groupActions ? ( +
{renderedItems}
+ ) : ( + renderedItems + ); } function renderMenu(className: string): ReactElement | null { @@ -128,29 +145,84 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { return ; } - function renderSearchBar(): ReactElement | null { + function renderSearchBar(overrides: Partial = {}): ReactElement | null { // Validate presence of all required fields if (!showSearchBar || !props.data) return null; const { SearchBar } = props.data.ui; - return ; + return ( + + ); } function renderLayout() { const { setMenuMountPoint } = props; const menuClassName = classNames('osdTopNavMenu', props.className); - if (setMenuMountPoint && props.navGroupEnabled) { - return ( - <> - - - {props.screenTitle ?? ''} - {renderMenu(menuClassName)} - {renderSearchBar()} - - - - ); - } else if (setMenuMountPoint) { + + if (setMenuMountPoint) { + if (groupActions) { + switch (showSearchBar) { + case TopNavMenuItemRenderType.IN_PORTAL: + return ( + <> + + + + {screenTitle} + + {renderMenu(menuClassName)} + {renderSearchBar()} + + + + ); + + case false: + case TopNavMenuItemRenderType.OMITTED: + return ( + <> + + {renderMenu(menuClassName)} + + + ); + + // Show the SearchBar in-place + default: + if (showDatePicker === TopNavMenuItemRenderType.IN_PORTAL) { + return ( + <> + + + + {screenTitle} + + {renderMenu(menuClassName)} + +
+ + + + {renderSearchBar({ datePickerRef })} + + ); + } + + return ( + <> + + {renderMenu(menuClassName)} + + {renderSearchBar()} + + ); + } + } + + // Legacy rendering behavior when setMenuMountPoint is set return ( <> @@ -159,14 +231,14 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { {renderSearchBar()} ); - } else { - return ( - <> - {renderMenu(menuClassName)} - {renderSearchBar()} - - ); } + + return ( + <> + {renderMenu(menuClassName)} + {renderSearchBar()} + + ); } return renderLayout(); @@ -180,4 +252,5 @@ TopNavMenu.defaultProps = { showFilterBar: true, showDataSourceMenu: false, screenTitle: '', + groupActions: false, }; diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx index c7a3220a896e..7f40e3ad8675 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx @@ -28,12 +28,15 @@ * under the License. */ -import { EuiButtonProps } from '@elastic/eui'; +import { EuiButtonProps, EuiButtonIconProps } from '@elastic/eui'; import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; export type TopNavMenuAction = (anchorElement: HTMLElement) => void; +export type TopNavMenuClickAction = (targetElement: HTMLElement) => void; +export type TopNavMenuSwitchAction = (targetElement: HTMLElement, checked: boolean) => void; -export interface TopNavMenuData { +// @deprecated +export interface TopNavMenuLegacyData { id?: string; label: string; run: TopNavMenuAction; @@ -50,6 +53,78 @@ export interface TopNavMenuData { type?: 'toggle' | 'button'; } -export interface RegisteredTopNavMenuData extends TopNavMenuData { - appName?: string; +type RequireAtLeastOne = Pick> & + { + [K in Keys]-?: Required> & Partial>>; + }[Keys]; + +interface TopNavMenuCommonData { + testId?: string; + className?: string; + disabled?: boolean | (() => boolean); + tooltip?: string | (() => string | undefined); } + +/* ToDo: Check with UX if this is needed +export type TopNavMenuLinkData = TopNavMenuCommonData & + RequireAtLeastOne< + { + label: string; + iconType?: EuiButtonProps['iconType']; + iconSide?: EuiButtonProps['iconSide']; + ariaLabel?: string; + isLoading?: boolean; + run?: TopNavMenuClickAction; + href?: string; + controlType: 'link'; + }, + 'href' | 'run' + >; + */ + +export type TopNavMenuButtonData = TopNavMenuCommonData & + RequireAtLeastOne< + { + label: string; + iconType?: EuiButtonProps['iconType']; + iconSide?: EuiButtonProps['iconSide']; + ariaLabel?: string; + isLoading?: boolean; + run?: TopNavMenuClickAction; + href?: string; + controlType: 'button'; + }, + 'href' | 'run' + >; + +export type TopNavMenuIconData = TopNavMenuCommonData & + RequireAtLeastOne< + { + iconType: EuiButtonIconProps['iconType']; + ariaLabel: string; + run?: TopNavMenuClickAction; + href?: string; + tooltip: string | (() => string | undefined); + controlType: 'icon'; + }, + 'href' | 'run' + >; + +export type TopNavMenuSwitchData = TopNavMenuCommonData & { + label: string; + ariaLabel?: string; + checked: boolean | (() => boolean); + run: TopNavMenuSwitchAction; + controlType: 'switch'; +}; + +export type TopNavMenuData = + | TopNavMenuLegacyData + // | TopNavMenuLinkData + | TopNavMenuButtonData + | TopNavMenuIconData + | TopNavMenuSwitchData; + +export type RegisteredTopNavMenuData = TopNavMenuData & { + appName?: string; +}; diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx index 629ae019407c..d08ad9a3f0c2 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx @@ -30,10 +30,25 @@ import { upperFirst, isFunction } from 'lodash'; import React, { MouseEvent } from 'react'; -import { EuiToolTip, EuiButton, EuiHeaderLink, EuiCompressedSwitch } from '@elastic/eui'; -import { TopNavMenuData } from './top_nav_menu_data'; +import classNames from 'classnames'; +import { + EuiToolTip, + EuiButton, + EuiHeaderLink, + EuiCompressedSwitch, + EuiButtonIcon, + EuiSwitch, + EuiSwitchEvent, +} from '@elastic/eui'; +import { + TopNavMenuClickAction, + TopNavMenuData, + TopNavMenuLegacyData, + TopNavMenuSwitchAction, + TopNavMenuSwitchData, +} from './top_nav_menu_data'; -export function TopNavMenuItem(props: TopNavMenuData) { +function TopNavMenuLegacyItem(props: TopNavMenuLegacyData) { function isDisabled(): boolean { const val = isFunction(props.disableButton) ? props.disableButton() : props.disableButton; return val!; @@ -91,6 +106,123 @@ export function TopNavMenuItem(props: TopNavMenuData) { return component; } +export function TopNavMenuItem(props: TopNavMenuData) { + if (!('controlType' in props)) return TopNavMenuLegacyItem(props); + + const { disabled, tooltip, run } = props as Exclude; + + const isDisabled = () => Boolean(typeof disabled === 'function' ? disabled() : disabled); + + const handleClick = (e: MouseEvent) => { + if (!isDisabled()) (run as TopNavMenuClickAction)?.(e.currentTarget); + }; + + const getComponent = (addTypeClassName: boolean = false) => { + const className = classNames(props.className, { + [`osdTopNavGroup--${props.controlType}`]: addTypeClassName, + 'osdTopNavGroup-isDisabled': isDisabled(), + }); + switch (props.controlType) { + case 'button': + return ( + <> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {props.label} + + + ); + + /* ToDo: Check with UX if this is needed + case 'link': + return ( + + {props.label} + + ); + */ + + case 'icon': + return ( + + ); + + case 'switch': + const { checked } = props as TopNavMenuSwitchData; + + const isChecked = () => Boolean(typeof checked === 'function' ? checked() : checked); + + const handleSwitch = (e: EuiSwitchEvent) => { + if (!isDisabled()) (run as TopNavMenuSwitchAction)?.(e.currentTarget, e.target.checked); + }; + + return ( + + ); + } + }; + + const tooltipContent = typeof tooltip === 'function' ? tooltip() : tooltip; + + if (tooltipContent) { + const className = classNames(`osdTopNavGroup--${props.controlType}`, { + 'osdTopNavGroup-isDisabled': isDisabled(), + }); + return ( + + {getComponent()} + + ); + } + + return getComponent(true); +} + TopNavMenuItem.defaultProps = { disableButton: false, tooltip: '', From 51ba337c48c3ee5ead73eeaaa3866c131c7f1265 Mon Sep 17 00:00:00 2001 From: Miki Date: Mon, 5 Aug 2024 02:58:19 -0700 Subject: [PATCH 10/35] Eliminate colors from the borders of grouped action menu items Signed-off-by: Miki --- src/plugins/navigation/public/top_nav_menu/_index.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/navigation/public/top_nav_menu/_index.scss b/src/plugins/navigation/public/top_nav_menu/_index.scss index 0325a6fb80ab..c51b38179911 100644 --- a/src/plugins/navigation/public/top_nav_menu/_index.scss +++ b/src/plugins/navigation/public/top_nav_menu/_index.scss @@ -15,6 +15,7 @@ & > .euiToolTipAnchor > .euiButton, & > .euiToolTipAnchor > .euiButtonIcon { border-radius: 0; + border: $euiFormInputGroupBorder; } // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors @@ -34,14 +35,15 @@ & > .euiToolTipAnchor:not(:last-child) > .euiButtonIcon { border-bottom-right-radius: 0; border-top-right-radius: 0; - border-right-color: map.get($euiSwitchColors, "text") !important; + + // border-right-color: map.get($euiSwitchColors, "text") !important; } // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors & > .osdTopNavGroup-isDisabled:not(:last-child):has(+ .osdTopNavGroup-isDisabled), & > .osdTopNavGroup-isDisabled.euiToolTipAnchor:not(:last-child):has(+ .osdTopNavGroup-isDisabled) > .euiButton, & > .osdTopNavGroup-isDisabled.euiToolTipAnchor:not(:last-child):has(+ .osdTopNavGroup-isDisabled) > .euiButtonIcon { - border-right-color: $euiButtonColorDisabled !important; + // border-right-color: $euiButtonColorDisabled !important; } } From d69eff88afa859bef30db4f8f308b791b0ccd43c Mon Sep 17 00:00:00 2001 From: Miki Date: Mon, 5 Aug 2024 22:10:56 -0700 Subject: [PATCH 11/35] Update TopNavControl*Data type to controlType for consistency Signed-off-by: Miki --- src/plugins/navigation/public/index.ts | 6 + .../navigation/public/top_nav_menu/index.ts | 10 +- .../top_nav_menu/top_nav_control_data.tsx | 118 +++++++++--------- .../top_nav_menu/top_nav_control_item.tsx | 80 +++++++----- 4 files changed, 123 insertions(+), 91 deletions(-) diff --git a/src/plugins/navigation/public/index.ts b/src/plugins/navigation/public/index.ts index 2393505e1fed..34534c5a4b88 100644 --- a/src/plugins/navigation/public/index.ts +++ b/src/plugins/navigation/public/index.ts @@ -44,6 +44,12 @@ export { TopNavMenuLegacyData, TopNavMenuItemRenderType, TopNavControlData, + TopNavControlButtonData, + TopNavControlLinkData, + TopNavControlIconData, + TopNavControlTextData, + TopNavControlDescriptionData, + TopNavControlComponentData, } from './top_nav_menu'; export { NavigationPublicPluginSetup, NavigationPublicPluginStart } from './types'; diff --git a/src/plugins/navigation/public/top_nav_menu/index.ts b/src/plugins/navigation/public/top_nav_menu/index.ts index 7a0feb764826..1b84f5503610 100644 --- a/src/plugins/navigation/public/top_nav_menu/index.ts +++ b/src/plugins/navigation/public/top_nav_menu/index.ts @@ -31,7 +31,15 @@ export { createTopNav, createTopNavControl } from './create_top_nav_menu'; export { TopNavMenu, TopNavMenuProps, TopNavMenuItemRenderType } from './top_nav_menu'; export { TopNavControls, TopNavControlsProps } from './top_nav_controls'; -export { TopNavControlData } from './top_nav_control_data'; +export { + TopNavControlData, + TopNavControlButtonData, + TopNavControlLinkData, + TopNavControlIconData, + TopNavControlTextData, + TopNavControlDescriptionData, + TopNavControlComponentData, +} from './top_nav_control_data'; export { TopNavMenuData, TopNavMenuButtonData, diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx index 65b8b211b522..2bba55263892 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx @@ -1,91 +1,85 @@ /* + * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Any modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you 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 { EuiButtonProps, EuiTextProps, EuiHeaderLinkProps, EuiButtonIconProps } from '@elastic/eui'; -import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -export type TopNavControlAction = (anchorElement: HTMLElement) => void; +export type TopNavControlAction = (targetElement: HTMLElement) => void; + +type RequireAtLeastOne = Pick> & + { + [K in Keys]-?: Required> & Partial>>; + }[Keys]; interface TopNavControlButtonOrLinkOrIconData { + // @deprecated id?: string; testId?: string; className?: string; - fill?: boolean; isDisabled?: boolean | (() => boolean); tooltip?: string | (() => string | undefined); ariaLabel?: string; - emphasize?: boolean; - iconSide?: EuiButtonProps['iconSide']; iconSize?: EuiButtonProps['iconSize']; - type?: 'button' | 'link' | 'icon'; } -interface TopNavControlTextData { +export type TopNavControlLinkData = TopNavControlButtonOrLinkOrIconData & + RequireAtLeastOne< + { + label: string; + isLoading?: boolean; + href?: string; + run?: TopNavControlAction; + iconType?: EuiHeaderLinkProps['iconType']; + iconSide?: EuiHeaderLinkProps['iconSide']; + color?: EuiHeaderLinkProps['color']; + controlType: 'link'; + }, + 'href' | 'run' + >; + +export type TopNavControlButtonData = TopNavControlButtonOrLinkOrIconData & + RequireAtLeastOne< + { + label: string; + isLoading?: boolean; + href?: string; + run?: TopNavControlAction; + iconType?: EuiButtonProps['iconType']; + iconSide?: EuiButtonProps['iconSide']; + color?: EuiButtonProps['color']; + fill?: EuiButtonProps['fill']; + controlType?: 'button'; + }, + 'href' | 'run' + >; + +export type TopNavControlIconData = TopNavControlButtonOrLinkOrIconData & + RequireAtLeastOne< + { + iconType: EuiButtonIconProps['iconType']; + ariaLabel: string; + href?: string; + run?: TopNavControlAction; + display?: EuiButtonIconProps['display']; + color?: EuiButtonIconProps['color']; + controlType: 'icon'; + }, + 'href' | 'run' + >; + +export interface TopNavControlTextData { text: string; className?: string; textAlign?: EuiTextProps['textAlign']; color?: EuiTextProps['color']; } -type TopNavControlLinkData = TopNavControlButtonOrLinkOrIconData & { - label: string; - isLoading?: boolean; - iconType?: EuiIconType; - href: string; - color?: EuiHeaderLinkProps['color']; - type: 'link'; -}; - -type TopNavControlButtonData = TopNavControlButtonOrLinkOrIconData & { - label: string; - isLoading?: boolean; - iconType?: EuiIconType; - run: TopNavControlAction; - color?: EuiButtonProps['color']; - type?: 'button'; -}; - -type TopNavControlIconData = TopNavControlButtonOrLinkOrIconData & { - iconType: EuiIconType; - ariaLabel: string; - color?: EuiButtonIconProps['color']; - display?: EuiButtonIconProps['display']; - run: TopNavControlAction; - type: 'icon'; -}; - -interface TopNavControlDescriptionData { +export interface TopNavControlDescriptionData { description: string; } -interface TopNavControlComponentData { +export interface TopNavControlComponentData { renderComponent: React.ReactElement; } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx index c8daad296e24..d12b71b12d11 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx @@ -69,49 +69,73 @@ export function TopNavControlItem(props: TopNavControlData) { return ''; } - function handleClick(e: MouseEvent) { + function handleClick(e: MouseEvent) { if ('run' in props && !isDisabled()) props.run?.(e.currentTarget); } - const elementProps = { - isDisabled: isDisabled(), - iconType: props.iconType, - iconSide: props.iconSide, - fill: props.fill, - 'data-test-subj': props.testId, - className: props.className, - 'aria-label': props.ariaLabel, - color: props.color, - }; - - if ('href' in props) { - // @ts-ignore - elementProps.href = props.href; - } else { - // @ts-ignore - elementProps.onClick = handleClick; - } - let component; - switch (props.type) { + switch (props.controlType) { case 'icon': - delete elementProps.iconSide; - component = ; + component = ( + + ); break; case 'link': component = ( - - {upperFirst(props.label || props.id!)} + + {upperFirst(props.label || props.id)} ); break; default: component = ( - - {upperFirst(props.label || props.id!)} - + <> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {upperFirst(props.label || props.id)} + + ); } From dcefaad483d63a7733e1612c2e94774a90fce23f Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Tue, 6 Aug 2024 09:25:46 -0700 Subject: [PATCH 12/35] Add tests for chrome Header Signed-off-by: Shenoy Pratik --- .../header/__snapshots__/header.test.tsx.snap | 13222 ++++++++++++---- .../public/chrome/ui/header/header.test.tsx | 58 +- src/core/public/chrome/ui/header/header.tsx | 6 +- 3 files changed, 9857 insertions(+), 3429 deletions(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 0517df4b6d7c..8b8d9338a326 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -2247,6 +2247,7 @@ exports[`Header handles visibility and lock changes 1`] = ` } } survey="/" + useUpdatedHeader={false} workspaceList$={ BehaviorSubject { "_isScalar": false, @@ -4320,6 +4321,7 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + useUpdatedHeader={false} > `; -exports[`Header renders condensed header 1`] = ` +exports[`Header renders application header without title and breadcrumbs 1`] = `
- -
+ - -
- -
- - - - - - - - - - , - } - } - className="euiHeaderSectionItemButton" - color="text" + aria-pressed="false" + class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton newAppTopNavExpander" data-test-subj="toggleNavButton" - onClick={[Function]} + type="button" > - - - -
-
- + + , + } + } + className="euiHeaderSectionItemButton newAppTopNavExpander" + color="text" + data-test-subj="toggleNavButton" + onClick={[Function]} > -
- -
-
- + + + + + + + + + + + + + + +
-
- + + } + workspaceList$={ + BehaviorSubject { + "_isScalar": false, + "_value": Array [], + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, }, "isStopped": false, - "syncErrorThrowable": false, + "syncErrorThrowable": true, "syncErrorThrown": false, "syncErrorValue": null, }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, + ], + "thrownError": null, + } } - } - navigateToApp={[MockFunction]} - > - - + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + initialFocus={false} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + repositionOnScroll={true} > - - + + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton headerRecentItemsButton" + color="text" + data-test-subj="recentItemsSectionButton" + onClick={[Function]} + size="s" + > + + + +
+
+ + +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+
+ +
+
+
+
+ + + + + +`; + +exports[`Header renders condensed header 1`] = ` +
+
+
+ +
+ +
+ +
+ + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton" + color="text" + data-test-subj="toggleNavButton" + onClick={[Function]} + > + + + +
+
+ +
+ +
+
+ +
+ + + + + + + + +
+ +
+
+
+
+
+
+ , + } + } + className="euiHeaderSectionItemButton header__homeLoaderNavButton" color="text" data-test-subj="homeLoader" href="/" @@ -10190,242 +12669,866 @@ exports[`Header renders condensed header 1`] = ` "_parentOrParents": [Circular], "_subscriptions": null, "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, - } - } - > - - + + + + + + + +
+ +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ - - - - - -
- -
-
- -
- -
- -
- -
- - -
- -
-
- -
- -
-
- -
- + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + repositionOnScroll={true} + > +
+
+ + + +
+
+
+
+
+
+
+
+ +
+ +
+ + + +
+
+`; + +exports[`Header renders page header with application title 1`] = ` +
+
+
+
+ +
+ + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton newPageTopNavExpander" + color="text" + data-test-subj="toggleNavButton" + onClick={[Function]} + > + + + + +
+ +
+ - + } + workspaceList$={ + BehaviorSubject { + "_isScalar": false, + "_value": Array [], + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, "syncErrorThrown": false, "syncErrorValue": null, - "toRespond": 0, - "values": Array [ - undefined, - "", - ], }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } + > + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + initialFocus={false} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + repositionOnScroll={true} + > +
+
+ + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton headerRecentItemsButton" + color="text" + data-test-subj="recentItemsSectionButton" + onClick={[Function]} + size="xs" + > + + + +
+
+
+
+
+
+
+
+ + + + + + + +
+
+ +
+ +
+ +
+ +
+

+ testTitle +

+
+
+
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ - +
+ +
+ + +
+ -
-
- - - -
-
- - - -
-
-
- -
- + } + side="right" + /> +
+
+
+
+
+ + +
+ +
+ +
+ + +
+ +
+ +
+ +
({ htmlIdGenerator: () => () => 'mockId', @@ -86,6 +87,7 @@ function mockProps() { navControlsLeftBottom$: new BehaviorSubject([]), setCurrentNavGroup: jest.fn(() => {}), workspaceList$: new BehaviorSubject([]), + useUpdatedHeader: false, }; } @@ -220,4 +222,54 @@ describe('Header', () => { component.find(EuiHeaderSectionItemButton).first().simulate('click'); expect(component).toMatchSnapshot(); }); + + it('renders page header with application title', () => { + const branding = { + useExpandedHeader: false, + }; + const useUpdatedHeader = true; + const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }, { text: 'testTitle' }]); + const props = { + ...mockProps(), + breadcrumbs$, + branding, + useUpdatedHeader, + }; + const component = mountWithIntl(
); + expect(component.find('[data-test-subj="headerApplicationTitle"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="breadcrumb first"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerBadgeControl"]').exists()).toBeTruthy(); + expect(component.find('HeaderBadge').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerLeftControl"]').exists()).toBeTruthy(); + expect(component.find('HeaderNavControls').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerCenterControl"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerRightControl"]').exists()).toBeTruthy(); + expect(component.find('HeaderActionMenu').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerDescriptionControl"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerBottomControl"]').exists()).toBeTruthy(); + expect(component).toMatchSnapshot(); + }); + + it('renders application header without title and breadcrumbs', () => { + const branding = { + useExpandedHeader: false, + }; + const useUpdatedHeader = true; + const headerVariant$ = new BehaviorSubject(HeaderVariant.APPLICATION); + const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }, { text: 'testTitle' }]); + const props = { + ...mockProps(), + breadcrumbs$, + branding, + useUpdatedHeader, + headerVariant$, + }; + const component = mountWithIntl(
); + expect(component.find('[data-test-subj="headerApplicationTitle"]').exists()).toBeFalsy(); + expect(component.find('[data-test-subj="breadcrumb first"]').exists()).toBeFalsy(); + expect(component.find('HeaderActionMenu').exists()).toBeTruthy(); + expect(component.find('RecentItems').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerRightControl"]').exists()).toBeTruthy(); + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 3d63881e6499..9bffb2f0c406 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -411,7 +411,11 @@ export function Header({ {/* Secondary header */} - + {breadcrumbs &&

{breadcrumbs[breadcrumbs.length - 1]?.text}

}
From 04335a8dc2b77470a18fdac87d2fc09997ad270c Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Tue, 6 Aug 2024 10:01:02 -0700 Subject: [PATCH 13/35] Update Breadcrumbs tests Signed-off-by: Shenoy Pratik --- .../header_breadcrumbs.test.tsx.snap | 15 +++++++++++ .../ui/header/header_breadcrumbs.test.tsx | 26 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap index 5080b23e99c2..441f43729e98 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap @@ -33,3 +33,18 @@ Array [ `; exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 3`] = `null`; + +exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable with updated header 1`] = `null`; + +exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable with updated header 2`] = ` + + First + +`; + +exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable with updated header 3`] = `null`; diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index ec82658efa21..6cc25b392ce3 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -32,8 +32,8 @@ import { mount } from 'enzyme'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; -import { HeaderBreadcrumbs } from './header_breadcrumbs'; import { ChromeBreadcrumb } from '../../chrome_service'; +import { HeaderBreadcrumbs } from './header_breadcrumbs'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { @@ -56,6 +56,30 @@ describe('HeaderBreadcrumbs', () => { expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); }); + it('renders updates to the breadcrumbs$ observable with updated header', () => { + const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); + const wrapper = mount( + + ); + expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); + expect(wrapper.find('.headerBreadcrumbs').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="breadcrumb first"]').exists()).toBeFalsy(); + + act(() => breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }])); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="breadcrumb first"]').exists()).toBeTruthy(); + + act(() => breadcrumbs$.next([])); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); + }); + it('prepend current nav group into existing breadcrumbs when nav group is enabled', () => { const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); const breadcrumbsEnricher$ = new BehaviorSubject((crumbs: ChromeBreadcrumb[]) => [ From a73791cc07a4de30acf4559b3c76b4deceee0d9a Mon Sep 17 00:00:00 2001 From: Miki Date: Tue, 6 Aug 2024 23:08:20 -0700 Subject: [PATCH 14/35] Add tests for HeaderControlsContainer Signed-off-by: Miki --- .../header/header_controls_container.test.tsx | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/core/public/chrome/ui/header/header_controls_container.test.tsx diff --git a/src/core/public/chrome/ui/header/header_controls_container.test.tsx b/src/core/public/chrome/ui/header/header_controls_container.test.tsx new file mode 100644 index 000000000000..189d84f54ae9 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_controls_container.test.tsx @@ -0,0 +1,131 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { BehaviorSubject } from 'rxjs'; +import { MountPoint, UnmountCallback } from '../../../types'; +import { HeaderControlsContainer } from './header_controls_container'; + +type MockedUnmount = jest.MockedFunction; + +describe('HeaderControlsContainer', () => { + let component: ReactWrapper; + let controlMount$: BehaviorSubject; + let unmounts: Record; + + beforeEach(() => { + controlMount$ = new BehaviorSubject(undefined); + unmounts = {}; + }); + + const refresh = () => { + new Promise(async (resolve) => { + if (component) { + act(() => { + component.update(); + }); + } + setImmediate(() => resolve(component)); // flushes any pending promises + }); + }; + + const createMountPoint = (id: string, content: string = id): MountPoint => ( + root + ): MockedUnmount => { + const container = document.createElement('DIV'); + // eslint-disable-next-line no-unsanitized/property + container.innerHTML = content; + root.appendChild(container); + const unmount = jest.fn(() => container.remove()); + unmounts[id] = unmount; + return unmount; + }; + + it('mounts the current value of the provided observable', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + }); + + it('clears the content of the component when emitting undefined', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + controlMount$.next(undefined); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
"` + ); + }); + + it('updates the dom when a new mount point is emitted', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + controlMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
BAR
"` + ); + }); + + it('calls the previous mount point `unmount` when mounting a new mount point', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO']); + expect(unmounts.FOO).not.toHaveBeenCalled(); + + act(() => { + controlMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO', 'BAR']); + expect(unmounts.FOO).toHaveBeenCalledTimes(1); + expect(unmounts.BAR).not.toHaveBeenCalled(); + }); +}); From 7a12fe688e358d179072f670df4be3c951b44cfe Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Tue, 6 Aug 2024 11:19:15 -0700 Subject: [PATCH 15/35] Add tests for TopNavControls and TopNavControlItem Signed-off-by: Shenoy Pratik --- .../top_nav_controls.test.tsx.snap | 122 ++++++++++++++++++ .../top_nav_control_item.test.tsx | 108 ++++++++++++++++ .../top_nav_menu/top_nav_controls.test.tsx | 67 ++++++++++ 3 files changed, 297 insertions(+) create mode 100644 src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_controls.test.tsx.snap create mode 100644 src/plugins/navigation/public/top_nav_menu/top_nav_control_item.test.tsx create mode 100644 src/plugins/navigation/public/top_nav_menu/top_nav_controls.test.tsx diff --git a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_controls.test.tsx.snap b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_controls.test.tsx.snap new file mode 100644 index 000000000000..1c62a3c148dc --- /dev/null +++ b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_controls.test.tsx.snap @@ -0,0 +1,122 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TopNavControls renders TopNavControlItems when controls are provided 1`] = ` + +`; diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.test.tsx new file mode 100644 index 000000000000..06266170d158 --- /dev/null +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.test.tsx @@ -0,0 +1,108 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { EuiButton, EuiButtonIcon, EuiHeaderLink, EuiText, EuiToolTip } from '@elastic/eui'; +import { ShallowWrapper } from 'enzyme'; +import React from 'react'; +import { shallowWithIntl } from '../../../../test_utils/public/enzyme_helpers'; +import { TopNavControlData } from './top_nav_control_data'; +import { TopNavControlItem } from './top_nav_control_item'; + +// Mock props for different scenarios +const buttonProps: TopNavControlData = { + controlType: 'button', + label: 'Button', + run: jest.fn(), +}; + +const linkProps: TopNavControlData = { + controlType: 'link', + label: 'Link', + href: 'http://example.com', +}; + +const iconProps: TopNavControlData = { + controlType: 'icon', + iconType: 'user', + ariaLabel: 'Icon', + run: jest.fn(), +}; + +const textProps: TopNavControlData = { + text: 'Text Content', +}; + +const descriptionProps: TopNavControlData = { + description: 'Description Content', +}; + +const componentProps: TopNavControlData = { + renderComponent:
Custom Component
, +}; + +describe('TopNavControlItem', () => { + it('renders a button control', () => { + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.find(EuiButton)).toHaveLength(1); + expect(wrapper.find(EuiButton).prop('onClick')).toBeDefined(); + }); + + it('renders a link control', () => { + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.find(EuiHeaderLink)).toHaveLength(1); + expect(wrapper.find(EuiHeaderLink).prop('href')).toEqual(linkProps.href); + }); + + it('renders an icon control', () => { + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.find(EuiButtonIcon)).toHaveLength(1); + expect(wrapper.find(EuiButtonIcon).prop('iconType')).toEqual(iconProps.iconType); + }); + + it('renders text content', () => { + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.find(EuiText)).toHaveLength(1); + expect(wrapper.find(EuiText).children().text()).toEqual(textProps.text); + }); + + it('renders description content', () => { + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.find(EuiText)).toHaveLength(1); + expect(wrapper.find(EuiText).children().text()).toEqual(descriptionProps.description); + }); + + it('renders a custom component', () => { + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.contains(componentProps.renderComponent)).toBe(true); + }); + + it('handles disabled state correctly', () => { + const disabledProps = { ...buttonProps, isDisabled: true }; + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); + }); + + it('handles tooltip correctly', () => { + const tooltipProps = { ...buttonProps, tooltip: 'Tooltip text' }; + const wrapper: ShallowWrapper = shallowWithIntl(); + expect(wrapper.find(EuiToolTip)).toHaveLength(1); + expect(wrapper.find(EuiToolTip).prop('content')).toEqual('Tooltip text'); + }); + + it('calls run function on button click', () => { + const mockEvent = { currentTarget: document.createElement('button') } as React.MouseEvent< + HTMLButtonElement + >; + const wrapper: ShallowWrapper = shallowWithIntl(); + wrapper.find(EuiButton).simulate('click', mockEvent); + expect(buttonProps.run).toHaveBeenCalledWith(mockEvent.currentTarget); + }); +}); diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_controls.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_controls.test.tsx new file mode 100644 index 000000000000..34419f6229af --- /dev/null +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_controls.test.tsx @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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 React from 'react'; +import { mountWithIntl } from '../../../../test_utils/public/enzyme_helpers'; +import { MountPointPortal } from '../../../opensearch_dashboards_react/public'; +import { TopNavControlData } from './top_nav_control_data'; +import { TopNavControls, TopNavControlsProps } from './top_nav_controls'; + +// Mock props for different scenarios +const controls: TopNavControlData[] = [ + { controlType: 'button', label: 'Button', run: jest.fn() }, + { controlType: 'link', label: 'Link', href: 'http://example.com' }, +]; + +describe('TopNavControls', () => { + it('renders null when controls is not provided', () => { + const props: TopNavControlsProps = {}; + const wrapper = mountWithIntl(); + expect(wrapper.isEmptyRender()).toBe(true); + }); + + it('renders null when controls is an empty array', () => { + const props: TopNavControlsProps = { controls: [] }; + const wrapper = mountWithIntl(); + expect(wrapper.isEmptyRender()).toBe(true); + }); + + it('renders TopNavControlItems when controls are provided', () => { + const props: TopNavControlsProps = { controls }; + const wrapper = mountWithIntl(); + expect(wrapper).toMatchSnapshot(); + }); + + it('renders MountPointPortal when setMountPoint is provided', () => { + const setMountPoint = jest.fn(); + const props: TopNavControlsProps = { controls, setMountPoint }; + const wrapper = mountWithIntl(); + expect(wrapper.find(MountPointPortal)).toHaveLength(1); + }); +}); From 67e28861d8e755740e35443d10011f75a20fa850 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Tue, 6 Aug 2024 12:22:37 -0700 Subject: [PATCH 16/35] Updated tests for TopNavMenu and TopNavMenuItem Signed-off-by: Shenoy Pratik --- .../public/top_nav_menu/top_nav_menu.test.tsx | 105 +++++++++++++++++- .../top_nav_menu/top_nav_menu_item.test.tsx | 60 +++++++++- 2 files changed, 162 insertions(+), 3 deletions(-) diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 99644e49bde0..6e9b4fc1810f 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -32,9 +32,10 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { MountPoint } from 'opensearch-dashboards/public'; -import { TopNavMenu } from './top_nav_menu'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { TopNavMenu, TopNavMenuItemRenderType } from './top_nav_menu'; import { TopNavMenuData } from './top_nav_menu_data'; -import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; +import { applicationServiceMock, uiSettingsServiceMock } from '../../../../core/public/mocks'; import * as testUtils from '../../../data_source_management/public/components/utils'; import { DataSourceSelectionService } from '../../../data_source_management/public/service/data_source_selection_service'; @@ -222,5 +223,105 @@ describe('TopNavMenu', () => { // menu is rendered outside of the component expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); }); + + it('mounts the data source menu with group actions enabled', async () => { + spyOn(testUtils, 'getApplication').and.returnValue(applicationServiceMock); + spyOn(testUtils, 'getUiSettings').and.returnValue( + uiSettingsServiceMock.createStartContract() + ); + spyOn(testUtils, 'getHideLocalCluster').and.returnValue(true); + spyOn(testUtils, 'getDataSourceSelection').and.returnValue(dataSourceSelection); + + const component = mountWithIntl( + + ); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + expect(component.find('.osdTopNavMenuScreenTitle').exists()).toBeFalsy(); + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(0); + }); + + it('mounts without data source menu with group actions enabled and showSearchBar in portal', async () => { + spyOn(testUtils, 'getApplication').and.returnValue(applicationServiceMock); + spyOn(testUtils, 'getUiSettings').and.returnValue( + uiSettingsServiceMock.createStartContract() + ); + spyOn(testUtils, 'getHideLocalCluster').and.returnValue(false); + spyOn(testUtils, 'getDataSourceSelection').and.returnValue(dataSourceSelection); + + const component = mountWithIntl( + + ); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + await (() => { + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); + expect(component.find('.osdTopNavMenuScreenTitle').exists()).toBeTruthy(); + }); + }); + + it('mounts without data source menu with group actions enabled and showSearchBar in place', async () => { + spyOn(testUtils, 'getApplication').and.returnValue(applicationServiceMock); + spyOn(testUtils, 'getUiSettings').and.returnValue( + uiSettingsServiceMock.createStartContract() + ); + spyOn(testUtils, 'getHideLocalCluster').and.returnValue(false); + spyOn(testUtils, 'getDataSourceSelection').and.returnValue(dataSourceSelection); + + const component = mountWithIntl( + + ); + + act(() => { + mountPoint(portalTarget); + }); + + await refresh(); + + await (() => { + expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); + expect(component.find('.osdTopNavMenuScreenTitle').exists()).toBeTruthy(); + expect(component.find('.globalDatePicker').exists()).toBeTruthy(); + }); + }); }); }); diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx index 7e759d2a6c09..7dba80698893 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx @@ -28,9 +28,15 @@ * under the License. */ +import { EuiButton, EuiButtonIcon, EuiSwitch, EuiToolTip } from '@elastic/eui'; import React from 'react'; import { TopNavMenuItem } from './top_nav_menu_item'; -import { TopNavMenuData } from './top_nav_menu_data'; +import { + TopNavMenuData, + TopNavMenuButtonData, + TopNavMenuIconData, + TopNavMenuSwitchData, +} from './top_nav_menu_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; describe('TopNavMenu', () => { @@ -138,4 +144,56 @@ describe('TopNavMenu', () => { run: jest.fn(), }); }); + + const defaultProps = { + label: 'Test Label', + run: jest.fn(), + testId: 'test-id', + }; + + it('Should render a button with tooltip', () => { + const props = { + ...defaultProps, + controlType: 'button', + tooltip: 'Test Tooltip', + } as TopNavMenuButtonData; + const wrapper = shallowWithIntl(); + expect(wrapper.find(EuiToolTip).length).toBe(1); + expect(wrapper.find(EuiButton).length).toBe(1); + }); + + it('Should render an icon button', () => { + const props = { + ...defaultProps, + controlType: 'icon', + iconType: 'alert', + tooltip: 'Test Tooltip', + ariaLabel: 'Test', + } as TopNavMenuIconData; + const wrapper = shallowWithIntl(); + expect(wrapper.find(EuiButtonIcon).length).toBe(1); + }); + + it('Should render a switch', () => { + const props = { ...defaultProps, controlType: 'switch', checked: true } as TopNavMenuSwitchData; + const wrapper = shallowWithIntl(); + expect(wrapper.find(EuiSwitch).length).toBe(1); + }); + + it('Should handles button click', () => { + const props = { ...defaultProps, controlType: 'button' } as TopNavMenuButtonData; + const wrapper = shallowWithIntl(); + wrapper.find(EuiButton).simulate('click', { currentTarget: {} }); + expect(props.run).toHaveBeenCalled(); + }); + + it('Should disable the button when disabled is true', () => { + const props = { + ...defaultProps, + controlType: 'button', + disabled: true, + } as TopNavMenuButtonData; + const wrapper = shallowWithIntl(); + expect(wrapper.find(EuiButton).props().isDisabled).toBe(true); + }); }); From e7531bf753d5113971063cc5d88d2e759a4fa715 Mon Sep 17 00:00:00 2001 From: Miki Date: Tue, 6 Aug 2024 14:21:57 -0700 Subject: [PATCH 17/35] Fix `uiSettingsServiceMock` missing `start` Signed-off-by: Miki --- src/core/public/ui_settings/ui_settings_service.mock.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/public/ui_settings/ui_settings_service.mock.ts b/src/core/public/ui_settings/ui_settings_service.mock.ts index 8458c86d6774..de9477ed0e08 100644 --- a/src/core/public/ui_settings/ui_settings_service.mock.ts +++ b/src/core/public/ui_settings/ui_settings_service.mock.ts @@ -67,6 +67,8 @@ const createMock = () => { }; mocked.setup.mockReturnValue(createSetupContractMock()); + // UiSettings.start returns the client that is returned by setup + mocked.start.mockReturnValue(createSetupContractMock()); return mocked; }; From 8268975f0d5dfbabdf61d8cd983fb0ae128cedfe Mon Sep 17 00:00:00 2001 From: Miki Date: Tue, 6 Aug 2024 14:39:22 -0700 Subject: [PATCH 18/35] Add the `target` property to TopNavControlItem Signed-off-by: Miki --- .../navigation/public/top_nav_menu/top_nav_control_data.tsx | 1 + .../navigation/public/top_nav_menu/top_nav_control_item.tsx | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx index 2bba55263892..83fa46e69508 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_control_data.tsx @@ -20,6 +20,7 @@ interface TopNavControlButtonOrLinkOrIconData { isDisabled?: boolean | (() => boolean); tooltip?: string | (() => string | undefined); ariaLabel?: string; + target?: '_blank'; iconSize?: EuiButtonProps['iconSize']; } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx index d12b71b12d11..ada02dba27bf 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_control_item.tsx @@ -84,6 +84,7 @@ export function TopNavControlItem(props: TopNavControlData) { color={props.color} className={props.className} href={props.href} + target={props.target} isDisabled={isDisabled()} onClick={handleClick} aria-label={props.ariaLabel} @@ -103,6 +104,7 @@ export function TopNavControlItem(props: TopNavControlData) { color={props.color} className={props.className} href={props.href} + target={props.target} isDisabled={isDisabled()} onClick={handleClick} aria-label={props.ariaLabel} @@ -127,6 +129,7 @@ export function TopNavControlItem(props: TopNavControlData) { fill={props.fill} className={props.className} href={props.href} + target={props.target} isDisabled={isDisabled()} onClick={handleClick} aria-label={props.ariaLabel} From f070ce7668b6eaefffe5c39b3d470b563d798dc6 Mon Sep 17 00:00:00 2001 From: Miki Date: Tue, 6 Aug 2024 14:58:29 -0700 Subject: [PATCH 19/35] Update Navigation mock and start contract Signed-off-by: Miki --- .../components/dashboard_top_nav/dashboard_top_nav.test.tsx | 4 ++-- src/plugins/navigation/public/index.ts | 1 + src/plugins/navigation/public/mocks.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.test.tsx b/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.test.tsx index 3dafdf447d45..d18abd28cf2c 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.test.tsx +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.test.tsx @@ -33,7 +33,7 @@ import { Dashboard } from '../../../dashboard'; import { DashboardContainer } from '../../embeddable'; import { createDashboardServicesMock } from '../../utils/mocks'; import { mount } from 'enzyme'; -import { TopNavMenu } from 'src/plugins/navigation/public'; +import { TopNavMenu, TopNavControls as HeaderControl } from 'src/plugins/navigation/public'; import { dashboardAppStateStub } from '../../utils/stubs'; let mockURL = '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))'; @@ -55,7 +55,7 @@ function wrapDashboardTopNavInContext(mockServices: any, currentState: Dashboard saveQuery: true, }, navigation: { - ui: { TopNavMenu }, + ui: { TopNavMenu, HeaderControl }, }, }; diff --git a/src/plugins/navigation/public/index.ts b/src/plugins/navigation/public/index.ts index 34534c5a4b88..fe89f39c44df 100644 --- a/src/plugins/navigation/public/index.ts +++ b/src/plugins/navigation/public/index.ts @@ -43,6 +43,7 @@ export { TopNavMenuIconData, TopNavMenuLegacyData, TopNavMenuItemRenderType, + TopNavControls, TopNavControlData, TopNavControlButtonData, TopNavControlLinkData, diff --git a/src/plugins/navigation/public/mocks.ts b/src/plugins/navigation/public/mocks.ts index 3c80117b9e7e..24347f988b55 100644 --- a/src/plugins/navigation/public/mocks.ts +++ b/src/plugins/navigation/public/mocks.ts @@ -45,6 +45,7 @@ const createStartContract = (): jest.Mocked => { const startContract = { ui: { TopNavMenu: jest.fn(), + HeaderControl: jest.fn(), }, }; return startContract; From 7bf25f973d9658b8d03f9366b3b3dd825e988446 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Tue, 6 Aug 2024 16:48:41 -0700 Subject: [PATCH 20/35] Add createGetterSetter mock in dashboards app state Signed-off-by: Shenoy Pratik --- .../public/application/utils/create_dashboard_app_state.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/dashboard/public/application/utils/create_dashboard_app_state.test.tsx b/src/plugins/dashboard/public/application/utils/create_dashboard_app_state.test.tsx index 8f39da02e90b..1c57e1087be0 100644 --- a/src/plugins/dashboard/public/application/utils/create_dashboard_app_state.test.tsx +++ b/src/plugins/dashboard/public/application/utils/create_dashboard_app_state.test.tsx @@ -23,6 +23,7 @@ const mockStopStateSync = jest.fn(); const mockStopQueryStateSync = jest.fn(); jest.mock('../../../../opensearch_dashboards_utils/public', () => ({ + createGetterSetter: jest.fn(() => []), createStateContainer: jest.fn(() => 'stateContainer'), syncState: jest.fn(() => ({ start: mockStartStateSync, From ddb7276011f08b55e10955dca667455f26f1affc Mon Sep 17 00:00:00 2001 From: Miki Date: Tue, 6 Aug 2024 22:32:04 -0700 Subject: [PATCH 21/35] Add tests for setting and unsetting header variant Signed-off-by: Miki --- src/core/public/chrome/chrome_service.test.ts | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 09ecbaedb55b..082ffbfa16ed 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -42,12 +42,17 @@ import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { ChromeService } from './chrome_service'; import { getAppInfo } from '../application/utils'; import { overlayServiceMock, workspacesServiceMock } from '../mocks'; +import { HeaderVariant } from './constants'; class FakeApp implements App { public title: string; public mount = () => () => {}; - constructor(public id: string, public chromeless?: boolean) { + constructor( + public id: string, + public chromeless?: boolean, + public headerVariant?: HeaderVariant + ) { this.title = `${this.id} App`; } } @@ -282,6 +287,68 @@ describe('start', () => { }); }); + describe('header variant', () => { + it('emits undefined when no application is mounted', async () => { + const { chrome, service } = await start(); + const promise = chrome.getHeaderVariant$().pipe(toArray()).toPromise(); + + chrome.setHeaderVariant(HeaderVariant.PAGE); + chrome.setHeaderVariant(HeaderVariant.APPLICATION); + chrome.setHeaderVariant(HeaderVariant.PAGE); + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(`Array []`); + }); + + it('emits application-wide value until manually overridden', async () => { + const startDeps = defaultStartDeps([ + new FakeApp('alpha', undefined, HeaderVariant.APPLICATION), + ]); + const { navigateToApp } = startDeps.application; + const { chrome, service } = await start({ startDeps }); + + const promise = chrome.getHeaderVariant$().pipe(toArray()).toPromise(); + + await navigateToApp('alpha'); + + chrome.setHeaderVariant(HeaderVariant.PAGE); + chrome.setHeaderVariant(HeaderVariant.APPLICATION); + + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(` + Array [ + "${HeaderVariant.APPLICATION}", + "${HeaderVariant.PAGE}", + "${HeaderVariant.APPLICATION}", + ] + `); + }); + + it('emits application-wide value after override is removed', async () => { + const startDeps = defaultStartDeps([new FakeApp('alpha', undefined, HeaderVariant.PAGE)]); + const { navigateToApp } = startDeps.application; + const { chrome, service } = await start({ startDeps }); + + const promise = chrome.getHeaderVariant$().pipe(toArray()).toPromise(); + + await navigateToApp('alpha'); + + chrome.setHeaderVariant(HeaderVariant.APPLICATION); + chrome.setHeaderVariant(); + + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(` + Array [ + "${HeaderVariant.PAGE}", + "${HeaderVariant.APPLICATION}", + "${HeaderVariant.PAGE}", + ] + `); + }); + }); + describe('application classes', () => { it('updates/emits the application classes', async () => { const { chrome, service } = await start(); From 93a344308a3d3bc7d1c40fe55033e02197b5411c Mon Sep 17 00:00:00 2001 From: Miki Date: Tue, 6 Aug 2024 22:35:26 -0700 Subject: [PATCH 22/35] Add tests for setting header controls Signed-off-by: Miki --- .../application/application_service.test.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index 52786446ad55..a614e39205c9 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -35,7 +35,7 @@ import { } from './application_service.test.mocks'; import { createElement } from 'react'; -import { BehaviorSubject, Subject } from 'rxjs'; +import { BehaviorSubject, Subject, Observable } from 'rxjs'; import { bufferCount, take, takeUntil } from 'rxjs/operators'; import { shallow, mount } from 'enzyme'; @@ -51,7 +51,9 @@ import { AppStatus, AppUpdater, WorkspaceAvailability, + InternalApplicationStart, } from './types'; +import { MountPoint } from '../types'; import { act } from 'react-dom/test-utils'; import { workspacesServiceMock } from '../mocks'; @@ -937,6 +939,34 @@ describe('#start()', () => { expect(setupDeps.redirectTo).not.toHaveBeenCalled(); }); }); + + describe('AppControls', () => { + test.each(['Left', 'Center', 'Right', 'Badge', 'Description', 'Bottom'])( + 'records the App%sControls', + async (container) => { + const { register } = service.setup(setupDeps); + + register(Symbol(), createApp({ id: `app${container}` })); + const appStart = await service.start(startDeps); + const setControls = appStart[ + `setApp${container}Controls` as keyof InternalApplicationStart + ] as (mount: MountPoint | undefined) => void; + const currentControls$ = appStart[ + `current${container}Controls$` as keyof InternalApplicationStart + ] as Observable; + + const oldMountPoint = jest.fn(); + const expectedMountPoint = jest.fn(); + + await appStart.navigateToApp(`app${container}`); + setControls(oldMountPoint); + setControls(expectedMountPoint); + + const mountPoint = await currentControls$.pipe(take(1)).toPromise(); + expect(mountPoint).toBe(expectedMountPoint); + } + ); + }); }); describe('#stop()', () => { From 272a85efa9a26a6d239e7f1bf2fbb544e7c30547 Mon Sep 17 00:00:00 2001 From: Miki Date: Fri, 2 Aug 2024 22:05:05 -0700 Subject: [PATCH 23/35] Re-skin DataSource selection's trigger button Signed-off-by: Miki --- .../public/components/button_title.scss | 7 - .../data_source_aggregated_view.test.tsx.snap | 203 ++++++++++++++++++ .../data_source_aggregated_view.tsx | 1 + .../data_source_error_menu.test.tsx.snap | 6 + .../create_data_source_menu.test.tsx.snap | 123 ++++++----- .../data_source_menu.test.tsx.snap | 76 ++++--- .../data_source_selectable.test.tsx.snap | 82 ++++--- .../data_source_selectable.tsx | 4 +- .../data_source_selector.tsx | 2 +- .../data_source_view.test.tsx.snap | 172 ++++----------- .../data_source_view/data_source_view.tsx | 1 + .../popover_button/popover_button.scss | 66 ++++++ .../popover_button/popover_button.test.tsx | 15 +- .../popover_button/popover_button.tsx | 23 +- 14 files changed, 523 insertions(+), 258 deletions(-) delete mode 100644 src/plugins/data_source_management/public/components/button_title.scss create mode 100644 src/plugins/data_source_management/public/components/popover_button/popover_button.scss diff --git a/src/plugins/data_source_management/public/components/button_title.scss b/src/plugins/data_source_management/public/components/button_title.scss deleted file mode 100644 index 66b32d4ee8b7..000000000000 --- a/src/plugins/data_source_management/public/components/button_title.scss +++ /dev/null @@ -1,7 +0,0 @@ -.dataSourceComponentButtonTitle { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: auto; - max-width: 16ch; -} diff --git a/src/plugins/data_source_management/public/components/data_source_aggregated_view/__snapshots__/data_source_aggregated_view.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_aggregated_view/__snapshots__/data_source_aggregated_view.test.tsx.snap index d9084c4c76ec..721c02526cab 100644 --- a/src/plugins/data_source_management/public/components/data_source_aggregated_view/__snapshots__/data_source_aggregated_view.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_aggregated_view/__snapshots__/data_source_aggregated_view.test.tsx.snap @@ -7,6 +7,7 @@ exports[`DataSourceAggregatedView empty state test due to filter out with local button={ } @@ -51,6 +52,12 @@ exports[`DataSourceAggregatedView empty state test due to filter out with local "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -94,6 +101,7 @@ exports[`DataSourceAggregatedView empty state test due to filter out with local button={ } @@ -139,6 +147,12 @@ exports[`DataSourceAggregatedView empty state test due to filter out with local "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -197,6 +211,7 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho button={ } @@ -241,6 +256,12 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -284,6 +305,7 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho button={ } @@ -328,6 +350,12 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -371,6 +399,7 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho button={ } @@ -416,6 +445,12 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -474,6 +509,7 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho button={ } @@ -519,6 +555,12 @@ exports[`DataSourceAggregatedView empty state test with local cluster hiding sho "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -577,6 +619,7 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster button={ } @@ -621,6 +664,12 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -664,6 +713,7 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster button={ } @@ -708,6 +758,12 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -751,6 +807,7 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster button={ } @@ -796,6 +853,12 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -854,6 +917,7 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster button={ } @@ -899,6 +963,12 @@ exports[`DataSourceAggregatedView error state test no matter hide local cluster "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -957,6 +1027,7 @@ exports[`DataSourceAggregatedView: dataSourceSelection) should render normally a button={ } @@ -1002,6 +1073,12 @@ exports[`DataSourceAggregatedView: dataSourceSelection) should render normally a "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -1060,6 +1137,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1105,6 +1183,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={3} @@ -1184,6 +1268,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1229,6 +1314,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={4} @@ -1314,6 +1405,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1359,6 +1451,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={2} @@ -1432,6 +1530,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1477,6 +1576,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={3} @@ -1556,6 +1661,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1601,6 +1707,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={3} @@ -1680,6 +1792,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1725,6 +1838,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={4} @@ -1810,6 +1929,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1855,6 +1975,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={2} @@ -1928,6 +2054,7 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou button={ } @@ -1973,6 +2100,12 @@ exports[`DataSourceAggregatedView: read active view (displayAllCompatibleDataSou "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={3} @@ -2052,6 +2185,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2096,6 +2230,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2139,6 +2279,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2183,6 +2324,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2226,6 +2373,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2270,6 +2418,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2313,6 +2467,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2357,6 +2512,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2400,6 +2561,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2444,6 +2606,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2487,6 +2655,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2531,6 +2700,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2574,6 +2749,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2618,6 +2794,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2661,6 +2843,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2705,6 +2888,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2748,6 +2937,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2792,6 +2982,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} @@ -2835,6 +3031,7 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource button={ } @@ -2879,6 +3076,12 @@ exports[`DataSourceAggregatedView: read all view (displayAllCompatibleDataSource "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} diff --git a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx index 46321dd5a6db..2b9363475d37 100644 --- a/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx +++ b/src/plugins/data_source_management/public/components/data_source_aggregated_view/data_source_aggregated_view.tsx @@ -224,6 +224,7 @@ export class DataSourceAggregatedView extends React.Component< } isOpen={this.state.isPopoverOpen} diff --git a/src/plugins/data_source_management/public/components/data_source_error_menu/__snapshots__/data_source_error_menu.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_error_menu/__snapshots__/data_source_error_menu.test.tsx.snap index f1f00f7c0ca8..5ac94541c153 100644 --- a/src/plugins/data_source_management/public/components/data_source_error_menu/__snapshots__/data_source_error_menu.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_error_menu/__snapshots__/data_source_error_menu.test.tsx.snap @@ -56,6 +56,12 @@ exports[`DataSourceErrorMenu renders without crashing 1`] = ` "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } totalDataSourceCount={0} diff --git a/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap index 01cf1a6a6456..37bcb8daa4f2 100644 --- a/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/create_data_source_menu.test.tsx.snap @@ -122,25 +122,34 @@ Object {
- + class="euiButtonContent euiButton__content" + > + + + + + +
@@ -154,25 +163,34 @@ Object {
- + class="euiButtonContent euiButton__content" + > + + + + + +
, @@ -251,25 +269,34 @@ Object {
- + class="euiButtonContent euiButton__content" + > + + + + + +
diff --git a/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/data_source_menu.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/data_source_menu.test.tsx.snap index 4dc6ce29141a..19ace22584f7 100644 --- a/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/data_source_menu.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_menu/__snapshots__/data_source_menu.test.tsx.snap @@ -75,25 +75,31 @@ Object {
- + class="euiButtonContent euiButton__content" + > + + + + +
@@ -106,25 +112,31 @@ Object {
- + class="euiButtonContent euiButton__content" + > + + + + +
, diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap index 443ec2faa18c..046869724c8c 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_selectable/__snapshots__/data_source_selectable.test.tsx.snap @@ -209,27 +209,36 @@ Object {
- + + +
@@ -380,27 +389,36 @@ Object {
- + + +
, diff --git a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx index 3b679c2c047b..8ba9867a898e 100644 --- a/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selectable/data_source_selectable.tsx @@ -11,6 +11,7 @@ import { EuiPanel, EuiSelectable, EuiPopoverTitle, + EuiSelectableOption, } from '@elastic/eui'; import { ApplicationStart, @@ -37,7 +38,6 @@ import { DataSourceItem } from '../data_source_item'; import { NoDataSource } from '../no_data_source'; import './data_source_selectable.scss'; import { DataSourceDropDownHeader } from '../drop_down_header'; -import '../button_title.scss'; import './data_source_selectable.scss'; import { DataSourceMenuPopoverButton } from '../popover_button/popover_button'; @@ -311,7 +311,7 @@ export class DataSourceSelectable extends React.Component< listProps={{ onFocusBadge: false, }} - options={this.state.dataSourceOptions} + options={this.state.dataSourceOptions as Array>} onChange={(newOptions) => this.onChange(newOptions)} singleSelection={'always'} data-test-subj={'dataSourceSelectable'} diff --git a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx index c2d2fe94d992..dbec565dc7b5 100644 --- a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx @@ -250,7 +250,7 @@ export class DataSourceSelector extends React.Component< isDisabled={this.props.disabled} fullWidth={this.props.fullWidth || false} data-test-subj={'dataSourceSelectorComboBox'} - renderOption={(option) => ( + renderOption={(option: EuiComboBoxOptionOption) => ( @@ -60,6 +61,7 @@ exports[`DataSourceView Should return error when provided datasource has been fi button={ @@ -115,6 +117,7 @@ exports[`DataSourceView When selected option is local cluster and hide local Clu button={ @@ -161,6 +164,7 @@ exports[`DataSourceView should call getDataSourceById when only pass id with no button={ @@ -217,6 +221,7 @@ exports[`DataSourceView should render normally with local cluster not hidden 1`] button={ @@ -277,122 +282,31 @@ Object { >
- -
- - -
-
- ); }; From 6a7d95138d040eb32c54c4ea9e0fc852e9701f1d Mon Sep 17 00:00:00 2001 From: Miki Date: Mon, 5 Aug 2024 03:22:19 -0700 Subject: [PATCH 24/35] Conditionally change where theme management menu item shows up Signed-off-by: Miki --- .../public/header_user_theme_menu.tsx | 48 ++++++++++++------- .../advanced_settings/public/plugin.ts | 2 +- .../public/register_nav_control.tsx | 4 +- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/plugins/advanced_settings/public/header_user_theme_menu.tsx b/src/plugins/advanced_settings/public/header_user_theme_menu.tsx index 0513016eaa9c..fc297aaa74bc 100644 --- a/src/plugins/advanced_settings/public/header_user_theme_menu.tsx +++ b/src/plugins/advanced_settings/public/header_user_theme_menu.tsx @@ -8,7 +8,6 @@ import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; import { EuiSmallButton, - EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiCompressedFormRow, @@ -18,18 +17,15 @@ import { EuiPopover, EuiPopoverTitle, EuiCompressedSelect, - EuiSpacer, EuiToolTip, + EuiButtonIcon, } from '@elastic/eui'; import { CoreStart } from 'opensearch-dashboards/public'; import { useOpenSearchDashboards, useUiSetting$ } from '../../opensearch_dashboards_react/public'; export const HeaderUserThemeMenu = () => { const { - services: { - http: { basePath }, - uiSettings, - }, + services: { uiSettings }, } = useOpenSearchDashboards(); // TODO: move to central location? const themeOptions = [ @@ -74,6 +70,8 @@ export const HeaderUserThemeMenu = () => { const defaultTheme = allSettings['theme:version'].value; const defaultScreenMode = allSettings['theme:darkMode'].value; + const legacyAppearance = !uiSettings.get('home:useNewHomePage'); + const onButtonClick = () => { setPopover(!isPopoverOpen); }; @@ -111,6 +109,31 @@ export const HeaderUserThemeMenu = () => { setPopover(false); }; + const innerButton = legacyAppearance ? ( + + ) : ( +