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]}
>
-
-
-
-
-
-
-
+
+
+
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`] = `
+
+
+
+`;
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 (
+
+ );
+};
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();