This is an automated email from the ASF dual-hosted git repository.

jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new b837df10291 [timeseries][ui] Add configurable max series limit for 
timeseries chart visualization (#16580)
b837df10291 is described below

commit b837df10291f99ba3ddf7c09a490ab83400e93d9
Author: Shaurya Chaturvedi <[email protected]>
AuthorDate: Tue Aug 12 22:44:23 2025 -0700

    [timeseries][ui] Add configurable max series limit for timeseries chart 
visualization (#16580)
---
 .../app/components/Query/TimeseriesChart.tsx       | 18 ++----
 .../app/components/Query/TimeseriesQueryPage.tsx   | 71 ++++++++++++++++------
 .../src/main/resources/app/utils/ChartConstants.ts |  4 +-
 3 files changed, 58 insertions(+), 35 deletions(-)

diff --git 
a/pinot-controller/src/main/resources/app/components/Query/TimeseriesChart.tsx 
b/pinot-controller/src/main/resources/app/components/Query/TimeseriesChart.tsx
index 908f56c3e66..64361d77d01 100644
--- 
a/pinot-controller/src/main/resources/app/components/Query/TimeseriesChart.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Query/TimeseriesChart.tsx
@@ -22,7 +22,7 @@ import ReactECharts from 'echarts-for-react';
 import { makeStyles } from '@material-ui/core/styles';
 import { Typography, Paper } from '@material-ui/core';
 import { ChartSeries } from 'Models';
-import { getSeriesColor, MAX_SERIES_LIMIT, CHART_PADDING_PERCENTAGE } from 
'../../utils/ChartConstants';
+import { getSeriesColor, CHART_PADDING_PERCENTAGE } from 
'../../utils/ChartConstants';
 
 // Define proper types for ECharts parameters
 interface EChartsTooltipParams {
@@ -33,9 +33,7 @@ interface EChartsTooltipParams {
 
 // Extract chart configuration functions
 const createChartSeries = (series: ChartSeries[], selectedMetric?: string) => {
-  const limitedSeries = series.slice(0, MAX_SERIES_LIMIT);
-
-  return limitedSeries.map((s, index) => {
+  return series.map((s, index) => {
     const isSelected = selectedMetric ? s.name === selectedMetric : true;
 
     return {
@@ -85,8 +83,7 @@ const createTooltipFormatter = (selectedMetric?: string) => {
 };
 
 const getTimeRange = (series: ChartSeries[]) => {
-  const limitedSeries = series.slice(0, MAX_SERIES_LIMIT);
-  const allTimestamps = limitedSeries.flatMap(s => s.data.map(dp => 
dp.timestamp));
+  const allTimestamps = series.flatMap(s => s.data.map(dp => dp.timestamp));
   const minTime = Math.min(...allTimestamps);
   const maxTime = Math.max(...allTimestamps);
   return { minTime, maxTime };
@@ -234,18 +231,11 @@ const TimeseriesChart: React.FC<TimeseriesChartProps> = ({
     );
   }
 
-  const hasMoreSeries = series.length > MAX_SERIES_LIMIT;
-
   return (
     <Paper className={classes.chartContainer} style={{ height: `${height}px` 
}}>
-      {hasMoreSeries && (
-        <Typography variant="body2" color="textSecondary" style={{ padding: 
'8px 16px', backgroundColor: '#fff3cd', borderBottom: '1px solid #ffeaa7' }}>
-          Showing first {MAX_SERIES_LIMIT} of {series.length} series. Consider 
filtering your query to improve performance.
-        </Typography>
-      )}
       <ReactECharts
         option={chartOption}
-        style={{ height: hasMoreSeries ? 'calc(100% - 40px)' : '100%', width: 
'100%' }}
+        style={{ height: '100%', width: '100%' }}
         opts={{ renderer: 'canvas' }}
       />
     </Paper>
diff --git 
a/pinot-controller/src/main/resources/app/components/Query/TimeseriesQueryPage.tsx
 
b/pinot-controller/src/main/resources/app/components/Query/TimeseriesQueryPage.tsx
index 7e63d556883..720a97268ea 100644
--- 
a/pinot-controller/src/main/resources/app/components/Query/TimeseriesQueryPage.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Query/TimeseriesQueryPage.tsx
@@ -47,7 +47,7 @@ import TimeseriesChart from './TimeseriesChart';
 import MetricStatsTable from './MetricStatsTable';
 import { parseTimeseriesResponse, isPrometheusFormat } from 
'../../utils/TimeseriesUtils';
 import { ChartSeries } from 'Models';
-import { MAX_SERIES_LIMIT } from '../../utils/ChartConstants';
+import { DEFAULT_SERIES_LIMIT } from '../../utils/ChartConstants';
 
 // Define proper types
 interface TimeseriesQueryResponse {
@@ -144,6 +144,20 @@ const useStyles = makeStyles((theme) => ({
     padding: theme.spacing(1),
     minWidth: 0,
   },
+  seriesLimitContainer: {
+    display: 'flex',
+    justifyContent: 'flex-end',
+    alignItems: 'center',
+    padding: theme.spacing(1),
+    borderTop: `1px solid ${theme.palette.divider}`,
+    backgroundColor: theme.palette.grey[50],
+  },
+  seriesLimitInput: {
+    width: 50,
+    marginLeft: theme.spacing(2),
+    '& .MuiInputBase-input': { padding: '8px 12px', fontSize: '1rem' },
+  },
+  timeseriesAccordionDetails: { overflow: 'hidden !important' },
 
 }));
 
@@ -157,8 +171,8 @@ const TruncationWarning: React.FC<{ totalSeries: number; 
truncatedSeries: number
   return (
     <Alert severity="warning" style={{ marginBottom: '16px' }}>
       <Typography variant="body2">
-        Large dataset detected: Showing first {truncatedSeries} of 
{totalSeries} series for visualization.
-        Switch to JSON view to see the complete dataset.
+        Truncation warning: Showing first {truncatedSeries} of {totalSeries} 
series for visualization.
+        Switch to JSON view to see the complete dataset or increase the max 
series limit.
       </Typography>
     </Alert>
   );
@@ -252,15 +266,15 @@ const TimeseriesQueryPage = () => {
   const [languagesLoading, setLanguagesLoading] = useState(true);
 
   const [rawOutput, setRawOutput] = useState<string>('');
-  const [rawData, setRawData] = useState<TimeseriesQueryResponse | null>(null);
   const [chartSeries, setChartSeries] = useState<ChartSeries[]>([]);
-  const [truncatedChartSeries, setTruncatedChartSeries] = 
useState<ChartSeries[]>([]);
+  const [totalSeriesCount, setTotalSeriesCount] = useState<number>(0);
   const [isLoading, setIsLoading] = useState<boolean>(false);
   const [error, setError] = useState<string>('');
   const [shouldAutoExecute, setShouldAutoExecute] = useState<boolean>(false);
   const [copyMsg, showCopyMsg] = React.useState(false);
   const [viewType, setViewType] = useState<'json' | 'chart'>('chart');
   const [selectedMetric, setSelectedMetric] = useState<string | null>(null);
+  const [seriesLimitInput, setSeriesLimitInput] = 
useState<string>(DEFAULT_SERIES_LIMIT.toString());
 
 
   // Fetch supported languages from controller configuration
@@ -351,30 +365,32 @@ const TimeseriesQueryPage = () => {
 
   // Extract data processing logic
   const processQueryResponse = useCallback((parsedData: 
TimeseriesQueryResponse) => {
-    setRawData(parsedData);
     setRawOutput(JSON.stringify(parsedData, null, 2));
 
     // Check if this is an error response
     if (parsedData.error != null && parsedData.error !== '') {
       setError(parsedData.error);
       setChartSeries([]);
-      setTruncatedChartSeries([]);
+      setTotalSeriesCount(0);
       return;
     }
 
     // Parse timeseries data for chart and stats
     if (isPrometheusFormat(parsedData)) {
       const series = parseTimeseriesResponse(parsedData);
-      setChartSeries(series);
+      setTotalSeriesCount(series.length);
 
-      // Create truncated series for visualization (limit to MAX_SERIES_LIMIT)
-      const truncatedSeries = series.slice(0, MAX_SERIES_LIMIT);
-      setTruncatedChartSeries(truncatedSeries);
+      // Create truncated series for visualization (limit to seriesLimitInput 
or default to DEFAULT_SERIES_LIMIT)
+      const limit = parseInt(seriesLimitInput, 10);
+      const effectiveLimit = !isNaN(limit) && limit > 0 ? limit : 
DEFAULT_SERIES_LIMIT;
+
+      const truncatedSeries = series.slice(0, effectiveLimit);
+      setChartSeries(truncatedSeries);
     } else {
       setChartSeries([]);
-      setTruncatedChartSeries([]);
+      setTotalSeriesCount(0);
     }
-  }, []);
+  }, [seriesLimitInput]);
 
   const handleExecuteQuery = useCallback(async () => {
     if (!config.query.trim()) {
@@ -386,6 +402,9 @@ const TimeseriesQueryPage = () => {
     setIsLoading(true);
     setError('');
     setRawOutput('');
+    setSelectedMetric(null);
+    setChartSeries([]);
+    setTotalSeriesCount(0);
 
     try {
       const requestPayload = {
@@ -544,7 +563,7 @@ const TimeseriesQueryPage = () => {
                          <ViewToggle
                viewType={viewType}
                onViewChange={setViewType}
-               isChartDisabled={truncatedChartSeries.length === 0}
+               isChartDisabled={chartSeries.length === 0}
                onCopy={copyToClipboard}
                copyMsg={copyMsg}
                classes={classes}
@@ -560,23 +579,37 @@ const TimeseriesQueryPage = () => {
               <SimpleAccordion
                 headerTitle="Timeseries Chart & Statistics"
                 showSearchBox={false}
+                detailsContainerClass={classes.timeseriesAccordionDetails}
               >
-                {truncatedChartSeries.length > 0 ? (
+                {chartSeries.length > 0 ? (
                   <>
                     <TruncationWarning
-                      totalSeries={chartSeries.length}
-                      truncatedSeries={truncatedChartSeries.length}
+                      totalSeries={totalSeriesCount}
+                      truncatedSeries={chartSeries.length}
                     />
                     <TimeseriesChart
-                      series={truncatedChartSeries}
+                      series={chartSeries}
                       height={500}
                       selectedMetric={selectedMetric}
                     />
                     <MetricStatsTable
-                      series={truncatedChartSeries}
+                      series={chartSeries}
                       selectedMetric={selectedMetric}
                       onMetricSelect={setSelectedMetric}
                     />
+                    <div className={classes.seriesLimitContainer}>
+                      <Typography variant="body2" color="textSecondary">
+                        Max Series Render Limit:
+                      </Typography>
+                      <FormControl className={classes.seriesLimitInput}>
+                        <Input
+                          value={seriesLimitInput}
+                          onChange={(e) => setSeriesLimitInput(e.target.value)}
+                          inputProps={{ min: 1, max: 1000 }}
+                          placeholder={DEFAULT_SERIES_LIMIT.toString()}
+                        />
+                      </FormControl>
+                    </div>
                   </>
                 ) : (
                   <Box p={3} textAlign="center">
diff --git a/pinot-controller/src/main/resources/app/utils/ChartConstants.ts 
b/pinot-controller/src/main/resources/app/utils/ChartConstants.ts
index 5e1f823c851..c5277087122 100644
--- a/pinot-controller/src/main/resources/app/utils/ChartConstants.ts
+++ b/pinot-controller/src/main/resources/app/utils/ChartConstants.ts
@@ -26,9 +26,9 @@ export const CHART_COLORS = [
 ];
 
 /**
- * Maximum number of series that can be rendered in the chart
+ * Default number of series that can be rendered in the chart
  */
-export const MAX_SERIES_LIMIT = 20;
+export const DEFAULT_SERIES_LIMIT = 40;
 
 /**
  * Chart padding percentage for time axis and series


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to