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

yashmayya pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new a94380a492 Render new UI options for rebalance server operation 
(#15256)
a94380a492 is described below

commit a94380a49234bb8ba0903bfbdf0dadb110d73cfb
Author: Soumya Himanish Mohapatra 
<[email protected]>
AuthorDate: Wed Mar 19 10:52:52 2025 +0530

    Render new UI options for rebalance server operation (#15256)
---
 .../main/resources/app/components/CustomDialog.tsx |  34 +++-
 .../RebalanceServerConfigurationOption.tsx         |  43 +++++
 .../RebalanceServerConfigurationOptionInteger.tsx  |  57 ++++++
 .../RebalanceServerConfigurationOptionLabel.tsx    |  38 ++++
 .../RebalanceServerConfigurationOptionSelect.tsx   |  63 +++++++
 ...alanceServerConfigurationOptionToggleSwitch.tsx |  54 ++++++
 .../RebalanceServerConfigurationSection.tsx        |  68 +++++++
 .../RebalanceServerDialogHeader.tsx                |  43 +++++
 .../RebalanceServer/RebalanceServerOptions.ts      | 168 +++++++++++++++++
 .../Homepage/Operations/RebalanceServerTableOp.tsx | 200 ++++++++++-----------
 10 files changed, 650 insertions(+), 118 deletions(-)

diff --git 
a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx 
b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
index 60815d8e03..0e5fcc6fb4 100644
--- a/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
+++ b/pinot-controller/src/main/resources/app/components/CustomDialog.tsx
@@ -17,8 +17,17 @@
  * under the License.
  */
 
-import React from 'react';
-import { Button, Dialog, DialogActions, DialogContent, DialogTitle, 
makeStyles, withStyles } from '@material-ui/core';
+import React, {ReactNode} from 'react';
+import {
+  Button,
+  Dialog,
+  DialogActions,
+  DialogContent,
+  DialogTitle,
+  Divider,
+  makeStyles,
+  withStyles
+} from '@material-ui/core';
 import { red } from '@material-ui/core/colors';
 
 const useStyles = makeStyles((theme) => ({
@@ -27,7 +36,7 @@ const useStyles = makeStyles((theme) => ({
       minWidth: '600px'
     },
     "& .MuiDialogContent-root": {
-      padding: 8
+      padding: '10px 24px',
     }
   },
   dialogTitle: {
@@ -56,8 +65,11 @@ type Props = {
   showCancelBtn?: boolean,
   showOkBtn?: boolean,
   size?: false | "xs" | "sm" | "md" | "lg" | "xl",
-  disableBackdropClick?: boolean
-  disableEscapeKeyDown?: boolean
+  disableBackdropClick?: boolean,
+  disableEscapeKeyDown?: boolean,
+  showTitleDivider?: boolean,
+  showFooterDivider?: boolean,
+  moreActions?: ReactNode
 };
 
 export default function CustomDialog({
@@ -72,7 +84,10 @@ export default function CustomDialog({
   showOkBtn = true,
   size,
   disableBackdropClick = false,
-  disableEscapeKeyDown = false
+  disableEscapeKeyDown = false,
+  showTitleDivider = false,
+  showFooterDivider = false,
+  moreActions
 }: Props) {
 
   const classes = useStyles();
@@ -89,16 +104,19 @@ export default function CustomDialog({
       disableEscapeKeyDown={disableEscapeKeyDown}
     >
       <DialogTitle className={classes.dialogTitle}>{title}</DialogTitle>
+      {showTitleDivider && <Divider style={{ marginBottom: 10 }} />}
       <DialogContent>
         {children}
       </DialogContent>
+      {showFooterDivider && <Divider style={{ marginBottom: 10 }} />}
       <DialogActions>
         {showCancelBtn &&
-        <CancelButton onClick={handleClose} variant="outlined">
+        <CancelButton onClick={handleClose}  style={{ textTransform: 'none' }} 
variant="outlined">
           {btnCancelText || 'Cancel'}
         </CancelButton>}
+        {moreActions}
         {showOkBtn &&
-        <Button onClick={handleSave} variant="outlined" color="primary">
+        <Button onClick={handleSave} variant="contained" style={{ 
textTransform: 'none' }} color="primary">
           {btnOkText || 'Save'}
         </Button>}
       </DialogActions>
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOption.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOption.tsx
new file mode 100644
index 0000000000..180c230621
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOption.tsx
@@ -0,0 +1,43 @@
+/**
+ * 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 {RebalanceServerOption} from "./RebalanceServerOptions";
+import {
+    RebalanceServerConfigurationOptionSwitch
+} from 
"./RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionToggleSwitch";
+import React from 'react';
+import {
+    RebalanceServerConfigurationOptionInteger
+} from 
"./RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionInteger";
+import {
+    RebalanceServerConfigurationOptionSelect
+} from 
"./RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionSelect";
+
+export const RebalanceServerConfigurationOption = (
+    { option, handleConfigChange }: { option: RebalanceServerOption, 
handleConfigChange: (config: { [key: string]: string | number | boolean }) => 
void }) => {
+    switch (option.type) {
+        case "BOOL":
+            return <RebalanceServerConfigurationOptionSwitch option={option} 
handleConfigChange={handleConfigChange} />;
+        case "INTEGER":
+            return <RebalanceServerConfigurationOptionInteger option={option} 
handleConfigChange={handleConfigChange} />;
+        case "SELECT":
+            return <RebalanceServerConfigurationOptionSelect option={option} 
handleConfigChange={handleConfigChange} />;
+        default:
+            return null;
+    }
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionInteger.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionInteger.tsx
new file mode 100644
index 0000000000..a209bdf73e
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionInteger.tsx
@@ -0,0 +1,57 @@
+/**
+ * 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 {Box, FormControl, TextField, Typography} from "@material-ui/core";
+import React, {useState} from "react";
+import {RebalanceServerOption} from "../RebalanceServerOptions";
+import {
+    RebalanceServerConfigurationOptionLabel
+} from 
"./RebalanceServerConfigurationOptionLabel/RebalanceServerConfigurationOptionLabel";
+
+type RebalanceServerConfigurationOptionIntegerProps = {
+    option: RebalanceServerOption;
+    handleConfigChange: (config: { [key: string]: string | number | boolean }) 
=> void;
+}
+export const RebalanceServerConfigurationOptionInteger = (
+    { option, handleConfigChange }: 
RebalanceServerConfigurationOptionIntegerProps
+) => {
+    const [value, setValue] = useState<number>(option.defaultValue as number);
+    return (
+        <Box display='flex' flexDirection='column'>
+            <FormControl fullWidth>
+                <RebalanceServerConfigurationOptionLabel option={option} />
+                <TextField
+                    variant='outlined'
+                    fullWidth
+                    style={{ width: '100%' }}
+                    size='small'
+                    id={`rebalance-server-number-input-${option.name}`}
+                    type='number'
+                    value={value}
+                    onChange={(e) => {
+                        handleConfigChange(
+                            {
+                                [option.name]: parseInt(e.target.value)
+                            });
+                        setValue(parseInt(e.target.value));
+                    }}/>
+                <Typography variant='caption'>{option.description}</Typography>
+            </FormControl>
+        </Box>
+    );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionLabel/RebalanceServerConfigurationOptionLabel.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionLabel/RebalanceServerConfigurationOptionLabel.tsx
new file mode 100644
index 0000000000..cbef81cc62
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionLabel/RebalanceServerConfigurationOptionLabel.tsx
@@ -0,0 +1,38 @@
+/**
+ * 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 {Box, Tooltip, Typography} from "@material-ui/core";
+import {ReportProblemOutlined} from "@material-ui/icons";
+import React from "react";
+import {RebalanceServerOption} from "../../RebalanceServerOptions";
+
+type RebalanceServerConfigurationOptionLabelProps = {
+    option: RebalanceServerOption;
+}
+export const RebalanceServerConfigurationOptionLabel = ({option}: 
RebalanceServerConfigurationOptionLabelProps) => (
+    <Box display='flex' flexDirection='row' alignItems='center'>
+        <Typography variant='body2' style={{marginRight: 10, fontWeight: 
"600"}}>
+            {option.label}
+        </Typography>
+        {option.markWithWarningIcon && (
+            <Tooltip title={option.toolTip} arrow placement="right">
+                <ReportProblemOutlined color='error' fontSize='small' />
+            </Tooltip>
+        )}
+    </Box>
+);
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionSelect.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionSelect.tsx
new file mode 100644
index 0000000000..5ed6e83478
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionSelect.tsx
@@ -0,0 +1,63 @@
+/**
+ * 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 {
+    Box, FormControl, MenuItem, TextField, Typography
+} from "@material-ui/core";
+import React, {useState} from "react";
+import {RebalanceServerOption} from "../RebalanceServerOptions";
+import {
+    RebalanceServerConfigurationOptionLabel
+} from 
"./RebalanceServerConfigurationOptionLabel/RebalanceServerConfigurationOptionLabel";
+
+type RebalanceServerConfigurationOptionSelectProps = {
+    option: RebalanceServerOption;
+    handleConfigChange: (config: { [key: string]: string | number | boolean }) 
=> void;
+}
+export const RebalanceServerConfigurationOptionSelect = (
+    { option, handleConfigChange }: 
RebalanceServerConfigurationOptionSelectProps
+) => {
+    const [value, setValue] = useState<string>(option.defaultValue as string);
+    return (
+        <Box display='flex' flexDirection='column'>
+            <FormControl fullWidth={true}>
+                <RebalanceServerConfigurationOptionLabel option={option} />
+                <TextField
+                    variant='outlined'
+                    fullWidth
+                    style={{ width: '100%' }}
+                    size='small'
+                    select
+                    id={`rebalance-server-select-input-${option.name}`}
+                    value={value}
+                    onChange={(e) => {
+                        handleConfigChange(
+                            {
+                                [option.name]: e.target.value
+                            });
+                        setValue(e.target.value);
+                    }}>
+                    {option.allowedValues.map((allowedValue) => (
+                        <MenuItem key={allowedValue} 
value={allowedValue}>{allowedValue}</MenuItem>
+                    ))}
+                </TextField>
+            </FormControl>
+            <Typography variant='caption'>{option.description}</Typography>
+        </Box>
+    );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionToggleSwitch.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionToggleSwitch.tsx
new file mode 100644
index 0000000000..e4f2f06448
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationOptions/RebalanceServerConfigurationOptionToggleSwitch.tsx
@@ -0,0 +1,54 @@
+/**
+ * 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 {Box, FormControlLabel, Switch, Typography} from "@material-ui/core";
+import React, {useState} from "react";
+import {RebalanceServerOption} from "../RebalanceServerOptions";
+import {
+    RebalanceServerConfigurationOptionLabel
+} from 
"./RebalanceServerConfigurationOptionLabel/RebalanceServerConfigurationOptionLabel";
+
+type RebalanceServerConfigurationOptionSwitchProps = {
+    option: RebalanceServerOption;
+    handleConfigChange: (config: { [key: string]: string | number | boolean }) 
=> void;
+}
+export const RebalanceServerConfigurationOptionSwitch = (
+    { option, handleConfigChange }: 
RebalanceServerConfigurationOptionSwitchProps) => {
+    const [isChecked, setIsChecked] = useState<boolean>(option.defaultValue as 
boolean);
+    return (
+        <Box display='flex' flexDirection='column'>
+            <FormControlLabel
+                control={
+                    <Switch
+                        checked={isChecked}
+                        onChange={() => {
+                            handleConfigChange({
+                                [option.name]: !isChecked
+                            })
+                            setIsChecked(isChecked => !isChecked)
+                        }}
+                        name={option.name}
+                        color="primary"
+                    />
+                }
+                label={<RebalanceServerConfigurationOptionLabel 
option={option} />}
+            />
+            <Typography variant='caption'>{option.description}</Typography>
+        </Box>
+    );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationSection.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationSection.tsx
new file mode 100644
index 0000000000..40a655ced9
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerConfigurationSection.tsx
@@ -0,0 +1,68 @@
+/**
+ * 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 {Box, Typography} from "@material-ui/core";
+import React, {ReactNode, useEffect, useRef, useState} from "react";
+import Link from "@material-ui/core/Link";
+
+type RebalanceServerConfigurationSectionProps = {
+    sectionTitle: string;
+    children: ReactNode;
+    showSectionByDefault?: boolean;
+    canHideSection?: boolean;
+}
+
+export const RebalanceServerConfigurationSection = (
+    { sectionTitle, children, showSectionByDefault = true, canHideSection = 
false }: RebalanceServerConfigurationSectionProps
+) => {
+    const [showSection, setShowSection] = 
useState<boolean>(showSectionByDefault);
+    const showHideSectionRef = useRef(null);
+
+    const handleScrollToSection = () => {
+        if (showHideSectionRef.current) {
+            showHideSectionRef.current.scrollIntoView(
+                {
+                    behavior: 'smooth',
+                    block: 'start',
+                });
+        }
+    };
+
+    useEffect(() => {
+        if (showSection && !showSectionByDefault) {
+            handleScrollToSection();
+        }
+    }, [showSection, showHideSectionRef]);
+
+    return (
+        <Box marginBottom={2}>
+            <Box display='flex' flexDirection='row' alignItems='center' 
marginBottom={2}>
+                <div ref={showHideSectionRef} />
+                <Typography variant='body1' style={{ fontWeight: 'bold', 
marginRight: 10 }}>
+                    {sectionTitle}
+                </Typography>
+                {canHideSection && (
+                    <Link style={{ cursor: 'pointer' }} onClick={() => 
setShowSection(visible => !visible)}>
+                        { showSection ? "Hide" : "Show" }
+                    </Link>
+                )}
+            </Box>
+            {showSection && children}
+        </Box>
+    );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerDialogHeader.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerDialogHeader.tsx
new file mode 100644
index 0000000000..59c464f0b3
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerDialogHeader.tsx
@@ -0,0 +1,43 @@
+/**
+ * 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 {Box, Typography} from "@material-ui/core";
+import React from "react";
+import Link from "@material-ui/core/Link";
+
+type LinkTextProps = {
+    text: string;
+    link: string;
+}
+const LinkText = ({ text, link }: LinkTextProps) => {
+    return (
+        <Link href={link} target="_blank" rel="noopener">
+            <Typography style={{ fontWeight: 'bold' }} 
variant="inherit">{text}</Typography>
+        </Link>
+    );
+}
+export const RebalanceServerDialogHeader = () => {
+    return (
+        <Box>
+            <Typography style={{ fontWeight: "bold" }} variant="h6">Rebalance 
Server</Typography>
+            <Typography variant="subtitle2">
+                Click <LinkText text='here' 
link='https://docs.pinot.apache.org/operators/operating-pinot/rebalance/rebalance-servers'
 /> for more details
+            </Typography>
+        </Box>
+    );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerOptions.ts
 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerOptions.ts
new file mode 100644
index 0000000000..3aaf3a22fa
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServer/RebalanceServerOptions.ts
@@ -0,0 +1,168 @@
+/**
+ * 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.
+ */
+export type RebalanceServerOption = {
+    name: string;
+    label: string;
+    type: "BOOL" | "INTEGER" | "SELECT";
+    description: string;
+    defaultValue: string | boolean | number;
+    isAdvancedConfig: boolean;
+    isStatsGatheringConfig: boolean;
+    markWithWarningIcon: boolean;
+    allowedValues?: string[];
+    toolTip?: string;
+}
+
+export const rebalanceServerOptions: RebalanceServerOption[] = [
+    {
+        "name": "dryRun",
+        "defaultValue": false,
+        "label": "Dry Run",
+        "type": "BOOL",
+        "description": "If enabled, rebalance will not run but expected 
changes that will occur will be returned",
+        "isAdvancedConfig": false,
+        "isStatsGatheringConfig": true,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "preChecks",
+        "defaultValue": false,
+        "type": "BOOL",
+        "label": "Pre-Checks",
+        "description": "If enabled, will perform some pre-checks to ensure 
rebalance is safe, must enable dryRun to enable this",
+        "isAdvancedConfig": false,
+        "isStatsGatheringConfig": true,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "reassignInstances",
+        "defaultValue": true,
+        "type": "BOOL",
+        "label": "Reassign Instances",
+        "description": "If enabled, reassign the instances of the table before 
making updates to the segment assignment",
+        "isAdvancedConfig": false,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "includeConsuming",
+        "defaultValue": true,
+        "type": "BOOL",
+        "label": "Include Consuming",
+        "description": "If enabled, CONSUMING segments will be included in the 
rebalance of realtime tables. This is mandatory for for upsert/dedup tables",
+        "isAdvancedConfig": false,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "minimizeDataMovement",
+        "defaultValue": "ENABLE",
+        "type": "SELECT",
+        "allowedValues": ["ENABLE", "DISABLE", "DEFAULT"],
+        "label": "Minimize Data Movement",
+        "description": "If enabled, it reduces the segments that will be moved 
by trying to minimize the changes to the instance assignment. Setting this to 
default will fallback to the value of this flag in the TableConfig",
+        "isAdvancedConfig": false,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": true,
+        "toolTip": "Disabling minimizeDataMovement can cause a large amount of 
data movement"
+    },
+    {
+        "name": "bootstrap",
+        "defaultValue": false,
+        "type": "BOOL",
+        "label": "Bootstrap",
+        "description": "If enabled, regardless of minimum segment movement, 
reassign all segments in a round-robin fashion as if adding new segments to an 
empty table",
+        "isAdvancedConfig": true,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": true,
+        "toolTip": "Enabling bootstrap can cause a large amount of data 
movement"
+    },
+    {
+        "name": "downtime",
+        "defaultValue": false,
+        "type": "BOOL",
+        "label": "Downtime",
+        "description": "If enabled, rebalance will be performed with downtime. 
This must be set to true if replication = 1",
+        "isAdvancedConfig": false,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": true,
+        "toolTip": "Enabling can cause downtime if replication > 1"
+    },
+    {
+        "name": "minAvailableReplicas",
+        "defaultValue": -1,
+        "type": "INTEGER",
+        "label": "Min Available Replicas",
+        "description": "For no-downtime rebalance, minimum number of replicas 
to keep alive during rebalance, or maximum number of replicas allowed to be 
unavailable if value is negative. Should not be 0 unless for downtime=true",
+        "isAdvancedConfig": false,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "lowDiskMode",
+        "defaultValue": false,
+        "type": "BOOL",
+        "label": "Low Disk Mode",
+        "description": "If enabled, perform rebalance by offloading segments 
off servers prior to adding them. Can slow down rebalance and is recommended to 
enable for scenarios which are low on disk capacity",
+        "isAdvancedConfig": true,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "bestEfforts",
+        "defaultValue": false,
+        "type": "BOOL",
+        "label": "Best Efforts",
+        "description": "If enabled, even if downtime=false do not fail 
rebalance if IS-EV convergence fails within timeout or segments are in ERROR 
state and continue rebalancing. This can cause downtime",
+        "isAdvancedConfig": true,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": true,
+        "toolTip": "Enabling can cause downtime even if downtime = true"
+    },
+    {
+        "name": "externalViewStabilizationTimeoutInMs",
+        "defaultValue": 3600000,
+        "type": "INTEGER",
+        "label": "External View Stabilization Timeout In Milliseconds",
+        "description": "How long to wait for EV-IS convergence, increase this 
timeout for large tables (TBs in size)",
+        "isAdvancedConfig": true,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "maxAttempts",
+        "defaultValue": 3,
+        "type": "INTEGER",
+        "label": "Max Attempts",
+        "description": "Max number of attempts to rebalance",
+        "isAdvancedConfig": true,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": false
+    },
+    {
+        "name": "updateTargetTier",
+        "defaultValue": false,
+        "type": "BOOL",
+        "label": "Update Target Tier",
+        "description": "If enabled, update segment target tier as part of the 
rebalance",
+        "isAdvancedConfig": true,
+        "isStatsGatheringConfig": false,
+        "markWithWarningIcon": false
+    }
+]
\ No newline at end of file
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
index 504e4200ce..7236530771 100644
--- 
a/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/Operations/RebalanceServerTableOp.tsx
@@ -18,11 +18,32 @@
  */
 
 import React from 'react';
-import { DialogContent, DialogContentText, FormControl, FormControlLabel, 
Grid, Input, InputLabel, Switch, Tooltip} from '@material-ui/core';
+import {
+  DialogContentText,
+  FormControl,
+  FormControlLabel,
+  Grid,
+  Input,
+  InputLabel,
+  Switch,
+  Box,
+  Typography,
+  List,
+  ListItem,
+  ListItemText,
+  ListItemIcon,
+  Divider, Button
+} from '@material-ui/core';
 import Dialog from '../../CustomDialog';
 import PinotMethodUtils from '../../../utils/PinotMethodUtils';
 import CustomCodemirror from '../../CustomCodemirror';
-import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
+import {RebalanceServerDialogHeader} from 
"./RebalanceServer/RebalanceServerDialogHeader";
+import {RebalanceServerConfigurationSection} from 
"./RebalanceServer/RebalanceServerConfigurationSection";
+import Alert from "@material-ui/lab/Alert";
+import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined";
+import {rebalanceServerOptions} from 
"./RebalanceServer/RebalanceServerOptions";
+import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
+import {RebalanceServerConfigurationOption} from 
"./RebalanceServer/RebalanceServerConfigurationOption";
 
 type Props = {
   tableType: string,
@@ -30,139 +51,99 @@ type Props = {
   hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void
 };
 
+const DryRunAction = ({ handleOnRun }: { handleOnRun: () => void }) => {
+  return (
+      <Button onClick={handleOnRun} variant="outlined" style={{ textTransform: 
'none' }} color="primary">
+        Dry Run
+      </Button>
+  );
+}
+
 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 [lowDiskMode, setLowDiskMode] = React.useState(false);
+  const [rebalanceConfig, setRebalanceConfig] = React.useState(
+      rebalanceServerOptions.reduce((config, option) => ({ ...config, 
[option.name]: option.defaultValue }), {})
+  );
 
   const getData = () => {
     return {
       type: tableType,
-      dryRun, reassignInstances, includeConsuming, bootstrap, downtime, 
bestEfforts, lowDiskMode,
-      minAvailableReplicas: parseInt(minAvailableReplicas, 10)
+      ...rebalanceConfig,
     }
   };
 
-  const handleSave = async (event) => {
+  const handleSave = async () => {
     const data = getData();
     const response = await 
PinotMethodUtils.rebalanceServersForTableOp(tableName, data);
     setRebalanceResponse(response);
   };
 
+  const handleDryRun = async () => {
+    const data = getData();
+    const response = await 
PinotMethodUtils.rebalanceServersForTableOp(tableName, {
+      ...data,
+      dryRun: true,
+      preChecks: true
+    });
+    setRebalanceResponse(response);
+  };
+
+  const handleConfigChange = (config: { [key: string]: string | number | 
boolean }) => {
+    setRebalanceConfig({
+      ...rebalanceConfig,
+      ...config
+    });
+  }
+
+
   return (
     <Dialog
+      showTitleDivider
+      showFooterDivider
+      size='md'
       open={true}
       handleClose={hideModal}
-      title={(<>Rebalance Server <Tooltip interactive title={(<a 
className={"tooltip-link"} target="_blank" 
href="https://docs.pinot.apache.org/operators/operating-pinot/rebalance/rebalance-servers";>Click
 here for more details</a>)} arrow 
placement="top"><InfoOutlinedIcon/></Tooltip></>)}
+      title={<RebalanceServerDialogHeader />}
       handleSave={handleSave}
+      btnOkText='Rebalance'
       showOkBtn={!rebalanceResponse}
+      moreActions={!rebalanceResponse ? <DryRunAction 
handleOnRun={handleDryRun} /> : null}
     >
-      <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"
-              />
-              <br />
-              <FormControlLabel
-                control={
-                  <Switch
-                    checked={lowDiskMode}
-                    onChange={() => setLowDiskMode(!lowDiskMode)} 
-                    name="lowDiskMode"
-                    color="primary"
-                  />
-                }
-                label="Low Disk Mode"
-              />
-            </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>
+          <Box flexDirection="column">
+            <RebalanceServerConfigurationSection sectionTitle='Before you 
begin'>
+              <Alert color='info' icon={<InfoOutlinedIcon fontSize='small' />}>
+                <Typography variant='body2'>
+                  It is strongly recommended to run once via "Dry Run" with 
the options enabled prior to running the actual "Rebalance" operation.
+                  This is needed to verify that rebalance will do what's 
expected.
+                </Typography>
+              </Alert>
+            </RebalanceServerConfigurationSection>
+            <Divider style={{ marginBottom: 20 }} />
+            <RebalanceServerConfigurationSection sectionTitle='Basic Options'>
+              <Grid container spacing={2}>
+                {rebalanceServerOptions.filter(option => 
!option.isAdvancedConfig && !option.isStatsGatheringConfig).map((option) => (
+                    <Grid item xs={12} key={`basic-options-${option.name}`}>
+                      <RebalanceServerConfigurationOption option={option} 
handleConfigChange={handleConfigChange} />
+                    </Grid>
+                ))}
+              </Grid>
+            </RebalanceServerConfigurationSection>
+            <Divider style={{ marginBottom: 20 }}/>
+            <RebalanceServerConfigurationSection sectionTitle='Advanced 
Options' canHideSection showSectionByDefault={false}>
+              <Grid container spacing={2}>
+                {rebalanceServerOptions.filter(option => 
option.isAdvancedConfig).map((option) => (
+                    <Grid item xs={12} key={`advanced-options-${option.name}`}>
+                      <RebalanceServerConfigurationOption option={option} 
handleConfigChange={handleConfigChange} />
+                    </Grid>
+                ))}
+              </Grid>
+            </RebalanceServerConfigurationSection>
+          </Box>
         : 
           <React.Fragment>
             <DialogContentText>
@@ -174,7 +155,6 @@ export default function RebalanceServerTableOp({
             />
           </React.Fragment>
         }
-      </DialogContent>
     </Dialog>
   );
 }
\ No newline at end of file


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


Reply via email to