Skip to content

Commit

Permalink
Implement an ability to customize Eclipse Che logo from the Custom Re…
Browse files Browse the repository at this point in the history
…source (#907)

* feat: implement an ability to customize Eclipse Che logo

Signed-off-by: Oleksii Orel <oorel@redhat.com>
  • Loading branch information
olexii4 authored Aug 31, 2023
1 parent 75d9743 commit 47b845e
Show file tree
Hide file tree
Showing 19 changed files with 330 additions and 115 deletions.
1 change: 1 addition & 0 deletions packages/common/src/dto/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface IServerConfig {
pluginRegistryInternalURL: string;
devfileRegistryURL: string;
devfileRegistryInternalURL: string;
dashboardLogo?: { base64data: string; mediatype: string };
}

export interface IUserProfile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,10 @@ export class ServerConfigApiService implements IServerConfigApi {
getWorkspaceStartTimeout(cheCustomResource: CustomResourceDefinition): number {
return cheCustomResource.spec.devEnvironments?.startTimeoutSeconds || startTimeoutSeconds;
}

getDashboardLogo(
cheCustomResource: CustomResourceDefinition,
): { base64data: string; mediatype: string } | undefined {
return cheCustomResource.spec.components?.dashboard?.branding?.logo;
}
}
13 changes: 13 additions & 0 deletions packages/dashboard-backend/src/devworkspaceClient/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ export type CustomResourceDefinitionSpecDevEnvironments = {

export type CustomResourceDefinitionSpecComponents = {
dashboard?: {
branding?: {
logo?: {
base64data: string;
mediatype: string;
};
};
headerMessage?: {
show?: boolean;
text?: string;
Expand Down Expand Up @@ -232,6 +238,13 @@ export interface IServerConfigApi {
* Returns the workspace start timeout
*/
getWorkspaceStartTimeout(cheCustomResource: CustomResourceDefinition): number;

/**
* Returns the dashboard branding logo
*/
getDashboardLogo(
cheCustomResource: CustomResourceDefinition,
): { base64data: string; mediatype: string } | undefined;
}

export interface IKubeConfigApi {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ describe('Server Config Route', () => {
},
],
},
dashboardLogo: {
base64data: 'base64-encoded-data',
mediatype: 'image/svg+xml',
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const defaultDevfileRegistryUrl = 'http://devfile-registry.eclipse-che.sv
export const defaultPluginRegistryUrl = 'http://plugin-registry.eclipse-che.svc/v3';
export const internalRegistryDisableStatus = true;
export const externalDevfileRegistries = [{ url: 'https://devfile.registry.test.org/' }];
export const dashboardLogo = {
base64data: 'base64-encoded-data',
mediatype: 'image/svg+xml',
};

export const stubDevWorkspacesList: api.IDevWorkspaceList = {
apiVersion: 'workspace.devfile.io/v1alpha2',
Expand Down Expand Up @@ -125,6 +129,7 @@ export function getDevWorkspaceClient(_args: Parameters<typeof helper>): ReturnT
getDefaultPluginRegistryUrl: _cheCustomResource => defaultPluginRegistryUrl,
getExternalDevfileRegistries: _cheCustomResource => externalDevfileRegistries,
getInternalRegistryDisableStatus: _cheCustomResource => internalRegistryDisableStatus,
getDashboardLogo: _cheCustomResource => dashboardLogo,
} as IServerConfigApi,
devworkspaceApi: {
create: (_devworkspace, _namespace) =>
Expand Down
5 changes: 5 additions & 0 deletions packages/dashboard-backend/src/routes/api/serverConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function registerServerConfigRoute(instance: FastifyInstance) {
serverConfigApi.getExternalDevfileRegistries(cheCustomResource);
const disableInternalRegistry =
serverConfigApi.getInternalRegistryDisableStatus(cheCustomResource);
const dashboardLogo = serverConfigApi.getDashboardLogo(cheCustomResource);

const serverConfig: api.IServerConfig = {
containerBuild,
Expand Down Expand Up @@ -74,6 +75,10 @@ export function registerServerConfigRoute(instance: FastifyInstance) {
devfileRegistryInternalURL,
};

if (dashboardLogo) {
serverConfig.dashboardLogo = dashboardLogo;
}

return serverConfig;
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,5 @@ exports[`Page header should correctly render the component 1`] = `
>
logout
</button>
<button
onClick={[Function]}
>
change theme
</button>
</header>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import React from 'react';
import renderer from 'react-test-renderer';
import { fireEvent, render, screen } from '@testing-library/react';
import Header from '..';
import { Brand } from '@patternfly/react-core';

jest.mock('../Tools', () => {
const FakeTools = (props: { logout: () => void; changeTheme: () => void }) => (
<React.Fragment>
<button onClick={() => props.logout()}>logout</button>
<button onClick={() => props.changeTheme()}>change theme</button>
</React.Fragment>
);
FakeTools.displayName = 'Tools';
Expand All @@ -30,20 +30,18 @@ jest.mock('../Tools', () => {
describe('Page header', () => {
const mockLogout = jest.fn();
const mockToggleNav = jest.fn();
const mockChangeTheme = jest.fn();

const logoUrl = 'branding/logo';
const logo = <Brand src="branding/logo" alt="Logo" />;
const isHeaderVisible = true;
const history = createHashHistory();

const component = (
<Header
history={history}
isVisible={isHeaderVisible}
logoUrl={logoUrl}
logo={logo}
logout={mockLogout}
toggleNav={mockToggleNav}
changeTheme={mockChangeTheme}
/>
);

Expand Down
9 changes: 3 additions & 6 deletions packages/dashboard-frontend/src/Layout/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@
* Red Hat, Inc. - initial API and implementation
*/

import { Brand, PageHeader } from '@patternfly/react-core';
import { PageHeader } from '@patternfly/react-core';
import { History } from 'history';
import React from 'react';
import { ThemeVariant } from '../themeVariant';
import HeaderTools from './Tools';

type Props = {
history: History;
isVisible: boolean;
logoUrl: string;
logo: React.ReactNode;
logout: () => void;
toggleNav: () => void;
changeTheme: (theme: ThemeVariant) => void;
};
type State = {
isVisible: boolean;
Expand Down Expand Up @@ -50,15 +48,14 @@ export default class Header extends React.PureComponent<Props, State> {
}

public render(): React.ReactElement {
const logo = <Brand src={this.props.logoUrl} alt="Logo" />;
const className = this.state.isVisible ? 'show-header' : 'hide-header';

return (
<PageHeader
style={{ zIndex: 'inherit' }}
className={className}
logoComponent="div"
logo={logo}
logo={this.props.logo}
showNavToggle={true}
onNavToggle={() => this.toggleNav()}
headerTools={
Expand Down
10 changes: 2 additions & 8 deletions packages/dashboard-frontend/src/Layout/Navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { connect, ConnectedProps } from 'react-redux';
import { History, Location, UnregisterCallback } from 'history';
import { Nav } from '@patternfly/react-core';

import { ThemeVariant } from '../themeVariant';
import { AppState } from '../../store';
import NavigationMainList from './MainList';
import NavigationRecentList from './RecentList';
Expand Down Expand Up @@ -47,7 +46,6 @@ export interface NavigationRecentItemObject {

type Props = MappedProps & {
history: History;
theme: ThemeVariant;
};

type State = {
Expand Down Expand Up @@ -112,15 +110,11 @@ export class Navigation extends React.PureComponent<Props, State> {
}

public render(): React.ReactElement {
const { theme, recentWorkspaces, history } = this.props;
const { recentWorkspaces, history } = this.props;
const { activeLocation } = this.state;

return (
<Nav
aria-label="Navigation"
onSelect={selected => this.handleNavSelect(selected)}
theme={theme}
>
<Nav aria-label="Navigation" onSelect={selected => this.handleNavSelect(selected)}>
<NavigationMainList activePath={activeLocation.pathname} />
<NavigationRecentList
workspaces={recentWorkspaces}
Expand Down
12 changes: 2 additions & 10 deletions packages/dashboard-frontend/src/Layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,17 @@ import { PageSidebar } from '@patternfly/react-core';
import { History } from 'history';

import Navigation from './Navigation';
import { ThemeVariant } from './themeVariant';

type Props = {
isManaged: boolean;
isNavOpen: boolean;
history: History;
theme: ThemeVariant;
};

export default class Sidebar extends React.PureComponent<Props> {
public render(): React.ReactElement {
const { isNavOpen, history, theme } = this.props;
const { isNavOpen, history } = this.props;

return (
<PageSidebar
isNavOpen={isNavOpen}
theme={theme}
nav={<Navigation history={history} theme={theme} />}
/>
);
return <PageSidebar isNavOpen={isNavOpen} nav={<Navigation history={history} />} />;
}
}
29 changes: 10 additions & 19 deletions packages/dashboard-frontend/src/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@

import React from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { Page } from '@patternfly/react-core';
import { Brand, Page } from '@patternfly/react-core';
import { History } from 'history';
import { matchPath } from 'react-router';
import Header from './Header';
import Sidebar from './Sidebar';
import StoreErrorsAlert from './StoreErrorsAlert';
import { ThemeVariant } from './themeVariant';
import { AppState } from '../store';
import { lazyInject } from '../inversify.config';
import { IssuesReporterService } from '../services/bootstrap/issuesReporter';
Expand All @@ -30,8 +29,8 @@ import { ROUTE } from '../Routes/routes';
import { selectBranding } from '../store/Branding/selectors';
import { ToggleBarsContext } from '../contexts/ToggleBars';
import { signOut } from '../services/helpers/login';
import { selectDashboardLogo } from '../store/ServerConfig/selectors';

const THEME_KEY = 'theme';
const IS_MANAGED_SIDEBAR = false;

type Props = MappedProps & {
Expand All @@ -41,7 +40,6 @@ type Props = MappedProps & {
type State = {
isSidebarVisible: boolean;
isHeaderVisible: boolean;
theme: ThemeVariant;
};

export class Layout extends React.PureComponent<Props, State> {
Expand All @@ -51,13 +49,9 @@ export class Layout extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);

const theme: ThemeVariant =
(window.sessionStorage.getItem(THEME_KEY) as ThemeVariant) || ThemeVariant.DARK;

this.state = {
isHeaderVisible: true,
isSidebarVisible: true,
theme,
};
}

Expand All @@ -67,11 +61,6 @@ export class Layout extends React.PureComponent<Props, State> {
});
}

private changeTheme(theme: ThemeVariant): void {
this.setState({ theme });
window.sessionStorage.setItem(THEME_KEY, theme);
}

private hideAllBars(): void {
this.setState({
isHeaderVisible: false,
Expand Down Expand Up @@ -112,10 +101,13 @@ export class Layout extends React.PureComponent<Props, State> {
}
}

const { isHeaderVisible, isSidebarVisible, theme } = this.state;
const { history } = this.props;
const { isHeaderVisible, isSidebarVisible } = this.state;
const { history, branding, dashboardLogo } = this.props;

const logoUrl = this.props.branding.logoFile;
const logoSrc =
dashboardLogo !== undefined
? `data:${dashboardLogo.mediatype};base64,${dashboardLogo.base64data}`
: branding.logoFile;

return (
<ToggleBarsContext.Provider
Expand All @@ -129,18 +121,16 @@ export class Layout extends React.PureComponent<Props, State> {
<Header
history={history}
isVisible={isHeaderVisible}
logoUrl={logoUrl}
logo={<Brand src={logoSrc} alt="Logo" />}
logout={() => signOut()}
toggleNav={() => this.toggleNav()}
changeTheme={theme => this.changeTheme(theme)}
/>
}
sidebar={
<Sidebar
isManaged={IS_MANAGED_SIDEBAR}
isNavOpen={isSidebarVisible}
history={history}
theme={theme}
/>
}
isManagedSidebar={IS_MANAGED_SIDEBAR}
Expand All @@ -158,6 +148,7 @@ export class Layout extends React.PureComponent<Props, State> {

const mapStateToProps = (state: AppState) => ({
branding: selectBranding(state),
dashboardLogo: selectDashboardLogo(state),
});

const connector = connect(mapStateToProps);
Expand Down
16 changes: 0 additions & 16 deletions packages/dashboard-frontend/src/Layout/themeVariant.ts

This file was deleted.

4 changes: 4 additions & 0 deletions packages/dashboard-frontend/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ div.main-page-loader .ide-page-loader-content img {
animation-timing-function: linear;
}

.pf-c-page__header-brand-link .pf-c-brand {
height: 3.17rem;
}

@keyframes hide {
0% {
overflow: visible;
Expand Down
Loading

0 comments on commit 47b845e

Please sign in to comment.