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 <shahsan...@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.kish...@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-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org