diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index d281b987ee37..6d8f26da7c02 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -150,3 +150,7 @@ # 'ff00::/8', # ] #vis_type_timeline.graphiteBlockedIPs: [] + +# user input URL for customized logo +# opensearchDashboards.branding.logoUrl: "" + diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 93747ee2501e..68e4f8318598 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -253,6 +253,7 @@ export class ChromeService { navControlsRight$={navControls.getRight$()} onIsLockedUpdate={setIsNavDrawerLocked} isLocked$={getIsNavDrawerLocked$} + branding={injectedMetadata.getBranding()} /> ), 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 9cef3e2d9aea..139c4510de33 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 @@ -247,6 +247,11 @@ exports[`Header renders 1`] = ` "serverBasePath": "/test", } } + branding={ + Object { + "logoUrl": "/", + } + } breadcrumbs$={ BehaviorSubject { "_isScalar": false, @@ -1715,6 +1720,7 @@ exports[`Header renders 1`] = ` } } href="/" + logoUrl="/" navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2821,6 +2827,7 @@ exports[`Header renders 1`] = ` } } href="/" + logoUrl="/" navLinks$={ BehaviorSubject { "_isScalar": false, @@ -2916,36 +2923,24 @@ exports[`Header renders 1`] = ` } navigateToApp={[MockFunction]} > - - - -
- - - + logo + +
diff --git a/src/core/public/chrome/ui/header/branding/__snapshots__/opensearch_dashboards_custom_logo.test.tsx.snap b/src/core/public/chrome/ui/header/branding/__snapshots__/opensearch_dashboards_custom_logo.test.tsx.snap new file mode 100644 index 000000000000..11c1aa814449 --- /dev/null +++ b/src/core/public/chrome/ui/header/branding/__snapshots__/opensearch_dashboards_custom_logo.test.tsx.snap @@ -0,0 +1,117 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Custom Logo Take in a normal URL string 1`] = ` + + logo + +`; diff --git a/src/core/public/chrome/ui/header/branding/opensearch_dashboards_custom_logo.test.tsx b/src/core/public/chrome/ui/header/branding/opensearch_dashboards_custom_logo.test.tsx new file mode 100644 index 000000000000..3a8d057a7e04 --- /dev/null +++ b/src/core/public/chrome/ui/header/branding/opensearch_dashboards_custom_logo.test.tsx @@ -0,0 +1,19 @@ +/* + * 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. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { CustomLogo } from './opensearch_dashboards_custom_logo'; + +describe('Custom Logo', () => { + it('Take in a normal URL string', () => { + const branding = { logoUrl: '/', className: '' }; + const component = mountWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/core/public/chrome/ui/header/branding/opensearch_dashboards_custom_logo.tsx b/src/core/public/chrome/ui/header/branding/opensearch_dashboards_custom_logo.tsx new file mode 100644 index 000000000000..00f5a70d915a --- /dev/null +++ b/src/core/public/chrome/ui/header/branding/opensearch_dashboards_custom_logo.tsx @@ -0,0 +1,51 @@ +/* + * 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. + */ + +/* + * 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. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React from 'react'; +import '../header_logo.scss'; + +export interface CustomLogoType { + logoUrl: string; +} + +export const CustomLogo = ({ ...branding }: CustomLogoType) => { + return ( + logo + ); +}; diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index d70973d942f0..fbed7f5e8bc3 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -69,6 +69,7 @@ function mockProps() { isLocked$: new BehaviorSubject(false), loadingCount$: new BehaviorSubject(0), onIsLockedUpdate: () => {}, + branding: { logoUrl: '/' }, }; } diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 5021461dd0b6..4eae83b0ba07 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -87,6 +87,7 @@ export interface HeaderProps { isLocked$: Observable; loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; + branding: { logoUrl: string }; } export function Header({ @@ -96,6 +97,7 @@ export function Header({ basePath, onIsLockedUpdate, homeHref, + branding, ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); @@ -125,6 +127,7 @@ export function Header({ forceNavigation$={observables.forceAppSwitcherNavigation$} navLinks$={observables.navLinks$} navigateToApp={application.navigateToApp} + logoUrl={branding.logoUrl} />, , ], diff --git a/src/core/public/chrome/ui/header/header_logo.scss b/src/core/public/chrome/ui/header/header_logo.scss new file mode 100644 index 000000000000..81761217851f --- /dev/null +++ b/src/core/public/chrome/ui/header/header_logo.scss @@ -0,0 +1,9 @@ +.logoContainer { + height: 30px; + padding: 3px 3px 3px 10px; +} + +.logoImage{ + height: 100%; + max-width: 100%; +} \ No newline at end of file diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index 36262fb3e317..eb12804e7f6f 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -30,14 +30,14 @@ * GitHub history for details. */ -import { EuiHeaderLogo } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; import Url from 'url'; import { ChromeNavLink } from '../..'; -import { OpenSearchDashboardsLogoDarkMode } from './branding/opensearch_dashboards_logo_darkmode'; +import { CustomLogo, CustomLogoType } from './branding/opensearch_dashboards_custom_logo'; +import './header_logo.scss'; function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { let current = element; @@ -104,21 +104,27 @@ interface Props { navLinks$: Observable; forceNavigation$: Observable; navigateToApp: (appId: string) => void; + logoUrl: string; } -export function HeaderLogo({ href, navigateToApp, ...observables }: Props) { +export function HeaderLogo({ href, navigateToApp, logoUrl, ...observables }: Props) { const forceNavigation = useObservable(observables.forceNavigation$, false); const navLinks = useObservable(observables.navLinks$, []); + const branding: CustomLogoType = { + logoUrl, + }; return ( - onClick(e, forceNavigation, navLinks, navigateToApp)} href={href} aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { defaultMessage: 'Go to home page', })} - /> + className="logoContainer" + > + + ); } diff --git a/src/core/public/injected_metadata/injected_metadata_service.mock.ts b/src/core/public/injected_metadata/injected_metadata_service.mock.ts index 3e2a80f7cc8c..e7ab88fffd20 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.mock.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.mock.ts @@ -45,6 +45,7 @@ const createSetupContractMock = () => { getInjectedVar: jest.fn(), getInjectedVars: jest.fn(), getOpenSearchDashboardsBuildNumber: jest.fn(), + getBranding: jest.fn(), }; setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true }); setupContract.getOpenSearchDashboardsVersion.mockReturnValue('opensearchDashboardsVersion'); diff --git a/src/core/public/injected_metadata/injected_metadata_service.test.ts b/src/core/public/injected_metadata/injected_metadata_service.test.ts index 8b34e9d5a1e3..185cb23b6c58 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.test.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.test.ts @@ -229,3 +229,16 @@ describe('setup.getInjectedVars()', () => { ); }); }); + +describe('setup.getBranding()', () => { + it('returns injectedMetadata.branding', () => { + const injectedMetadata = new InjectedMetadataService({ + injectedMetadata: { + branding: { logoUrl: '/' }, + }, + } as any); + + const logoURL = injectedMetadata.setup().getBranding(); + expect(logoURL).toEqual({ logoUrl: '/' }); + }); +}); diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index ef0ba0bf0bce..9e57f09191c1 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -76,6 +76,9 @@ export interface InjectedMetadataParams { user?: Record; }; }; + branding: { + logoUrl: string; + }; }; } @@ -143,6 +146,10 @@ export class InjectedMetadataService { getOpenSearchDashboardsBranch: () => { return this.state.branch; }, + + getBranding: () => { + return this.state.branding; + }, }; } } @@ -176,6 +183,9 @@ export interface InjectedMetadataSetup { getInjectedVars: () => { [key: string]: unknown; }; + getBranding: () => { + logoUrl: string; + }; } /** @internal */ diff --git a/src/core/server/opensearch_dashboards_config.ts b/src/core/server/opensearch_dashboards_config.ts index 3f9415212be2..7e3b64a7b219 100644 --- a/src/core/server/opensearch_dashboards_config.ts +++ b/src/core/server/opensearch_dashboards_config.ts @@ -52,6 +52,12 @@ export const config = { index: schema.string({ defaultValue: '.kibana' }), autocompleteTerminateAfter: schema.duration({ defaultValue: 100000 }), autocompleteTimeout: schema.duration({ defaultValue: 1000 }), + branding: schema.object({ + logoUrl: schema.string({ + defaultValue: + 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg', + }), + }), }), deprecations, }; diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts index d3db5f16bb43..e3547c9f8baf 100644 --- a/src/core/server/rendering/__mocks__/rendering_service.ts +++ b/src/core/server/rendering/__mocks__/rendering_service.ts @@ -41,9 +41,11 @@ export const setupMock: jest.Mocked = { }; export const mockSetup = jest.fn().mockResolvedValue(setupMock); export const mockStop = jest.fn(); +export const mockCheckUrlValid = jest.fn(); export const mockRenderingService: jest.Mocked = { setup: mockSetup, stop: mockStop, + checkUrlValid: mockCheckUrlValid, }; export const RenderingService = jest.fn( () => mockRenderingService diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index 07ca59a48c6b..e934b3f9af6f 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -5,6 +5,9 @@ Object { "anonymousStatusPage": false, "basePath": "/mock-server-basepath", "branch": Any, + "branding": Object { + "logoUrl": "https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg", + }, "buildNumber": Any, "csp": Object { "warnLegacyBrowsers": true, @@ -48,6 +51,9 @@ Object { "anonymousStatusPage": false, "basePath": "/mock-server-basepath", "branch": Any, + "branding": Object { + "logoUrl": "https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg", + }, "buildNumber": Any, "csp": Object { "warnLegacyBrowsers": true, @@ -91,6 +97,9 @@ Object { "anonymousStatusPage": false, "basePath": "/mock-server-basepath", "branch": Any, + "branding": Object { + "logoUrl": "https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg", + }, "buildNumber": Any, "csp": Object { "warnLegacyBrowsers": true, @@ -138,6 +147,9 @@ Object { "anonymousStatusPage": false, "basePath": "", "branch": Any, + "branding": Object { + "logoUrl": "https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg", + }, "buildNumber": Any, "csp": Object { "warnLegacyBrowsers": true, @@ -181,6 +193,9 @@ Object { "anonymousStatusPage": false, "basePath": "/mock-server-basepath", "branch": Any, + "branding": Object { + "logoUrl": "https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg", + }, "buildNumber": Any, "csp": Object { "warnLegacyBrowsers": true, diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index efeb37dcb298..5c5c091de7c4 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -34,9 +34,13 @@ import { load } from 'cheerio'; import { httpServerMock } from '../http/http_server.mocks'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; -import { mockRenderingServiceParams, mockRenderingSetupDeps } from './__mocks__/params'; +import { mockRenderingSetupDeps } from './__mocks__/params'; import { InternalRenderingServiceSetup } from './types'; import { RenderingService } from './rendering_service'; +import { configServiceMock } from '../config/mocks'; +import { BehaviorSubject } from 'rxjs'; +import { config as RawOpenSearchDashboardsConfig } from '../opensearch_dashboards_config'; +import { mockCoreContext } from '../core_context.mock'; const INJECTED_METADATA = { version: expect.any(String), @@ -62,10 +66,15 @@ const { createOpenSearchDashboardsRequest, createRawRequest } = httpServerMock; describe('RenderingService', () => { let service: RenderingService; + const configService = configServiceMock.create(); + configService.atPath.mockImplementation(() => { + return new BehaviorSubject(RawOpenSearchDashboardsConfig.schema.validate({})); + }); + const context = mockCoreContext.create({ configService }); beforeEach(() => { jest.clearAllMocks(); - service = new RenderingService(mockRenderingServiceParams); + service = new RenderingService(context); }); describe('setup()', () => { @@ -127,4 +136,42 @@ describe('RenderingService', () => { }); }); }); + describe('checkUrlvalid()', () => { + it('URL is valid', async () => { + jest.mock('axios', () => ({ + async get() { + return { + status: 200, + }; + }, + })); + const result = await service.checkUrlValid( + 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg' + ); + expect(result).toEqual( + 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg' + ); + }); + it('URL does not contain jpeg, jpg, gif, or png', async () => { + const result = await service.checkUrlValid( + 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode' + ); + expect(result).toEqual( + 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg' + ); + }); + it('URL is invalid', async () => { + jest.mock('axios', () => ({ + async get() { + return { + status: 404, + }; + }, + })); + const result = await service.checkUrlValid('http://notfound'); + expect(result).toEqual( + 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg' + ); + }); + }); }); diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 3feb5ada2ca9..13be829d0b7c 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -32,9 +32,12 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; -import { take } from 'rxjs/operators'; +import { first, take } from 'rxjs/operators'; import { i18n } from '@osd/i18n'; +import Axios from 'axios'; +// @ts-expect-error untyped internal module used to prevent axios from using xhr adapter in tests +import AxiosHttpAdapter from 'axios/lib/adapters/http'; import { UiPlugins } from '../plugins'; import { CoreContext } from '../core_context'; import { Template } from './views'; @@ -44,16 +47,24 @@ import { InternalRenderingServiceSetup, RenderingMetadata, } from './types'; +import { OpenSearchDashboardsConfigType } from '../opensearch_dashboards_config'; /** @internal */ export class RenderingService { constructor(private readonly coreContext: CoreContext) {} - + private logger = this.coreContext.logger; public async setup({ http, status, uiPlugins, }: RenderingSetupDeps): Promise { + const opensearchDashboardsConfig = await this.coreContext.configService + .atPath('opensearchDashboards') + .pipe(first()) + .toPromise(); + + const validLogoUrl = await this.checkUrlValid(opensearchDashboardsConfig.branding.logoUrl); + return { render: async ( request, @@ -102,6 +113,9 @@ export class RenderingService { legacyMetadata: { uiSettings: settings, }, + branding: { + logoUrl: validLogoUrl, + }, }, }; @@ -117,4 +131,23 @@ export class RenderingService { return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record; } + + public checkUrlValid = async (url: string): Promise => { + if (url.match(/\.(png|svg)$/) === null) { + this.logger + .get('branding') + .error('Invalid URL for logo. Rendering default OpenSearch Dashboard Logo.'); + return 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg'; + } + return await Axios.get(url, { adapter: AxiosHttpAdapter }) + .then(() => { + return url; + }) + .catch(() => { + this.logger + .get('branding') + .error('Invalid URL for logo. Rendering default OpenSearch Dashboard Logo.'); + return 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg'; + }); + }; } diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 7bba46eeb938..72a45661ed4a 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -74,6 +74,9 @@ export interface RenderingMetadata { user: Record>; }; }; + branding: { + logoUrl: string; + }; }; } diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index d19c9dbb4ed8..3f21233ad9c9 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -233,6 +233,11 @@ export default () => autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000), // TODO Also allow units here like in opensearch config once this is moved to the new platform autocompleteTimeout: Joi.number().integer().min(1).default(1000), + branding: Joi.object({ + logoUrl: Joi.string().default( + 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg' + ), + }), }).default(), savedObjects: HANDLED_IN_NEW_PLATFORM, diff --git a/test/common/config.js b/test/common/config.js index a122a78094d6..be5baaa5d055 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -74,6 +74,8 @@ export default function () { // `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'newsfeed')}`, // `--newsfeed.service.urlRoot=${servers.opensearchDashboards.protocol}://${servers.opensearchDashboards.hostname}:${servers.opensearchDashboards.port}`, // `--newsfeed.service.pathTemplate=/api/_newsfeed-FTS-external-service-simulators/opensearch-dashboards/v{VERSION}.json`, + // Custom branding config + `--opensearchDashboards.branding.logoUrl=https://opensearch.org/assets/brand/SVG/Logo/opensearch_logo_darkmode.svg`, ], }, services, diff --git a/test/functional/apps/visualize/_custom_branding.js b/test/functional/apps/visualize/_custom_branding.js new file mode 100644 index 000000000000..b783a3e5acab --- /dev/null +++ b/test/functional/apps/visualize/_custom_branding.js @@ -0,0 +1,58 @@ +/* + * 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. + */ + +/* + * 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. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +import expect from '@osd/expect'; + +export default function ({ getService, getPageObjects }) { + const browser = getService('browser'); + const globalNav = getService('globalNav'); + const PageObjects = getPageObjects(['common', 'home', 'header']); + + describe('OpenSearch Dashboards branding configuration', function customLogo() { + this.tags('includeFirefox'); + const expectedUrl = 'https://opensearch.org/assets/brand/SVG/Logo/opensearch_logo_darkmode.svg'; + before(async function () { + await PageObjects.common.navigateToApp('home'); + }); + + it('should show customized logo in Navbar on the main page', async () => { + await globalNav.logoExistsOrFail(expectedUrl); + }); + + it('should show a customized logo that can take to home page', async () => { + await PageObjects.common.navigateToApp('settings'); + await globalNav.clickLogo(); + await PageObjects.header.waitUntilLoadingHasFinished(); + const url = await browser.getCurrentUrl(); + expect(url.includes('/app/home')).to.be(true); + }); + }); +} diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 25b46435870f..fa5468d4c171 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -58,6 +58,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('', function () { this.tags('ciGroup9'); + loadTestFile(require.resolve('./_custom_branding')); loadTestFile(require.resolve('./_embedding_chart')); loadTestFile(require.resolve('./_chart_types')); loadTestFile(require.resolve('./_area_chart')); diff --git a/test/functional/services/global_nav.ts b/test/functional/services/global_nav.ts index 7cc64e8847db..945ffae37488 100644 --- a/test/functional/services/global_nav.ts +++ b/test/functional/services/global_nav.ts @@ -74,6 +74,15 @@ export function GlobalNavProvider({ getService }: FtrProviderContext) { public async badgeMissingOrFail(): Promise { await testSubjects.missingOrFail('headerBadge'); } + + public async logoExistsOrFail(expectedUrl: string): Promise { + await testSubjects.exists('headerGlobalNav > logo > customLogo'); + const actualLabel = await testSubjects.getAttribute( + 'headerGlobalNav > logo > customLogo', + 'data-test-image-url' + ); + expect(actualLabel.toUpperCase()).to.equal(expectedUrl.toUpperCase()); + } } return new GlobalNav();