Skip to content

Commit

Permalink
Welcome page title and logo configurable (#738)
Browse files Browse the repository at this point in the history
Add two new configs branding.smallLogoUrl and branding.title
in the yaml file for making the welcome page logo and title
 configurable. If URL is invalid, the default branding will be shown.

Signed-off-by: Qingyang(Abby) Hu <[email protected]>
  • Loading branch information
abbyhu2000 authored and kavilla committed Oct 30, 2021
1 parent 9365ed1 commit 6e8003e
Show file tree
Hide file tree
Showing 33 changed files with 615 additions and 112 deletions.
9 changes: 8 additions & 1 deletion config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@
# ]
#vis_type_timeline.graphiteBlockedIPs: []

# user input URL for customized logo
# full version customized logo URL
# opensearchDashboards.branding.logoUrl: ""

# smaller version customized logo URL
# opensearchDashboards.branding.smallLogoUrl: ""

# custom application title
# opensearchDashboards.branding.title: ""


Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import { CustomLogo } from './opensearch_dashboards_custom_logo';

describe('Custom Logo', () => {
it('Take in a normal URL string', () => {
const branding = { logoUrl: '/', className: '' };
const branding = { logoUrl: '/custom' };
const component = mountWithIntl(<CustomLogo {...branding} />);
expect(component).toMatchSnapshot();
});
it('Take in a invalid URL string', () => {
const branding = {};
const component = mountWithIntl(<CustomLogo {...branding} />);
expect(component).toMatchSnapshot();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@

import React from 'react';
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
*/
export interface CustomLogoType {
logoUrl: string;
logoUrl?: string;
}

export const CustomLogo = ({ ...branding }: CustomLogoType) => {
return (
return !branding.logoUrl ? (
OpenSearchDashboardsLogoDarkMode()
) : (
<img
data-test-subj="customLogo"
data-test-image-url={branding.logoUrl}
Expand Down
2 changes: 1 addition & 1 deletion src/core/public/chrome/ui/header/header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function mockProps() {
isLocked$: new BehaviorSubject(false),
loadingCount$: new BehaviorSubject(0),
onIsLockedUpdate: () => {},
branding: { logoUrl: '/' },
branding: { logoUrl: '/', smallLogoUrl: '/', title: 'OpenSearch Dashboards' },
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/public/chrome/ui/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export interface HeaderProps {
isLocked$: Observable<boolean>;
loadingCount$: ReturnType<HttpStart['getLoadingCount$']>;
onIsLockedUpdate: OnIsLockedUpdate;
branding: { logoUrl: string };
branding: { logoUrl?: string };
}

export function Header({
Expand Down
2 changes: 1 addition & 1 deletion src/core/public/chrome/ui/header/header_logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ interface Props {
navLinks$: Observable<ChromeNavLink[]>;
forceNavigation$: Observable<boolean>;
navigateToApp: (appId: string) => void;
logoUrl: string;
logoUrl?: string;
}

export function HeaderLogo({ href, navigateToApp, logoUrl, ...observables }: Props) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/public/core_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export class CoreSystem {
docLinks,
http,
i18n,
injectedMetadata: pick(injectedMetadata, ['getInjectedVar']),
injectedMetadata: pick(injectedMetadata, ['getInjectedVar', 'getBranding']),
notifications,
overlays,
savedObjects,
Expand Down
10 changes: 10 additions & 0 deletions src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
* */
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
getBranding: () => {
logoUrl?: string;
smallLogoUrl?: string;
title: string;
};
};
/** {@link StartServicesAccessor} */
getStartServices: StartServicesAccessor<TPluginsStart, TStart>;
Expand Down Expand Up @@ -291,6 +296,11 @@ export interface CoreStart {
* */
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
getBranding: () => {
logoUrl?: string;
smallLogoUrl?: string;
title: string;
};
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ describe('setup.getBranding()', () => {
it('returns injectedMetadata.branding', () => {
const injectedMetadata = new InjectedMetadataService({
injectedMetadata: {
branding: { logoUrl: '/' },
branding: { logoUrl: '/', smallLogoUrl: '/', title: 'title' },
},
} as any);

const logoURL = injectedMetadata.setup().getBranding();
expect(logoURL).toEqual({ logoUrl: '/' });
const branding = injectedMetadata.setup().getBranding();
expect(branding).toEqual({ logoUrl: '/', smallLogoUrl: '/', title: 'title' });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ export interface InjectedMetadataParams {
};
};
branding: {
logoUrl: string;
logoUrl?: string;
smallLogoUrl?: string;
title: string;
};
};
}
Expand Down Expand Up @@ -184,7 +186,9 @@ export interface InjectedMetadataSetup {
[key: string]: unknown;
};
getBranding: () => {
logoUrl: string;
logoUrl?: string;
smallLogoUrl?: string;
title: string;
};
}

Expand Down
1 change: 1 addition & 0 deletions src/core/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ function createCoreStartMock({ basePath = '' } = {}) {
savedObjects: savedObjectsServiceMock.createStartContract(),
injectedMetadata: {
getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar,
getBranding: injectedMetadataServiceMock.createStartContract().getBranding,
},
fatalErrors: fatalErrorsServiceMock.createStartContract(),
};
Expand Down
2 changes: 2 additions & 0 deletions src/core/public/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export function createPluginSetupContext<
uiSettings: deps.uiSettings,
injectedMetadata: {
getInjectedVar: deps.injectedMetadata.getInjectedVar,
getBranding: deps.injectedMetadata.getBranding,
},
getStartServices: () => plugin.startDependencies,
};
Expand Down Expand Up @@ -166,6 +167,7 @@ export function createPluginStartContext<
savedObjects: deps.savedObjects,
injectedMetadata: {
getInjectedVar: deps.injectedMetadata.getInjectedVar,
getBranding: deps.injectedMetadata.getBranding,
},
fatalErrors: deps.fatalErrors,
};
Expand Down
4 changes: 2 additions & 2 deletions src/core/public/plugins/plugins_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe('PluginsService', () => {
...mockSetupDeps,
application: expect.any(Object),
getStartServices: expect.any(Function),
injectedMetadata: pick(mockSetupDeps.injectedMetadata, 'getInjectedVar'),
injectedMetadata: pick(mockSetupDeps.injectedMetadata, 'getInjectedVar', 'getBranding'),
};
mockStartDeps = {
application: applicationServiceMock.createInternalStartContract(),
Expand All @@ -132,7 +132,7 @@ describe('PluginsService', () => {
...mockStartDeps,
application: expect.any(Object),
chrome: omit(mockStartDeps.chrome, 'getComponent'),
injectedMetadata: pick(mockStartDeps.injectedMetadata, 'getInjectedVar'),
injectedMetadata: pick(mockStartDeps.injectedMetadata, 'getInjectedVar', 'getBranding'),
};

// Reset these for each test.
Expand Down
7 changes: 5 additions & 2 deletions src/core/server/opensearch_dashboards_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ export const config = {
autocompleteTimeout: schema.duration({ defaultValue: 1000 }),
branding: schema.object({
logoUrl: schema.string({
defaultValue:
'https://opensearch.org/assets/brand/SVG/Logo/opensearch_dashboards_logo_darkmode.svg',
defaultValue: '/',
}),
smallLogoUrl: schema.string({
defaultValue: '/',
}),
title: schema.string({ defaultValue: 'OpenSearch Dashboards' }),
}),
}),
deprecations,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 15 additions & 29 deletions src/core/server/rendering/rendering_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,42 +136,28 @@ describe('RenderingService', () => {
});
});
});
describe('checkUrlvalid()', () => {
it('URL is valid', async () => {
jest.mock('axios', () => ({
async get() {
return {
status: 200,
};
},
}));
describe('checkUrlValid()', () => {
it('SVG URL is valid', async () => {
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'
'https://opensearch.org/assets/brand/SVG/Mark/opensearch_mark_default.svg',
'config'
);
expect(result).toEqual(true);
});
it('URL does not contain jpeg, jpg, gif, or png', async () => {
it('PNG URL is valid', 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'
'https://opensearch.org/assets/brand/PNG/Mark/opensearch_mark_default.png',
'config'
);
expect(result).toEqual(true);
});
it('URL does not contain svg, or png', async () => {
const result = await service.checkUrlValid('https://validUrl', 'config');
expect(result).toEqual(false);
});
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'
);
const result = await service.checkUrlValid('http://notfound.svg', 'config');
expect(result).toEqual(false);
});
});
});
31 changes: 19 additions & 12 deletions src/core/server/rendering/rendering_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ export class RenderingService {
.pipe(first())
.toPromise();

const validLogoUrl = await this.checkUrlValid(opensearchDashboardsConfig.branding.logoUrl);
const isLogoUrlValid = await this.checkUrlValid(
opensearchDashboardsConfig.branding.logoUrl,
'logoUrl'
);
const isSmallLogoUrlValid = await this.checkUrlValid(
opensearchDashboardsConfig.branding.smallLogoUrl,
'smallLogoUrl'
);

return {
render: async (
Expand Down Expand Up @@ -114,7 +121,11 @@ export class RenderingService {
uiSettings: settings,
},
branding: {
logoUrl: validLogoUrl,
logoUrl: isLogoUrlValid ? opensearchDashboardsConfig.branding.logoUrl : undefined,
smallLogoUrl: isSmallLogoUrlValid
? opensearchDashboardsConfig.branding.smallLogoUrl
: undefined,
title: opensearchDashboardsConfig.branding.title,
},
},
};
Expand All @@ -132,22 +143,18 @@ export class RenderingService {
return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record<string, any>;
}

public checkUrlValid = async (url: string): Promise<string> => {
public checkUrlValid = async (url: string, configName?: string): Promise<boolean> => {
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';
this.logger.get('branding').warn(configName + ' config is not found or invalid.');
return false;
}
return await Axios.get(url, { adapter: AxiosHttpAdapter })
.then(() => {
return url;
return true;
})
.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';
this.logger.get('branding').warn(configName + ' config is not found or invalid');
return false;
});
};
}
4 changes: 3 additions & 1 deletion src/core/server/rendering/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ export interface RenderingMetadata {
};
};
branding: {
logoUrl: string;
logoUrl?: string;
smallLogoUrl?: string;
title: string;
};
};
}
Expand Down
Loading

0 comments on commit 6e8003e

Please sign in to comment.