From 9964e3425a7362465d2c2918fbadf24eeef6ca79 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Wed, 28 Feb 2024 14:56:50 +0530 Subject: [PATCH] Feat: Bar chart (#4562) * feat: added bar panel and configuration for bar chart --- frontend/jest.config.ts | 2 +- frontend/src/assets/Dashboard/BarIcon.tsx | 41 + frontend/src/constants/panelTypes.ts | 2 + frontend/src/constants/queryBuilder.ts | 1 + .../FormAlertRules/ChartPreview/index.tsx | 13 +- .../container/FormAlertRules/QuerySection.tsx | 4 +- .../src/container/FormAlertRules/index.tsx | 19 +- .../GridCard/FullView/contants.ts | 1 + .../GridCard/FullView/index.tsx | 10 +- .../GridCardLayout/GridCard/index.tsx | 13 +- .../src/container/GridPanelSwitch/index.tsx | 5 + .../src/container/GridPanelSwitch/types.ts | 3 + .../container/ListAlertRules/ListAlert.tsx | 4 +- .../ComponentsSlider/constants.ts | 1 + .../ComponentsSlider/menuItems.ts | 2 + .../WidgetGraph/WidgetGraphContainer.tsx | 12 +- .../WidgetGraph/WidgetGraphs.tsx | 5 + .../LeftContainer/WidgetGraph/index.tsx | 3 +- .../NewWidget/RightContainer/constants.ts | 7 + .../container/QueryBuilder/QueryBuilder.tsx | 4 + .../hooks/queryBuilder/useCreateAlerts.tsx | 5 +- .../hooks/queryBuilder/useGetQueryRange.ts | 18 +- .../src/lib/uPlotLib/getUplotChartOptions.ts | 13 +- frontend/src/lib/uPlotLib/utils/constants.ts | 15 + frontend/src/lib/uPlotLib/utils/getAxes.ts | 1 - .../src/lib/uPlotLib/utils/getRenderer.ts | 23 +- .../src/lib/uPlotLib/utils/getSeriesData.ts | 35 +- .../utils/tests/__mocks__/seriesData.ts | 889 ++++++++++++++++++ .../tests/__mocks__/uplotChartOptionsData.ts | 453 +++++++++ .../utils/tests/getSeriesData.test.ts | 32 + .../utils/tests/getUplotChartOptions.test.ts | 68 ++ frontend/src/providers/QueryBuilder.tsx | 8 +- frontend/src/utils/getGraphType.ts | 9 + frontend/src/utils/getSortedSeriesData.ts | 20 + 34 files changed, 1694 insertions(+), 47 deletions(-) create mode 100644 frontend/src/assets/Dashboard/BarIcon.tsx create mode 100644 frontend/src/lib/uPlotLib/utils/constants.ts create mode 100644 frontend/src/lib/uPlotLib/utils/tests/__mocks__/seriesData.ts create mode 100644 frontend/src/lib/uPlotLib/utils/tests/__mocks__/uplotChartOptionsData.ts create mode 100644 frontend/src/lib/uPlotLib/utils/tests/getSeriesData.test.ts create mode 100644 frontend/src/lib/uPlotLib/utils/tests/getUplotChartOptions.test.ts create mode 100644 frontend/src/utils/getGraphType.ts create mode 100644 frontend/src/utils/getSortedSeriesData.ts diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index 0493353115..7b52ca5cf6 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -24,7 +24,7 @@ const config: Config.InitialOptions = { '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': 'jest-preview/transforms/file', }, transformIgnorePatterns: [ - 'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens)/)', + 'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|d3-interpolate|d3-color)/)', ], setupFilesAfterEnv: ['jest.setup.ts'], testPathIgnorePatterns: ['/node_modules/', '/public/'], diff --git a/frontend/src/assets/Dashboard/BarIcon.tsx b/frontend/src/assets/Dashboard/BarIcon.tsx new file mode 100644 index 0000000000..b8e6b3c52f --- /dev/null +++ b/frontend/src/assets/Dashboard/BarIcon.tsx @@ -0,0 +1,41 @@ +import { CSSProperties } from 'react'; + +function BarIcon({ + fillColor, +}: { + fillColor: CSSProperties['color']; +}): JSX.Element { + return ( + + + + + + ); +} + +export default BarIcon; diff --git a/frontend/src/constants/panelTypes.ts b/frontend/src/constants/panelTypes.ts index d16e5bf92d..c6db5db2da 100644 --- a/frontend/src/constants/panelTypes.ts +++ b/frontend/src/constants/panelTypes.ts @@ -14,6 +14,7 @@ export const PANEL_TYPES_COMPONENT_MAP = { [PANEL_TYPES.TRACE]: null, [PANEL_TYPES.LIST]: LogsPanelComponent, [PANEL_TYPES.EMPTY_WIDGET]: null, + [PANEL_TYPES.BAR]: Uplot, } as const; export const getComponentForPanelType = ( @@ -27,6 +28,7 @@ export const getComponentForPanelType = ( [PANEL_TYPES.TRACE]: null, [PANEL_TYPES.LIST]: dataSource === DataSource.LOGS ? LogsPanelComponent : TracesTableComponent, + [PANEL_TYPES.BAR]: Uplot, [PANEL_TYPES.EMPTY_WIDGET]: null, }; diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index c53873bc5c..936bfccdde 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -264,6 +264,7 @@ export enum PANEL_TYPES { TABLE = 'table', LIST = 'list', TRACE = 'trace', + BAR = 'bar', EMPTY_WIDGET = 'EMPTY_WIDGET', } diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index 97b540df35..9df51314d0 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -19,6 +19,8 @@ import { AlertDef } from 'types/api/alerts/def'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { getGraphType } from 'utils/getGraphType'; +import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { getTimeRange } from 'utils/getTimeRange'; import { ChartContainer, FailedMessageContainer } from './styles'; @@ -86,7 +88,7 @@ function ChartPreview({ { query: query || initialQueriesMap.metrics, globalSelectedInterval: selectedInterval, - graphType, + graphType: getGraphType(graphType), selectedTime, params: { allowSelectedIntervalForStepGen, @@ -114,6 +116,13 @@ function ChartPreview({ setMaxTimeScale(endTime); }, [maxTime, minTime, globalSelectedInterval, queryResponse]); + if (queryResponse.data && graphType === PANEL_TYPES.BAR) { + const sortedSeriesData = getSortedSeriesData( + queryResponse.data?.payload.data.result, + ); + queryResponse.data.payload.data.result = sortedSeriesData; + } + const chartData = getUPlotChartData(queryResponse?.data?.payload); const containerDimensions = useResizeObserver(graphRef); @@ -153,6 +162,7 @@ function ChartPreview({ ], softMax: null, softMin: null, + panelType: graphType, }), [ yAxisUnit, @@ -165,6 +175,7 @@ function ChartPreview({ t, optionName, alertDef?.condition.targetUnit, + graphType, ], ); diff --git a/frontend/src/container/FormAlertRules/QuerySection.tsx b/frontend/src/container/FormAlertRules/QuerySection.tsx index 619e61af2f..9eb4fd4703 100644 --- a/frontend/src/container/FormAlertRules/QuerySection.tsx +++ b/frontend/src/container/FormAlertRules/QuerySection.tsx @@ -22,6 +22,7 @@ function QuerySection({ setQueryCategory, alertType, runQuery, + panelType, }: QuerySectionProps): JSX.Element { // init namespace for translations const { t } = useTranslation('alerts'); @@ -44,7 +45,7 @@ function QuerySection({ const renderMetricUI = (): JSX.Element => ( void; alertType: AlertTypes; runQuery: VoidFunction; + panelType: PANEL_TYPES; } export default QuerySection; diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index b87f025f9d..396474211f 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -10,6 +10,7 @@ import { import saveAlertApi from 'api/alerts/save'; import testAlertApi from 'api/alerts/testAlert'; import { FeatureKeys } from 'constants/features'; +import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag'; @@ -20,6 +21,7 @@ import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval'; import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag'; import { useNotifications } from 'hooks/useNotifications'; +import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi'; import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi'; @@ -68,14 +70,23 @@ function FormAlertRules({ GlobalReducer >((state) => state.globalTime); + const urlQuery = useUrlQuery(); + + const panelType = urlQuery.get(QueryParams.panelTypes) as PANEL_TYPES | null; + const { currentQuery, - panelType, stagedQuery, handleRunQuery, + handleSetConfig, + initialDataSource, redirectWithQueryBuilderData, } = useQueryBuilder(); + useEffect(() => { + handleSetConfig(panelType || PANEL_TYPES.TIME_SERIES, initialDataSource); + }, [handleSetConfig, initialDataSource, panelType]); + // use query client const ruleCache = useQueryClient(); @@ -277,7 +288,7 @@ function FormAlertRules({ promQueries: mapQueryDataToApi(currentQuery.promql, 'name').data, chQueries: mapQueryDataToApi(currentQuery.clickhouse_sql, 'name').data, queryType: currentQuery.queryType, - panelType: initQuery.panelType, + panelType: panelType || initQuery.panelType, unit: currentQuery.unit, }, }, @@ -290,6 +301,7 @@ function FormAlertRules({ alertDef, alertType, initQuery, + panelType, ]); const isAlertAvialable = useIsFeatureDisabled( @@ -423,6 +435,7 @@ function FormAlertRules({ selectedInterval={globalSelectedInterval} alertDef={alertDef} yAxisUnit={yAxisUnit || ''} + graphType={panelType || PANEL_TYPES.TIME_SERIES} /> ); @@ -439,6 +452,7 @@ function FormAlertRules({ alertDef={alertDef} selectedInterval={globalSelectedInterval} yAxisUnit={yAxisUnit || ''} + graphType={panelType || PANEL_TYPES.TIME_SERIES} /> ); @@ -495,6 +509,7 @@ function FormAlertRules({ setQueryCategory={onQueryCategoryChange} alertType={alertType || AlertTypes.METRICS_BASED_ALERT} runQuery={handleRunQuery} + panelType={panelType || PANEL_TYPES.TIME_SERIES} /> ; + }; [PANEL_TYPES.EMPTY_WIDGET]: null; }; diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx index 52d54146b8..4ed655b8e4 100644 --- a/frontend/src/container/ListAlertRules/ListAlert.tsx +++ b/frontend/src/container/ListAlertRules/ListAlert.tsx @@ -120,7 +120,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { history.push( `${ROUTES.EDIT_ALERTS}?ruleId=${record.id.toString()}&${ QueryParams.compositeQuery - }=${encodeURIComponent(JSON.stringify(compositeQuery))}`, + }=${encodeURIComponent(JSON.stringify(compositeQuery))}&panelTypes=${ + record.condition.compositeQuery.panelType + }`, ); }) .catch(handleError); diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/constants.ts b/frontend/src/container/NewDashboard/ComponentsSlider/constants.ts index 44512e3a00..11b693be3b 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/constants.ts +++ b/frontend/src/container/NewDashboard/ComponentsSlider/constants.ts @@ -9,6 +9,7 @@ export const PANEL_TYPES_INITIAL_QUERY = { [PANEL_TYPES.TABLE]: initialQueriesMap.metrics, [PANEL_TYPES.LIST]: initialQueriesMap.logs, [PANEL_TYPES.TRACE]: initialQueriesMap.traces, + [PANEL_TYPES.BAR]: initialQueriesMap.metrics, [PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics, }; diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts index 1aaa3a71ea..3ea0268cdb 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts +++ b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts @@ -1,3 +1,4 @@ +import BarIcon from 'assets/Dashboard/BarIcon'; import List from 'assets/Dashboard/List'; import TableIcon from 'assets/Dashboard/Table'; import TimeSeriesIcon from 'assets/Dashboard/TimeSeries'; @@ -18,6 +19,7 @@ const Items: ItemsProps[] = [ }, { name: PANEL_TYPES.TABLE, Icon: TableIcon, display: 'Table' }, { name: PANEL_TYPES.LIST, Icon: List, display: 'List' }, + { name: PANEL_TYPES.BAR, Icon: BarIcon, display: 'Bar' }, ]; interface ItemsProps { diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx index 2472d94092..1e5bcf6721 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx @@ -5,6 +5,8 @@ import { WidgetGraphProps } from 'container/NewWidget/types'; import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange'; import useUrlQuery from 'hooks/useUrlQuery'; import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { getGraphType } from 'utils/getGraphType'; +import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { NotFoundContainer } from './styles'; import WidgetGraph from './WidgetGraphs'; @@ -31,10 +33,17 @@ function WidgetGraphContainer({ const selectedWidget = widgets.find((e) => e.id === widgetId); const getWidgetQueryRange = useGetWidgetQueryRange({ - graphType: selectedGraph, + graphType: getGraphType(selectedGraph), selectedTime: selectedTime.enum, }); + if (getWidgetQueryRange.data && selectedGraph === PANEL_TYPES.BAR) { + const sortedSeriesData = getSortedSeriesData( + getWidgetQueryRange.data?.payload.data.result, + ); + getWidgetQueryRange.data.payload.data.result = sortedSeriesData; + } + if (selectedWidget === undefined) { return Invalid widget; } @@ -83,6 +92,7 @@ function WidgetGraphContainer({ selectedLogFields={selectedLogFields} selectedTracesFields={selectedTracesFields} selectedTime={selectedTime} + selectedGraph={selectedGraph} /> ); } diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx index ccd0a91ea3..647b746c2d 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx @@ -1,4 +1,5 @@ import { QueryParams } from 'constants/query'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import GridPanelSwitch from 'container/GridPanelSwitch'; import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types'; import { timePreferance } from 'container/NewWidget/RightContainer/timeItems'; @@ -34,6 +35,7 @@ function WidgetGraph({ selectedLogFields, selectedTracesFields, selectedTime, + selectedGraph, }: WidgetGraphProps): JSX.Element { const { stagedQuery, currentQuery } = useQueryBuilder(); @@ -130,6 +132,7 @@ function WidgetGraph({ maxTimeScale, softMax, softMin, + panelType: selectedGraph, }), [ widgetId, @@ -144,6 +147,7 @@ function WidgetGraph({ maxTimeScale, softMax, softMin, + selectedGraph, ], ); @@ -183,6 +187,7 @@ interface WidgetGraphProps { selectedLogFields: Widgets['selectedLogFields']; selectedTracesFields: Widgets['selectedTracesFields']; selectedTime: timePreferance; + selectedGraph: PANEL_TYPES; } export default WidgetGraph; diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx index 1f306a41a2..14dcedad08 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx @@ -5,6 +5,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import useUrlQuery from 'hooks/useUrlQuery'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { memo } from 'react'; +import { getGraphType } from 'utils/getGraphType'; import { WidgetGraphProps } from '../../types'; import PlotTag from './PlotTag'; @@ -34,7 +35,7 @@ function WidgetGraph({ const selectedWidget = widgets.find((e) => e.id === widgetId); const getWidgetQueryRange = useGetWidgetQueryRange({ - graphType: selectedGraph, + graphType: getGraphType(selectedGraph), selectedTime: selectedTime.enum, }); diff --git a/frontend/src/container/NewWidget/RightContainer/constants.ts b/frontend/src/container/NewWidget/RightContainer/constants.ts index a6663ac75c..a3ce202f85 100644 --- a/frontend/src/container/NewWidget/RightContainer/constants.ts +++ b/frontend/src/container/NewWidget/RightContainer/constants.ts @@ -26,6 +26,7 @@ export const panelTypeVsThreshold: { [key in PANEL_TYPES]: boolean } = { [PANEL_TYPES.VALUE]: true, [PANEL_TYPES.TABLE]: true, [PANEL_TYPES.LIST]: false, + [PANEL_TYPES.BAR]: true, [PANEL_TYPES.TRACE]: false, [PANEL_TYPES.EMPTY_WIDGET]: false, } as const; @@ -35,6 +36,7 @@ export const panelTypeVsSoftMinMax: { [key in PANEL_TYPES]: boolean } = { [PANEL_TYPES.VALUE]: false, [PANEL_TYPES.TABLE]: false, [PANEL_TYPES.LIST]: false, + [PANEL_TYPES.BAR]: true, [PANEL_TYPES.TRACE]: false, [PANEL_TYPES.EMPTY_WIDGET]: false, } as const; @@ -44,6 +46,7 @@ export const panelTypeVsDragAndDrop: { [key in PANEL_TYPES]: boolean } = { [PANEL_TYPES.VALUE]: true, [PANEL_TYPES.TABLE]: true, [PANEL_TYPES.LIST]: false, + [PANEL_TYPES.BAR]: false, [PANEL_TYPES.TRACE]: false, [PANEL_TYPES.EMPTY_WIDGET]: false, } as const; @@ -53,6 +56,7 @@ export const panelTypeVsFillSpan: { [key in PANEL_TYPES]: boolean } = { [PANEL_TYPES.VALUE]: false, [PANEL_TYPES.TABLE]: false, [PANEL_TYPES.LIST]: false, + [PANEL_TYPES.BAR]: false, [PANEL_TYPES.TRACE]: false, [PANEL_TYPES.EMPTY_WIDGET]: false, } as const; @@ -62,6 +66,7 @@ export const panelTypeVsYAxisUnit: { [key in PANEL_TYPES]: boolean } = { [PANEL_TYPES.VALUE]: true, [PANEL_TYPES.TABLE]: true, [PANEL_TYPES.LIST]: false, + [PANEL_TYPES.BAR]: true, [PANEL_TYPES.TRACE]: false, [PANEL_TYPES.EMPTY_WIDGET]: false, } as const; @@ -71,6 +76,7 @@ export const panelTypeVsCreateAlert: { [key in PANEL_TYPES]: boolean } = { [PANEL_TYPES.VALUE]: true, [PANEL_TYPES.TABLE]: false, [PANEL_TYPES.LIST]: false, + [PANEL_TYPES.BAR]: true, [PANEL_TYPES.TRACE]: false, [PANEL_TYPES.EMPTY_WIDGET]: false, } as const; @@ -82,6 +88,7 @@ export const panelTypeVsPanelTimePreferences: { [PANEL_TYPES.VALUE]: true, [PANEL_TYPES.TABLE]: true, [PANEL_TYPES.LIST]: false, + [PANEL_TYPES.BAR]: true, [PANEL_TYPES.TRACE]: false, [PANEL_TYPES.EMPTY_WIDGET]: false, } as const; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index f67533e427..0bdc321c1e 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -46,6 +46,10 @@ export const QueryBuilder = memo(function QueryBuilder({ useEffect(() => { if (currentDataSource !== initialDataSource || newPanelType !== panelType) { + if (panelType === PANEL_TYPES.BAR) { + handleSetConfig(PANEL_TYPES.BAR, DataSource.METRICS); + return; + } handleSetConfig(newPanelType, currentDataSource); } }, [ diff --git a/frontend/src/hooks/queryBuilder/useCreateAlerts.tsx b/frontend/src/hooks/queryBuilder/useCreateAlerts.tsx index a80806a8fd..6cf7da047c 100644 --- a/frontend/src/hooks/queryBuilder/useCreateAlerts.tsx +++ b/frontend/src/hooks/queryBuilder/useCreateAlerts.tsx @@ -14,6 +14,7 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { Widgets } from 'types/api/dashboard/getAll'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { getGraphType } from 'utils/getGraphType'; const useCreateAlerts = (widget?: Widgets): VoidFunction => { const queryRangeMutation = useMutation(getQueryRangeFormat); @@ -33,7 +34,7 @@ const useCreateAlerts = (widget?: Widgets): VoidFunction => { const { queryPayload } = prepareQueryRangePayload({ query: widget.query, globalSelectedInterval, - graphType: widget.panelTypes, + graphType: getGraphType(widget.panelTypes), selectedTime: widget.timePreferance, variables: getDashboardVariables(selectedDashboard?.data.variables), }); @@ -44,7 +45,7 @@ const useCreateAlerts = (widget?: Widgets): VoidFunction => { history.push( `${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent( JSON.stringify(updatedQuery), - )}`, + )}&${QueryParams.panelTypes}=${widget.panelTypes}`, ); }, onError: () => { diff --git a/frontend/src/hooks/queryBuilder/useGetQueryRange.ts b/frontend/src/hooks/queryBuilder/useGetQueryRange.ts index c54a07461d..e832be1c4c 100644 --- a/frontend/src/hooks/queryBuilder/useGetQueryRange.ts +++ b/frontend/src/hooks/queryBuilder/useGetQueryRange.ts @@ -1,3 +1,4 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { GetMetricQueryRange, @@ -14,6 +15,17 @@ type UseGetQueryRange = ( ) => UseQueryResult, Error>; export const useGetQueryRange: UseGetQueryRange = (requestData, options) => { + const newRequestData: GetQueryResultsProps = useMemo( + () => ({ + ...requestData, + graphType: + requestData.graphType === PANEL_TYPES.BAR + ? PANEL_TYPES.TIME_SERIES + : requestData.graphType, + }), + [requestData], + ); + const queryKey = useMemo(() => { if (options?.queryKey && Array.isArray(options.queryKey)) { return [...options.queryKey]; @@ -23,11 +35,11 @@ export const useGetQueryRange: UseGetQueryRange = (requestData, options) => { return options.queryKey; } - return [REACT_QUERY_KEY.GET_QUERY_RANGE, requestData]; - }, [options?.queryKey, requestData]); + return [REACT_QUERY_KEY.GET_QUERY_RANGE, newRequestData]; + }, [options?.queryKey, newRequestData]); return useQuery, Error>({ - queryFn: async ({ signal }) => GetMetricQueryRange(requestData, signal), + queryFn: async ({ signal }) => GetMetricQueryRange(newRequestData, signal), ...options, queryKey, }); diff --git a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts index b6e61a2ae6..dae5bbdfd5 100644 --- a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts +++ b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts @@ -4,6 +4,7 @@ /* eslint-disable sonarjs/cognitive-complexity */ import './uPlotLib.styles.scss'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { FullViewProps } from 'container/GridCardLayout/GridCard/FullView/types'; import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types'; import { Dimensions } from 'hooks/useDimensions'; @@ -19,11 +20,12 @@ import getSeries from './utils/getSeriesData'; import { getXAxisScale } from './utils/getXAxisScale'; import { getYAxisScale } from './utils/getYAxisScale'; -interface GetUPlotChartOptions { +export interface GetUPlotChartOptions { id?: string; apiResponse?: MetricRangePayloadProps; dimensions: Dimensions; isDarkMode: boolean; + panelType?: PANEL_TYPES; onDragSelect?: (startTime: number, endTime: number) => void; yAxisUnit?: string; onClickHandler?: OnClickPluginOpts['onClick']; @@ -55,6 +57,7 @@ export const getUPlotChartOptions = ({ fillSpans, softMax, softMin, + panelType, }: GetUPlotChartOptions): uPlot.Options => { const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale); @@ -209,12 +212,12 @@ export const getUPlotChartOptions = ({ }, ], }, - series: getSeries( + series: getSeries({ apiResponse, - apiResponse?.data.result, + widgetMetaData: apiResponse?.data.result, graphsVisibilityStates, - fillSpans, - ), + panelType, + }), axes: getAxes(isDarkMode, yAxisUnit), }; }; diff --git a/frontend/src/lib/uPlotLib/utils/constants.ts b/frontend/src/lib/uPlotLib/utils/constants.ts new file mode 100644 index 0000000000..7bba405533 --- /dev/null +++ b/frontend/src/lib/uPlotLib/utils/constants.ts @@ -0,0 +1,15 @@ +// Define type annotations for style and interp +export const drawStyles = { + line: 'line', + bars: 'bars', + barsLeft: 'barsLeft', + barsRight: 'barsRight', + points: 'points', +}; + +export const lineInterpolations = { + linear: 'linear', + stepAfter: 'stepAfter', + stepBefore: 'stepBefore', + spline: 'spline', +}; diff --git a/frontend/src/lib/uPlotLib/utils/getAxes.ts b/frontend/src/lib/uPlotLib/utils/getAxes.ts index 2066f125da..8613ef9cbf 100644 --- a/frontend/src/lib/uPlotLib/utils/getAxes.ts +++ b/frontend/src/lib/uPlotLib/utils/getAxes.ts @@ -61,5 +61,4 @@ const getAxes = (isDarkMode: boolean, yAxisUnit?: string): any => [ }, }, ]; - export default getAxes; diff --git a/frontend/src/lib/uPlotLib/utils/getRenderer.ts b/frontend/src/lib/uPlotLib/utils/getRenderer.ts index 564a4532b0..805644b4cc 100644 --- a/frontend/src/lib/uPlotLib/utils/getRenderer.ts +++ b/frontend/src/lib/uPlotLib/utils/getRenderer.ts @@ -1,30 +1,21 @@ import uPlot from 'uplot'; -// Define type annotations for style and interp -export const drawStyles = { - line: 'line', - bars: 'bars', - barsLeft: 'barsLeft', - barsRight: 'barsRight', - points: 'points', -}; +import { drawStyles, lineInterpolations } from './constants'; -export const lineInterpolations = { - linear: 'linear', - stepAfter: 'stepAfter', - stepBefore: 'stepBefore', - spline: 'spline', -}; - -const { spline: splinePath } = uPlot.paths; +const { spline: splinePath, bars: barsPath } = uPlot.paths; const spline = splinePath && splinePath(); +const bars = barsPath && barsPath(); const getRenderer = (style: any, interp: any): any => { if (style === drawStyles.line && interp === lineInterpolations.spline) { return spline; } + if (style === drawStyles.bars) { + return bars; + } + return null; }; diff --git a/frontend/src/lib/uPlotLib/utils/getSeriesData.ts b/frontend/src/lib/uPlotLib/utils/getSeriesData.ts index 16be3aa04e..cf60a632cb 100644 --- a/frontend/src/lib/uPlotLib/utils/getSeriesData.ts +++ b/frontend/src/lib/uPlotLib/utils/getSeriesData.ts @@ -1,10 +1,13 @@ +/* eslint-disable sonarjs/cognitive-complexity */ +import { PANEL_TYPES } from 'constants/queryBuilder'; import { themeColors } from 'constants/theme'; import getLabelName from 'lib/getLabelName'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { QueryData } from 'types/api/widgets/getQuery'; +import { drawStyles, lineInterpolations } from './constants'; import { generateColor } from './generateColor'; -import getRenderer, { drawStyles, lineInterpolations } from './getRenderer'; +import getRenderer from './getRenderer'; const paths = ( u: any, @@ -23,17 +26,17 @@ const paths = ( return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip); }; -const getSeries = ( - apiResponse?: MetricRangePayloadProps, - widgetMetaData: QueryData[] = [], - graphsVisibilityStates?: boolean[], -): uPlot.Options['series'] => { +const getSeries = ({ + apiResponse, + widgetMetaData, + graphsVisibilityStates, + panelType, +}: GetSeriesProps): uPlot.Options['series'] => { const configurations: uPlot.Series[] = [ { label: 'Timestamp', stroke: 'purple' }, ]; const seriesList = apiResponse?.data.result || []; - const newGraphVisibilityStates = graphsVisibilityStates?.slice(1); for (let i = 0; i < seriesList?.length; i += 1) { @@ -52,10 +55,17 @@ const getSeries = ( const seriesObj: any = { paths, - drawStyle: drawStyles.line, - lineInterpolation: lineInterpolations.spline, + drawStyle: + panelType && panelType === PANEL_TYPES.BAR + ? drawStyles.bars + : drawStyles.line, + lineInterpolation: + panelType && panelType === PANEL_TYPES.BAR + ? null + : lineInterpolations.spline, show: newGraphVisibilityStates ? newGraphVisibilityStates[i] : true, label, + fill: panelType && panelType === PANEL_TYPES.BAR ? `${color}40` : undefined, stroke: color, width: 2, spanGaps: true, @@ -72,4 +82,11 @@ const getSeries = ( return configurations; }; +export type GetSeriesProps = { + apiResponse?: MetricRangePayloadProps; + widgetMetaData: QueryData[]; + graphsVisibilityStates?: boolean[]; + panelType?: PANEL_TYPES; +}; + export default getSeries; diff --git a/frontend/src/lib/uPlotLib/utils/tests/__mocks__/seriesData.ts b/frontend/src/lib/uPlotLib/utils/tests/__mocks__/seriesData.ts new file mode 100644 index 0000000000..6986625bf8 --- /dev/null +++ b/frontend/src/lib/uPlotLib/utils/tests/__mocks__/seriesData.ts @@ -0,0 +1,889 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; + +import { GetSeriesProps } from '../../getSeriesData'; + +export const seriesBarChartData = { + apiResponse: { + data: { + result: [ + { + metric: {}, + values: [ + [1708683840, '6260'], + [1708683240, '6251'], + [1708683780, '6237'], + [1708683660, '6188'], + [1708683720, '6176'], + [1708683360, '6169'], + [1708683480, '6068'], + [1708683540, '6025'], + [1708683300, '6042'], + [1708683420, '6001'], + [1708683600, '5969'], + [1708683900, '5955'], + [1708683180, '4301'], + ], + queryName: 'F1', + legend: 'firstLegend', + }, + { + metric: {}, + values: [ + [1708683240, '3378'], + [1708683300, '3269'], + [1708683360, '3341'], + [1708683420, '3269'], + [1708683480, '3296'], + [1708683540, '3280'], + [1708683600, '3260'], + [1708683660, '3351'], + [1708683720, '3345'], + [1708683780, '3370'], + [1708683840, '3382'], + [1708683900, '3249'], + [1708683960, '212'], + ], + queryName: 'A', + legend: 'secondLegend', + }, + { + metric: {}, + values: [ + [1708683840, '2878'], + [1708683240, '2873'], + [1708683780, '2867'], + [1708683660, '2837'], + [1708683720, '2831'], + [1708683360, '2828'], + [1708683300, '2773'], + [1708683480, '2772'], + [1708683540, '2745'], + [1708683420, '2732'], + [1708683180, '2729'], + [1708683600, '2709'], + [1708683900, '2706'], + ], + queryName: 'B', + legend: 'thirdLegend', + }, + { + metric: { + F2: 'F2', + }, + values: [ + [1708683840, '504'], + [1708683240, '505'], + [1708683780, '503'], + [1708683660, '514'], + [1708683720, '514'], + [1708683360, '513'], + [1708683480, '524'], + [1708683540, '535'], + [1708683300, '496'], + [1708683420, '537'], + [1708683600, '551'], + [1708683900, '543'], + [1708683180, '-1157'], + ], + queryName: 'F2', + legend: 'forthLength', + }, + ], + resultType: '', + newResult: { + status: 'success', + data: { + resultType: '', + result: [ + { + queryName: 'A', + series: [ + { + labels: {}, + labelsArray: null, + values: [ + { + timestamp: 1708683240000, + value: '3378', + }, + { + timestamp: 1708683300000, + value: '3269', + }, + { + timestamp: 1708683360000, + value: '3341', + }, + { + timestamp: 1708683420000, + value: '3269', + }, + { + timestamp: 1708683480000, + value: '3296', + }, + { + timestamp: 1708683540000, + value: '3280', + }, + { + timestamp: 1708683600000, + value: '3260', + }, + { + timestamp: 1708683660000, + value: '3351', + }, + { + timestamp: 1708683720000, + value: '3345', + }, + { + timestamp: 1708683780000, + value: '3370', + }, + { + timestamp: 1708683840000, + value: '3382', + }, + { + timestamp: 1708683900000, + value: '3249', + }, + { + timestamp: 1708683960000, + value: '212', + }, + ], + }, + ], + list: null, + }, + { + queryName: 'B', + series: [ + { + labels: {}, + labelsArray: null, + values: [ + { + timestamp: 1708683840000, + value: '2878', + }, + { + timestamp: 1708683240000, + value: '2873', + }, + { + timestamp: 1708683780000, + value: '2867', + }, + { + timestamp: 1708683660000, + value: '2837', + }, + { + timestamp: 1708683720000, + value: '2831', + }, + { + timestamp: 1708683360000, + value: '2828', + }, + { + timestamp: 1708683300000, + value: '2773', + }, + { + timestamp: 1708683480000, + value: '2772', + }, + { + timestamp: 1708683540000, + value: '2745', + }, + { + timestamp: 1708683420000, + value: '2732', + }, + { + timestamp: 1708683180000, + value: '2729', + }, + { + timestamp: 1708683600000, + value: '2709', + }, + { + timestamp: 1708683900000, + value: '2706', + }, + ], + }, + ], + list: null, + }, + { + queryName: 'F2', + series: [ + { + labels: { + F2: 'F2', + }, + values: [ + { + timestamp: 1708683840000, + value: '504', + }, + { + timestamp: 1708683240000, + value: '505', + }, + { + timestamp: 1708683780000, + value: '503', + }, + { + timestamp: 1708683660000, + value: '514', + }, + { + timestamp: 1708683720000, + value: '514', + }, + { + timestamp: 1708683360000, + value: '513', + }, + { + timestamp: 1708683480000, + value: '524', + }, + { + timestamp: 1708683540000, + value: '535', + }, + { + timestamp: 1708683300000, + value: '496', + }, + { + timestamp: 1708683420000, + value: '537', + }, + { + timestamp: 1708683600000, + value: '551', + }, + { + timestamp: 1708683900000, + value: '543', + }, + { + timestamp: 1708683180000, + value: '-1157', + }, + ], + }, + ], + list: null, + }, + { + queryName: 'F1', + series: [ + { + labels: {}, + labelsArray: null, + values: [ + { + timestamp: 1708683840000, + value: '6260', + }, + { + timestamp: 1708683240000, + value: '6251', + }, + { + timestamp: 1708683780000, + value: '6237', + }, + { + timestamp: 1708683660000, + value: '6188', + }, + { + timestamp: 1708683720000, + value: '6176', + }, + { + timestamp: 1708683360000, + value: '6169', + }, + { + timestamp: 1708683480000, + value: '6068', + }, + { + timestamp: 1708683540000, + value: '6025', + }, + { + timestamp: 1708683300000, + value: '6042', + }, + { + timestamp: 1708683420000, + value: '6001', + }, + { + timestamp: 1708683600000, + value: '5969', + }, + { + timestamp: 1708683900000, + value: '5955', + }, + { + timestamp: 1708683180000, + value: '4301', + }, + ], + }, + ], + list: null, + }, + ], + }, + }, + }, + }, + widgetMetaData: [ + { + metric: {}, + values: [ + [1708683840, '6260'], + [1708683240, '6251'], + [1708683780, '6237'], + [1708683660, '6188'], + [1708683720, '6176'], + [1708683360, '6169'], + [1708683480, '6068'], + [1708683540, '6025'], + [1708683300, '6042'], + [1708683420, '6001'], + [1708683600, '5969'], + [1708683900, '5955'], + [1708683180, '4301'], + ], + queryName: 'F1', + legend: 'firstLegend', + }, + { + metric: {}, + values: [ + [1708683240, '3378'], + [1708683300, '3269'], + [1708683360, '3341'], + [1708683420, '3269'], + [1708683480, '3296'], + [1708683540, '3280'], + [1708683600, '3260'], + [1708683660, '3351'], + [1708683720, '3345'], + [1708683780, '3370'], + [1708683840, '3382'], + [1708683900, '3249'], + [1708683960, '212'], + ], + queryName: 'A', + legend: 'A-A', + }, + { + metric: {}, + values: [ + [1708683840, '2878'], + [1708683240, '2873'], + [1708683780, '2867'], + [1708683660, '2837'], + [1708683720, '2831'], + [1708683360, '2828'], + [1708683300, '2773'], + [1708683480, '2772'], + [1708683540, '2745'], + [1708683420, '2732'], + [1708683180, '2729'], + [1708683600, '2709'], + [1708683900, '2706'], + ], + queryName: 'B', + legend: 'B-B', + }, + { + metric: { + F2: 'F2', + }, + values: [ + [1708683840, '504'], + [1708683240, '505'], + [1708683780, '503'], + [1708683660, '514'], + [1708683720, '514'], + [1708683360, '513'], + [1708683480, '524'], + [1708683540, '535'], + [1708683300, '496'], + [1708683420, '537'], + [1708683600, '551'], + [1708683900, '543'], + [1708683180, '-1157'], + ], + queryName: 'F2', + legend: 'F2', + }, + ], + graphsVisibilityStates: [true, true, true, true, true], + panelType: PANEL_TYPES.BAR, +} as GetSeriesProps; + +export const seriesLineChartData = { + apiResponse: { + data: { + result: [ + { + metric: {}, + values: [ + [1708683840, '6260'], + [1708683240, '6251'], + [1708683780, '6237'], + [1708683660, '6188'], + [1708683720, '6176'], + [1708683360, '6169'], + [1708683480, '6068'], + [1708683540, '6025'], + [1708683300, '6042'], + [1708683420, '6001'], + [1708683600, '5969'], + [1708683900, '5955'], + [1708683180, '4301'], + ], + queryName: 'F1', + legend: 'firstLegend', + }, + { + metric: {}, + values: [ + [1708683240, '3378'], + [1708683300, '3269'], + [1708683360, '3341'], + [1708683420, '3269'], + [1708683480, '3296'], + [1708683540, '3280'], + [1708683600, '3260'], + [1708683660, '3351'], + [1708683720, '3345'], + [1708683780, '3370'], + [1708683840, '3382'], + [1708683900, '3249'], + [1708683960, '212'], + ], + queryName: 'A', + legend: 'secondLegend', + }, + { + metric: {}, + values: [ + [1708683840, '2878'], + [1708683240, '2873'], + [1708683780, '2867'], + [1708683660, '2837'], + [1708683720, '2831'], + [1708683360, '2828'], + [1708683300, '2773'], + [1708683480, '2772'], + [1708683540, '2745'], + [1708683420, '2732'], + [1708683180, '2729'], + [1708683600, '2709'], + [1708683900, '2706'], + ], + queryName: 'B', + legend: 'thirdLegend', + }, + { + metric: { + F2: 'F2', + }, + values: [ + [1708683840, '504'], + [1708683240, '505'], + [1708683780, '503'], + [1708683660, '514'], + [1708683720, '514'], + [1708683360, '513'], + [1708683480, '524'], + [1708683540, '535'], + [1708683300, '496'], + [1708683420, '537'], + [1708683600, '551'], + [1708683900, '543'], + [1708683180, '-1157'], + ], + queryName: 'F2', + legend: 'forthLength', + }, + ], + resultType: '', + newResult: { + status: 'success', + data: { + resultType: '', + result: [ + { + queryName: 'A', + series: [ + { + labels: {}, + labelsArray: null, + values: [ + { + timestamp: 1708683240000, + value: '3378', + }, + { + timestamp: 1708683300000, + value: '3269', + }, + { + timestamp: 1708683360000, + value: '3341', + }, + { + timestamp: 1708683420000, + value: '3269', + }, + { + timestamp: 1708683480000, + value: '3296', + }, + { + timestamp: 1708683540000, + value: '3280', + }, + { + timestamp: 1708683600000, + value: '3260', + }, + { + timestamp: 1708683660000, + value: '3351', + }, + { + timestamp: 1708683720000, + value: '3345', + }, + { + timestamp: 1708683780000, + value: '3370', + }, + { + timestamp: 1708683840000, + value: '3382', + }, + { + timestamp: 1708683900000, + value: '3249', + }, + { + timestamp: 1708683960000, + value: '212', + }, + ], + }, + ], + list: null, + }, + { + queryName: 'B', + series: [ + { + labels: {}, + labelsArray: null, + values: [ + { + timestamp: 1708683840000, + value: '2878', + }, + { + timestamp: 1708683240000, + value: '2873', + }, + { + timestamp: 1708683780000, + value: '2867', + }, + { + timestamp: 1708683660000, + value: '2837', + }, + { + timestamp: 1708683720000, + value: '2831', + }, + { + timestamp: 1708683360000, + value: '2828', + }, + { + timestamp: 1708683300000, + value: '2773', + }, + { + timestamp: 1708683480000, + value: '2772', + }, + { + timestamp: 1708683540000, + value: '2745', + }, + { + timestamp: 1708683420000, + value: '2732', + }, + { + timestamp: 1708683180000, + value: '2729', + }, + { + timestamp: 1708683600000, + value: '2709', + }, + { + timestamp: 1708683900000, + value: '2706', + }, + ], + }, + ], + list: null, + }, + { + queryName: 'F2', + series: [ + { + labels: { + F2: 'F2', + }, + values: [ + { + timestamp: 1708683840000, + value: '504', + }, + { + timestamp: 1708683240000, + value: '505', + }, + { + timestamp: 1708683780000, + value: '503', + }, + { + timestamp: 1708683660000, + value: '514', + }, + { + timestamp: 1708683720000, + value: '514', + }, + { + timestamp: 1708683360000, + value: '513', + }, + { + timestamp: 1708683480000, + value: '524', + }, + { + timestamp: 1708683540000, + value: '535', + }, + { + timestamp: 1708683300000, + value: '496', + }, + { + timestamp: 1708683420000, + value: '537', + }, + { + timestamp: 1708683600000, + value: '551', + }, + { + timestamp: 1708683900000, + value: '543', + }, + { + timestamp: 1708683180000, + value: '-1157', + }, + ], + }, + ], + list: null, + }, + { + queryName: 'F1', + series: [ + { + labels: {}, + labelsArray: null, + values: [ + { + timestamp: 1708683840000, + value: '6260', + }, + { + timestamp: 1708683240000, + value: '6251', + }, + { + timestamp: 1708683780000, + value: '6237', + }, + { + timestamp: 1708683660000, + value: '6188', + }, + { + timestamp: 1708683720000, + value: '6176', + }, + { + timestamp: 1708683360000, + value: '6169', + }, + { + timestamp: 1708683480000, + value: '6068', + }, + { + timestamp: 1708683540000, + value: '6025', + }, + { + timestamp: 1708683300000, + value: '6042', + }, + { + timestamp: 1708683420000, + value: '6001', + }, + { + timestamp: 1708683600000, + value: '5969', + }, + { + timestamp: 1708683900000, + value: '5955', + }, + { + timestamp: 1708683180000, + value: '4301', + }, + ], + }, + ], + list: null, + }, + ], + }, + }, + }, + }, + widgetMetaData: [ + { + metric: {}, + values: [ + [1708683840, '6260'], + [1708683240, '6251'], + [1708683780, '6237'], + [1708683660, '6188'], + [1708683720, '6176'], + [1708683360, '6169'], + [1708683480, '6068'], + [1708683540, '6025'], + [1708683300, '6042'], + [1708683420, '6001'], + [1708683600, '5969'], + [1708683900, '5955'], + [1708683180, '4301'], + ], + queryName: 'F1', + legend: 'firstLegend', + }, + { + metric: {}, + values: [ + [1708683240, '3378'], + [1708683300, '3269'], + [1708683360, '3341'], + [1708683420, '3269'], + [1708683480, '3296'], + [1708683540, '3280'], + [1708683600, '3260'], + [1708683660, '3351'], + [1708683720, '3345'], + [1708683780, '3370'], + [1708683840, '3382'], + [1708683900, '3249'], + [1708683960, '212'], + ], + queryName: 'A', + legend: 'A-A', + }, + { + metric: {}, + values: [ + [1708683840, '2878'], + [1708683240, '2873'], + [1708683780, '2867'], + [1708683660, '2837'], + [1708683720, '2831'], + [1708683360, '2828'], + [1708683300, '2773'], + [1708683480, '2772'], + [1708683540, '2745'], + [1708683420, '2732'], + [1708683180, '2729'], + [1708683600, '2709'], + [1708683900, '2706'], + ], + queryName: 'B', + legend: 'B-B', + }, + { + metric: { + F2: 'F2', + }, + values: [ + [1708683840, '504'], + [1708683240, '505'], + [1708683780, '503'], + [1708683660, '514'], + [1708683720, '514'], + [1708683360, '513'], + [1708683480, '524'], + [1708683540, '535'], + [1708683300, '496'], + [1708683420, '537'], + [1708683600, '551'], + [1708683900, '543'], + [1708683180, '-1157'], + ], + queryName: 'F2', + legend: 'F2', + }, + ], + graphsVisibilityStates: [true, true, true, true, true], + panelType: PANEL_TYPES.TIME_SERIES, +} as GetSeriesProps; diff --git a/frontend/src/lib/uPlotLib/utils/tests/__mocks__/uplotChartOptionsData.ts b/frontend/src/lib/uPlotLib/utils/tests/__mocks__/uplotChartOptionsData.ts new file mode 100644 index 0000000000..d3682939a4 --- /dev/null +++ b/frontend/src/lib/uPlotLib/utils/tests/__mocks__/uplotChartOptionsData.ts @@ -0,0 +1,453 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; + +export const inputPropsTimeSeries = { + id: '', + dimensions: { + width: 400, + height: 288, + }, + isDarkMode: true, + apiResponse: { + data: { + result: [ + { + metric: { + A: 'A', + }, + values: [ + [1708623120, '122'], + [1708623180, '112'], + [1708623240, '106'], + [1708623300, '106'], + [1708623360, '116'], + [1708623420, '110'], + [1708623480, '110'], + [1708623540, '114'], + [1708623600, '114'], + [1708623660, '118'], + [1708623720, '110'], + [1708623780, '112'], + [1708623840, '116'], + [1708623900, '104'], + [1708623960, '106'], + [1708624020, '120'], + [1708624080, '110'], + [1708624140, '112'], + [1708624200, '110'], + [1708624260, '112'], + [1708624320, '110'], + [1708624380, '112'], + [1708624440, '108'], + [1708624500, '110'], + [1708624560, '114'], + [1708624620, '104'], + [1708624680, '122'], + [1708624740, '112'], + [1708624800, '104'], + [1708624860, '90'], + ], + queryName: 'A', + legend: 'A', + }, + ], + resultType: '', + newResult: { + status: 'success', + data: { + resultType: '', + result: [ + { + queryName: 'A', + series: [ + { + labels: { + A: 'A', + }, + values: [ + { + timestamp: 1708623120000, + value: '122', + }, + { + timestamp: 1708623180000, + value: '112', + }, + { + timestamp: 1708623240000, + value: '106', + }, + { + timestamp: 1708623300000, + value: '106', + }, + { + timestamp: 1708623360000, + value: '116', + }, + { + timestamp: 1708623420000, + value: '110', + }, + { + timestamp: 1708623480000, + value: '110', + }, + { + timestamp: 1708623540000, + value: '114', + }, + { + timestamp: 1708623600000, + value: '114', + }, + { + timestamp: 1708623660000, + value: '118', + }, + { + timestamp: 1708623720000, + value: '110', + }, + { + timestamp: 1708623780000, + value: '112', + }, + { + timestamp: 1708623840000, + value: '116', + }, + { + timestamp: 1708623900000, + value: '104', + }, + { + timestamp: 1708623960000, + value: '106', + }, + { + timestamp: 1708624020000, + value: '120', + }, + { + timestamp: 1708624080000, + value: '110', + }, + { + timestamp: 1708624140000, + value: '112', + }, + { + timestamp: 1708624200000, + value: '110', + }, + { + timestamp: 1708624260000, + value: '112', + }, + { + timestamp: 1708624320000, + value: '110', + }, + { + timestamp: 1708624380000, + value: '112', + }, + { + timestamp: 1708624440000, + value: '108', + }, + { + timestamp: 1708624500000, + value: '110', + }, + { + timestamp: 1708624560000, + value: '114', + }, + { + timestamp: 1708624620000, + value: '104', + }, + { + timestamp: 1708624680000, + value: '122', + }, + { + timestamp: 1708624740000, + value: '112', + }, + { + timestamp: 1708624800000, + value: '104', + }, + { + timestamp: 1708624860000, + value: '90', + }, + ], + }, + ], + list: null, + }, + ], + }, + }, + }, + }, + yAxisUnit: 'none', + minTimeScale: 1708623105, + maxTimeScale: 1708624905, + graphsVisibilityStates: [true, true], + thresholds: [], + softMax: null, + softMin: null, + panelType: PANEL_TYPES.TIME_SERIES, +} as GetUPlotChartOptions; + +export const inputPropsBar = { + id: '', + dimensions: { + width: 400, + height: 288, + }, + isDarkMode: true, + apiResponse: { + data: { + result: [ + { + metric: { + A: 'A', + }, + values: [ + [1708623120, '122'], + [1708623180, '112'], + [1708623240, '106'], + [1708623300, '106'], + [1708623360, '116'], + [1708623420, '110'], + [1708623480, '110'], + [1708623540, '114'], + [1708623600, '114'], + [1708623660, '118'], + [1708623720, '110'], + [1708623780, '112'], + [1708623840, '116'], + [1708623900, '104'], + [1708623960, '106'], + [1708624020, '120'], + [1708624080, '110'], + [1708624140, '112'], + [1708624200, '110'], + [1708624260, '112'], + [1708624320, '110'], + [1708624380, '112'], + [1708624440, '108'], + [1708624500, '110'], + [1708624560, '114'], + [1708624620, '104'], + [1708624680, '122'], + [1708624740, '112'], + [1708624800, '104'], + [1708624860, '90'], + ], + queryName: 'A', + legend: 'A', + }, + ], + resultType: '', + newResult: { + status: 'success', + data: { + resultType: '', + result: [ + { + queryName: 'A', + series: [ + { + labels: { + A: 'A', + }, + values: [ + { + timestamp: 1708623120000, + value: '122', + }, + { + timestamp: 1708623180000, + value: '112', + }, + { + timestamp: 1708623240000, + value: '106', + }, + { + timestamp: 1708623300000, + value: '106', + }, + { + timestamp: 1708623360000, + value: '116', + }, + { + timestamp: 1708623420000, + value: '110', + }, + { + timestamp: 1708623480000, + value: '110', + }, + { + timestamp: 1708623540000, + value: '114', + }, + { + timestamp: 1708623600000, + value: '114', + }, + { + timestamp: 1708623660000, + value: '118', + }, + { + timestamp: 1708623720000, + value: '110', + }, + { + timestamp: 1708623780000, + value: '112', + }, + { + timestamp: 1708623840000, + value: '116', + }, + { + timestamp: 1708623900000, + value: '104', + }, + { + timestamp: 1708623960000, + value: '106', + }, + { + timestamp: 1708624020000, + value: '120', + }, + { + timestamp: 1708624080000, + value: '110', + }, + { + timestamp: 1708624140000, + value: '112', + }, + { + timestamp: 1708624200000, + value: '110', + }, + { + timestamp: 1708624260000, + value: '112', + }, + { + timestamp: 1708624320000, + value: '110', + }, + { + timestamp: 1708624380000, + value: '112', + }, + { + timestamp: 1708624440000, + value: '108', + }, + { + timestamp: 1708624500000, + value: '110', + }, + { + timestamp: 1708624560000, + value: '114', + }, + { + timestamp: 1708624620000, + value: '104', + }, + { + timestamp: 1708624680000, + value: '122', + }, + { + timestamp: 1708624740000, + value: '112', + }, + { + timestamp: 1708624800000, + value: '104', + }, + { + timestamp: 1708624860000, + value: '90', + }, + ], + }, + ], + list: null, + }, + ], + }, + }, + }, + }, + yAxisUnit: 'none', + minTimeScale: 1708623105, + maxTimeScale: 1708624905, + graphsVisibilityStates: [true, true], + thresholds: [], + softMax: null, + softMin: null, + panelType: PANEL_TYPES.BAR, +} as GetUPlotChartOptions; + +export const seriesDataTimeSeries = [ + { + label: 'Timestamp', + stroke: 'purple', + }, + { + drawStyle: 'line', + lineInterpolation: 'spline', + show: true, + label: 'A', + stroke: '#6495ED', + width: 2, + spanGaps: true, + points: { + size: 5, + show: false, + stroke: '#6495ED', + }, + }, +]; + +export const seriesDataBarChart = [ + { + label: 'Timestamp', + stroke: 'purple', + }, + { + drawStyle: 'bars', + lineInterpolation: null, + show: true, + label: 'A', + fill: '#6495ED40', + stroke: '#6495ED', + width: 2, + spanGaps: true, + points: { + size: 5, + show: false, + stroke: '#6495ED', + }, + }, +]; diff --git a/frontend/src/lib/uPlotLib/utils/tests/getSeriesData.test.ts b/frontend/src/lib/uPlotLib/utils/tests/getSeriesData.test.ts new file mode 100644 index 0000000000..75ee3c7f58 --- /dev/null +++ b/frontend/src/lib/uPlotLib/utils/tests/getSeriesData.test.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import getSeries from '../getSeriesData'; +import { + seriesBarChartData, + seriesLineChartData, +} from './__mocks__/seriesData'; + +jest.mock('../getRenderer', () => jest.fn().mockImplementation(() => () => {})); + +describe('Get Series Data', () => { + test('Should return series data for uplot chart', () => { + const seriesData = getSeries(seriesBarChartData); + expect(seriesData.length).toBe(5); + expect(seriesData[1].label).toBe('firstLegend'); + expect(seriesData[1].show).toBe(true); + expect(seriesData[1].fill).toBe('#C7158540'); + expect(seriesData[1].width).toBe(2); + }); + + test('Should return series drawline bar chart for panel type barchart', () => { + const seriesData = getSeries(seriesBarChartData); + // @ts-ignore + expect(seriesData[1].drawStyle).toBe('bars'); + }); + + test('Should return seris drawline line chart for panel type time series', () => { + const seriesData = getSeries(seriesLineChartData); + // @ts-ignore + expect(seriesData[1].drawStyle).toBe('line'); + }); +}); diff --git a/frontend/src/lib/uPlotLib/utils/tests/getUplotChartOptions.test.ts b/frontend/src/lib/uPlotLib/utils/tests/getUplotChartOptions.test.ts new file mode 100644 index 0000000000..a955d787ac --- /dev/null +++ b/frontend/src/lib/uPlotLib/utils/tests/getUplotChartOptions.test.ts @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; + +import { + inputPropsTimeSeries, + seriesDataBarChart, + seriesDataTimeSeries, +} from './__mocks__/uplotChartOptionsData'; + +jest.mock('../../plugins/tooltipPlugin', () => jest.fn().mockReturnValue({})); +jest.mock('../getSeriesData', () => + jest.fn().mockImplementation((props) => { + const { panelType } = props; + + if (panelType === PANEL_TYPES.TIME_SERIES) { + return seriesDataTimeSeries; + } + return seriesDataBarChart; + }), +); + +describe('getUPlotChartOptions', () => { + test('should return uPlot options', () => { + const options = getUPlotChartOptions(inputPropsTimeSeries); + expect(options.legend?.isolate).toBe(true); + expect(options.width).toBe(inputPropsTimeSeries.dimensions.width); + expect(options.height).toBe(inputPropsTimeSeries.dimensions.height - 30); + expect(options.axes?.length).toBe(2); + expect(options.series[1].label).toBe('A'); + }); + + test('Should return line chart as drawStyle for time series', () => { + const options = getUPlotChartOptions(inputPropsTimeSeries); + // @ts-ignore + expect(options.series[1].drawStyle).toBe('line'); + // @ts-ignore + expect(options.series[1].lineInterpolation).toBe('spline'); + // @ts-ignore + expect(options.series[1].show).toBe(true); + expect(options.series[1].label).toBe('A'); + expect(options.series[1].stroke).toBe('#6495ED'); + expect(options.series[1].width).toBe(2); + expect(options.series[1].spanGaps).toBe(true); + // @ts-ignore + expect(options.series[1].points.size).toBe(5); + }); + + test('should return bar chart as drawStyle for panel type bar', () => { + const options = getUPlotChartOptions({ + ...inputPropsTimeSeries, + panelType: PANEL_TYPES.BAR, + }); + // @ts-ignore + expect(options.series[1].drawStyle).toBe('bars'); + // @ts-ignore + expect(options.series[1].lineInterpolation).toBe(null); + // @ts-ignore + expect(options.series[1].show).toBe(true); + expect(options.series[1].label).toBe('A'); + expect(options.series[1].fill).toBe('#6495ED40'); + expect(options.series[1].stroke).toBe('#6495ED'); + expect(options.series[1].width).toBe(2); + expect(options.series[1].spanGaps).toBe(true); + // @ts-ignore + expect(options.series[1].points.size).toBe(5); + }); +}); diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index e7177a0536..80bc673a83 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -100,7 +100,13 @@ export function QueryBuilderProvider({ null, ); - const [panelType, setPanelType] = useState(null); + const panelTypeQueryParams = urlQuery.get( + QueryParams.panelTypes, + ) as PANEL_TYPES | null; + + const [panelType, setPanelType] = useState( + panelTypeQueryParams, + ); const [currentQuery, setCurrentQuery] = useState(queryState); const [stagedQuery, setStagedQuery] = useState(null); diff --git a/frontend/src/utils/getGraphType.ts b/frontend/src/utils/getGraphType.ts new file mode 100644 index 0000000000..fa37bf8f4f --- /dev/null +++ b/frontend/src/utils/getGraphType.ts @@ -0,0 +1,9 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; + +export const getGraphType = (panelType: PANEL_TYPES): PANEL_TYPES => { + // backend don't support graphType as bar, as we consume time series data, sending graphType as time_series whenever we use bar as panel_type + if (panelType === PANEL_TYPES.BAR) { + return PANEL_TYPES.TIME_SERIES; + } + return panelType; +}; diff --git a/frontend/src/utils/getSortedSeriesData.ts b/frontend/src/utils/getSortedSeriesData.ts new file mode 100644 index 0000000000..98336e8b1f --- /dev/null +++ b/frontend/src/utils/getSortedSeriesData.ts @@ -0,0 +1,20 @@ +import { QueryData } from 'types/api/widgets/getQuery'; + +// Sorting the series data in desending matter for plotting cummulative bar chart. +export const getSortedSeriesData = ( + result: QueryData[] | undefined, +): QueryData[] => { + const seriesList = result || []; + + return seriesList.sort((a, b) => { + if (a.values.length === 0) return 1; + if (b.values.length === 0) return -1; + const avgA = + a.values.reduce((acc, curr) => acc + parseFloat(curr[1]), 0) / + a.values.length; + const avgB = + b.values.reduce((acc, curr) => acc + parseFloat(curr[1]), 0) / + b.values.length; + return avgB - avgA; + }); +};