Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an ability to customize Eclipse Che logo from the Custom Resource #907

Merged
merged 5 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
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
Loading