diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 7b3416f6208d..9c1ba20aa5cd 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -152,10 +152,10 @@ #vis_type_timeline.graphiteBlockedIPs: [] # full version customized logo URL -# opensearchDashboards.branding.logoUrl: "" +# opensearchDashboards.branding.fullLogoUrl: "" # smaller version customized logo URL -# opensearchDashboards.branding.smallLogoUrl: "" +# opensearchDashboards.branding.logoUrl: "" # customized loading logo URL # opensearchDashboards.branding.loadingLogoUrl: "" 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 23021f21c535..8421a105e5f0 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 @@ -249,8 +249,8 @@ exports[`Header renders 1`] = ` } branding={ Object { + "fullLogoUrl": "/", "logoUrl": "/", - "smallLogoUrl": "/", "title": "OpenSearch Dashboards", } } @@ -1672,6 +1672,13 @@ exports[`Header renders 1`] = ` "borders": "none", "items": Array [ logo - - - - - - - - - - - - - - - - - + title logo `; -exports[`Custom Logo Take in a normal URL string 1`] = ` +exports[`Custom Logo Take in an invalid full logo URL string and a valid logo URL string 1`] = ` logo `; + +exports[`Custom Logo Take in invalid full logo URL and logo URL 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 index 66a3f7a3694a..ef54524ddd3e 100644 --- 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 @@ -11,13 +11,20 @@ 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: '/custom' }; + it('Take in a normal full logo URL string', () => { + const branding = { fullLogoUrl: '/custom', title: 'title' }; const component = mountWithIntl(); expect(component).toMatchSnapshot(); }); - it('Take in a invalid URL string', () => { - const branding = {}; + + it('Take in an invalid full logo URL string and a valid logo URL string', () => { + const branding = { logoUrl: '/custom', title: 'title' }; + const component = mountWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('Take in invalid full logo URL and logo URL', () => { + const branding = { title: 'title' }; 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 index bdf240cb1a7c..28caf065bf4c 100644 --- 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 @@ -35,23 +35,29 @@ import '../header_logo.scss'; import { OpenSearchDashboardsLogoDarkMode } from './opensearch_dashboards_logo_darkmode'; /** - * @param {string} logoUrl - custom URL for the top left logo of the main screen + * @param {string} fullLogoUrl - custom URL for the top left logo of the main screen + * @param {string} logoUrl - custom URL for the logo icon + * @param {string} title - custom title for the application */ export interface CustomLogoType { + fullLogoUrl?: string; logoUrl?: string; + title: string; } export const CustomLogo = ({ ...branding }: CustomLogoType) => { - return !branding.logoUrl ? ( - OpenSearchDashboardsLogoDarkMode() - ) : ( - logo - ); + if (!branding.fullLogoUrl && !branding.logoUrl) { + return OpenSearchDashboardsLogoDarkMode(); + } else { + return ( + {branding.title + ); + } }; diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 6393f59833bd..9a3ca4ece870 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -69,7 +69,7 @@ function mockProps() { isLocked$: new BehaviorSubject(false), loadingCount$: new BehaviorSubject(0), onIsLockedUpdate: () => {}, - branding: { logoUrl: '/', smallLogoUrl: '/', title: 'OpenSearch Dashboards' }, + branding: { fullLogoUrl: '/', logoUrl: '/', title: 'OpenSearch Dashboards' }, }; } diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index e7a4abd53cba..8555342503ab 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -87,7 +87,7 @@ export interface HeaderProps { isLocked$: Observable; loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; - branding: { logoUrl?: string }; + branding: { fullLogoUrl?: string; logoUrl?: string; title: string }; } export function Header({ @@ -127,7 +127,7 @@ export function Header({ forceNavigation$={observables.forceAppSwitcherNavigation$} navLinks$={observables.navLinks$} navigateToApp={application.navigateToApp} - logoUrl={branding.logoUrl} + branding={branding} />, , ], diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx index b7219a2e1863..b042da3c0d69 100644 --- a/src/core/public/chrome/ui/header/header_logo.tsx +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -104,15 +104,12 @@ interface Props { navLinks$: Observable; forceNavigation$: Observable; navigateToApp: (appId: string) => void; - logoUrl?: string; + branding: CustomLogoType; } -export function HeaderLogo({ href, navigateToApp, logoUrl, ...observables }: Props) { +export function HeaderLogo({ href, navigateToApp, branding, ...observables }: Props) { const forceNavigation = useObservable(observables.forceNavigation$, false); const navLinks = useObservable(observables.navLinks$, []); - const branding: CustomLogoType = { - logoUrl, - }; return ( unknown; getBranding: () => { + fullLogoUrl?: string; logoUrl?: string; - smallLogoUrl?: string; title: string; }; }; @@ -297,8 +297,8 @@ export interface CoreStart { injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; getBranding: () => { + fullLogoUrl?: string; logoUrl?: string; - smallLogoUrl?: string; title: string; }; }; 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 140fa96f72f3..d21de7e96b64 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.test.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.test.ts @@ -234,11 +234,11 @@ describe('setup.getBranding()', () => { it('returns injectedMetadata.branding', () => { const injectedMetadata = new InjectedMetadataService({ injectedMetadata: { - branding: { logoUrl: '/', smallLogoUrl: '/', title: 'title' }, + branding: { fullLogoUrl: '/', logoUrl: '/', title: 'title' }, }, } as any); const branding = injectedMetadata.setup().getBranding(); - expect(branding).toEqual({ logoUrl: '/', smallLogoUrl: '/', title: 'title' }); + expect(branding).toEqual({ fullLogoUrl: '/', logoUrl: '/', title: 'title' }); }); }); diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 2cb1eef378ad..ee69c8208267 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -77,8 +77,8 @@ export interface InjectedMetadataParams { }; }; branding: { + fullLogoUrl?: string; logoUrl?: string; - smallLogoUrl?: string; title: string; }; }; @@ -186,8 +186,8 @@ export interface InjectedMetadataSetup { [key: string]: unknown; }; getBranding: () => { + fullLogoUrl?: string; logoUrl?: string; - smallLogoUrl?: string; title: string; }; } diff --git a/src/core/server/opensearch_dashboards_config.ts b/src/core/server/opensearch_dashboards_config.ts index 8284ab6c6e9b..c3f2c7821c26 100644 --- a/src/core/server/opensearch_dashboards_config.ts +++ b/src/core/server/opensearch_dashboards_config.ts @@ -53,16 +53,16 @@ export const config = { autocompleteTerminateAfter: schema.duration({ defaultValue: 100000 }), autocompleteTimeout: schema.duration({ defaultValue: 1000 }), branding: schema.object({ - logoUrl: schema.string({ + fullLogoUrl: schema.string({ defaultValue: '/', }), - smallLogoUrl: schema.string({ + logoUrl: schema.string({ defaultValue: '/', }), loadingLogoUrl: schema.string({ defaultValue: '/', }), - title: schema.string({ defaultValue: 'OpenSearch Dashboards' }), + title: schema.string({ defaultValue: '' }), }), }), deprecations, diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts index e3547c9f8baf..d4e0ede01828 100644 --- a/src/core/server/rendering/__mocks__/rendering_service.ts +++ b/src/core/server/rendering/__mocks__/rendering_service.ts @@ -42,10 +42,12 @@ export const setupMock: jest.Mocked = { export const mockSetup = jest.fn().mockResolvedValue(setupMock); export const mockStop = jest.fn(); export const mockCheckUrlValid = jest.fn(); +export const mockCheckTitleValid = jest.fn(); export const mockRenderingService: jest.Mocked = { setup: mockSetup, stop: mockStop, checkUrlValid: mockCheckUrlValid, + checkTitleValid: mockCheckTitleValid, }; export const RenderingService = jest.fn( () => mockRenderingService diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 9a1d37203ff4..ac726838d431 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -138,7 +138,7 @@ describe('RenderingService', () => { }); describe('checkUrlValid()', () => { - it('SVG URL is valid', async () => { + it('checks valid SVG URL', async () => { const result = await service.checkUrlValid( 'https://opensearch.org/assets/brand/SVG/Mark/opensearch_mark_default.svg', 'config' @@ -146,7 +146,7 @@ describe('RenderingService', () => { expect(result).toEqual(true); }); - it('PNG URL is valid', async () => { + it('checks valid PNG URL', async () => { const result = await service.checkUrlValid( 'https://opensearch.org/assets/brand/PNG/Mark/opensearch_mark_default.png', 'config' @@ -154,14 +154,34 @@ describe('RenderingService', () => { expect(result).toEqual(true); }); - it('URL does not contain svg, png or gif', async () => { + it('checks invalid URL that does not contain svg, png or gif', async () => { const result = await service.checkUrlValid('https://validUrl', 'config'); expect(result).toEqual(false); }); - it('URL is invalid', async () => { + it('checks invalid URL', async () => { const result = await service.checkUrlValid('http://notfound.svg', 'config'); expect(result).toEqual(false); }); }); + + describe('checkTitleValid()', () => { + it('checks valid title', () => { + const result = service.checkTitleValid('OpenSearch Dashboards', 'config'); + expect(result).toEqual(true); + }); + + it('checks invalid title with empty string', () => { + const result = service.checkTitleValid('', 'config'); + expect(result).toEqual(false); + }); + + it('checks invalid title with length > 36 character', () => { + const result = service.checkTitleValid( + 'OpenSearch Dashboardssssssssssssssssssssss', + 'config' + ); + expect(result).toEqual(false); + }); + }); }); diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 49b5897defee..cf0e028b6332 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -63,18 +63,20 @@ export class RenderingService { .pipe(first()) .toPromise(); + const isFullLogoUrlValid = await this.checkUrlValid( + opensearchDashboardsConfig.branding.fullLogoUrl, + 'fullLogoUrl' + ); const isLogoUrlValid = await this.checkUrlValid( opensearchDashboardsConfig.branding.logoUrl, 'logoUrl' ); - const isSmallLogoUrlValid = await this.checkUrlValid( - opensearchDashboardsConfig.branding.smallLogoUrl, - 'smallLogoUrl' - ); const isLoadingLogoUrlValid = await this.checkUrlValid( opensearchDashboardsConfig.branding.loadingLogoUrl, 'loadingLogoUrl' ); + const isTitleValid = this.checkTitleValid(opensearchDashboardsConfig.branding.title, 'title'); + const defaultTitle = 'OpenSearch Dashboards'; return { render: async ( @@ -125,14 +127,14 @@ export class RenderingService { uiSettings: settings, }, branding: { - logoUrl: isLogoUrlValid ? opensearchDashboardsConfig.branding.logoUrl : undefined, - smallLogoUrl: isSmallLogoUrlValid - ? opensearchDashboardsConfig.branding.smallLogoUrl + fullLogoUrl: isFullLogoUrlValid + ? opensearchDashboardsConfig.branding.fullLogoUrl : undefined, + logoUrl: isLogoUrlValid ? opensearchDashboardsConfig.branding.logoUrl : undefined, loadingLogoUrl: isLoadingLogoUrlValid ? opensearchDashboardsConfig.branding.loadingLogoUrl : undefined, - title: opensearchDashboardsConfig.branding.title, + title: isTitleValid ? opensearchDashboardsConfig.branding.title : defaultTitle, }, }, }; @@ -151,7 +153,7 @@ export class RenderingService { } public checkUrlValid = async (url: string, configName?: string): Promise => { - if (url.match(/\.(png|svg|gif)$/) === null) { + if (url.match(/\.(png|svg|gif|PNG|SVG|GIF)$/) === null) { this.logger.get('branding').warn(configName + ' config is not found or invalid.'); return false; } @@ -164,4 +166,12 @@ export class RenderingService { return false; }); }; + + public checkTitleValid = (title: string, configName?: string): boolean => { + if (!title || title.length > 36) { + this.logger.get('branding').warn(configName + ' config is not found or invalid'); + return false; + } + return true; + }; } diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 31a610448882..98ee1ba20ae1 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -75,8 +75,8 @@ export interface RenderingMetadata { }; }; branding: { + fullLogoUrl?: string; logoUrl?: string; - smallLogoUrl?: string; loadingLogoUrl?: string; title: string; }; diff --git a/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap b/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap index b54c56b50f3e..beb6ec2b2693 100644 --- a/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap +++ b/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap @@ -66,7 +66,7 @@ Array [ data="{\\"strictCsp\\":true}" />, ,
@@ -367,7 +367,7 @@ Array [ data="{\\"strictCsp\\":true}" />, ,
diff --git a/src/core/server/rendering/views/template.test.tsx b/src/core/server/rendering/views/template.test.tsx index 08532e8b1329..f562e4846534 100644 --- a/src/core/server/rendering/views/template.test.tsx +++ b/src/core/server/rendering/views/template.test.tsx @@ -95,7 +95,7 @@ describe('Template', () => { it('renders with static logo with horizontal loading bar', () => { const branding = { - smallLogoUrl: '/', + logoUrl: '/', title: '', }; injectedMetadata.getBranding.mockReturnValue(branding); @@ -105,7 +105,7 @@ describe('Template', () => { it('renders with customized loading logo', () => { const branding = { - smallLogoUrl: '/', + logoUrl: '/', loadingLogoUrl: '/', title: '', }; diff --git a/src/core/server/rendering/views/template.tsx b/src/core/server/rendering/views/template.tsx index 7eb8bf06f42a..f3fab623615c 100644 --- a/src/core/server/rendering/views/template.tsx +++ b/src/core/server/rendering/views/template.tsx @@ -97,13 +97,13 @@ export const Template: FunctionComponent = ({ ); const renderBrandingEnabledOrDisabledLoadingBar = () => { - if (!injectedMetadata.branding.loadingLogoUrl && injectedMetadata.branding.smallLogoUrl) { + if (!injectedMetadata.branding.loadingLogoUrl && injectedMetadata.branding.logoUrl) { return
; } }; const renderBrandingEnabledOrDisabledLoadingLogo = () => { - if (!injectedMetadata.branding.loadingLogoUrl && !injectedMetadata.branding.smallLogoUrl) { + if (!injectedMetadata.branding.loadingLogoUrl && !injectedMetadata.branding.logoUrl) { return openSearchLogoSpinner; } else { return ( @@ -112,7 +112,7 @@ export const Template: FunctionComponent = ({ className="loadingLogo" src={ !injectedMetadata.branding.loadingLogoUrl - ? injectedMetadata.branding.smallLogoUrl + ? injectedMetadata.branding.logoUrl : injectedMetadata.branding.loadingLogoUrl } alt={injectedMetadata.branding.title + ' logo'} diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 1b58b6c4983d..89c1b6cc7553 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -234,10 +234,10 @@ export default () => // 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('/'), - smallLogoUrl: Joi.string().default('/'), - loadingLogoUrl: Joi.string().default('/'), - title: Joi.string().default('OpenSearch Dashboards'), + fullLogoUrl: Joi.any().default('/'), + logoUrl: Joi.any().default('/'), + loadingLogoUrl: Joi.any().default('/'), + title: Joi.any().default(''), }), }).default(), diff --git a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap index bb331c752bad..c25085bc1b1b 100644 --- a/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap +++ b/src/plugins/home/public/application/components/__snapshots__/welcome.test.tsx.snap @@ -18,7 +18,7 @@ exports[`should render a Welcome screen with customized branding 1`] = ` className="homWelcome__customLogoContainer" > logo logo logo logo { */ const branding = { - smallLogoUrl: '/', + logoUrl: '/', title: 'OpenSearch Dashboards', }; @@ -102,7 +102,7 @@ test('should render a Welcome screen with the default OpenSearch Dashboards bran test('should render a Welcome screen with customized branding', () => { const customBranding = { - smallLogoUrl: '/custom', + logoUrl: '/custom', title: 'custom title', }; const component = shallow( diff --git a/src/plugins/home/public/application/components/welcome.tsx b/src/plugins/home/public/application/components/welcome.tsx index 77d265c0862b..67f7dbb4cf38 100644 --- a/src/plugins/home/public/application/components/welcome.tsx +++ b/src/plugins/home/public/application/components/welcome.tsx @@ -59,7 +59,7 @@ interface Props { onSkip: () => void; telemetry?: TelemetryPluginStart; branding: { - smallLogoUrl?: string; + logoUrl?: string; title: string; }; } @@ -145,7 +145,7 @@ export class Welcome extends React.Component { }; private renderBrandingEnabledOrDisabledLogo = () => { - if (!this.props.branding.smallLogoUrl) { + if (!this.props.branding.logoUrl) { return ( @@ -157,9 +157,9 @@ export class Welcome extends React.Component { logo
); diff --git a/src/plugins/home/public/application/opensearch_dashboards_services.ts b/src/plugins/home/public/application/opensearch_dashboards_services.ts index 7a7dc60a3d1d..a14d33ee54d9 100644 --- a/src/plugins/home/public/application/opensearch_dashboards_services.ts +++ b/src/plugins/home/public/application/opensearch_dashboards_services.ts @@ -71,8 +71,8 @@ export interface HomeOpenSearchDashboardsServices { injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; getBranding: () => { + fullLogoUrl?: string; logoUrl?: string; - smallLogoUrl?: string; title: string; }; }; diff --git a/test/common/config.js b/test/common/config.js index 05432914a95b..fb252e2c3c67 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -75,8 +75,8 @@ export default function () { // `--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.fullLogoUrl=https://opensearch.org/assets/brand/SVG/Logo/opensearch_logo_darkmode.svg`, `--opensearchDashboards.branding.logoUrl=https://opensearch.org/assets/brand/SVG/Logo/opensearch_logo_darkmode.svg`, - `--opensearchDashboards.branding.smallLogoUrl=https://opensearch.org/assets/brand/SVG/Logo/opensearch_logo_darkmode.svg`, `--opensearchDashboards.branding.title=OpenSearch`, ], },