You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ki...@apache.org on 2020/08/30 08:02:16 UTC

[incubator-pinot] branch zookeeper-put-api created (now 418b6ad)

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

kishoreg pushed a change to branch zookeeper-put-api
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git.


      at 418b6ad  Adding api to edit ZK path

This branch includes the following new commits:

     new d6b9bef  adding autocomplete in sql editor
     new 418b6ad  Adding api to edit ZK path

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org


[incubator-pinot] 01/02: adding autocomplete in sql editor

Posted by ki...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kishoreg pushed a commit to branch zookeeper-put-api
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git

commit d6b9bef3a1ae10c0c7ec3707652e950c6cdca147
Author: Sanket Shah <er...@gmail.com>
AuthorDate: Wed Aug 5 19:54:37 2020 +0530

    adding autocomplete in sql editor
---
 .../src/main/resources/app/pages/Query.tsx         | 161 +++++++++++++-------
 .../src/main/resources/app/styles/styles.css       |  61 ++++++++
 .../main/resources/app/utils/PinotMethodUtils.ts   |  11 +-
 .../src/main/resources/app/utils/Utils.tsx         | 163 ++++++++++++++++++++-
 pinot-controller/src/main/resources/package.json   |   1 +
 5 files changed, 342 insertions(+), 55 deletions(-)

diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx b/pinot-controller/src/main/resources/app/pages/Query.tsx
index fdb9b88..e3b67f5 100644
--- a/pinot-controller/src/main/resources/app/pages/Query.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Query.tsx
@@ -29,6 +29,10 @@ import 'codemirror/lib/codemirror.css';
 import 'codemirror/theme/material.css';
 import 'codemirror/mode/javascript/javascript';
 import 'codemirror/mode/sql/sql';
+import 'codemirror/addon/hint/show-hint';
+import 'codemirror/addon/hint/sql-hint';
+import 'codemirror/addon/hint/show-hint.css';
+import NativeCodeMirror from 'codemirror';
 import _ from 'lodash';
 import FormControlLabel from '@material-ui/core/FormControlLabel';
 import Switch from '@material-ui/core/Switch';
@@ -40,6 +44,7 @@ import QuerySideBar from '../components/Query/QuerySideBar';
 import TableToolbar from '../components/TableToolbar';
 import SimpleAccordion from '../components/SimpleAccordion';
 import PinotMethodUtils from '../utils/PinotMethodUtils';
+import '../styles/styles.css';
 
 const useStyles = makeStyles((theme) => ({
   title: {
@@ -48,7 +53,11 @@ const useStyles = makeStyles((theme) => ({
   },
   rightPanel: {},
   codeMirror: {
-    '& .CodeMirror': { height: 100, border: '1px solid #BDCCD9', fontSize: '13px' },
+    '& .CodeMirror': {
+      height: 100,
+      border: '1px solid #BDCCD9',
+      fontSize: '13px',
+    },
   },
   queryOutput: {
     '& .CodeMirror': { height: 430, border: '1px solid #BDCCD9' },
@@ -74,8 +83,8 @@ const useStyles = makeStyles((theme) => ({
     marginBottom: '20px',
   },
   sqlError: {
-    whiteSpace: 'pre-wrap'
-  }
+    whiteSpace: 'pre-wrap',
+  },
 }));
 
 const jsonoptions = {
@@ -85,16 +94,19 @@ const jsonoptions = {
   gutters: ['CodeMirror-lint-markers'],
   lint: true,
   theme: 'default',
-  readOnly: true
+  readOnly: true,
 };
 
 const sqloptions = {
   lineNumbers: true,
-  mode: 'sql',
+  mode: 'text/x-sql',
   styleActiveLine: true,
-  gutters: ['CodeMirror-lint-markers'],
   lint: true,
-  theme: 'default'
+  theme: 'default',
+  indentWithTabs: true,
+  smartIndent: true,
+  lineWrapping: true,
+  extraKeys: { "'@'": 'autocomplete' },
 };
 
 const QueryPage = () => {
@@ -125,13 +137,13 @@ const QueryPage = () => {
 
   const [queryStats, setQueryStats] = useState<TableData>({
     columns: [],
-    records: []
+    records: [],
   });
 
   const [checked, setChecked] = React.useState({
     tracing: false,
     querySyntaxPQL: false,
-    showResultJSON: false
+    showResultJSON: false,
   });
 
   const [copyMsg, showCopyMsg] = React.useState(false);
@@ -162,10 +174,14 @@ const QueryPage = () => {
       });
     }
 
-    const results = await PinotMethodUtils.getQueryResults(params, url, checked);
+    const results = await PinotMethodUtils.getQueryResults(
+      params,
+      url,
+      checked
+    );
     setResultError(results.error || '');
-    setResultData(results.result || {columns: [], records: []});
-    setQueryStats(results.queryStats || {columns: [], records: []});
+    setResultData(results.result || { columns: [], records: [] });
+    setQueryStats(results.queryStats || { columns: [], records: [] });
     setOutputResult(JSON.stringify(results.data, null, 2) || '');
     setQueryLoader(false);
   };
@@ -223,6 +239,40 @@ const QueryPage = () => {
     fetchData();
   }, []);
 
+  const handleSqlHints = (cm: NativeCodeMirror.Editor) => {
+    const tableNames = [];
+    tableList.records.forEach((obj, i) => {
+      tableNames.push(obj[i]);
+    });
+    const columnNames = tableSchema.records.map((obj) => {
+      return obj[0];
+    });
+    const hintOptions = [];
+    const defaultHint = (NativeCodeMirror as any).hint.sql(cm);
+
+    Array.prototype.push.apply(hintOptions, Utils.generateCodeMirrorOptions(tableNames, 'TABLE'));
+    Array.prototype.push.apply(hintOptions, Utils.generateCodeMirrorOptions(columnNames, 'COLUMNS'));
+
+    const cur = cm.getCursor();
+    const curLine = cm.getLine(cur.line);
+    let start = cur.ch;
+    let end = start;
+    // eslint-disable-next-line no-plusplus
+    while (end < curLine.length && /[\w$]/.test(curLine.charAt(end))) ++end;
+    // eslint-disable-next-line no-plusplus
+    while (start && /[\w$]/.test(curLine.charAt(start - 1))) --start;
+    const curWord = start !== end && curLine.slice(start, end);
+    const regex = new RegExp(`^${  curWord}`, 'i');
+
+    const finalList =  (!curWord ? hintOptions : hintOptions.filter(function (item) {
+      return item.displayText.match(regex);
+    })).sort();
+
+    Array.prototype.push.apply(defaultHint.list, finalList);
+
+    return defaultHint;
+  };
+
   return fetching ? (
     <AppLoader />
   ) : (
@@ -235,13 +285,27 @@ const QueryPage = () => {
           selectedTable={selectedTable}
         />
       </Grid>
-      <Grid item xs style={{ padding: 20, backgroundColor: 'white', maxHeight: 'calc(100vh - 70px)', overflowY: 'auto' }}>
+      <Grid
+        item
+        xs
+        style={{
+          padding: 20,
+          backgroundColor: 'white',
+          maxHeight: 'calc(100vh - 70px)',
+          overflowY: 'auto',
+        }}
+      >
         <Grid container>
           <Grid item xs={12} className={classes.rightPanel}>
             <div className={classes.sqlDiv}>
               <TableToolbar name="SQL Editor" showSearchBox={false} />
               <CodeMirror
-                options={sqloptions}
+                options={{
+                  ...sqloptions,
+                  hintOptions: {
+                    hint: handleSqlHints,
+                  },
+                }}
                 value={inputQuery}
                 onChange={handleOutputDataChange}
                 className={classes.codeMirror}
@@ -281,16 +345,17 @@ const QueryPage = () => {
               </Grid>
             </Grid>
 
-            {queryLoader ?
+            {queryLoader ? (
               <AppLoader />
-            :
+            ) : (
               <>
-                {
-                  resultError ?
-                  <Alert severity="error" className={classes.sqlError}>{resultError}</Alert>
-                :
+                {resultError ? (
+                  <Alert severity="error" className={classes.sqlError}>
+                    {resultError}
+                  </Alert>
+                ) : (
                   <>
-                    {queryStats.records.length ?
+                    {queryStats.records.length ? (
                       <Grid item xs style={{ backgroundColor: 'white' }}>
                         <CustomizedTables
                           title="Query Response Stats"
@@ -299,8 +364,7 @@ const QueryPage = () => {
                           inAccordionFormat={true}
                         />
                       </Grid>
-                      : null 
-                    }
+                    ) : null}
 
                     <Grid item xs style={{ backgroundColor: 'white' }}>
                       {resultData.records.length ? (
@@ -338,7 +402,8 @@ const QueryPage = () => {
                                 icon={<FileCopyIcon fontSize="inherit" />}
                                 severity="info"
                               >
-                                Copied {resultData.records.length} rows to Clipboard
+                                Copied {resultData.records.length} rows to
+                                Clipboard
                               </Alert>
                             ) : null}
 
@@ -355,37 +420,35 @@ const QueryPage = () => {
                               className={classes.runNowBtn}
                             />
                           </Grid>
-                          {!checked.showResultJSON
-                            ?
-                              <CustomizedTables
-                                title="Query Result"
-                                data={resultData}
-                                isPagination
-                                isSticky={true}
-                                showSearchBox={true}
-                                inAccordionFormat={true}
+                          {!checked.showResultJSON ? (
+                            <CustomizedTables
+                              title="Query Result"
+                              data={resultData}
+                              isPagination
+                              isSticky={true}
+                              showSearchBox={true}
+                              inAccordionFormat={true}
+                            />
+                          ) : resultData.records.length ? (
+                            <SimpleAccordion
+                              headerTitle="Query Result (JSON Format)"
+                              showSearchBox={false}
+                            >
+                              <CodeMirror
+                                options={jsonoptions}
+                                value={outputResult}
+                                className={classes.queryOutput}
+                                autoCursor={false}
                               />
-                            :
-                            resultData.records.length ? (
-                              <SimpleAccordion
-                                headerTitle="Query Result (JSON Format)"
-                                showSearchBox={false}
-                              >
-                                <CodeMirror
-                                  options={jsonoptions}
-                                  value={outputResult}
-                                  className={classes.queryOutput}
-                                  autoCursor={false}
-                                />
-                              </SimpleAccordion>
-                            ) : null}
+                            </SimpleAccordion>
+                          ) : null}
                         </>
                       ) : null}
                     </Grid>
                   </>
-                }
+                )}
               </>
-            }
+            )}
           </Grid>
         </Grid>
       </Grid>
diff --git a/pinot-controller/src/main/resources/app/styles/styles.css b/pinot-controller/src/main/resources/app/styles/styles.css
index 8e764f2..d3c2d97 100644
--- a/pinot-controller/src/main/resources/app/styles/styles.css
+++ b/pinot-controller/src/main/resources/app/styles/styles.css
@@ -19,4 +19,65 @@
 
 body {
     font-family: 'Source Sans Pro', sans-serif;
+}
+
+li.CodeMirror-hint {
+  margin: 0;
+  padding: 4px 0 4px 25px;
+  border-radius: 2px;
+  white-space: pre;
+  color: black;
+  cursor: pointer;
+  position: relative;
+  line-height: 1.5;
+}
+
+li.CodeMirror-hint:before {
+  position: absolute;
+  left: 5px;
+  top: 5px;
+  border-radius: 50%;
+  font-size: 12px;
+  height: 15px;
+  width: 15px;
+  line-height: 15px;
+  text-align: center;
+  color: white;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+li.codemirror-table.CodeMirror-hint,
+li.codemirror-column.CodeMirror-hint{
+  color: black;
+}
+
+li.codemirror-table.CodeMirror-hint > div >  span.funcText > span,
+li.codemirror-column.CodeMirror-hint > div >  span.funcText > span{
+  font-weight: bold;
+  font-size: 10px;
+}
+
+li.codemirror-table.CodeMirror-hint::before {
+  content: "T";
+  background: #2F7E89;
+}
+
+li.codemirror-column.CodeMirror-hint::before {
+  content: "C";
+  background: #05a;
+}
+
+.CodeMirror-hints {
+  padding: 4px;
+  box-shadow: 0px 0px 5px rgba(0,0,0,.2);
+  border-radius: 0px;
+  font-family: 'Source Sans Pro', sans-serif;
+  font-size: 12px;
+  border: 1px solid #eaeaea;
+  min-width: 140px;
+}
+
+li.CodeMirror-hint-active {
+  background-color: #dae2f2;
 }
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
index ff80e01..30947cf 100644
--- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
+++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
@@ -92,7 +92,7 @@ const getInstanceData = (instances, liveInstanceArr) => {
 
   return Promise.all(promiseArr).then((result) => {
     return {
-      columns: ['Insance Name', 'Enabled', 'Hostname', 'Port', 'Status'],
+      columns: ['Instance Name', 'Enabled', 'Hostname', 'Port', 'Status'],
       records: [
         ...result.map(({ data }) => [
           data.instanceName,
@@ -533,6 +533,15 @@ const getNodeData = (path) => {
     const currentNodeData = results[0].data || {};
     const currentNodeListStat = results[1].data;
     const currentNodeMetadata = results[2].data;
+
+    if(currentNodeMetadata['ctime'] || currentNodeMetadata['mtime']){
+      currentNodeMetadata['ctime'] = moment(+currentNodeMetadata['ctime']).format(
+        'MMMM Do YYYY, h:mm:ss'
+      );
+      currentNodeMetadata['mtime'] = moment(+currentNodeMetadata['mtime']).format(
+        'MMMM Do YYYY, h:mm:ss'
+      );
+    }
     return { currentNodeData, currentNodeMetadata, currentNodeListStat };
   });
 };
diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx b/pinot-controller/src/main/resources/app/utils/Utils.tsx
index 8fa36d8..7f80dfb 100644
--- a/pinot-controller/src/main/resources/app/utils/Utils.tsx
+++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-nested-ternary */
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -49,7 +50,7 @@ const tableFormat = (data) => {
   const results = [];
   rows.forEach((singleRow) => {
     const obj = {};
-    singleRow.forEach((val: any, index: number)=>{
+    singleRow.forEach((val: any, index: number) => {
       obj[header[index]] = val;
     });
     results.push(obj);
@@ -64,14 +65,14 @@ const getSegmentStatus = (idealStateObj, externalViewObj) => {
   const externalSegmentKeys = Object.keys(externalViewObj);
   const externalSegmentCount = externalSegmentKeys.length;
 
-  if(idealSegmentCount !== externalSegmentCount){
+  if (idealSegmentCount !== externalSegmentCount) {
     return 'Bad';
   }
 
   let segmentStatus = 'Good';
   idealSegmentKeys.map((segmentKey) => {
-    if(segmentStatus === 'Good'){
-      if( !_.isEqual( idealStateObj[segmentKey], externalViewObj[segmentKey] ) ){
+    if (segmentStatus === 'Good') {
+      if (!_.isEqual(idealStateObj[segmentKey], externalViewObj[segmentKey])) {
         segmentStatus = 'Bad';
       }
     }
@@ -90,9 +91,161 @@ const findNestedObj = (entireObj, keyToFind, valToFind) => {
   return foundObj;
 };
 
+const generateCodeMirrorOptions = (array, type, modeType?) => {
+  const arr = [];
+  // eslint-disable-next-line no-shadow
+  const nestedFields = (arrayList, type, level, oldObj?) => {
+    _.map(arrayList, (a) => {
+      const obj = {
+        text: a.displayName || a.name || a,
+        displayText: a.displayName || a.name || a,
+        filterText: oldObj
+          ? `${oldObj.filterText}.${a.displayName || a.name || a}`
+          : a.displayName || a.name || a,
+        argsType: '',
+        description: '',
+        render: (el, cm, data) => {},
+        className:
+          type === 'FUNCTION'
+            ? 'codemirror-func'
+            : type === 'SQL'
+              ? 'codemirror-sql'
+              : type === 'BINARY-OPERATORS'
+                ? 'codemirror-Operators'
+                : type === 'TABLE'
+                  ? 'codemirror-table'
+                  : 'codemirror-column'
+      };
+      obj[type === 'FUNCTION' ? 'returnType' : 'type'] =
+        type === 'FUNCTION'
+          ? a.returnType
+          : type === 'SQL'
+            ? 'SQL'
+            : type === 'BINARY-OPERATORS'
+              ? 'Binary Operators'
+              : a.type;
+      if (type === 'FUNCTION') {
+        obj.argsType = a.argTypes.toString();
+        obj.description = a.description
+          ? `Description: ${a.description}`
+          : undefined;
+      }
+      obj.render = (el, cm, data) => {
+        codeMirrorOptionsTemplate(el, data);
+      };
+
+      if (oldObj === undefined) {
+        arr.push(obj);
+      } else {
+        const index = _.findIndex(
+          arr,
+          (n) => n.filterText === oldObj.filterText
+        );
+        if (index !== -1) {
+          const name = obj.displayText;
+          if (modeType === 'sql') {
+            obj.displayText = `${oldObj.displayText}.${name}`;
+            obj.text = `${oldObj.text}.${name}`;
+          } else {
+            obj.displayText = name;
+            obj.text = name;
+          }
+          obj.filterText = `${oldObj.text}.${name}`;
+          if (arr[index].fields) {
+            arr[index].fields.push(obj);
+          } else {
+            arr[index].fields = [];
+            arr[index].fields.push(obj);
+          }
+        } else {
+          const indexPath = getNestedObjPathFromList(arr, oldObj);
+          if (indexPath && indexPath.length) {
+            pushNestedObjectInArray(indexPath, obj, arr, modeType);
+          }
+        }
+      }
+      if (a.fields) {
+        nestedFields(a.fields, type, level + 1, obj);
+      }
+    });
+    return arr;
+  };
+  return nestedFields(array, type, 0);
+};
+
+const pushNestedObjectInArray = (pathArr, obj, targetList, modeType) => {
+  const rollOverFields = (target) => {
+    _.map(target, (list) => {
+      if (pathArr === list.filterText) {
+        if (modeType === 'sql') {
+          obj.displayText = `${pathArr}.${obj.displayText}`;
+          obj.text = `${pathArr}.${obj.text}`;
+        } else {
+          obj.displayText = obj.displayText;
+          obj.text = obj.text;
+        }
+        obj.filterText = `${pathArr}.${obj.text}`;
+        if (list.fields) {
+          list.fields.push(obj);
+        } else {
+          list.fields = [];
+          list.fields.push(obj);
+        }
+      } else if (list.fields) {
+        rollOverFields(list.fields);
+      }
+    });
+  };
+  rollOverFields(targetList);
+};
+
+const getNestedObjPathFromList = (list, obj) => {
+  const str = [];
+  const recursiveFunc = (arr, level) => {
+    _.map(arr, (a) => {
+      if (a.fields) {
+        str.push(a.filterText);
+        recursiveFunc(a.fields, level + 1);
+      } else if (obj.filterText === a.filterText) {
+        str.push(a.filterText);
+      }
+    });
+    return _.findLast(str);
+  };
+  return recursiveFunc(list, 0);
+};
+
+const codeMirrorOptionsTemplate = (el, data) => {
+  const text = document.createElement('div');
+  const fNameSpan = document.createElement('span');
+  fNameSpan.setAttribute('class', 'funcText');
+  fNameSpan.innerHTML = data.displayText;
+
+  // data.argsType is only for UDF Function
+  if (data.argsType && data.argsType.length) {
+    const paramSpan = document.createElement('span');
+    paramSpan.innerHTML = `(${data.argsType})`;
+    fNameSpan.appendChild(paramSpan);
+  }
+  text.appendChild(fNameSpan);
+  el.appendChild(text);
+
+  // data.returnType is for UDF Function ||  data.type is for Fields
+  if (data.returnType || data.type) {
+    const returnTypetxt = document.createElement('div');
+    returnTypetxt.setAttribute('class', 'fieldText');
+    const content = data.returnType
+      ? `Return Type: ${data.returnType}`
+      : `Type: ${data.type}`;
+    returnTypetxt.innerHTML = content;
+    el.appendChild(returnTypetxt);
+  }
+};
+
 export default {
   sortArray,
   tableFormat,
   getSegmentStatus,
-  findNestedObj
+  findNestedObj,
+  generateCodeMirrorOptions
 };
diff --git a/pinot-controller/src/main/resources/package.json b/pinot-controller/src/main/resources/package.json
index 81598e1..a05cb3b 100644
--- a/pinot-controller/src/main/resources/package.json
+++ b/pinot-controller/src/main/resources/package.json
@@ -58,6 +58,7 @@
     "@material-ui/core": "4.11.0",
     "@material-ui/icons": "^4.9.1",
     "@material-ui/lab": "^4.0.0-alpha.51",
+    "@types/codemirror": "0.0.97",
     "@types/react-router-dom": "^5.1.5",
     "axios": "^0.19.2",
     "codemirror": "^5.55.0",


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org


[incubator-pinot] 02/02: Adding api to edit ZK path

Posted by ki...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kishoreg pushed a commit to branch zookeeper-put-api
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git

commit 418b6ad24e53f5a177da5aec586e78ff99e94e33
Author: kishoreg <g....@gmail.com>
AuthorDate: Sun Aug 30 01:01:44 2020 -0700

    Adding api to edit ZK path
---
 .../api/resources/ZookeeperResource.java           | 38 ++++++++++++++++++++++
 .../helix/core/PinotHelixResourceManager.java      |  4 +++
 2 files changed, 42 insertions(+)

diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ZookeeperResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ZookeeperResource.java
index 3093052..f4c00ba 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ZookeeperResource.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/ZookeeperResource.java
@@ -19,6 +19,7 @@
 package org.apache.pinot.controller.api.resources;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.base.Charsets;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -30,11 +31,14 @@ import java.util.Map;
 import javax.inject.Inject;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.helix.AccessOption;
 import org.apache.helix.ZNRecord;
 import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
@@ -75,6 +79,40 @@ public class ZookeeperResource {
     return null;
   }
 
+  @PUT
+  @Path("/zk/put")
+  @Produces(MediaType.TEXT_PLAIN)
+  @ApiOperation(value = "Get content of the znode")
+  @ApiResponses(value = { //
+      @ApiResponse(code = 200, message = "Success"), //
+      @ApiResponse(code = 404, message = "ZK Path not found"), //
+      @ApiResponse(code = 204, message = "No Content"), //
+      @ApiResponse(code = 500, message = "Internal server error")})
+  public SuccessResponse putData(
+      @ApiParam(value = "Zookeeper Path, must start with /", required = true, defaultValue = "/") @QueryParam("path") @DefaultValue("") String path,
+      @ApiParam(value = "Content", required = true) @QueryParam("data") @DefaultValue("") String content,
+      @ApiParam(value = "expectedVersion", required = true, defaultValue = "-1") @QueryParam("expectedVersion") @DefaultValue("-1") String expectedVersion,
+      @ApiParam(value = "accessOption", required = true, defaultValue = "1") @QueryParam("accessOption") @DefaultValue("1") String accessOption) {
+    path = validateAndNormalizeZKPath(path);
+    ZNRecord record = null;
+    if (content != null) {
+      record = (ZNRecord) _znRecordSerializer.deserialize(content.getBytes(Charsets.UTF_8));
+    }
+    try {
+      boolean result = pinotHelixResourceManager
+          .setZKData(path, record, Integer.parseInt(expectedVersion), Integer.parseInt(accessOption));
+      if (result) {
+        return new SuccessResponse("Successfully Updated path: " + path);
+      } else {
+        throw new ControllerApplicationException(LOGGER, "Failed to update path: " + path,
+            Response.Status.INTERNAL_SERVER_ERROR);
+      }
+    } catch (Exception e) {
+      throw new ControllerApplicationException(LOGGER, "Failed to update path: " + path,
+          Response.Status.INTERNAL_SERVER_ERROR, e);
+    }
+  }
+
   @GET
   @Path("/zk/ls")
   @Produces(MediaType.APPLICATION_JSON)
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
index 7b6c468..f07b6be 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
@@ -1191,6 +1191,10 @@ public class PinotHelixResourceManager {
     }
   }
 
+  public boolean setZKData(String path, ZNRecord record, int expectedVersion, int accessOption) {
+    return _helixDataAccessor.getBaseDataAccessor().set(path, record, expectedVersion, accessOption);
+  }
+
   public ZNRecord readZKData(String path) {
     return _helixDataAccessor.getBaseDataAccessor().get(path, null, -1);
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org