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/12/03 21:51:31 UTC

[incubator-pinot] branch master updated: support to add offline and realtime tables, individually able to add schema and schema listing in UI (#6296)

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 f898c18  support to add offline and realtime tables, individually able to add schema and schema listing in UI (#6296)
f898c18 is described below

commit f898c1813ee2678e77c7dcc96194c64f441cd713
Author: Sanket Shah <sh...@users.noreply.github.com>
AuthorDate: Fri Dec 4 03:21:14 2020 +0530

    support to add offline and realtime tables, individually able to add schema and schema listing in UI (#6296)
    
    * support to add offline and realtime tables, individually able to add schema and schema listing in UI
    
    * fixing issues
    
    * show multi-value only for dimension
    
    * additional ui fixes and added schema edit and delete option in schema detail page
    
    * show asterix for required fields and fix issue where loader is always spinning
---
 .../main/resources/app/components/Breadcrumbs.tsx  |   9 +-
 .../Homepage/Operations/AddDeleteComponent.tsx     | 174 +++++++++++
 .../Homepage/Operations/AddIndexingComponent.tsx   | 161 ++++++++++
 .../Homepage/Operations/AddIngestionComponent.tsx  | 169 ++++++++++
 ...{AddTableSchemaOp.tsx => AddOfflineTableOp.tsx} | 235 ++++++++++----
 .../Operations/AddOfflineTenantComponent.tsx       | 151 +++++++++
 .../Homepage/Operations/AddPartionComponent.tsx    | 259 ++++++++++++++++
 .../Homepage/Operations/AddQueryComponent.tsx      |  89 ++++++
 .../Operations/AddRealTimeIngestionComponent.tsx   | 167 ++++++++++
 .../Operations/AddRealTimePartionComponent.tsx     | 230 ++++++++++++++
 ...Component.tsx => AddRealtimeTableComponent.tsx} |   9 +-
 .../{AddTableOp.tsx => AddRealtimeTableOp.tsx}     | 161 +++++++++-
 .../components/Homepage/Operations/AddSchemaOp.tsx | 203 ++++++++++++
 .../Homepage/Operations/AddStorageComponent.tsx    | 117 +++++++
 .../Homepage/Operations/AddTableComponent.tsx      |   8 +-
 .../Homepage/Operations/AddTenantComponent.tsx     | 172 +++++++++++
 .../Homepage/Operations/MultiIndexingComponent.tsx | 344 +++++++++++++++++++++
 .../Homepage/Operations/MultiMetricComponent.tsx   | 182 +++++++++++
 .../Operations/MultipleSelectComponent.tsx         | 185 +++++++++++
 .../Homepage/Operations/SchemaComponent.tsx        |  44 ++-
 .../Homepage/Operations/SchemaNameComponent.tsx    | 104 +++++++
 .../src/main/resources/app/interfaces/types.d.ts   |   8 +
 .../main/resources/app/pages/SchemaPageDetails.tsx | 308 ++++++++++++++++++
 .../main/resources/app/pages/TablesListingPage.tsx |  65 +++-
 .../src/main/resources/app/pages/TenantDetails.tsx |   2 +-
 .../src/main/resources/app/requests/index.ts       |   3 +
 pinot-controller/src/main/resources/app/router.tsx |   2 +
 .../src/main/resources/app/styles/styles.css       |  33 ++
 .../main/resources/app/utils/PinotMethodUtils.ts   |  42 ++-
 29 files changed, 3532 insertions(+), 104 deletions(-)

diff --git a/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx b/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
index 1cf97ef..db05202 100644
--- a/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
+++ b/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
@@ -91,7 +91,7 @@ const BreadcrumbsComponent = ({ ...props }) => {
     const breadcrumbs = [getClickableLabel(breadcrumbNameMap['/'], '/')];
     const paramsKeys = _.keys(props.match.params);
     if(paramsKeys.length){
-      const {tenantName, tableName, segmentName, instanceName} = props.match.params;
+      const {tenantName, tableName, segmentName, instanceName, schemaName} = props.match.params;
       if((tenantName || instanceName) && tableName){
         breadcrumbs.push(
           getClickableLabel(
@@ -113,7 +113,12 @@ const BreadcrumbsComponent = ({ ...props }) => {
           getClickableLabel('Tables', '/tables')
         );
       }
-      breadcrumbs.push(getLabel(segmentName || tableName || tenantName || instanceName));
+      if(schemaName){
+        breadcrumbs.push(
+          getClickableLabel('Schemas', '/tables')
+        );
+      }
+      breadcrumbs.push(getLabel(segmentName || tableName || tenantName || instanceName || schemaName));
     } else {
       breadcrumbs.push(getLabel(breadcrumbNameMap[location.pathname]));
     }
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddDeleteComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddDeleteComponent.tsx
new file mode 100644
index 0000000..c1d5571
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddDeleteComponent.tsx
@@ -0,0 +1,174 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, TextField, Theme, IconButton, Fab, Button} from '@material-ui/core';
+import AddIcon from '@material-ui/icons/Add';
+import AddCircleIcon from '@material-ui/icons/AddCircle';
+import ClearIcon from '@material-ui/icons/Clear';
+import DeleteIcon from '@material-ui/icons/Delete';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      width: '100%'
+    },
+    deleteIcon:{
+        marginTop:15,
+        marginLeft:-15,
+        color: theme.palette.error.main
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 170
+    },
+    autoCompleteControl: {
+      '& .MuiFormControl-marginNormal': {
+        marginTop: 0
+      }
+    },
+    redColor: {
+      color: theme.palette.error.main
+    }
+  })
+);
+
+type Props = {
+    changeHandler: any,
+  streamConfigsObj: Object
+};
+
+const compulsoryKeys = ["stream.kafka.broker.list","stream.kafka.topic.name","stream.kafka.consumer.type","stream.kafka.decoder.class.name"];
+
+export default function AddDeleteComponent({
+    changeHandler,
+    streamConfigsObj
+}: Props) {
+    const classes = useStyles();
+    const [streamConfigObj, setStreamConfigObj] = useState(streamConfigsObj);
+    const [keys,setKeys] = useState(Object.keys(streamConfigObj));
+    const [value,setValue] = useState(Object.values(streamConfigObj));
+
+    const addButtonClick = ()=>{
+        let data = "";
+        let tempStreamConfigObj = {...streamConfigObj,
+            [data]:data}
+        setStreamConfigObj(tempStreamConfigObj);
+        changeHandler('streamConfigs',tempStreamConfigObj)
+    }
+
+    const keyChange = (input,index, value) =>{
+        input[index] = value;
+        setKeys([...input]);
+    }
+
+    const valueChange = (input,index, value) =>{
+        input[index] = value;
+        setValue([...input]);
+    }
+
+    const updateJson = () =>{
+        let configObj = {}
+        keys.map((k,i)=>{
+            configObj[k] = value[i]
+        })
+        changeHandler('streamConfigs',configObj);
+    }
+
+    const deleteClick = (val) =>{
+        let tempStreamConfigObj = {...streamConfigObj};
+        delete tempStreamConfigObj[val];
+        setStreamConfigObj(tempStreamConfigObj);
+        setKeys([]);
+        setValue([]);
+        changeHandler('streamConfigs',tempStreamConfigObj)
+    }
+
+    useEffect(() => {
+        setStreamConfigObj(streamConfigsObj);
+        setKeys(Object.keys(streamConfigObj));
+        setValue(Object.values(streamConfigObj));
+    }, [streamConfigsObj]);
+
+
+  const requiredAstrix = <span className={classes.redColor}>*</span>;
+
+  return (
+    <Grid container>
+        <h3 className="accordion-subtitle">Stream Config</h3>
+                {
+                    keys.map((o,i)=>{
+                        return(
+                            <Grid item xs={6}>
+                                <div className="box-border">
+                                <Grid container spacing={2}>
+                                    <Grid item xs={6}>
+                                        <FormControl className={classes.formControl}>
+                                            <InputLabel htmlFor={o}>Key { compulsoryKeys.includes(o) && requiredAstrix }</InputLabel>
+                                            <Input
+                                                id={o}
+                                                value={o}
+                                                key={i+"key"}
+                                                onChange={(e)=> keyChange(keys,i,e.target.value)}
+                                                onBlur={updateJson}
+                                            />
+                                        </FormControl>
+                                    </Grid>
+                                    <Grid item xs={5}>
+                                        <FormControl className={classes.formControl}>
+                                            <InputLabel htmlFor={value[i]}>Value</InputLabel>
+                                            <Input
+                                                id={value[i]}
+                                                value={value[i]}
+                                                key={i+"value"}
+                                                onChange={(e)=> valueChange(value,i,e.target.value)}
+                                                onBlur={updateJson}
+                                            />
+                                        </FormControl>
+                                    </Grid>
+                                    <Grid item xs={1}>
+                                        <FormControl>
+                                            <IconButton aria-label="delete" key={"delete"+i} className={classes.deleteIcon} onClick={()=>{
+                                                deleteClick(o)}}>
+                                                <ClearIcon />
+                                            </IconButton>
+                                        </FormControl>
+                                    </Grid>
+                                </Grid>
+                                </div>
+                            </Grid>)
+                        })
+                }
+                <Grid item xs={3}>
+                <FormControl className={classes.formControl}>
+                    <Button
+                    aria-label="plus"
+                    variant="outlined"
+                    color="primary"
+                    onClick={addButtonClick}
+                    startIcon={(<AddCircleIcon />)}
+                    >
+                        Add new Field
+                    </Button>
+                </FormControl>
+                </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddIndexingComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddIndexingComponent.tsx
new file mode 100644
index 0000000..3815264
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddIndexingComponent.tsx
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, Grid, makeStyles, Theme} from '@material-ui/core';
+import MultiIndexingComponent from './MultiIndexingComponent';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 220
+    }
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function,
+  columnName: Array<string>
+};
+
+export default function AddIndexingComponent({
+  tableObj,
+  setTableObj,
+  columnName
+}: Props) {
+  const classes = useStyles();
+
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+  const [showTree,setShowTree] = useState(0);
+  const ITEM_HEIGHT = 48;
+  const ITEM_PADDING_TOP = 8;
+  const MenuProps = {
+      PaperProps: {
+        style: {
+          maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+          width: 250,
+        },
+      },
+    };
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+      case 'maxLeafRecords':
+        newTableObj.tableIndexConfig.starTreeIndexConfigs[0].maxLeafRecords = value;
+        // newTableObj.segmentsConfig.schemaName = value;
+      break;
+      case 'enableStarTree':
+        setShowTree(value);
+      break;
+      case 'dimensionsSplitOrder':
+          if(!newTableObj.tableIndexConfig.starTreeIndexConfigs){
+            newTableObj.tableIndexConfig.starTreeIndexConfigs = [];
+            newTableObj.tableIndexConfig.starTreeIndexConfigs[0] = {};
+          }
+          newTableObj.tableIndexConfig.starTreeIndexConfigs[0].dimensionsSplitOrder = value;
+      break;
+      case 'maxLeafRecords':
+        if(!newTableObj.tableIndexConfig.starTreeIndexConfigs){
+            newTableObj.tableIndexConfig.starTreeIndexConfigs = [];
+            newTableObj.tableIndexConfig.starTreeIndexConfigs[0] = {};
+          }
+        newTableObj.tableIndexConfig.starTreeIndexConfigs[0].maxLeafRecords = value;
+      break;
+      case 'tableIndexConfig':
+        newTableObj.tableIndexConfig  = {...newTableObj.tableIndexConfig,...value.tableIndexConfig};
+        newTableObj.fieldConfigList = [...value.fieldConfigList];
+      break;
+    };
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+    setTableDataObj(tableObj);
+  }, [tableObj]);
+
+  return (
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+      {/* <FormControl className={classes.selectFormControl}>
+            <InputLabel htmlFor="enableStarTree">Enable default star tree?</InputLabel>
+            <Select
+            labelId="enableStarTree"
+            id="enableStarTree"
+            value={showTree}
+            onChange={(e)=> changeHandler('enableStarTree', e.target.value)}
+            >
+                <MenuItem value={1}>True</MenuItem>
+                <MenuItem value={0}>False</MenuItem>
+            </Select>
+        </FormControl>
+      <FormControl className={classes.selectFormControl}>
+            <InputLabel htmlFor="dimensionsSplitOrder">Dimension split order</InputLabel>
+            <Select
+                labelId="dimensionsSplitOrder"
+                id="dimensionsSplitOrder"
+                key="dimensionsSplitOrder"
+                multiple
+                value={tableDataObj.tableIndexConfig.starTreeIndexConfigs && tableDataObj.tableIndexConfig.starTreeIndexConfigs[0].dimensionsSplitOrder || []}
+                onChange={(e)=> changeHandler('dimensionsSplitOrder', e.target.value)}
+                input={<Input id="select-multiple-chip" />}
+                renderValue={(selected) => (
+                    <div className={"chips"}>
+                    {(selected as string[]).map((value) => (
+                        <Chip key={value} label={value} className={"chip"} />
+                    ))}
+                    </div>
+                )}
+                MenuProps={MenuProps}
+                >
+                <MenuItem value="Country">Country</MenuItem>
+                <MenuItem value="Browser">Browser</MenuItem>
+                <MenuItem value="Locale">Locale</MenuItem>
+            </Select>
+        </FormControl>
+        <FormControl className={classes.formControl} >
+            <InputLabel htmlFor="maxLeafRecords">Max leaf records</InputLabel>
+            <Input
+                id="maxLeafRecords"
+                value={tableDataObj.tableIndexConfig.starTreeIndexConfigs && tableDataObj.tableIndexConfig.starTreeIndexConfigs[0].maxLeafRecords}
+                onChange={(e)=> changeHandler('maxLeafRecords', e.target.value)}
+                error = {tableDataObj.tableIndexConfig.starTreeIndexConfigs && tableDataObj.tableIndexConfig.starTreeIndexConfigs[0].maxLeafRecords ? isNaN(Number(tableDataObj.tableIndexConfig.starTreeIndexConfigs[0].maxLeafRecords)) : false}
+            />
+        </FormControl> */}
+        <MultiIndexingComponent
+            key = {"multiIndex"}
+            streamConfigsObj = {{...tableDataObj.tableIndexConfig}}
+            textDataObj={tableDataObj.fieldConfigList ? [...tableDataObj.fieldConfigList] : []}
+            changeHandler = {changeHandler}
+            columnName={columnName}/>
+        {/* <MultiMetricComponent
+            key={"multiMetrix"}
+            streamConfigsObj={tableDataObj.tableIndexConfig.starTreeIndexConfigs ? {...tableDataObj.tableIndexConfig.starTreeIndexConfigs[0]} : {}}
+            changeHandler = {changeHandler}/> */}
+      </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddIngestionComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddIngestionComponent.tsx
new file mode 100644
index 0000000..5c06fa0
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddIngestionComponent.tsx
@@ -0,0 +1,169 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, Theme} from '@material-ui/core';
+import AddDeleteComponent from './AddDeleteComponent';
+import MultipleSelectComponent from './MultipleSelectComponent';
+import _ from 'lodash';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 220
+    },
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function,
+  columnName: Array<string>
+};
+
+export default function AddIngestionComponent({
+  tableObj,
+  setTableObj,
+  columnName
+}: Props) {
+  const classes = useStyles();
+  const [newSize,setNewSize] = useState([0,1])
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+      case 'filterConfig':
+        newTableObj.ingestionConfig.filterConfig[fieldName] = value;
+      break;
+      case 'segmentPushFrequency':
+        newTableObj.segmentsConfig[fieldName] = value;
+      break;
+      case 'segmentPushType':
+        newTableObj.segmentsConfig[fieldName] = value;
+      break;
+      case 'streamConfigs':
+        newTableObj.tableIndexConfig.streamConfigs = {...value};
+      break;
+      case 'filterFunction':
+          if(!newTableObj.ingestionConfig.filterConfig){
+            newTableObj.ingestionConfig.filterConfig = {};
+          }
+        newTableObj.ingestionConfig.filterConfig.filterFunction = value;
+      break;
+      case 'transformConfigs':
+        tableDataObj.ingestionConfig.transformConfigs = value;
+    };
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+    let newTableObj = {...tableObj};
+      if(newTableObj.tableType === "REALTIME" && !newTableObj.streamConfigs && _.isEmpty(newTableObj.streamConfigs) ){
+        newTableObj.tableIndexConfig.streamConfigs =
+        {
+            "streamType": "kafka",
+            "stream.kafka.topic.name": "",
+            "stream.kafka.broker.list": "",
+            "stream.kafka.consumer.type": "lowlevel",
+            "stream.kafka.consumer.prop.auto.offset.reset": "smallest",
+            "stream.kafka.consumer.factory.class.name":"org.apache.pinot.plugin.stream.kafka20.KafkaConsumerFactory",
+            "stream.kafka.decoder.class.name":"org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder",
+            "realtime.segment.flush.threshold.rows": "0",
+            "realtime.segment.flush.threshold.time": "24h",
+            "realtime.segment.flush.segment.size": "100M"
+        }
+        setTableObj(newTableObj);
+      }else if(newTableObj.tableType !== "REALTIME" && newTableObj.streamConfigs){
+        newTableObj.streamConfigs = null;
+        setTableObj(newTableObj);
+      }
+    setTableDataObj(newTableObj);
+  }, [tableObj]);
+
+  useEffect(()=>{
+    setNewSize(newSize);
+  },[newSize])
+
+  return (
+    <Grid container spacing={2}>
+        <Grid item xs={12}>
+            {
+                tableDataObj.tableType === "OFFLINE" ?
+                    <FormControl className={classes.selectFormControl}>
+                        <InputLabel htmlFor="segmentPushFrequency">Offline push frequency</InputLabel>
+                        <Select
+                            labelId="segmentPushFrequency"
+                            id="segmentPushFrequency"
+                            value={tableDataObj.segmentsConfig.segmentPushFrequency !== "" ? tableDataObj.segmentsConfig.segmentPushFrequency : ""}
+                            onChange={(e)=> changeHandler('segmentPushFrequency', e.target.value)}
+                        >
+                            <MenuItem value="HOURLY">HOURLY</MenuItem>
+                            <MenuItem value="DAILY">DAILY</MenuItem>
+                        </Select>
+                    </FormControl> : null
+            }
+            {
+                tableDataObj.tableType === "OFFLINE" ?
+                    <FormControl className={classes.selectFormControl}>
+                        <InputLabel htmlFor="segmentPushType">Offline push type</InputLabel>
+                        <Select
+                            labelId="segmentPushType"
+                            id="segmentPushType"
+                            value={tableDataObj.segmentsConfig.segmentPushType !== "" ? tableDataObj.segmentsConfig.segmentPushType : ""}
+                            onChange={(e)=> changeHandler('segmentPushType', e.target.value)}
+                        >
+                            <MenuItem value="APPEND">APPEND</MenuItem>
+                            <MenuItem value="REFRESH">REFRESH</MenuItem>
+                        </Select>
+                    </FormControl> : null
+            }
+            </Grid>
+            <Grid item xs={12}>
+            <FormControl className={classes.formControl}>
+                <InputLabel htmlFor="filterFunction">Filter function</InputLabel>
+                <Input
+                    id="filterFunction"
+                    value={tableObj.ingestionConfig.filterConfig && tableObj.ingestionConfig.filterConfig.filterFunction || ""}
+                    onChange={(e)=> changeHandler('filterFunction', e.target.value)}
+                />
+            </FormControl>
+            {
+                tableDataObj.tableIndexConfig.streamConfigs ?
+                <AddDeleteComponent
+                    key = {"streamConfigs"}
+                    streamConfigsObj = {{...tableDataObj.tableIndexConfig.streamConfigs}}
+                    changeHandler = {changeHandler}/>
+                : null
+            }
+            <MultipleSelectComponent
+                key = {"transformConfigs"}
+                streamConfigsObj = {tableDataObj.ingestionConfig.transformConfigs || []}
+                changeHandler = {changeHandler}
+                columnName= {columnName}/>
+          </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableSchemaOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddOfflineTableOp.tsx
similarity index 53%
rename from pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableSchemaOp.tsx
rename to pinot-controller/src/main/resources/app/components/Homepage/Operations/AddOfflineTableOp.tsx
index c87f750..74f8f4f 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableSchemaOp.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddOfflineTableOp.tsx
@@ -21,11 +21,18 @@ import React, { useEffect, useState } from 'react';
 import { createStyles, DialogContent, Grid, makeStyles, Theme} from '@material-ui/core';
 import Dialog from '../../CustomDialog';
 import SimpleAccordion from '../../SimpleAccordion';
-import SchemaComponent from './SchemaComponent';
 import AddTableComponent from './AddTableComponent';
 import CustomCodemirror from '../../CustomCodemirror';
 import PinotMethodUtils from '../../../utils/PinotMethodUtils';
 import { NotificationContext } from '../../Notification/NotificationContext';
+import AddTenantComponent from './AddTenantComponent';
+import AddIngestionComponent from './AddIngestionComponent';
+import AddIndexingComponent from './AddIndexingComponent';
+import AddPartionComponent from './AddPartionComponent';
+import AddStorageComponent from './AddStorageComponent';
+import AddQueryComponent from './AddQueryComponent';
+import _ from 'lodash';
+import AddOfflineTenantComponent from './AddOfflineTenantComponent';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -42,7 +49,8 @@ const useStyles = makeStyles((theme: Theme) =>
 
 type Props = {
   hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
-  fetchData: Function
+  fetchData: Function,
+  tableType: String
 };
 
 const defaultTableObj = {
@@ -50,8 +58,7 @@ const defaultTableObj = {
   "tableType": "",
   "tenants": {
     "broker": "DefaultTenant",
-    "server": "DefaultTenant",
-    "tagOverrideConfig": {}
+    "server": "DefaultTenant"
   },
   "segmentsConfig": {
     "schemaName": "",
@@ -62,7 +69,6 @@ const defaultTableObj = {
     "retentionTimeValue": null,
     "segmentPushType": "APPEND",
     "segmentPushFrequency": "HOURLY",
-    "completionConfig": null,
     "crypterClassName": null,
     "peerSegmentDownloadScheme": null
   },
@@ -82,22 +88,7 @@ const defaultTableObj = {
     "enableDynamicStarTreeCreation": false,
     "segmentPartitionConfig": null,
     "columnMinMaxValueGeneratorMode": null,
-    "aggregateMetrics": false,
-    "nullHandlingEnabled": false,
-    "streamConfigs": {
-      "streamType": "kafka",
-      "stream.kafka.topic.name": "",
-      "stream.kafka.broker.list": "",
-      "stream.kafka.consumer.type": "lowlevel",
-      "stream.kafka.consumer.prop.auto.offset.reset": "smallest",
-      "stream.kafka.consumer.factory.class.name": "org.apache.pinot.plugin.stream.kafka20.KafkaConsumerFactory",
-      "stream.kafka.decoder.class.name": "org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder",
-      "stream.kafka.decoder.prop.schema.registry.rest.url": null,
-      "stream.kafka.decoder.prop.schema.registry.schema.name": null,
-      "realtime.segment.flush.threshold.rows": "0",
-      "realtime.segment.flush.threshold.time": "24h",
-      "realtime.segment.flush.segment.size": "100M"
-    }
+    "nullHandlingEnabled": false
   },
   "metadata": {},
   "ingestionConfig": {
@@ -122,37 +113,106 @@ const defaultTableObj = {
   "tierConfigs": null
 };
 
-export default function AddTableSchemaOp({
+const defaultSchemaObj = {
+  schemaName: '',
+  dimensionFieldSpecs: [],
+  metricFieldSpecs: [],
+  dateTimeFieldSpecs: []
+};
+
+let timerId = null;
+
+const tableNamekey = ["dimensionFieldSpecs","metricFieldSpecs","dateTimeFieldSpecs"];
+
+export default function AddOfflineTableOp({
   hideModal,
-  fetchData
+  fetchData,
+  tableType
 }: Props) {
   const classes = useStyles();
-  const [schemaObj, setSchemaObj] = useState({schemaName:'', dateTimeFieldSpecs: []});
-  const [schemaName, setSchemaName] = useState("");
   const [tableObj, setTableObj] = useState(JSON.parse(JSON.stringify(defaultTableObj)));
+  const [schemaObj, setSchemaObj] = useState(JSON.parse(JSON.stringify(defaultSchemaObj)));
+  const [tableName, setTableName] = useState('');
+  const [columnName, setColumnName] = useState([]);
   const {dispatch} = React.useContext(NotificationContext);
+  let isError = false;
 
-  useEffect(() => {
-    let newSchemaObj = {...schemaObj};
-    newSchemaObj.schemaName = tableObj.tableName;
-    setSchemaObj(newSchemaObj);
-    setTimeout(()=>{setSchemaName(tableObj.tableName);},0);
-  }, [tableObj])
+  useEffect(()=>{
+    if(tableName !== tableObj.tableName){
+      setTableName(tableObj.tableName);
+      clearTimeout(timerId);
+      timerId = setTimeout(()=>{
+        updateSchemaObj(tableObj.tableName);
+      }, 1000);
+    }
+  }, [tableObj]);
+
+  useEffect(()=>{
+    setTableObj({...tableObj,"tableType":tableType})
+  },[])
 
-  const validateSchema = async () => {
-    const validSchema = await PinotMethodUtils.validateSchemaAction(schemaObj);
-    if(validSchema.error || typeof validSchema === 'string'){
+  const updateSchemaObj = async (tableName) => {
+    //table name is same as schema name
+    const schemaObj = await PinotMethodUtils.getSchemaData(tableName);
+    if(schemaObj.error || typeof schemaObj === 'string'){
       dispatch({
         type: 'error',
-        message: validSchema.error || validSchema,
+        message: schemaObj.error || schemaObj,
         show: true
       });
-      return false;
+      setSchemaObj(defaultSchemaObj)
+    } else {
+      setSchemaObj({...defaultSchemaObj, ...schemaObj});
     }
-    return true;
-  };
+  }
+
+  const returnValue = (data,key) =>{
+    Object.keys(data).map(async (o)=>{
+    if(!_.isEmpty(data[o]) && typeof data[o] === "object"){
+        await returnValue(data[o],key);
+      }
+      else if(!_.isEmpty(data[o]) && _.isArray(data[o])){
+        data[o].map(async (obj)=>{
+          await returnValue(obj,key);
+        })
+     }else{
+        if(o === key && (data[key] === null || data[key] === "")){
+          dispatch({
+            type: 'error',
+            message: `${key} cannot be empty`,
+            show: true
+          });
+          isError = true;
+        }
+      }
+    })
+  }
+
+const checkFields = (tableObj,fields) => {
+    fields.forEach(async (o:any)=>{
+        if(tableObj[o.key] === undefined){
+          await returnValue(tableObj,o.key);
+        }else{
+          if((tableObj[o.key] === null || tableObj[o.key] === "")){
+            dispatch({
+              type: 'error',
+              message: `${o.label} cannot be empty`,
+              show: true
+            });
+            isError = true;
+          }
+        }
+    });
+  }
+
 
   const validateTableConfig = async () => {
+    const fields = [{key:"tableName",label:"Table Name"}];
+    await checkFields(tableObj,fields);
+    if(isError){
+      isError = false;
+      return false;
+    }
     const validTable = await PinotMethodUtils.validateTableAction(tableObj);
     if(validTable.error || typeof validTable === 'string'){
       dispatch({
@@ -166,30 +226,36 @@ export default function AddTableSchemaOp({
   };
 
   const handleSave = async () => {
-    if(await validateSchema() && await validateTableConfig()){
-      const schemaCreationResp = await PinotMethodUtils.saveSchemaAction(schemaObj);
-      dispatch({
-        type: (schemaCreationResp.error || typeof schemaCreationResp === 'string') ? 'error' : 'success',
-        message: schemaCreationResp.error || schemaCreationResp.status || schemaCreationResp,
-        show: true
-      });
+    if(await validateTableConfig()){
       const tableCreationResp = await PinotMethodUtils.saveTableAction(tableObj);
       dispatch({
         type: (tableCreationResp.error || typeof tableCreationResp === 'string') ? 'error' : 'success',
         message: tableCreationResp.error || tableCreationResp.status || tableCreationResp,
         show: true
       });
-      fetchData();
-      hideModal(null);
+      tableCreationResp.status && fetchData();
+      tableCreationResp.status && hideModal(null);
     }
   };
 
+  useEffect(()=>{
+    let columnName = [];
+    if(!_.isEmpty(schemaObj)){
+      tableNamekey.map((o)=>{
+        schemaObj[o] && schemaObj[o].map((obj)=>{
+          columnName.push(obj.name);
+        })
+      })
+    }
+    setColumnName(columnName);
+  },[schemaObj])
+
   return (
     <Dialog
       open={true}
       handleClose={hideModal}
       handleSave={handleSave}
-      title="Add Schema & Table"
+      title={`Add ${tableType} Table`}
       size="xl"
       disableBackdropClick={true}
       disableEscapeKeyDown={true}
@@ -198,24 +264,83 @@ export default function AddTableSchemaOp({
         <Grid container spacing={2}>
           <Grid item xs={12}>
             <SimpleAccordion
-              headerTitle="Add Schema"
+              headerTitle="Add Table"
               showSearchBox={false}
             >
-              <SchemaComponent
-                schemaName={schemaName}
-                setSchemaObj={(o)=>{setSchemaObj(o);}}
+              <AddTableComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
+                dateTimeFieldSpecs={schemaObj.dateTimeFieldSpecs}
+                disable={tableType !== ""}
               />
             </SimpleAccordion>
           </Grid>
           <Grid item xs={12}>
             <SimpleAccordion
-              headerTitle="Add Table"
+              headerTitle="Tenants"
               showSearchBox={false}
             >
-              <AddTableComponent
+              <AddOfflineTenantComponent
+                tableObj={{...tableObj}}
+                setTableObj={setTableObj}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Ingestion"
+              showSearchBox={false}
+            >
+              <AddIngestionComponent
+                tableObj={{...tableObj}}
+                setTableObj={setTableObj}
+                columnName={columnName}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Indexing & encoding"
+              showSearchBox={false}
+            >
+              <AddIndexingComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
+                columnName={columnName}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Partitioning & Routing"
+              showSearchBox={false}
+            >
+              <AddPartionComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
+                columnName={columnName}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Storage & Data retention"
+              showSearchBox={false}
+            >
+              <AddStorageComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Query"
+              showSearchBox={false}
+            >
+              <AddQueryComponent
                 tableObj={tableObj}
                 setTableObj={setTableObj}
-                dateTimeFieldSpecs={schemaObj.dateTimeFieldSpecs}
               />
             </SimpleAccordion>
           </Grid>
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddOfflineTenantComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddOfflineTenantComponent.tsx
new file mode 100644
index 0000000..4b1984d
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddOfflineTenantComponent.tsx
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, TextField, Theme} from '@material-ui/core';
+import { Autocomplete } from '@material-ui/lab';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 305
+    },
+    autoCompleteControl: {
+      '& .MuiFormControl-marginNormal': {
+        marginTop: 0
+      }
+    },
+    redColor: {
+      color: theme.palette.error.main
+    }
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function
+};
+
+export default function AddOfflineTenantComponent({
+  tableObj,
+  setTableObj
+}: Props) {
+  const classes = useStyles();
+
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+  const [tenantServer,setTenantServer] = useState('');
+  const [tenantBroker,setTenantBroker] = useState('');
+  const [serverOptions,setServerOptions] = useState([]);
+  const [brokerOptions,setBrokerOptions] = useState([]);
+  const [showRealtimeCompleted,setShowRealtimeCompleted] = useState(0);
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+        case 'broker':
+            setTenantBroker(value);
+            newTableObj.tenants.broker = value;
+        break;
+        case 'server':
+            setTenantServer(value);
+            newTableObj.tenants.server = value;
+        break;
+        case 'tagOverrideConfig':
+            setShowRealtimeCompleted(value);
+            if(value){
+                newTableObj.tenants.tagOverrideConfig = {};
+                newTableObj.tenants.tagOverrideConfig.realtimeCompleted = `${tenantServer}_OFFLINE`;
+            }else{
+                delete newTableObj.tenants.tagOverrideConfig;
+            }
+            break;
+        case 'showRealtimeCompleted':
+            newTableObj.tenants.tagOverrideConfig.realtimeCompleted = value;
+        break;
+        }
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+      if(!(tableDataObj.tenants.tagOverrideConfig && tableDataObj.tenants.tagOverrideConfig.realtimeCompleted)){
+        setShowRealtimeCompleted(0);
+      }
+      setTableDataObj(tableObj);
+  }, [tableObj]);
+
+
+  useEffect(()=>{
+      let serverOptions = [],
+      brokerOptions = [];
+      setServerOptions(serverOptions);
+      setBrokerOptions(brokerOptions);
+      setTenantServer(tableDataObj.tenants.server);
+      setTenantBroker(tableDataObj.tenants.broker);
+  },[])
+
+  const requiredAstrix = <span className={classes.redColor}>*</span>;
+  return (
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+        <FormControl className={classes.selectFormControl}>
+          <Autocomplete
+            key={'server'}
+            className={classes.autoCompleteControl}
+            value={tenantServer}
+            options={serverOptions}
+            onChange={(e, value)=> changeHandler('server', value ? value: '')}
+            disableClearable={true}
+            autoHighlight={true}
+            renderInput={(params) => (
+              <TextField
+                {...params}
+                label = {<>Server Tenant</>}
+                margin="normal"
+              />
+            )}
+          />
+        </FormControl>
+        <FormControl className={classes.selectFormControl}>
+          <Autocomplete
+            key={'broker'}
+            className={classes.autoCompleteControl}
+            value={tenantBroker}
+            options={brokerOptions}
+            onChange={(e, value)=> changeHandler('broker', value ? value: '')}
+            disableClearable={true}
+            autoHighlight={true}
+            renderInput={(params) => (
+              <TextField
+                {...params}
+                label = {<>Broker Tenant</>}
+                margin="normal"
+              />
+            )}
+          />
+        </FormControl>
+      </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddPartionComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddPartionComponent.tsx
new file mode 100644
index 0000000..d6d4559
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddPartionComponent.tsx
@@ -0,0 +1,259 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, Theme, Chip} from '@material-ui/core';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+      width: 320
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 320
+    },
+    redColor: {
+      color: theme.palette.error.main
+    }
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function,
+  columnName: Array<string>
+};
+
+export default function AddPartionComponent({
+  tableObj,
+  setTableObj,
+  columnName
+}: Props) {
+  const classes = useStyles();
+
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+  const [showPartition, setShowPartition] = useState(0);
+  const [showReplica, setShowReplica] = useState(0);
+  const ITEM_HEIGHT = 48;
+  const ITEM_PADDING_TOP = 8;
+  const MenuProps = {
+      PaperProps: {
+        style: {
+          maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+          width: 250,
+        },
+      },
+    };
+
+  const [columnNameTemp,setColumnNameTemp] = useState("");
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+      case 'segmentPrunerTypes':
+        // newTableObj[fieldName] = value;
+        setShowPartition(value);
+        if(value){
+          newTableObj.routing.segmentPrunerTypes = ["partition"];
+            newTableObj.tableIndexConfig.segmentPartitionConfig = {
+                columnPartitionMap : {
+                    "" : {
+                        functionName : "Murmur"
+                    }
+                }
+            }
+        }else{
+            newTableObj.tableIndexConfig.segmentPartitionConfig = null;
+        }
+      break;
+      case 'functionName':
+        newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].functionName = value;
+      break;
+      case 'numPartitions':
+        newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].numPartitions = value;
+      break;
+      case 'instanceSelectorType':
+          if(value){
+            newTableObj.routing.instanceSelectorType = "replicaGroup"
+          }
+          else{
+            delete newTableObj.routing.instanceSelectorType;
+          }
+          setShowReplica(value);
+        break;
+        case 'numReplicaGroups':
+          if(!(newTableObj.instanceAssignmentConfigMap && newTableObj.instanceAssignmentConfigMap["OFFLINE"])){
+            newTableObj.instanceAssignmentConfigMap = {
+              ["OFFLINE"]: {
+                   tagPoolConfig: {
+                     tag: "DefaultTenant_OFFLINE"
+                   },
+                  replicaGroupPartitionConfig: {
+                      replicaGroupBased: true,
+                      numReplicaGroups: null,
+                      numInstancesPerReplicaGroup:null
+                     }
+                 }
+             }
+          }
+            newTableObj.instanceAssignmentConfigMap["OFFLINE"].replicaGroupPartitionConfig.numReplicaGroups = value;
+        break;
+        case 'numInstancesPerReplicaGroup':
+          if(!(newTableObj.instanceAssignmentConfigMap && newTableObj.instanceAssignmentConfigMap["OFFLINE"])){
+            newTableObj.instanceAssignmentConfigMap = {
+              ["OFFLINE"]: {
+                   tagPoolConfig: {
+                     tag: "DefaultTenant_OFFLINE"
+                   },
+                  replicaGroupPartitionConfig: {
+                      replicaGroupBased: true,
+                      numReplicaGroups: null,
+                      numInstancesPerReplicaGroup:null
+                     }
+                 }
+             }
+          }
+            newTableObj.instanceAssignmentConfigMap["OFFLINE"].replicaGroupPartitionConfig.numInstancesPerReplicaGroup = value;
+        break;
+        case 'columnName':
+          newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[value] = newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp];
+          delete newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp];
+          setColumnNameTemp(value);
+        break;
+    };
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+    setTableDataObj(tableObj);
+  }, [tableObj]);
+
+  const requiredAstrix = <span className={classes.redColor}>*</span>;
+  return (
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+      <FormControl className={classes.selectFormControl}>
+          <InputLabel htmlFor="segmentPrunerTypes">Enable partitioning</InputLabel>
+          <Select
+            labelId="segmentPrunerTypes"
+            id="segmentPrunerTypes"
+            value={showPartition}
+            onChange={(e)=> changeHandler('segmentPrunerTypes', e.target.value)}
+          >
+            <MenuItem value={1}>True</MenuItem>
+            <MenuItem value={0}>False</MenuItem>
+          </Select>
+        </FormControl>
+         {
+            showPartition ?
+                <FormControl className={classes.selectFormControl}>
+                    <InputLabel htmlFor="columnName">Column Name {requiredAstrix}</InputLabel>
+                        <Select
+                            labelId="columnName"
+                            id="columnName"
+                            key="columnName"
+                            value={columnNameTemp}
+                            onChange={(e)=> changeHandler('columnName', e.target.value)}
+                            >
+                              {columnName.map((val)=>{
+                                                      return <MenuItem value={val}>{val}</MenuItem>
+                                                    })}
+                    </Select>
+                </FormControl> : null
+         }
+         {
+            showPartition ?
+                <FormControl className={classes.selectFormControl}>
+                    <InputLabel htmlFor="functionName">Function Name {requiredAstrix}</InputLabel>
+                        <Select
+                            labelId="functionNamePartition"
+                            id="functionNamePartition"
+                            key="functionName"
+                            value={tableDataObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].functionName}
+                            onChange={(e)=> changeHandler('functionName', e.target.value)}
+                           >
+
+                        <MenuItem value="Modulo">Modulo</MenuItem>
+                        <MenuItem value="Murmur">Murmur</MenuItem>
+                        <MenuItem value="ByteArray">ByteArray</MenuItem>
+                        <MenuItem value="HashCode">HashCode</MenuItem>
+                    </Select>
+                </FormControl> : null
+         }
+        {
+            showPartition ?
+                <FormControl className={classes.formControl} >
+                    <InputLabel htmlFor="numPartitions">Number of partitions​ {requiredAstrix}</InputLabel>
+                    <Input
+                        id="numPartitions"
+                        value={tableDataObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].numPartitions}
+                        onChange={(e)=> changeHandler('numPartitions', e.target.value)}
+                        type="number"
+                    />
+                </FormControl> : null }
+            </Grid>
+        <Grid item xs={12}>
+        <FormControl className={classes.selectFormControl}>
+            <InputLabel htmlFor="instanceSelectorType">Enable replica groups</InputLabel>
+            <Select
+            labelId="instanceSelectorType"
+            id="instanceSelectorType"
+            value={showReplica}
+            onChange={(e)=> changeHandler('instanceSelectorType', e.target.value)}
+            >
+                <MenuItem value={1}>True</MenuItem>
+                <MenuItem value={0}>False</MenuItem>
+            </Select>
+        </FormControl>
+
+        {
+            tableDataObj.routing.instanceSelectorType ?
+                <FormControl className={classes.formControl} >
+                    <InputLabel htmlFor="numReplicaGroups">Number of replica groups {requiredAstrix}</InputLabel>
+                    <Input
+                        id="numReplicaGroups"
+                        value={tableDataObj.instanceAssignmentConfigMap ? Number(tableDataObj.instanceAssignmentConfigMap["OFFLINE"].replicaGroupPartitionConfig.numReplicaGroups) : null}
+                        onChange={(e)=> changeHandler('numReplicaGroups', e.target.value)}
+                        type="number"
+                    />
+                </FormControl>
+            : null
+        }
+
+        {
+            tableDataObj.routing.instanceSelectorType ?
+                <FormControl className={classes.formControl} >
+                    <InputLabel htmlFor="numInstancesPerReplicaGroup">Number of instances per replica group​ {requiredAstrix}</InputLabel>
+                    <Input
+                        id="numInstancesPerReplicaGroup"
+                        value={tableDataObj.instanceAssignmentConfigMap ? Number(tableDataObj.instanceAssignmentConfigMap["OFFLINE"].replicaGroupPartitionConfig.numInstancesPerReplicaGroup) : null}
+                        onChange={(e)=> changeHandler('numInstancesPerReplicaGroup', e.target.value)}
+                        type="number"
+                    />
+                </FormControl>
+            : null
+        }
+      </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddQueryComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddQueryComponent.tsx
new file mode 100644
index 0000000..c8f08ac
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddQueryComponent.tsx
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, Theme} from '@material-ui/core';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    }
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function
+};
+
+export default function AddQueryComponent({
+  tableObj,
+  setTableObj
+}: Props) {
+  const classes = useStyles();
+
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+        case 'timeoutMs':
+          newTableObj.query[fieldName] = value;
+        break;
+        case 'maxQueriesPerSecond':
+          newTableObj.quota[fieldName] = value;
+        break;
+    }
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+    setTableDataObj(tableObj);
+  }, [tableObj]);
+
+  return (
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+        <FormControl className={classes.formControl} >
+          <InputLabel htmlFor="timeoutMs">Query Timeout Ms</InputLabel>
+          <Input
+            id="timeoutMs"
+            key="timeoutMs"
+            value={tableDataObj.query.timeoutMs || ""}
+            onChange={(e)=> changeHandler('timeoutMs', e.target.value)}
+            type="number"
+          />
+        </FormControl>
+        <FormControl className={classes.formControl} >
+          <InputLabel htmlFor="maxQueriesPerSecond">Queries Per Second</InputLabel>
+          <Input
+            id="maxQueriesPerSecond"
+            key="maxQueriesPerSecond"
+            value={tableDataObj.quota.maxQueriesPerSecond || ""}
+            onChange={(e)=> changeHandler('maxQueriesPerSecond', e.target.value)}
+            type="number"
+          />
+        </FormControl>
+      </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealTimeIngestionComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealTimeIngestionComponent.tsx
new file mode 100644
index 0000000..d9fab60
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealTimeIngestionComponent.tsx
@@ -0,0 +1,167 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, Theme} from '@material-ui/core';
+import AddDeleteComponent from './AddDeleteComponent';
+import MultipleSelectComponent from './MultipleSelectComponent';
+import _ from 'lodash';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 220
+    },
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function,
+  columnName: Array<string>
+};
+
+export default function AddRealTimeIngestionComponent({
+  tableObj,
+  setTableObj,
+  columnName
+}: Props) {
+  const classes = useStyles();
+  const [newSize,setNewSize] = useState([0,1])
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+      case 'filterConfig':
+        newTableObj.ingestionConfig.filterConfig[fieldName] = value;
+      break;
+      case 'segmentPushFrequency':
+        newTableObj.segmentsConfig[fieldName] = value;
+      break;
+      case 'segmentPushType':
+        newTableObj.segmentsConfig[fieldName] = value;
+      break;
+      case 'streamConfigs':
+        newTableObj.tableIndexConfig.streamConfigs = {...value};
+      break;
+      case 'filterFunction':
+          if(!newTableObj.ingestionConfig.filterConfig){
+            newTableObj.ingestionConfig.filterConfig = {};
+          }
+        newTableObj.ingestionConfig.filterConfig.filterFunction = value;
+      break;
+      case 'transformConfigs':
+        tableDataObj.ingestionConfig.transformConfigs = value;
+    };
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+    let newTableObj = {...tableObj};
+      if(newTableObj.tableType === "REALTIME" && !newTableObj.tableIndexConfig.streamConfigs && _.isEmpty(newTableObj.tableIndexConfig.streamConfigs) ){
+        newTableObj.tableIndexConfig.streamConfigs =
+        {
+            "streamType": "kafka",
+            "stream.kafka.topic.name": "",
+            "stream.kafka.broker.list": "",
+            "stream.kafka.consumer.type": "lowlevel",
+            "stream.kafka.consumer.prop.auto.offset.reset": "smallest",
+            "stream.kafka.consumer.factory.class.name":"org.apache.pinot.plugin.stream.kafka20.KafkaConsumerFactory",
+            "stream.kafka.decoder.class.name":"org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder",
+            "realtime.segment.flush.threshold.rows": "0",
+            "realtime.segment.flush.threshold.time": "24h",
+            "realtime.segment.flush.segment.size": "100M"
+        }
+        setTableObj(newTableObj);
+      }else if(newTableObj.tableType !== "REALTIME" && newTableObj.tableIndexConfig.streamConfigs){
+        newTableObj.tableIndexConfig.streamConfigs = null;
+        setTableObj(newTableObj);
+      }
+    setTableDataObj(newTableObj);
+  }, [tableObj]);
+
+  useEffect(()=>{
+    setNewSize(newSize);
+  },[newSize])
+
+  return (
+    <Grid container spacing={2}>
+        <Grid item xs={12}>
+            {
+                tableDataObj.tableType === "OFFLINE" ?
+                    <FormControl className={classes.selectFormControl}>
+                        <InputLabel htmlFor="segmentPushFrequency">Offline push frequency</InputLabel>
+                        <Select
+                            labelId="segmentPushFrequency"
+                            id="segmentPushFrequency"
+                            value={tableDataObj.segmentsConfig.segmentPushFrequency !== "" ? tableDataObj.segmentsConfig.segmentPushFrequency : ""}
+                            onChange={(e)=> changeHandler('segmentPushFrequency', e.target.value)}
+                        >
+                            <MenuItem value="HOURLY">HOURLY</MenuItem>
+                            <MenuItem value="DAILY">DAILY</MenuItem>
+                        </Select>
+                    </FormControl> : null
+            }
+            {
+                tableDataObj.tableType === "OFFLINE" ?
+                    <FormControl className={classes.selectFormControl}>
+                        <InputLabel htmlFor="segmentPushType">Offline push type</InputLabel>
+                        <Select
+                            labelId="segmentPushType"
+                            id="segmentPushType"
+                            value={tableDataObj.segmentsConfig.segmentPushType !== "" ? tableDataObj.segmentsConfig.segmentPushType : ""}
+                            onChange={(e)=> changeHandler('segmentPushType', e.target.value)}
+                        >
+                            <MenuItem value="APPEND">APPEND</MenuItem>
+                            <MenuItem value="REFRESH">REFRESH</MenuItem>
+                        </Select>
+                    </FormControl> : null
+            }
+            <FormControl className={classes.formControl}>
+                <InputLabel htmlFor="filterFunction">Filter function</InputLabel>
+                <Input
+                    id="filterFunction"
+                    value={tableObj.ingestionConfig.filterConfig && tableObj.ingestionConfig.filterConfig.filterFunction || ""}
+                    onChange={(e)=> changeHandler('filterFunction', e.target.value)}
+                />
+            </FormControl>
+            <MultipleSelectComponent
+                key = {"transformConfigs"}
+                streamConfigsObj = {tableDataObj.ingestionConfig.transformConfigs || []}
+                changeHandler = {changeHandler}
+                columnName= {columnName}/>
+            {
+                tableDataObj.tableIndexConfig.streamConfigs ?
+                <AddDeleteComponent
+                    key = {"streamConfigs"}
+                    streamConfigsObj = {{...tableDataObj.tableIndexConfig.streamConfigs}}
+                    changeHandler = {changeHandler}/>
+                : null
+            }
+          </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealTimePartionComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealTimePartionComponent.tsx
new file mode 100644
index 0000000..c5126e0
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealTimePartionComponent.tsx
@@ -0,0 +1,230 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, Theme, Chip} from '@material-ui/core';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+      width: 300
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 300
+    },
+    redColor: {
+      color: theme.palette.error.main
+    }
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function,
+  columnName: Array<string>
+};
+
+export default function AddRealTimePartionComponent({
+  tableObj,
+  setTableObj,
+  columnName
+}: Props) {
+  const classes = useStyles();
+
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+  const [showPartition, setShowPartition] = useState(0);
+  const [showReplica, setShowReplica] = useState(0);
+  const ITEM_HEIGHT = 48;
+  const ITEM_PADDING_TOP = 8;
+  const MenuProps = {
+      PaperProps: {
+        style: {
+          maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+          width: 250,
+        },
+      },
+    };
+  const [columnNameTemp,setColumnNameTemp] = useState("");
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+      case 'segmentPrunerTypes':
+        // newTableObj[fieldName] = value;
+        setShowPartition(value);
+        if(value){
+          newTableObj.routing.segmentPrunerTypes = ["partition"];
+            newTableObj.tableIndexConfig.segmentPartitionConfig = {
+                columnPartitionMap : {
+                    "" : {
+                        functionName : "Murmur"
+                    }
+                }
+            }
+        }else{
+            newTableObj.tableIndexConfig.segmentPartitionConfig = null;
+        }
+      break;
+      case 'functionName':
+        newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].functionName = value;
+      break;
+      case 'numPartitions':
+        newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].numPartitions = value;
+      break;
+      case 'instanceSelectorType':
+          if(value){
+            newTableObj.routing.instanceSelectorType = "replicaGroup"
+          }
+          else{
+            delete newTableObj.routing.instanceSelectorType;
+          }
+          setShowReplica(value);
+        break;
+        case 'numReplicaGroups':
+          if(!(newTableObj.instanceAssignmentConfigMap && newTableObj.instanceAssignmentConfigMap["OFFLINE"])){
+            newTableObj.instanceAssignmentConfigMap = {
+              ["OFFLINE"]: {
+                   tagPoolConfig: {
+                     tag: "DefaultTenant_OFFLINE"
+                   },
+                  replicaGroupPartitionConfig: {
+                      replicaGroupBased: true,
+                      numReplicaGroups: null,
+                      numInstancesPerReplicaGroup:null
+                     }
+                 }
+             }
+          }
+            newTableObj.instanceAssignmentConfigMap["OFFLINE"].replicaGroupPartitionConfig.numReplicaGroups = value;
+        break;
+        case 'numInstancesPerReplicaGroup':
+          if(!(newTableObj.instanceAssignmentConfigMap && newTableObj.instanceAssignmentConfigMap["OFFLINE"])){
+            newTableObj.instanceAssignmentConfigMap = {
+              ["OFFLINE"]: {
+                   tagPoolConfig: {
+                     tag: "DefaultTenant_OFFLINE"
+                   },
+                  replicaGroupPartitionConfig: {
+                      replicaGroupBased: true,
+                      numReplicaGroups: null,
+                      numInstancesPerReplicaGroup:null
+                     }
+                 }
+             }
+          }
+            newTableObj.instanceAssignmentConfigMap["OFFLINE"].replicaGroupPartitionConfig.numInstancesPerReplicaGroup = value;
+        break;
+        case 'columnName':
+          newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[value] = newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp];
+          delete newTableObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp];
+          setColumnNameTemp(value);
+        break;
+    };
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+    setTableDataObj(tableObj);
+  }, [tableObj]);
+
+  const requiredAstrix = <span className={classes.redColor}>*</span>;
+
+  return (
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+      <FormControl className={classes.selectFormControl}>
+          <InputLabel htmlFor="segmentPrunerTypes">Enable partitioning</InputLabel>
+          <Select
+            labelId="segmentPrunerTypes"
+            id="segmentPrunerTypes"
+            value={showPartition}
+            onChange={(e)=> changeHandler('segmentPrunerTypes', e.target.value)}
+          >
+            <MenuItem value={1}>True</MenuItem>
+            <MenuItem value={0}>False</MenuItem>
+          </Select>
+        </FormControl>
+         {
+            showPartition ?
+                <FormControl className={classes.selectFormControl}>
+                    <InputLabel htmlFor="columnName">Column Name {requiredAstrix}</InputLabel>
+                        <Select
+                            labelId="columnName"
+                            id="columnName"
+                            key="columnName"
+                            value={columnNameTemp}
+                            onChange={(e)=> changeHandler('columnName', e.target.value)}
+                            >
+                              {columnName.map((val)=>{
+                                                      return <MenuItem value={val}>{val}</MenuItem>
+                                                    })}
+                    </Select>
+                </FormControl> : null
+         }
+         {
+            showPartition ?
+                <FormControl className={classes.selectFormControl}>
+                    <InputLabel htmlFor="functionName">Function Name {requiredAstrix}</InputLabel>
+                        <Select
+                            labelId="functionNamePartition"
+                            id="functionNamePartition"
+                            key="functionName"
+                            value={tableDataObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].functionName}
+                            onChange={(e)=> changeHandler('functionName', e.target.value)}
+                           >
+                        <MenuItem value="Modulo">Modulo</MenuItem>
+                        <MenuItem value="Murmur">Murmur</MenuItem>
+                        <MenuItem value="ByteArray">ByteArray</MenuItem>
+                        <MenuItem value="HashCode">HashCode</MenuItem>
+                    </Select>
+                </FormControl> : null
+         }
+        {
+            showPartition ?
+                <FormControl className={classes.formControl} >
+                    <InputLabel htmlFor="numPartitions">Number of partitions​ {requiredAstrix}</InputLabel>
+                    <Input
+                        id="numPartitions"
+                        value={tableDataObj.tableIndexConfig.segmentPartitionConfig.columnPartitionMap[columnNameTemp].numPartitions}
+                        onChange={(e)=> changeHandler('numPartitions', e.target.value)}
+                        type="number"
+                    />
+                </FormControl> : null }
+            </Grid>
+        <Grid item xs={12}>
+        <FormControl className={classes.selectFormControl}>
+            <InputLabel htmlFor="instanceSelectorType">Enable replica groups</InputLabel>
+            <Select
+            labelId="instanceSelectorType"
+            id="instanceSelectorType"
+            value={showReplica}
+            onChange={(e)=> changeHandler('instanceSelectorType', e.target.value)}
+            >
+                <MenuItem value={1}>True</MenuItem>
+                <MenuItem value={0}>False</MenuItem>
+            </Select>
+        </FormControl>
+      </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealtimeTableComponent.tsx
similarity index 97%
copy from pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableComponent.tsx
copy to pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealtimeTableComponent.tsx
index 2867df3..0efca0c 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableComponent.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealtimeTableComponent.tsx
@@ -46,12 +46,14 @@ type Props = {
   tableObj: any,
   setTableObj: Function,
   dateTimeFieldSpecs: Array<any>
+  disable:boolean
 };
 
-export default function AddTableComponent({
+export default function AddRealtimeTableComponent({
   tableObj,
   setTableObj,
-  dateTimeFieldSpecs
+  dateTimeFieldSpecs,
+  disable
 }: Props) {
   const classes = useStyles();
 
@@ -122,7 +124,7 @@ export default function AddTableComponent({
             onChange={(e)=> changeHandler('tableName', e.target.value)}
           />
         </FormControl>
-        
+
         <FormControl className={classes.selectFormControl}>
           <InputLabel htmlFor="tableType">Table Type {requiredAstrix}</InputLabel>
           <Select
@@ -130,6 +132,7 @@ export default function AddTableComponent({
             id="tableType"
             value={tableDataObj.tableType}
             onChange={(e)=> changeHandler('tableType', e.target.value)}
+            disabled={disable}
           >
             <MenuItem value="OFFLINE">OFFLINE</MenuItem>
             <MenuItem value="REALTIME">REALTIME</MenuItem>
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealtimeTableOp.tsx
similarity index 61%
rename from pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableOp.tsx
rename to pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealtimeTableOp.tsx
index 8b4bac1..08a95c7 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableOp.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddRealtimeTableOp.tsx
@@ -25,6 +25,15 @@ import AddTableComponent from './AddTableComponent';
 import CustomCodemirror from '../../CustomCodemirror';
 import PinotMethodUtils from '../../../utils/PinotMethodUtils';
 import { NotificationContext } from '../../Notification/NotificationContext';
+import AddTenantComponent from './AddTenantComponent';
+import AddIngestionComponent from './AddIngestionComponent';
+import AddIndexingComponent from './AddIndexingComponent';
+import AddPartionComponent from './AddPartionComponent';
+import AddStorageComponent from './AddStorageComponent';
+import AddQueryComponent from './AddQueryComponent';
+import _ from 'lodash';
+import AddRealTimeIngestionComponent from './AddRealTimeIngestionComponent';
+import AddRealTimePartionComponent from './AddRealTimePartionComponent';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -41,7 +50,8 @@ const useStyles = makeStyles((theme: Theme) =>
 
 type Props = {
   hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
-  fetchData: Function
+  fetchData: Function,
+  tableType: String
 };
 
 const defaultTableObj = {
@@ -59,8 +69,6 @@ const defaultTableObj = {
     "replicasPerPartition": "1",
     "retentionTimeUnit": null,
     "retentionTimeValue": null,
-    "segmentPushType": "APPEND",
-    "segmentPushFrequency": "HOURLY",
     "completionConfig": null,
     "crypterClassName": null,
     "peerSegmentDownloadScheme": null
@@ -91,8 +99,6 @@ const defaultTableObj = {
       "stream.kafka.consumer.prop.auto.offset.reset": "smallest",
       "stream.kafka.consumer.factory.class.name": "org.apache.pinot.plugin.stream.kafka20.KafkaConsumerFactory",
       "stream.kafka.decoder.class.name": "org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder",
-      "stream.kafka.decoder.prop.schema.registry.rest.url": null,
-      "stream.kafka.decoder.prop.schema.registry.schema.name": null,
       "realtime.segment.flush.threshold.rows": "0",
       "realtime.segment.flush.threshold.time": "24h",
       "realtime.segment.flush.segment.size": "100M"
@@ -112,7 +118,6 @@ const defaultTableObj = {
     "segmentPrunerTypes": null,
     "instanceSelectorType": null
   },
-  "instanceAssignmentConfigMap": null,
   "query": {
     "timeoutMs": null
   },
@@ -130,15 +135,20 @@ const defaultSchemaObj = {
 
 let timerId = null;
 
-export default function AddTableOp({
+const tableNamekey = ["dimensionFieldSpecs","metricFieldSpecs","dateTimeFieldSpecs"];
+
+export default function AddRealtimeTableOp({
   hideModal,
-  fetchData
+  fetchData,
+  tableType
 }: Props) {
   const classes = useStyles();
   const [tableObj, setTableObj] = useState(JSON.parse(JSON.stringify(defaultTableObj)));
   const [schemaObj, setSchemaObj] = useState(JSON.parse(JSON.stringify(defaultSchemaObj)));
   const [tableName, setTableName] = useState('');
+  const [columnName, setColumnName] = useState([]);
   const {dispatch} = React.useContext(NotificationContext);
+  let isError = false;
 
   useEffect(()=>{
     if(tableName !== tableObj.tableName){
@@ -150,6 +160,10 @@ export default function AddTableOp({
     }
   }, [tableObj]);
 
+  useEffect(()=>{
+    setTableObj({...tableObj,"tableType":tableType})
+  },[])
+
   const updateSchemaObj = async (tableName) => {
     //table name is same as schema name
     const schemaObj = await PinotMethodUtils.getSchemaData(tableName);
@@ -164,8 +178,53 @@ export default function AddTableOp({
       setSchemaObj({...defaultSchemaObj, ...schemaObj});
     }
   }
-  
+
+  const returnValue = (data,key) =>{
+    Object.keys(data).map(async (o)=>{
+    if(!_.isEmpty(data[o]) && typeof data[o] === "object"){
+        await returnValue(data[o],key);
+      }
+      else if(!_.isEmpty(data[o]) && _.isArray(data[o])){
+        data[o].map(async (obj)=>{
+          await returnValue(obj,key);
+        })
+     }else{
+        if(o === key && (data[key] === null || data[key] === "")){
+          dispatch({
+            type: 'error',
+            message: `${key} cannot be empty`,
+            show: true
+          });
+          isError = true;
+        }
+      }
+    })
+  }
+
+const checkFields = (tableObj,fields) => {
+    fields.forEach(async (o:any)=>{
+        if(tableObj[o.key] === undefined){
+          await returnValue(tableObj,o.key);
+        }else{
+          if((tableObj[o.key] === null || tableObj[o.key] === "")){
+            dispatch({
+              type: 'error',
+              message: `${o.label} cannot be empty`,
+              show: true
+            });
+            isError = true;
+          }
+        }
+    });
+  }
+
   const validateTableConfig = async () => {
+    const fields = [{key:"tableName",label:"Table Name"},{key:"tableType",label:"Table Type"},{key:"stream.kafka.broker.list",label:"stream.kafka.broker.list"},{key:"stream.kafka.topic.name",label:"stream.kafka.topic.name"},{key:"stream.kafka.consumer.type",label:"stream.kafka.consumer.type"},{key:"stream.kafka.decoder.class.name",label:"stream.kafka.decoder.class.name"}];
+    await checkFields(tableObj,fields);
+    if(isError){
+      isError  = false;
+      return false;
+    }
     const validTable = await PinotMethodUtils.validateTableAction(tableObj);
     if(validTable.error || typeof validTable === 'string'){
       dispatch({
@@ -191,12 +250,24 @@ export default function AddTableOp({
     }
   };
 
+  useEffect(()=>{
+    let columnName = [];
+    if(!_.isEmpty(schemaObj)){
+      tableNamekey.map((o)=>{
+        schemaObj[o] && schemaObj[o].map((obj)=>{
+          columnName.push(obj.name);
+        })
+      })
+    }
+    setColumnName(columnName);
+  },[schemaObj])
+
   return (
     <Dialog
       open={true}
       handleClose={hideModal}
       handleSave={handleSave}
-      title="Add Table"
+      title={`Add ${tableType} Table`}
       size="xl"
       disableBackdropClick={true}
       disableEscapeKeyDown={true}
@@ -212,6 +283,76 @@ export default function AddTableOp({
                 tableObj={tableObj}
                 setTableObj={setTableObj}
                 dateTimeFieldSpecs={schemaObj.dateTimeFieldSpecs}
+                disable={tableType !== ""}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Tenants"
+              showSearchBox={false}
+            >
+              <AddTenantComponent
+                tableObj={{...tableObj}}
+                setTableObj={setTableObj}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Ingestion"
+              showSearchBox={false}
+            >
+              <AddRealTimeIngestionComponent
+                tableObj={{...tableObj}}
+                setTableObj={setTableObj}
+                columnName={columnName}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Indexing & encoding"
+              showSearchBox={false}
+            >
+              <AddIndexingComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
+                columnName={columnName}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Partitioning & Routing"
+              showSearchBox={false}
+            >
+              <AddRealTimePartionComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
+                columnName={columnName}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Storage & Data retention"
+              showSearchBox={false}
+            >
+              <AddStorageComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Query"
+              showSearchBox={false}
+            >
+              <AddQueryComponent
+                tableObj={tableObj}
+                setTableObj={setTableObj}
               />
             </SimpleAccordion>
           </Grid>
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddSchemaOp.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddSchemaOp.tsx
new file mode 100644
index 0000000..72011cd
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddSchemaOp.tsx
@@ -0,0 +1,203 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, DialogContent, Grid, makeStyles, Theme} from '@material-ui/core';
+import Dialog from '../../CustomDialog';
+import SimpleAccordion from '../../SimpleAccordion';
+import SchemaComponent from './SchemaComponent';
+import CustomCodemirror from '../../CustomCodemirror';
+import PinotMethodUtils from '../../../utils/PinotMethodUtils';
+import { NotificationContext } from '../../Notification/NotificationContext';
+import _ from 'lodash';
+import SchemaNameComponent from './SchemaNameComponent';
+import CustomizedTables from '../../Table';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    sqlDiv: {
+      border: '1px #BDCCD9 solid',
+      borderRadius: 4,
+      marginBottom: '20px',
+    },
+    queryOutput: {
+      '& .CodeMirror': { height: '532px !important' },
+    },
+  })
+);
+
+type Props = {
+  hideModal: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void,
+  fetchData: Function
+};
+
+export default function AddSchemaOp({
+  hideModal,
+  fetchData
+}: Props) {
+  const classes = useStyles();
+  const [schemaObj, setSchemaObj] = useState({schemaName:'', dateTimeFieldSpecs: []});
+  const {dispatch} = React.useContext(NotificationContext);
+  let isError = false
+
+    const returnValue = (data,key) =>{
+        Object.keys(data).map(async (o)=>{
+          if(!_.isEmpty(data[o]) && typeof data[o] === "object"){
+            await returnValue(data[o],key);
+          }
+          else if(!_.isEmpty(data[o]) && _.isArray(data[o])){
+            data[o].map(async (obj)=>{
+              await returnValue(obj,key);
+            })
+          }else{
+            if(o === key && (data[key] === null || data[key] === "")){
+              dispatch({
+                type: 'error',
+                message: `${key} cannot be empty`,
+                show: true
+              });
+              isError = true;
+            }
+          }
+        })
+      }
+
+      const checkFields = (tableObj,fields) => {
+        fields.forEach(async (o:any)=>{
+            if(tableObj[o.key] === undefined){
+              await returnValue(tableObj,o.key);
+            }else{
+              if((tableObj[o.key] === null || tableObj[o.key] === "")){
+                dispatch({
+                  type: 'error',
+                  message: `${o.label} cannot be empty`,
+                  show: true
+                });
+                isError = true;
+              }
+            }
+        });
+      }
+
+      const isObjEmpty = () =>{
+        const types = ["dimensionFieldSpecs","metricFieldSpecs","dateTimeFieldSpecs"];
+        let notEmpty = true;
+        types.map((t)=>{
+          if(schemaObj[t].length)
+          {
+            notEmpty = false
+          }
+        })
+        return notEmpty;
+      }
+
+  const validateSchema = async () => {
+    const validSchema = await PinotMethodUtils.validateSchemaAction(schemaObj);
+    if(validSchema.error || typeof validSchema === 'string'){
+      dispatch({
+        type: 'error',
+        message: validSchema.error || validSchema,
+        show: true
+      });
+      return false;
+    }
+    return true;
+  };
+
+  const handleSave = async () => {
+    const fields = [{key:"schemaName",label:"schema Name"},{key:"name",label:"Column Name"},{key:"dataType",label:"Data Type"}];
+    await checkFields(schemaObj,fields);
+    if(isError){
+      isError = false;
+      return false;
+    }
+    if(!isObjEmpty()){
+    if(await validateSchema()){
+      const schemaCreationResp = await PinotMethodUtils.saveSchemaAction(schemaObj);
+      dispatch({
+        type: (schemaCreationResp.error || typeof schemaCreationResp === 'string') ? 'error' : 'success',
+        message: schemaCreationResp.error || schemaCreationResp.status || schemaCreationResp,
+        show: true
+      });
+      if(!schemaCreationResp.error && typeof schemaCreationResp !== 'string'){
+        fetchData();
+        hideModal(null);
+      }
+    }
+    }else{
+        dispatch({
+          type: 'error',
+          message: "Please Enter atleast one Type",
+          show: true
+        });
+      }
+  };
+
+  return (
+    <Dialog
+      open={true}
+      handleClose={hideModal}
+      handleSave={handleSave}
+      title="Add Schema"
+      size="xl"
+      disableBackdropClick={true}
+      disableEscapeKeyDown={true}
+    >
+      <DialogContent>
+        <Grid container spacing={2}>
+          <Grid item xs={12}>
+            <SimpleAccordion
+              headerTitle="Add Schema"
+              showSearchBox={false}
+            >
+              <SchemaComponent
+                schemaObj={schemaObj}
+                schemaName={schemaObj.schemaName}
+                setSchemaObj={(o)=>{setSchemaObj(o);}}
+              />
+            </SimpleAccordion>
+          </Grid>
+          <Grid item xs={6}>
+            <div className={classes.sqlDiv}>
+              <SimpleAccordion
+                headerTitle="Schema Config (Read Only)"
+                showSearchBox={false}
+              >
+                <CustomCodemirror
+                  customClass={classes.queryOutput}
+                  data={schemaObj}
+                  isEditable={false}
+                  returnCodemirrorValue={(newValue)=>{
+                    try{
+                      const jsonObj = JSON.parse(newValue);
+                      if(jsonObj){
+                        jsonObj.segmentsConfig.replicasPerPartition = jsonObj.segmentsConfig.replication;
+                        setSchemaObj(jsonObj);
+                      }
+                    }catch(e){}
+                  }}
+                />
+              </SimpleAccordion>
+            </div>
+          </Grid>
+        </Grid>
+      </DialogContent>
+    </Dialog>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddStorageComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddStorageComponent.tsx
new file mode 100644
index 0000000..37bf2b4
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddStorageComponent.tsx
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, Theme} from '@material-ui/core';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 170
+    }
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function
+};
+
+export default function AddStorageComponent({
+  tableObj,
+  setTableObj
+}: Props) {
+  const classes = useStyles();
+
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+      case 'retentionTimeUnit':
+        newTableObj.segmentsConfig.retentionTimeUnit = value;
+      break;
+      case 'retentionTimeValue':
+        newTableObj.segmentsConfig.retentionTimeValue = value;
+      break;
+      case 'maxQueriesPerSecond':
+        newTableObj.quota.storage = value;
+      break;
+    };
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+    setTableDataObj(tableObj);
+  }, [tableObj]);
+
+  return (
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+
+      <FormControl className={classes.formControl} >
+          <InputLabel htmlFor="retentionTimeValue">Retention Value</InputLabel>
+          <Input
+            id="retentionTimeValue"
+            key="retentionTimeValue"
+            value={tableDataObj.segmentsConfig.retentionTimeValue || ""}
+            onChange={(e)=>
+                changeHandler('retentionTimeValue', e.target.value)
+            }
+            type="number"
+        />
+        </FormControl>
+
+        <FormControl className={classes.selectFormControl}>
+          <InputLabel htmlFor="retentionTimeUnit">Retention unit</InputLabel>
+          <Select
+            labelId="retentionTimeUnit"
+            id="retentionTimeUnit"
+            key="retentionTimeUnit"
+            value={tableDataObj.segmentsConfig.retentionTimeUnit && tableDataObj.segmentsConfig.retentionTimeUnit !== "" ? tableDataObj.segmentsConfig.retentionTimeUnit : ''}
+            onChange={(e)=>
+                changeHandler('retentionTimeUnit', e.target.value)
+            }
+          >
+            <MenuItem value="HOURS">HOURS</MenuItem>
+            <MenuItem value="DAYS">DAYS</MenuItem>
+          </Select>
+        </FormControl>
+
+        <FormControl className={classes.formControl} >
+          <InputLabel htmlFor="maxQueriesPerSecond">Storage Quota</InputLabel>
+          <Input
+            id="maxQueriesPerSecond"
+            key="maxQueriesPerSecond"
+            value={tableDataObj.quota.storage || ""}
+            onChange={(e)=>
+                changeHandler('maxQueriesPerSecond', e.target.value)
+            }
+          />
+        </FormControl>
+      </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableComponent.tsx
index 2867df3..13de6c9 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableComponent.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTableComponent.tsx
@@ -46,12 +46,14 @@ type Props = {
   tableObj: any,
   setTableObj: Function,
   dateTimeFieldSpecs: Array<any>
+  disable:boolean
 };
 
 export default function AddTableComponent({
   tableObj,
   setTableObj,
-  dateTimeFieldSpecs
+  dateTimeFieldSpecs,
+  disable
 }: Props) {
   const classes = useStyles();
 
@@ -130,12 +132,12 @@ export default function AddTableComponent({
             id="tableType"
             value={tableDataObj.tableType}
             onChange={(e)=> changeHandler('tableType', e.target.value)}
+            disabled={disable}
           >
             <MenuItem value="OFFLINE">OFFLINE</MenuItem>
             <MenuItem value="REALTIME">REALTIME</MenuItem>
           </Select>
         </FormControl>
-
         <FormControl className={classes.selectFormControl}>
           <Autocomplete
             className={classes.autoCompleteControl}
@@ -155,7 +157,7 @@ export default function AddTableComponent({
         </FormControl>
 
         <FormControl className={classes.selectFormControl}>
-          <InputLabel htmlFor="replication">Replication {requiredAstrix}</InputLabel>
+          <InputLabel htmlFor="replication">Replication</InputLabel>
           <Select
             labelId="replication"
             id="replication"
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTenantComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTenantComponent.tsx
new file mode 100644
index 0000000..f92bddc
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/AddTenantComponent.tsx
@@ -0,0 +1,172 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, TextField, Theme} from '@material-ui/core';
+import { Autocomplete } from '@material-ui/lab';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 305
+    },
+    autoCompleteControl: {
+      '& .MuiFormControl-marginNormal': {
+        marginTop: 0
+      }
+    },
+    redColor: {
+      color: theme.palette.error.main
+    }
+  })
+);
+
+type Props = {
+  tableObj: any,
+  setTableObj: Function
+};
+
+export default function AddTenantComponent({
+  tableObj,
+  setTableObj
+}: Props) {
+  const classes = useStyles();
+
+  const [tableDataObj, setTableDataObj] = useState(tableObj);
+  const [tenantServer,setTenantServer] = useState('');
+  const [tenantBroker,setTenantBroker] = useState('');
+  const [serverOptions,setServerOptions] = useState([]);
+  const [brokerOptions,setBrokerOptions] = useState([]);
+  const [showRealtimeCompleted,setShowRealtimeCompleted] = useState(0);
+
+  const changeHandler = (fieldName, value) => {
+    let newTableObj = {...tableDataObj};
+    switch(fieldName){
+        case 'broker':
+            setTenantBroker(value);
+            newTableObj.tenants.broker = value;
+        break;
+        case 'server':
+            setTenantServer(value);
+            newTableObj.tenants.server = value;
+        break;
+        case 'tagOverrideConfig':
+            setShowRealtimeCompleted(value);
+            if(value){
+                newTableObj.tenants.tagOverrideConfig = {};
+                newTableObj.tenants.tagOverrideConfig.realtimeCompleted = `${tenantServer}_OFFLINE`;
+            }else{
+                delete newTableObj.tenants.tagOverrideConfig;
+            }
+            break;
+        case 'showRealtimeCompleted':
+            newTableObj.tenants.tagOverrideConfig.realtimeCompleted = value;
+        break;
+        }
+    setTableDataObj(newTableObj);
+    setTableObj(newTableObj);
+  };
+
+  useEffect(()=>{
+      if(!(tableDataObj.tenants.tagOverrideConfig && tableDataObj.tenants.tagOverrideConfig.realtimeCompleted)){
+        setShowRealtimeCompleted(0);
+      }
+      setTableDataObj(tableObj);
+  }, [tableObj]);
+
+
+  useEffect(()=>{
+      let serverOptions = [],
+      brokerOptions = [];
+      setServerOptions(serverOptions);
+      setBrokerOptions(brokerOptions);
+      setTenantServer(tableDataObj.tenants.server);
+      setTenantBroker(tableDataObj.tenants.broker);
+  },[])
+
+  const requiredAstrix = <span className={classes.redColor}>*</span>;
+  return (
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+        <FormControl className={classes.selectFormControl}>
+          <Autocomplete
+            key={'server'}
+            className={classes.autoCompleteControl}
+            value={tenantServer}
+            options={serverOptions}
+            onChange={(e, value)=> changeHandler('server', value ? value: '')}
+            disableClearable={true}
+            autoHighlight={true}
+            renderInput={(params) => (
+              <TextField
+                {...params}
+                label = {<>Server Tenant</>}
+                margin="normal"
+              />
+            )}
+          />
+        </FormControl>
+        <FormControl className={classes.selectFormControl}>
+          <Autocomplete
+            key={'broker'}
+            className={classes.autoCompleteControl}
+            value={tenantBroker}
+            options={brokerOptions}
+            onChange={(e, value)=> changeHandler('broker', value ? value: '')}
+            disableClearable={true}
+            autoHighlight={true}
+            renderInput={(params) => (
+              <TextField
+                {...params}
+                label = {<>Broker Tenant</>}
+                margin="normal"
+              />
+            )}
+          />
+        </FormControl>
+        <FormControl className={classes.selectFormControl}>
+          <InputLabel htmlFor="tagOverrideConfig">Relocate realtime completed segments?</InputLabel>
+          <Select
+            labelId="tagOverrideConfig"
+            id="tagOverrideConfig"
+            value={showRealtimeCompleted}
+            onChange={(e)=> changeHandler('tagOverrideConfig', e.target.value)}
+          >
+            <MenuItem value={1}>True</MenuItem>
+            <MenuItem value={0}>False</MenuItem>
+          </Select>
+        </FormControl>
+        { showRealtimeCompleted ?
+        <FormControl className={classes.formControl}>
+          <InputLabel htmlFor="realtimeCompleted">Relocate to tag</InputLabel>
+          <Input
+            id="realtimeCompleted"
+            value={tableDataObj.tenants.tagOverrideConfig.realtimeCompleted}
+            onChange={(e)=> changeHandler('showRealtimeCompleted', e.target.value)}
+          />
+        </FormControl> : null}
+      </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultiIndexingComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultiIndexingComponent.tsx
new file mode 100644
index 0000000..7b3f5f5
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultiIndexingComponent.tsx
@@ -0,0 +1,344 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, TextField, Theme, IconButton, Fab, Button, Chip} from '@material-ui/core';
+import AddCircleIcon from '@material-ui/icons/AddCircle';
+import ClearIcon from '@material-ui/icons/Clear';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      width: '100%'
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 170
+    },
+    deleteIcon:{
+        marginTop:15,
+        color: theme.palette.error.main
+    },
+    autoCompleteControl: {
+      '& .MuiFormControl-marginNormal': {
+        marginTop: 0
+      }
+    },
+    redColor: {
+      color: theme.palette.error.main
+    }
+  })
+);
+
+type Props = {
+    changeHandler: any,
+    streamConfigsObj: Object,
+    columnName:Array<string>,
+    textDataObj: Array<TextObj>
+};
+
+type TextObj = {
+    name:string,
+    encodingType:string,
+    indexType:string
+}
+
+export default function MultiIndexingComponent({
+    changeHandler,
+    streamConfigsObj,
+    columnName,
+    textDataObj
+}: Props) {
+    const classes = useStyles();
+    const ITEM_HEIGHT = 48;
+    const ITEM_PADDING_TOP = 8;
+    const MenuProps = {
+        PaperProps: {
+          style: {
+            maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+            width: 250,
+          },
+        },
+      };
+
+      const [jsonUpdated,setJsonUpdated] = useState(false);
+
+    const updateFieldForColumnName = (tempStreamConfigObj,values,Field,majorField) =>{
+        values.map((v)=>{
+            let valChanged = false;
+            tempStreamConfigObj.map((o)=>{
+                if(v === o.columnName){
+                    if("Encoding" === majorField)
+                    {
+                        o[majorField] = Field;
+                    }else{
+                        o[majorField].push(Field);
+                    }
+                    valChanged = true;
+                }
+            })
+            if(!valChanged){
+                tempStreamConfigObj.push({
+                    columnName : v,
+                    Indexing : [],
+                    Encoding: "",
+                    [majorField] : "Encoding" === majorField ? Field : [Field]
+                })
+            }
+        })
+        return tempStreamConfigObj;
+    }
+
+    const convertInputToData = (input,isJsonUpdated) =>{
+            let tempStreamConfigObj = [];
+            Object.keys(input).map((o)=>{
+                switch(o){
+                    case "invertedIndexColumns":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"Inverted","Indexing");
+                    break;
+                    case "rangeIndexColumns":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"Range","Indexing");
+                    break;
+                    case "sortedColumn":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"Sorted","Indexing");
+                    break;
+                    case "bloomFilterColumns":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"bloomFilter","Indexing");
+                    break;
+                    case "noDictionaryColumns":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"None","Encoding");
+                    break;
+                    case "DictionaryColumns":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"Dictionary","Encoding");
+                    break;
+                    case "onHeapDictionaryColumns":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"heapDictionary","Encoding");
+                    break;
+                    case "varLengthDictionaryColumns":
+                        tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,input[o],"varDictionary","Encoding");
+                    break;
+                }
+            })
+            if(textDataObj && textDataObj.length){
+                textDataObj.map((o)=>{
+                    tempStreamConfigObj = updateFieldForColumnName(tempStreamConfigObj,[o.name],"Text","Indexing");
+                })
+            }
+            return tempStreamConfigObj;
+    }
+
+    const [streamConfigObj, setStreamConfigObj] = useState(convertInputToData(streamConfigsObj,false));
+
+    const addButtonClick = ()=>{
+        let data = {
+            columnName : "",
+            Indexing : [],
+            Encoding: ""
+        };
+        let tempStreamConfigObj = [...streamConfigObj,data]
+        setStreamConfigObj(tempStreamConfigObj);
+        // changeHandler('tableIndexConfig',convertDataToInput(tempStreamConfigObj));
+    }
+
+    const convertDataToInput = async (input) => {
+        let outputData = {
+            invertedIndexColumns: [],
+            rangeIndexColumns : [],
+            sortedColumn : [],
+            noDictionaryColumns : [],
+            DictionaryColumns: [],
+            onHeapDictionaryColumns : [],
+            varLengthDictionaryColumns :  [],
+            bloomFilterColumns : []
+        };
+        let fieldConfigList= [];
+
+        input.map((i)=>{
+            i.Indexing.map((o)=>{
+                switch(o){
+                    case "Sorted":
+                        outputData.sortedColumn.push(i.columnName);
+                    break;
+                    case "Inverted":
+                        outputData.invertedIndexColumns.push(i.columnName);
+                    break;
+                    case "Range":
+                        outputData.rangeIndexColumns.push(i.columnName);
+                    break;
+                    case "bloomFilter":
+                        outputData.bloomFilterColumns.push(i.columnName);
+                    break;
+                    case "Text":
+                        fieldConfigList.push({"name":i.columnName, "encodingType":"RAW", "indexType":"TEXT"})
+                    break;
+                }
+            })
+                switch(i.Encoding){
+                    case "None":
+                        outputData.noDictionaryColumns.push(i.columnName);
+                    break;
+                    case "Dictionary":
+                        outputData.DictionaryColumns.push(i.columnName);
+                    break;
+                    case "heapDictionary":
+                        outputData.onHeapDictionaryColumns.push(i.columnName);
+                    break;
+                    case "varDictionary":
+                        outputData.varLengthDictionaryColumns.push(i.columnName);
+                    break;
+                }
+        });
+        return {
+            tableIndexConfig:outputData,
+            fieldConfigList
+        };
+    }
+
+    const keyChange = (input,index, value) =>{
+        let tempStreamConfigObj = [...streamConfigObj];
+        tempStreamConfigObj[index][input] = value;
+        setStreamConfigObj(tempStreamConfigObj);
+    }
+
+    const updateJson = async () =>{
+        changeHandler('tableIndexConfig',await convertDataToInput(streamConfigObj));
+    }
+
+    const deleteClick = async (ind) =>{
+        let tempStreamConfigObj = [...streamConfigObj];
+        tempStreamConfigObj.splice(ind,1);
+        setStreamConfigObj(tempStreamConfigObj);
+        changeHandler('tableIndexConfig',await convertDataToInput(tempStreamConfigObj));
+    }
+
+    useEffect(() => {
+        if(jsonUpdated){
+            setJsonUpdated(false);
+        }
+        else{
+            let value = convertInputToData(streamConfigsObj,true);
+            if(value.length >= streamConfigObj.length){
+                setStreamConfigObj(value);
+            }
+            setJsonUpdated(true);
+        }
+    }, [streamConfigsObj]);
+
+  return (
+    <Grid container spacing={2}>
+                {
+                    streamConfigObj && streamConfigObj.map((o,i)=>{
+                        return(
+                            <Grid item xs={6}>
+                                <div className="box-border">
+                                <Grid container spacing={2}>
+                                    <Grid item xs={3}>
+                                        <FormControl className={classes.formControl}>
+                                            <InputLabel htmlFor={o.columnName}>Column Name</InputLabel>
+                                            <Select
+                                                labelId={o.columnName}
+                                                id={o.columnName}
+                                                value={o.columnName}
+                                                key={i+"columnName"}
+                                                onChange={(e)=> keyChange("columnName",i,e.target.value)}
+                                                onBlur={updateJson}
+                                            >
+                                               {columnName.map((val)=>{
+                                                      return <MenuItem value={val}>{val}</MenuItem>
+                                                    })}
+                                            </Select>
+                                        </FormControl>
+                                    </Grid>
+                                    <Grid item xs={4}>
+                                        <FormControl className={classes.formControl}>
+                                        <InputLabel htmlFor={o.Encoding}>Encoding</InputLabel>
+                                             <Select
+                                                labelId={o.Encoding}
+                                                id={o.Encoding}
+                                                key={i+"Encoding"}
+                                                value={o.Encoding}
+                                                onChange={(e)=> keyChange("Encoding",i,e.target.value)}
+                                                onBlur={updateJson}
+                                            >
+                                                <MenuItem value="None">None</MenuItem>
+                                                <MenuItem value="Dictionary">Dictionary</MenuItem>
+                                                <MenuItem value="varDictionary">Variable Length Dictionary</MenuItem>
+                                                <MenuItem value="heapDictionary">On Heap Dictionary</MenuItem>
+                                            </Select>
+                                        </FormControl>
+                                    </Grid>
+                                    <Grid item xs={4}>
+                                        <FormControl className={classes.formControl}>
+                                        <InputLabel htmlFor={i+"keymulti"}>Indexing</InputLabel>
+                                                <Select
+                                                    labelId={i+"keymulti"}
+                                                    id={i+"keymulti"}
+                                                    key={i+"Indexing"}
+                                                    multiple
+                                                    value={o.Indexing || []}
+                                                    onChange={(e)=> keyChange("Indexing",i,e.target.value)}
+                                                    input={<Input id="select-multiple-chip" />}
+                                                    renderValue={(selected) => (
+                                                        <div className={"chips"}>
+                                                        {(selected as string[]).map((value) => (
+                                                            <Chip key={value} label={value} className={"chip"} />
+                                                        ))}
+                                                        </div>
+                                                    )}
+                                                    MenuProps={MenuProps}
+                                                    onBlur={updateJson}>
+                                                <MenuItem value="None">None</MenuItem>
+                                                <MenuItem value="Sorted">Sorted</MenuItem>
+                                                <MenuItem value="Inverted">Inverted</MenuItem>
+                                                <MenuItem value="Range">Range</MenuItem>
+                                                <MenuItem value="Text">Text</MenuItem>
+                                                <MenuItem value="bloomFilter">Bloom filter</MenuItem>
+                                            </Select>
+                                        </FormControl>
+                                    </Grid>
+                                    <Grid item xs={1}>
+                                        <FormControl>
+                                            <IconButton aria-label="delete" className={classes.deleteIcon} onClick={()=>{
+                                                deleteClick(i)}}>
+                                                <ClearIcon />
+                                            </IconButton>
+                                        </FormControl>
+                                    </Grid>
+                                </Grid>
+                            </div>
+                            </Grid>)
+                        })
+                }
+                <Grid item xs={3}>
+                <FormControl className={classes.formControl}>
+                    <Button
+                    aria-label="plus"
+                    variant="outlined"
+                    color="primary"
+                    onClick={addButtonClick}
+                    startIcon={(<AddCircleIcon />)}
+                    >
+                        Add new Field
+                    </Button>
+                </FormControl>
+                </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultiMetricComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultiMetricComponent.tsx
new file mode 100644
index 0000000..b73c943
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultiMetricComponent.tsx
@@ -0,0 +1,182 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, TextField, Theme, IconButton, Fab, Button, Chip} from '@material-ui/core';
+import AddIcon from '@material-ui/icons/Add';
+import AddCircleIcon from '@material-ui/icons/AddCircle';
+import ClearIcon from '@material-ui/icons/Clear';
+import DeleteIcon from '@material-ui/icons/Delete';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      width: '100%'
+    },
+    deleteIcon:{
+        marginTop:15,
+        color: theme.palette.error.main
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 170
+    },
+    autoCompleteControl: {
+      '& .MuiFormControl-marginNormal': {
+        marginTop: 0
+      }
+    },
+    redColor: {
+      color: theme.palette.error.main
+    }
+  })
+);
+
+type Props = {
+    changeHandler: any,
+  streamConfigsObj: Array<any>
+};
+
+export default function MultiMetricComponent({
+    changeHandler,
+    streamConfigsObj
+}: Props) {
+    const classes = useStyles();
+    const [streamConfigObj, setStreamConfigObj] = useState(streamConfigsObj);
+    const ITEM_HEIGHT = 48;
+    const ITEM_PADDING_TOP = 8;
+    const MenuProps = {
+        PaperProps: {
+          style: {
+            maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+            width: 250,
+          },
+        },
+      };
+
+    const addButtonClick = ()=>{
+        let data = {columnName:"",transformFunction:""};
+        let tempStreamConfigObj = [...streamConfigObj,data];
+        setStreamConfigObj(tempStreamConfigObj);
+        changeHandler('transformConfigs',tempStreamConfigObj);
+    }
+
+    const inputChange = (input,index, value) =>{
+        let tempStreamConfigObj = [...streamConfigObj]
+        tempStreamConfigObj[index][input] = value;
+        setStreamConfigObj(tempStreamConfigObj);
+    }
+
+    const updateJson = () =>{
+        changeHandler('transformConfigs',streamConfigObj);
+    }
+
+    const deleteClick = (index) =>{
+        let tempStreamConfigObj = {...streamConfigObj};
+        tempStreamConfigObj.splice(index,1);
+        changeHandler('transformConfigs',tempStreamConfigObj)
+    }
+
+    useEffect(() => {
+        setStreamConfigObj(streamConfigsObj);
+    }, [streamConfigsObj]);
+
+
+  return (
+    <Grid container spacing={2}>
+        <h3 className="accordion-subtitle">Transform functions</h3>
+                {
+                    streamConfigObj.map((o,i)=>{
+                        return(
+                            <Grid item xs={4}>
+                                <Grid container spacing={2}>
+                                    <Grid item xs={5}>
+                                        <FormControl className={classes.formControl}>
+                                            <InputLabel htmlFor={o.columnName}>Column Name</InputLabel>
+                                            <Input
+                                                id={o.columnName}
+                                                value={o.columnName}
+                                                key={i+"keyval"}
+                                                onChange={(e)=> inputChange("columnName",i,e.target.value)}
+                                                onBlur={updateJson}
+                                            />
+                                        </FormControl>
+                                    </Grid>
+                                    <Grid item xs={5}>
+                                        <FormControl className={classes.formControl}>
+                                        <InputLabel htmlFor={i+"keymulti"}>Transform Function</InputLabel>
+                                            {/* <Select
+                                                labelId={o.transformFunction}
+                                                id={o.transformFunction}
+                                                value={i+"valuemulti"}
+                                                onChange={(e)=> inputChange("transformFunction",i,e.target.value)}
+                                                onBlur={updateJson}
+                                            > */}
+                                                <Select
+                                                    id={i+"keymulti"}
+                                                    labelId={i+"keymulti"}
+                                                    key={i+"keymulti"}
+                                                    multiple
+                                                    value={o.transformFunction || []}
+                                                    onChange={(e)=> inputChange("transformFunction",i,e.target.value)}
+                                                    input={<Input id="select-multiple-chip" />}
+                                                    renderValue={(selected) => (
+                                                        <div className={"chips"}>
+                                                        {(selected as string[]).map((value) => (
+                                                            <Chip key={value} label={value} className={"chip"} />
+                                                        ))}
+                                                        </div>
+                                                    )}
+                                                    MenuProps={MenuProps}
+                                                    >
+                                                <MenuItem value="HOURLY">HOURLY</MenuItem>
+                                                <MenuItem value="DAILY">DAILY</MenuItem>
+                                            </Select>
+                                        </FormControl>
+
+                                    </Grid>
+                                    <Grid item xs={2}>
+                                        <FormControl>
+                                            <IconButton aria-label="delete" className = {classes.deleteIcon} onClick={()=>{
+                                                deleteClick(i)}}>
+                                                <ClearIcon />
+                                            </IconButton>
+                                        </FormControl>
+                                    </Grid>
+                                </Grid>
+                            </Grid>)
+                        })
+                }
+                <Grid item xs={3}>
+                <FormControl className={classes.formControl}>
+                    <Button
+                    aria-label="plus"
+                    variant="outlined"
+                    color="primary"
+                    onClick={addButtonClick}
+                    startIcon={(<AddCircleIcon />)}
+                    >
+                        Add new Field
+                    </Button>
+                </FormControl>
+                </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultipleSelectComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultipleSelectComponent.tsx
new file mode 100644
index 0000000..04fdb4d
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/MultipleSelectComponent.tsx
@@ -0,0 +1,185 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, MenuItem, Select, TextField, Theme, IconButton, Fab, Button, Chip} from '@material-ui/core';
+import AddIcon from '@material-ui/icons/Add';
+import AddCircleIcon from '@material-ui/icons/AddCircle';
+import ClearIcon from '@material-ui/icons/Clear';
+import DeleteIcon from '@material-ui/icons/Delete';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    formControl: {
+      margin: theme.spacing(1),
+      width: '100%'
+    },
+    deleteIcon:{
+        marginTop:15,
+        color: theme.palette.error.main
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 170
+    },
+    autoCompleteControl: {
+      '& .MuiFormControl-marginNormal': {
+        marginTop: 0
+      }
+    }
+  })
+);
+
+type Props = {
+    changeHandler: any,
+    streamConfigsObj: Array<any>,
+    columnName: Array<string>
+};
+
+export default function AddDeleteComponent({
+    changeHandler,
+    streamConfigsObj,
+    columnName
+}: Props) {
+    const classes = useStyles();
+    const [streamConfigObj, setStreamConfigObj] = useState(streamConfigsObj);
+    const ITEM_HEIGHT = 48;
+    const ITEM_PADDING_TOP = 8;
+    const MenuProps = {
+        PaperProps: {
+          style: {
+            maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+            width: 250,
+          },
+        },
+      };
+
+    const addButtonClick = ()=>{
+        let data = {columnName:"",transformFunction:""};
+        let tempStreamConfigObj = [...streamConfigObj,data];
+        setStreamConfigObj(tempStreamConfigObj);
+        changeHandler('transformConfigs',tempStreamConfigObj);
+    }
+
+    const inputChange = (input,index, value) =>{
+        let tempStreamConfigObj = [...streamConfigObj]
+        tempStreamConfigObj[index][input] = value;
+        setStreamConfigObj(tempStreamConfigObj);
+    }
+
+    const updateJson = () =>{
+        changeHandler('transformConfigs',streamConfigObj);
+    }
+
+    const deleteClick = (index) =>{
+        let tempStreamConfigObj = [...streamConfigObj];
+        tempStreamConfigObj.splice(index,1);
+        changeHandler('transformConfigs',tempStreamConfigObj)
+    }
+
+    useEffect(() => {
+        setStreamConfigObj(streamConfigsObj);
+    }, [streamConfigsObj]);
+
+
+  return (
+    <Grid container spacing={2}>
+        <h3 className="accordion-subtitle">Transform functions</h3>
+                {
+                    streamConfigObj.map((o,i)=>{
+                        return(
+                            <Grid item xs={4}>
+                              <div className="box-border">
+                                <Grid container spacing={2}>
+                                    <Grid item xs={5}>
+                                        <FormControl className={classes.formControl}>
+                                        <InputLabel htmlFor={o.columnName}>Column Name</InputLabel>
+                                            <Select
+                                                labelId={o.columnName }
+                                                id={o.columnName}
+                                                value={o.columnName}
+                                                key = {i+"valuemulti"}
+                                                onChange={(e)=> inputChange("columnName",i,e.target.value)}
+                                                onBlur={updateJson}
+                                            >
+                                                {/* <Select
+                                                    labelId={o.columnName}
+                                                    id={o.columnName}
+                                                    key={i+"keymulti"}
+                                                    multiple
+                                                    value={o.columnName || []}
+                                                    onChange={(e)=> inputChange("columnName",i,e.target.value)}
+                                                    input={<Input id="select-multiple-chip" />}
+                                                    renderValue={(selected) => (
+                                                        <div className={"chips"}>
+                                                        {(selected as string[]).map((value) => (
+                                                            <Chip key={value} label={value} className={"chip"} />
+                                                        ))}
+                                                        </div>
+                                                    )}
+                                                    MenuProps={MenuProps}
+                                                    >   */}
+                                                    {columnName.map((val)=>{
+                                                      return <MenuItem value={val}>{val}</MenuItem>
+                                                    })}
+                                            </Select>
+                                        </FormControl>
+
+                                    </Grid>
+                                    <Grid item xs={5}>
+                                        <FormControl className={classes.formControl}>
+                                            <InputLabel htmlFor={o.transformFunction}>Transform Function</InputLabel>
+                                            <Input
+                                                id={o.transformFunction}
+                                                value={o.transformFunction}
+                                                key={i+"keyval"}
+                                                onChange={(e)=> inputChange("transformFunction",i,e.target.value)}
+                                                onBlur={updateJson}
+                                            />
+                                        </FormControl>
+                                    </Grid>
+                                    <Grid item xs={2}>
+                                        <FormControl>
+                                            <IconButton aria-label="delete" className = {classes.deleteIcon} onClick={()=>{
+                                                deleteClick(i)}}>
+                                                <ClearIcon />
+                                            </IconButton>
+                                        </FormControl>
+                                    </Grid>
+                                </Grid>
+                                </div>
+                            </Grid>)
+                        })
+                }
+                <Grid item xs={3}>
+                <FormControl className={classes.formControl}>
+                    <Button
+                    aria-label="plus"
+                    variant="outlined"
+                    color="primary"
+                    onClick={addButtonClick}
+                    startIcon={(<AddCircleIcon />)}
+                    >
+                        Add new Field
+                    </Button>
+                </FormControl>
+                </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/SchemaComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/SchemaComponent.tsx
index c1d2c8e..69ccb5b 100644
--- a/pinot-controller/src/main/resources/app/components/Homepage/Operations/SchemaComponent.tsx
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/SchemaComponent.tsx
@@ -22,6 +22,8 @@ import { createStyles, FormControl, Grid, IconButton, Input, InputLabel, makeSty
 import AddIcon from '@material-ui/icons/Add';
 import ClearIcon from '@material-ui/icons/Clear';
 import { Autocomplete } from '@material-ui/lab';
+import { debug } from 'webpack';
+import _ from 'lodash';
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -50,7 +52,7 @@ const useStyles = makeStyles((theme: Theme) =>
     },
     selectFormControl: {
       margin: theme.spacing(1),
-      width: 170
+      width: 220
     },
     MVFFormControl: {
       margin: theme.spacing(1),
@@ -72,12 +74,14 @@ const useStyles = makeStyles((theme: Theme) =>
 
 type Props = {
   schemaName: string,
-  setSchemaObj: Function
+  setSchemaObj: Function,
+  schemaObj? : any
 };
 
 export default function SchemaComponent({
   schemaName = '',
-  setSchemaObj
+  setSchemaObj,
+  schemaObj,
 }: Props) {
   const classes = useStyles();
   const defaultColumnObj = {
@@ -165,9 +169,25 @@ export default function SchemaComponent({
     setColumnList(newColumnList);
   }
 
+  const schemaNameChange = (schemaName) =>{
+    let newSchemaObj = {...schemaObj};
+    newSchemaObj.schemaName = schemaName;
+    setSchemaObj(newSchemaObj);
+  }
+
   const requiredAstrix = <span className={classes.redColor}>*</span>;
   return (
     <Grid container className={classes.rootContainer}>
+        <Grid item xs={12} key={"schemaName"}>
+              <FormControl className={classes.formControl}>
+                <InputLabel htmlFor="schemaName">Schema Name {requiredAstrix}</InputLabel>
+                <Input
+                  id="schemaName"
+                  value={schemaName}
+                  onChange={(e)=> schemaNameChange(e.target.value)}
+                />
+              </FormControl>
+        </Grid>
       {columnList.map((columnObj, index)=>{
         return(
           <Grid item xs={12} key={index} className={classes.gridItem}>
@@ -206,7 +226,7 @@ export default function SchemaComponent({
                   {defaultDataTypeOptions[columnObj.type || 'dimension'].map((dataType, i)=>(<MenuItem key={i} value={dataType}>{dataType}</MenuItem>))}
                 </Select>
               </FormControl>
-
+{/*
               <FormControl className={classes.formControl}>
                 <InputLabel htmlFor="defaultNullValue">Default Null Value</InputLabel>
                 <Input
@@ -214,8 +234,7 @@ export default function SchemaComponent({
                   value={columnObj.defaultNullValue}
                   onChange={(e)=> changeHandler(index, 'defaultNullValue', e.target.value)}
                 />
-              </FormControl>
-
+              </FormControl> */}
               {columnObj.type === 'dimension' &&
                 <FormControl className={classes.MVFFormControl}>
                   <InputLabel htmlFor="multiValue">Multi Value Field</InputLabel>
@@ -230,9 +249,8 @@ export default function SchemaComponent({
                   </Select>
                 </FormControl>
               }
-
-              <br/>
-              
+                {columnObj.type !== 'datetime' &&
+                <>
               <FormControl className={classes.formControl}>
                 <InputLabel htmlFor="maxLength">Max Value Length</InputLabel>
                 <Input
@@ -253,10 +271,12 @@ export default function SchemaComponent({
                   )}
                 />
               </FormControl>
-            </div>
+              </>
+              }
+            <br/>
 
             {columnObj.type === 'datetime' &&
-              <div className={classes.dateTimeDiv}>
+              <div>
                 <FormControl className={classes.formControl}>
                   <InputLabel htmlFor="timeUnit">Time Unit {requiredAstrix}</InputLabel>
                   <Select
@@ -296,7 +316,6 @@ export default function SchemaComponent({
                     />
                   </FormControl>
                 }
-                <br/>
                 <FormControl className={classes.formControl}>
                   <InputLabel htmlFor="granularityInterval">Granularity Interval {requiredAstrix}</InputLabel>
                   <Input
@@ -323,6 +342,7 @@ export default function SchemaComponent({
                 </FormControl>
               </div>
             }
+            </div>
             <div className={classes.iconDiv}>
               <FormControl>
                 <IconButton
diff --git a/pinot-controller/src/main/resources/app/components/Homepage/Operations/SchemaNameComponent.tsx b/pinot-controller/src/main/resources/app/components/Homepage/Operations/SchemaNameComponent.tsx
new file mode 100644
index 0000000..54fe9ee
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/Homepage/Operations/SchemaNameComponent.tsx
@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { createStyles, FormControl, Grid, Input, InputLabel, makeStyles, Theme} from '@material-ui/core';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    rootContainer:{
+      padding: '0 15px 15px'
+    },
+    gridItem:{
+      display: 'flex',
+      alignItems: 'center',
+      marginTop: '15px',
+      padding: '5px',
+      border: '1px #ccc solid',
+      borderRadius: 5
+    },
+    formControl: {
+      margin: theme.spacing(1),
+      minWidth: 120,
+    },
+    selectFormControl: {
+      margin: theme.spacing(1),
+      width: 300
+    },
+    MVFFormControl: {
+      margin: theme.spacing(1),
+      width: 126
+    },
+    autoCompleteControl: {
+      '& .MuiFormControl-marginNormal': {
+        marginTop: 0
+      }
+    },
+    greenColor: {
+      color: theme.palette.success.main
+    },
+    redColor: {
+      color: theme.palette.error.main
+    },
+  })
+);
+
+type Props = {
+  setSchemaObj: Function,
+  schemaName: string,
+  schemaObj:any
+};
+
+export default function SchemaNameComponent({
+  setSchemaObj,
+  schemaName,
+  schemaObj
+}: Props) {
+  const classes = useStyles();
+  const [schemaObjTemp,setSchemaObjTemp] = useState(schemaObj);
+
+  const changeHandler = (value) =>{
+    let newSchemaObj = {...schemaObjTemp};
+    newSchemaObj.schemaName = value;
+    setSchemaObj(newSchemaObj);
+  }
+
+
+  useEffect(()=>{
+    let newSchemaObj = {...schemaObjTemp};
+    newSchemaObj.schemaName = schemaName;
+    setSchemaObjTemp(newSchemaObj);
+  },[schemaName])
+
+  const requiredAstrix = <span className={classes.redColor}>*</span>;
+  return (
+    <Grid container className={classes.rootContainer}>
+          <Grid item xs={12} key={"schemaName"}>
+              <FormControl className={classes.formControl}>
+                <InputLabel htmlFor="schemaName">Schema Name {requiredAstrix}</InputLabel>
+                <Input
+                  id="schemaName"
+                  value={schemaObjTemp.schemaName}
+                  onChange={(e)=> changeHandler(e.target.value)}
+                />
+              </FormControl>
+          </Grid>
+    </Grid>
+  );
+}
\ No newline at end of file
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 2acbc7e..6d66719 100644
--- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts
+++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
@@ -24,6 +24,14 @@ declare module 'Models' {
     error?: string;
   };
 
+  type SchemaDetails = {
+    schemaName: string,
+    totalColumns: number,
+    dimensions: number,
+    metrics: number,
+    dateTime: number
+  };
+
   export type Tenants = {
     SERVER_TENANTS: Array<string>;
     BROKER_TENANTS: Array<string>;
diff --git a/pinot-controller/src/main/resources/app/pages/SchemaPageDetails.tsx b/pinot-controller/src/main/resources/app/pages/SchemaPageDetails.tsx
new file mode 100644
index 0000000..5b6f516
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/pages/SchemaPageDetails.tsx
@@ -0,0 +1,308 @@
+/**
+ * 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, { useState, useEffect } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import { Grid } from '@material-ui/core';
+import { RouteComponentProps, useHistory } from 'react-router-dom';
+import { UnControlled as CodeMirror } from 'react-codemirror2';
+import { TableData } from 'Models';
+import _ from 'lodash';
+import AppLoader from '../components/AppLoader';
+import CustomizedTables from '../components/Table';
+import 'codemirror/lib/codemirror.css';
+import 'codemirror/theme/material.css';
+import 'codemirror/mode/javascript/javascript';
+import 'codemirror/mode/sql/sql';
+import SimpleAccordion from '../components/SimpleAccordion';
+import PinotMethodUtils from '../utils/PinotMethodUtils';
+import EditConfigOp from '../components/Homepage/Operations/EditConfigOp';
+import { NotificationContext } from '../components/Notification/NotificationContext';
+import Utils from '../utils/Utils';
+import CustomButton from '../components/CustomButton';
+import Confirm from '../components/Confirm';
+
+const useStyles = makeStyles(() => ({
+  root: {
+    border: '1px #BDCCD9 solid',
+    borderRadius: 4,
+    marginBottom: '20px',
+  },
+  highlightBackground: {
+    border: '1px #4285f4 solid',
+    backgroundColor: 'rgba(66, 133, 244, 0.05)',
+    borderRadius: 4,
+    marginBottom: '20px',
+  },
+  body: {
+    borderTop: '1px solid #BDCCD9',
+    fontSize: '16px',
+    lineHeight: '3rem',
+    paddingLeft: '15px',
+  },
+  queryOutput: {
+    border: '1px solid #BDCCD9',
+    '& .CodeMirror': { height: 532 },
+  },
+  sqlDiv: {
+    border: '1px #BDCCD9 solid',
+    borderRadius: 4,
+    marginBottom: '20px',
+  },
+  operationDiv: {
+    border: '1px #BDCCD9 solid',
+    borderRadius: 4,
+    marginBottom: 20
+  }
+}));
+
+const jsonoptions = {
+  lineNumbers: true,
+  mode: 'application/json',
+  styleActiveLine: true,
+  gutters: ['CodeMirror-lint-markers'],
+  theme: 'default',
+  readOnly: true
+};
+
+type Props = {
+  tenantName: string;
+  schemaName: string;
+  instanceName: string;
+};
+
+type Summary = {
+    schemaName: string;
+  reportedSize: string | number;
+  estimatedSize: string | number;
+};
+
+const SchemaPageDetails = ({ match }: RouteComponentProps<Props>) => {
+  const { schemaName } = match.params;
+  const classes = useStyles();
+  const history = useHistory();
+  const [fetching, setFetching] = useState(true);
+  const [] = useState<Summary>({
+    schemaName: match.params.schemaName,
+    reportedSize: '',
+    estimatedSize: '',
+  });
+
+  const [] = 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 [tableSchema, setTableSchema] = useState<TableData>({
+    columns: [],
+    records: [],
+  });
+  const [tableConfig, setTableConfig] = useState('');
+  const [schemaJSON, setSchemaJSON] = useState(null);
+  const [actionType,setActionType] = useState(null);
+  const [schemaJSONFormat, setSchemaJSONFormat] = useState(false);
+
+  const fetchTableSchema = async () => {
+    const result = await PinotMethodUtils.getTableSchemaData(schemaName);
+    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.getSchemaData(schemaName);
+    setTableConfig(JSON.stringify(result, null, 2));
+    setFetching(false);
+  };
+
+  useEffect(()=>{
+    fetchTableSchema();
+  },[])
+
+  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(schemaName, 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});
+      setShowEditConfig(false);
+      fetchTableJSON();
+    } else {
+      dispatch({type: 'error', message: result.error, show: true});
+    }
+  };
+
+  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);
+    history.push('/tables');
+  };
+
+  const closeDialog = () => {
+    setConfirmDialog(false);
+    setDialogDetails(null);
+  };
+
+  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={()=>{
+                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>
+            </div>
+        </SimpleAccordion>
+      </div>
+      <Grid container spacing={2}>
+        <Grid item xs={6}>
+          <div className={classes.sqlDiv}>
+            <SimpleAccordion
+              headerTitle="Schema Json"
+              showSearchBox={false}
+            >
+              <CodeMirror
+                options={jsonoptions}
+                value={tableConfig}
+                className={classes.queryOutput}
+                autoCursor={false}
+              />
+            </SimpleAccordion>
+          </div>
+        </Grid>
+        <Grid item xs={6}>
+          {!schemaJSONFormat ?
+            <CustomizedTables
+              title="Table Schema"
+              data={tableSchema}
+              isPagination={false}
+              noOfRows={tableSchema.records.length}
+              showSearchBox={true}
+            />
+          :
+          <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>
+          }
+        </Grid>
+      </Grid>
+      <EditConfigOp
+        showModal={showEditConfig}
+        hideModal={()=>{setShowEditConfig(false);}}
+        saveConfig={saveConfigAction}
+        config={config}
+        handleConfigChange={handleConfigChange}
+      />
+      {confirmDialog && dialogDetails && <Confirm
+        openDialog={confirmDialog}
+        dialogTitle={dialogDetails.title}
+        dialogContent={dialogDetails.content}
+        successCallback={dialogDetails.successCb}
+        closeDialog={closeDialog}
+        dialogYesLabel='Yes'
+        dialogNoLabel='No'
+      />}
+    </Grid>
+  );
+};
+
+export default SchemaPageDetails;
diff --git a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
index fb41766..b233057 100644
--- a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx
@@ -25,8 +25,9 @@ import PinotMethodUtils from '../utils/PinotMethodUtils';
 import CustomizedTables from '../components/Table';
 import CustomButton from '../components/CustomButton';
 import SimpleAccordion from '../components/SimpleAccordion';
-import AddTableSchemaOp from '../components/Homepage/Operations/AddTableSchemaOp';
-import AddTableOp from '../components/Homepage/Operations/AddTableOp';
+import AddSchemaOp from '../components/Homepage/Operations/AddSchemaOp';
+import AddOfflineTableOp from '../components/Homepage/Operations/AddOfflineTableOp';
+import AddRealtimeTableOp from '../components/Homepage/Operations/AddRealtimeTableOp';
 
 const useStyles = makeStyles(() => ({
   gridContainer: {
@@ -46,12 +47,17 @@ const TablesListingPage = () => {
   const classes = useStyles();
 
   const [fetching, setFetching] = useState(true);
+  const [schemaDetails,setSchemaDetails] = useState<TableData>({
+    columns: [],
+    records: []
+  });
   const [tableData, setTableData] = useState<TableData>({
     columns: [],
     records: []
   });
-  const [showAddTableSchemaModal, setShowAddTableSchemaModal] = useState(false);
-  const [showAddTableModal, setShowAddTableModal] = useState(false);
+  const [showSchemaModal, setShowSchemaModal] = useState(false);
+  const [showAddOfflineTableModal, setShowAddOfflineTableModal] = useState(false);
+  const [showAddRealtimeTableModal, setShowAddRealtimeTableModal] = useState(false);
 
   const fetchData = async () => {
     !fetching && setFetching(true);
@@ -61,7 +67,9 @@ const TablesListingPage = () => {
       tablesList.push(...record);
     });
     const tableDetails = await PinotMethodUtils.getAllTableDetails(tablesList);
+    const schemaDetailsData = await PinotMethodUtils.getAllSchemaDetails();
     setTableData(tableDetails);
+    setSchemaDetails(schemaDetailsData)
     setFetching(false);
   };
 
@@ -80,18 +88,25 @@ const TablesListingPage = () => {
         >
           <div>
             <CustomButton
-              onClick={()=>{setShowAddTableSchemaModal(true)}}
-              tooltipTitle="Add Schema & Table"
+              onClick={()=>{setShowSchemaModal(true)}}
+              tooltipTitle="Add Schema"
+              enableTooltip={true}
+            >
+              Add Schema
+            </CustomButton>
+            <CustomButton
+              onClick={()=>{setShowAddOfflineTableModal(true)}}
+              tooltipTitle="Add Offline Table"
               enableTooltip={true}
             >
-              Add Schema & Table
+              Add Offline Table
             </CustomButton>
             <CustomButton
-              onClick={()=>{setShowAddTableModal(true)}}
-              tooltipTitle="Add Table"
+              onClick={()=>{setShowAddRealtimeTableModal(true)}}
+              tooltipTitle="Add Realtime Table"
               enableTooltip={true}
             >
-              Add Table
+              Add Realtime Table
             </CustomButton>
           </div>
         </SimpleAccordion>
@@ -105,18 +120,36 @@ const TablesListingPage = () => {
         showSearchBox={true}
         inAccordionFormat={true}
       />
+      <CustomizedTables
+          title="Schemas"
+          data={schemaDetails}
+          isPagination
+          showSearchBox={true}
+          inAccordionFormat={true}
+          addLinks
+          baseURL="/tenants/schema/"
+      />
+      {
+        showSchemaModal &&
+        <AddSchemaOp
+          hideModal={()=>{setShowSchemaModal(false);}}
+          fetchData={fetchData}
+        />
+      }
       {
-        showAddTableSchemaModal &&
-        <AddTableSchemaOp
-          hideModal={()=>{setShowAddTableSchemaModal(false);}}
+        showAddOfflineTableModal &&
+        <AddOfflineTableOp
+          hideModal={()=>{setShowAddOfflineTableModal(false);}}
           fetchData={fetchData}
+          tableType={"OFFLINE"}
         />
       }
       {
-        showAddTableModal &&
-        <AddTableOp
-          hideModal={()=>{setShowAddTableModal(false);}}
+        showAddRealtimeTableModal &&
+        <AddRealtimeTableOp
+          hideModal={()=>{setShowAddRealtimeTableModal(false);}}
           fetchData={fetchData}
+          tableType={"REALTIME"}
         />
       }
     </Grid>
diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
index ccbeb18..9a44eee 100644
--- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
@@ -152,7 +152,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
     const result = await PinotMethodUtils.getSegmentList(tableName);
     const {columns, records, externalViewObj} = result;
     const instanceObj = {};
-    Object.keys(externalViewObj).map((segmentName)=>{
+    externalViewObj && Object.keys(externalViewObj).map((segmentName)=>{
       const instanceKeys = Object.keys(externalViewObj[segmentName]);
       instanceKeys.map((instanceName)=>{
         if(!instanceObj[instanceName]){
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts b/pinot-controller/src/main/resources/app/requests/index.ts
index c0fe697..26249f5 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -45,6 +45,9 @@ export const getTenantTableDetails = (tableName: string): Promise<AxiosResponse<
 export const putTable = (name: string, params: string): Promise<AxiosResponse<OperationResponse>> =>
   baseApi.put(`/tables/${name}`, params, { headers });
 
+export const getSchemaList = (): Promise<AxiosResponse<OperationResponse>> =>
+  baseApi.get(`/schemas`);
+
 export const getSchema = (name: string): Promise<AxiosResponse<OperationResponse>> =>
   baseApi.get(`/schemas/${name}`);
 
diff --git a/pinot-controller/src/main/resources/app/router.tsx b/pinot-controller/src/main/resources/app/router.tsx
index de0bd4f..ebd3c77 100644
--- a/pinot-controller/src/main/resources/app/router.tsx
+++ b/pinot-controller/src/main/resources/app/router.tsx
@@ -27,6 +27,7 @@ import QueryPage from './pages/Query';
 import SegmentDetails from './pages/SegmentDetails';
 import InstanceDetails from './pages/InstanceDetails';
 import ZookeeperPage from './pages/ZookeeperPage';
+import SchemaPageDetails from './pages/SchemaPageDetails';
 
 export default [
   { path: '/', Component: HomePage },
@@ -38,6 +39,7 @@ export default [
   { path: '/tables', Component: TablesListingPage },
   { path: '/tenants/:tenantName', Component: TenantsPage },
   { path: '/tenants/:tenantName/table/:tableName', Component: TenantPageDetails },
+  { path: '/tenants/schema/:schemaName', Component: SchemaPageDetails },
   { path: '/tenants/table/:tableName', Component: TenantPageDetails },
   { path: '/tenants/:tenantName/table/:tableName/:segmentName', Component: SegmentDetails },
   { path: '/instance/:instanceName', Component: InstanceDetails },
diff --git a/pinot-controller/src/main/resources/app/styles/styles.css b/pinot-controller/src/main/resources/app/styles/styles.css
index a2a7a6d..7f3bb68 100644
--- a/pinot-controller/src/main/resources/app/styles/styles.css
+++ b/pinot-controller/src/main/resources/app/styles/styles.css
@@ -89,4 +89,37 @@ li.CodeMirror-hint-active {
 
 .CodeMirror-lint-tooltip{
   z-index: 9999 !important;
+}
+
+h3.accordion-subtitle {
+  display: flex;
+  width: 100%;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+  color: #4285f3;
+  margin: 20px 0 0;
+}
+h3.accordion-subtitle:before, h3.accordion-subtitle:after {
+  content: "";
+  border-top: 2px #bdccd9 solid;
+  margin: 0 20px;
+  flex: 1 0 20px;
+}
+.chip {
+  margin: 2;
+  background-color: rgb(66 133 244 / 0.3) !important;
+}
+.chips {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.MuiListItem-root.Mui-selected, .MuiListItem-root.Mui-selected:hover {
+  background-color: rgb(66 133 244 / 0.3) !important;
+}
+.box-border{
+  border: 1px #ccc solid;
+  border-radius: 5px;
+  margin: 8px;
 }
\ 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 011e8d7..592e562 100644
--- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
+++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
@@ -62,7 +62,8 @@ import {
   validateTable,
   saveSchema,
   saveTable,
-  getSchema
+  getSchema,
+  getSchemaList
 } from '../requests';
 import Utils from './Utils';
 
@@ -307,6 +308,42 @@ const getTenantTableData = (tenantName) => {
   });
 };
 
+const getSchemaObject = async (schemaName) =>{
+  let schemaObj:Array<any> = [];
+    let {data} = await getSchema(schemaName);
+    console.log(data);
+      schemaObj.push(data.schemaName);
+      schemaObj.push(data.dimensionFieldSpecs ? data.dimensionFieldSpecs.length : 0);
+      schemaObj.push(data.dateTimeFieldSpecs ? data.dateTimeFieldSpecs.length : 0);
+      schemaObj.push(data.metricFieldSpecs ? data.metricFieldSpecs.length : 0);
+      schemaObj.push(schemaObj[1] + schemaObj[2] + schemaObj[3]);
+      return schemaObj;
+  }
+
+const getAllSchemaDetails = async () => {
+  const columnHeaders = ["Schema Name", "Dimension Columns", "Date-Time Columns", "Metrics Columns", "Total Columns"]
+  let schemaDetails:Array<any> = [];
+  let promiseArr = [];
+  const {data} = await getSchemaList()
+  promiseArr = data.map(async (o)=>{
+    return await getSchema(o);
+  });
+  const results = await Promise.all(promiseArr);
+  schemaDetails = results.map((obj)=>{
+    let schemaObj = [];
+    schemaObj.push(obj.data.schemaName);
+    schemaObj.push(obj.data.dimensionFieldSpecs ? obj.data.dimensionFieldSpecs.length : 0);
+    schemaObj.push(obj.data.dateTimeFieldSpecs ? obj.data.dateTimeFieldSpecs.length : 0);
+    schemaObj.push(obj.data.metricFieldSpecs ? obj.data.metricFieldSpecs.length : 0);
+    schemaObj.push(schemaObj[1] + schemaObj[2] + schemaObj[3]);
+    return schemaObj;
+  })
+  return {
+    columns: columnHeaders,
+    records: schemaDetails
+  };
+}
+
 const getAllTableDetails = (tablesList) => {
   const columnHeaders = [
     'Table Name',
@@ -736,5 +773,6 @@ export default {
   validateTableAction,
   saveSchemaAction,
   saveTableAction,
-  getSchemaData
+  getSchemaData,
+  getAllSchemaDetails
 };


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