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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2e16aa4  updated cluster manage UI and added table details page and 
segment details page (#5732)
2e16aa4 is described below

commit 2e16aa4676198f44c7a9532d1d229125069e567b
Author: Sanket Shah <shahsan...@users.noreply.github.com>
AuthorDate: Thu Jul 23 13:00:38 2020 +0530

    updated cluster manage UI and added table details page and segment details 
page (#5732)
    
    * updated cluster manage UI and added table details page and segment 
details page
    
    * showing error message if sql query returns any exception
---
 .../main/resources/app/components/Breadcrumbs.tsx  |  11 +-
 .../app/components/Homepage/ClusterConfig.tsx      |  24 +-
 .../app/components/Homepage/InstanceTable.tsx      |  42 +-
 .../app/components/Homepage/InstancesTables.tsx    |  26 +-
 .../app/components/Homepage/TenantsTable.tsx       |  43 +-
 .../src/main/resources/app/components/Layout.tsx   |   7 +-
 .../app/components/Query/QuerySideBar.tsx          |   7 +-
 .../main/resources/app/components/SearchBar.tsx    |  14 +-
 .../resources/app/components/SimpleAccordion.tsx   |  96 +++++
 .../app/components/SvgIcons/ClusterManagerIcon.tsx |  32 ++
 .../app/components/SvgIcons/QueryConsoleIcon.tsx   |  36 +-
 .../src/main/resources/app/components/Table.tsx    | 249 ++++++-----
 .../{EnhancedTableToolbar.tsx => TableToolbar.tsx} |  12 +-
 .../src/main/resources/app/interfaces/types.d.ts   |  23 +-
 .../src/main/resources/app/pages/Query.tsx         | 298 +++++++------
 .../main/resources/app/pages/SegmentDetails.tsx    | 170 ++++++++
 .../src/main/resources/app/pages/TenantDetails.tsx | 139 ++++++-
 .../src/main/resources/app/pages/Tenants.tsx       |  64 +--
 .../src/main/resources/app/requests/index.ts       |  16 +-
 pinot-controller/src/main/resources/app/router.tsx |   6 +-
 .../main/resources/app/utils/PinotMethodUtils.ts   | 462 +++++++++++++++++++++
 .../src/main/resources/app/utils/Utils.tsx         |  25 ++
 pinot-controller/src/main/resources/package.json   |   3 +-
 23 files changed, 1375 insertions(+), 430 deletions(-)

diff --git a/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx 
b/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
index 0cdf09a..0df6f25 100644
--- a/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
+++ b/pinot-controller/src/main/resources/app/components/Breadcrumbs.tsx
@@ -85,13 +85,14 @@ const BreadcrumbsComponent = ({ ...props }) => {
       const breadcrumbs = [getClickableLabel(breadcrumbNameMap['/'], '/')];
       const paramsKeys = _.keys(props.match.params);
       if(paramsKeys.length){
-        const {tenantName, tableName} = props.match.params;
-        if(!tableName && tenantName){
-          breadcrumbs.push(getLabel(tenantName));
-        } else {
+        const {tenantName, tableName, segmentName} = props.match.params;
+        if(tenantName && tableName){
           breadcrumbs.push(getClickableLabel(tenantName, 
`/tenants/${tenantName}`));
-          breadcrumbs.push(getLabel(tableName));
         }
+        if(tenantName && tableName && segmentName){
+          breadcrumbs.push(getClickableLabel(tableName, 
`/tenants/${tenantName}/table/${tableName}`));
+        }
+        breadcrumbs.push(getLabel(segmentName || tableName || tenantName));
       } else {
         breadcrumbs.push(getLabel(breadcrumbNameMap[location.pathname]));
       }
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/ClusterConfig.tsx 
b/pinot-controller/src/main/resources/app/components/Homepage/ClusterConfig.tsx
index 8b65e59..1b5217c 100644
--- 
a/pinot-controller/src/main/resources/app/components/Homepage/ClusterConfig.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/ClusterConfig.tsx
@@ -19,9 +19,9 @@
 
 import React, { useEffect, useState } from 'react';
 import { TableData } from 'Models';
-import { getClusterConfig } from '../../requests';
 import AppLoader from '../AppLoader';
 import CustomizedTables from '../Table';
+import PinotMethodUtils from '../../utils/PinotMethodUtils';
 
 const ClusterConfig = () => {
 
@@ -31,21 +31,23 @@ const ClusterConfig = () => {
     records: []
   });
 
+  const fetchData = async () => {
+    const result = await PinotMethodUtils.getClusterConfigData();
+    setTableData(result);
+    setFetching(false);
+  };
   useEffect(() => {
-    getClusterConfig().then(({ data }) => {
-      setTableData({
-        columns: ['Property', 'Value'],
-        records: [
-          ...Object.keys(data).map(key => [key, data[key]])
-        ]
-      });
-      setFetching(false);
-    });
+    fetchData();
   }, []);
 
   return (
     fetching ? <AppLoader /> :
-    <CustomizedTables title="Cluster configuration" data={tableData} />
+    <CustomizedTables
+      title="Cluster configuration"
+      data={tableData}
+      showSearchBox={true}
+      inAccordionFormat={true}
+    />
   );
 };
 
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx 
b/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx
index a3657d9..e05d370 100644
--- 
a/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/InstanceTable.tsx
@@ -19,9 +19,9 @@
 
 import React, { useEffect, useState } from 'react';
 import { TableData } from 'Models';
-import { getInstance } from '../../requests';
 import CustomizedTables from '../Table';
 import AppLoader from '../AppLoader';
+import PinotMethodUtils from '../../utils/PinotMethodUtils';
 
 type Props = {
   name: string,
@@ -36,28 +36,34 @@ const InstaceTable = ({ name, instances }: Props) => {
     records: []
   });
 
-  useEffect(() => {
+  const fetchClusterName = async () => {
+    const clusterName = await PinotMethodUtils.getClusterName();
+    fetchLiveInstance(clusterName);
+  }
+
+  const fetchLiveInstance = async (clusterName) => {
+    const liveInstanceArr = await 
PinotMethodUtils.getLiveInstance(clusterName);
+    fetchData(liveInstanceArr.data);
+  }
 
-    const promiseArr = [
-      ...instances.map(inst => getInstance(inst))
-    ];
+  const fetchData = async (liveInstanceArr) => {
+    const result = await PinotMethodUtils.getInstanceData(instances, 
liveInstanceArr);
+    setTableData(result);
+    setFetching(false);
+  };
 
-    Promise.all(promiseArr).then(result => {
-      setTableData({
-        columns: ['Name', 'Enabled', 'Hostname', 'Port', 'URI'],
-        records: [
-          ...result.map(({ data }) => (
-            [data.instanceName, data.enabled, data.hostName, data.port, 
`${data.hostName}:${data.port}`]
-          ))
-        ]
-      });
-      setFetching(false);
-    });
-  }, [instances]);
+  useEffect(() => {
+    fetchClusterName();
+  }, []);
 
   return (
     fetching ? <AppLoader /> :
-    <CustomizedTables title={name} data={tableData} />
+    <CustomizedTables
+      title={name}
+      data={tableData}
+      showSearchBox={true}
+      inAccordionFormat={true}
+    />
   );
 };
 
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/InstancesTables.tsx
 
b/pinot-controller/src/main/resources/app/components/Homepage/InstancesTables.tsx
index 0f86502..39f2ed5 100644
--- 
a/pinot-controller/src/main/resources/app/components/Homepage/InstancesTables.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/InstancesTables.tsx
@@ -19,9 +19,9 @@
 
 import React, { useEffect, useState } from 'react';
 import map from 'lodash/map';
-import { getInstances } from '../../requests';
 import AppLoader from '../AppLoader';
-import InstaceTable from './InstanceTable';
+import InstanceTable from './InstanceTable';
+import PinotMethodUtils from '../../utils/PinotMethodUtils';
 
 type DataTable = {
   [name: string]: string[]
@@ -31,21 +31,13 @@ const Instances = () => {
   const [fetching, setFetching] = useState(true);
   const [instances, setInstances] = useState<DataTable>();
 
+  const fetchData = async () => {
+    const result = await PinotMethodUtils.getAllInstances();
+    setInstances(result);
+    setFetching(false);
+  };
   useEffect(() => {
-    getInstances().then(({ data }) => {
-      const initialVal: DataTable = {};
-      // It will create instances list array like
-      // {Controller: ['Controller1', 'Controller2'], Broker: ['Broker1', 
'Broker2']}
-      const groupedData = data.instances.reduce((r, a) => {
-        const y = a.split('_');
-        const key = y[0].trim();
-        r[key] = [...r[key] || [], a];
-        return r;
-      }, initialVal);
-
-      setInstances(groupedData);
-      setFetching(false);
-    });
+    fetchData();
   }, []);
 
   return fetching ? (
@@ -54,7 +46,7 @@ const Instances = () => {
     <>
       {
         map(instances, (value, key) => {
-          return <InstaceTable key={key} name={key} instances={value} />;
+          return <InstanceTable key={key} name={key} instances={value} />;
         })
       }
     </>
diff --git 
a/pinot-controller/src/main/resources/app/components/Homepage/TenantsTable.tsx 
b/pinot-controller/src/main/resources/app/components/Homepage/TenantsTable.tsx
index 04256cf..0696961 100644
--- 
a/pinot-controller/src/main/resources/app/components/Homepage/TenantsTable.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/Homepage/TenantsTable.tsx
@@ -19,39 +19,36 @@
 
 import React, { useEffect, useState } from 'react';
 import { TableData } from 'Models';
-import union from 'lodash/union';
-import { getTenants } from '../../requests';
 import AppLoader from '../AppLoader';
 import CustomizedTables from '../Table';
+import PinotMethodUtils from '../../utils/PinotMethodUtils';
 
 const TenantsTable = () => {
   const [fetching, setFetching] = useState(true);
   const [tableData, setTableData] = useState<TableData>({ records: [], 
columns: [] });
 
+  const fetchData = async () => {
+    const result = await PinotMethodUtils.getTenantsData();
+    setTableData(result);
+    setFetching(false);
+  };
   useEffect(() => {
-    getTenants().then(({ data }) => {
-      const records = union(
-        data.SERVER_TENANTS,
-        data.BROKER_TENANTS
-      );
-      setTableData({
-        columns: ['Name', 'Server', 'Broker', 'Tables'],
-        records: [
-          ...records.map(record => [
-            record,
-            data.SERVER_TENANTS.indexOf(record) > -1 ? 1 : 0,
-            data.BROKER_TENANTS.indexOf(record) > -1 ? 1 : 0,
-            '-'
-          ])
-        ]
-      });
-      setFetching(false);
-    });
+    fetchData();
   }, []);
 
-  return (
-    fetching ? <AppLoader /> : <CustomizedTables title="Tenants" 
data={tableData} addLinks isPagination baseURL="/tenants/" />
+  return fetching ? (
+    <AppLoader />
+  ) : (
+    <CustomizedTables
+      title="Tenants"
+      data={tableData}
+      addLinks
+      isPagination
+      baseURL="/tenants/"
+      showSearchBox={true}
+      inAccordionFormat={true}
+    />
   );
 };
 
-export default TenantsTable;
\ No newline at end of file
+export default TenantsTable;
diff --git a/pinot-controller/src/main/resources/app/components/Layout.tsx 
b/pinot-controller/src/main/resources/app/components/Layout.tsx
index b958bdc..a11a292 100644
--- a/pinot-controller/src/main/resources/app/components/Layout.tsx
+++ b/pinot-controller/src/main/resources/app/components/Layout.tsx
@@ -23,11 +23,12 @@ import Sidebar from './SideBar';
 import Header from './Header';
 import QueryConsoleIcon from './SvgIcons/QueryConsoleIcon';
 import SwaggerIcon from './SvgIcons/SwaggerIcon';
+import ClusterManagerIcon from './SvgIcons/ClusterManagerIcon';
 
 const navigationItems = [
-  // { id: 1, name: 'Cluster Manager', link: '/' },
-  { id: 1, name: 'Query Console', link: '/', icon: <QueryConsoleIcon/> },
-  { id: 2, name: 'Swagger REST API', link: 'help', target: '_blank', icon: 
<SwaggerIcon/> }
+  { id: 1, name: 'Cluster Manager', link: '/', icon: <ClusterManagerIcon/> },
+  { id: 2, name: 'Query Console', link: '/query', icon: <QueryConsoleIcon/> },
+  { id: 3, name: 'Swagger REST API', link: 'help', target: '_blank', icon: 
<SwaggerIcon/> }
 ];
 
 const Layout = (props) => {
diff --git 
a/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx 
b/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx
index a3cbd3c..5e12d46 100644
--- a/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx
+++ b/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx
@@ -76,9 +76,10 @@ type Props = {
   tableList: TableData;
   fetchSQLData: Function;
   tableSchema: TableData;
+  selectedTable: string;
 };
 
-const Sidebar = ({ tableList, fetchSQLData, tableSchema }: Props) => {
+const Sidebar = ({ tableList, fetchSQLData, tableSchema, selectedTable }: 
Props) => {
   const classes = useStyles();
 
   return (
@@ -101,15 +102,17 @@ const Sidebar = ({ tableList, fetchSQLData, tableSchema 
}: Props) => {
               noOfRows={tableList.records.length}
               cellClickCallback={fetchSQLData}
               isCellClickable
+              showSearchBox={false}
             />
 
             {tableSchema.records.length ? (
               <CustomizedTables
-                title="Schema:"
+                title={`${selectedTable} schema`}
                 data={tableSchema}
                 isPagination={false}
                 noOfRows={tableSchema.records.length}
                 highlightBackground
+                showSearchBox={false}
               />
             ) : null}
           </Grid>
diff --git a/pinot-controller/src/main/resources/app/components/SearchBar.tsx 
b/pinot-controller/src/main/resources/app/components/SearchBar.tsx
index dcbfc4c..f647533 100644
--- a/pinot-controller/src/main/resources/app/components/SearchBar.tsx
+++ b/pinot-controller/src/main/resources/app/components/SearchBar.tsx
@@ -27,14 +27,18 @@ const useStyles = makeStyles((theme) => ({
   search: {
     position: 'relative',
     borderRadius: theme.shape.borderRadius,
-    marginRight: theme.spacing(2),
-    marginLeft: 0,
     width: '100%',
     [theme.breakpoints.up('sm')]: {
-      marginLeft: theme.spacing(3),
+      // marginLeft: theme.spacing(3),
       width: 'auto',
     },
   },
+  searchOnRight:{
+    position: 'relative',
+    borderRadius: theme.shape.borderRadius,
+    width: '150px',
+    marginLeft: 'auto',
+  },
   searchIcon: {
     padding: theme.spacing(0, 2),
     height: '100%',
@@ -58,10 +62,10 @@ const useStyles = makeStyles((theme) => ({
   },
 }));
 
-const SearchBar = (props: InputBaseProps) => {
+const SearchBar = (props) => {
   const classes = useStyles();
   return (
-    <div className={classes.search}>
+    <div className={props.searchOnRight ? classes.searchOnRight : 
classes.search}>
       <div className={classes.searchIcon}>
         <SearchIcon />
       </div>
diff --git 
a/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx 
b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
new file mode 100644
index 0000000..3366f20
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/components/SimpleAccordion.tsx
@@ -0,0 +1,96 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { Theme, createStyles, makeStyles } from '@material-ui/core/styles';
+import Accordion from '@material-ui/core/Accordion';
+import AccordionSummary from '@material-ui/core/AccordionSummary';
+import AccordionDetails from '@material-ui/core/AccordionDetails';
+import Typography from '@material-ui/core/Typography';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import SearchBar from './SearchBar';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      backgroundColor: 'rgba(66, 133, 244, 0.1)',
+      borderBottom: '1px #BDCCD9 solid',
+      minHeight: '0 !important',
+      '& .MuiAccordionSummary-content.Mui-expanded':{
+        margin: 0
+      }
+    },
+    heading: {
+      fontWeight: 600,
+      letterSpacing: '1px',
+      fontSize: '1rem',
+      color: '#4285f4'
+    },
+    details: {
+      flexDirection: 'column',
+      padding: '0'
+    }
+  }),
+);
+
+type Props = {
+  headerTitle: string;
+  showSearchBox: boolean;
+  searchValue?: string;
+  handleSearch?: Function;
+  recordCount?: number
+  children: any;
+};
+
+export default function SimpleAccordion({
+  headerTitle,
+  showSearchBox,
+  searchValue,
+  handleSearch,
+  recordCount,
+  children
+}: Props) {
+  const classes = useStyles();
+
+  return (
+    <Accordion
+      defaultExpanded={true}
+    >
+      <AccordionSummary
+        expandIcon={<ExpandMoreIcon />}
+        aria-controls={`panel1a-content-${headerTitle}`}
+        id={`panel1a-header-${headerTitle}`}
+        className={classes.root}
+      >
+        <Typography className={classes.heading}>{`${headerTitle.toUpperCase()} 
${recordCount !== undefined ? ` - (${recordCount})` : ''}`}</Typography>
+      </AccordionSummary>
+      <AccordionDetails className={classes.details}>
+        {showSearchBox ?
+          <SearchBar
+            // searchOnRight={true}
+            value={searchValue}
+            onChange={(e) => handleSearch(e.target.value)}
+          />
+          : null
+        }
+        {children}
+      </AccordionDetails>
+    </Accordion>
+  );
+}
\ No newline at end of file
diff --git 
a/pinot-controller/src/main/resources/app/components/SvgIcons/ClusterManagerIcon.tsx
 
b/pinot-controller/src/main/resources/app/components/SvgIcons/ClusterManagerIcon.tsx
new file mode 100644
index 0000000..71da8b3
--- /dev/null
+++ 
b/pinot-controller/src/main/resources/app/components/SvgIcons/ClusterManagerIcon.tsx
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as React from 'react';
+import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
+
+export default (props: SvgIconProps) => (
+  <SvgIcon style={{ width: 24, height: 24, verticalAlign: 'middle' }} 
viewBox="0 0 512 512" fill="none" {...props}>
+    <g>
+      <path d="m63.623 
367.312h-62.815v144.688h148.689v-116.926h-68.752zm55.971 
114.786h-88.884v-84.883h16.222l17.123 27.761h55.539z"/>
+      <path d="m244.47 
367.312h-62.815v144.688h148.689v-116.926h-68.751zm55.972 
114.786h-88.884v-84.883h16.222l17.123 27.761h55.539z"/>
+      <path d="m442.44 
395.074-17.122-27.761h-62.815v144.687h148.689v-116.926zm38.85 
87.024h-88.884v-84.883h16.222l17.122 27.761h55.54z"/>
+      <path d="m90.104 
292.958h150.945v42.267h29.902v-42.267h150.945v42.267h29.902v-72.169h-180.847v-64.555h80.049c31.767
 0 57.612-25.926 57.612-57.793 
0-26.955-18.608-49.646-43.653-55.903-4.476-22.152-24.093-38.882-47.545-38.882-1.967
 0-3.917.116-5.842.347-4.829-26.287-27.911-46.27-55.572-46.27s-50.744 
19.983-55.572 46.269c-1.925-.23-3.876-.347-5.843-.347-23.452 0-43.069 
16.73-47.544 38.882-25.044 6.256-43.653 28.948-43.653 55.903 0 31.867 25.844 
57.793 57.612 57.793h80.049v64.555h-180. [...]
+    </g>
+  </SvgIcon>
+);
diff --git 
a/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
 
b/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
index 81d41be..2870583 100644
--- 
a/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
+++ 
b/pinot-controller/src/main/resources/app/components/SvgIcons/QueryConsoleIcon.tsx
@@ -21,28 +21,18 @@ import * as React from 'react';
 import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
 
 export default (props: SvgIconProps) => (
-  <SvgIcon style={{ width: 24, height: 24, verticalAlign: 'middle' }} 
viewBox="0 0 499.9 499.9" fill="none" {...props}>
-    <g>
-      <g>
-        <path 
d="M499.9,189.9c0-24.7-4.7-48.8-13.9-71.5c-9.5-23.6-23.6-44.7-41.7-62.8c-18.1-18.1-39.2-32.1-62.8-41.7
-          
C358.8,4.7,334.7,0,310,0s-48.8,4.7-71.5,13.9c-23.6,9.5-44.7,23.6-62.8,41.7c-15.5,15.5-28.1,33.3-37.4,53
-          
c-9,19-14.7,39.3-17,60.3c-4.6,42.1,5,84.9,27,120.7l2.1,3.4l-2.8,2.8L0,443.4l56.6,56.6l147.6-147.6l2.8-2.8l3.4,2.1
-          
c29.9,18.4,64.4,28.2,99.7,28.2c24.7,0,48.8-4.7,71.6-13.9c23.6-9.5,44.7-23.6,62.8-41.6c18.1-18.1,32.1-39.2,41.7-62.8
-          C495.3,238.7,499.9,214.6,499.9,189.9z 
M186.7,341.5L60.1,468.1l-3.5,3.5l-3.5-3.5l-21.2-21.2l-3.5-3.5l3.5-3.5l126.6-126.6
-          
l3.8-3.8l3.5,4.1c3.2,3.7,6.5,7.3,9.9,10.7c3.4,3.4,7,6.8,10.7,9.9l4.1,3.5L186.7,341.5z
 M430.2,310.1
-          
c-16.2,16.2-35.1,28.7-56.2,37.3c-20.4,8.2-41.9,12.4-64,12.4s-43.6-4.2-64-12.4c-21.1-8.5-40-21.1-56.2-37.3
-          
s-28.7-35.1-37.3-56.2c-8.2-20.4-12.4-41.9-12.4-64c0-22.1,4.2-43.6,12.4-64c8.5-21.1,21.1-40,37.3-56.2
-          
C206,53.5,224.9,41,246.1,32.4C266.4,24.2,288,20,310,20c0,0,0,0,0,0c22.1,0,43.6,4.2,64,12.4c21.1,8.5,40,21.1,56.2,37.3
-          C496.5,136,496.5,243.8,430.2,310.1z"/>
-      </g>
-      <path d="M206.1,233.9h30v30h-30V233.9z"/>
-      <path d="M266,233.9h30v30h-30V233.9z"/>
-      <path d="M326,233.9h30v30h-30V233.9z"/>
-      <path d="M386,233.9h30v30h-30V233.9z"/>
-      <path d="M206.1,113.9h30v90h-30V113.9z"/>
-      <path d="M266,113.9h30v90h-30V113.9z"/>
-      <path d="M326,113.9h30v90h-30V113.9z"/>
-      <path d="M386,113.9h30v90h-30V113.9z"/>
-    </g>
+  <SvgIcon style={{ width: 24, height: 24, verticalAlign: 'middle' }} 
viewBox="0 0 512 512" fill="none" {...props}>
+    <path 
d="M401.6,246.5c17.3-24.9,27.5-55.1,27.5-87.7C429.1,74,360.1,5,275.3,5S121.5,74,121.5,158.8c0,20.4,4,39.9,11.2,57.7
+       
c-32.8,0.3-63.7,5.1-87.3,13.6C7.9,243.5,0,261.8,0,274.8v86.8c0,0,0,0.1,0,0.1s0,0.1,0,0.1v86.8c0,13,7.9,31.4,45.5,44.8
+       
c24.5,8.8,56.7,13.6,90.8,13.6s66.3-4.8,90.8-13.6c37.6-13.4,45.5-31.8,45.5-44.8v-86.8c0,0,0-0.1,0-0.1s0-0.1,0-0.1v-49.1
+       
c0.9,0,1.8,0,2.7,0c18.1,0,35.4-3.1,51.5-8.9l109.3,148.8l75.8-55.7L401.6,246.5z 
M55.6,258.3c21.3-7.6,50-11.8,80.7-11.8
+       
c4.4,0,8.7,0.1,12.9,0.3c14.2,20.3,33.2,37,55.3,48.5c-19.5,5.2-43.1,8-68.2,8c-30.7,0-59.4-4.2-80.7-11.8
+       c-21-7.5-25.6-15.1-25.6-16.5S34.6,265.8,55.6,258.3z 
M242.6,448.6c0,1.5-4.5,9-25.6,16.5c-21.3,7.6-50,11.8-80.7,11.8
+       
c-30.7,0-59.4-4.2-80.7-11.8c-21-7.5-25.6-15.1-25.6-16.5v-48.7c4.5,2.3,9.6,4.5,15.5,6.6c24.5,8.8,56.7,13.6,90.8,13.6
+       s66.3-4.8,90.8-13.6c5.8-2.1,11-4.3,15.5-6.6V448.6z 
M217,378.2c-21.3,7.6-50,11.8-80.7,11.8c-30.7,0-59.4-4.2-80.7-11.8
+       
c-21-7.5-25.6-15.1-25.6-16.5v-48.5c4.5,2.3,9.6,4.5,15.5,6.6c24.5,8.8,56.7,13.6,90.8,13.6s66.3-4.8,90.8-13.6
+       c5.8-2.1,11-4.3,15.5-6.6v48.5h0C242.6,363.1,238,370.6,217,378.2z 
M275.3,282.5c-68.2,0-123.8-55.5-123.8-123.8S207.1,35,275.3,35
+       s123.8,55.5,123.8,123.8C399.1,227,343.5,282.5,275.3,282.5z 
M354.5,290.6c9.8-5.9,18.8-12.8,27-20.7L470,390.3l-27.4,20.2
+       L354.5,290.6z M200.3,143.8h30v30h-30V143.8z 
M260.3,143.8h30v30h-30V143.8z M320.3,143.8h30v30h-30V143.8z"/>
   </SvgIcon>
 );
diff --git a/pinot-controller/src/main/resources/app/components/Table.tsx 
b/pinot-controller/src/main/resources/app/components/Table.tsx
index dd95ad5..98b3e6c 100644
--- a/pinot-controller/src/main/resources/app/components/Table.tsx
+++ b/pinot-controller/src/main/resources/app/components/Table.tsx
@@ -45,7 +45,8 @@ import { NavLink } from 'react-router-dom';
 import Chip from '@material-ui/core/Chip';
 import _ from 'lodash';
 import Utils from '../utils/Utils';
-import EnhancedTableToolbar from './EnhancedTableToolbar';
+import TableToolbar from './TableToolbar';
+import SimpleAccordion from './SimpleAccordion';
 
 type Props = {
   title?: string;
@@ -57,7 +58,10 @@ type Props = {
   isCellClickable?: boolean,
   highlightBackground?: boolean,
   isSticky?: boolean
-  baseURL?: string
+  baseURL?: string,
+  recordsCount?: number,
+  showSearchBox: boolean,
+  inAccordionFormat?: boolean
 };
 
 const StyledTableRow = withStyles((theme) =>
@@ -148,6 +152,10 @@ const useStyles = makeStyles((theme) => ({
   cellStatusBad: {
     color: '#f44336',
     border: '1px solid #f44336',
+  },
+  cellStatusConsuming: {
+    color: '#ff9800',
+    border: '1px solid #ff9800',
   }
 }));
 
@@ -238,7 +246,10 @@ export default function CustomizedTables({
   isCellClickable,
   highlightBackground,
   isSticky,
-  baseURL
+  baseURL,
+  recordsCount,
+  showSearchBox,
+  inAccordionFormat
 }: Props) {
   const [finalData, setFinalData] = React.useState(Utils.tableFormat(data));
 
@@ -284,7 +295,7 @@ export default function CustomizedTables({
   React.useEffect(() => {
     clearTimeout(timeoutId.current);
     timeoutId.current = setTimeout(() => {
-      filterSearchResults(search);
+      filterSearchResults(search.toLowerCase());
     }, 200);
 
     return () => {
@@ -292,8 +303,8 @@ export default function CustomizedTables({
     };
   }, [search, timeoutId, filterSearchResults]);
 
-  const styleCell = (str: string | number | boolean) => {
-    if (str === 'Good') {
+  const styleCell = (str: string) => {
+    if (str === 'Good' || str.toLowerCase() === 'online' || str.toLowerCase() 
=== 'alive') {
       return (
         <StyledChip
           label={str}
@@ -302,7 +313,7 @@ export default function CustomizedTables({
         />
       );
     }
-    if (str === 'Bad') {
+    if (str === 'Bad' || str.toLowerCase() === 'offline' || str.toLowerCase() 
=== 'dead') {
       return (
         <StyledChip
           label={str}
@@ -311,103 +322,147 @@ export default function CustomizedTables({
         />
       );
     }
+    if (str.toLowerCase() === 'consuming') {
+      return (
+        <StyledChip
+          label={str}
+          className={classes.cellStatusConsuming}
+          variant="outlined"
+        />
+      );
+    }
     return str.toString();
   };
 
-  return (
-    <div className={highlightBackground ? classes.highlightBackground : 
classes.root}>
-      {title ? (
-        <EnhancedTableToolbar
-          name={title}
-          showSearchBox={true}
-          searchValue={search}
-          handleSearch={(val: string) => setSearch(val)}
-        />
-      ) : null}
-      <TableContainer style={{ maxHeight: isSticky ? 400 : 600 }}>
-        <Table className={classes.table} size="small" stickyHeader={isSticky}>
-          <TableHead>
-            <TableRow>
-              {data.columns.map((column, index) => (
-                <StyledTableCell
-                  className={classes.head}
-                  key={index}
-                  onClick={() => {
-                    setFinalData(_.orderBy(finalData, column, order ? 'asc' : 
'desc'));
-                    setOrder(!order);
-                    setColumnClicked(column);
-                  }}
-                >
-                  {column}
-                  {column === columnClicked ? order ? (
-                    <ArrowDropDownIcon
-                      color="primary"
-                      style={{ verticalAlign: 'middle' }}
-                    />
-                  ) : (
-                    <ArrowDropUpIcon
-                      color="primary"
-                      style={{ verticalAlign: 'middle' }}
-                    />
-                  ) : null}
-                </StyledTableCell>
-              ))}
-            </TableRow>
-          </TableHead>
-          <TableBody className={classes.body}>
-            {finalData.length === 0 ? (
+  const renderTableComponent = () => {
+    return (
+      <>
+        <TableContainer style={{ maxHeight: isSticky ? 400 : 500 }}>
+          <Table className={classes.table} size="small" 
stickyHeader={isSticky}>
+            <TableHead>
               <TableRow>
-                <StyledTableCell
-                  className={classes.nodata}
-                  colSpan={data.columns.length}
-                >
-                  No Record(s) found
-                </StyledTableCell>
+                {data.columns.map((column, index) => (
+                  <StyledTableCell
+                    className={classes.head}
+                    key={index}
+                    onClick={() => {
+                      setFinalData(_.orderBy(finalData, column, order ? 'asc' 
: 'desc'));
+                      setOrder(!order);
+                      setColumnClicked(column);
+                    }}
+                  >
+                    {column}
+                    {column === columnClicked ? order ? (
+                      <ArrowDropDownIcon
+                        color="primary"
+                        style={{ verticalAlign: 'middle' }}
+                      />
+                    ) : (
+                      <ArrowDropUpIcon
+                        color="primary"
+                        style={{ verticalAlign: 'middle' }}
+                      />
+                    ) : null}
+                  </StyledTableCell>
+                ))}
               </TableRow>
-            ) : (
-              finalData
-                .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
-                .map((row, index) => (
-                  <StyledTableRow key={index} hover>
-                    {Object.values(row).map((cell, idx) =>
-                      addLinks && !idx ? (
-                        <StyledTableCell key={idx}>
-                          <NavLink
-                            className={classes.link}
-                            to={`${baseURL}${cell}`}
+            </TableHead>
+            <TableBody className={classes.body}>
+              {finalData.length === 0 ? (
+                <TableRow>
+                  <StyledTableCell
+                    className={classes.nodata}
+                    colSpan={2}
+                  >
+                    No Record(s) found
+                  </StyledTableCell>
+                </TableRow>
+              ) : (
+                finalData
+                  .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
+                  .map((row, index) => (
+                    <StyledTableRow key={index} hover>
+                      {Object.values(row).map((cell, idx) =>
+                        addLinks && !idx ? (
+                          <StyledTableCell key={idx}>
+                            <NavLink
+                              className={classes.link}
+                              to={`${baseURL}${cell}`}
+                            >
+                              {cell}
+                            </NavLink>
+                          </StyledTableCell>
+                        ) : (
+                          <StyledTableCell
+                            key={idx}
+                            className={isCellClickable ? 
classes.isCellClickable : (isSticky ? classes.isSticky : '')}
+                            onClick={() => {cellClickCallback && 
cellClickCallback(cell);}}
                           >
-                            {cell}
-                          </NavLink>
-                        </StyledTableCell>
-                      ) : (
-                        <StyledTableCell
-                          key={idx}
-                          className={isCellClickable ? classes.isCellClickable 
: (isSticky ? classes.isSticky : '')}
-                          onClick={() => {cellClickCallback && 
cellClickCallback(cell);}}
-                        >
-                          {styleCell(cell.toString())}
-                        </StyledTableCell>
-                      )
-                    )}
-                  </StyledTableRow>
-                ))
-            )}
-          </TableBody>
-        </Table>
-      </TableContainer>
-      {isPagination && finalData.length > 10 ? (
-        <TablePagination
-          rowsPerPageOptions={[5, 10, 25]}
-          component="div"
-          count={finalData.length}
-          rowsPerPage={rowsPerPage}
-          page={page}
-          onChangePage={handleChangePage}
-          onChangeRowsPerPage={handleChangeRowsPerPage}
-          ActionsComponent={TablePaginationActions}
-          classes={{ spacer: classes.spacer }}
+                            {styleCell(cell.toString())}
+                          </StyledTableCell>
+                        )
+                      )}
+                    </StyledTableRow>
+                  ))
+              )}
+            </TableBody>
+          </Table>
+        </TableContainer>
+        {isPagination && finalData.length > 10 ? (
+          <TablePagination
+            rowsPerPageOptions={[5, 10, 25]}
+            component="div"
+            count={finalData.length}
+            rowsPerPage={rowsPerPage}
+            page={page}
+            onChangePage={handleChangePage}
+            onChangeRowsPerPage={handleChangeRowsPerPage}
+            ActionsComponent={TablePaginationActions}
+            classes={{ spacer: classes.spacer }}
+          />
+        ) : null}
+      </>
+    );
+  };
+
+  const renderTable = () => {
+    return (
+      <>
+        <TableToolbar
+          name={title}
+          showSearchBox={showSearchBox}
+          searchValue={search}
+          handleSearch={(val: string) => setSearch(val)}
+          recordCount={recordsCount}
         />
-      ) : null}
+        {renderTableComponent()}
+      </>
+    );
+  };
+
+  const renderTableInAccordion = () => {
+    return (
+      <>
+        <SimpleAccordion
+          headerTitle={title}
+          showSearchBox={showSearchBox}
+          searchValue={search}
+          handleSearch={(val: string) => setSearch(val)}
+          recordCount={recordsCount}
+        >
+          {renderTableComponent()}
+        </SimpleAccordion>
+      </>
+    );
+  }
+
+  return (
+    <div className={highlightBackground ? classes.highlightBackground : 
classes.root}>
+      {inAccordionFormat ?
+        renderTableInAccordion()
+      :
+        renderTable()
+      }
     </div>
   );
 }
diff --git 
a/pinot-controller/src/main/resources/app/components/EnhancedTableToolbar.tsx 
b/pinot-controller/src/main/resources/app/components/TableToolbar.tsx
similarity index 88%
rename from 
pinot-controller/src/main/resources/app/components/EnhancedTableToolbar.tsx
rename to pinot-controller/src/main/resources/app/components/TableToolbar.tsx
index 9417900..9286b6b 100644
--- 
a/pinot-controller/src/main/resources/app/components/EnhancedTableToolbar.tsx
+++ b/pinot-controller/src/main/resources/app/components/TableToolbar.tsx
@@ -29,6 +29,7 @@ type Props = {
   showSearchBox: boolean;
   searchValue?: string;
   handleSearch?: Function;
+  recordCount?: number
 };
 
 const useToolbarStyles = makeStyles((theme) => ({
@@ -36,20 +37,23 @@ const useToolbarStyles = makeStyles((theme) => ({
     paddingLeft: '15px',
     paddingRight: '15px',
     minHeight: 48,
+    backgroundColor: 'rgba(66, 133, 244, 0.1)'
   },
   title: {
     flex: '1 1 auto',
     fontWeight: 600,
     letterSpacing: '1px',
-    fontSize: '1rem'
+    fontSize: '1rem',
+    color: '#4285f4'
   },
 }));
 
-export default function EnhancedTableToolbar({
+export default function TableToolbar({
   name,
   showSearchBox,
   searchValue,
-  handleSearch
+  handleSearch,
+  recordCount
 }: Props) {
   const classes = useToolbarStyles();
 
@@ -66,7 +70,7 @@ export default function EnhancedTableToolbar({
       {showSearchBox ? <SearchBar
         value={searchValue}
         onChange={(e) => handleSearch(e.target.value)}
-      /> : null}
+      /> : <strong>{(recordCount)}</strong>}
     </Toolbar>
   );
 }
\ 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 056ae41..7d42884 100644
--- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts
+++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts
@@ -82,6 +82,7 @@ declare module 'Models' {
   type schema = {
     name: string,
     dataType: string
+    fieldType?: string
   };
 
   export type SQLResult = {
@@ -91,6 +92,26 @@ declare module 'Models' {
         columnNames: Array<string>;
       }
       rows: Array<Array<number | string>>;
-    };
+    },
+    timeUsedMs: number
+    numDocsScanned: number
+    totalDocs: number
+    numServersQueried: number
+    numServersResponded: number
+    numSegmentsQueried: number
+    numSegmentsProcessed: number
+    numSegmentsMatched: number
+    numConsumingSegmentsQueried: number
+    numEntriesScannedInFilter: number
+    numEntriesScannedPostFilter: number
+    numGroupsLimitReached: boolean
+    partialResponse?: number
+    minConsumingFreshnessTimeMs: number
   };
+
+  export type ClusterName = {
+    clusterName: string
+  }
+
+  export type LiveInstances = Array<string>
 }
diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx 
b/pinot-controller/src/main/resources/app/pages/Query.tsx
index c9e5460..0464866 100644
--- a/pinot-controller/src/main/resources/app/pages/Query.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Query.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-nested-ternary */
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -22,20 +23,23 @@ import { makeStyles } from '@material-ui/core/styles';
 import { Grid, Checkbox, Button } from '@material-ui/core';
 import Alert from '@material-ui/lab/Alert';
 import FileCopyIcon from '@material-ui/icons/FileCopy';
-import { TableData, SQLResult } from 'Models';
+import { TableData } from 'Models';
 import { UnControlled as CodeMirror } from 'react-codemirror2';
 import 'codemirror/lib/codemirror.css';
 import 'codemirror/theme/material.css';
 import 'codemirror/mode/javascript/javascript';
 import 'codemirror/mode/sql/sql';
 import _ from 'lodash';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Switch from '@material-ui/core/Switch';
 import exportFromJSON from 'export-from-json';
 import Utils from '../utils/Utils';
-import { getQueryTables, getTableSchema, getQueryResult } from '../requests';
 import AppLoader from '../components/AppLoader';
 import CustomizedTables from '../components/Table';
 import QuerySideBar from '../components/Query/QuerySideBar';
-import EnhancedTableToolbar from '../components/EnhancedTableToolbar';
+import TableToolbar from '../components/TableToolbar';
+import SimpleAccordion from '../components/SimpleAccordion';
+import PinotMethodUtils from '../utils/PinotMethodUtils';
 
 const useStyles = makeStyles((theme) => ({
   title: {
@@ -49,7 +53,7 @@ const useStyles = makeStyles((theme) => ({
     '& .CodeMirror': { height: 100, border: '1px solid #BDCCD9' },
   },
   queryOutput: {
-    border: '1px solid #BDCCD9',
+    '& .CodeMirror': { height: 430, border: '1px solid #BDCCD9' },
   },
   btn: {
     margin: '10px 10px 0 0',
@@ -70,6 +74,9 @@ const useStyles = makeStyles((theme) => ({
     border: '1px #BDCCD9 solid',
     borderRadius: 4,
     marginBottom: '20px',
+  },
+  sqlError: {
+    whiteSpace: 'pre'
   }
 }));
 
@@ -94,6 +101,7 @@ const sqloptions = {
 const QueryPage = () => {
   const classes = useStyles();
   const [fetching, setFetching] = useState(true);
+  const [queryLoader, setQueryLoader] = useState(false);
   const [tableList, setTableList] = useState<TableData>({
     columns: [],
     records: [],
@@ -114,9 +122,17 @@ const QueryPage = () => {
 
   const [outputResult, setOutputResult] = useState('');
 
+  const [resultError, setResultError] = useState('');
+
+  const [queryStats, setQueryStats] = useState<TableData>({
+    columns: [],
+    records: []
+  });
+
   const [checked, setChecked] = React.useState({
     tracing: false,
     querySyntaxPQL: false,
+    showResultJSON: false
   });
 
   const [copyMsg, showCopyMsg] = React.useState(false);
@@ -129,15 +145,8 @@ const QueryPage = () => {
     setInputQuery(value);
   };
 
-  const getAsObject = (str: SQLResult) => {
-    if (typeof str === 'string' || str instanceof String) {
-      return JSON.parse(JSON.stringify(str));
-    }
-    return str;
-  };
-
-  const handleRunNow = (query?: string) => {
-    setFetching(true);
+  const handleRunNow = async (query?: string) => {
+    setQueryLoader(true);
     let url;
     let params;
     if (checked.querySyntaxPQL) {
@@ -154,80 +163,17 @@ const QueryPage = () => {
       });
     }
 
-    getQueryResult(params, url).then(({ data }) => {
-      let queryResponse = null;
-
-      queryResponse = getAsObject(data);
-
-      let dataArray = [];
-      let columnList = [];
-      if (checked.querySyntaxPQL === true) {
-        if (queryResponse) {
-          if (queryResponse.selectionResults) {
-            // Selection query
-            columnList = queryResponse.selectionResults.columns;
-            dataArray = queryResponse.selectionResults.results;
-          } else if (!queryResponse.aggregationResults[0]?.groupByResult) {
-            // Simple aggregation query
-            columnList = _.map(
-              queryResponse.aggregationResults,
-              (aggregationResult) => {
-                return { title: aggregationResult.function };
-              }
-            );
-
-            dataArray.push(
-              _.map(queryResponse.aggregationResults, (aggregationResult) => {
-                return aggregationResult.value;
-              })
-            );
-          } else if (queryResponse.aggregationResults[0]?.groupByResult) {
-            // Aggregation group by query
-            // TODO - Revisit
-            const columns = queryResponse.aggregationResults[0].groupByColumns;
-            columns.push(queryResponse.aggregationResults[0].function);
-            columnList = _.map(columns, (columnName) => {
-              return columnName;
-            });
-
-            dataArray = _.map(
-              queryResponse.aggregationResults[0].groupByResult,
-              (aggregationGroup) => {
-                const row = aggregationGroup.group;
-                row.push(aggregationGroup.value);
-                return row;
-              }
-            );
-          }
-        }
-      } else if (queryResponse.resultTable?.dataSchema?.columnNames?.length) {
-        columnList = queryResponse.resultTable.dataSchema.columnNames;
-        dataArray = queryResponse.resultTable.rows;
-      }
-
-      setResultData({
-        columns: columnList,
-        records: dataArray,
-      });
-      setFetching(false);
-
-      setOutputResult(JSON.stringify(data, null, 2));
-    });
+    const results = await PinotMethodUtils.getQueryResults(params, url, 
checked);
+    setResultError(results.error || '');
+    setResultData(results.result || {columns: [], records: []});
+    setQueryStats(results.queryStats || {columns: [], records: []});
+    setOutputResult(JSON.stringify(results.data, null, 2) || '');
+    setQueryLoader(false);
   };
 
-  const fetchSQLData = (tableName) => {
-    getTableSchema(tableName).then(({ data }) => {
-      const dimensionFields = data.dimensionFieldSpecs || [];
-      const metricFields = data.metricFieldSpecs || [];
-      const dateTimeField = data.dateTimeFieldSpecs || [];
-      const columnList = [...dimensionFields, ...metricFields, 
...dateTimeField];
-      setTableSchema({
-        columns: ['column', 'type'],
-        records: columnList.map((field) => {
-          return [field.name, field.dataType];
-        }),
-      });
-    });
+  const fetchSQLData = async (tableName) => {
+    const result = await PinotMethodUtils.getTableSchemaData(tableName, false);
+    setTableSchema(result);
 
     const query = `select * from ${tableName} limit 10`;
     setInputQuery(query);
@@ -268,16 +214,14 @@ const QueryPage = () => {
     }, 3000);
   };
 
+  const fetchData = async () => {
+    const result = await PinotMethodUtils.getQueryTablesList();
+    setTableList(result);
+    setFetching(false);
+  };
+
   useEffect(() => {
-    getQueryTables().then(({ data }) => {
-      setTableList({
-        columns: ['Tables'],
-        records: data.tables.map((table) => {
-          return [table];
-        }),
-      });
-      setFetching(false);
-    });
+    fetchData();
   }, []);
 
   return fetching ? (
@@ -289,13 +233,14 @@ const QueryPage = () => {
           tableList={tableList}
           fetchSQLData={fetchSQLData}
           tableSchema={tableSchema}
+          selectedTable={selectedTable}
         />
       </Grid>
       <Grid item xs style={{ padding: 20, backgroundColor: 'white', maxHeight: 
'calc(100vh - 70px)', overflowY: 'auto' }}>
         <Grid container>
           <Grid item xs={12} className={classes.rightPanel}>
             <div className={classes.sqlDiv}>
-              <EnhancedTableToolbar name="SQL Editor" showSearchBox={false} />
+              <TableToolbar name="SQL Editor" showSearchBox={false} />
               <CodeMirror
                 options={sqloptions}
                 value={inputQuery}
@@ -337,64 +282,111 @@ const QueryPage = () => {
               </Grid>
             </Grid>
 
-            <Grid item xs style={{ backgroundColor: 'white' }}>
-              {resultData.records.length ? (
-                <>
-                  <Grid container className={classes.actionBtns}>
-                    <Button
-                      variant="contained"
-                      color="primary"
-                      size="small"
-                      className={classes.btn}
-                      onClick={() => downloadData('xls')}
-                    >
-                      Excel
-                    </Button>
-                    <Button
-                      variant="contained"
-                      color="primary"
-                      size="small"
-                      className={classes.btn}
-                      onClick={() => downloadData('csv')}
-                    >
-                      CSV
-                    </Button>
-                    <Button
-                      variant="contained"
-                      color="primary"
-                      size="small"
-                      className={classes.btn}
-                      onClick={() => copyToClipboard()}
-                    >
-                      Copy
-                    </Button>
-                    {copyMsg ? (
-                      <Alert
-                        icon={<FileCopyIcon fontSize="inherit" />}
-                        severity="info"
-                      >
-                        Copied {resultData.records.length} rows to Clipboard
-                      </Alert>
-                    ) : null}
-                  </Grid>
-                  <CustomizedTables
-                    title={selectedTable}
-                    data={resultData}
-                    isPagination
-                    isSticky={true}
-                  />
-                </>
-              ) : null}
-            </Grid>
-
-            {resultData.records.length ? (
-              <CodeMirror
-                options={jsonoptions}
-                value={outputResult}
-                className={classes.queryOutput}
-                autoCursor={false}
-              />
-            ) : null}
+            {queryLoader ?
+              <AppLoader />
+            :
+              <>
+                {
+                  resultError ?
+                  <Alert severity="error" 
className={classes.sqlError}>{resultError}</Alert>
+                :
+                  <>
+                    {queryStats.records.length ?
+                      <Grid item xs style={{ backgroundColor: 'white' }}>
+                        <CustomizedTables
+                          title="Query Response Stats"
+                          data={queryStats}
+                          showSearchBox={true}
+                          inAccordionFormat={true}
+                        />
+                      </Grid>
+                      : null 
+                    }
+
+                    <Grid item xs style={{ backgroundColor: 'white' }}>
+                      {resultData.records.length ? (
+                        <>
+                          <Grid container className={classes.actionBtns}>
+                            <Button
+                              variant="contained"
+                              color="primary"
+                              size="small"
+                              className={classes.btn}
+                              onClick={() => downloadData('xls')}
+                            >
+                              Excel
+                            </Button>
+                            <Button
+                              variant="contained"
+                              color="primary"
+                              size="small"
+                              className={classes.btn}
+                              onClick={() => downloadData('csv')}
+                            >
+                              CSV
+                            </Button>
+                            <Button
+                              variant="contained"
+                              color="primary"
+                              size="small"
+                              className={classes.btn}
+                              onClick={() => copyToClipboard()}
+                            >
+                              Copy
+                            </Button>
+                            {copyMsg ? (
+                              <Alert
+                                icon={<FileCopyIcon fontSize="inherit" />}
+                                severity="info"
+                              >
+                                Copied {resultData.records.length} rows to 
Clipboard
+                              </Alert>
+                            ) : null}
+
+                            <FormControlLabel
+                              control={
+                                <Switch
+                                  checked={checked.showResultJSON}
+                                  onChange={handleChange}
+                                  name="showResultJSON"
+                                  color="primary"
+                                />
+                              }
+                              label="Show JSON format"
+                              className={classes.runNowBtn}
+                            />
+                          </Grid>
+                          {!checked.showResultJSON
+                            ?
+                              <CustomizedTables
+                                title="Query Result"
+                                data={resultData}
+                                isPagination
+                                isSticky={true}
+                                showSearchBox={true}
+                                inAccordionFormat={true}
+                              />
+                            :
+                            resultData.records.length ? (
+                              <SimpleAccordion
+                                headerTitle="Query Result (JSON Format)"
+                                showSearchBox={false}
+                              >
+                                <CodeMirror
+                                  options={jsonoptions}
+                                  value={outputResult}
+                                  className={classes.queryOutput}
+                                  autoCursor={false}
+                                />
+                              </SimpleAccordion>
+                            ) : null}
+                        </>
+                      ) : null}
+                    </Grid>
+                  </>
+                }
+              </>
+            }
           </Grid>
         </Grid>
       </Grid>
diff --git a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx 
b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
new file mode 100644
index 0000000..731b8b4
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx
@@ -0,0 +1,170 @@
+/**
+ * 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 } from 'react-router-dom';
+import { UnControlled as CodeMirror } from 'react-codemirror2';
+import AppLoader from '../components/AppLoader';
+import TableToolbar from '../components/TableToolbar';
+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 CustomizedTables from '../components/Table';
+import PinotMethodUtils from '../utils/PinotMethodUtils';
+
+const useStyles = makeStyles((theme) => ({
+  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',
+  },
+}));
+
+const jsonoptions = {
+  lineNumbers: true,
+  mode: 'application/json',
+  styleActiveLine: true,
+  gutters: ['CodeMirror-lint-markers'],
+  lint: true,
+  theme: 'default',
+};
+
+type Props = {
+  tenantName: string;
+  tableName: string;
+  segmentName: string;
+};
+
+type Summary = {
+  segmentName: string;
+  totalDocs: string | number;
+  createTime: unknown;
+};
+
+const SegmentDetails = ({ match }: RouteComponentProps<Props>) => {
+  const classes = useStyles();
+  const { tableName, segmentName } = match.params;
+
+  const [fetching, setFetching] = useState(true);
+  const [segmentSummary, setSegmentSummary] = useState<Summary>({
+    segmentName,
+    totalDocs: '',
+    createTime: '',
+  });
+
+  const [replica, setReplica] = useState({
+    columns: [],
+    records: []
+  });
+
+  const [value, setValue] = useState('');
+  const fetchData = async () => {
+    const result = await PinotMethodUtils.getSegmentDetails(tableName, 
segmentName);
+    setSegmentSummary(result.summary);
+    setReplica(result.replicaSet);
+    setValue(JSON.stringify(result.JSON, null, 2));
+    setFetching(false);
+  };
+  useEffect(() => {
+    fetchData();
+  }, []);
+  return fetching ? (
+    <AppLoader />
+  ) : (
+    <Grid
+      item
+      xs
+      style={{
+        padding: 20,
+        backgroundColor: 'white',
+        maxHeight: 'calc(100vh - 70px)',
+        overflowY: 'auto',
+      }}
+    >
+      <div className={classes.highlightBackground}>
+        <TableToolbar name="Summary" showSearchBox={false} />
+        <Grid container className={classes.body}>
+          <Grid item xs={6}>
+            <strong>Segment Name:</strong> {segmentSummary.segmentName}
+          </Grid>
+          <Grid item xs={3}>
+            <strong>Total Docs:</strong> {segmentSummary.totalDocs}
+          </Grid>
+          <Grid item xs={3}>
+            <strong>Create Time:</strong>  {segmentSummary.createTime}
+          </Grid>
+        </Grid>
+      </div>
+
+      <Grid container spacing={2}>
+        <Grid item xs={6}>
+          <CustomizedTables
+            title="Replica Set"
+            data={replica}
+            isPagination={true}
+            showSearchBox={true}
+            inAccordionFormat={true}
+          />
+        </Grid>
+        <Grid item xs={6}>
+          <div className={classes.sqlDiv}>
+            <SimpleAccordion
+              headerTitle="Metadata"
+              showSearchBox={false}
+            >
+              <CodeMirror
+                options={jsonoptions}
+                value={value}
+                className={classes.queryOutput}
+                autoCursor={false}
+              />
+            </SimpleAccordion>
+          </div>
+        </Grid>
+      </Grid>
+    </Grid>
+  );
+};
+
+export default SegmentDetails;
diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx 
b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
index fe65ce0..edfefa7 100644
--- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
+++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx
@@ -22,18 +22,39 @@ import { makeStyles } from '@material-ui/core/styles';
 import { Grid } from '@material-ui/core';
 import { RouteComponentProps } from 'react-router-dom';
 import { UnControlled as CodeMirror } from 'react-codemirror2';
+import { TableData } from 'Models';
+import _ from 'lodash';
 import AppLoader from '../components/AppLoader';
-import { getTenantTableDetails } from '../requests';
-import EnhancedTableToolbar from '../components/EnhancedTableToolbar';
+import CustomizedTables from '../components/Table';
+import TableToolbar from '../components/TableToolbar';
 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';
 
 const useStyles = makeStyles((theme) => ({
+  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: 800 },
+    '& .CodeMirror': { height: 532 },
   },
   sqlDiv: {
     border: '1px #BDCCD9 solid',
@@ -56,18 +77,58 @@ type Props = {
   tableName: string;
 };
 
+type Summary = {
+  tableName: string;
+  reportedSize: string | number;
+  estimatedSize: string | number;
+};
+
 const TenantPageDetails = ({ match }: RouteComponentProps<Props>) => {
+  const { tenantName, tableName } = match.params;
   const classes = useStyles();
   const [fetching, setFetching] = useState(true);
+  const [tableSummary, setTableSummary] = useState<Summary>({
+    tableName: match.params.tableName,
+    reportedSize: '',
+    estimatedSize: '',
+  });
+
+  const [segmentList, setSegmentList] = useState<TableData>({
+    columns: [],
+    records: [],
+  });
+
+  const [tableSchema, setTableSchema] = useState<TableData>({
+    columns: [],
+    records: [],
+  });
   const [value, setValue] = useState('');
 
+  const fetchTableData = async () => {
+    const result = await PinotMethodUtils.getTableSummaryData(tableName);
+    setTableSummary(result);
+    fetchSegmentData();
+  };
+
+  const fetchSegmentData = async () => {
+    const result = await PinotMethodUtils.getSegmentList(tableName);
+    setSegmentList(result);
+    fetchTableSchema();
+  };
+
+  const fetchTableSchema = async () => {
+    const result = await PinotMethodUtils.getTableSchemaData(tableName, true);
+    setTableSchema(result);
+    fetchTableJSON();
+  };
+
+  const fetchTableJSON = async () => {
+    const result = await PinotMethodUtils.getTableDetails(tableName);
+    setValue(JSON.stringify(result, null, 2));
+    setFetching(false);
+  };
   useEffect(() => {
-    getTenantTableDetails(match.params.tableName).then(
-      ({ data }) => {
-        setValue(JSON.stringify(data, null, 2));
-        setFetching(false);
-      }
-    );
+    fetchTableData();
   }, []);
   return fetching ? (
     <AppLoader />
@@ -82,15 +143,59 @@ const TenantPageDetails = ({ match }: 
RouteComponentProps<Props>) => {
         overflowY: 'auto',
       }}
     >
-      <div className={classes.sqlDiv}>
-        <EnhancedTableToolbar name={match.params.tableName} 
showSearchBox={false} />
-        <CodeMirror
-          options={jsonoptions}
-          value={value}
-          className={classes.queryOutput}
-          autoCursor={false}
-        />
+      <div className={classes.highlightBackground}>
+        <TableToolbar name="Summary" showSearchBox={false} />
+        <Grid container className={classes.body}>
+          <Grid item xs={4}>
+            <strong>Table Name:</strong> {tableSummary.tableName}
+          </Grid>
+          <Grid item xs={4}>
+            <strong>Reported Size:</strong> {tableSummary.reportedSize}
+          </Grid>
+          <Grid item xs={4}>
+            <strong>Estimated Size: </strong>
+            {tableSummary.estimatedSize}
+          </Grid>
+        </Grid>
       </div>
+
+      <Grid container spacing={2}>
+        <Grid item xs={6}>
+          <div className={classes.sqlDiv}>
+            <SimpleAccordion
+              headerTitle="Table Config"
+              showSearchBox={false}
+            >
+              <CodeMirror
+                options={jsonoptions}
+                value={value}
+                className={classes.queryOutput}
+                autoCursor={false}
+              />
+            </SimpleAccordion>
+          </div>
+          <CustomizedTables
+            title="Segments"
+            data={segmentList}
+            isPagination={false}
+            noOfRows={segmentList.records.length}
+            baseURL={`/tenants/${tenantName}/table/${tableName}/`}
+            addLinks
+            showSearchBox={true}
+            inAccordionFormat={true}
+          />
+        </Grid>
+        <Grid item xs={6}>
+          <CustomizedTables
+            title="Table Schema"
+            data={tableSchema}
+            isPagination={false}
+            noOfRows={tableSchema.records.length}
+            showSearchBox={true}
+            inAccordionFormat={true}
+          />
+        </Grid>
+      </Grid>
     </Grid>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/pages/Tenants.tsx 
b/pinot-controller/src/main/resources/app/pages/Tenants.tsx
index 30fef01..f425f47 100644
--- a/pinot-controller/src/main/resources/app/pages/Tenants.tsx
+++ b/pinot-controller/src/main/resources/app/pages/Tenants.tsx
@@ -23,7 +23,7 @@ import { TableData } from 'Models';
 import { RouteComponentProps } from 'react-router-dom';
 import CustomizedTables from '../components/Table';
 import AppLoader from '../components/AppLoader';
-import { getTenantTable, getTableSize, getIdealState } from '../requests';
+import PinotMethodUtils from '../utils/PinotMethodUtils';
 
 type Props = {
   tenantName: string
@@ -32,61 +32,33 @@ type Props = {
 const TenantPage = ({ match }: RouteComponentProps<Props>) => {
 
   const tenantName = match.params.tenantName;
+  const columnHeaders = ['Table Name', 'Reported Size', 'Estimated Size', 
'Number of Segments', 'Status'];
   const [fetching, setFetching] = useState(true);
   const [tableData, setTableData] = useState<TableData>({
-    columns: [],
+    columns: columnHeaders,
     records: []
   });
 
+  const fetchData = async () => {
+    const result = await PinotMethodUtils.getTenantTableData(tenantName);
+    setTableData(result);
+    setFetching(false);
+  };
   useEffect(() => {
-    getTenantTable(tenantName).then(({ data }) => {
-      const tableArr = data.tables.map(table => table);
-      if(tableArr.length){
-        const promiseArr = tableArr.map(name => getTableSize(name));
-        const promiseArr2 = tableArr.map(name => getIdealState(name));
-
-        Promise.all(promiseArr).then(results => {
-          Promise.all(promiseArr2).then(response => {
-            setTableData({
-              columns: ['Table Name', 'Reported Size', 'Estimated Size', 
'Number of Segments', 'Status'],
-              records: [
-                ...results.map(( result ) => {
-                  let actualValue; let idealValue;
-                  const tableSizeObj = result.data;
-                  response.forEach((res) => {
-                    const idealStateObj = res.data;
-                    if(tableSizeObj.realtimeSegments !== null && 
idealStateObj.REALTIME !== null){
-                      const { segments } = tableSizeObj.realtimeSegments;
-                      actualValue = Object.keys(segments).length;
-                      idealValue = Object.keys(idealStateObj.REALTIME).length;
-                    }else
-                    if(tableSizeObj.offlineSegments !== null && 
idealStateObj.OFFLINE !== null){
-                      const { segments } = tableSizeObj.offlineSegments;
-                      actualValue = Object.keys(segments).length;
-                      idealValue = Object.keys(idealStateObj.OFFLINE).length;
-                    }
-                  });
-                  return [tableSizeObj.tableName, 
tableSizeObj.reportedSizeInBytes, tableSizeObj.estimatedSizeInBytes,
-                    `${actualValue} / ${idealValue}`, actualValue === 
idealValue ? 'Good' : 'Bad'];
-                })
-              ]
-            });
-            setFetching(false);
-          });
-        });
-      }else {
-        setTableData({
-          columns: ['Table Name', 'Reported Size', 'Estimated Size', 'Number 
of Segments', 'Status'],
-          records: []
-        });
-        setFetching(false);
-      }
-    });
+    fetchData();
   }, []);
   return (
     fetching ? <AppLoader /> :
     <Grid item xs style={{ padding: 20, backgroundColor: 'white', maxHeight: 
'calc(100vh - 70px)', overflowY: 'auto' }}>
-      <CustomizedTables title={tenantName} data={tableData} isPagination 
addLinks baseURL={`/tenants/${tenantName}/table/`} />
+      <CustomizedTables
+        title={tenantName}
+        data={tableData}
+        isPagination
+        addLinks
+        baseURL={`/tenants/${tenantName}/table/`}
+        showSearchBox={true}
+        inAccordionFormat={true}
+      />
     </Grid>
   );
 };
diff --git a/pinot-controller/src/main/resources/app/requests/index.ts 
b/pinot-controller/src/main/resources/app/requests/index.ts
index cf22df4..80d9eed 100644
--- a/pinot-controller/src/main/resources/app/requests/index.ts
+++ b/pinot-controller/src/main/resources/app/requests/index.ts
@@ -18,7 +18,7 @@
  */
 
 import { AxiosResponse } from 'axios';
-import { TableData, Instances, Instance, Tenants, ClusterConfig, TableName, 
TableSize, IdealState, QueryTables, TableSchema, SQLResult } from 'Models';
+import { TableData, Instances, Instance, Tenants, ClusterConfig, TableName, 
TableSize, IdealState, QueryTables, TableSchema, SQLResult, ClusterName, 
LiveInstances } from 'Models';
 import { baseApi } from '../utils/axios-config';
 
 export const getTenants = (): Promise<AxiosResponse<Tenants>> =>
@@ -33,12 +33,18 @@ export const getTenantTable = (name: string): 
Promise<AxiosResponse<TableName>>
 export const getTenantTableDetails = (tableName: string): 
Promise<AxiosResponse<IdealState>> =>
   baseApi.get(`/tables/${tableName}`);
 
+export const getSegmentMetadata = (tableName: string, segmentName: string): 
Promise<AxiosResponse<IdealState>> =>
+  baseApi.get(`/segments/${tableName}/${segmentName}/metadata`);
+
 export const getTableSize = (name: string): Promise<AxiosResponse<TableSize>> 
=>
   baseApi.get(`/tables/${name}/size`);
 
 export const getIdealState = (name: string): 
Promise<AxiosResponse<IdealState>> =>
   baseApi.get(`/tables/${name}/idealstate`);
 
+export const getExternalView = (name: string): 
Promise<AxiosResponse<IdealState>> =>
+  baseApi.get(`/tables/${name}/externalview`);
+
 export const getInstances = (): Promise<AxiosResponse<Instances>> =>
   baseApi.get('/instances');
 
@@ -55,4 +61,10 @@ export const getTableSchema = (name: string): 
Promise<AxiosResponse<TableSchema>
   baseApi.get(`/tables/${name}/schema`);
 
 export const getQueryResult = (params: Object, url: string): 
Promise<AxiosResponse<SQLResult>> =>
-  baseApi.post(`/${url}`, params, { headers: { 'Content-Type': 
'application/json; charset=UTF-8', 'Accept': 'text/plain, */*; q=0.01' } });
\ No newline at end of file
+  baseApi.post(`/${url}`, params, { headers: { 'Content-Type': 
'application/json; charset=UTF-8', 'Accept': 'text/plain, */*; q=0.01' } });
+
+export const getClusterInfo = (): Promise<AxiosResponse<ClusterName>> =>
+  baseApi.get('/cluster/info');
+
+  export const getLiveInstancesFromClusterName = (params: string): 
Promise<AxiosResponse<LiveInstances>> =>
+  baseApi.get(`/zk/ls?path=${params}`);
diff --git a/pinot-controller/src/main/resources/app/router.tsx 
b/pinot-controller/src/main/resources/app/router.tsx
index 7a9a7ca..7023647 100644
--- a/pinot-controller/src/main/resources/app/router.tsx
+++ b/pinot-controller/src/main/resources/app/router.tsx
@@ -21,10 +21,12 @@ import HomePage from './pages/HomePage';
 import TenantsPage from './pages/Tenants';
 import TenantPageDetails from './pages/TenantDetails';
 import QueryPage from './pages/Query';
+import SegmentDetails from './pages/SegmentDetails';
 
 export default [
-  { path: "/cluster", Component: HomePage },
+  { path: "/", Component: HomePage },
   { path: "/tenants/:tenantName", Component: TenantsPage },
   { path: "/tenants/:tenantName/table/:tableName", Component: 
TenantPageDetails },
-  { path: "/", Component: QueryPage }
+  { path: "/query", Component: QueryPage },
+  { path: "/tenants/:tenantName/table/:tableName/:segmentName", Component: 
SegmentDetails }
 ];
\ 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
new file mode 100644
index 0000000..2b01b09
--- /dev/null
+++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts
@@ -0,0 +1,462 @@
+/**
+ * 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 _ from 'lodash';
+import { SQLResult } from 'Models';
+import moment from 'moment';
+import {
+  getTenants,
+  getInstances,
+  getInstance,
+  getClusterConfig,
+  getQueryTables,
+  getTableSchema,
+  getQueryResult,
+  getTenantTable,
+  getTableSize,
+  getIdealState,
+  getExternalView,
+  getTenantTableDetails,
+  getSegmentMetadata,
+  getClusterInfo,
+  getLiveInstancesFromClusterName
+} from '../requests';
+import Utils from './Utils';
+
+// This method is used to display tenants listing on cluster manager home page
+// API: /tenants
+// Expected Output: {columns: [], records: []}
+const getTenantsData = () => {
+  return getTenants().then(({ data }) => {
+    const records = _.union(data.SERVER_TENANTS, data.BROKER_TENANTS);
+    return {
+      columns: ['Tenant Name', 'Server', 'Broker', 'Tables'],
+      records: [
+        ...records.map((record) => [
+          record,
+          data.SERVER_TENANTS.indexOf(record) > -1 ? 1 : 0,
+          data.BROKER_TENANTS.indexOf(record) > -1 ? 1 : 0,
+          '-',
+        ]),
+      ],
+    };
+  });
+};
+
+type DataTable = {
+  [name: string]: string[];
+};
+
+// This method is used to fetch all instances on cluster manager home page
+// API: /instances
+// Expected Output: {Controller: ['Controller1', 'Controller2'], Broker: 
['Broker1', 'Broker2']}
+const getAllInstances = () => {
+  return getInstances().then(({ data }) => {
+    const initialVal: DataTable = {};
+    // It will create instances list array like
+    // {Controller: ['Controller1', 'Controller2'], Broker: ['Broker1', 
'Broker2']}
+    const groupedData = data.instances.reduce((r, a) => {
+      const y = a.split('_');
+      const key = y[0].trim();
+      r[key] = [...(r[key] || []), a];
+      return r;
+    }, initialVal);
+    return groupedData;
+  });
+};
+
+// This method is used to display instance data on cluster manager home page
+// API: /instances/:instaneName
+// Expected Output: {columns: [], records: []}
+const getInstanceData = (instances, liveInstanceArr) => {
+  const promiseArr = [...instances.map((inst) => getInstance(inst))];
+
+  return Promise.all(promiseArr).then((result) => {
+    return {
+        columns: ['Insance Name', 'Enabled', 'Hostname', 'Port', 'Status'],
+        records: [
+        ...result.map(({ data }) => [
+          data.instanceName,
+          data.enabled,
+          data.hostName,
+          data.port,
+          liveInstanceArr.indexOf(data.instanceName) > -1 ? 'Alive' : 'Dead'
+        ]),
+      ],
+    };
+  });
+};
+
+// This method is used to fetch cluster name
+// API: /cluster/info
+// Expected Output: {clusterName: ''}
+const getClusterName = () => {
+  return getClusterInfo().then(({ data }) => {
+    return data.clusterName;
+    })
+}
+
+// This method is used to fetch array of live instances name
+// API: /zk/ls?path=:ClusterName/LIVEINSTANCES
+// Expected Output: []
+const getLiveInstance = (clusterName) => {
+  const params = encodeURIComponent(`/${clusterName}/LIVEINSTANCES`)
+    return getLiveInstancesFromClusterName(params).then((data) => {
+      return data;
+    })
+}
+
+// This method is used to diaplay cluster congifuration on cluster manager 
home page
+// API: /cluster/configs
+// Expected Output: {columns: [], records: []}
+const getClusterConfigData = () => {
+  return getClusterConfig().then(({ data }) => {
+    return {
+      columns: ['Property', 'Value'],
+      records: [...Object.keys(data).map((key) => [key, data[key]])],
+    };
+  });
+};
+
+// This method is used to display table listing on query page
+// API: /tables
+// Expected Output: {columns: [], records: []}
+const getQueryTablesList = () => {
+  return getQueryTables().then(({ data }) => {
+    return {
+      columns: ['Tables'],
+      records: data.tables.map((table) => {
+        return [table];
+      }),
+    };
+  });
+};
+
+// This method is used to display particular table schema on query page
+// API: /tables/:tableName/schema
+// Expected Output: {columns: [], records: []}
+const getTableSchemaData = (tableName, showFieldType) => {
+  return getTableSchema(tableName).then(({ data }) => {
+    const dimensionFields = data.dimensionFieldSpecs || [];
+    const metricFields = data.metricFieldSpecs || [];
+    const dateTimeField = data.dateTimeFieldSpecs || [];
+
+    dimensionFields.map((field) => {
+      field.fieldType = 'Dimension';
+    });
+
+    metricFields.map((field) => {
+      field.fieldType = 'Metric';
+    });
+
+    dateTimeField.map((field) => {
+      field.fieldType = 'Date-Time';
+    });
+    const columnList = [...dimensionFields, ...metricFields, ...dateTimeField];
+    if (showFieldType) {
+      return {
+        columns: ['column', 'type', 'Field Type'],
+        records: columnList.map((field) => {
+          return [field.name, field.dataType, field.fieldType];
+        }),
+      };
+    }
+    return {
+      columns: ['column', 'type'],
+      records: columnList.map((field) => {
+        return [field.name, field.dataType];
+      }),
+    };
+  });
+};
+
+const getAsObject = (str: SQLResult) => {
+  if (typeof str === 'string' || str instanceof String) {
+    return JSON.parse(JSON.stringify(str));
+  }
+  return str;
+};
+
+// This method is used to display query output in tabular format as well as 
JSON format on query page
+// API: /:urlName (Eg: sql or pql)
+// Expected Output: {columns: [], records: []}
+const getQueryResults = (params, url, checkedOptions) => {
+  return getQueryResult(params, url).then(({ data }) => {
+    let queryResponse = null;
+
+    queryResponse = getAsObject(data);
+
+    // if sql api throws error, handle here
+    if(typeof queryResponse === 'string'){
+      return {error: queryResponse};
+    } else if(queryResponse.exceptions.length){
+      return {error: JSON.stringify(queryResponse.exceptions, null, 2)};
+    }
+
+    let dataArray = [];
+    let columnList = [];
+    if (checkedOptions.querySyntaxPQL === true) {
+      if (queryResponse) {
+        if (queryResponse.selectionResults) {
+          // Selection query
+          columnList = queryResponse.selectionResults.columns;
+          dataArray = queryResponse.selectionResults.results;
+        } else if (!queryResponse.aggregationResults[0]?.groupByResult) {
+          // Simple aggregation query
+          columnList = _.map(
+            queryResponse.aggregationResults,
+            (aggregationResult) => {
+              return { title: aggregationResult.function };
+            }
+          );
+
+          dataArray.push(
+            _.map(queryResponse.aggregationResults, (aggregationResult) => {
+              return aggregationResult.value;
+            })
+          );
+        } else if (queryResponse.aggregationResults[0]?.groupByResult) {
+          // Aggregation group by query
+          // TODO - Revisit
+          const columns = queryResponse.aggregationResults[0].groupByColumns;
+          columns.push(queryResponse.aggregationResults[0].function);
+          columnList = _.map(columns, (columnName) => {
+            return columnName;
+          });
+
+          dataArray = _.map(
+            queryResponse.aggregationResults[0].groupByResult,
+            (aggregationGroup) => {
+              const row = aggregationGroup.group;
+              row.push(aggregationGroup.value);
+              return row;
+            }
+          );
+        }
+      }
+    } else if (queryResponse.resultTable?.dataSchema?.columnNames?.length) {
+      columnList = queryResponse.resultTable.dataSchema.columnNames;
+      dataArray = queryResponse.resultTable.rows;
+    }
+
+    const columnStats = [ 'timeUsedMs',
+      'numDocsScanned',
+      'totalDocs',
+      'numServersQueried',
+      'numServersResponded',
+      'numSegmentsQueried',
+      'numSegmentsProcessed',
+      'numSegmentsMatched',
+      'numConsumingSegmentsQueried',
+      'numEntriesScannedInFilter',
+      'numEntriesScannedPostFilter',
+      'numGroupsLimitReached',
+      'partialResponse',
+      'minConsumingFreshnessTimeMs'];
+
+    return {
+      result: {
+        columns: columnList,
+        records: dataArray,
+      },
+      queryStats: {
+        columns: columnStats,
+        records: [[data.timeUsedMs, data.numDocsScanned, data.totalDocs, 
data.numServersQueried, data.numServersResponded,
+          data.numSegmentsQueried, data.numSegmentsProcessed, 
data.numSegmentsMatched, data.numConsumingSegmentsQueried,
+          data.numEntriesScannedInFilter, data.numEntriesScannedPostFilter, 
data.numGroupsLimitReached,
+          data.partialResponse ? data.partialResponse : '-', 
data.minConsumingFreshnessTimeMs]]
+      },
+      data,
+    };
+  });
+};
+
+// This method is used to display table data of a particular tenant
+// API: /tenants/:tenantName/tables
+//      /tables/:tableName/size
+//      /tables/:tableName/idealstate
+//      /tables/:tableName/externalview
+// Expected Output: {columns: [], records: []}
+const getTenantTableData = (tenantName) => {
+  const columnHeaders = [
+    'Table Name',
+    'Reported Size',
+    'Estimated Size',
+    'Number of Segments',
+    'Status',
+  ];
+  return getTenantTable(tenantName).then(({ data }) => {
+    const tableArr = data.tables.map((table) => table);
+    if (tableArr.length) {
+      const promiseArr = [];
+      tableArr.map((name) => {
+        promiseArr.push(getTableSize(name));
+        promiseArr.push(getIdealState(name));
+        promiseArr.push(getExternalView(name));
+      });
+
+      return Promise.all(promiseArr).then((results) => {
+        let finalRecordsArr = [];
+        let singleTableData = [];
+        let idealStateObj = null;
+        let externalViewObj = null;
+        results.map((result, index) => {
+          // since we have 3 promises, we are using mod 3 below
+          if (index % 3 === 0) {
+            // response of getTableSize API
+            const {
+              tableName,
+              reportedSizeInBytes,
+              estimatedSizeInBytes,
+            } = result.data;
+            singleTableData.push(
+              tableName,
+              reportedSizeInBytes,
+              estimatedSizeInBytes
+            );
+          } else if (index % 3 === 1) {
+            // response of getIdealState API
+            idealStateObj = result.data.OFFLINE || result.data.REALTIME;
+          } else if (index % 3 === 2) {
+            // response of getExternalView API
+            externalViewObj = result.data.OFFLINE || result.data.REALTIME;
+            const externalSegmentCount = Object.keys(externalViewObj).length;
+            const idealSegmentCount = Object.keys(idealStateObj).length;
+            // Generating data for the record
+            singleTableData.push(
+              `${externalSegmentCount} / ${idealSegmentCount}`,
+              Utils.getSegmentStatus(idealStateObj, externalViewObj)
+            );
+            // saving into records array
+            finalRecordsArr.push(singleTableData);
+            // resetting the required variables
+            singleTableData = [];
+            idealStateObj = null;
+            externalViewObj = null;
+          }
+        });
+        return {
+          columns: columnHeaders,
+          records: finalRecordsArr,
+        };
+      });
+    }
+  });
+};
+
+// This method is used to display summary of a particular tenant table
+// API: /tables/:tableName/size
+// Expected Output: {tableName: '', reportedSize: '', estimatedSize: ''}
+const getTableSummaryData = (tableName) => {
+  return getTableSize(tableName).then(({ data }) => {
+    return {
+      tableName: data.tableName,
+      reportedSize: data.reportedSizeInBytes,
+      estimatedSize: data.estimatedSizeInBytes,
+    };
+  });
+};
+
+// This method is used to display segment list of a particular tenant table
+// API: /tables/:tableName/idealstate
+//      /tables/:tableName/externalview
+// Expected Output: {columns: [], records: []}
+const getSegmentList = (tableName) => {
+  const promiseArr = [];
+  promiseArr.push(getIdealState(tableName));
+  promiseArr.push(getExternalView(tableName));
+
+  return Promise.all(promiseArr).then((results) => {
+    const idealStateObj = results[0].data.OFFLINE || results[0].data.REALTIME;
+    const externalViewObj = results[1].data.OFFLINE || 
results[1].data.REALTIME;
+
+    return {
+      columns: ['Segment Name', 'Status'],
+      records: Object.keys(idealStateObj).map((key) => {
+        return [
+          key,
+          _.isEqual(idealStateObj[key], externalViewObj[key]) ? 'Good' : 'Bad',
+        ];
+      }),
+    };
+  });
+};
+
+// This method is used to display JSON format of a particular tenant table
+// API: /tables/:tableName/idealstate
+//      /tables/:tableName/externalview
+// Expected Output: {columns: [], records: []}
+const getTableDetails = (tableName) => {
+  return getTenantTableDetails(tableName).then(({ data }) => {
+    return data;
+  });
+};
+
+// This method is used to display summary of a particular segment, replia set 
as well as JSON format of a tenant table
+// API: /tables/tableName/externalview
+//      /segments/:tableName/:segmentName/metadata
+// Expected Output: {columns: [], records: []}
+const getSegmentDetails = (tableName, segmentName) => {
+  const promiseArr = [];
+  promiseArr.push(getExternalView(tableName));
+  promiseArr.push(getSegmentMetadata(tableName, segmentName));
+
+  return Promise.all(promiseArr).then((results) => {
+    const obj = results[0].data.OFFLINE || results[0].data.REALTIME;
+    const segmentMetaData = results[1].data;
+
+    let result = [];
+    for (const prop in obj[segmentName]) {
+      if (obj[segmentName]) {
+        result.push([prop, obj[segmentName][prop]]);
+      }
+    }
+
+    return {
+      replicaSet: {
+        columns: ['Server Name', 'Status'],
+        records: [...result],
+      },
+      summary: {
+        segmentName,
+        totalDocs: segmentMetaData['segment.total.docs'],
+        createTime: moment(+segmentMetaData['segment.creation.time']).format(
+          'MMMM Do YYYY, h:mm:ss'
+        ),
+      },
+      JSON: segmentMetaData
+    };
+  });
+};
+export default {
+  getTenantsData,
+  getAllInstances,
+  getInstanceData,
+  getClusterConfigData,
+  getQueryTablesList,
+  getTableSchemaData,
+  getQueryResults,
+  getTenantTableData,
+  getTableSummaryData,
+  getSegmentList,
+  getTableDetails,
+  getSegmentDetails,
+  getClusterName,
+  getLiveInstance
+};
diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx 
b/pinot-controller/src/main/resources/app/utils/Utils.tsx
index 4af560c..ad45b28 100644
--- a/pinot-controller/src/main/resources/app/utils/Utils.tsx
+++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx
@@ -17,6 +17,8 @@
  * under the License.
  */
 
+import _ from 'lodash';
+
 const sortArray = function (sortingArr, keyName, ascendingFlag) {
   if (ascendingFlag) {
     return sortingArr.sort(function (a, b) {
@@ -55,7 +57,30 @@ const tableFormat = (data) => {
   return results;
 };
 
+const getSegmentStatus = (idealStateObj, externalViewObj) => {
+  const idealSegmentKeys = Object.keys(idealStateObj);
+  const idealSegmentCount = idealSegmentKeys.length;
+
+  const externalSegmentKeys = Object.keys(externalViewObj);
+  const externalSegmentCount = externalSegmentKeys.length;
+
+  if(idealSegmentCount !== externalSegmentCount){
+    return 'Bad';
+  }
+
+  let segmentStatus = 'Good';
+  idealSegmentKeys.map((segmentKey) => {
+    if(segmentStatus === 'Good'){
+      if( !_.isEqual( idealStateObj[segmentKey], externalViewObj[segmentKey] ) 
){
+        segmentStatus = 'Bad';
+      }
+    }
+  });
+  return segmentStatus;
+};
+
 export default {
   sortArray,
   tableFormat,
+  getSegmentStatus
 };
diff --git a/pinot-controller/src/main/resources/package.json 
b/pinot-controller/src/main/resources/package.json
index e298c97..0634b4a 100644
--- a/pinot-controller/src/main/resources/package.json
+++ b/pinot-controller/src/main/resources/package.json
@@ -55,7 +55,7 @@
     "@fortawesome/fontawesome-svg-core": "^1.2.29",
     "@fortawesome/free-solid-svg-icons": "^5.13.1",
     "@fortawesome/react-fontawesome": "^0.1.11",
-    "@material-ui/core": "^4.9.11",
+    "@material-ui/core": "4.11.0",
     "@material-ui/icons": "^4.9.1",
     "@material-ui/lab": "^4.0.0-alpha.51",
     "@types/react-router-dom": "^5.1.5",
@@ -67,6 +67,7 @@
     "html-loader": "0.5.5",
     "html-webpack-plugin": "^4.2.1",
     "lodash": "^4.17.17",
+    "moment": "^2.27.0",
     "prop-types": "^15.7.2",
     "react": "16.13.1",
     "react-codemirror2": "^7.2.1",


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

Reply via email to