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

ankitsultana 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 45389974f6f [timeseries] Adding timeseries language endpoint and UI 
integration (#16424)
45389974f6f is described below

commit 45389974f6fad5a5f35b074d06063673112e8dc2
Author: Shaurya Chaturvedi <[email protected]>
AuthorDate: Tue Jul 29 12:09:06 2025 -0700

    [timeseries] Adding timeseries language endpoint and UI integration (#16424)
    
    Co-authored-by: Shaurya Chaturvedi <[email protected]>
---
 .../apache/pinot/controller/ControllerConf.java    | 18 +++++++
 .../PinotControllerTimeseriesResource.java         | 58 ++++++++++++++++++++++
 .../app/components/Query/TimeseriesQueryPage.tsx   | 50 +++++++++++++++----
 .../src/main/resources/app/requests/index.ts       |  3 ++
 4 files changed, 120 insertions(+), 9 deletions(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java
index e8ffa8ebc8c..67a48e679f0 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/ControllerConf.java
@@ -19,6 +19,7 @@
 package org.apache.pinot.controller;
 
 import com.google.common.base.Preconditions;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -27,6 +28,7 @@ import java.util.Optional;
 import java.util.Random;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.helix.controller.rebalancer.strategy.AutoRebalanceStrategy;
@@ -37,6 +39,7 @@ import org.apache.pinot.spi.filesystem.LocalPinotFS;
 import org.apache.pinot.spi.utils.CommonConstants;
 import org.apache.pinot.spi.utils.Enablement;
 import org.apache.pinot.spi.utils.TimeUtils;
+import org.apache.pinot.tsdb.spi.PinotTimeSeriesConfiguration;
 
 import static 
org.apache.pinot.spi.utils.CommonConstants.Controller.CONFIG_OF_CONTROLLER_METRICS_PREFIX;
 import static 
org.apache.pinot.spi.utils.CommonConstants.Controller.CONFIG_OF_INSTANCE_ID;
@@ -1333,4 +1336,19 @@ public class ControllerConf extends PinotConfiguration {
   public int getMaxForceCommitZkJobs() {
     return getProperty(CONFIG_OF_MAX_FORCE_COMMIT_JOBS_IN_ZK, 
ControllerJob.DEFAULT_MAXIMUM_CONTROLLER_JOBS_IN_ZK);
   }
+
+  /**
+   * Get the configured timeseries languages from controller configuration.
+   * @return List of enabled timeseries languages
+   */
+  public List<String> getTimeseriesLanguages() {
+    String languagesConfig = 
getProperty(PinotTimeSeriesConfiguration.getEnabledLanguagesConfigKey());
+    if (languagesConfig == null) {
+      return new ArrayList<>();
+    }
+    return Arrays.stream(languagesConfig.split(","))
+        .map(String::trim)
+        .filter(lang -> !lang.isEmpty())
+        .collect(Collectors.toList());
+  }
 }
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerTimeseriesResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerTimeseriesResource.java
new file mode 100644
index 00000000000..74c159c1733
--- /dev/null
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotControllerTimeseriesResource.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.controller.api.resources;
+
+import io.swagger.annotations.ApiOperation;
+import java.util.List;
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.pinot.controller.ControllerConf;
+import 
org.apache.pinot.controller.api.exception.ControllerApplicationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("/timeseries")
+public class PinotControllerTimeseriesResource {
+  public static final Logger LOGGER = 
LoggerFactory.getLogger(PinotControllerTimeseriesResource.class);
+
+  @Inject
+  ControllerConf _controllerConf;
+
+  @GET
+  @Produces(MediaType.APPLICATION_JSON)
+  @Path("languages")
+  @ApiOperation(value = "Get timeseries languages from controller 
configuration",
+      notes = "Get timeseries languages from controller configuration")
+  public List<String> getBrokerTimeSeriesLanguages(@Context HttpHeaders 
headers) {
+    try {
+      return _controllerConf.getTimeseriesLanguages();
+    } catch (Exception e) {
+      LOGGER.error("Error fetching timeseries languages from controller 
configuration", e);
+      throw new ControllerApplicationException(LOGGER,
+          "Error fetching timeseries languages from controller configuration: 
" + e.getMessage(),
+          Response.Status.INTERNAL_SERVER_ERROR);
+    }
+  }
+}
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 964945813be..7e63d556883 100644
--- 
a/pinot-controller/src/main/resources/app/components/Query/TimeseriesQueryPage.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Query/TimeseriesQueryPage.tsx
@@ -38,7 +38,7 @@ import { UnControlled as CodeMirror } from 
'react-codemirror2';
 import 'codemirror/lib/codemirror.css';
 import 'codemirror/theme/material.css';
 import 'codemirror/mode/javascript/javascript';
-import { getTimeSeriesQueryResult } from '../../requests';
+import { getTimeSeriesQueryResult, getTimeSeriesLanguages } from 
'../../requests';
 import { useHistory, useLocation } from 'react-router';
 import TableToolbar from '../TableToolbar';
 import { Resizable } from 're-resizable';
@@ -224,10 +224,6 @@ const jsonoptions = {
   wordWrap: 'break-word',
 };
 
-const SUPPORTED_QUERY_LANGUAGES = [
-  { value: 'm3ql', label: 'M3QL' },
-];
-
 interface TimeseriesQueryConfig {
   queryLanguage: string;
   query: string;
@@ -252,6 +248,9 @@ const TimeseriesQueryPage = () => {
     timeout: 60000,
   });
 
+  const [supportedLanguages, setSupportedLanguages] = 
useState<Array<string>>([]);
+  const [languagesLoading, setLanguagesLoading] = useState(true);
+
   const [rawOutput, setRawOutput] = useState<string>('');
   const [rawData, setRawData] = useState<TimeseriesQueryResponse | null>(null);
   const [chartSeries, setChartSeries] = useState<ChartSeries[]>([]);
@@ -264,6 +263,25 @@ const TimeseriesQueryPage = () => {
   const [selectedMetric, setSelectedMetric] = useState<string | null>(null);
 
 
+  // Fetch supported languages from controller configuration
+  useEffect(() => {
+    const fetchLanguages = async () => {
+      try {
+        setLanguagesLoading(true);
+        const response = await getTimeSeriesLanguages();
+        const languages = response.data || [];
+
+        setSupportedLanguages(languages);
+      } catch (error) {
+        console.error('Error fetching timeseries languages:', error);
+        setSupportedLanguages([]);
+      } finally {
+        setLanguagesLoading(false);
+      }
+    };
+    fetchLanguages();
+  }, []);
+
   // Update config when URL parameters change
   useEffect(() => {
     const urlParams = new URLSearchParams(location.search);
@@ -410,6 +428,15 @@ const TimeseriesQueryPage = () => {
 
   return (
     <Grid container>
+      {/* Banner for no enabled languages */}
+      {!languagesLoading && supportedLanguages.length === 0 && (
+        <Grid item xs={12}>
+          <Alert severity="warning" style={{ marginBottom: '16px' }}>
+            <strong>No timeseries languages enabled.</strong> Please configure 
timeseries languages in your controller, broker and server configurations using 
the <code>pinot.timeseries.languages</code> property.
+          </Alert>
+        </Grid>
+      )}
+
       <Grid item xs={12} className={classes.rightPanel}>
         <Resizable
           defaultSize={{ width: '100%', height: 148 }}
@@ -434,6 +461,7 @@ const TimeseriesQueryPage = () => {
                 lineWrapping: true,
                 indentWithTabs: true,
                 smartIndent: true,
+                readOnly: supportedLanguages.length === 0,
               }}
               className={classes.codeMirror}
               autoCursor={false}
@@ -449,10 +477,11 @@ const TimeseriesQueryPage = () => {
               <Select
                 value={config.queryLanguage}
                 onChange={(e) => handleConfigChange('queryLanguage', 
e.target.value as string)}
+                disabled={languagesLoading || supportedLanguages.length === 0}
               >
-                {SUPPORTED_QUERY_LANGUAGES.map((lang) => (
-                  <MenuItem key={lang.value} value={lang.value}>
-                    {lang.label}
+                {supportedLanguages.map((lang) => (
+                  <MenuItem key={lang} value={lang}>
+                    {lang}
                   </MenuItem>
                 ))}
               </Select>
@@ -467,6 +496,7 @@ const TimeseriesQueryPage = () => {
                 value={config.startTime}
                 onChange={(e) => handleConfigChange('startTime', 
e.target.value as string)}
                 placeholder={getOneMinuteAgoTimestamp()}
+                disabled={supportedLanguages.length === 0}
               />
             </FormControl>
           </Grid>
@@ -479,6 +509,7 @@ const TimeseriesQueryPage = () => {
                 value={config.endTime}
                 onChange={(e) => handleConfigChange('endTime', e.target.value 
as string)}
                 placeholder={getCurrentTimestamp()}
+                disabled={supportedLanguages.length === 0}
               />
             </FormControl>
           </Grid>
@@ -490,6 +521,7 @@ const TimeseriesQueryPage = () => {
                 type="text"
                 value={config.timeout}
                 onChange={(e) => handleConfigChange('timeout', 
parseInt(e.target.value as string) || 60000)}
+                disabled={supportedLanguages.length === 0}
               />
             </FormControl>
           </Grid>
@@ -499,7 +531,7 @@ const TimeseriesQueryPage = () => {
               variant="contained"
               color="primary"
               onClick={handleExecuteQuery}
-              disabled={isLoading || !config.query.trim()}
+              disabled={isLoading || !config.query.trim() || 
supportedLanguages.length === 0}
               endIcon={<span style={{fontSize: '0.8em', lineHeight: 
1}}>{navigator.platform.includes('Mac') ? '⌘↵' : 'Ctrl+↵'}</span>}
             >
               {isLoading ? 'Running Query...' : 'Run Query'}
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts 
b/pinot-controller/src/main/resources/app/requests/index.ts
index a069c693dbe..4bef8fdff29 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -238,6 +238,9 @@ export const getQueryResult = (params: Object): 
Promise<AxiosResponse<SQLResult>
 export const getTimeSeriesQueryResult = (params: Object): 
Promise<AxiosResponse<any>> =>
   transformApi.get(`/timeseries/api/v1/query_range`, { params });
 
+export const getTimeSeriesLanguages = (): Promise<AxiosResponse<string[]>> =>
+  baseApi.get('/timeseries/languages');
+
 export const getClusterInfo = (): Promise<AxiosResponse<ClusterName>> =>
   baseApi.get('/cluster/info');
 


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

Reply via email to