From 40127273db413a0bd688a8855ff43e2538bb4cc3 Mon Sep 17 00:00:00 2001 From: MariaAga Date: Tue, 8 Oct 2024 16:03:49 +0100 Subject: [PATCH] Fixes #37882 - Remove @theforeman/vendor and seperate javascript and react app imports --- Procfile | 4 +- app/helpers/reactjs_helper.rb | 5 - app/views/layouts/base.html.erb | 2 +- config/webpack.config.js | 105 ++++++++++++------ config/webpack.vendor.js | 101 ++++++++++++++++- developer_docs/foreman_dev_setup.asciidoc | 2 +- package.json | 90 +++++++++++++-- .../lint_generic_config.js | 4 +- script/lint/lint_core_config.js | 3 + script/npm_lint_plugins.js | 5 +- webpack/assets/javascripts/bundle.js | 7 ++ webpack/assets/javascripts/exportAll.js | 4 +- webpack/assets/javascripts/jquery.js | 14 +++ .../javascripts/react_app/common/helpers.js | 9 ++ .../react_app/common/urlHelpers.js | 2 +- .../react_app/common/urlHelpers.test.js | 3 +- .../HostDetails/ActionsBar/actions.js | 3 +- .../HostDetails/ActionsBar/index.js | 3 +- .../components/HostStatuses/index.js | 2 +- .../HostsIndex/BulkActions/bulkDelete.js | 3 +- .../ImpersonateIcon/ImpersonateIconActions.js | 2 +- .../common/forms/OrderableSelect/index.js | 2 +- .../javascripts/react_app/redux/API/API.js | 2 +- .../redux/actions/notifications/index.js | 2 +- .../RegistrationCommandsPageActions.js | 2 +- .../RegistrationCommandsPageHelpers.js | 2 +- .../components/Actions.js | 2 +- .../react_app/routes/RoutingService.js | 2 +- .../common/EmptyPage/RedirectedEmptyPage.js | 2 +- webpack/core_test_setup.js | 11 ++ webpack/jest.config.js | 17 +-- webpack/resolveNodeModule.js | 62 ----------- 32 files changed, 320 insertions(+), 159 deletions(-) create mode 100644 webpack/assets/javascripts/jquery.js delete mode 100644 webpack/resolveNodeModule.js diff --git a/Procfile b/Procfile index cc0b07a963..5418acf42f 100644 --- a/Procfile +++ b/Procfile @@ -2,6 +2,4 @@ # If you wish to use a different server then the default, use e.g. `export RAILS_STARTUP='puma -w 3 -p 3000 --preload'` rails: [ -n "$RAILS_STARTUP" ] && env PRY_WARNING=1 $RAILS_STARTUP || [ -n "$BIND" ] && bin/rails server -b $BIND || env PRY_WARNING=1 bin/rails server -# you can use WEBPACK_OPTS to customize webpack server, e.g. 'WEBPACK_OPTS=--progress' foreman start ' -# filter out webpack options that are commonly used but not supported by webpack 5 and not needed in the new configutation as webpack is not run as a server anymore -webpack: FILTERED_WEBPACK_OPTS=$(echo $WEBPACK_OPTS | sed -e 's/--key [^ ]*//g' -e 's/--public [^ ]*//g' -e 's/--https [^ ]*//g' -e 's/--cert [^ ]*//g' -e 's/--cacert [^ ]*//g' -e 's/--host [^ ]*//g') && [ -n "$NODE_ENV" ] && npx webpack --config config/webpack.config.js --watch $FILTERED_WEBPACK_OPTS || env NODE_ENV=development npx webpack --config config/webpack.config.js --watch $FILTERED_WEBPACK_OPTS \ No newline at end of file +webpack: env NODE_OPTIONS=${NODE_OPTIONS:---max-old-space-size=8096} NODE_ENV=${NODE_ENV:-development} npx webpack --config config/webpack.config.js --watch $WEBPACK_OPTS \ No newline at end of file diff --git a/app/helpers/reactjs_helper.rb b/app/helpers/reactjs_helper.rb index 7c8425f5b2..6afdc4f5ec 100644 --- a/app/helpers/reactjs_helper.rb +++ b/app/helpers/reactjs_helper.rb @@ -36,11 +36,6 @@ def get_webpack_chunk(name, extension) data['assetsByChunkName'][name]&.find { |value| value.end_with?(".#{extension}") } end - def get_webpack_foreman_vendor_js - foreman_vendor_js = get_webpack_chunk('foreman-vendor', 'js') - javascript_include_tag("/webpack/#{foreman_vendor_js}") - end - def get_webpack_foreman_vendor_css foreman_vendor_css = get_webpack_chunk('vendorStyles', 'css') stylesheet_link_tag("/webpack/#{foreman_vendor_css}") diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index 1b94dd0d26..927f57bd08 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -36,7 +36,7 @@ - <%= get_webpack_foreman_vendor_js %> + <%= javascript_include_tag("/webpack/#{get_webpack_chunk('vendor', 'js')}") %> <%= javascript_include_tag("/webpack/#{get_webpack_chunk('bundle', 'js')}") %> <%= javascript_include_tag("/webpack/#{get_webpack_chunk('reactExports', 'js')}") %> diff --git a/config/webpack.config.js b/config/webpack.config.js index cd249d3491..5c16d1799a 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -4,17 +4,33 @@ var path = require('path'); var webpack = require('webpack'); const dotenv = require('dotenv'); +const root = path.resolve(__dirname, '..'); dotenv.config(); -var ForemanVendorPlugin = require('@theforeman/vendor') - .WebpackForemanVendorPlugin; var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; var vendorEntry = require('./webpack.vendor'); var fs = require('fs'); const { ModuleFederationPlugin } = require('webpack').container; var pluginUtils = require('../script/plugin_webpack_directories'); -var { generateExportsFile }= require('../webpack/assets/javascripts/exportAll'); +var { + generateExportsFile, +} = require('../webpack/assets/javascripts/exportAll'); var CompressionPlugin = require('compression-webpack-plugin'); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const packageJsonPath = path.resolve(root, 'package.json'); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); +const dependencies = packageJson.dependencies || {}; +const devDependencies = packageJson.devDependencies || {}; +const allDependencies = { ...dependencies, ...devDependencies }; +const shared = isPlugin => + Object.keys(allDependencies).map(dep => ({ + [dep]: { + eager: !isPlugin, // core should load all dependencies eagerly so they will be available for plugins + singleton: true, + requiredVersion: allDependencies[dep], + import: isPlugin ? false : dep, + }, + })); class AddRuntimeRequirement { // to avoid "webpackRequire.l is not a function" error @@ -33,7 +49,7 @@ class AddRuntimeRequirement { } const supportedLocales = () => { - const localeDir = path.join(__dirname, '..', 'locale'); + const localeDir = path.join(root, 'locale'); // Find all files in ./locale/* const localesFiles = fs.readdirSync(localeDir); @@ -80,14 +96,31 @@ const commonConfig = function() { os: require.resolve('os-browserify'), }, alias: { + 'patternfly-react$': path.resolve( + __dirname, + '..', + 'node_modules/patternfly-react/dist/js/index.js' + ), // to avoid circular dependency in dist/esm + '/node_modules/jquery': path.resolve( + __dirname, + '..', + 'webpack/assets/javascripts/jquery.js' + ), + jquery: path.resolve(root, 'webpack/assets/javascripts/jquery.js'), foremanReact: path.join( __dirname, '../webpack/assets/javascripts/react_app' ), + 'react/jsx-runtime': 'react/jsx-runtime.js', // for react-dnd + 'react/jsx-dev-runtime': 'react/jsx-dev-runtime.js', // for react-dnd + 'datatables.net': path.resolve( + root, + 'node_modules/datatables.net/js/jquery.dataTables.js' + ), // otherwise we get datatables.net-bs Cannot read properties of undefined (reading 'classes') since dataTables.mjs is a module. }, }, resolveLoader: { - modules: [path.resolve(__dirname, '..', 'node_modules')], + modules: [path.resolve(root, 'node_modules')], }, module: { rules: [ @@ -118,9 +151,6 @@ const commonConfig = function() { ], }, plugins: [ - new ForemanVendorPlugin({ - mode, - }), new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify(mode), @@ -148,30 +178,32 @@ const commonConfig = function() { const coreConfig = function() { var config = commonConfig(); var manifestFilename = 'manifest.json'; - var bundleEntry = path.join( - __dirname, - '..', - 'webpack/assets/javascripts/bundle.js' - ); - config.context = path.resolve(__dirname, '..'); + var bundleEntry = path.join(root, 'webpack/assets/javascripts/bundle.js'); + config.context = path.resolve(root); if (config.mode == 'production') { - var chunkFilename = '[name]-[chunkhash].js' + var chunkFilename = '[name]-[chunkhash].js'; } else { - var chunkFilename = '[name].js' + var chunkFilename = '[name].js'; } config.entry = { + /* keep bundle entry files and reactExports seperate to avoid late loading issues of mixed files, import in react_app only from react_app and node_modules */ bundle: { import: bundleEntry, dependOn: ['vendor', 'reactExports'] }, + reactExports: { + import: path.join( + root, + 'webpack/assets/javascripts/all_react_app_exports.js' + ), + dependOn: 'vendor', + }, vendor: vendorEntry, - reactExports: path.join( - __dirname, - '..', - 'webpack/assets/javascripts/all_react_app_exports.js' - ), - vendorStyles: path.join(__dirname, '..', 'webpack/assets/javascripts/react_app/common/scss/vendor-core.scss'), + vendorStyles: path.join( + root, + 'webpack/assets/javascripts/react_app/common/scss/vendor-core.scss' + ), }; config.output = { - path: path.join(__dirname, '..', 'public', 'webpack'), + path: path.join(root, 'public', 'webpack'), publicPath: '/webpack/', library: { name: ['TheForeman', '[name]'], @@ -181,9 +213,11 @@ const coreConfig = function() { }; var plugins = config.plugins; + plugins.push(new MiniCssExtractPlugin()); plugins.push( new ModuleFederationPlugin({ name: 'foremanReact', + shared: shared(false), }) ); plugins.push( @@ -191,11 +225,10 @@ const coreConfig = function() { filename: manifestFilename, }) ); - plugins.push( - new MiniCssExtractPlugin() - ); + plugins.push(new MiniCssExtractPlugin()); config.plugins = plugins; var rules = config.module.rules; + rules.push({ test: /\.(sa|sc|c)ss$/, exclude: /vendor-core/i, @@ -213,11 +246,7 @@ const coreConfig = function() { }); rules.push({ test: /vendor-core/i, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - 'sass-loader', - ], + use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], }); config.module.rules = rules; return config; @@ -239,7 +268,9 @@ const pluginConfig = function(plugin) { config.externals = function({ request }, callback) { if (/^foremanReact(\/.*)?$/.test(request)) { const prefix = 'var TheForeman.reactExports.'; - const newPath = prefix + convertImportStatement(request.substring('foremanReact'.length)); + const newPath = + prefix + + convertImportStatement(request.substring('foremanReact'.length)); return callback(null, newPath); } return callback(); @@ -257,7 +288,7 @@ const pluginConfig = function(plugin) { if (config.mode == 'production') { var outputPath = path.join(pluginRoot, 'public', 'webpack', pluginName); - var chunkFilename = '[name]-[chunkhash].js' + var chunkFilename = '[name]-[chunkhash].js'; } else { var outputPath = path.join( __dirname, @@ -266,7 +297,7 @@ const pluginConfig = function(plugin) { 'webpack', pluginName ); - var chunkFilename = '[name].js' + var chunkFilename = '[name].js'; } config.output = { path: outputPath, @@ -276,7 +307,7 @@ const pluginConfig = function(plugin) { }; var configModules = config.resolve.modules || []; // make webpack to resolve modules from core first - configModules.unshift(path.resolve(__dirname, '..', 'node_modules')); + configModules.unshift(path.resolve(root, 'node_modules')); // add plugin's node_modules to the reslver list configModules.push(path.resolve(pluginRoot, 'node_modules')); configModules.push('node_modules/'); @@ -290,8 +321,10 @@ const pluginConfig = function(plugin) { name: pluginName, filename: pluginName + '_remoteEntry.js', exposes: pluginEntries, + shared: shared(true), }) ); + config.plugins = plugins; var rules = config.module.rules; rules.push({ diff --git a/config/webpack.vendor.js b/config/webpack.vendor.js index d6f31b3222..001cfce756 100644 --- a/config/webpack.vendor.js +++ b/config/webpack.vendor.js @@ -1 +1,100 @@ -module.exports = ['react-intl', 'intl']; +/* eslint-disable */ + +module.exports = [ + 'intl', + /** + * React related + */ + '@apollo/client', + '@apollo/client/link/batch-http', + '@reduxjs/toolkit', + 'core-js/shim', + 'regenerator-runtime/runtime', + 'formik', + 'rc-input-number', + 'react', + 'react-ace', + 'react-dom', + 'react-dnd', + 'react-dnd-html5-backend', + 'react-debounce-input', + 'react-diff-view', + 'react-ellipsis-with-tooltip', + 'react-onclickoutside', + 'react-password-strength', + 'react-router-dom', + 'react-router-bootstrap', + 'react-loading-skeleton', + 'react-redux', + 'redux', + 'redux-logger', + 'redux-thunk', + 'reselect', + 'prop-types', + 'classnames', + 'seamless-immutable', + 'connected-react-router', + 'react-helmet', + 'react-intl', + + /** + * Patternfly related + */ + 'patternfly-react', + 'patternfly-react-extensions', + '@patternfly/react-core', + '@patternfly/react-icons', + '@patternfly/react-table', + '@patternfly/react-tokens', + '@patternfly/react-styles', + '@patternfly/react-charts', + + /** + * ace-builds related + */ + 'ace-builds', + 'ace-builds/src-noconflict/ace', + 'ace-builds/src-noconflict/ext-language_tools', + 'ace-builds/src-noconflict/mode-ruby', + 'ace-builds/src-noconflict/mode-json', + 'ace-builds/src-noconflict/mode-sh', + 'ace-builds/src-noconflict/mode-html_ruby', + 'ace-builds/src-noconflict/mode-xml', + 'ace-builds/src-noconflict/mode-yaml', + 'ace-builds/src-noconflict/theme-github', + 'ace-builds/src-noconflict/theme-monokai', + 'ace-builds/src-noconflict/keybinding-vim', + 'ace-builds/src-noconflict/keybinding-emacs', + 'ace-builds/src-min-noconflict/ext-searchbox', + + /** + * UUID + */ + 'uuid', + 'uuid/v1', + 'uuid/v3', + 'uuid/v4', + 'uuid/v5', + + 'jstz', + 'diff', + + /** + * Other packages + */ + 'history', + 'number_helpers', + 'lodash', + 'axios', + 'file-saver', + 'humanize-duration', + 'unidiff', + 'urijs', + 'yup', + 'select2', + 'multiselect', + '@novnc/novnc/core/rfb', + + // '@webcomponents/webcomponentsjs/webcomponents-bundle', + // '@webcomponents/webcomponentsjs/custom-elements-es5-adapter', +]; diff --git a/developer_docs/foreman_dev_setup.asciidoc b/developer_docs/foreman_dev_setup.asciidoc index f91cb94f04..e85be57626 100644 --- a/developer_docs/foreman_dev_setup.asciidoc +++ b/developer_docs/foreman_dev_setup.asciidoc @@ -150,7 +150,7 @@ list of options that can be set in an .env file (as well as in the cli call): * `NOTIFICATIONS_POLLING` is the notification polling interval in ms. -* `NODE_OPTIONS` is used for npm actions, when running plugin tests with `npm run test:plugins` we add `--max-old-space-size=8192` to the options, but it can be overridden by writing your own `NODE_OPTIONS` +* `NODE_OPTIONS` is used for npm actions, when running plugin tests with `npm run test:plugins` we add `--max-old-space-size=8192` to the options, but it can be overridden by writing your own `NODE_OPTIONS`. It is also added for webpack compile (`foreman start webpack`). === Resetting password diff --git a/package.json b/package.json index 46fbc59005..1d78303225 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "description": "Foreman isn't really a node module, these are just dependencies needed to build the webpack bundle. 'dependencies' are the asset libraries in use and 'devDependencies' are used for the build process.", "private": true, "engines": { - "node": ">=18.0.0 <21.0.0" + "node": ">=18.0.0 <21.0.0", + "npm": ">=8.0.0" }, "scripts": { "lint": "eslint ./webpack ./script", @@ -22,24 +23,83 @@ "analyze": "./script/webpack-analyze" }, "dependencies": { + "@apollo/client": "^3.3.7", "@module-federation/utilities": "^1.7.0", + "@novnc/novnc": "1.3.0", "@patternfly/patternfly": "^5.4.2", + "@patternfly/react-charts": "~6.94.15", + "@patternfly/react-core": "^5.2.0", + "@patternfly/react-icons": "^5.2.0", + "@patternfly/react-styles": "^5.2.0", + "@patternfly/react-table": "^5.2.0", + "@patternfly/react-tokens": "^5.2.0", + "@reduxjs/toolkit": "^1.6.0", + "@spice-project/spice-html5": "^0.2.1", "@theforeman/vendor": "^15.0.0", + "@webcomponents/webcomponentsjs": "^2.2.10", + "ace-builds": "^1.4.13", + "axios": "^0.21.1", "bootstrap-sass": "^3.4.3", + "classnames": "^2.2.5", + "connected-react-router": "6.6.1", + "core-js": "^2.5.7", "datatables.net-bs": "1.13.5", + "datatables.net-dt": "1.13.5", + "datatables.net": "1.13.5", + "diff": "5.2.0", "dsmorse-gridster": "^0.8.0", + "file-saver": "^2.0.1", + "formik": "^1.5.8", "graphql-tag": "^2.11.0", + "graphql": "^15.5.0", + "history": "^4.7.2", + "humanize-duration": "3.27.0", "intl": "~1.2.5", + "ipaddr.js": "~1.2.0", "jed": "^1.1.1", + "jquery-ujs": "~1.2.0", + "jquery": "^3.7.1", "js-cookie": "^3.0.5", + "jstz": "~1.0.7", + "lodash": "^4.17.14", "multiselect": "~0.9.12", + "number_helpers": "^0.1.1", "os-browserify": "^0.3.0", "patternfly-react-extensions": "^3.0.15", "patternfly-react": "^2.40.0", "patternfly": "^3.59.5", + "prop-types": "^15.6.0", + "rc-input-number": "^6.0.0", + "react-ace": "^9.5.0", + "react-debounce-input": "^3.2.0", "react-diff-view": "^2.6.0", + "react-dnd-html5-backend": "^14.0.0", + "react-dnd": "^14.0.2", + "react-dom": "^16.8.1", + "react-bootstrap": "^0.32.0", + "react-ellipsis-with-tooltip": "^1.0.8", + "react-helmet": "^6.1.0", "react-intl": "^2.8.0", - "select2": "4.0.12" + "react-loading-skeleton": "^1.1.2", + "react-onclickoutside": "^6.6.2", + "react-password-strength": "^2.4.0", + "react-redux": "^7.1.0", + "react-router-bootstrap": "^0.25.0", + "react-router-dom": "^5.1.2", + "react-router": "^5.3.4", + "react": "^16.9.0", + "redux-logger": "^2.8.1", + "redux-thunk": "^2.2.0", + "redux": "^4.0.4", + "regenerator-runtime": "^0.13.3", + "reselect": "^3.0.1", + "sanitize-html": "2.3.2", + "seamless-immutable": "^7.1.2", + "select2": "4.0.12", + "unidiff": "^1.0.0", + "urijs": "^1.19.4", + "uuid": "^3.3.2", + "yup": "^0.29.3" }, "devDependencies": { "@apollo/react-testing": "^4.0.0", @@ -49,7 +109,6 @@ "@testing-library/react": "^10.0.2", "@testing-library/react-hooks": "^3.4.2", "@theforeman/builder": "^15.0.0", - "@theforeman/vendor-core": "^15.0.0", "@types/jest": "<27.0.0", "argv-parse": "^1.0.1", "axios-mock-adapter": "^1.1.7", @@ -67,11 +126,11 @@ "enzyme-to-json": "^3.4.3", "eslint": "^6.7.2", "eslint-import-resolver-alias": "^1.1.2", - "eslint-plugin-spellcheck": "0.0.17", - "eslint-plugin-patternfly-react": "0.2.0", "eslint-plugin-jquery": "^1.5.1", + "eslint-plugin-patternfly-react": "0.2.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react-hooks": "^2.1.1", + "eslint-plugin-spellcheck": "0.0.17", "graphql": "^15.5.0", "identity-obj-proxy": "^3.0.0", "jest": "^26.4.0", @@ -92,12 +151,27 @@ "stylelint": "^9.3.0", "stylelint-config-standard": "^18.0.0", "tabbable": "^6.2.0", - "victory-core": "~36.8.6", - "victory-legend": "~36.8.6", - "victory-pie": "~36.8.6", "webpack": "^5.75.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^5.0.1", "webpack-stats-plugin": "^1.0.3" + }, + "overrides": { + "victory-core": "36.8.6", + "victory-pie": "36.8.6", + "victory-area": "36.8.6", + "victory-axis": "36.8.6", + "victory-bar": "36.8.6", + "victory-chart": "36.8.6", + "victory-create-container": "36.8.6", + "victory-cursor-container": "36.8.6", + "victory-group": "36.8.6", + "victory-legend": "36.8.6", + "victory-line": "36.8.6", + "victory-scatter": "36.8.6", + "victory-stack": "36.8.6", + "victory-tooltip": "36.8.6", + "victory-voronoi-container": "36.8.6", + "victory-zoom-container": "36.8.6" } } diff --git a/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js b/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js index cd0d40ce54..db34d33645 100644 --- a/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js +++ b/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js @@ -1,6 +1,4 @@ -const foremanVendorRelative = './node_modules/@theforeman/vendor-core/'; - -const packageJsonDirectories = ['./', foremanVendorRelative]; +const packageJsonDirectories = ['./']; module.exports = { plugins: [ diff --git a/script/lint/lint_core_config.js b/script/lint/lint_core_config.js index dfcb7ed65d..2dd8ea08b1 100644 --- a/script/lint/lint_core_config.js +++ b/script/lint/lint_core_config.js @@ -38,6 +38,7 @@ module.exports = { 'datacenter', 'datastore', 'datastores', + 'datatables', 'datepicker', 'datetime', 'datetimepicker', @@ -50,6 +51,7 @@ module.exports = { 'dow', 'dropdown', 'dropdowns', + 'dsmorse', 'ec2', 'erb', 'fieldset', @@ -175,6 +177,7 @@ module.exports = { 'txt', 'typeahead', 'ubuntu', + 'ujs', 'uncheck', 'unencrypted', 'unmount', diff --git a/script/npm_lint_plugins.js b/script/npm_lint_plugins.js index d06403638c..fcb05698a8 100755 --- a/script/npm_lint_plugins.js +++ b/script/npm_lint_plugins.js @@ -30,11 +30,8 @@ try { console.error(`Error: ${error.message}`); } -const packageJsonDirectories = [ - './', - './node_modules/@theforeman/vendor-core/', -]; const errors = []; +const packageJsonDirectories = ['./']; dirsKeys.forEach(dirsKey => { let exitCode; const pluginPath = allPluginDirs[dirsKey]; diff --git a/webpack/assets/javascripts/bundle.js b/webpack/assets/javascripts/bundle.js index 8074352d34..cab68adb40 100644 --- a/webpack/assets/javascripts/bundle.js +++ b/webpack/assets/javascripts/bundle.js @@ -1,6 +1,9 @@ import 'core-js/shim'; import 'regenerator-runtime/runtime'; import Cookies from 'js-cookie'; +import jstz from 'jstz'; +import ipaddr from 'ipaddr.js'; +import diff from 'diff'; import compute from './foreman_compute_resource'; import componentRegistry from './react_app/components/componentRegistry'; @@ -59,3 +62,7 @@ window.tfm = Object.assign(window.tfm || {}, { lookupKeys, Cookies, }); + +window.jstz = jstz; +window.ipaddr = ipaddr; +window.diff = diff; diff --git a/webpack/assets/javascripts/exportAll.js b/webpack/assets/javascripts/exportAll.js index feb2aaa33a..c050fab76d 100644 --- a/webpack/assets/javascripts/exportAll.js +++ b/webpack/assets/javascripts/exportAll.js @@ -20,9 +20,11 @@ function generateExports(directoryPath, exportFileContent = '') { !dirent.name.endsWith('.fixtures.js') && !dirent.name.endsWith('mockRequests.js') && !dirent.name.endsWith('vendor-core.scss') && + !dirent.name.endsWith('jquery.js') && !fileNameWithoutExtension.includes('TestHelper') && !fileNameWithoutExtension.includes('testHelper') && - !fileNameWithoutExtension.includes('APITestSetup') + !fileNameWithoutExtension.includes('APITestSetup') && + !fileNameWithoutExtension.includes('vendor-core.scss') ) { let relativeFilePath = path.relative( __dirname, diff --git a/webpack/assets/javascripts/jquery.js b/webpack/assets/javascripts/jquery.js new file mode 100644 index 0000000000..fef4929489 --- /dev/null +++ b/webpack/assets/javascripts/jquery.js @@ -0,0 +1,14 @@ +// to avoid webpack alias loop +const jquery = require('../../../node_modules/jquery'); + +window.$ = jquery; +window.jQuery = jquery; +window.jquery = jquery; + +module.exports = jquery; + +require('jquery-ujs'); +require('multiselect'); +require('select2'); +require('datatables.net-bs'); +require('dsmorse-gridster/dist/jquery.dsmorse-gridster'); diff --git a/webpack/assets/javascripts/react_app/common/helpers.js b/webpack/assets/javascripts/react_app/common/helpers.js index da7067197e..fe63f1e041 100644 --- a/webpack/assets/javascripts/react_app/common/helpers.js +++ b/webpack/assets/javascripts/react_app/common/helpers.js @@ -204,6 +204,13 @@ export const formatDateTime = date => { // generates an absolute, needed in case of running Foreman from a subpath export const foremanUrl = path => `${window.URL_PREFIX}${path}`; +export const visit = url => { + window.location.href = url; +}; + +export const reloadPage = () => { + window.location.reload(); +}; export default { isoCompatibleDate, bindMethods, @@ -224,4 +231,6 @@ export default { formatDateTime, foremanUrl, getWikiURL, + visit, + reloadPage, }; diff --git a/webpack/assets/javascripts/react_app/common/urlHelpers.js b/webpack/assets/javascripts/react_app/common/urlHelpers.js index 8d15d9dda8..de5e54227f 100644 --- a/webpack/assets/javascripts/react_app/common/urlHelpers.js +++ b/webpack/assets/javascripts/react_app/common/urlHelpers.js @@ -1,6 +1,6 @@ import URI from 'urijs'; -import { visit } from '../../foreman_navigation'; +import { visit } from './helpers'; /** * Build a url from given controller, action and id diff --git a/webpack/assets/javascripts/react_app/common/urlHelpers.test.js b/webpack/assets/javascripts/react_app/common/urlHelpers.test.js index efd5e44616..471143a370 100644 --- a/webpack/assets/javascripts/react_app/common/urlHelpers.test.js +++ b/webpack/assets/javascripts/react_app/common/urlHelpers.test.js @@ -1,5 +1,5 @@ import { mockWindowLocation } from './testHelpers'; -import { visit } from '../../foreman_navigation'; +import { visit } from './helpers'; import { urlBuilder, urlWithSearch, @@ -12,6 +12,7 @@ import { exportURL, } from './urlHelpers'; +jest.mock('./helpers') describe('urlBuilder', () => { const controller = 'testController'; const action = 'testAction'; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js index c362380c2d..fc39b96f1a 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js @@ -1,7 +1,6 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { visit } from '../../../../foreman_navigation'; -import { foremanUrl } from '../../../common/helpers'; +import { visit, foremanUrl } from '../../../common/helpers'; import { sprintf, translate as __ } from '../../../common/I18n'; import { openConfirmModal } from '../../ConfirmModal'; import { APIActions } from '../../../redux/API'; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js index c3b4622d4f..07dc6db5f0 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js @@ -16,10 +16,9 @@ import { BuildIcon, TerminalIcon, } from '@patternfly/react-icons'; -import { visit } from '../../../../foreman_navigation'; import { translate as __ } from '../../../common/I18n'; import { selectKebabItems } from './Selectors'; -import { foremanUrl } from '../../../common/helpers'; +import { visit, foremanUrl } from '../../../common/helpers'; import { cancelBuild, deleteHost, isHostTurnOn } from './actions'; import { useForemanSettings, diff --git a/webpack/assets/javascripts/react_app/components/HostStatuses/index.js b/webpack/assets/javascripts/react_app/components/HostStatuses/index.js index 3849896ca1..afd5dd7886 100644 --- a/webpack/assets/javascripts/react_app/components/HostStatuses/index.js +++ b/webpack/assets/javascripts/react_app/components/HostStatuses/index.js @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import { useSelector } from 'react-redux'; import { PageSection, Grid, GridItem, Title } from '@patternfly/react-core'; -import { foremanUrl } from '../../../foreman_tools'; +import { foremanUrl } from '../../common/helpers'; import { useAPI } from '../../common/hooks/API/APIHooks'; import Status from './Status'; import StatusSkeleton from './Status/StatusSkeleton'; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js index e6d04bff2a..be546dc337 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js @@ -1,7 +1,6 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { visit } from '../../../../foreman_navigation'; -import { foremanUrl } from '../../../common/helpers'; +import { visit, foremanUrl } from '../../../common/helpers'; import { sprintf, translate as __ } from '../../../common/I18n'; import { openConfirmModal } from '../../ConfirmModal'; import { APIActions } from '../../../redux/API'; diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js b/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js index 0a8e01050f..cca7c5d4c2 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js +++ b/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js @@ -1,5 +1,5 @@ import { API } from '../../../../redux/API'; -import { foremanUrl } from '../../../../../foreman_tools'; +import { foremanUrl } from '../../../../common/helpers'; import { addToast } from '../../../ToastsList'; diff --git a/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js b/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js index 533ab841ae..fc39637c90 100644 --- a/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js +++ b/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { DndProvider } from 'react-dnd'; -import HTML5Backend from 'react-dnd-html5-backend'; +import { HTML5Backend } from 'react-dnd-html5-backend'; import OrderableSelect from './OrderableSelect'; diff --git a/webpack/assets/javascripts/react_app/redux/API/API.js b/webpack/assets/javascripts/react_app/redux/API/API.js index cf766e7039..1a21cf3df5 100644 --- a/webpack/assets/javascripts/react_app/redux/API/API.js +++ b/webpack/assets/javascripts/react_app/redux/API/API.js @@ -1,6 +1,6 @@ import axios from 'axios'; import './APITestSetup'; -import { foremanUrl } from '../../../foreman_tools'; +import { foremanUrl } from '../../common/helpers'; const getcsrfToken = () => { const token = document.querySelector('meta[name="csrf-token"]'); diff --git a/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js b/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js index 7b340e4bf9..9373901024 100644 --- a/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js +++ b/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js @@ -10,7 +10,7 @@ import { } from '../../consts'; import * as sessionStorage from '../../../components/notifications/NotificationDrawerSessionStorage'; import { API, get } from '../../API'; -import { reloadPage } from '../../../../foreman_navigation'; +import { reloadPage } from '../../../common/helpers'; import { stopInterval, withInterval, diff --git a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js index c98599ca32..84ea051c9e 100644 --- a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js +++ b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js @@ -1,4 +1,4 @@ -import { foremanUrl } from '../../../../foreman_tools'; +import { foremanUrl } from '../../../common/helpers'; import { get, post } from '../../../redux/API'; import { diff --git a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js index 44849b41ad..e81b3adb28 100644 --- a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js +++ b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js @@ -2,7 +2,7 @@ import React from 'react'; import { FormSelectOption } from '@patternfly/react-core'; -import { foremanUrl } from '../../../../foreman_tools'; +import { foremanUrl } from '../../../common/helpers'; import { sprintf, translate as __ } from '../../../common/I18n'; // Form helpers diff --git a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js index c77c78cf51..e76aada4e7 100644 --- a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js +++ b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js @@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'; import { ActionGroup, Button } from '@patternfly/react-core'; import { translate as __ } from '../../../../common/I18n'; -import { foremanUrl } from '../../../../../foreman_tools'; +import { foremanUrl } from '../../../../common/helpers'; const Actions = ({ isLoading, isGenerating, handleSubmit, invalidFields }) => ( <> diff --git a/webpack/assets/javascripts/react_app/routes/RoutingService.js b/webpack/assets/javascripts/react_app/routes/RoutingService.js index 9f463dcc07..ac7bd65ea6 100644 --- a/webpack/assets/javascripts/react_app/routes/RoutingService.js +++ b/webpack/assets/javascripts/react_app/routes/RoutingService.js @@ -1,6 +1,6 @@ import { Route } from 'react-router-dom'; import React from 'react'; -import { visit } from '../../foreman_navigation'; +import { visit } from '../common/helpers'; import { addGlobalFill } from '../components/common/Fill/GlobalFill'; let currentPath = window.location.href; diff --git a/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js b/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js index d1f1b7014c..1407501d96 100644 --- a/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js +++ b/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js @@ -4,7 +4,7 @@ import { Button } from '@patternfly/react-core'; import { useHistory } from 'react-router-dom'; import { SearchIcon } from '@patternfly/react-icons'; import { translate as __ } from '../../../common/I18n'; -import { visit } from '../../../../foreman_navigation'; +import { visit } from '../../../common/helpers'; import { EmptyStatePattern } from '../../../components/common/EmptyState'; const RedirectedEmptyPage = ({ location: { state = {} } }) => { diff --git a/webpack/core_test_setup.js b/webpack/core_test_setup.js index 8a84e98556..3f2c120c2c 100644 --- a/webpack/core_test_setup.js +++ b/webpack/core_test_setup.js @@ -26,3 +26,14 @@ jest.mock('./assets/javascripts/foreman_tools', () => ({ foremanUrl: url => url, })); jest.mock('./assets/javascripts/foreman_navigation'); + +jest.mock('./assets/javascripts/react_app/common/helpers', () => { + const helpers = jest.requireActual( + './assets/javascripts/react_app/common/helpers' + ); + return { + ...helpers, + visit: jest.fn(), + reloadPage: jest.fn(), + }; +}); diff --git a/webpack/jest.config.js b/webpack/jest.config.js index 4e0f1812c3..3ffc174d89 100644 --- a/webpack/jest.config.js +++ b/webpack/jest.config.js @@ -5,19 +5,10 @@ const path = require('path'); const nodeModules = path.resolve(__dirname, '..', 'node_modules'); const packageJsonPath = path.resolve(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); -const vendorCorePackageJsonPath = path.resolve( - nodeModules, - '@theforeman/vendor-core', - 'package.json' -); -const vendorCorePackageJson = JSON.parse( - fs.readFileSync(vendorCorePackageJsonPath, 'utf8') -); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies, - ...vendorCorePackageJson.dependencies, '@apollo/client/testing': '@apollo/client/testing', }; // Use shared dependencies from foreman node_modules and not plugin node_modules to avoid jest errors due to multiple instances of same package @@ -52,12 +43,7 @@ module.exports = { '.+fixtures.+', 'foreman/webpack', // dont test foreman core in plugins ], - moduleDirectories: [ - `node_modules`, - `/node_modules/@theforeman/vendor-core/node_modules`, - `node_modules/@theforeman/vendor-core/node_modules`, - '/node_modules', - ], + moduleDirectories: [`node_modules`, '/node_modules'], transform: { '^.+\\.js?$': 'babel-jest', '\\.(gql|graphql)$': require.resolve('jest-transform-graphql'), // for graphql-tag @@ -82,5 +68,4 @@ module.exports = { __testing__: true, URL_PREFIX: '', }, - resolver: require.resolve('./resolveNodeModule'), }; diff --git a/webpack/resolveNodeModule.js b/webpack/resolveNodeModule.js deleted file mode 100644 index 30f3d22b31..0000000000 --- a/webpack/resolveNodeModule.js +++ /dev/null @@ -1,62 +0,0 @@ -const { modules } = require('@theforeman/vendor-core'); - -const isRequestedByVendorCore = currentFileDirectory => - (currentFileDirectory.includes('foreman-js/packages/vendor-core') || - currentFileDirectory.includes('@theforeman/vendor-core')) && - !currentFileDirectory.includes( - 'foreman-js/packages/vendor-core/node_modules' - ) && - !currentFileDirectory.includes('@theforeman/vendor-core/node_modules'); - -const getModuleToResolve = ({ sourcePath, currentFileDirectory }) => { - // map the custom foreman js module to the correct path - const requestPath = sourcePath === '.' ? './index' : sourcePath; - const sourcePathSplit = sourcePath.split('/'); - const name = sourcePathSplit[sourcePathSplit.length - 1]; - const vendorModule = modules.find(m => m.name === name); - const requestedByVendorCore = isRequestedByVendorCore(currentFileDirectory); - - const shouldResolveCustomVendorModule = modules.find( - m => m.name === name && m.hasCustomPath && !requestedByVendorCore - ); - - return shouldResolveCustomVendorModule ? vendorModule.path : requestPath; -}; - -/** - * resolve a import/require of a node module - * this method should be calld by the jest-resolver - * to resolve every require statement - * This cannot be done with changing the paths with moduleNameMapper - */ - -const resolveNodeModule = (sourcePath, currentFile) => { - const { basedir, rootDir } = currentFile; - const moduleToResolve = getModuleToResolve({ - sourcePath, - currentFileDirectory: basedir, - }); - if (sourcePath.includes('theforeman_test_dependencies')) { - // eslint-disable-next-line no-console - console.warn( - 'import from @theforeman/test is deprecated, please remove the package and import from enzyme, axios-mock-adapter, foremanReact/testHelpers, foremanReact/common/IntegrationTestHelper directly instead.' - ); - } - let results; - try { - results = require.resolve(moduleToResolve, { - paths: [basedir], - }); - } catch (error) { - results = currentFile.defaultResolver(moduleToResolve, currentFile); - } - - return rootDir - ? results.replace( - /.*\/foreman-js\/packages/, - `${rootDir}/node_modules/@theforeman` - ) - : results; -}; - -module.exports = resolveNodeModule;