Skip to content

Commit

Permalink
Setup branding to be configurable during the build
Browse files Browse the repository at this point in the history
Refactor and enhance branding capabilities.  Now branding is included
from a single directory that contains at least `strings.json`,
`manifest.json`, and `favicon.ico`.  Any other assets may be placed
in the directory and will be copied to the bundled app.

Change summary:
  - Move Konveyor branding assets to a project top-level branding
    directory

  - Remove MTA branding assets

  - Remove profile/branding constants from `env` and client module

  - Embed branding strings and assets in the common module
    - `strings.json` is templated to allow the build to adjust asset
      URL path roots as necessary
    - `brandingAssetPath()`

  - server's index.html generation sources the template strings from
    the common module's branding strings

  - HeaderApp support brand definitions

  - client about and masthead to use 'useBranding' hook

  - `BRANDING` as a relative path is computed from the project root

  - webpack build source branding assets directly from the common module

  - Unit tests, snapshots and jest configs updated as necessary

Jest changes:
  - Use `react-i18next` mock from `client/__mocks__` as a more robust
    mock borrowed from react-i18n repos

  - Move `setupTests.ts` into `client/src/app/test-config` to keep jest
    test config code all in the same directory

Related changes:
  - Upgrade rollup to v4, add new rollup plugins (copy, virtual)

Signed-off-by: Scott J Dickerson <sdickers@redhat.com>
  • Loading branch information
sjd78 committed Jan 17, 2024
1 parent b219623 commit f26c53e
Show file tree
Hide file tree
Showing 42 changed files with 734 additions and 305 deletions.
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
4 changes: 2 additions & 2 deletions client/public/manifest.json → branding/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"short_name": "tackle-ui",
"name": "Tackle UI",
"short_name": "konveyor-ui",
"name": "Konveyor UI",
"icons": [
{
"src": "favicon.ico",
Expand Down
21 changes: 21 additions & 0 deletions branding/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"application": {
"title": "Konveyor",
"name": "Konveyor Tackle UI",
"description": "Konveyor/Tackle UI"
},
"about": {
"displayName": "Konveyor",
"imageSrc": "<%= brandingRoot %>/images/masthead-logo.svg",
"documentationUrl": "https://konveyor.github.io/konveyor/"
},
"masthead": {
"leftBrand": {
"src": "<%= brandingRoot %>/images/masthead-logo.svg",
"alt": "brand",
"height": "60px"
},
"leftTitle": null,
"rightBrand": null
}
}
65 changes: 65 additions & 0 deletions client/__mocks__/react-i18next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* eslint-env node */

// Adapted from https://github.com/i18next/react-i18next/blob/master/example/test-jest/src/__mocks__/react-i18next.js
import React from "react";
import * as reactI18next from "react-i18next";

const hasChildren = (node) =>
node && (node.children || (node.props && node.props.children));

const getChildren = (node) =>
node && node.children ? node.children : node.props && node.props.children;

const renderNodes = (reactNodes) => {
if (typeof reactNodes === "string") {
return reactNodes;
}

return Object.keys(reactNodes).map((key, i) => {
const child = reactNodes[key];
const isElement = React.isValidElement(child);

if (typeof child === "string") {
return child;
}
if (hasChildren(child)) {
const inner = renderNodes(getChildren(child));
return React.cloneElement(child, { ...child.props, key: i }, inner);
}
if (typeof child === "object" && !isElement) {
return Object.keys(child).reduce(
(str, childKey) => `${str}${child[childKey]}`,
""
);
}

return child;
});
};

const useMock = [(k) => k, { changeLanguage: () => new Promise(() => {}) }];
useMock.t = (k) => k;
useMock.i18n = { changeLanguage: () => new Promise(() => {}) };

module.exports = {
// Note: Ignoring "withTranslation" HOC. We don't use it.

Trans: ({ children, i18nKey }) =>
!children
? i18nKey
: Array.isArray(children)
? renderNodes(children)
: renderNodes([children]),

Translation: ({ children }) => children((k) => k, { i18n: {} }),

useTranslation: () => useMock,

// mock if needed
I18nextProvider: reactI18next.I18nextProvider,
initReactI18next: reactI18next.initReactI18next,
setDefaults: reactI18next.setDefaults,
getDefaults: reactI18next.getDefaults,
setI18n: reactI18next.setI18n,
getI18n: reactI18next.getI18n,
};
5 changes: 4 additions & 1 deletion client/config/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const config: JestConfigWithTsJest = {
"@patternfly/react-icons/dist/esm/icons/":
"<rootDir>/__mocks__/fileMock.js",

// other mocks
"react-i18next": "<rootDir>/__mocks__/react-i18next.js",

// match the paths in tsconfig.json
"@app/(.*)": "<rootDir>/src/app/$1",
"@assets/(.*)":
Expand All @@ -44,7 +47,7 @@ const config: JestConfigWithTsJest = {
},

// Code to set up the testing framework before each test file in the suite is executed
setupFilesAfterEnv: ["<rootDir>/src/app/setupTests.ts"],
setupFilesAfterEnv: ["<rootDir>/src/app/test-config/setupTests.ts"],
};

export default config;
31 changes: 21 additions & 10 deletions client/config/webpack.common.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import path from "path";
import { Configuration } from "webpack";
// import CaseSensitivePathsWebpackPlugin from "case-sensitive-paths-webpack-plugin";
import CopyPlugin from "copy-webpack-plugin";
import Dotenv from "dotenv-webpack";
import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin";
import MonacoWebpackPlugin from "monaco-editor-webpack-plugin";

import { brandingAssetPath } from "@konveyor-ui/common";
import { LANGUAGES_BY_FILE_EXTENSION } from "./monacoConstants";

const BG_IMAGES_DIRNAME = "images";
const pathTo = (relativePath: string) => path.resolve(__dirname, relativePath);
const brandingPath = brandingAssetPath();
const manifestPath = path.resolve(brandingPath, "manifest.json");

const BG_IMAGES_DIRNAME = "images";

const config: Configuration = {
entry: {
Expand Down Expand Up @@ -150,22 +153,26 @@ const config: Configuration = {
exports: "xmllint",
},
},
// For monaco-editor-webpack-plugin
{
test: /\.yaml$/,
use: "raw-loader",
},

// For monaco-editor-webpack-plugin --->
{
test: /\.css$/,
include: [pathTo("../../node_modules/monaco-editor")],
use: ["style-loader", "css-loader"],
},
// For monaco-editor-webpack-plugin
{
test: /\.ttf$/,
type: "asset/resource",
},
// <--- For monaco-editor-webpack-plugin
],
},

plugins: [
// new CaseSensitivePathsWebpackPlugin(),
new Dotenv({
systemvars: true,
silent: true,
Expand All @@ -174,15 +181,19 @@ const config: Configuration = {
patterns: [
{
from: pathTo("../public/locales"),
to: pathTo("../dist/locales"),
to: "./locales/",
},
{
from: pathTo("../public/manifest.json"),
to: pathTo("../dist/manifest.json"),
from: pathTo("../public/templates"),
to: "./templates/",
},
{
from: pathTo("../public/templates"),
to: pathTo("../dist/templates"),
from: manifestPath,
to: ".",
},
{
from: brandingPath,
to: "./branding/",
},
],
}),
Expand Down
14 changes: 6 additions & 8 deletions client/config/webpack.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import path from "path";
import { mergeWithRules } from "webpack-merge";
import type { Configuration as WebpackConfiguration } from "webpack";
import type { Configuration as DevServerConfiguration } from "webpack-dev-server";

import CopyPlugin from "copy-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";
import ReactRefreshTypeScript from "react-refresh-typescript";
Expand All @@ -14,12 +13,14 @@ import {
KONVEYOR_ENV,
SERVER_ENV_KEYS,
proxyMap,
brandingStrings,
brandingAssetPath,
} from "@konveyor-ui/common";
import { stylePaths } from "./stylePaths";
import commonWebpackConfiguration from "./webpack.common";

const brandType = KONVEYOR_ENV.PROFILE;
const pathTo = (relativePath: string) => path.resolve(__dirname, relativePath);
const faviconPath = path.resolve(brandingAssetPath(), "favicon.ico");

interface Configuration extends WebpackConfiguration {
devServer?: DevServerConfiguration;
Expand Down Expand Up @@ -75,10 +76,6 @@ const config: Configuration = mergeWithRules({
include: [...stylePaths],
use: ["style-loader", "css-loader"],
},
{
test: /\.yaml$/,
use: "raw-loader",
},
],
},

Expand All @@ -96,15 +93,16 @@ const config: Configuration = mergeWithRules({
},
],
}),

// index.html generated at compile time to inject `_env`
new HtmlWebpackPlugin({
filename: "index.html",
template: pathTo("../public/index.html.ejs"),
templateParameters: {
_env: encodeEnv(KONVEYOR_ENV, SERVER_ENV_KEYS),
brandType,
branding: brandingStrings,
},
favicon: pathTo(`../public/${brandType}-favicon.ico`),
favicon: faviconPath,
minify: {
collapseWhitespace: false,
keepClosingSlash: true,
Expand Down
11 changes: 4 additions & 7 deletions client/config/webpack.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";

import { KONVEYOR_ENV } from "@konveyor-ui/common";
import { brandingAssetPath } from "@konveyor-ui/common";
import { stylePaths } from "./stylePaths";
import commonWebpackConfiguration from "./webpack.common";

const brandType = KONVEYOR_ENV.PROFILE;
const pathTo = (relativePath: string) => path.resolve(__dirname, relativePath);
const faviconPath = path.resolve(brandingAssetPath(), "favicon.ico");

const config = merge<Configuration>(commonWebpackConfiguration, {
mode: "production",
Expand All @@ -36,10 +36,6 @@ const config = merge<Configuration>(commonWebpackConfiguration, {
include: [...stylePaths],
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.yaml$/,
use: "raw-loader",
},
],
},

Expand All @@ -56,11 +52,12 @@ const config = merge<Configuration>(commonWebpackConfiguration, {
new webpack.EnvironmentPlugin({
NODE_ENV: "production",
}),

// index.html generated at runtime via the express server to inject `_env`
new HtmlWebpackPlugin({
filename: "index.html.ejs",
template: `!!raw-loader!${pathTo("../public/index.html.ejs")}`,
favicon: pathTo(`../public/${brandType}-favicon.ico`),
favicon: faviconPath,
minify: {
collapseWhitespace: false,
keepClosingSlash: true,
Expand Down
12 changes: 3 additions & 9 deletions client/public/index.html.ejs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<% if (brandType == 'mta') { %>
<title>Migration Toolkit for Applications</title>
<meta name="description" content="MTA UI" />
<meta id="appName" name="application-name" content="MTA UI" />
<% } else { %>
<title>Konveyor</title>
<meta name="description" content="Konveyor/Tackle UI" />
<meta id="appName" name="application-name" content="Konveyor Tackle UI" />
<% } %>
<title><%= branding.application.title %></title>
<meta name="description" content="<%= branding.application.description %>" />
<meta id="appName" name="application-name" content="<%= branding.application.name %>" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
Expand Down
4 changes: 2 additions & 2 deletions client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"about": {
"about": "About",
"bottom1": "{{brandType}} is a project within the",
"bottom2": "For more information, refer to",
"bottom1": "{{brandType}} is a project within the <2>Konveyor community</2>.",
"bottom2": "For more information, refer to <1>{{brandType}} documentation</1>.",
"description": "{{brandType}} allows users to maintain their portfolio of applications with a full set of metadata and to assess their suitability for modernization leveraging a questionnaire based approach.",
"iconLibrary": "The Icon Library used in this project is a derivative of the <2>Standard Icons library</2> by <5>Red Hat</5>, used under <8>CC BY 4.0</8>",
"introduction": "{{brandType}} is a collection of tools that supports large-scale application modernization and migration projects to Kubernetes."
Expand Down
Binary file removed client/public/mta-favicon.ico
Binary file not shown.
6 changes: 0 additions & 6 deletions client/src/app/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,8 @@ import {
import { EffortEstimate, ProposedAction, Risk } from "@app/api/models";
import { ENV } from "./env";

export enum BrandType {
Konveyor = "konveyor",
MTA = "mta",
}

export const isAuthRequired = ENV.AUTH_REQUIRED !== "false";
export const uploadLimit = ENV.UI_INGRESS_PROXY_BODY_SIZE || "500m";
export const APP_BRAND = ENV.PROFILE as BrandType;
export const isRWXSupported = ENV.RWX_SUPPORTED === "true";

export const DEFAULT_SELECT_MAX_HEIGHT = 200;
Expand Down
12 changes: 12 additions & 0 deletions client/src/app/hooks/useBranding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BrandingStrings, brandingStrings } from "@konveyor-ui/common";

/**
* Wrap the branding strings in a hook so components access it in a standard
* React way instead of a direct import. This allows the branding implementation
* to change in future with a minimal amount of refactoring in existing components.
*/
export const useBranding = (): BrandingStrings => {
return brandingStrings;
};

export default useBranding;
33 changes: 0 additions & 33 deletions client/src/app/images/avatar.svg

This file was deleted.

Binary file removed client/src/app/images/konveyor-logo-white-text.png
Binary file not shown.
Loading

0 comments on commit f26c53e

Please sign in to comment.