feat: research on data limit for signoz dashboard perf

This commit is contained in:
SagarRajput-7 2025-06-02 13:21:46 +05:30
parent f9cb9f10be
commit 520a3f93c6
13 changed files with 626 additions and 45 deletions

View File

@ -0,0 +1,85 @@
.custom-data-controls {
margin: 8px 0;
.custom-data-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.custom-data-inputs {
margin-top: 8px;
.input-group {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.input-label {
margin-right: 8px;
}
}
}
}
.global-custom-data-controls {
background-color: #f0f2f5;
border-radius: 4px;
padding: 10px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
.ant-card-body {
padding: 12px;
}
&.dark-mode {
background-color: #141414;
border: 1px solid #303030;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
&.inline-layout {
.custom-data-inline-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
.title-switch-group {
display: flex;
align-items: center;
gap: 12px;
min-width: 180px;
.ant-typography {
white-space: nowrap;
}
}
.inputs-container {
display: flex;
align-items: center;
gap: 20px;
.input-group {
display: flex;
align-items: center;
margin-bottom: 0;
.input-label {
margin-right: 8px;
white-space: nowrap;
}
}
}
}
// Reduce vertical padding for the card
.ant-card-body {
padding: 8px 12px;
}
}
}

View File

@ -0,0 +1,75 @@
import './CustomDataControls.styles.scss';
import { Card, InputNumber, Switch, Typography } from 'antd';
import { Widgets } from 'types/api/dashboard/getAll';
interface CustomDataControlsProps {
widget: Widgets;
onUpdate: (updatedWidget: Partial<Widgets>) => void;
}
const { Text } = Typography;
function CustomDataControls({
widget,
onUpdate,
}: CustomDataControlsProps): JSX.Element {
const handleCustomDataModeChange = (checked: boolean): void => {
onUpdate({
customDataMode: checked,
customXData: checked ? widget.customXData || 15 : undefined,
customYData: checked ? widget.customYData || 4 : undefined,
});
};
const handleDataPointsChange = (value: number | null): void => {
if (value !== null && value > 0) {
onUpdate({ customXData: value });
}
};
const handleSeriesCountChange = (value: number | null): void => {
if (value !== null && value > 0) {
onUpdate({ customYData: value });
}
};
return (
<Card className="custom-data-controls" size="small">
<div className="custom-data-header">
<Text strong>Custom Data Generator</Text>
<Switch
checked={widget.customDataMode || false}
onChange={handleCustomDataModeChange}
size="small"
/>
</div>
{widget.customDataMode && (
<div className="custom-data-inputs">
<div className="input-group">
<Text className="input-label">Data Points (X):</Text>
<InputNumber
value={widget.customXData || 15}
onChange={handleDataPointsChange}
size="small"
style={{ width: 80 }}
/>
</div>
<div className="input-group">
<Text className="input-label">Series Count (Y):</Text>
<InputNumber
value={widget.customYData || 4}
onChange={handleSeriesCountChange}
size="small"
style={{ width: 80 }}
/>
</div>
</div>
)}
</Card>
);
}
export default CustomDataControls;

View File

@ -0,0 +1,91 @@
import './CustomDataControls.styles.scss';
import { Card, InputNumber, Switch, Typography } from 'antd';
interface GlobalCustomDataControlsProps {
customDataMode: boolean;
setCustomDataMode: (value: boolean) => void;
customXData: number;
setCustomXData: (value: number) => void;
customYData: number;
setCustomYData: (value: number) => void;
}
const { Text } = Typography;
function GlobalCustomDataControls({
customDataMode,
setCustomDataMode,
customXData,
setCustomXData,
customYData,
setCustomYData,
}: GlobalCustomDataControlsProps): JSX.Element {
const handleCustomDataModeChange = (checked: boolean): void => {
setCustomDataMode(checked);
// Set default values if not already set
if (checked) {
if (!customXData) setCustomXData(15);
if (!customYData) setCustomYData(4);
}
};
const handleDataPointsChange = (value: number | null): void => {
if (value !== null && value > 0) {
setCustomXData(value);
}
};
const handleSeriesCountChange = (value: number | null): void => {
if (value !== null && value > 0) {
setCustomYData(value);
}
};
return (
<Card
className="custom-data-controls global-custom-data-controls"
size="small"
>
<div className="custom-data-header">
<Text strong>Global Custom Data Generator</Text>
<Switch
checked={customDataMode}
onChange={handleCustomDataModeChange}
size="small"
/>
</div>
{customDataMode && (
<div className="custom-data-inputs">
<div className="input-group">
<Text className="input-label">Data Points (X):</Text>
<InputNumber
value={customXData || 15}
onChange={handleDataPointsChange}
size="small"
style={{ width: 80 }}
min={1}
max={1000}
/>
</div>
<div className="input-group">
<Text className="input-label">Series Count (Y):</Text>
<InputNumber
value={customYData || 4}
onChange={handleSeriesCountChange}
size="small"
style={{ width: 80 }}
min={1}
max={20}
/>
</div>
</div>
)}
</Card>
);
}
export default GlobalCustomDataControls;

View File

@ -0,0 +1,88 @@
import './CustomDataControls.styles.scss';
import { Card, InputNumber, Switch, Typography } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useDashboard } from 'providers/Dashboard/Dashboard';
const { Text } = Typography;
function GlobalCustomDataControlsHeader(): JSX.Element {
const {
globalCustomDataMode,
setGlobalCustomDataMode,
globalCustomXData,
setGlobalCustomXData,
globalCustomYData,
setGlobalCustomYData,
} = useDashboard();
const isDarkMode = useIsDarkMode();
const handleCustomDataModeChange = (checked: boolean): void => {
setGlobalCustomDataMode(checked);
// Set default values if not already set
if (checked) {
if (!globalCustomXData) setGlobalCustomXData(15);
if (!globalCustomYData) setGlobalCustomYData(4);
}
};
const handleDataPointsChange = (value: number | null): void => {
if (value !== null && value > 0) {
setGlobalCustomXData(value);
}
};
const handleSeriesCountChange = (value: number | null): void => {
if (value !== null && value > 0) {
setGlobalCustomYData(value);
}
};
return (
<Card
className={`custom-data-controls global-custom-data-controls inline-layout ${
isDarkMode ? 'dark-mode' : ''
}`}
size="small"
>
<div className="custom-data-inline-container">
<div className="title-switch-group">
<Text strong>Custom Data Generator</Text>
<Switch
checked={globalCustomDataMode}
onChange={handleCustomDataModeChange}
size="small"
/>
</div>
{globalCustomDataMode && (
<div className="inputs-container">
<div className="input-group">
<Text className="input-label">Points (X):</Text>
<InputNumber
value={globalCustomXData || 15}
onChange={handleDataPointsChange}
size="small"
style={{ width: 80 }}
/>
</div>
<div className="input-group">
<Text className="input-label">Series (Y):</Text>
<InputNumber
value={globalCustomYData || 4}
onChange={handleSeriesCountChange}
size="small"
style={{ width: 80 }}
/>
</div>
</div>
)}
</div>
</Card>
);
}
export default GlobalCustomDataControlsHeader;

View File

@ -121,6 +121,10 @@
} }
} }
.global-custom-data-section {
margin-bottom: 16px;
}
.dashboard-details { .dashboard-details {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@ -12,6 +12,7 @@ import {
Typography, Typography,
} from 'antd'; } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import GlobalCustomDataControlsHeader from 'components/CustomDataControls/GlobalCustomDataControlsHeader';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
@ -478,6 +479,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
)} )}
</div> </div>
</section> </section>
<section className="global-custom-data-section">
<GlobalCustomDataControlsHeader />
</section>
{(tags?.length || 0) > 0 && ( {(tags?.length || 0) > 0 && (
<div className="dashboard-tags"> <div className="dashboard-tags">
{tags?.map((tag) => ( {tags?.map((tag) => (

View File

@ -12,6 +12,7 @@ import {
Switch, Switch,
Typography, Typography,
} from 'antd'; } from 'antd';
import CustomDataControls from 'components/CustomDataControls/CustomDataControls';
import TimePreference from 'components/TimePreferenceDropDown'; import TimePreference from 'components/TimePreferenceDropDown';
import { PANEL_TYPES, PanelDisplay } from 'constants/queryBuilder'; import { PANEL_TYPES, PanelDisplay } from 'constants/queryBuilder';
import GraphTypes, { import GraphTypes, {
@ -113,6 +114,12 @@ function RightContainer({
customLegendColors, customLegendColors,
setCustomLegendColors, setCustomLegendColors,
queryResponse, queryResponse,
customDataMode,
setCustomDataMode,
customXData,
setCustomXData,
customYData,
setCustomYData,
}: RightContainerProps): JSX.Element { }: RightContainerProps): JSX.Element {
const { selectedDashboard } = useDashboard(); const { selectedDashboard } = useDashboard();
const [inputValue, setInputValue] = useState(title); const [inputValue, setInputValue] = useState(title);
@ -280,6 +287,30 @@ function RightContainer({
rootClassName="description-input" rootClassName="description-input"
/> />
</section> </section>
{/* Custom Data Controls */}
{selectedWidget && (
<CustomDataControls
widget={{
...selectedWidget,
customDataMode,
customXData,
customYData,
}}
onUpdate={(updatedWidget: Partial<Widgets>): void => {
if (updatedWidget.customDataMode !== undefined) {
setCustomDataMode(updatedWidget.customDataMode);
}
if (updatedWidget.customXData !== undefined) {
setCustomXData(updatedWidget.customXData);
}
if (updatedWidget.customYData !== undefined) {
setCustomYData(updatedWidget.customYData);
}
}}
/>
)}
<section className="panel-config"> <section className="panel-config">
<Typography.Text className="typography">Panel Type</Typography.Text> <Typography.Text className="typography">Panel Type</Typography.Text>
<Select <Select
@ -554,6 +585,12 @@ interface RightContainerProps {
SuccessResponse<MetricRangePayloadProps, unknown>, SuccessResponse<MetricRangePayloadProps, unknown>,
Error Error
>; >;
customDataMode: boolean;
setCustomDataMode: Dispatch<SetStateAction<boolean>>;
customXData: number;
setCustomXData: Dispatch<SetStateAction<number>>;
customYData: number;
setCustomYData: Dispatch<SetStateAction<number>>;
} }
RightContainer.defaultProps = { RightContainer.defaultProps = {

View File

@ -239,6 +239,17 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
selectedWidget?.columnUnits || {}, selectedWidget?.columnUnits || {},
); );
// Custom data generator state
const [customDataMode, setCustomDataMode] = useState<boolean>(
selectedWidget?.customDataMode || false,
);
const [customXData, setCustomXData] = useState<number>(
selectedWidget?.customXData || 15,
);
const [customYData, setCustomYData] = useState<number>(
selectedWidget?.customYData || 4,
);
useEffect(() => { useEffect(() => {
setSelectedWidget((prev) => { setSelectedWidget((prev) => {
if (!prev) { if (!prev) {
@ -268,6 +279,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
legendPosition, legendPosition,
customLegendColors, customLegendColors,
columnWidths: columnWidths?.[selectedWidget?.id], columnWidths: columnWidths?.[selectedWidget?.id],
customDataMode,
customXData,
customYData,
}; };
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -294,6 +308,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
legendPosition, legendPosition,
customLegendColors, customLegendColors,
columnWidths, columnWidths,
customDataMode,
customXData,
customYData,
]); ]);
const closeModal = (): void => { const closeModal = (): void => {
@ -503,6 +520,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
selectedTracesFields: selectedWidget?.selectedTracesFields || [], selectedTracesFields: selectedWidget?.selectedTracesFields || [],
legendPosition: selectedWidget?.legendPosition || LegendPosition.BOTTOM, legendPosition: selectedWidget?.legendPosition || LegendPosition.BOTTOM,
customLegendColors: selectedWidget?.customLegendColors || {}, customLegendColors: selectedWidget?.customLegendColors || {},
customDataMode: selectedWidget?.customDataMode || false,
customXData: selectedWidget?.customXData || 15,
customYData: selectedWidget?.customYData || 4,
}, },
] ]
: [ : [
@ -532,6 +552,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
selectedTracesFields: selectedWidget?.selectedTracesFields || [], selectedTracesFields: selectedWidget?.selectedTracesFields || [],
legendPosition: selectedWidget?.legendPosition || LegendPosition.BOTTOM, legendPosition: selectedWidget?.legendPosition || LegendPosition.BOTTOM,
customLegendColors: selectedWidget?.customLegendColors || {}, customLegendColors: selectedWidget?.customLegendColors || {},
customDataMode: selectedWidget?.customDataMode || false,
customXData: selectedWidget?.customXData || 15,
customYData: selectedWidget?.customYData || 4,
}, },
...afterWidgets, ...afterWidgets,
], ],
@ -796,6 +819,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setSoftMin={setSoftMin} setSoftMin={setSoftMin}
softMax={softMax} softMax={softMax}
setSoftMax={setSoftMax} setSoftMax={setSoftMax}
customDataMode={customDataMode}
setCustomDataMode={setCustomDataMode}
customXData={customXData}
setCustomXData={setCustomXData}
customYData={customYData}
setCustomYData={setCustomYData}
/> />
</OverlayScrollbar> </OverlayScrollbar>
</RightContainerWrapper> </RightContainerWrapper>

View File

@ -10,12 +10,19 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions'; import { useResizeObserver } from 'hooks/useDimensions';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import {
getCustomApiResponse,
getCustomChartData,
} from 'lib/uPlotLib/utils/getCustomChartData';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { cloneDeep, isEqual, isUndefined } from 'lodash-es'; import { cloneDeep, isEqual, isUndefined } from 'lodash-es';
import _noop from 'lodash-es/noop'; import _noop from 'lodash-es/noop';
import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone'; import { useTimezone } from 'providers/Timezone';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import uPlot from 'uplot'; import uPlot from 'uplot';
import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { getSortedSeriesData } from 'utils/getSortedSeriesData';
import { getTimeRange } from 'utils/getTimeRange'; import { getTimeRange } from 'utils/getTimeRange';
@ -35,7 +42,14 @@ function UplotPanelWrapper({
customTooltipElement, customTooltipElement,
customSeries, customSeries,
}: PanelWrapperProps): JSX.Element { }: PanelWrapperProps): JSX.Element {
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard(); const {
toScrollWidgetId,
setToScrollWidgetId,
globalCustomDataMode,
globalCustomXData,
globalCustomYData,
} = useDashboard();
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const lineChartRef = useRef<ToggleGraphProps>(); const lineChartRef = useRef<ToggleGraphProps>();
const graphRef = useRef<HTMLDivElement>(null); const graphRef = useRef<HTMLDivElement>(null);
@ -43,6 +57,11 @@ function UplotPanelWrapper({
const [maxTimeScale, setMaxTimeScale] = useState<number>(); const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
// Get global time for custom data generation
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const [hiddenGraph, setHiddenGraph] = useState<{ [key: string]: boolean }>(); const [hiddenGraph, setHiddenGraph] = useState<{ [key: string]: boolean }>();
useEffect(() => { useEffect(() => {
@ -56,12 +75,64 @@ function UplotPanelWrapper({
} }
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]); }, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
// Create custom response when custom data mode is enabled (either via widget or global setting)
const effectiveQueryResponse = useMemo(() => {
// If global custom data mode is enabled, it should override widget settings
const isCustomDataEnabled = globalCustomDataMode || widget.customDataMode;
// When global custom data is enabled, use global values regardless of widget settings
let xData = null;
let yData = null;
if (globalCustomDataMode) {
// Global settings override widget settings
xData = globalCustomXData;
yData = globalCustomYData;
} else if (widget.customDataMode) {
// Only use widget settings if global is not enabled
xData = widget.customXData;
yData = widget.customYData;
}
if (isCustomDataEnabled && xData && yData) {
// Convert nanoseconds to seconds for custom data generation
const startTimeSeconds = Math.floor(minTime / 1000000000);
const endTimeSeconds = Math.floor(maxTime / 1000000000);
const customResponse = getCustomApiResponse(
xData,
yData,
startTimeSeconds,
endTimeSeconds,
);
// Return a properly structured response that matches the expected type
return {
...queryResponse,
data: customResponse,
isSuccess: true,
isError: false,
isLoading: false,
} as typeof queryResponse;
}
return queryResponse;
}, [
queryResponse,
widget.customDataMode,
widget.customXData,
widget.customYData,
globalCustomDataMode,
globalCustomXData,
globalCustomYData,
minTime,
maxTime,
]);
useEffect((): void => { useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse); const { startTime, endTime } = getTimeRange(effectiveQueryResponse);
setMinTimeScale(startTime); setMinTimeScale(startTime);
setMaxTimeScale(endTime); setMaxTimeScale(endTime);
}, [queryResponse]); }, [effectiveQueryResponse]);
const containerDimensions = useResizeObserver(graphRef); const containerDimensions = useResizeObserver(graphRef);
@ -69,28 +140,71 @@ function UplotPanelWrapper({
const { const {
graphVisibilityStates: localStoredVisibilityState, graphVisibilityStates: localStoredVisibilityState,
} = getLocalStorageGraphVisibilityState({ } = getLocalStorageGraphVisibilityState({
apiResponse: queryResponse.data?.payload.data.result || [], apiResponse: effectiveQueryResponse.data?.payload.data.result || [],
name: widget.id, name: widget.id,
}); });
if (setGraphVisibility) { if (setGraphVisibility) {
setGraphVisibility(localStoredVisibilityState); setGraphVisibility(localStoredVisibilityState);
} }
}, [queryResponse.data?.payload.data.result, setGraphVisibility, widget.id]); }, [
effectiveQueryResponse.data?.payload.data.result,
setGraphVisibility,
widget.id,
]);
if (queryResponse.data && widget.panelTypes === PANEL_TYPES.BAR) { if (effectiveQueryResponse.data && widget.panelTypes === PANEL_TYPES.BAR) {
const sortedSeriesData = getSortedSeriesData( const sortedSeriesData = getSortedSeriesData(
queryResponse.data?.payload.data.result, effectiveQueryResponse.data?.payload.data.result,
); );
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
queryResponse.data.payload.data.result = sortedSeriesData; effectiveQueryResponse.data.payload.data.result = sortedSeriesData;
} }
const chartData = getUPlotChartData( const chartData = useMemo(() => {
queryResponse?.data?.payload, // If global custom data mode is enabled, it should override widget settings
const isCustomDataEnabled = globalCustomDataMode || widget.customDataMode;
// When global custom data is enabled, use global values regardless of widget settings
let xData = null;
let yData = null;
if (globalCustomDataMode) {
// Global settings override widget settings
xData = globalCustomXData;
yData = globalCustomYData;
} else if (widget.customDataMode) {
// Only use widget settings if global is not enabled
xData = widget.customXData;
yData = widget.customYData;
}
if (isCustomDataEnabled && xData && yData) {
// Convert nanoseconds to seconds for custom data generation
const startTimeSeconds = Math.floor(minTime / 1000000000);
const endTimeSeconds = Math.floor(maxTime / 1000000000);
return getCustomChartData(xData, yData, startTimeSeconds, endTimeSeconds);
}
return getUPlotChartData(
effectiveQueryResponse?.data?.payload,
widget.fillSpans,
widget?.stackedBarChart,
hiddenGraph,
);
}, [
widget.customDataMode,
widget.customXData,
widget.customYData,
globalCustomDataMode,
globalCustomXData,
globalCustomYData,
minTime,
maxTime,
effectiveQueryResponse?.data?.payload,
widget.fillSpans, widget.fillSpans,
widget?.stackedBarChart, widget?.stackedBarChart,
hiddenGraph, hiddenGraph,
); ]);
useEffect(() => { useEffect(() => {
if (widget.panelTypes === PANEL_TYPES.BAR && widget?.stackedBarChart) { if (widget.panelTypes === PANEL_TYPES.BAR && widget?.stackedBarChart) {
@ -110,16 +224,22 @@ function UplotPanelWrapper({
const { timezone } = useTimezone(); const { timezone } = useTimezone();
// Standard click handler without performance monitoring
const enhancedClickHandler = useMemo(() => {
if (!onClickHandler) return _noop;
return onClickHandler;
}, [onClickHandler]);
const options = useMemo( const options = useMemo(
() => () =>
getUPlotChartOptions({ getUPlotChartOptions({
id: widget?.id, id: widget?.id,
apiResponse: queryResponse.data?.payload, apiResponse: effectiveQueryResponse.data?.payload,
dimensions: containerDimensions, dimensions: containerDimensions,
isDarkMode, isDarkMode,
onDragSelect, onDragSelect,
yAxisUnit: widget?.yAxisUnit, yAxisUnit: widget?.yAxisUnit,
onClickHandler: onClickHandler || _noop, onClickHandler: enhancedClickHandler,
thresholds: widget.thresholds, thresholds: widget.thresholds,
minTimeScale, minTimeScale,
maxTimeScale, maxTimeScale,
@ -150,11 +270,11 @@ function UplotPanelWrapper({
widget.softMin, widget.softMin,
widget.panelTypes, widget.panelTypes,
widget?.stackedBarChart, widget?.stackedBarChart,
queryResponse.data?.payload, effectiveQueryResponse.data?.payload,
containerDimensions, containerDimensions,
isDarkMode, isDarkMode,
onDragSelect, onDragSelect,
onClickHandler, enhancedClickHandler,
minTimeScale, minTimeScale,
maxTimeScale, maxTimeScale,
graphVisibility, graphVisibility,
@ -171,6 +291,14 @@ function UplotPanelWrapper({
], ],
); );
console.log(
'chartData',
'x-axis',
chartData[0].length,
'y-axis',
chartData.length,
);
return ( return (
<div style={{ height: '100%', width: '100%' }} ref={graphRef}> <div style={{ height: '100%', width: '100%' }} ref={graphRef}>
<Uplot options={options} data={chartData} ref={lineChartRef} /> <Uplot options={options} data={chartData} ref={lineChartRef} />
@ -183,7 +311,10 @@ function UplotPanelWrapper({
)} )}
{isFullViewMode && setGraphVisibility && !widget?.stackedBarChart && ( {isFullViewMode && setGraphVisibility && !widget?.stackedBarChart && (
<GraphManager <GraphManager
data={getUPlotChartData(queryResponse?.data?.payload, widget.fillSpans)} data={getUPlotChartData(
effectiveQueryResponse?.data?.payload,
widget.fillSpans,
)}
name={widget.id} name={widget.id}
options={options} options={options}
yAxisUnit={widget.yAxisUnit} yAxisUnit={widget.yAxisUnit}

View File

@ -431,9 +431,10 @@ export const getUPlotChartOptions = ({
// Add single global cleanup listener for this chart // Add single global cleanup listener for this chart
const globalCleanupHandler = (e: MouseEvent): void => { const globalCleanupHandler = (e: MouseEvent): void => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
console.log('target', target);
if ( if (
!target.closest('.u-legend') && !target?.closest?.('.u-legend') &&
!target.classList.contains('legend-tooltip') !target?.classList?.contains('legend-tooltip')
) { ) {
cleanupAllTooltips(); cleanupAllTooltips();
} }

View File

@ -32,7 +32,7 @@ import { useTranslation } from 'react-i18next';
import { useMutation, useQuery, UseQueryResult } from 'react-query'; import { useMutation, useQuery, UseQueryResult } from 'react-query';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useRouteMatch } from 'react-router-dom'; import { useRouteMatch } from 'react-router-dom';
import { Dispatch } from 'redux'; import { Dispatch as ReduxDispatch } from 'redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime'; import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
@ -51,7 +51,7 @@ const DashboardContext = createContext<IDashboardContext>({
isDashboardSliderOpen: false, isDashboardSliderOpen: false,
isDashboardLocked: false, isDashboardLocked: false,
handleToggleDashboardSlider: () => {}, handleToggleDashboardSlider: () => {},
handleDashboardLockToggle: () => {}, handleDashboardLockToggle: async () => {},
dashboardResponse: {} as UseQueryResult<Dashboard, unknown>, dashboardResponse: {} as UseQueryResult<Dashboard, unknown>,
selectedDashboard: {} as Dashboard, selectedDashboard: {} as Dashboard,
dashboardId: '', dashboardId: '',
@ -80,6 +80,13 @@ const DashboardContext = createContext<IDashboardContext>({
isDashboardFetching: false, isDashboardFetching: false,
columnWidths: {}, columnWidths: {},
setColumnWidths: () => {}, setColumnWidths: () => {},
// Global custom data state
globalCustomDataMode: false,
setGlobalCustomDataMode: () => {},
globalCustomXData: 15,
setGlobalCustomXData: () => {},
globalCustomYData: 4,
setGlobalCustomYData: () => {},
}); });
interface Props { interface Props {
@ -146,7 +153,7 @@ export function DashboardProvider({
function setListSortOrder(sortOrder: DashboardSortOrder): void { function setListSortOrder(sortOrder: DashboardSortOrder): void {
if (!isEqual(sortOrder, listSortOrder)) { if (!isEqual(sortOrder, listSortOrder)) {
setListOrder(sortOrder); setListOrder(sortOrder as any);
} }
params.set('columnKey', sortOrder.columnKey as string); params.set('columnKey', sortOrder.columnKey as string);
params.set('order', sortOrder.order as string); params.set('order', sortOrder.order as string);
@ -155,7 +162,7 @@ export function DashboardProvider({
safeNavigate({ search: params.toString() }); safeNavigate({ search: params.toString() });
} }
const dispatch = useDispatch<Dispatch<AppActions>>(); const dispatch = useDispatch<ReduxDispatch<AppActions>>();
const globalTime = useSelector<AppState, GlobalReducer>( const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@ -416,6 +423,13 @@ export function DashboardProvider({
const [columnWidths, setColumnWidths] = useState<WidgetColumnWidths>({}); const [columnWidths, setColumnWidths] = useState<WidgetColumnWidths>({});
// Global custom data state
const [globalCustomDataMode, setGlobalCustomDataMode] = useState<boolean>(
false,
);
const [globalCustomXData, setGlobalCustomXData] = useState<number>(15);
const [globalCustomYData, setGlobalCustomYData] = useState<number>(4);
const value: IDashboardContext = useMemo( const value: IDashboardContext = useMemo(
() => ({ () => ({
toScrollWidgetId, toScrollWidgetId,
@ -424,7 +438,7 @@ export function DashboardProvider({
handleToggleDashboardSlider, handleToggleDashboardSlider,
handleDashboardLockToggle, handleDashboardLockToggle,
dashboardResponse, dashboardResponse,
selectedDashboard, selectedDashboard: selectedDashboard as Dashboard,
dashboardId, dashboardId,
layouts, layouts,
listSortOrder, listSortOrder,
@ -445,6 +459,13 @@ export function DashboardProvider({
isDashboardFetching, isDashboardFetching,
columnWidths, columnWidths,
setColumnWidths, setColumnWidths,
// Global custom data state
globalCustomDataMode,
setGlobalCustomDataMode,
globalCustomXData,
setGlobalCustomXData,
globalCustomYData,
setGlobalCustomYData,
}), }),
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[ [
@ -469,6 +490,10 @@ export function DashboardProvider({
isDashboardFetching, isDashboardFetching,
columnWidths, columnWidths,
setColumnWidths, setColumnWidths,
// Global custom data state
globalCustomDataMode,
globalCustomXData,
globalCustomYData,
], ],
); );

View File

@ -1,39 +1,40 @@
import dayjs from 'dayjs'; import { Dayjs } from 'dayjs';
import { Dispatch, SetStateAction } from 'react';
import { Layout } from 'react-grid-layout'; import { Layout } from 'react-grid-layout';
import { UseQueryResult } from 'react-query'; import { UseQueryResult } from 'react-query';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Dashboard } from 'types/api/dashboard/getAll';
export interface DashboardSortOrder { export interface DashboardSortOrder {
columnKey: string; columnKey?: string | null;
order: string; order?: string | null;
pagination: string; pagination?: string;
search: string; search?: string;
} }
export type WidgetColumnWidths = { export interface WidgetColumnWidths {
[widgetId: string]: Record<string, number>; [key: string]: Record<string, number>;
}; }
export interface IDashboardContext { export interface IDashboardContext {
isDashboardSliderOpen: boolean; isDashboardSliderOpen: boolean;
isDashboardLocked: boolean; isDashboardLocked: boolean;
handleToggleDashboardSlider: (value: boolean) => void; handleToggleDashboardSlider: (value: boolean) => void;
handleDashboardLockToggle: (value: boolean) => void; handleDashboardLockToggle: (value: boolean) => Promise<void>;
dashboardResponse: UseQueryResult<Dashboard, unknown>; dashboardResponse: UseQueryResult<Dashboard, unknown>;
selectedDashboard: Dashboard | undefined; selectedDashboard: Dashboard;
dashboardId: string; dashboardId: string;
layouts: Layout[]; layouts: Layout[];
panelMap: Record<string, { widgets: Layout[]; collapsed: boolean }>; panelMap: Record<string, { widgets: Layout[]; collapsed: boolean }>;
setPanelMap: React.Dispatch<React.SetStateAction<Record<string, any>>>; setPanelMap: Dispatch<
listSortOrder: DashboardSortOrder; SetStateAction<Record<string, { widgets: Layout[]; collapsed: boolean }>>
setListSortOrder: (sortOrder: DashboardSortOrder) => void;
setLayouts: React.Dispatch<React.SetStateAction<Layout[]>>;
setSelectedDashboard: React.Dispatch<
React.SetStateAction<Dashboard | undefined>
>; >;
updatedTimeRef: React.MutableRefObject<dayjs.Dayjs | null>; listSortOrder: DashboardSortOrder;
setListSortOrder: (value: DashboardSortOrder) => void;
setLayouts: Dispatch<SetStateAction<Layout[]>>;
setSelectedDashboard: Dispatch<SetStateAction<Dashboard | undefined>>;
updatedTimeRef: React.MutableRefObject<Dayjs | null>;
toScrollWidgetId: string; toScrollWidgetId: string;
setToScrollWidgetId: React.Dispatch<React.SetStateAction<string>>; setToScrollWidgetId: Dispatch<SetStateAction<string>>;
updateLocalStorageDashboardVariables: ( updateLocalStorageDashboardVariables: (
id: string, id: string,
selectedValue: selectedValue:
@ -46,12 +47,19 @@ export interface IDashboardContext {
allSelected: boolean, allSelected: boolean,
) => void; ) => void;
variablesToGetUpdated: string[]; variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>; setVariablesToGetUpdated: Dispatch<SetStateAction<string[]>>;
dashboardQueryRangeCalled: boolean; dashboardQueryRangeCalled: boolean;
setDashboardQueryRangeCalled: (value: boolean) => void; setDashboardQueryRangeCalled: Dispatch<SetStateAction<boolean>>;
selectedRowWidgetId: string | null; selectedRowWidgetId: string | null;
setSelectedRowWidgetId: React.Dispatch<React.SetStateAction<string | null>>; setSelectedRowWidgetId: Dispatch<SetStateAction<string | null>>;
isDashboardFetching: boolean; isDashboardFetching: boolean;
columnWidths: WidgetColumnWidths; columnWidths: WidgetColumnWidths;
setColumnWidths: React.Dispatch<React.SetStateAction<WidgetColumnWidths>>; setColumnWidths: Dispatch<SetStateAction<WidgetColumnWidths>>;
// Global custom data state
globalCustomDataMode: boolean;
setGlobalCustomDataMode: Dispatch<SetStateAction<boolean>>;
globalCustomXData: number;
setGlobalCustomXData: Dispatch<SetStateAction<number>>;
globalCustomYData: number;
setGlobalCustomYData: Dispatch<SetStateAction<number>>;
} }

View File

@ -118,6 +118,9 @@ export interface IBaseWidget {
columnWidths?: Record<string, number>; columnWidths?: Record<string, number>;
legendPosition?: LegendPosition; legendPosition?: LegendPosition;
customLegendColors?: Record<string, string>; customLegendColors?: Record<string, string>;
customDataMode?: boolean;
customXData?: number; // number of data points
customYData?: number; // number of series
} }
export interface Widgets extends IBaseWidget { export interface Widgets extends IBaseWidget {
query: Query; query: Query;