diff --git a/frontend/src/components/empty-object.js b/frontend/src/components/empty-object.js index 1d318b51..d671e157 100644 --- a/frontend/src/components/empty-object.js +++ b/frontend/src/components/empty-object.js @@ -7,7 +7,7 @@ import { EmptyStateBody, Content, } from '@patternfly/react-core'; -import { SearchIcon } from '@patternfly/react-icons'; +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; import { NavLink } from 'react-router-dom'; diff --git a/frontend/src/components/filtering/active-filters.js b/frontend/src/components/filtering/active-filters.js index ff4df493..93ef7b3f 100644 --- a/frontend/src/components/filtering/active-filters.js +++ b/frontend/src/components/filtering/active-filters.js @@ -14,7 +14,8 @@ import { useMemo } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { filtersToSearchParams, toTitleCase } from '../../utilities'; import { OPERATIONS } from '../../constants'; -import { ChevronRightIcon, TimesCircleIcon } from '@patternfly/react-icons'; +import ChevronRightIcon from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; +import TimesCircleIcon from '@patternfly/react-icons/dist/esm/icons/times-circle-icon'; const BADGE_STYLE = { margin: '0.1rem', diff --git a/frontend/src/components/filtering/result-filter.js b/frontend/src/components/filtering/result-filter.js index 49746441..37dbd611 100644 --- a/frontend/src/components/filtering/result-filter.js +++ b/frontend/src/components/filtering/result-filter.js @@ -17,7 +17,7 @@ import { import PropTypes from 'prop-types'; import { useCallback, useContext, useEffect, useState } from 'react'; -import { TimesIcon } from '@patternfly/react-icons'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; import MultiValueInput from '../multi-value-input'; import ActiveFilters from './active-filters'; diff --git a/frontend/src/components/hooks/use-table-filters.js b/frontend/src/components/hooks/use-table-filters.js index 6f12a7fa..58db207a 100644 --- a/frontend/src/components/hooks/use-table-filters.js +++ b/frontend/src/components/hooks/use-table-filters.js @@ -25,7 +25,7 @@ import { parseSearchToFilter, } from '../../utilities'; import { IbutsuContext } from '../contexts/ibutsu-context'; -import { TimesIcon } from '@patternfly/react-icons'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; import PropTypes from 'prop-types'; import { OPERATION_MODE_MAP, FILTER_MODE_MAP } from '../../constants'; diff --git a/frontend/src/components/hooks/use-widgets.js b/frontend/src/components/hooks/use-widgets.js index 36265e73..d6ecd71a 100644 --- a/frontend/src/components/hooks/use-widgets.js +++ b/frontend/src/components/hooks/use-widgets.js @@ -1,20 +1,54 @@ // Assisted by watsonx Code Assistant -import { useState, useEffect, useMemo, useContext } from 'react'; +import { + useState, + useEffect, + useMemo, + useContext, + lazy, + Suspense, +} from 'react'; +import PropTypes from 'prop-types'; import { HttpClient } from '../../utilities/http'; import { KNOWN_WIDGETS } from '../../constants'; import { Settings } from '../../pages/settings'; import { GridItem } from '@patternfly/react-core'; -import { - FilterHeatmapWidget, - HEATMAP_TYPES, -} from '../../widgets/filter-heatmap'; -import GenericAreaWidget from '../../widgets/generic-area'; -import GenericBarWidget from '../../widgets/generic-bar'; -import ImportanceComponentWidget from '../../widgets/importance-component'; import { IbutsuContext } from '../contexts/ibutsu-context'; -import ResultSummaryApex from '../../widgets/result-summary-apex'; -import ResultAggregateApex from '../../widgets/result-aggregate-apex'; -import RunAggregateApex from '../../widgets/run-aggregate-apex'; +import { WidgetSpinner } from '../loading-spinners'; + +// Lazy load widget components for code splitting +const FilterHeatmapWidget = lazy(() => + import('../../widgets/filter-heatmap').then((module) => ({ + default: module.FilterHeatmapWidget, + })), +); + +const GenericAreaWidget = lazy(() => import('../../widgets/generic-area')); +const GenericBarWidget = lazy(() => import('../../widgets/generic-bar')); +const ImportanceComponentWidget = lazy( + () => import('../../widgets/importance-component'), +); +const ResultSummaryApex = lazy( + () => import('../../widgets/result-summary-apex'), +); +const ResultAggregateApex = lazy( + () => import('../../widgets/result-aggregate-apex'), +); +const RunAggregateApex = lazy(() => import('../../widgets/run-aggregate-apex')); + +// Lazy-loaded cache for HEATMAP_TYPES - only loads when actually needed +let heatmapTypesPromise = null; +let heatmapTypesCache = null; + +const getHeatmapTypes = async () => { + if (heatmapTypesCache) return heatmapTypesCache; + if (!heatmapTypesPromise) { + heatmapTypesPromise = import('../../widgets/filter-heatmap').then( + (module) => module.HEATMAP_TYPES, + ); + } + heatmapTypesCache = await heatmapTypesPromise; + return heatmapTypesCache; +}; // Move constants outside component to prevent unnecessary re-renders const DEFAULT_COLSPAN = Object.freeze({ @@ -55,6 +89,51 @@ const ROW_SPAN = Object.freeze({ 'importance-component': DEFAULT_ROWSPAN, }); +// Wrapper component for jenkins-heatmap that handles async HEATMAP_TYPES +const JenkinsHeatmapWrapper = ({ + title, + params, + onDeleteClick, + onEditClick, +}) => { + const [heatmapType, setHeatmapType] = useState(null); + + useEffect(() => { + let isMounted = true; + + getHeatmapTypes().then((types) => { + if (isMounted) { + setHeatmapType(types.jenkins); + } + }); + + return () => { + isMounted = false; + }; + }, []); + + if (!heatmapType) { + return ; + } + + return ( + + ); +}; + +JenkinsHeatmapWrapper.propTypes = { + title: PropTypes.string, + params: PropTypes.object, + onDeleteClick: PropTypes.func, + onEditClick: PropTypes.func, +}; + export const useWidgets = ({ dashboardId = null, editCallback = () => {}, @@ -105,121 +184,125 @@ export const useWidgets = ({ {...ROW_SPAN[widget.widget]} key={`${widget.id}-${loadKey}`} > - {widget.type === 'widget' && - widget.widget === 'jenkins-heatmap' && ( - { - deleteCallback(widget.id); - }} - onEditClick={() => { - editCallback(widget.id); - }} - /> - )} - {widget.type === 'widget' && widget.widget === 'filter-heatmap' && ( - { - deleteCallback(widget.id); - }} - onEditClick={() => { - editCallback(widget.id); - }} - /> - )} - {widget.type === 'widget' && widget.widget === 'run-aggregator' && ( - { - deleteCallback(widget.id); - }} - onEditClick={() => { - editCallback(widget.id); - }} - /> - )} - {widget.type === 'widget' && widget.widget === 'result-summary' && ( - { - editCallback(widget.id); - }} - onDeleteClick={() => { - deleteCallback(widget.id); - }} - /> - )} - {widget.type === 'widget' && - widget.widget === 'result-aggregator' && ( - { - deleteCallback(widget.id); - }} - onEditClick={() => { - editCallback(widget.id); - }} - /> - )} - {widget.type === 'widget' && - widget.widget === 'jenkins-line-chart' && ( - { - deleteCallback(widget.id); - }} - onEditClick={() => { - editCallback(widget.id); - }} - /> - )} - {widget.type === 'widget' && - widget.widget === 'jenkins-bar-chart' && ( - { - deleteCallback(widget.id); - }} - onEditClick={() => { - editCallback(widget.id); - }} - /> - )} - {widget.type === 'widget' && - widget.widget === 'importance-component' && ( - { - deleteCallback(widget.id); - }} - onEditClick={() => { - editCallback(widget.id); - }} - /> - )} + }> + {widget.type === 'widget' && + widget.widget === 'jenkins-heatmap' && ( + { + deleteCallback(widget.id); + }} + onEditClick={() => { + editCallback(widget.id); + }} + /> + )} + {widget.type === 'widget' && + widget.widget === 'filter-heatmap' && ( + { + deleteCallback(widget.id); + }} + onEditClick={() => { + editCallback(widget.id); + }} + /> + )} + {widget.type === 'widget' && + widget.widget === 'run-aggregator' && ( + { + deleteCallback(widget.id); + }} + onEditClick={() => { + editCallback(widget.id); + }} + /> + )} + {widget.type === 'widget' && + widget.widget === 'result-summary' && ( + { + editCallback(widget.id); + }} + onDeleteClick={() => { + deleteCallback(widget.id); + }} + /> + )} + {widget.type === 'widget' && + widget.widget === 'result-aggregator' && ( + { + deleteCallback(widget.id); + }} + onEditClick={() => { + editCallback(widget.id); + }} + /> + )} + {widget.type === 'widget' && + widget.widget === 'jenkins-line-chart' && ( + { + deleteCallback(widget.id); + }} + onEditClick={() => { + editCallback(widget.id); + }} + /> + )} + {widget.type === 'widget' && + widget.widget === 'jenkins-bar-chart' && ( + { + deleteCallback(widget.id); + }} + onEditClick={() => { + editCallback(widget.id); + }} + /> + )} + {widget.type === 'widget' && + widget.widget === 'importance-component' && ( + { + deleteCallback(widget.id); + }} + onEditClick={() => { + editCallback(widget.id); + }} + /> + )} + ); } diff --git a/frontend/src/components/loading-spinners.js b/frontend/src/components/loading-spinners.js new file mode 100644 index 00000000..93a9702f --- /dev/null +++ b/frontend/src/components/loading-spinners.js @@ -0,0 +1,49 @@ +import { Bullseye, Spinner } from '@patternfly/react-core'; +import PropTypes from 'prop-types'; + +/** + * Shared loading spinner components for consistent loading UX across the application. + * Use these instead of defining inline spinner components. + */ + +/** + * Full-page spinner for route-level loading states. + * Centers the spinner in the full viewport height. + */ +export const PageSpinner = ({ ariaLabel = 'Loading page...' }) => ( + + + +); + +PageSpinner.propTypes = { + ariaLabel: PropTypes.string, +}; + +/** + * Content spinner for lazy-loaded page content. + * Uses a smaller minimum height suitable for content sections. + */ +export const ContentSpinner = ({ ariaLabel = 'Loading content...' }) => ( + + + +); + +ContentSpinner.propTypes = { + ariaLabel: PropTypes.string, +}; + +/** + * Widget spinner for dashboard widget loading states. + * Alias for ContentSpinner but semantically named for widget context. + */ +export const WidgetSpinner = ({ ariaLabel = 'Loading widget...' }) => ( + + + +); + +WidgetSpinner.propTypes = { + ariaLabel: PropTypes.string, +}; diff --git a/frontend/src/components/modals/edit-widget-modal.js b/frontend/src/components/modals/edit-widget-modal.js index ef36efd4..a47ccc30 100644 --- a/frontend/src/components/modals/edit-widget-modal.js +++ b/frontend/src/components/modals/edit-widget-modal.js @@ -16,7 +16,7 @@ import { Skeleton, TextInput, } from '@patternfly/react-core'; -import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import { HttpClient } from '../../utilities/http'; import { Settings } from '../../pages/settings'; diff --git a/frontend/src/components/modals/new-dashboard-modal.js b/frontend/src/components/modals/new-dashboard-modal.js index 83b9d09b..ab003939 100644 --- a/frontend/src/components/modals/new-dashboard-modal.js +++ b/frontend/src/components/modals/new-dashboard-modal.js @@ -12,7 +12,7 @@ import { ModalVariant, TextInput, } from '@patternfly/react-core'; -import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; const NewDashboardModal = ({ project, diff --git a/frontend/src/components/multi-value-input.js b/frontend/src/components/multi-value-input.js index 6dc2be35..539544b3 100644 --- a/frontend/src/components/multi-value-input.js +++ b/frontend/src/components/multi-value-input.js @@ -9,7 +9,7 @@ import { TextInputGroupUtilities, } from '@patternfly/react-core'; -import { TimesIcon } from '@patternfly/react-icons'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; const MultiValueInput = ({ onAddValue, diff --git a/frontend/src/components/result-view.js b/frontend/src/components/result-view.js index 954dd00c..16c19200 100644 --- a/frontend/src/components/result-view.js +++ b/frontend/src/components/result-view.js @@ -16,12 +16,10 @@ import { Tab, Button, } from '@patternfly/react-core'; -import { - InfoCircleIcon, - CodeIcon, - SearchIcon, - FileAltIcon, -} from '@patternfly/react-icons'; +import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; +import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; +import FileAltIcon from '@patternfly/react-icons/dist/esm/icons/file-alt-icon'; import { CodeEditor, Language } from '@patternfly/react-code-editor'; import { Link, useParams } from 'react-router-dom'; diff --git a/frontend/src/components/table-states.js b/frontend/src/components/table-states.js index 7fbf6b35..30c8d33a 100644 --- a/frontend/src/components/table-states.js +++ b/frontend/src/components/table-states.js @@ -7,7 +7,8 @@ import { EmptyStateBody, EmptyStateFooter, } from '@patternfly/react-core'; -import { ErrorCircleOIcon, SearchIcon } from '@patternfly/react-icons'; +import ErrorCircleOIcon from '@patternfly/react-icons/dist/esm/icons/error-circle-o-icon'; +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; // TODO this could be one component with variable icons and textual content. diff --git a/frontend/src/components/user-dropdown.js b/frontend/src/components/user-dropdown.js index a70e04c5..a5f518fb 100644 --- a/frontend/src/components/user-dropdown.js +++ b/frontend/src/components/user-dropdown.js @@ -7,7 +7,7 @@ import { MenuToggle, } from '@patternfly/react-core'; -import { UserIcon } from '@patternfly/react-icons'; +import UserIcon from '@patternfly/react-icons/dist/esm/icons/user-icon'; import { useNavigate } from 'react-router-dom'; import { AuthService } from '../utilities/auth'; diff --git a/frontend/src/components/widget-header.js b/frontend/src/components/widget-header.js index 448c5548..5db52cfe 100644 --- a/frontend/src/components/widget-header.js +++ b/frontend/src/components/widget-header.js @@ -1,10 +1,8 @@ import PropTypes from 'prop-types'; import { Button, CardHeader, Title } from '@patternfly/react-core'; -import { - PficonHistoryIcon, - TimesIcon, - PencilAltIcon, -} from '@patternfly/react-icons'; +import PficonHistoryIcon from '@patternfly/react-icons/dist/esm/icons/pficon-history-icon'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; +import PencilAltIcon from '@patternfly/react-icons/dist/esm/icons/pencil-alt-icon'; const WidgetHeader = ({ id, diff --git a/frontend/src/constants.js b/frontend/src/constants.js index 3ce9a642..ea0bfb81 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -1,15 +1,13 @@ import packageJson from '../package.json'; -import { - CheckCircleIcon, - ChevronCircleRightIcon, - ClockIcon, - ExclamationCircleIcon, - InfoAltIcon, - QuestionCircleIcon, - TimesCircleIcon, - FlagIcon, -} from '@patternfly/react-icons'; +import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; +import ChevronCircleRightIcon from '@patternfly/react-icons/dist/esm/icons/chevron-circle-right-icon'; +import ClockIcon from '@patternfly/react-icons/dist/esm/icons/clock-icon'; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import InfoAltIcon from '@patternfly/react-icons/dist/esm/icons/info-alt-icon'; +import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; +import TimesCircleIcon from '@patternfly/react-icons/dist/esm/icons/times-circle-icon'; +import FlagIcon from '@patternfly/react-icons/dist/esm/icons/flag-icon'; export const VERSION = packageJson.version; export const MONITOR_UPLOAD_TIMEOUT = 1 * 1000; // 1 second diff --git a/frontend/src/pages/admin/project-edit.js b/frontend/src/pages/admin/project-edit.js index b0098d6c..f3393809 100644 --- a/frontend/src/pages/admin/project-edit.js +++ b/frontend/src/pages/admin/project-edit.js @@ -26,7 +26,7 @@ import { import { Link, useNavigate, useParams } from 'react-router-dom'; import { nanoid } from 'nanoid/non-secure'; -import { TimesIcon } from '@patternfly/react-icons'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; import { HttpClient } from '../../utilities/http'; import { Settings } from '../settings'; diff --git a/frontend/src/pages/admin/project-list.js b/frontend/src/pages/admin/project-list.js index 5c32e45e..789fd478 100644 --- a/frontend/src/pages/admin/project-list.js +++ b/frontend/src/pages/admin/project-list.js @@ -12,11 +12,9 @@ import { ModalHeader, ModalVariant, } from '@patternfly/react-core'; -import { - PencilAltIcon, - PlusCircleIcon, - TrashIcon, -} from '@patternfly/react-icons'; +import PencilAltIcon from '@patternfly/react-icons/dist/esm/icons/pencil-alt-icon'; +import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon'; import { Link } from 'react-router-dom'; import { HttpClient } from '../../utilities/http'; diff --git a/frontend/src/pages/admin/user-edit.js b/frontend/src/pages/admin/user-edit.js index 4d81fbbe..1b170657 100644 --- a/frontend/src/pages/admin/user-edit.js +++ b/frontend/src/pages/admin/user-edit.js @@ -26,7 +26,7 @@ import { Title, } from '@patternfly/react-core'; -import { TimesIcon } from '@patternfly/react-icons'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; import { HttpClient } from '../../utilities/http'; import { Settings } from '../settings'; diff --git a/frontend/src/pages/admin/user-list.js b/frontend/src/pages/admin/user-list.js index 6425fff9..f613d3fd 100644 --- a/frontend/src/pages/admin/user-list.js +++ b/frontend/src/pages/admin/user-list.js @@ -28,13 +28,11 @@ import usePagination from '../../components/hooks/use-pagination'; import FilterTable from '../../components/filtering/filtered-table-card'; import { FilterContext } from '../../components/contexts/filter-context'; import AdminFilter from '../../components/filtering/admin-filter'; -import { - BanIcon, - CheckIcon, - LinuxIcon, - PencilAltIcon, - TrashIcon, -} from '@patternfly/react-icons'; +import BanIcon from '@patternfly/react-icons/dist/esm/icons/ban-icon'; +import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon'; +import LinuxIcon from '@patternfly/react-icons/dist/esm/icons/linux-icon'; +import PencilAltIcon from '@patternfly/react-icons/dist/esm/icons/pencil-alt-icon'; +import TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon'; import { Link } from 'react-router-dom'; const COLUMNS = Object.values(USER_COLUMNS); diff --git a/frontend/src/pages/login.js b/frontend/src/pages/login.js index 978a671a..8cc3d8a0 100644 --- a/frontend/src/pages/login.js +++ b/frontend/src/pages/login.js @@ -16,16 +16,14 @@ import { LoginPage, TextInput, } from '@patternfly/react-core'; -import { - EyeIcon, - EyeSlashIcon, - GoogleIcon, - FacebookIcon, - GithubIcon, - GitlabIcon, - RedhatIcon, - KeyIcon, -} from '@patternfly/react-icons'; +import EyeIcon from '@patternfly/react-icons/dist/esm/icons/eye-icon'; +import EyeSlashIcon from '@patternfly/react-icons/dist/esm/icons/eye-slash-icon'; +import GoogleIcon from '@patternfly/react-icons/dist/esm/icons/google-icon'; +import FacebookIcon from '@patternfly/react-icons/dist/esm/icons/facebook-icon'; +import GithubIcon from '@patternfly/react-icons/dist/esm/icons/github-icon'; +import GitlabIcon from '@patternfly/react-icons/dist/esm/icons/gitlab-icon'; +import RedhatIcon from '@patternfly/react-icons/dist/esm/icons/redhat-icon'; +import KeyIcon from '@patternfly/react-icons/dist/esm/icons/key-icon'; import { NavLink, useLocation, useNavigate } from 'react-router-dom'; import { GoogleLogin } from '@react-oauth/google'; import OAuth2Login from 'react-simple-oauth2-login'; diff --git a/frontend/src/pages/reset-password.js b/frontend/src/pages/reset-password.js index b13e67d0..93b6c4c9 100644 --- a/frontend/src/pages/reset-password.js +++ b/frontend/src/pages/reset-password.js @@ -15,7 +15,8 @@ import { LoginPage, TextInput, } from '@patternfly/react-core'; -import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons'; +import EyeIcon from '@patternfly/react-icons/dist/esm/icons/eye-icon'; +import EyeSlashIcon from '@patternfly/react-icons/dist/esm/icons/eye-slash-icon'; import { NavLink, useParams } from 'react-router-dom'; import { ErrorBoundary } from 'react-error-boundary'; diff --git a/frontend/src/pages/run.js b/frontend/src/pages/run.js index 6dafefb9..00166496 100644 --- a/frontend/src/pages/run.js +++ b/frontend/src/pages/run.js @@ -24,18 +24,16 @@ import { Content, TreeView, } from '@patternfly/react-core'; -import { - CatalogIcon, - ChevronRightIcon, - CodeIcon, - FileAltIcon, - FileIcon, - FolderIcon, - FolderOpenIcon, - InfoCircleIcon, - MessagesIcon, - RepositoryIcon, -} from '@patternfly/react-icons'; +import CatalogIcon from '@patternfly/react-icons/dist/esm/icons/catalog-icon'; +import ChevronRightIcon from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; +import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; +import FileAltIcon from '@patternfly/react-icons/dist/esm/icons/file-alt-icon'; +import FileIcon from '@patternfly/react-icons/dist/esm/icons/file-icon'; +import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon'; +import FolderOpenIcon from '@patternfly/react-icons/dist/esm/icons/folder-open-icon'; +import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon'; +import MessagesIcon from '@patternfly/react-icons/dist/esm/icons/messages-icon'; +import RepositoryIcon from '@patternfly/react-icons/dist/esm/icons/repository-icon'; import { CodeEditor, Language } from '@patternfly/react-code-editor'; import { HttpClient } from '../utilities/http'; diff --git a/frontend/src/pages/sign-up.js b/frontend/src/pages/sign-up.js index fc367f48..74ecb06a 100644 --- a/frontend/src/pages/sign-up.js +++ b/frontend/src/pages/sign-up.js @@ -16,7 +16,8 @@ import { LoginPage, TextInput, } from '@patternfly/react-core'; -import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons'; +import EyeIcon from '@patternfly/react-icons/dist/esm/icons/eye-icon'; +import EyeSlashIcon from '@patternfly/react-icons/dist/esm/icons/eye-slash-icon'; import { NavLink } from 'react-router-dom'; import { AuthService } from '../utilities/auth'; diff --git a/frontend/src/pages/user-profile.js b/frontend/src/pages/user-profile.js index a9fbbe40..ea935861 100644 --- a/frontend/src/pages/user-profile.js +++ b/frontend/src/pages/user-profile.js @@ -17,7 +17,9 @@ import { Title, Skeleton, } from '@patternfly/react-core'; -import { CheckIcon, PencilAltIcon, TimesIcon } from '@patternfly/react-icons'; +import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon'; +import PencilAltIcon from '@patternfly/react-icons/dist/esm/icons/pencil-alt-icon'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; import { HttpClient } from '../utilities/http'; import { Settings } from './settings'; diff --git a/frontend/src/pages/user-tokens.js b/frontend/src/pages/user-tokens.js index bbd00a9f..40842d5b 100644 --- a/frontend/src/pages/user-tokens.js +++ b/frontend/src/pages/user-tokens.js @@ -10,7 +10,7 @@ import { Content, Title, } from '@patternfly/react-core'; -import { PlusCircleIcon } from '@patternfly/react-icons'; +import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; import { HttpClient } from '../utilities/http'; import { Settings } from './settings'; diff --git a/frontend/src/routes/admin.js b/frontend/src/routes/admin.js index df70fd87..cbcd9b14 100644 --- a/frontend/src/routes/admin.js +++ b/frontend/src/routes/admin.js @@ -1,35 +1,56 @@ +import { lazy, Suspense } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; -import AdminHome from '../pages/admin/home'; -import UserList from '../pages/admin/user-list'; -import UserEdit from '../pages/admin/user-edit'; -import ProjectList from '../pages/admin/project-list'; -import ProjectEdit from '../pages/admin/project-edit'; - import '../app.css'; import AdminPage from './admin-page'; import { STRING_PROJECT_FIELDS, STRING_USER_FIELDS } from '../constants'; import FilterProvider from '../components/contexts/filter-context'; +import { ContentSpinner } from '../components/loading-spinners'; + +// Lazy load admin page components for code splitting +const AdminHome = lazy(() => import('../pages/admin/home')); +const UserList = lazy(() => import('../pages/admin/user-list')); +const UserEdit = lazy(() => import('../pages/admin/user-edit')); +const ProjectList = lazy(() => import('../pages/admin/project-list')); +const ProjectEdit = lazy(() => import('../pages/admin/project-edit')); const Admin = () => { return ( }> - } /> + }> + + + } + /> - + }> + + } /> - } /> + }> + + + } + /> - + }> + + } /> @@ -41,7 +62,9 @@ const Admin = () => { key="project_edit" fieldOptions={STRING_USER_FIELDS} > - + }> + + } /> diff --git a/frontend/src/routes/app.js b/frontend/src/routes/app.js index 1e40f7dc..5ebe922d 100644 --- a/frontend/src/routes/app.js +++ b/frontend/src/routes/app.js @@ -1,18 +1,21 @@ -import { useEffect } from 'react'; +import { useEffect, lazy, Suspense } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; -import Dashboard from '../pages/dashboard'; -import RunList from '../pages/run-list'; -import Run from '../pages/run'; -import ResultList from '../pages/result-list'; -import Result from '../pages/result'; import IbutsuPage from './ibutsu-page'; -import View from '../pages/View'; import '../app.css'; import FilterProvider from '../components/contexts/filter-context.js'; import { RESULT_FIELDS, RUN_FIELDS } from '../constants'; +import { ContentSpinner } from '../components/loading-spinners'; + +// Lazy load page components for code splitting +const Dashboard = lazy(() => import('../pages/dashboard')); +const RunList = lazy(() => import('../pages/run-list')); +const Run = lazy(() => import('../pages/run')); +const ResultList = lazy(() => import('../pages/result-list')); +const Result = lazy(() => import('../pages/result')); +const View = lazy(() => import('../pages/View')); const App = () => { // apparently it's good practice to set this after render via effect @@ -25,8 +28,22 @@ const App = () => { } /> }> {/* Nested project routes */} - } /> - } /> + }> + + + } + /> + }> + + + } + /> { // set key to force mount on FilterProviders // RunList uses RunFilters - + }> + + } /> - } /> + }> + + + } + /> - + }> + + } /> - } /> + }> + + + } + /> - } /> + }> + + + } + /> } /> diff --git a/frontend/src/routes/base.js b/frontend/src/routes/base.js index a43f7c09..52acbb95 100644 --- a/frontend/src/routes/base.js +++ b/frontend/src/routes/base.js @@ -1,57 +1,65 @@ +import { lazy, Suspense } from 'react'; import { BrowserRouter as Router, Route, Routes, Navigate, } from 'react-router-dom'; -import App from './app'; -import Admin from './admin'; -import Profile from './profile'; -import Login from '../pages/login'; -import { SignUp } from '../pages/sign-up'; -import ForgotPassword from '../pages/forgot-password'; -import ResetPassword from '../pages/reset-password'; import ProtectedRoute from '../components/ProtectedRoute'; import { IbutsuContextProvider } from '../components/contexts/ibutsu-context'; +import { PageSpinner } from '../components/loading-spinners'; + +// Lazy load route-level components for code splitting +const App = lazy(() => import('./app')); +const Admin = lazy(() => import('./admin')); +const Profile = lazy(() => import('./profile')); +const Login = lazy(() => import('../pages/login')); +const SignUp = lazy(() => + import('../pages/sign-up').then((module) => ({ default: module.SignUp })), +); +const ForgotPassword = lazy(() => import('../pages/forgot-password')); +const ResetPassword = lazy(() => import('../pages/reset-password')); export const Base = () => ( - - } /> - } /> - } /> - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - + }> + + } /> + } /> + } /> + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + + ); diff --git a/frontend/src/routes/ibutsu-page.js b/frontend/src/routes/ibutsu-page.js index 4521c5f8..8f4e3386 100644 --- a/frontend/src/routes/ibutsu-page.js +++ b/frontend/src/routes/ibutsu-page.js @@ -11,7 +11,7 @@ import { EmptyState, EmptyStateBody, Page } from '@patternfly/react-core'; import IbutsuHeader from '../components/ibutsu-header'; import { IbutsuContext } from '../components/contexts/ibutsu-context'; import IbutsuSidebar from '../components/sidebar'; -import { ArchiveIcon } from '@patternfly/react-icons'; +import ArchiveIcon from '@patternfly/react-icons/dist/esm/icons/archive-icon'; import { ToastContainer } from 'react-toastify'; import { ALERT_TIMEOUT } from '../constants'; diff --git a/frontend/src/utilities/tables.js b/frontend/src/utilities/tables.js index 29ee33e1..86dc08a3 100644 --- a/frontend/src/utilities/tables.js +++ b/frontend/src/utilities/tables.js @@ -1,6 +1,6 @@ import { Fragment, isValidElement } from 'react'; import { Badge, Label } from '@patternfly/react-core'; -import { ChevronRightIcon } from '@patternfly/react-icons'; +import ChevronRightIcon from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; import { Link } from 'react-router-dom'; import { ICON_RESULT_MAP } from '../constants'; import RunSummary from '../components/run-summary'; diff --git a/frontend/src/views/accessibility-analysis.js b/frontend/src/views/accessibility-analysis.js index 415d2b1e..31734f69 100644 --- a/frontend/src/views/accessibility-analysis.js +++ b/frontend/src/views/accessibility-analysis.js @@ -15,11 +15,9 @@ import { Tabs, Content, } from '@patternfly/react-core'; -import { - CatalogIcon, - ChevronRightIcon, - CodeIcon, -} from '@patternfly/react-icons'; +import CatalogIcon from '@patternfly/react-icons/dist/esm/icons/catalog-icon'; +import ChevronRightIcon from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; +import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; import { ChartLegend, ChartDonut, diff --git a/frontend/src/views/jenkins-job.js b/frontend/src/views/jenkins-job.js index 665e1ac2..db01eb80 100644 --- a/frontend/src/views/jenkins-job.js +++ b/frontend/src/views/jenkins-job.js @@ -1,7 +1,7 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { ChevronRightIcon } from '@patternfly/react-icons'; +import ChevronRightIcon from '@patternfly/react-icons/dist/esm/icons/chevron-right-icon'; import { Link, useParams } from 'react-router-dom'; diff --git a/frontend/src/widgets/filter-heatmap.js b/frontend/src/widgets/filter-heatmap.js index 27156481..e50a2a6a 100644 --- a/frontend/src/widgets/filter-heatmap.js +++ b/frontend/src/widgets/filter-heatmap.js @@ -10,12 +10,10 @@ import { EmptyStateBody, Content, } from '@patternfly/react-core'; -import { - ArrowDownIcon, - ArrowRightIcon, - ArrowUpIcon, - ChartLineIcon, -} from '@patternfly/react-icons'; +import ArrowDownIcon from '@patternfly/react-icons/dist/esm/icons/arrow-down-icon'; +import ArrowRightIcon from '@patternfly/react-icons/dist/esm/icons/arrow-right-icon'; +import ArrowUpIcon from '@patternfly/react-icons/dist/esm/icons/arrow-up-icon'; +import ChartLineIcon from '@patternfly/react-icons/dist/esm/icons/chart-line-icon'; import { Link } from 'react-router-dom'; import HeatMapWrapper from '../components/heat-map-wrapper';