Skip to content
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
2 changes: 1 addition & 1 deletion frontend/src/components/empty-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/filtering/active-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/filtering/result-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/hooks/use-table-filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
335 changes: 209 additions & 126 deletions frontend/src/components/hooks/use-widgets.js
Original file line number Diff line number Diff line change
@@ -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({
Expand Down Expand Up @@ -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 <WidgetSpinner />;
}

return (
<FilterHeatmapWidget
title={title}
params={params}
type={heatmapType}
onDeleteClick={onDeleteClick}
onEditClick={onEditClick}
/>
);
};

JenkinsHeatmapWrapper.propTypes = {
title: PropTypes.string,
params: PropTypes.object,
onDeleteClick: PropTypes.func,
onEditClick: PropTypes.func,
};

export const useWidgets = ({
dashboardId = null,
editCallback = () => {},
Expand Down Expand Up @@ -105,121 +184,125 @@ export const useWidgets = ({
{...ROW_SPAN[widget.widget]}
key={`${widget.id}-${loadKey}`}
>
{widget.type === 'widget' &&
widget.widget === 'jenkins-heatmap' && (
<FilterHeatmapWidget
title={widget.title}
params={widgetParams}
type={HEATMAP_TYPES.jenkins}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' && widget.widget === 'filter-heatmap' && (
<FilterHeatmapWidget
title={widget.title}
params={widgetParams}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' && widget.widget === 'run-aggregator' && (
<RunAggregateApex
title={widget.title}
params={widgetParams}
horizontal={true}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' && widget.widget === 'result-summary' && (
<ResultSummaryApex
title="Test Results Summary"
params={widgetParams}
onEditClick={() => {
editCallback(widget.id);
}}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'result-aggregator' && (
<ResultAggregateApex
title={widget.title}
params={widgetParams}
days={widgetParams.days}
groupField={widgetParams.group_field}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'jenkins-line-chart' && (
<GenericAreaWidget
title={widget.title}
params={widgetParams}
yLabel="Execution time"
widgetEndpoint="jenkins-line-chart"
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'jenkins-bar-chart' && (
<GenericBarWidget
title={widget.title}
params={widgetParams}
barWidth={20}
horizontal={true}
hideDropdown={true}
widgetEndpoint="jenkins-bar-chart"
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'importance-component' && (
<ImportanceComponentWidget
title={widget.title}
params={widgetParams}
barWidth={20}
horizontal={true}
hideDropdown={true}
widgetEndpoint="importance-component"
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
<Suspense fallback={<WidgetSpinner />}>
{widget.type === 'widget' &&
widget.widget === 'jenkins-heatmap' && (
<JenkinsHeatmapWrapper
title={widget.title}
params={widgetParams}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'filter-heatmap' && (
<FilterHeatmapWidget
title={widget.title}
params={widgetParams}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'run-aggregator' && (
<RunAggregateApex
title={widget.title}
params={widgetParams}
horizontal={true}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'result-summary' && (
<ResultSummaryApex
title="Test Results Summary"
params={widgetParams}
onEditClick={() => {
editCallback(widget.id);
}}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'result-aggregator' && (
<ResultAggregateApex
title={widget.title}
params={widgetParams}
days={widgetParams.days}
groupField={widgetParams.group_field}
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'jenkins-line-chart' && (
<GenericAreaWidget
title={widget.title}
params={widgetParams}
yLabel="Execution time"
widgetEndpoint="jenkins-line-chart"
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'jenkins-bar-chart' && (
<GenericBarWidget
title={widget.title}
params={widgetParams}
barWidth={20}
horizontal={true}
hideDropdown={true}
widgetEndpoint="jenkins-bar-chart"
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
{widget.type === 'widget' &&
widget.widget === 'importance-component' && (
<ImportanceComponentWidget
title={widget.title}
params={widgetParams}
barWidth={20}
horizontal={true}
hideDropdown={true}
widgetEndpoint="importance-component"
onDeleteClick={() => {
deleteCallback(widget.id);
}}
onEditClick={() => {
editCallback(widget.id);
}}
/>
)}
</Suspense>
</GridItem>
);
}
Expand Down
Loading