You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ne...@apache.org on 2020/10/29 22:12:48 UTC
[incubator-pinot] branch master updated: Adding operation in table
details page (#6198)
This is an automated email from the ASF dual-hosted git repository.
nehapawar 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 ce43288 Adding operation in table details page (#6198)
ce43288 is described below
commit ce4328896e93740dc9f000e56584d27125c0df6b
Author: Sanket Shah <sh...@users.noreply.github.com>
AuthorDate: Fri Oct 30 03:42:22 2020 +0530
Adding operation in table details page (#6198)
---
pinot-controller/src/main/resources/app/App.tsx | 45 +--
.../main/resources/app/components/CustomButton.tsx | 33 +-
.../main/resources/app/components/CustomDialog.tsx | 10 +-
.../app/components/CustomNotification.tsx | 59 ++--
.../Homepage/Operations/EditConfigOp.tsx | 2 +-
.../Homepage/Operations/RebalanceServerTableOp.tsx | 166 +++++++++
...ditConfigOp.tsx => RebalanceServerTenantOp.tsx} | 35 +-
.../Homepage/Operations/ReloadStatusOp.tsx | 125 +++++++
.../NotificationContext.tsx} | 40 +--
.../Notification/NotificationContextProvider.tsx | 61 ++++
.../resources/app/components/SimpleAccordion.tsx | 33 +-
.../src/main/resources/app/components/Table.tsx | 17 +-
.../app/components/Zookeeper/TreeDirectory.tsx | 38 +-
.../src/main/resources/app/interfaces/types.d.ts | 10 +-
.../main/resources/app/pages/InstanceDetails.tsx | 41 +--
.../src/main/resources/app/pages/Query.tsx | 5 +-
.../main/resources/app/pages/SegmentDetails.tsx | 47 ++-
.../main/resources/app/pages/TablesListingPage.tsx | 26 +-
.../src/main/resources/app/pages/TenantDetails.tsx | 390 ++++++++++++++++++++-
.../src/main/resources/app/pages/Tenants.tsx | 56 ++-
.../src/main/resources/app/requests/index.ts | 29 +-
.../main/resources/app/utils/PinotMethodUtils.ts | 241 ++++++++-----
.../src/main/resources/app/utils/Utils.tsx | 46 ++-
23 files changed, 1218 insertions(+), 337 deletions(-)
diff --git a/pinot-controller/src/main/resources/app/App.tsx b/pinot-controller/src/main/resources/app/App.tsx
index 63e3c85..27680cb 100644
--- a/pinot-controller/src/main/resources/app/App.tsx
+++ b/pinot-controller/src/main/resources/app/App.tsx
@@ -25,6 +25,8 @@ import theme from './theme';
import Layout from './components/Layout';
import RouterData from './router';
import PinotMethodUtils from './utils/PinotMethodUtils';
+import CustomNotification from './components/CustomNotification';
+import { NotificationContextProvider } from './components/Notification/NotificationContextProvider';
const App = () => {
const fetchClusterName = async () => {
@@ -36,26 +38,29 @@ const App = () => {
}, []);
return (
<MuiThemeProvider theme={theme}>
- <Router>
- <Switch>
- {RouterData.map(({ path, Component }, key) => (
- <Route
- exact
- path={path}
- key={key}
- render={props => {
- return (
- <div className="p-8">
- <Layout {...props}>
- <Component {...props} />
- </Layout>
- </div>
- );
- }}
- />
- ))}
- </Switch>
- </Router>
+ <NotificationContextProvider>
+ <CustomNotification />
+ <Router>
+ <Switch>
+ {RouterData.map(({ path, Component }, key) => (
+ <Route
+ exact
+ path={path}
+ key={key}
+ render={props => {
+ return (
+ <div className="p-8">
+ <Layout {...props}>
+ <Component {...props} />
+ </Layout>
+ </div>
+ );
+ }}
+ />
+ ))}
+ </Switch>
+ </Router>
+ </NotificationContextProvider>
</MuiThemeProvider>
);
};
diff --git a/pinot-controller/src/main/resources/app/components/CustomButton.tsx b/pinot-controller/src/main/resources/app/components/CustomButton.tsx
index 6461cc8..db62618 100644
--- a/pinot-controller/src/main/resources/app/components/CustomButton.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomButton.tsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
-import { Button, makeStyles } from '@material-ui/core';
+import { Button, makeStyles, Tooltip } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
button: {
@@ -29,24 +29,33 @@ const useStyles = makeStyles((theme) => ({
type Props = {
children: any;
- onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
+ onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+ isDisabled?: boolean,
+ tooltipTitle?: string,
+ enableTooltip?: boolean
};
export default function CustomButton({
children,
- onClick
+ isDisabled,
+ onClick,
+ tooltipTitle = '',
+ enableTooltip = false
}: Props) {
const classes = useStyles();
return (
- <Button
- variant="contained"
- color="primary"
- className={classes.button}
- size="small"
- onClick={onClick}
- >
- {children}
- </Button>
+ <Tooltip title={tooltipTitle} disableHoverListener={!enableTooltip} placement="top" arrow>
+ <Button
+ variant="contained"
+ color="primary"
+ className={classes.button}
+ size="small"
+ onClick={onClick}
+ disabled={isDisabled}
+ >
+ {children}
+ </Button>
+ </Tooltip>
);
}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
index cefcac9..8bfe279 100644
--- a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
@@ -45,13 +45,14 @@ const CancelButton = withStyles(() => ({
type Props = {
open: boolean,
handleClose: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
- handleSave: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+ handleSave?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
title: string,
children: any,
btnCancelText?: string,
btnOkText?: string,
showCancelBtn?: boolean,
- showOkBtn?: boolean
+ showOkBtn?: boolean,
+ largeSize?: boolean
};
export default function CustomDialog({
@@ -63,13 +64,14 @@ export default function CustomDialog({
btnCancelText,
btnOkText,
showCancelBtn = true,
- showOkBtn = true
+ showOkBtn = true,
+ largeSize = false
}: Props) {
const classes = useStyles();
return (
- <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" className={classes.root}>
+ <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" className={classes.root} maxWidth={largeSize ? "lg" : false} fullWidth={largeSize}>
<DialogTitle className={classes.dialogTitle}>{title}</DialogTitle>
{children}
<DialogActions>
diff --git a/pinot-controller/src/main/resources/app/components/CustomNotification.tsx b/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
index 6668cb7..8b35063 100644
--- a/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomNotification.tsx
@@ -20,49 +20,34 @@
import React, { } from 'react';
import { Snackbar } from '@material-ui/core';
import MuiAlert from '@material-ui/lab/Alert';
-
+import { NotificationContext } from './Notification/NotificationContext';
const Alert = (props) => {
return <MuiAlert elevation={6} variant="filled" {...props} />;
};
-type Props = {
- type: string,
- message: string,
- show: boolean,
- hide: Function
-};
-
-const CustomNotification = ({
- type, message, show, hide
-}: Props) => {
-
- const [notificationData, setNotificationData] = React.useState({type, message});
- const [showNotification, setShowNotification] = React.useState(show);
-
- React.useEffect(()=>{
- setShowNotification(show);
- }, [show]);
-
- React.useEffect(()=>{
- setNotificationData({type, message});
- }, [type, message]);
-
- const hideNotification = () => {
- hide();
- setShowNotification(false);
- };
-
+const CustomNotification = () => {
return (
- <Snackbar
- anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
- open={showNotification}
- onClose={hideNotification}
- key="notification"
- autoHideDuration={3000}
- >
- <Alert severity={notificationData.type}>{notificationData.message}</Alert>
- </Snackbar>
+ <NotificationContext.Consumer>
+ {context =>
+ <Snackbar
+ anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
+ open={context && context.show}
+ onClose={()=>{
+ context.dispatch({type: '', message: "", show:false});
+ context.hide && context.hide()
+ }}
+ autoHideDuration={10000}
+ >
+ <Alert
+ onClose={context.hide && context.hide()}
+ severity={context && context.type}
+ >
+ {context && context.message}
+ </Alert>
+ </Snackbar>
+ }
+ </NotificationContext.Consumer>
);
};
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
index 4a32e0e..c856ce9 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
@@ -30,7 +30,7 @@ type Props = {
handleConfigChange: (value: string) => void,
};
-export default function CustomModal({
+export default function EditConfigOp({
showModal,
hideModal,
saveConfig,
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx
new file mode 100644
index 0000000..fb882a1
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx
@@ -0,0 +1,166 @@
+/**
+ * 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 { DialogContent, DialogContentText, FormControl, FormControlLabel, Grid, Input, InputLabel, Switch} from '@material-ui/core';
+import Dialog from '../../CustomDialog';
+import PinotMethodUtils from '../../../utils/PinotMethodUtils';
+import CustomCodemirror from '../../CustomCodemirror';
+
+type Props = {
+ tableType: string,
+ tableName: string,
+ hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
+};
+
+export default function RebalanceServerTableOp({
+ hideModal,
+ tableName,
+ tableType
+}: Props) {
+ const [rebalanceResponse, setRebalanceResponse] = React.useState(null)
+ const [dryRun, setDryRun] = React.useState(false);
+ const [reassignInstances, setReassignInstances] = React.useState(false);
+ const [includeConsuming, setIncludeConsuming] = React.useState(false);
+ const [bootstrap, setBootstrap] = React.useState(false);
+ const [downtime, setDowntime] = React.useState(false);
+ const [minAvailableReplicas, setMinAvailableReplicas] = React.useState("1");
+ const [bestEfforts, setBestEfforts] = React.useState(false);
+
+ const getData = () => {
+ return {
+ type: tableType,
+ dryRun, reassignInstances, includeConsuming, bootstrap, downtime, bestEfforts,
+ minAvailableReplicas: parseInt(minAvailableReplicas, 10)
+ }
+ };
+
+ const handleSave = async (event) => {
+ const data = getData();
+ const response = await PinotMethodUtils.rebalanceServersForTableOp(tableName, data);
+ setRebalanceResponse(response);
+ };
+
+ return (
+ <Dialog
+ open={true}
+ handleClose={hideModal}
+ title="Rebalance Server"
+ handleSave={handleSave}
+ showOkBtn={!rebalanceResponse}
+ >
+ <DialogContent>
+ {!rebalanceResponse ?
+ <Grid container spacing={2}>
+ <Grid item xs={6}>
+ <FormControlLabel
+ control={
+ <Switch
+ checked={dryRun}
+ onChange={() => setDryRun(!dryRun)}
+ name="dryRun"
+ color="primary"
+ />
+ }
+ label="Dry Run"
+ />
+ <br/>
+ <FormControlLabel
+ control={
+ <Switch
+ checked={includeConsuming}
+ onChange={() => setIncludeConsuming(!includeConsuming)}
+ name="includeConsuming"
+ color="primary"
+ />
+ }
+ label="Include Consuming"
+ />
+ <br/>
+ <FormControlLabel
+ control={
+ <Switch
+ checked={downtime}
+ onChange={() => setDowntime(!downtime)}
+ name="downtime"
+ color="primary"
+ />
+ }
+ label="Downtime"
+ />
+ </Grid>
+ <Grid item xs={6}>
+ <FormControlLabel
+ control={
+ <Switch
+ checked={reassignInstances}
+ onChange={() => setReassignInstances(!reassignInstances)}
+ name="reassignInstances"
+ color="primary"
+ />
+ }
+ label="Reassign Instances"
+ />
+ <br/>
+ <FormControlLabel
+ control={
+ <Switch
+ checked={bootstrap}
+ onChange={() => setBootstrap(!bootstrap)}
+ name="bootstrap"
+ color="primary"
+ />
+ }
+ label="Bootstrap"
+ />
+ <br/>
+ <FormControlLabel
+ control={
+ <Switch
+ checked={bestEfforts}
+ onChange={() => setBestEfforts(!bestEfforts)}
+ name="bestEfforts"
+ color="primary"
+ />
+ }
+ label="Best Efforts"
+ />
+ </Grid>
+ <Grid item xs={6}>
+ <FormControl fullWidth={true}>
+ <InputLabel htmlFor="my-input">Minimum Available Replicas</InputLabel>
+ <Input id="my-input" type="number" value={minAvailableReplicas} onChange={(e)=> setMinAvailableReplicas(e.target.value)}/>
+ </FormControl>
+ </Grid>
+ </Grid>
+ :
+ <React.Fragment>
+ <DialogContentText>
+ Operation Status:
+ </DialogContentText>
+ <CustomCodemirror
+ data={rebalanceResponse}
+ isEditable={false}
+ />
+ </React.Fragment>
+ }
+ </DialogContent>
+ </Dialog>
+ );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTenantOp.tsx
similarity index 65%
copy from pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
copy to pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTenantOp.tsx
index 4a32e0e..3810647 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/EditConfigOp.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTenantOp.tsx
@@ -18,41 +18,36 @@
*/
import React from 'react';
-import { DialogContent} from '@material-ui/core';
+import { DialogContent, DialogContentText, makeStyles } from '@material-ui/core';
import Dialog from '../../CustomDialog';
-import CustomCodemirror from '../../CustomCodemirror';
+
+const useStyles = makeStyles((theme) => ({
+
+}));
type Props = {
showModal: boolean,
- hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
- saveConfig: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
- config: string,
- handleConfigChange: (value: string) => void,
+ hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
};
-export default function CustomModal({
+export default function RebalanceServerTenantOp({
showModal,
- hideModal,
- saveConfig,
- handleConfigChange,
- config
+ hideModal
}: Props) {
+ const classes = useStyles();
return (
<Dialog
open={showModal}
handleClose={hideModal}
- title="Edit Config"
- handleSave={saveConfig}
+ handleSave={(e)=>{console.log('save clicked');}}
+ title="Rebalance Server Tenant"
>
<DialogContent>
- <CustomCodemirror
- data={config}
- isEditable={true}
- returnCodemirrorValue={(newValue)=>{
- handleConfigChange(newValue);
- }}
- />
+ <DialogContentText>
+ To subscribe to this website, please enter your email address here. We will send updates
+ occasionally.
+ </DialogContentText>
</DialogContent>
</Dialog>
);
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/ReloadStatusOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/ReloadStatusOp.tsx
new file mode 100644
index 0000000..5d2c465
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/ReloadStatusOp.tsx
@@ -0,0 +1,125 @@
+/**
+ * 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 { CircularProgress, createStyles, DialogContent, makeStyles, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Theme, withStyles} from '@material-ui/core';
+import Dialog from '../../CustomDialog';
+import CloseIcon from '@material-ui/icons/Close';
+import CheckIcon from '@material-ui/icons/Check';
+import { red } from '@material-ui/core/colors';
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ textAlign: 'center'
+ },
+ container: {
+ maxHeight: 540,
+ },
+ greenColor: {
+ color: theme.palette.success.main
+ },
+ redColor: {
+ color: theme.palette.error.main
+ },
+ })
+);
+
+const StyledTableCell = withStyles((theme: Theme) =>
+ createStyles({
+ head: {
+ backgroundColor: '#ecf3fe',
+ color: theme.palette.primary.main,
+ fontWeight: 600
+ }
+ }),
+)(TableCell);
+
+type Props = {
+ data: any,
+ hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
+};
+
+export default function ReloadStatusOp({
+ data,
+ hideModal
+}: Props) {
+ const classes = useStyles();
+ const segmentNames = data && Object.keys(data);
+ const indexes = data && data[segmentNames[0]]?.indexes;
+ const indexesKeys = indexes && Object.keys(indexes);
+ const indexObjKeys = indexes && indexes[indexesKeys[0]] && Object.keys(indexes[indexesKeys[0]]);
+ return (
+ <Dialog
+ open={true}
+ handleClose={hideModal}
+ title="Reload Status"
+ showOkBtn={false}
+ largeSize={true}
+ >
+ {!data ?
+ <div className={classes.root}><CircularProgress/></div>
+ :
+ <DialogContent>
+ <TableContainer component={Paper} className={classes.container}>
+ <Table stickyHeader aria-label="sticky table" size="small">
+ <TableHead>
+ <TableRow>
+ <StyledTableCell></StyledTableCell>
+ {indexObjKeys.map((o, i)=>{
+ return (
+ <StyledTableCell key={i} align="right">{o}</StyledTableCell>
+ );
+ })}
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {indexesKeys.map((indexName, i) => {
+ const indexObj = indexes[indexName];
+ return (
+ <TableRow key={i}>
+ <StyledTableCell component="th" scope="row">
+ {indexName}
+ </StyledTableCell>
+ {indexObjKeys.map((o, i)=>{
+ let iconElement = null;
+ if(indexObj[o].toLowerCase() === 'yes'){
+ iconElement = <CheckIcon className={classes.greenColor}/>;
+ } else if(indexObj[o].toLowerCase() === 'no'){
+ iconElement = <CloseIcon className={classes.redColor}/>;
+ } else {
+ iconElement = indexObj[o];
+ }
+ return (
+ <StyledTableCell align="center" key={i}>
+ {iconElement}
+ </StyledTableCell>
+ )
+ })}
+ </TableRow>
+ )
+ })}
+ </TableBody>
+ </Table>
+ </TableContainer>
+ </DialogContent>
+ }
+ </Dialog>
+ );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/CustomButton.tsx b/pinot-controller/src/main/resources/app/components/Notification/NotificationContext.tsx
similarity index 57%
copy from pinot-controller/src/main/resources/app/components/CustomButton.tsx
copy to pinot-controller/src/main/resources/app/components/Notification/NotificationContext.tsx
index 6461cc8..46ec205 100644
--- a/pinot-controller/src/main/resources/app/components/CustomButton.tsx
+++ b/pinot-controller/src/main/resources/app/components/Notification/NotificationContext.tsx
@@ -17,36 +17,14 @@
* under the License.
*/
-import React from 'react';
-import { Button, makeStyles } from '@material-ui/core';
+import * as React from 'react';
-const useStyles = makeStyles((theme) => ({
- button: {
- margin: theme.spacing(1),
- textTransform: 'none'
- }
-}));
+export interface NotificationContextInterface {
+ type: string,
+ message: string,
+ show: boolean,
+ hide: Function,
+ dispatch: React.Dispatch<any>;
+}
-type Props = {
- children: any;
- onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
-};
-
-export default function CustomButton({
- children,
- onClick
-}: Props) {
- const classes = useStyles();
-
- return (
- <Button
- variant="contained"
- color="primary"
- className={classes.button}
- size="small"
- onClick={onClick}
- >
- {children}
- </Button>
- );
-}
\ No newline at end of file
+export const NotificationContext = React.createContext<NotificationContextInterface>(null);
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Notification/NotificationContextProvider.tsx b/pinot-controller/src/main/resources/app/components/Notification/NotificationContextProvider.tsx
new file mode 100644
index 0000000..c7ff2c9
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Notification/NotificationContextProvider.tsx
@@ -0,0 +1,61 @@
+/**
+ * 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 * as React from 'react';
+import { NotificationContext } from "./NotificationContext";
+import { useReducer } from "react";
+
+
+type NotificationContextReducers = {
+ type: string,
+ message: string,
+ show: boolean,
+ hide: Function
+};
+
+const NotificationContextValue : NotificationContextReducers = {
+ type: "",
+ message: "",
+ show: false,
+ hide: ()=>{}
+};
+
+
+const notificationReducer = (state) => {
+ return state;
+}
+
+const mainReducer = ({ type, message, show, hide }, action) => ({
+ type: notificationReducer(action.type),
+ message: notificationReducer(action.message),
+ show: notificationReducer(action.show),
+ hide: notificationReducer(action.hide)
+});
+
+
+const NotificationContextProvider: React.FC = (props) =>{
+ const [state, dispatch] = useReducer(mainReducer, NotificationContextValue);
+ return(
+ <NotificationContext.Provider value={{...state,dispatch}}>
+ {props.children}
+ </NotificationContext.Provider>
+ )
+ }
+
+ export { NotificationContextProvider };
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
index 16e51f4..c8e10d8 100644
--- a/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
+++ b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
@@ -25,6 +25,7 @@ import AccordionDetails from '@material-ui/core/AccordionDetails';
import Typography from '@material-ui/core/Typography';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import SearchBar from './SearchBar';
+import { FormControlLabel, Switch } from '@material-ui/core';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -33,7 +34,8 @@ const useStyles = makeStyles((theme: Theme) =>
borderBottom: '1px #BDCCD9 solid',
minHeight: '0 !important',
'& .MuiAccordionSummary-content.Mui-expanded':{
- margin: 0
+ margin: 0,
+ alignItems: 'center',
}
},
heading: {
@@ -45,6 +47,11 @@ const useStyles = makeStyles((theme: Theme) =>
details: {
flexDirection: 'column',
padding: '0'
+ },
+ formControl: {
+ marginRight: 0,
+ marginLeft: 'auto',
+ zoom: 0.85
}
}),
);
@@ -54,8 +61,13 @@ type Props = {
showSearchBox: boolean;
searchValue?: string;
handleSearch?: Function;
- recordCount?: number
+ recordCount?: number;
children: any;
+ accordionToggleObject?: {
+ toggleChangeHandler: (event: React.ChangeEvent<HTMLInputElement>) => void;
+ toggleName: string;
+ toggleValue: boolean;
+ }
};
export default function SimpleAccordion({
@@ -64,7 +76,8 @@ export default function SimpleAccordion({
searchValue,
handleSearch,
recordCount,
- children
+ children,
+ accordionToggleObject
}: Props) {
const classes = useStyles();
@@ -79,6 +92,20 @@ export default function SimpleAccordion({
className={classes.root}
>
<Typography className={classes.heading}>{`${headerTitle.toUpperCase()} ${recordCount !== undefined ? ` - (${recordCount})` : ''}`}</Typography>
+ {accordionToggleObject &&
+ <FormControlLabel
+ className={classes.formControl}
+ control={
+ <Switch
+ checked={accordionToggleObject.toggleValue}
+ onChange={accordionToggleObject.toggleChangeHandler}
+ name={accordionToggleObject.toggleName}
+ color="primary"
+ />
+ }
+ label={accordionToggleObject.toggleName}
+ />
+ }
</AccordionSummary>
<AccordionDetails className={classes.details}>
{showSearchBox ?
diff --git a/pinot-controller/src/main/resources/app/components/Table.tsx b/pinot-controller/src/main/resources/app/components/Table.tsx
index 9f021f5..9d4e01a 100644
--- a/pinot-controller/src/main/resources/app/components/Table.tsx
+++ b/pinot-controller/src/main/resources/app/components/Table.tsx
@@ -62,7 +62,12 @@ type Props = {
recordsCount?: number,
showSearchBox: boolean,
inAccordionFormat?: boolean,
- regexReplace?: boolean
+ regexReplace?: boolean,
+ accordionToggleObject?: {
+ toggleChangeHandler: (event: React.ChangeEvent<HTMLInputElement>) => void;
+ toggleName: string;
+ toggleValue: boolean;
+ }
};
const StyledTableRow = withStyles((theme) =>
@@ -251,7 +256,8 @@ export default function CustomizedTables({
recordsCount,
showSearchBox,
inAccordionFormat,
- regexReplace
+ regexReplace,
+ accordionToggleObject
}: Props) {
const [finalData, setFinalData] = React.useState(Utils.tableFormat(data));
@@ -261,7 +267,7 @@ export default function CustomizedTables({
const classes = useStyles();
const [rowsPerPage, setRowsPerPage] = React.useState(noOfRows || 10);
const [page, setPage] = React.useState(0);
-
+
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
@@ -305,6 +311,10 @@ export default function CustomizedTables({
};
}, [search, timeoutId, filterSearchResults]);
+ React.useCallback(()=>{
+ setFinalData(Utils.tableFormat(data));
+ }, [data]);
+
const styleCell = (str: string) => {
if (str === 'Good' || str.toLowerCase() === 'online' || str.toLowerCase() === 'alive') {
return (
@@ -457,6 +467,7 @@ export default function CustomizedTables({
searchValue={search}
handleSearch={(val: string) => setSearch(val)}
recordCount={recordsCount}
+ accordionToggleObject={accordionToggleObject}
>
{renderTableComponent()}
</SimpleAccordion>
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 b869125..e04234a 100644
--- a/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
+++ b/pinot-controller/src/main/resources/app/components/Zookeeper/TreeDirectory.tsx
@@ -32,7 +32,7 @@ import Confirm from '../Confirm';
import CustomCodemirror from '../CustomCodemirror';
import PinotMethodUtils from '../../utils/PinotMethodUtils';
import Utils from '../../utils/Utils';
-import CustomNotification from '../CustomNotification';
+import { NotificationContext } from '../Notification/NotificationContext';
const drawerWidth = 400;
@@ -118,8 +118,7 @@ const TreeDirectory = ({
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 {dispatch} = React.useContext(NotificationContext);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
@@ -133,6 +132,7 @@ const TreeDirectory = ({
if(!isLeafNodeSelected){
return;
}
+ newCodeMirrorData = JSON.stringify(currentNodeData);
setDialogTitle('Update Node Data');
setDialogContent(<CustomCodemirror
data={currentNodeData}
@@ -170,12 +170,19 @@ const TreeDirectory = ({
};
const result = await PinotMethodUtils.putNodeData(nodeData);
if(result.data.status){
- setNotificationData({type: 'success', message: result.data.status});
+ dispatch({
+ type: 'success',
+ message: result.data.status,
+ show: true
+ });
showInfoEvent(selectedNode);
} else {
- setNotificationData({type: 'error', message: result.data.error});
+ dispatch({
+ type: 'error',
+ message: result.data.error,
+ show: true
+ });
}
- setShowNotification(true);
closeDialog();
};
@@ -184,13 +191,20 @@ const TreeDirectory = ({
const treeObj = Utils.findNestedObj(treeData, 'fullPath', parentPath);
const result = await PinotMethodUtils.deleteNode(selectedNode);
if(result.data.status){
- setNotificationData({type: 'success', message: result.data.status});
+ dispatch({
+ type: 'success',
+ message: result.data.status,
+ show: true
+ });
showInfoEvent(selectedNode);
fetchInnerPath(treeObj);
} else {
- setNotificationData({type: 'error', message: result.data.error});
+ dispatch({
+ type: 'error',
+ message: result.data.error,
+ show: true
+ });
}
- setShowNotification(true);
closeDialog();
};
@@ -271,12 +285,6 @@ const TreeDirectory = ({
dialogYesLabel={dialogYesLabel}
dialogNoLabel={dialogNoLabel}
/>
- <CustomNotification
- type={notificationData.type}
- message={notificationData.message}
- show={showNotification}
- hide={()=>{setShowNotification(false)}}
- />
</>
);
};
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 02256fe..2acbc7e 100644
--- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts
+++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
@@ -21,6 +21,7 @@ declare module 'Models' {
export type TableData = {
records: Array<Array<string | number | boolean>>;
columns: Array<string>;
+ error?: string;
};
export type Tenants = {
@@ -77,6 +78,7 @@ declare module 'Models' {
dimensionFieldSpecs: Array<schema>;
metricFieldSpecs?: Array<schema>;
dateTimeFieldSpecs?: Array<schema>;
+ error?: string;
};
type schema = {
@@ -125,10 +127,14 @@ declare module 'Models' {
[name: string]: Array<string>
};
- export type BrokerList = Array<string>;
+ export type BrokerList = {
+ error?: string,
+ Array
+ };
export type ServerList = {
ServerInstances: Array<string>,
- tenantName: string
+ tenantName: string,
+ error: string
}
}
diff --git a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
index ae2f05d..cbbc423 100644
--- a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx
@@ -32,7 +32,7 @@ import SimpleAccordion from '../components/SimpleAccordion';
import CustomButton from '../components/CustomButton';
import EditTagsOp from '../components/Homepage/Operations/EditTagsOp';
import EditConfigOp from '../components/Homepage/Operations/EditConfigOp';
-import CustomNotification from '../components/CustomNotification';
+import { NotificationContext } from '../components/Notification/NotificationContext';
import _ from 'lodash';
import Confirm from '../components/Confirm';
@@ -91,8 +91,7 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
const [showEditTag, setShowEditTag] = useState(false);
const [showEditConfig, setShowEditConfig] = useState(false);
- const [notificationData, setNotificationData] = React.useState({type: '', message: ''});
- const [showNotification, setShowNotification] = React.useState(false);
+ const {dispatch} = React.useContext(NotificationContext);
const fetchData = async () => {
const configResponse = await PinotMethodUtils.getInstanceConfig(clutserName, instanceName);
@@ -198,12 +197,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
}
const result = await PinotMethodUtils.updateTags(instanceName, newTagsList);
if(result.status){
- setNotificationData({type: 'success', message: result.status});
+ dispatch({type: 'success', message: result.status, show: true});
fetchData();
} else {
- setNotificationData({type: 'error', message: result.error});
+ dispatch({type: 'error', message: result.error, show: true});
}
- setShowNotification(true);
setShowEditTag(false);
};
@@ -219,12 +217,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
const dropInstance = async () => {
const result = await PinotMethodUtils.deleteInstance(instanceName);
if(result.status){
- setNotificationData({type: 'success', message: result.status});
+ dispatch({type: 'success', message: result.status, show: true});
fetchData();
} else {
- setNotificationData({type: 'error', message: result.error});
+ dispatch({type: 'error', message: result.error, show: true});
}
- setShowNotification(true);
closeDialog();
};
@@ -240,12 +237,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
const toggleInstanceState = async () => {
const result = await PinotMethodUtils.toggleInstanceState(instanceName, state.enabled ? 'DISABLE' : 'ENABLE');
if(result.status){
- setNotificationData({type: 'success', message: result.status});
+ dispatch({type: 'success', message: result.status, show: true});
fetchData();
} else {
- setNotificationData({type: 'error', message: result.error});
+ dispatch({type: 'error', message: result.error, show: true});
}
- setShowNotification(true);
setState({ enabled: !state.enabled });
closeDialog();
};
@@ -258,12 +254,11 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
if(JSON.parse(config)){
const result = await PinotMethodUtils.updateInstanceDetails(instanceName, config);
if(result.status){
- setNotificationData({type: 'success', message: result.status});
+ dispatch({type: 'success', message: result.status, show: true});
fetchData();
} else {
- setNotificationData({type: 'error', message: result.error});
+ dispatch({type: 'error', message: result.error, show: true});
}
- setShowNotification(true);
setShowEditConfig(false);
}
};
@@ -297,6 +292,8 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
setTagsList(JSON.parse(instanceConfig)?.listFields?.TAG_LIST || []);
setShowEditTag(true);
}}
+ tooltipTitle="Edit Tags"
+ enableTooltip={true}
>
Edit Tags
</CustomButton>
@@ -305,10 +302,16 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
setConfig(instanceDetails);
setShowEditConfig(true);
}}
+ tooltipTitle="Edit Config"
+ enableTooltip={true}
>
Edit Config
</CustomButton>
- <CustomButton onClick={handleDropAction}>
+ <CustomButton
+ onClick={handleDropAction}
+ tooltipTitle="Drop"
+ enableTooltip={true}
+ >
Drop
</CustomButton>
<FormControlLabel
@@ -394,12 +397,6 @@ const InstanceDetails = ({ match }: RouteComponentProps<Props>) => {
dialogYesLabel='Yes'
dialogNoLabel='No'
/>}
- <CustomNotification
- type={notificationData.type}
- message={notificationData.message}
- show={showNotification}
- hide={()=>{setShowNotification(false)}}
- />
</Grid>
);
};
diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx b/pinot-controller/src/main/resources/app/pages/Query.tsx
index f304982..3ed7f89 100644
--- a/pinot-controller/src/main/resources/app/pages/Query.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Query.tsx
@@ -193,8 +193,9 @@ const QueryPage = () => {
};
const fetchSQLData = async (tableName) => {
- const result = await PinotMethodUtils.getTableSchemaData(tableName, false);
- setTableSchema(result);
+ const result = await PinotMethodUtils.getTableSchemaData(tableName);
+ const tableSchema = Utils.syncTableSchemaData(result, false);
+ setTableSchema(tableSchema);
const query = `select * from ${tableName} limit 10`;
setInputQuery(query);
diff --git a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
index 31c5a1a..c2f7065 100644
--- a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
@@ -33,7 +33,8 @@ import CustomizedTables from '../components/Table';
import PinotMethodUtils from '../utils/PinotMethodUtils';
import CustomButton from '../components/CustomButton';
import Confirm from '../components/Confirm';
-import CustomNotification from '../components/CustomNotification';
+import { NotificationContext } from '../components/Notification/NotificationContext';
+import Utils from '../utils/Utils';
const useStyles = makeStyles((theme) => ({
root: {
@@ -75,6 +76,7 @@ const jsonoptions = {
styleActiveLine: true,
gutters: ['CodeMirror-lint-markers'],
theme: 'default',
+ readOnly: true
};
type Props = {
@@ -98,8 +100,7 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
const [fetching, setFetching] = useState(true);
const [confirmDialog, setConfirmDialog] = React.useState(false);
const [dialogDetails, setDialogDetails] = React.useState(null);
- const [notificationData, setNotificationData] = React.useState({type: '', message: ''});
- const [showNotification, setShowNotification] = React.useState(false);
+ const {dispatch} = React.useContext(NotificationContext);
const [segmentSummary, setSegmentSummary] = useState<Summary>({
segmentName,
@@ -140,27 +141,18 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
const handleDeleteSegment = async () => {
const result = await PinotMethodUtils.deleteSegmentOp(tableName, segmentName);
- if(result.status){
- setNotificationData({type: 'success', message: result.status});
+ if(result && result.status){
+ dispatch({type: 'success', message: result.status, show: true});
fetchData();
} else {
- setNotificationData({type: 'error', message: result.error});
+ dispatch({type: 'error', message: result.error, show: true});
}
- setShowNotification(true);
closeDialog();
setTimeout(()=>{
- navigateToPreviousPage();
+ history.push(Utils.navigateToPreviousPage(location, false));
}, 1000);
};
- const navigateToPreviousPage = () => {
- const hasharr = location.pathname.split('/');
- hasharr.pop();
- const path = hasharr.join('/');
- console.log(path)
- history.push(path);
- };
-
const handleReloadSegmentClick = () => {
setDialogDetails({
title: 'Reload Segment',
@@ -173,12 +165,11 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
const handleReloadOp = async () => {
const result = await PinotMethodUtils.reloadSegmentOp(tableName, segmentName);
if(result.status){
- setNotificationData({type: 'success', message: result.status});
+ dispatch({type: 'success', message: result.status, show: true});
fetchData();
} else {
- setNotificationData({type: 'error', message: result.error});
+ dispatch({type: 'error', message: result.error, show: true});
}
- setShowNotification(true);
closeDialog();
}
@@ -201,10 +192,18 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
showSearchBox={false}
>
<div>
- <CustomButton onClick={()=>{handleDeleteSegmentClick()}}>
+ <CustomButton
+ onClick={()=>{handleDeleteSegmentClick()}}
+ tooltipTitle="Delete Segment"
+ enableTooltip={true}
+ >
Delete Segment
</CustomButton>
- <CustomButton onClick={()=>{handleReloadSegmentClick()}}>
+ <CustomButton
+ onClick={()=>{handleReloadSegmentClick()}}
+ tooltipTitle="Reload Segment"
+ enableTooltip={true}
+ >
Reload Segment
</CustomButton>
</div>
@@ -260,12 +259,6 @@ const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
dialogYesLabel='Yes'
dialogNoLabel='No'
/>}
- <CustomNotification
- type={notificationData.type}
- message={notificationData.message}
- show={showNotification}
- hide={()=>{setShowNotification(false)}}
- />
</Grid>
);
};
diff --git a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
index 2aa8e5e..4abdb31 100644
--- a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
@@ -38,30 +38,20 @@ const TablesListingPage = () => {
const classes = useStyles();
const [fetching, setFetching] = useState(true);
- const columnHeaders = ['Table Name', 'Tenant Name', 'Reported Size', 'Estimated Size', 'Number of Segments', 'Status'];
- const records = [];
const [tableData, setTableData] = useState<TableData>({
- columns: columnHeaders,
+ columns: [],
records: []
});
const fetchData = async () => {
- const tenantsDataResponse = await PinotMethodUtils.getTenantsData();
- const promiseArr = [];
- tenantsDataResponse.records.map((tenantRecord)=>{
- promiseArr.push(PinotMethodUtils.getTenantTableData(tenantRecord[0]));
- });
- Promise.all(promiseArr).then((results)=>{
- results.map((result, index)=>{
- const tenantName = tenantsDataResponse.records[index][0];
- records.push(...result.records.map((record)=>{
- record.splice(1, 0, tenantName);
- return record;
- }));
- });
- setTableData({columns: columnHeaders, records});
- setFetching(false);
+ const tablesResponse = await PinotMethodUtils.getQueryTablesList({bothType: true});
+ const tablesList = [];
+ tablesResponse.records.map((record)=>{
+ tablesList.push(...record);
});
+ const tableDetails = await PinotMethodUtils.getAllTableDetails(tablesList);
+ setTableData(tableDetails);
+ setFetching(false);
};
useEffect(() => {
diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
index 561a547..5e2e7e2 100644
--- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
@@ -17,10 +17,10 @@
* under the License.
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import { Grid } from '@material-ui/core';
-import { RouteComponentProps } from 'react-router-dom';
+import { FormControlLabel, Grid, Switch } from '@material-ui/core';
+import { RouteComponentProps, useHistory, useLocation } from 'react-router-dom';
import { UnControlled as CodeMirror } from 'react-codemirror2';
import { TableData } from 'Models';
import _ from 'lodash';
@@ -33,6 +33,13 @@ import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/sql/sql';
import SimpleAccordion from '../components/SimpleAccordion';
import PinotMethodUtils from '../utils/PinotMethodUtils';
+import CustomButton from '../components/CustomButton';
+import EditConfigOp from '../components/Homepage/Operations/EditConfigOp';
+import ReloadStatusOp from '../components/Homepage/Operations/ReloadStatusOp';
+import RebalanceServerTableOp from '../components/Homepage/Operations/RebalanceServerTableOp';
+import Confirm from '../components/Confirm';
+import { NotificationContext } from '../components/Notification/NotificationContext';
+import Utils from '../utils/Utils';
const useStyles = makeStyles((theme) => ({
root: {
@@ -61,6 +68,11 @@ const useStyles = makeStyles((theme) => ({
borderRadius: 4,
marginBottom: '20px',
},
+ operationDiv: {
+ border: '1px #BDCCD9 solid',
+ borderRadius: 4,
+ marginBottom: 20
+ }
}));
const jsonoptions = {
@@ -69,6 +81,7 @@ const jsonoptions = {
styleActiveLine: true,
gutters: ['CodeMirror-lint-markers'],
theme: 'default',
+ readOnly: true
};
type Props = {
@@ -86,6 +99,8 @@ type Summary = {
const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
const { tenantName, tableName, instanceName } = match.params;
const classes = useStyles();
+ const history = useHistory();
+ const location = useLocation();
const [fetching, setFetching] = useState(true);
const [tableSummary, setTableSummary] = useState<Summary>({
tableName: match.params.tableName,
@@ -93,6 +108,21 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
estimatedSize: '',
});
+ const [state, setState] = React.useState({
+ enabled: true,
+ });
+
+ const [confirmDialog, setConfirmDialog] = React.useState(false);
+ const [dialogDetails, setDialogDetails] = React.useState(null);
+ const {dispatch} = React.useContext(NotificationContext);
+
+ const [showEditConfig, setShowEditConfig] = useState(false);
+ const [config, setConfig] = useState('{}');
+ const [instanceCountData, setInstanceCountData] = useState<TableData>({
+ columns: [],
+ records: [],
+ });
+
const [segmentList, setSegmentList] = useState<TableData>({
columns: [],
records: [],
@@ -102,9 +132,17 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
columns: [],
records: [],
});
- const [value, setValue] = useState('');
+ const [tableType, setTableType] = useState('');
+ const [tableConfig, setTableConfig] = useState('');
+ const [schemaJSON, setSchemaJSON] = useState(null);
+ const [actionType, setActionType] = useState(null);
+ const [showReloadStatusModal, setShowReloadStatusModal] = useState(false);
+ const [reloadStatusData, setReloadStatusData] = useState(null);
+ const [showRebalanceServerModal, setShowRebalanceServerModal] = useState(false);
+ const [schemaJSONFormat, setSchemaJSONFormat] = useState(false);
const fetchTableData = async () => {
+ setFetching(true);
const result = await PinotMethodUtils.getTableSummaryData(tableName);
setTableSummary(result);
fetchSegmentData();
@@ -112,24 +150,193 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
const fetchSegmentData = async () => {
const result = await PinotMethodUtils.getSegmentList(tableName);
- setSegmentList(result);
+ const {columns, records, externalViewObj} = result;
+ const instanceObj = {};
+ Object.keys(externalViewObj).map((segmentName)=>{
+ const instanceKeys = Object.keys(externalViewObj[segmentName]);
+ instanceKeys.map((instanceName)=>{
+ if(!instanceObj[instanceName]){
+ instanceObj[instanceName] = 0;
+ }
+ instanceObj[instanceName] += 1;
+ })
+ });
+ const instanceRecords = [];
+ Object.keys(instanceObj).map((instanceName)=>{
+ instanceRecords.push([instanceName, instanceObj[instanceName]]);
+ })
+ setInstanceCountData({
+ columns: ["Instance Name", "# of segments"],
+ records: instanceRecords
+ });
+ setSegmentList({columns, records});
fetchTableSchema();
};
const fetchTableSchema = async () => {
- const result = await PinotMethodUtils.getTableSchemaData(tableName, true);
- setTableSchema(result);
+ const result = await PinotMethodUtils.getTableSchemaData(tableName);
+ if(result.error){
+ setSchemaJSON(null);
+ setTableSchema({
+ columns: ['Column', 'Type', 'Field Type'],
+ records: []
+ });
+ } else {
+ setSchemaJSON(JSON.parse(JSON.stringify(result)));
+ const tableSchema = Utils.syncTableSchemaData(result, true);
+ setTableSchema(tableSchema);
+ }
fetchTableJSON();
};
const fetchTableJSON = async () => {
const result = await PinotMethodUtils.getTableDetails(tableName);
- setValue(JSON.stringify(result, null, 2));
+ const tableObj:any = result.OFFLINE || result.REALTIME;
+ setTableType(tableObj.tableType);
+ setTableConfig(JSON.stringify(result, null, 2));
setFetching(false);
};
useEffect(() => {
fetchTableData();
}, []);
+
+ const handleSwitchChange = (event) => {
+ setDialogDetails({
+ title: state.enabled ? 'Disable Table' : 'Enable Table',
+ content: `Are you sure want to ${state.enabled ? 'disable' : 'enable'} this table?`,
+ successCb: () => toggleTableState()
+ });
+ setConfirmDialog(true);
+ };
+
+ const toggleTableState = async () => {
+ const result = await PinotMethodUtils.toggleTableState(tableName, state.enabled ? 'disable' : 'enable', tableType.toLowerCase());
+ if(result[0].state){
+ if(result[0].state.successful){
+ dispatch({type: 'success', message: result[0].state.message, show: true});
+ setState({ enabled: !state.enabled });
+ fetchTableData();
+ } else {
+ dispatch({type: 'error', message: result[0].state.message, show: true});
+ }
+ closeDialog();
+ } else {
+ syncResponse(result);
+ }
+ };
+
+ const handleConfigChange = (value: string) => {
+ setConfig(value);
+ };
+
+ const saveConfigAction = async () => {
+ let configObj = JSON.parse(config);
+ if(actionType === 'editTable'){
+ if(configObj.OFFLINE || configObj.REALTIME){
+ configObj = configObj.OFFLINE || configObj.REALTIME;
+ }
+ const result = await PinotMethodUtils.updateTable(tableName, configObj);
+ syncResponse(result);
+ } else if(actionType === 'editSchema'){
+ const result = await PinotMethodUtils.updateSchema(schemaJSON.schemaName, configObj);
+ syncResponse(result);
+ }
+ };
+
+ const syncResponse = (result) => {
+ if(result.status){
+ dispatch({type: 'success', message: result.status, show: true});
+ fetchTableData();
+ setShowEditConfig(false);
+ } else {
+ dispatch({type: 'error', message: result.error, show: true});
+ }
+ closeDialog();
+ };
+
+ const handleDeleteTableAction = () => {
+ setDialogDetails({
+ title: 'Delete Table',
+ content: 'Are you sure want to delete this table? All data and configs will be deleted.',
+ successCb: () => deleteTable()
+ });
+ setConfirmDialog(true);
+ };
+
+ const deleteTable = async () => {
+ const result = await PinotMethodUtils.deleteTableOp(tableName);
+ if(result.status){
+ dispatch({type: 'success', message: result.status, show: true});
+ } else {
+ dispatch({type: 'error', message: result.error, show: true});
+ }
+ closeDialog();
+ if(result.status){
+ setTimeout(()=>{
+ history.push(Utils.navigateToPreviousPage(location, true));
+ }, 1000);
+ }
+ };
+
+ const handleDeleteSchemaAction = () => {
+ setDialogDetails({
+ title: 'Delete Schema',
+ content: 'Are you sure want to delete this schema? Any tables using this schema might not function correctly.',
+ successCb: () => deleteSchema()
+ });
+ setConfirmDialog(true);
+ };
+
+ const deleteSchema = async () => {
+ const result = await PinotMethodUtils.deleteSchemaOp(schemaJSON.schemaName);
+ syncResponse(result);
+ };
+
+ const handleReloadSegments = () => {
+ setDialogDetails({
+ title: 'Reload all segments',
+ content: 'Are you sure want to reload all the segments?',
+ successCb: () => reloadSegments()
+ });
+ setConfirmDialog(true);
+ };
+
+ const reloadSegments = async () => {
+ const result = await PinotMethodUtils.reloadAllSegmentsOp(tableName, tableType);
+ syncResponse(result);
+ };
+
+ const handleReloadStatus = async () => {
+ setShowReloadStatusModal(true);
+ const result = await PinotMethodUtils.reloadStatusOp(tableName, tableType);
+ if(result.error){
+ setShowReloadStatusModal(false);
+ dispatch({type: 'error', message: result.error, show: true});
+ setShowReloadStatusModal(false);
+ return;
+ }
+ setReloadStatusData(result);
+ };
+
+ const handleRebalanceBrokers = () => {
+ setDialogDetails({
+ title: 'Rebalance brokers',
+ content: 'Are you sure want to rebalance the brokers?',
+ successCb: () => rebalanceBrokers()
+ });
+ setConfirmDialog(true);
+ };
+
+ const rebalanceBrokers = async () => {
+ const result = await PinotMethodUtils.rebalanceBrokersForTableOp(tableName);
+ syncResponse(result);
+ };
+
+ const closeDialog = () => {
+ setConfirmDialog(false);
+ setDialogDetails(null);
+ };
+
return fetching ? (
<AppLoader />
) : (
@@ -143,6 +350,98 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
overflowY: 'auto',
}}
>
+ <div className={classes.operationDiv}>
+ <SimpleAccordion
+ headerTitle="Operations"
+ showSearchBox={false}
+ >
+ <div>
+ <CustomButton
+ onClick={()=>{
+ setActionType('editTable');
+ setConfig(tableConfig);
+ setShowEditConfig(true);
+ }}
+ tooltipTitle="Edit Table"
+ enableTooltip={true}
+ >
+ Edit Table
+ </CustomButton>
+ <CustomButton
+ onClick={handleDeleteTableAction}
+ tooltipTitle="Delete Table"
+ enableTooltip={true}
+ >
+ Delete Table
+ </CustomButton>
+ <CustomButton
+ onClick={()=>{
+ setActionType('editSchema');
+ setConfig(JSON.stringify(schemaJSON, null, 2));
+ setShowEditConfig(true);
+ }}
+ tooltipTitle="Edit Schema"
+ enableTooltip={true}
+ >
+ Edit Schema
+ </CustomButton>
+ <CustomButton
+ isDisabled={!schemaJSON} onClick={handleDeleteSchemaAction}
+ tooltipTitle="Delete Schema"
+ enableTooltip={true}
+ >
+ Delete Schema
+ </CustomButton>
+ <CustomButton
+ isDisabled={true} onClick={()=>{console.log('truncate table');}}
+ // tooltipTitle="Truncate Table"
+ // enableTooltip={true}
+ >
+ Truncate Table
+ </CustomButton>
+ <CustomButton
+ onClick={handleReloadSegments}
+ tooltipTitle="Reload All Segments"
+ enableTooltip={true}
+ >
+ Reload All Segments
+ </CustomButton>
+ <CustomButton
+ onClick={handleReloadStatus}
+ tooltipTitle="Reload Status"
+ enableTooltip={true}
+ >
+ Reload Status
+ </CustomButton>
+ <CustomButton
+ onClick={()=>{setShowRebalanceServerModal(true);}}
+ tooltipTitle="Rebalance Servers"
+ enableTooltip={true}
+ >
+ Rebalance Servers
+ </CustomButton>
+ <CustomButton
+ onClick={handleRebalanceBrokers}
+ tooltipTitle="Rebalance Brokers"
+ enableTooltip={true}
+ >
+ Rebalance Brokers
+ </CustomButton>
+ <FormControlLabel
+ control={
+ <Switch
+ checked={state.enabled}
+ onChange={handleSwitchChange}
+ name="enabled"
+ color="primary"
+ disabled={true}
+ />
+ }
+ label="Enable"
+ />
+ </div>
+ </SimpleAccordion>
+ </div>
<div className={classes.highlightBackground}>
<TableToolbar name="Summary" showSearchBox={false} />
<Grid container className={classes.body}>
@@ -168,7 +467,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
>
<CodeMirror
options={jsonoptions}
- value={value}
+ value={tableConfig}
className={classes.queryOutput}
autoCursor={false}
/>
@@ -180,23 +479,88 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
isPagination={false}
noOfRows={segmentList.records.length}
baseURL={
- tenantName ? `/tenants/${tenantName}/table/${tableName}/` : `/instance/${instanceName}/table/${tableName}/`}
+ tenantName ? `/tenants/${tenantName}/table/${tableName}/` : `/instance/${instanceName}/table/${tableName}/`
+ }
addLinks
showSearchBox={true}
inAccordionFormat={true}
/>
</Grid>
<Grid item xs={6}>
+ {!schemaJSONFormat ?
+ <CustomizedTables
+ title="Table Schema"
+ data={tableSchema}
+ isPagination={false}
+ noOfRows={tableSchema.records.length}
+ showSearchBox={true}
+ inAccordionFormat={true}
+ accordionToggleObject={{
+ toggleName: "JSON Format",
+ toggleValue: schemaJSONFormat,
+ toggleChangeHandler: ()=>{setSchemaJSONFormat(!schemaJSONFormat);}
+ }}
+ />
+ :
+ <div className={classes.sqlDiv}>
+ <SimpleAccordion
+ headerTitle="Table Schema"
+ showSearchBox={false}
+ accordionToggleObject={{
+ toggleName: "JSON Format",
+ toggleValue: schemaJSONFormat,
+ toggleChangeHandler: ()=>{setSchemaJSONFormat(!schemaJSONFormat);}
+ }}
+ >
+ <CodeMirror
+ options={jsonoptions}
+ value={JSON.stringify(schemaJSON, null, 2)}
+ className={classes.queryOutput}
+ autoCursor={false}
+ />
+ </SimpleAccordion>
+ </div>
+ }
<CustomizedTables
- title="Table Schema"
- data={tableSchema}
+ title="Instance Count"
+ data={instanceCountData}
isPagination={false}
- noOfRows={tableSchema.records.length}
+ noOfRows={instanceCountData.records.length}
showSearchBox={true}
inAccordionFormat={true}
/>
</Grid>
</Grid>
+ <EditConfigOp
+ showModal={showEditConfig}
+ hideModal={()=>{setShowEditConfig(false);}}
+ saveConfig={saveConfigAction}
+ config={config}
+ handleConfigChange={handleConfigChange}
+ />
+ {
+ showReloadStatusModal &&
+ <ReloadStatusOp
+ hideModal={()=>{setShowReloadStatusModal(false); setReloadStatusData(null)}}
+ data={reloadStatusData}
+ />
+ }
+ {showRebalanceServerModal &&
+ <RebalanceServerTableOp
+ hideModal={()=>{setShowRebalanceServerModal(false)}}
+ tableType={tableType.toUpperCase()}
+ tableName={tableName}
+ />
+ }
+ {confirmDialog && dialogDetails && <Confirm
+ openDialog={confirmDialog}
+ dialogTitle={dialogDetails.title}
+ dialogContent={dialogDetails.content}
+ successCallback={dialogDetails.successCb}
+ closeDialog={closeDialog}
+ dialogYesLabel='Yes'
+ dialogNoLabel='No'
+ />}
</Grid>
);
};
diff --git a/pinot-controller/src/main/resources/app/pages/Tenants.tsx b/pinot-controller/src/main/resources/app/pages/Tenants.tsx
index 9c87678..33286de 100644
--- a/pinot-controller/src/main/resources/app/pages/Tenants.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Tenants.tsx
@@ -18,12 +18,22 @@
*/
import React, { useState, useEffect } from 'react';
-import { Grid } from '@material-ui/core';
+import { Grid, makeStyles } from '@material-ui/core';
import { TableData } from 'Models';
import { RouteComponentProps } from 'react-router-dom';
import CustomizedTables from '../components/Table';
import AppLoader from '../components/AppLoader';
import PinotMethodUtils from '../utils/PinotMethodUtils';
+import SimpleAccordion from '../components/SimpleAccordion';
+import CustomButton from '../components/CustomButton';
+
+const useStyles = makeStyles((theme) => ({
+ operationDiv: {
+ border: '1px #BDCCD9 solid',
+ borderRadius: 4,
+ marginBottom: 20
+ }
+}));
type Props = {
tenantName: string
@@ -31,14 +41,14 @@ type Props = {
const TenantPage = ({ match }: RouteComponentProps<Props>) => {
- const tenantName = match.params.tenantName;
+ const {tenantName} = match.params;
const columnHeaders = ['Table Name', 'Reported Size', 'Estimated Size', 'Number of Segments', 'Status'];
const [fetching, setFetching] = useState(true);
const [tableData, setTableData] = useState<TableData>({
columns: columnHeaders,
records: []
});
- const [brokerData, setBrokerData] = useState([]);
+ const [brokerData, setBrokerData] = useState(null);
const [serverData, setServerData] = useState([]);
const fetchData = async () => {
@@ -46,16 +56,44 @@ const TenantPage = ({ match }: RouteComponentProps<Props>) => {
const brokersData = await PinotMethodUtils.getBrokerOfTenant(tenantName);
const serversData = await PinotMethodUtils.getServerOfTenant(tenantName);
setTableData(tenantData);
- setBrokerData(brokersData);
- setServerData(serversData);
+ setBrokerData(brokersData || []);
+ setServerData(serversData || []);
setFetching(false);
};
useEffect(() => {
fetchData();
}, []);
+
+ const classes = useStyles();
+
return (
fetching ? <AppLoader /> :
<Grid item xs style={{ padding: 20, backgroundColor: 'white', maxHeight: 'calc(100vh - 70px)', overflowY: 'auto' }}>
+ <div className={classes.operationDiv}>
+ <SimpleAccordion
+ headerTitle="Operations"
+ showSearchBox={false}
+ >
+ <div>
+ <CustomButton
+ onClick={()=>{console.log('rebalance');}}
+ // tooltipTitle="Rebalance Server Tenant - Coming soon"
+ // enableTooltip={true}
+ isDisabled={true}
+ >
+ Rebalance Server Tenant
+ </CustomButton>
+ <CustomButton
+ onClick={()=>{console.log('rebuild');}}
+ // tooltipTitle="Rebuild Broker Resource - Coming soon"
+ // enableTooltip={true}
+ isDisabled={true}
+ >
+ Rebuild Broker Resource
+ </CustomButton>
+ </div>
+ </SimpleAccordion>
+ </div>
<CustomizedTables
title={tenantName}
data={tableData}
@@ -71,11 +109,11 @@ const TenantPage = ({ match }: RouteComponentProps<Props>) => {
title="Brokers"
data={{
columns: ['Instance Name'],
- records: [brokerData]
+ records: brokerData.length > 0 ? [brokerData] : []
}}
isPagination
addLinks
- baseURL={'/instance/'}
+ baseURL="/instance/"
showSearchBox={true}
inAccordionFormat={true}
/>
@@ -85,11 +123,11 @@ const TenantPage = ({ match }: RouteComponentProps<Props>) => {
title="Servers"
data={{
columns: ['Instance Name'],
- records: [serverData]
+ records: serverData.length > 0 ? [serverData] : []
}}
isPagination
addLinks
- baseURL={'/instance/'}
+ baseURL="/instance/"
showSearchBox={true}
inAccordionFormat={true}
/>
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts b/pinot-controller/src/main/resources/app/requests/index.ts
index ade67a1..03e7a41 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -42,6 +42,12 @@ export const getTenantTable = (name: string): Promise<AxiosResponse<TableName>>
export const getTenantTableDetails = (tableName: string): Promise<AxiosResponse<IdealState>> =>
baseApi.get(`/tables/${tableName}`);
+export const putTable = (name: string, params: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.put(`/tables/${name}`, params, { headers });
+
+export const putSchema = (name: string, params: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.put(`/schemas/${name}`, params, { headers });
+
export const getSegmentMetadata = (tableName: string, segmentName: string): Promise<AxiosResponse<IdealState>> =>
baseApi.get(`/segments/${tableName}/${segmentName}/metadata`);
@@ -69,6 +75,9 @@ export const updateInstanceTags = (name: string, params: string): Promise<AxiosR
export const setInstanceState = (name: string, stateName: string): Promise<AxiosResponse<OperationResponse>> =>
baseApi.post(`/instances/${name}/state`, stateName, { headers: {'Content-Type': 'text/plain', 'Accept': 'application/json'} });
+export const setTableState = (name: string, stateName: string, tableType: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.get(`/tables/${name}?state=${stateName}&type=${tableType}`);
+
export const dropInstance = (name: string): Promise<AxiosResponse<OperationResponse>> =>
baseApi.delete(`instances/${name}`, { headers });
@@ -114,5 +123,23 @@ export const getServerListOfTenant = (name: string): Promise<AxiosResponse<Serve
export const reloadSegment = (tableName: string, instanceName: string): Promise<AxiosResponse<OperationResponse>> =>
baseApi.post(`/segments/${tableName}/${instanceName}/reload`, null, {headers});
+export const reloadAllSegments = (tableName: string, tableType: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.post(`/segments/${tableName}/reload?type=${tableType}`, null, {headers});
+
+export const reloadStatus = (tableName: string, tableType: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.get(`/segments/${tableName}/metadata?type=${tableType}`);
+
export const deleteSegment = (tableName: string, instanceName: string): Promise<AxiosResponse<OperationResponse>> =>
- baseApi.delete(`/segments/${tableName}/${instanceName}`, {headers});
\ No newline at end of file
+ baseApi.delete(`/segments/${tableName}/${instanceName}`, {headers});
+
+export const deleteTable = (tableName: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.delete(`/tables/${tableName}`, {headers});
+
+export const deleteSchema = (schemaName: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.delete(`/schemas/${schemaName}`, {headers});
+
+export const rebalanceServersForTable = (tableName: string, qParams: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.post(`/tables/${tableName}/rebalance?${qParams}`, null, {headers});
+
+export const rebalanceBrokersForTable = (tableName: string): Promise<AxiosResponse<OperationResponse>> =>
+ baseApi.post(`/tables/${tableName}/rebuildBrokerResourceFromHelixTags`, null, {headers});
\ 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 f279284..e733401 100644
--- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
+++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
@@ -26,6 +26,7 @@ import {
getInstance,
putInstance,
setInstanceState,
+ setTableState,
dropInstance,
updateInstanceTags,
getClusterConfig,
@@ -48,7 +49,15 @@ import {
zookeeperDeleteNode,
getBrokerListOfTenant,
getServerListOfTenant,
- deleteSegment
+ deleteSegment,
+ putTable,
+ putSchema,
+ deleteTable,
+ deleteSchema,
+ reloadAllSegments,
+ reloadStatus,
+ rebalanceServersForTable,
+ rebalanceBrokersForTable
} from '../requests';
import Utils from './Utils';
@@ -173,39 +182,9 @@ const getQueryTablesList = ({bothType = false}) => {
// This method is used to display particular table schema on query page
// API: /tables/:tableName/schema
-// Expected Output: {columns: [], records: []}
-const getTableSchemaData = (tableName, showFieldType) => {
+const getTableSchemaData = (tableName) => {
return getTableSchema(tableName).then(({ data }) => {
- const dimensionFields = data.dimensionFieldSpecs || [];
- const metricFields = data.metricFieldSpecs || [];
- const dateTimeField = data.dateTimeFieldSpecs || [];
-
- dimensionFields.map((field) => {
- field.fieldType = 'Dimension';
- });
-
- metricFields.map((field) => {
- field.fieldType = 'Metric';
- });
-
- dateTimeField.map((field) => {
- field.fieldType = 'Date-Time';
- });
- const columnList = [...dimensionFields, ...metricFields, ...dateTimeField];
- if (showFieldType) {
- return {
- columns: ['column', 'type', 'Field Type'],
- records: columnList.map((field) => {
- return [field.name, field.dataType, field.fieldType];
- }),
- };
- }
- return {
- columns: ['column', 'type'],
- records: columnList.map((field) => {
- return [field.name, field.dataType];
- }),
- };
+ return data;
});
};
@@ -317,6 +296,13 @@ const getQueryResults = (params, url, checkedOptions) => {
// /tables/:tableName/externalview
// Expected Output: {columns: [], records: []}
const getTenantTableData = (tenantName) => {
+ return getTenantTable(tenantName).then(({ data }) => {
+ const tableArr = data.tables.map((table) => table);
+ return getAllTableDetails(tableArr);
+ });
+};
+
+const getAllTableDetails = (tablesList) => {
const columnHeaders = [
'Table Name',
'Reported Size',
@@ -324,67 +310,64 @@ const getTenantTableData = (tenantName) => {
'Number of Segments',
'Status',
];
- return getTenantTable(tenantName).then(({ data }) => {
- const tableArr = data.tables.map((table) => table);
- if (tableArr.length) {
- const promiseArr = [];
- tableArr.map((name) => {
- promiseArr.push(getTableSize(name));
- promiseArr.push(getIdealState(name));
- promiseArr.push(getExternalView(name));
- });
+ if (tablesList.length) {
+ const promiseArr = [];
+ tablesList.map((name) => {
+ promiseArr.push(getTableSize(name));
+ promiseArr.push(getIdealState(name));
+ promiseArr.push(getExternalView(name));
+ });
- return Promise.all(promiseArr).then((results) => {
- const finalRecordsArr = [];
- let singleTableData = [];
- let idealStateObj = null;
- let externalViewObj = null;
- results.map((result, index) => {
- // since we have 3 promises, we are using mod 3 below
- if (index % 3 === 0) {
- // response of getTableSize API
- const {
- tableName,
- reportedSizeInBytes,
- estimatedSizeInBytes,
- } = result.data;
- singleTableData.push(
- tableName,
- reportedSizeInBytes,
- estimatedSizeInBytes
- );
- } else if (index % 3 === 1) {
- // response of getIdealState API
- idealStateObj = result.data.OFFLINE || result.data.REALTIME;
- } else if (index % 3 === 2) {
- // response of getExternalView API
- externalViewObj = result.data.OFFLINE || result.data.REALTIME;
- const externalSegmentCount = Object.keys(externalViewObj).length;
- const idealSegmentCount = Object.keys(idealStateObj).length;
- // Generating data for the record
- singleTableData.push(
- `${externalSegmentCount} / ${idealSegmentCount}`,
- Utils.getSegmentStatus(idealStateObj, externalViewObj)
- );
- // saving into records array
- finalRecordsArr.push(singleTableData);
- // resetting the required variables
- singleTableData = [];
- idealStateObj = null;
- externalViewObj = null;
- }
- });
- return {
- columns: columnHeaders,
- records: finalRecordsArr,
- };
+ return Promise.all(promiseArr).then((results) => {
+ const finalRecordsArr = [];
+ let singleTableData = [];
+ let idealStateObj = null;
+ let externalViewObj = null;
+ results.map((result, index) => {
+ // since we have 3 promises, we are using mod 3 below
+ if (index % 3 === 0) {
+ // response of getTableSize API
+ const {
+ tableName,
+ reportedSizeInBytes,
+ estimatedSizeInBytes,
+ } = result.data;
+ singleTableData.push(
+ tableName,
+ reportedSizeInBytes,
+ estimatedSizeInBytes
+ );
+ } else if (index % 3 === 1) {
+ // response of getIdealState API
+ idealStateObj = result.data.OFFLINE || result.data.REALTIME;
+ } else if (index % 3 === 2) {
+ // response of getExternalView API
+ externalViewObj = result.data.OFFLINE || result.data.REALTIME;
+ const externalSegmentCount = Object.keys(externalViewObj).length;
+ const idealSegmentCount = Object.keys(idealStateObj).length;
+ // Generating data for the record
+ singleTableData.push(
+ `${externalSegmentCount} / ${idealSegmentCount}`,
+ Utils.getSegmentStatus(idealStateObj, externalViewObj)
+ );
+ // saving into records array
+ finalRecordsArr.push(singleTableData);
+ // resetting the required variables
+ singleTableData = [];
+ idealStateObj = null;
+ externalViewObj = null;
+ }
});
- }
- return {
- columns: columnHeaders,
- records: []
- };
- });
+ return {
+ columns: columnHeaders,
+ records: finalRecordsArr,
+ };
+ });
+ }
+ return {
+ columns: columnHeaders,
+ records: []
+ };
};
// This method is used to display summary of a particular tenant table
@@ -403,7 +386,7 @@ const getTableSummaryData = (tableName) => {
// This method is used to display segment list of a particular tenant table
// API: /tables/:tableName/idealstate
// /tables/:tableName/externalview
-// Expected Output: {columns: [], records: []}
+// Expected Output: {columns: [], records: [], externalViewObject: {}}
const getSegmentList = (tableName) => {
const promiseArr = [];
promiseArr.push(getIdealState(tableName));
@@ -421,6 +404,7 @@ const getSegmentList = (tableName) => {
_.isEqual(idealStateObj[key], externalViewObj[key]) ? 'Good' : 'Bad',
];
}),
+ externalViewObj
};
});
};
@@ -578,13 +562,13 @@ const deleteNode = (path) => {
const getBrokerOfTenant = (tenantName) => {
return getBrokerListOfTenant(tenantName).then((response)=>{
- return response.data;
+ return !response.data.error ? response.data : [];
});
};
const getServerOfTenant = (tenantName) => {
return getServerListOfTenant(tenantName).then((response)=>{
- return response.data.ServerInstances;
+ return !response.data.error ? response.data.ServerInstances : [];
});
};
@@ -600,6 +584,12 @@ const toggleInstanceState = (instanceName, state) => {
});
};
+const toggleTableState = (tableName, state, tableType) => {
+ return setTableState(tableName, state, tableType).then((response)=>{
+ return response.data;
+ });
+};
+
const deleteInstance = (instanceName) => {
return dropInstance(instanceName).then((response)=>{
return response.data;
@@ -612,12 +602,61 @@ const reloadSegmentOp = (tableName, segmentName) => {
});
};
+const reloadAllSegmentsOp = (tableName, tableType) => {
+ return reloadAllSegments(tableName, tableType).then((response)=>{
+ return response.data;
+ });
+};
+
+const reloadStatusOp = (tableName, tableType) => {
+ return reloadStatus(tableName, tableType).then((response)=>{
+ return response.data;
+ });
+}
+
const deleteSegmentOp = (tableName, segmentName) => {
return deleteSegment(tableName, segmentName).then((response)=>{
return response.data;
});
};
+const updateTable = (tableName: string, table: string) => {
+ return putTable(tableName, table).then((res)=>{
+ return res.data;
+ })
+};
+
+const updateSchema = (schemaName: string, schema: string) => {
+ return putSchema(schemaName, schema).then((res)=>{
+ return res.data;
+ })
+};
+
+const deleteTableOp = (tableName) => {
+ return deleteTable(tableName).then((response)=>{
+ return response.data;
+ });
+};
+
+const deleteSchemaOp = (tableName) => {
+ return deleteSchema(tableName).then((response)=>{
+ return response.data;
+ });
+};
+
+const rebalanceServersForTableOp = (tableName, queryParams) => {
+ const q_params = Utils.serialize(queryParams);
+ return rebalanceServersForTable(tableName, q_params).then((response)=>{
+ return response.data;
+ });
+};
+
+const rebalanceBrokersForTableOp = (tableName) => {
+ return rebalanceBrokersForTable(tableName).then((response)=>{
+ return response.data;
+ });
+};
+
export default {
getTenantsData,
getAllInstances,
@@ -627,6 +666,7 @@ export default {
getTableSchemaData,
getQueryResults,
getTenantTableData,
+ getAllTableDetails,
getTableSummaryData,
getSegmentList,
getTableDetails,
@@ -645,7 +685,16 @@ export default {
getServerOfTenant,
updateTags,
toggleInstanceState,
+ toggleTableState,
deleteInstance,
deleteSegmentOp,
- reloadSegmentOp
+ reloadSegmentOp,
+ reloadStatusOp,
+ reloadAllSegmentsOp,
+ updateTable,
+ updateSchema,
+ deleteTableOp,
+ deleteSchemaOp,
+ rebalanceServersForTableOp,
+ rebalanceBrokersForTableOp
};
diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx b/pinot-controller/src/main/resources/app/utils/Utils.tsx
index aaf698e..0b77e5f 100644
--- a/pinot-controller/src/main/resources/app/utils/Utils.tsx
+++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx
@@ -242,11 +242,55 @@ const serialize = (obj: any, prefix?: any) => {
return str.join("&");
};
+const navigateToPreviousPage = (location, popTwice) => {
+ const hasharr = location.pathname.split('/');
+ hasharr.pop();
+ if(popTwice){
+ hasharr.pop();
+ }
+ return hasharr.join('/');
+};
+
+const syncTableSchemaData = (data, showFieldType) => {
+ const dimensionFields = data.dimensionFieldSpecs || [];
+ const metricFields = data.metricFieldSpecs || [];
+ const dateTimeField = data.dateTimeFieldSpecs || [];
+
+ dimensionFields.map((field) => {
+ field.fieldType = 'Dimension';
+ });
+
+ metricFields.map((field) => {
+ field.fieldType = 'Metric';
+ });
+
+ dateTimeField.map((field) => {
+ field.fieldType = 'Date-Time';
+ });
+ const columnList = [...dimensionFields, ...metricFields, ...dateTimeField];
+ if (showFieldType) {
+ return {
+ columns: ['Column', 'Type', 'Field Type'],
+ records: columnList.map((field) => {
+ return [field.name, field.dataType, field.fieldType];
+ }),
+ };
+ }
+ return {
+ columns: ['Column', 'Type'],
+ records: columnList.map((field) => {
+ return [field.name, field.dataType];
+ }),
+ };
+};
+
export default {
sortArray,
tableFormat,
getSegmentStatus,
findNestedObj,
generateCodeMirrorOptions,
- serialize
+ serialize,
+ navigateToPreviousPage,
+ syncTableSchemaData
};
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org