feat: Make Frontend Basepath Configurable via APP_PREFIX env variable (#517)

* Make Frontend Basepath Configurable via APP_PREFIX env variable

Signed-off-by: Charles Thao <cthao@redhat.com>

* Fix Cypress tests

Signed-off-by: Charles Thao <cthao@redhat.com>

---------

Signed-off-by: Charles Thao <cthao@redhat.com>
This commit is contained in:
Charles Thao 2025-08-05 14:25:53 -04:00 committed by GitHub
parent f25a22eac3
commit ede4708f1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 24 additions and 12 deletions

View File

@ -6,7 +6,8 @@ const CopyPlugin = require('copy-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const Dotenv = require('dotenv-webpack');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ASSET_PATH = process.env.ASSET_PATH || '/';
const { EnvironmentPlugin } = require('webpack');
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
const IMAGES_DIRNAME = 'images';
const relativeDir = path.resolve(__dirname, '..');
module.exports = (env) => {
@ -153,7 +154,7 @@ module.exports = (env) => {
output: {
filename: '[name].bundle.js',
path: path.resolve(relativeDir, 'dist'),
publicPath: ASSET_PATH,
publicPath: APP_PREFIX,
},
plugins: [
new HtmlWebpackPlugin({
@ -167,6 +168,9 @@ module.exports = (env) => {
patterns: [{ from: './src/images', to: 'images' }],
}),
new ForkTsCheckerWebpackPlugin(),
new EnvironmentPlugin({
APP_PREFIX: process.env.APP_PREFIX || '/workspaces',
}),
],
resolve: {
extensions: ['.js', '.ts', '.tsx', '.jsx'],

View File

@ -12,6 +12,7 @@ const PROXY_PORT = process.env.PROXY_PORT || '4000';
const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http:';
const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED || 'false';
const relativeDir = path.resolve(__dirname, '..');
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
module.exports = merge(common('development'), {
mode: 'development',
@ -19,9 +20,13 @@ module.exports = merge(common('development'), {
devServer: {
host: HOST,
port: PORT,
historyApiFallback: true,
historyApiFallback: {
index: APP_PREFIX + '/index.html',
},
open: [APP_PREFIX],
static: {
directory: path.resolve(relativeDir, 'dist'),
publicPath: APP_PREFIX,
},
client: {
overlay: true,

View File

@ -35,7 +35,7 @@
"cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ",
"cypress:run": "cypress run -b chrome --project src/__tests__/cypress",
"cypress:run:mock": "CY_MOCK=1 npm run cypress:run -- ",
"cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 npm run build",
"cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 APP_PREFIX=/ webpack --config ./config/webpack.prod.js",
"cypress:server": "serve ./dist -p 9001 -s -L",
"prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",
"prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",

View File

@ -41,6 +41,7 @@ export default defineConfig({
env: {
MOCK: !!env.CY_MOCK,
coverage: !!env.CY_COVERAGE,
APP_PREFIX: env.APP_PREFIX || '/workspaces',
codeCoverage: {
exclude: [path.resolve(__dirname, '../../third_party/**')],
},
@ -48,7 +49,7 @@ export default defineConfig({
},
defaultCommandTimeout: 10000,
e2e: {
baseUrl: env.CY_MOCK ? BASE_URL : 'http://localhost:9000',
baseUrl: env.CY_MOCK ? BASE_URL || 'http://localhost:9001' : 'http://localhost:9000',
specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`,
experimentalInteractiveRunEvents: true,
setupNodeEvents(on, config) {

View File

@ -13,7 +13,7 @@ describe('Application', () => {
cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, {
body: mockBFFResponse({ mockWorkspace1 }),
}).as('getWorkspaces');
cy.visit('/');
cy.visit('/workspaces');
cy.wait('@getNamespaces');
cy.wait('@getWorkspaces');
});

View File

@ -6,7 +6,7 @@ describe('WorkspaceDetailsActivity Component', () => {
cy.intercept('GET', 'api/v1/workspaces', {
body: mockBFFResponse(mockWorkspaces),
}).as('getWorkspaces');
cy.visit('/');
cy.visit('/workspaces');
});
// This tests depends on the mocked workspaces data at home page, needs revisit once workspace data fetched from BE

View File

@ -12,6 +12,7 @@ import { useTypedLocation } from '~/app/routerHelper';
import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes';
import { isMUITheme, LOGO_LIGHT } from './const';
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => {
const location = useTypedLocation();
@ -61,7 +62,7 @@ const NavSidebar: React.FC = () => {
<NavItem>
<Brand
className="kubeflow_brand"
src={`${window.location.origin}/images/${LOGO_LIGHT}`}
src={`${window.location.origin}${APP_PREFIX}/images/${LOGO_LIGHT}`}
alt="Kubeflow Logo"
/>
</NavItem>

View File

@ -9,8 +9,8 @@
<title>Kubeflow Workspaces</title>
<meta content="Kubeflow Workspaces" id="appName" name="application-name">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="/images/favicon.ico" rel="icon" type="image/x-icon">
<base href="/">
<link id="icon" href="/images/favicon.ico" rel="icon" type="image/x-icon">
<base href="<%= htmlWebpackPlugin.options.publicPath %>">
</head>
<body>
@ -18,4 +18,4 @@
<div id="root"></div>
</body>
</html>
</html>

View File

@ -6,10 +6,11 @@ import App from './app/App';
const theme = createTheme({ cssVariables: true });
const root = ReactDOM.createRoot(document.getElementById('root')!);
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
root.render(
<React.StrictMode>
<Router>
<Router basename={APP_PREFIX}>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>