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 {
display: flex;
justify-content: space-between;

View File

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

View File

@ -12,6 +12,7 @@ import {
Switch,
Typography,
} from 'antd';
import CustomDataControls from 'components/CustomDataControls/CustomDataControls';
import TimePreference from 'components/TimePreferenceDropDown';
import { PANEL_TYPES, PanelDisplay } from 'constants/queryBuilder';
import GraphTypes, {
@ -113,6 +114,12 @@ function RightContainer({
customLegendColors,
setCustomLegendColors,
queryResponse,
customDataMode,
setCustomDataMode,
customXData,
setCustomXData,
customYData,
setCustomYData,
}: RightContainerProps): JSX.Element {
const { selectedDashboard } = useDashboard();
const [inputValue, setInputValue] = useState(title);
@ -280,6 +287,30 @@ function RightContainer({
rootClassName="description-input"
/>
</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">
<Typography.Text className="typography">Panel Type</Typography.Text>
<Select
@ -554,6 +585,12 @@ interface RightContainerProps {
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
customDataMode: boolean;
setCustomDataMode: Dispatch<SetStateAction<boolean>>;
customXData: number;
setCustomXData: Dispatch<SetStateAction<number>>;
customYData: number;
setCustomYData: Dispatch<SetStateAction<number>>;
}
RightContainer.defaultProps = {

View File

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

View File

@ -10,12 +10,19 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import {
getCustomApiResponse,
getCustomChartData,
} from 'lib/uPlotLib/utils/getCustomChartData';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { cloneDeep, isEqual, isUndefined } from 'lodash-es';
import _noop from 'lodash-es/noop';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone';
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 { getSortedSeriesData } from 'utils/getSortedSeriesData';
import { getTimeRange } from 'utils/getTimeRange';
@ -35,7 +42,14 @@ function UplotPanelWrapper({
customTooltipElement,
customSeries,
}: PanelWrapperProps): JSX.Element {
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const {
toScrollWidgetId,
setToScrollWidgetId,
globalCustomDataMode,
globalCustomXData,
globalCustomYData,
} = useDashboard();
const isDarkMode = useIsDarkMode();
const lineChartRef = useRef<ToggleGraphProps>();
const graphRef = useRef<HTMLDivElement>(null);
@ -43,6 +57,11 @@ function UplotPanelWrapper({
const [maxTimeScale, setMaxTimeScale] = useState<number>();
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 }>();
useEffect(() => {
@ -56,12 +75,64 @@ function UplotPanelWrapper({
}
}, [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 => {
const { startTime, endTime } = getTimeRange(queryResponse);
const { startTime, endTime } = getTimeRange(effectiveQueryResponse);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [queryResponse]);
}, [effectiveQueryResponse]);
const containerDimensions = useResizeObserver(graphRef);
@ -69,28 +140,71 @@ function UplotPanelWrapper({
const {
graphVisibilityStates: localStoredVisibilityState,
} = getLocalStorageGraphVisibilityState({
apiResponse: queryResponse.data?.payload.data.result || [],
apiResponse: effectiveQueryResponse.data?.payload.data.result || [],
name: widget.id,
});
if (setGraphVisibility) {
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(
queryResponse.data?.payload.data.result,
effectiveQueryResponse.data?.payload.data.result,
);
// eslint-disable-next-line no-param-reassign
queryResponse.data.payload.data.result = sortedSeriesData;
effectiveQueryResponse.data.payload.data.result = sortedSeriesData;
}
const chartData = getUPlotChartData(
queryResponse?.data?.payload,
const chartData = 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);
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?.stackedBarChart,
hiddenGraph,
);
]);
useEffect(() => {
if (widget.panelTypes === PANEL_TYPES.BAR && widget?.stackedBarChart) {
@ -110,16 +224,22 @@ function UplotPanelWrapper({
const { timezone } = useTimezone();
// Standard click handler without performance monitoring
const enhancedClickHandler = useMemo(() => {
if (!onClickHandler) return _noop;
return onClickHandler;
}, [onClickHandler]);
const options = useMemo(
() =>
getUPlotChartOptions({
id: widget?.id,
apiResponse: queryResponse.data?.payload,
apiResponse: effectiveQueryResponse.data?.payload,
dimensions: containerDimensions,
isDarkMode,
onDragSelect,
yAxisUnit: widget?.yAxisUnit,
onClickHandler: onClickHandler || _noop,
onClickHandler: enhancedClickHandler,
thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
@ -150,11 +270,11 @@ function UplotPanelWrapper({
widget.softMin,
widget.panelTypes,
widget?.stackedBarChart,
queryResponse.data?.payload,
effectiveQueryResponse.data?.payload,
containerDimensions,
isDarkMode,
onDragSelect,
onClickHandler,
enhancedClickHandler,
minTimeScale,
maxTimeScale,
graphVisibility,
@ -171,6 +291,14 @@ function UplotPanelWrapper({
],
);
console.log(
'chartData',
'x-axis',
chartData[0].length,
'y-axis',
chartData.length,
);
return (
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
<Uplot options={options} data={chartData} ref={lineChartRef} />
@ -183,7 +311,10 @@ function UplotPanelWrapper({
)}
{isFullViewMode && setGraphVisibility && !widget?.stackedBarChart && (
<GraphManager
data={getUPlotChartData(queryResponse?.data?.payload, widget.fillSpans)}
data={getUPlotChartData(
effectiveQueryResponse?.data?.payload,
widget.fillSpans,
)}
name={widget.id}
options={options}
yAxisUnit={widget.yAxisUnit}

View File

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

View File

@ -32,7 +32,7 @@ import { useTranslation } from 'react-i18next';
import { useMutation, useQuery, UseQueryResult } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { useRouteMatch } from 'react-router-dom';
import { Dispatch } from 'redux';
import { Dispatch as ReduxDispatch } from 'redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
@ -51,7 +51,7 @@ const DashboardContext = createContext<IDashboardContext>({
isDashboardSliderOpen: false,
isDashboardLocked: false,
handleToggleDashboardSlider: () => {},
handleDashboardLockToggle: () => {},
handleDashboardLockToggle: async () => {},
dashboardResponse: {} as UseQueryResult<Dashboard, unknown>,
selectedDashboard: {} as Dashboard,
dashboardId: '',
@ -80,6 +80,13 @@ const DashboardContext = createContext<IDashboardContext>({
isDashboardFetching: false,
columnWidths: {},
setColumnWidths: () => {},
// Global custom data state
globalCustomDataMode: false,
setGlobalCustomDataMode: () => {},
globalCustomXData: 15,
setGlobalCustomXData: () => {},
globalCustomYData: 4,
setGlobalCustomYData: () => {},
});
interface Props {
@ -146,7 +153,7 @@ export function DashboardProvider({
function setListSortOrder(sortOrder: DashboardSortOrder): void {
if (!isEqual(sortOrder, listSortOrder)) {
setListOrder(sortOrder);
setListOrder(sortOrder as any);
}
params.set('columnKey', sortOrder.columnKey as string);
params.set('order', sortOrder.order as string);
@ -155,7 +162,7 @@ export function DashboardProvider({
safeNavigate({ search: params.toString() });
}
const dispatch = useDispatch<Dispatch<AppActions>>();
const dispatch = useDispatch<ReduxDispatch<AppActions>>();
const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
@ -416,6 +423,13 @@ export function DashboardProvider({
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(
() => ({
toScrollWidgetId,
@ -424,7 +438,7 @@ export function DashboardProvider({
handleToggleDashboardSlider,
handleDashboardLockToggle,
dashboardResponse,
selectedDashboard,
selectedDashboard: selectedDashboard as Dashboard,
dashboardId,
layouts,
listSortOrder,
@ -445,6 +459,13 @@ export function DashboardProvider({
isDashboardFetching,
columnWidths,
setColumnWidths,
// Global custom data state
globalCustomDataMode,
setGlobalCustomDataMode,
globalCustomXData,
setGlobalCustomXData,
globalCustomYData,
setGlobalCustomYData,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[
@ -469,6 +490,10 @@ export function DashboardProvider({
isDashboardFetching,
columnWidths,
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 { UseQueryResult } from 'react-query';
import { Dashboard } from 'types/api/dashboard/getAll';
export interface DashboardSortOrder {
columnKey: string;
order: string;
pagination: string;
search: string;
columnKey?: string | null;
order?: string | null;
pagination?: string;
search?: string;
}
export type WidgetColumnWidths = {
[widgetId: string]: Record<string, number>;
};
export interface WidgetColumnWidths {
[key: string]: Record<string, number>;
}
export interface IDashboardContext {
isDashboardSliderOpen: boolean;
isDashboardLocked: boolean;
handleToggleDashboardSlider: (value: boolean) => void;
handleDashboardLockToggle: (value: boolean) => void;
handleDashboardLockToggle: (value: boolean) => Promise<void>;
dashboardResponse: UseQueryResult<Dashboard, unknown>;
selectedDashboard: Dashboard | undefined;
selectedDashboard: Dashboard;
dashboardId: string;
layouts: Layout[];
panelMap: Record<string, { widgets: Layout[]; collapsed: boolean }>;
setPanelMap: React.Dispatch<React.SetStateAction<Record<string, any>>>;
listSortOrder: DashboardSortOrder;
setListSortOrder: (sortOrder: DashboardSortOrder) => void;
setLayouts: React.Dispatch<React.SetStateAction<Layout[]>>;
setSelectedDashboard: React.Dispatch<
React.SetStateAction<Dashboard | undefined>
setPanelMap: Dispatch<
SetStateAction<Record<string, { widgets: Layout[]; collapsed: boolean }>>
>;
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;
setToScrollWidgetId: React.Dispatch<React.SetStateAction<string>>;
setToScrollWidgetId: Dispatch<SetStateAction<string>>;
updateLocalStorageDashboardVariables: (
id: string,
selectedValue:
@ -46,12 +47,19 @@ export interface IDashboardContext {
allSelected: boolean,
) => void;
variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
setVariablesToGetUpdated: Dispatch<SetStateAction<string[]>>;
dashboardQueryRangeCalled: boolean;
setDashboardQueryRangeCalled: (value: boolean) => void;
setDashboardQueryRangeCalled: Dispatch<SetStateAction<boolean>>;
selectedRowWidgetId: string | null;
setSelectedRowWidgetId: React.Dispatch<React.SetStateAction<string | null>>;
setSelectedRowWidgetId: Dispatch<SetStateAction<string | null>>;
isDashboardFetching: boolean;
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>;
legendPosition?: LegendPosition;
customLegendColors?: Record<string, string>;
customDataMode?: boolean;
customXData?: number; // number of data points
customYData?: number; // number of series
}
export interface Widgets extends IBaseWidget {
query: Query;