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/09/15 14:08:31 UTC
[incubator-pinot] branch master updated: Support for Update &
Delete in ZooKeeper Browser and added SQL Functions in SQL Editor
autocomplete list (#5981)
This is an automated email from the ASF dual-hosted git repository.
kishoreg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 5da3433 Support for Update & Delete in ZooKeeper Browser and added SQL Functions in SQL Editor autocomplete list (#5981)
5da3433 is described below
commit 5da34330bce20ea1f4b86b47b2b2266a764e29c1
Author: Sanket Shah <sh...@users.noreply.github.com>
AuthorDate: Tue Sep 15 19:38:16 2020 +0530
Support for Update & Delete in ZooKeeper Browser and added SQL Functions in SQL Editor autocomplete list (#5981)
* Adding api to edit ZK path
* Adding delete api
* Support for Update & Delete in ZooKeeper Browser and added SQL Functions in SQL Editor autocomplete list
* showing notification on operation completion, display last refresh time, fixed refresh action
Co-authored-by: kishoreg <g....@gmail.com>
---
.../src/main/resources/app/components/Confirm.tsx | 106 ++++++++++++++++
.../resources/app/components/CustomCodemirror.tsx | 66 ++++++++++
.../app/components/Zookeeper/TreeDirectory.tsx | 136 +++++++++++++++++++--
.../src/main/resources/app/interfaces/types.d.ts | 1 +
.../src/main/resources/app/pages/Query.tsx | 9 ++
.../src/main/resources/app/pages/ZookeeperPage.tsx | 63 +++++-----
.../src/main/resources/app/requests/index.ts | 10 +-
.../src/main/resources/app/styles/styles.css | 5 +
.../main/resources/app/utils/PinotMethodUtils.ts | 22 +++-
.../src/main/resources/app/utils/Utils.tsx | 31 ++---
.../src/main/resources/app/utils/axios-config.ts | 2 +-
11 files changed, 394 insertions(+), 57 deletions(-)
diff --git a/pinot-controller/src/main/resources/app/components/Confirm.tsx b/pinot-controller/src/main/resources/app/components/Confirm.tsx
new file mode 100644
index 0000000..bb11f99
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Confirm.tsx
@@ -0,0 +1,106 @@
+/* eslint-disable no-nested-ternary */
+/**
+ * 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.
+ */
+
+import React, { useEffect } from 'react';
+import { Button, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, makeStyles } from '@material-ui/core';
+import { green, red } from '@material-ui/core/colors';
+
+const useStyles = makeStyles((theme) => ({
+ dialogContent: {
+ minWidth: 900
+ },
+ dialogTextContent: {
+ fontWeight: 600
+ },
+ dialogActions: {
+ justifyContent: 'center'
+ },
+ green: {
+ fontWeight: 600,
+ color: green[500],
+ borderColor: green[500],
+ '&:hover': {
+ backgroundColor: green[50],
+ borderColor: green[500]
+ }
+ },
+ red: {
+ fontWeight: 600,
+ color: red[500],
+ borderColor: red[500],
+ '&:hover': {
+ backgroundColor: red[50],
+ borderColor: red[500]
+ }
+ }
+}));
+
+
+type Props = {
+ openDialog: boolean,
+ dialogTitle?: string,
+ dialogContent: string,
+ successCallback: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
+ closeDialog: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
+ dialogYesLabel?: string,
+ dialogNoLabel?: string
+};
+
+const Confirm = ({openDialog, dialogTitle, dialogContent, successCallback, closeDialog, dialogYesLabel, dialogNoLabel}: Props) => {
+ const classes = useStyles();
+ const [open, setOpen] = React.useState(openDialog);
+
+ useEffect(()=>{
+ setOpen(openDialog);
+ }, [openDialog])
+
+ const isStringDialog = typeof dialogContent === 'string';
+
+ return (
+ <div>
+ <Dialog
+ open={open}
+ onClose={closeDialog}
+ aria-labelledby="alert-dialog-title"
+ aria-describedby="alert-dialog-description"
+ maxWidth={false}
+ >
+ {dialogTitle && <DialogTitle id="alert-dialog-title">{dialogTitle}</DialogTitle>}
+ <DialogContent className={`${!isStringDialog ? classes.dialogContent : ""}`}>
+ {isStringDialog ?
+ <DialogContentText id="alert-dialog-description" className={classes.dialogTextContent}>
+ {dialogContent}
+ </DialogContentText>
+ : dialogContent}
+ </DialogContent>
+ <DialogActions style={{paddingBottom: 20}} className={`${isStringDialog ? classes.dialogActions : ""}`}>
+ <Button variant="outlined" onClick={closeDialog} color="secondary" className={classes.red}>
+ {dialogNoLabel || "No"}
+ </Button>
+ <Button variant="outlined" onClick={successCallback} color="primary" autoFocus className={classes.green}>
+ {dialogYesLabel || "Yes"}
+ </Button>
+ </DialogActions>
+ </Dialog>
+ </div>
+ );
+};
+
+export default Confirm;
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/CustomCodemirror.tsx b/pinot-controller/src/main/resources/app/components/CustomCodemirror.tsx
new file mode 100644
index 0000000..4e55dec
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/CustomCodemirror.tsx
@@ -0,0 +1,66 @@
+/* eslint-disable no-nested-ternary */
+/**
+ * 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.
+ */
+
+import React, { } from 'react';
+import { UnControlled as CodeMirror } from 'react-codemirror2';
+import 'codemirror/lib/codemirror.css';
+import 'codemirror/theme/material.css';
+import 'codemirror/mode/javascript/javascript'
+import { makeStyles } from '@material-ui/core';
+
+type Props = {
+ data: Object,
+ isEditable?: Object,
+ returnCodemirrorValue?: Function
+};
+
+const useStyles = makeStyles((theme) => ({
+ codeMirror: {
+ '& .CodeMirror': { height: 600, border: '1px solid #BDCCD9', fontSize: '13px' },
+ }
+}));
+
+const CustomCodemirror = ({data, isEditable, returnCodemirrorValue}: Props) => {
+ const classes = useStyles();
+
+ const jsonoptions = {
+ lineNumbers: true,
+ mode: 'application/json',
+ styleActiveLine: true,
+ gutters: ['CodeMirror-lint-markers'],
+ lint: true,
+ theme: 'default',
+ readOnly: !isEditable
+ };
+
+ return (
+ <CodeMirror
+ options={jsonoptions}
+ value={JSON.stringify(data, null , 2)}
+ className={classes.codeMirror}
+ autoCursor={false}
+ onChange={(editor, data, value) => {
+ returnCodemirrorValue && returnCodemirrorValue(value);
+ }}
+ />
+ );
+};
+
+export default CustomCodemirror;
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx b/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
index 20074fb..10e5e6f 100644
--- a/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
+++ b/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
@@ -26,11 +26,20 @@ import RefreshOutlinedIcon from '@material-ui/icons/RefreshOutlined';
import NoteAddOutlinedIcon from '@material-ui/icons/NoteAddOutlined';
import DeleteOutlineOutlinedIcon from '@material-ui/icons/DeleteOutlineOutlined';
import EditOutlinedIcon from '@material-ui/icons/EditOutlined';
-import { Grid, ButtonGroup, Button, Tooltip, Popover, Typography } from '@material-ui/core';
+import { Grid, ButtonGroup, Button, Tooltip, Popover, Typography, Snackbar } from '@material-ui/core';
import MaterialTree from '../MaterialTree';
+import Confirm from '../Confirm';
+import CustomCodemirror from '../CustomCodemirror';
+import PinotMethodUtils from '../../utils/PinotMethodUtils';
+import Utils from '../../utils/Utils';
+import MuiAlert from '@material-ui/lab/Alert';
const drawerWidth = 400;
+const Alert = (props) => {
+ return <MuiAlert elevation={6} variant="filled" {...props} />;
+}
+
const useStyles = makeStyles((theme: Theme) =>
createStyles({
drawer: {
@@ -92,13 +101,29 @@ type Props = {
selected: any;
handleToggle: any;
handleSelect: any;
- refreshAction: Function;
+ isLeafNodeSelected: boolean;
+ currentNodeData: Object;
+ currentNodeMetadata: any;
+ showInfoEvent: Function;
+ fetchInnerPath: Function;
};
-const TreeDirectory = ({treeData, showChildEvent, expanded, selected, handleToggle, handleSelect, refreshAction}: Props) => {
+const TreeDirectory = ({
+ treeData, showChildEvent, selectedNode, expanded, selected, handleToggle, fetchInnerPath,
+ handleSelect, isLeafNodeSelected, currentNodeData, currentNodeMetadata, showInfoEvent
+}: Props) => {
const classes = useStyles();
+ let newCodeMirrorData = null;
+ const [confirmDialog, setConfirmDialog] = React.useState(false);
+ const [dialogTitle, setDialogTitle] = React.useState(null);
+ const [dialogContent, setDialogContent] = React.useState(null);
+ const [dialogSuccessCb, setDialogSuccessCb] = React.useState(null);
+ const [dialogYesLabel, setDialogYesLabel] = React.useState(null);
+ const [dialogNoLabel, setDialogNoLabel] = React.useState(null);
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
+ const [notificationData, setNotificationData] = React.useState({type: '', message: ''});
+ const [showNotification, setShowNotification] = React.useState(false);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
@@ -108,6 +133,83 @@ const TreeDirectory = ({treeData, showChildEvent, expanded, selected, handleTogg
setAnchorEl(null);
};
+ const handleEditClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+ if(!isLeafNodeSelected){
+ return;
+ }
+ setDialogTitle("Update Node Data");
+ setDialogContent(<CustomCodemirror
+ data={currentNodeData}
+ isEditable={true}
+ returnCodemirrorValue={(val)=>{ newCodeMirrorData = val;}}
+ />)
+ setDialogYesLabel("Update");
+ setDialogNoLabel("Cancel");
+ setDialogSuccessCb(() => confirmUpdate);
+ setConfirmDialog(true);
+ };
+
+ const handleDeleteClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+ if(!isLeafNodeSelected){
+ return;
+ }
+ setDialogContent("Delete this node?");
+ setDialogSuccessCb(() => deleteNode);
+ setConfirmDialog(true);
+ };
+
+ const confirmUpdate = () => {
+ setDialogYesLabel("Yes");
+ setDialogNoLabel("No");
+ setDialogContent("Are you sure want to update this node?");
+ setDialogSuccessCb(() => updateNode);
+ }
+
+ const updateNode = async () => {
+ const nodeData = {
+ path: selectedNode,
+ data: newCodeMirrorData.trim(),
+ expectedVersion: currentNodeMetadata.version,
+ accessOption: currentNodeMetadata.ephemeralOwner === 0 ? 1 : 10
+ }
+ const result = await PinotMethodUtils.putNodeData(nodeData);
+ if(result.data.status){
+ setNotificationData({type: 'success', message: result.data.status})
+ showInfoEvent(selectedNode);
+ } else {
+ setNotificationData({type: 'error', message: result.data.error})
+ }
+ setShowNotification(true);
+ closeDialog();
+ }
+
+ const deleteNode = async () => {
+ const parentPath = selectedNode.split('/').slice(0, selectedNode.split('/').length-1).join('/');
+ const treeObj = Utils.findNestedObj(treeData, 'fullPath', parentPath);
+ const result = await PinotMethodUtils.deleteNode(selectedNode);
+ if(result.data.status){
+ setNotificationData({type: 'success', message: result.data.status})
+ showInfoEvent(selectedNode);
+ fetchInnerPath(treeObj);
+ } else {
+ setNotificationData({type: 'error', message: result.data.error})
+ }
+ setShowNotification(true);
+ closeDialog();
+ }
+
+ const closeDialog = () => {
+ setConfirmDialog(false);
+ setDialogContent(null);
+ setDialogTitle(null);
+ setDialogYesLabel(null);
+ setDialogNoLabel(null);
+ };
+
+ const hideNotification = () => {
+ setShowNotification(false);
+ }
+
const open = Boolean(anchorEl);
const id = open ? 'simple-popover' : undefined;
@@ -127,16 +229,16 @@ const TreeDirectory = ({treeData, showChildEvent, expanded, selected, handleTogg
<div className={classes.buttonGrpDiv}>
<ButtonGroup color="primary" aria-label="outlined primary button group" className={classes.btnGroup}>
<Tooltip title="Refresh">
- <Button onClick={(e)=>{refreshAction();}}><RefreshOutlinedIcon/></Button>
+ <Button onClick={(e)=>{showInfoEvent(selectedNode);}}><RefreshOutlinedIcon/></Button>
</Tooltip>
<Tooltip title="Add">
<Button onClick={handleClick}><NoteAddOutlinedIcon/></Button>
</Tooltip>
- <Tooltip title="Delete">
- <Button onClick={handleClick}><DeleteOutlineOutlinedIcon/></Button>
+ <Tooltip title="Delete" open={false}>
+ <Button onClick={handleDeleteClick} disabled={!isLeafNodeSelected}><DeleteOutlineOutlinedIcon/></Button>
</Tooltip>
- <Tooltip title="Edit">
- <Button onClick={handleClick}><EditOutlinedIcon/></Button>
+ <Tooltip title="Edit" open={false}>
+ <Button onClick={handleEditClick} disabled={!isLeafNodeSelected}><EditOutlinedIcon/></Button>
</Tooltip>
</ButtonGroup>
</div>
@@ -168,6 +270,24 @@ const TreeDirectory = ({treeData, showChildEvent, expanded, selected, handleTogg
</Grid>
</div>
</Drawer>
+ <Confirm
+ openDialog={confirmDialog}
+ dialogTitle={dialogTitle}
+ dialogContent={dialogContent}
+ successCallback={dialogSuccessCb}
+ closeDialog={closeDialog}
+ dialogYesLabel={dialogYesLabel}
+ dialogNoLabel={dialogNoLabel}
+ />
+ <Snackbar
+ anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
+ open={showNotification}
+ onClose={hideNotification}
+ key="notification"
+ autoHideDuration={3000}
+ >
+ <Alert severity={notificationData.type}>{notificationData.message}</Alert>
+ </Snackbar>
</>
);
};
diff --git a/pinot-controller/src/main/resources/app/interfaces/types.d.ts b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
index b46f261..4199174 100644
--- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts
+++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
@@ -116,4 +116,5 @@ declare module 'Models' {
export type ZKGetList = Array<string>
export type ZKConfig = Object;
+ export type ZKOperationResponsne = any;
}
diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx b/pinot-controller/src/main/resources/app/pages/Query.tsx
index e3b67f5..53fd15d 100644
--- a/pinot-controller/src/main/resources/app/pages/Query.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Query.tsx
@@ -109,6 +109,13 @@ const sqloptions = {
extraKeys: { "'@'": 'autocomplete' },
};
+const sqlFuntionsList = [
+ "COUNT", "MIN", "MAX", "SUM", "AVG", "MINMAXRANGE", "DISTINCTCOUNT", "DISTINCTCOUNTBITMAP",
+ "SEGMENTPARTITIONEDDISTINCTCOUNT", "DISTINCTCOUNTHLL", "DISTINCTCOUNTRAWHLL", "FASTHLL",
+ "DISTINCTCOUNTTHETASKETCH", "DISTINCTCOUNTRAWTHETASKETCH", "COUNTMV", "MINMV", "MAXMV",
+ "SUMMV", "AVGMV", "MINMAXRANGEMV", "DISTINCTCOUNTMV", "DISTINCTCOUNTBITMAPMV", "DISTINCTCOUNTHLLMV",
+ "DISTINCTCOUNTRAWHLLMV", "DISTINCT", "ST_UNION"];
+
const QueryPage = () => {
const classes = useStyles();
const [fetching, setFetching] = useState(true);
@@ -252,6 +259,7 @@ const QueryPage = () => {
Array.prototype.push.apply(hintOptions, Utils.generateCodeMirrorOptions(tableNames, 'TABLE'));
Array.prototype.push.apply(hintOptions, Utils.generateCodeMirrorOptions(columnNames, 'COLUMNS'));
+ Array.prototype.push.apply(hintOptions, Utils.generateCodeMirrorOptions(sqlFuntionsList, 'FUNCTION'));
const cur = cm.getCursor();
const curLine = cm.getLine(cur.line);
@@ -270,6 +278,7 @@ const QueryPage = () => {
Array.prototype.push.apply(defaultHint.list, finalList);
+ defaultHint.list = _.uniqBy(defaultHint.list, 'text');
return defaultHint;
};
diff --git a/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx b/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx
index 8a88b16..fa83c33 100644
--- a/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/ZookeeperPage.tsx
@@ -21,16 +21,13 @@
import React, { useEffect, useState } from 'react';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import { Grid, Paper, Tabs, Tab } from '@material-ui/core';
-import { UnControlled as CodeMirror } from 'react-codemirror2';
-import 'codemirror/lib/codemirror.css';
-import 'codemirror/theme/material.css';
-import 'codemirror/mode/javascript/javascript';
import _ from 'lodash';
import AppLoader from '../components/AppLoader';
import PinotMethodUtils from '../utils/PinotMethodUtils';
import TreeDirectory from '../components/Zookeeper/TreeDirectory';
import TabPanel from '../components/TabPanel';
import Utils from '../utils/Utils';
+import CustomCodemirror from '../components/CustomCodemirror';
const useStyles = makeStyles((theme) => ({
root:{
@@ -48,21 +45,12 @@ const useStyles = makeStyles((theme) => ({
borderRadius: 4,
marginBottom: '20px',
},
- codeMirror: {
- '& .CodeMirror': { height: 600, border: '1px solid #BDCCD9', fontSize: '13px' },
+ lastRefreshDiv: {
+ direction: 'rtl',
+ margin: '-15px 0'
}
}));
-const jsonoptions = {
- lineNumbers: true,
- mode: 'application/json',
- styleActiveLine: true,
- gutters: ['CodeMirror-lint-markers'],
- lint: true,
- theme: 'default',
- readOnly: true
-};
-
const ZookeeperPage = () => {
const classes = useStyles();
const theme = useTheme();
@@ -72,10 +60,12 @@ const ZookeeperPage = () => {
const [currentNodeMetadata, setCurrentNodeMetadata] = useState({});
const [selectedNode, setSelectedNode] = useState(null);
const [count, setCount] = useState(1);
+ const [leafNode, setLeafNode] = useState(false);
// states and handlers for toggle and select of tree
const [expanded, setExpanded] = React.useState<string[]>(["1"]);
const [selected, setSelected] = React.useState<string[]>(["1"]);
+ const [lastRefresh, setLastRefresh] = React.useState(null);
const handleToggle = (event: React.ChangeEvent<{}>, nodeIds: string[]) => {
setExpanded(nodeIds);
@@ -86,6 +76,8 @@ const ZookeeperPage = () => {
setSelected(nodeIds);
const treeObj = Utils.findNestedObj(treeData, 'nodeId', nodeIds);
if(treeObj){
+ setLeafNode(treeObj.isLeafNode);
+ setSelectedNode(treeObj.fullPath || '/');
showInfoEvent(treeObj.fullPath || '/');
}
}
@@ -96,6 +88,7 @@ const ZookeeperPage = () => {
const { currentNodeData, currentNodeMetadata } = await PinotMethodUtils.getNodeData(fullPath);
setCurrentNodeData(currentNodeData);
setCurrentNodeMetadata(currentNodeMetadata);
+ setLastRefresh(new Date());
}
// handlers for Tabs
@@ -114,6 +107,7 @@ const ZookeeperPage = () => {
const fetchInnerPath = async (pathObj) => {
const {newTreeData, currentNodeData, currentNodeMetadata, counter } = await PinotMethodUtils.getZookeeperData(pathObj.fullPath, count);
pathObj.child = newTreeData[0].child;
+ pathObj.isLeafNode = newTreeData[0].child.length === 0;
pathObj.hasChildRendered = true;
// setting the old treeData again here since pathObj has the reference of old treeData
// and newTreeData is not useful here.
@@ -135,6 +129,7 @@ const ZookeeperPage = () => {
setCount(counter);
setExpanded(["1"]);
setSelected(["1"]);
+ setLastRefresh(new Date());
setFetching(false);
};
@@ -142,6 +137,20 @@ const ZookeeperPage = () => {
fetchData();
}, []);
+ const renderLastRefresh = () => (
+ <div className={classes.lastRefreshDiv}>
+ <p>
+ {`Last Refreshed: ${lastRefresh.toLocaleTimeString("en-US",{
+ hour12: true,
+ hour: 'numeric',
+ minute: '2-digit',
+ second: '2-digit'
+ })}
+ `}
+ </p>
+ </div>
+ )
+
return fetching ? (
<AppLoader />
) : (
@@ -155,7 +164,11 @@ const ZookeeperPage = () => {
selected={selected}
handleToggle={handleToggle}
handleSelect={handleSelect}
- refreshAction={fetchData}
+ isLeafNodeSelected={leafNode}
+ currentNodeData={currentNodeData}
+ currentNodeMetadata={currentNodeMetadata}
+ showInfoEvent={showInfoEvent}
+ fetchInnerPath={fetchInnerPath}
/>
</Grid>
<Grid item xs style={{ padding: 20, backgroundColor: 'white', maxHeight: 'calc(100vh - 70px)', overflowY: 'auto' }}>
@@ -178,23 +191,15 @@ const ZookeeperPage = () => {
index={0}
dir={theme.direction}
>
+ {lastRefresh && renderLastRefresh()}
<div className={classes.codeMirrorDiv}>
- <CodeMirror
- options={jsonoptions}
- value={JSON.stringify(currentNodeData, null , 2)}
- className={classes.codeMirror}
- autoCursor={false}
- />
+ <CustomCodemirror data={currentNodeData}/>
</div>
</TabPanel>
<TabPanel value={value} index={1} dir={theme.direction}>
+ {lastRefresh && renderLastRefresh()}
<div className={classes.codeMirrorDiv}>
- <CodeMirror
- options={jsonoptions}
- value={JSON.stringify(currentNodeMetadata, null , 2)}
- className={classes.codeMirror}
- autoCursor={false}
- />
+ <CustomCodemirror data={currentNodeMetadata}/>
</div>
</TabPanel>
</Grid>
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts b/pinot-controller/src/main/resources/app/requests/index.ts
index 4b6e239..373c9e7 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -19,7 +19,7 @@
import { AxiosResponse } from 'axios';
import { TableData, Instances, Instance, Tenants, ClusterConfig, TableName, TableSize,
- IdealState, QueryTables, TableSchema, SQLResult, ClusterName, ZKGetList, ZKConfig
+ IdealState, QueryTables, TableSchema, SQLResult, ClusterName, ZKGetList, ZKConfig, ZKOperationResponsne
} from 'Models';
import { baseApi } from '../utils/axios-config';
@@ -78,4 +78,10 @@ export const zookeeperGetStat = (params: string): Promise<AxiosResponse<ZKConfig
baseApi.get(`/zk/stat?path=${params}`);
export const zookeeperGetListWithStat = (params: string): Promise<AxiosResponse<ZKConfig>> =>
- baseApi.get(`/zk/lsl?path=${params}`);
\ No newline at end of file
+ baseApi.get(`/zk/lsl?path=${params}`);
+
+export const zookeeperPutData = (params: string): Promise<AxiosResponse<ZKOperationResponsne>> =>
+ baseApi.put(`/zk/put?${params}`, null, { headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Accept': 'text/plain, */*; q=0.01' } });
+
+export const zookeeperDeleteNode = (params: string): Promise<AxiosResponse<ZKOperationResponsne>> =>
+ baseApi.delete(`/zk/delete?path=${params}`);
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/styles/styles.css b/pinot-controller/src/main/resources/app/styles/styles.css
index d3c2d97..5415efc 100644
--- a/pinot-controller/src/main/resources/app/styles/styles.css
+++ b/pinot-controller/src/main/resources/app/styles/styles.css
@@ -68,6 +68,11 @@ li.codemirror-column.CodeMirror-hint::before {
background: #05a;
}
+li.codemirror-func.CodeMirror-hint::before {
+ content: "F";
+ background: #74457a;
+}
+
.CodeMirror-hints {
padding: 4px;
box-shadow: 0px 0px 5px rgba(0,0,0,.2);
diff --git a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
index 30947cf..35a2c9e 100644
--- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
+++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
@@ -38,7 +38,9 @@ import {
zookeeperGetList,
zookeeperGetData,
zookeeperGetListWithStat,
- zookeeperGetStat
+ zookeeperGetStat,
+ zookeeperPutData,
+ zookeeperDeleteNode
} from '../requests';
import Utils from './Utils';
@@ -546,6 +548,20 @@ const getNodeData = (path) => {
});
};
+const putNodeData = (data) => {
+ const serializedData = Utils.serialize(data);
+ return zookeeperPutData(serializedData).then((obj)=>{
+ return obj;
+ });
+};
+
+const deleteNode = (path) => {
+ const params = encodeURIComponent(path);
+ return zookeeperDeleteNode(params).then((obj)=>{
+ return obj;
+ });
+};
+
export default {
getTenantsData,
getAllInstances,
@@ -565,5 +581,7 @@ export default {
getInstanceConfig,
getTenantsFromInstance,
getZookeeperData,
- getNodeData
+ getNodeData,
+ putNodeData,
+ deleteNode
};
diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx b/pinot-controller/src/main/resources/app/utils/Utils.tsx
index 7f80dfb..c859a96 100644
--- a/pinot-controller/src/main/resources/app/utils/Utils.tsx
+++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx
@@ -102,8 +102,6 @@ const generateCodeMirrorOptions = (array, type, modeType?) => {
filterText: oldObj
? `${oldObj.filterText}.${a.displayName || a.name || a}`
: a.displayName || a.name || a,
- argsType: '',
- description: '',
render: (el, cm, data) => {},
className:
type === 'FUNCTION'
@@ -124,12 +122,6 @@ const generateCodeMirrorOptions = (array, type, modeType?) => {
: 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);
};
@@ -221,12 +213,6 @@ const codeMirrorOptionsTemplate = (el, data) => {
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);
@@ -242,10 +228,25 @@ const codeMirrorOptionsTemplate = (el, data) => {
}
};
+const serialize = (obj: any, prefix?: any) => {
+ let str = [], p;
+ for (p in obj) {
+ if (obj.hasOwnProperty(p)) {
+ var k = prefix ? prefix + "[" + p + "]" : p,
+ v = obj[p];
+ str.push((v !== null && typeof v === "object") ?
+ serialize(v, k) :
+ encodeURIComponent(k) + "=" + encodeURIComponent(v));
+ }
+ }
+ return str.join("&");
+}
+
export default {
sortArray,
tableFormat,
getSegmentStatus,
findNestedObj,
- generateCodeMirrorOptions
+ generateCodeMirrorOptions,
+ serialize
};
diff --git a/pinot-controller/src/main/resources/app/utils/axios-config.ts b/pinot-controller/src/main/resources/app/utils/axios-config.ts
index 3d2b924..2277066 100644
--- a/pinot-controller/src/main/resources/app/utils/axios-config.ts
+++ b/pinot-controller/src/main/resources/app/utils/axios-config.ts
@@ -27,7 +27,7 @@ const handleError = (error: any) => {
if (isDev) {
console.log(error);
}
- return error;
+ return error.response || error;
};
const handleResponse = (response: any) => {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org