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

agrove pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-ballista.git


The following commit(s) were added to refs/heads/master by this push:
     new d894beaa [UI] Use tabbed pane with Queries and Executors tabs (#309)
d894beaa is described below

commit d894beaa848584b80d35f9d951b441353c59ea36
Author: Andy Grove <[email protected]>
AuthorDate: Mon Oct 3 07:51:21 2022 -0600

    [UI] Use tabbed pane with Queries and Executors tabs (#309)
---
 ballista/rust/scheduler/src/api/handlers.rs        | 32 ++++++++++------
 ballista/rust/scheduler/src/api/mod.rs             | 11 ++++--
 ballista/ui/scheduler/src/App.tsx                  | 43 +++++++++++++++++++---
 .../{NodesList.tsx => ExecutorsList.tsx}           | 23 ++++++------
 .../ui/scheduler/src/components/QueriesList.tsx    | 30 +++++++--------
 ballista/ui/scheduler/src/components/Summary.tsx   |  7 ----
 ballista/ui/scheduler/yarn.lock                    | 18 +++++++++
 7 files changed, 110 insertions(+), 54 deletions(-)

diff --git a/ballista/rust/scheduler/src/api/handlers.rs 
b/ballista/rust/scheduler/src/api/handlers.rs
index fb42c22d..d51b1e60 100644
--- a/ballista/rust/scheduler/src/api/handlers.rs
+++ b/ballista/rust/scheduler/src/api/handlers.rs
@@ -22,12 +22,16 @@ use graphviz_rust::printer::PrinterContext;
 use warp::Rejection;
 
 #[derive(Debug, serde::Serialize)]
-struct StateResponse {
-    executors: Vec<ExecutorMetaResponse>,
+struct SchedulerStateResponse {
     started: u128,
     version: &'static str,
 }
 
+#[derive(Debug, serde::Serialize)]
+struct ExecutorsResponse {
+    executors: Vec<ExecutorMetaResponse>,
+}
+
 #[derive(Debug, serde::Serialize)]
 pub struct ExecutorMetaResponse {
     pub id: String,
@@ -45,12 +49,21 @@ pub struct JobResponse {
     pub percent_complete: u8,
 }
 
-/// Return current scheduler state, including list of executors and active, 
completed, and failed
-/// job ids.
-pub(crate) async fn get_state<T: AsLogicalPlan, U: AsExecutionPlan>(
+/// Return current scheduler state
+pub(crate) async fn get_scheduler_state<T: AsLogicalPlan, U: AsExecutionPlan>(
+    data_server: SchedulerServer<T, U>,
+) -> Result<impl warp::Reply, Rejection> {
+    let response = SchedulerStateResponse {
+        started: data_server.start_time,
+        version: BALLISTA_VERSION,
+    };
+    Ok(warp::reply::json(&response))
+}
+
+/// Return list of executors
+pub(crate) async fn get_executors<T: AsLogicalPlan, U: AsExecutionPlan>(
     data_server: SchedulerServer<T, U>,
 ) -> Result<impl warp::Reply, Rejection> {
-    // TODO: Display last seen information in UI
     let state = data_server.state;
     let executors: Vec<ExecutorMetaResponse> = state
         .executor_manager
@@ -66,12 +79,7 @@ pub(crate) async fn get_state<T: AsLogicalPlan, U: 
AsExecutionPlan>(
         })
         .collect();
 
-    let response = StateResponse {
-        executors,
-        started: data_server.start_time,
-        version: BALLISTA_VERSION,
-    };
-    Ok(warp::reply::json(&response))
+    Ok(warp::reply::json(&executors))
 }
 
 /// Return list of jobs
diff --git a/ballista/rust/scheduler/src/api/mod.rs 
b/ballista/rust/scheduler/src/api/mod.rs
index ca621f5f..42518a47 100644
--- a/ballista/rust/scheduler/src/api/mod.rs
+++ b/ballista/rust/scheduler/src/api/mod.rs
@@ -85,9 +85,13 @@ fn with_data_server<T: AsLogicalPlan + Clone, U: 'static + 
AsExecutionPlan>(
 pub fn get_routes<T: AsLogicalPlan + Clone, U: 'static + AsExecutionPlan>(
     scheduler_server: SchedulerServer<T, U>,
 ) -> BoxedFilter<(impl Reply,)> {
-    let route_state = warp::path!("api" / "state")
+    let route_scheduler_state = warp::path!("api" / "state")
         .and(with_data_server(scheduler_server.clone()))
-        .and_then(handlers::get_state);
+        .and_then(handlers::get_scheduler_state);
+
+    let route_executors = warp::path!("api" / "executors")
+        .and(with_data_server(scheduler_server.clone()))
+        .and_then(handlers::get_executors);
 
     let route_jobs = warp::path!("api" / "jobs")
         .and(with_data_server(scheduler_server.clone()))
@@ -104,7 +108,8 @@ pub fn get_routes<T: AsLogicalPlan + Clone, U: 'static + 
AsExecutionPlan>(
         .and(with_data_server(scheduler_server))
         .and_then(|job_id, data_server| 
handlers::get_job_svg_graph(data_server, job_id));
 
-    let routes = route_state
+    let routes = route_scheduler_state
+        .or(route_executors)
         .or(route_jobs)
         .or(route_job_summary)
         .or(route_job_dot)
diff --git a/ballista/ui/scheduler/src/App.tsx 
b/ballista/ui/scheduler/src/App.tsx
index 4ff9cbc1..c66fb37f 100644
--- a/ballista/ui/scheduler/src/App.tsx
+++ b/ballista/ui/scheduler/src/App.tsx
@@ -16,17 +16,27 @@
 // under the License.
 
 import React, { useState, useEffect } from "react";
-import { Box, Grid, VStack } from "@chakra-ui/react";
+import {
+  Box,
+  Grid,
+  Tab,
+  TabList,
+  TabPanel,
+  TabPanels,
+  Tabs,
+  VStack,
+} from "@chakra-ui/react";
 import { Header } from "./components/Header";
 import { Summary } from "./components/Summary";
+import { ExecutorsList } from "./components/ExecutorsList";
 import { QueriesList } from "./components/QueriesList";
 import { Footer } from "./components/Footer";
-
 import "./App.css";
 
 const App: React.FunctionComponent<any> = () => {
   const [schedulerState, setSchedulerState] = useState(undefined);
   const [jobs, setJobs] = useState(undefined);
+  const [executors, setExecutors] = useState(undefined);
 
   function getSchedulerState() {
     return fetch(`/api/state`, {
@@ -50,20 +60,43 @@ const App: React.FunctionComponent<any> = () => {
       .then((res) => setJobs(res));
   }
 
+  function getExecutors() {
+    return fetch(`/api/executors`, {
+      method: "POST",
+      headers: {
+        Accept: "application/json",
+      },
+    })
+      .then((res) => res.json())
+      .then((res) => setExecutors(res));
+  }
+
   useEffect(() => {
     getSchedulerState();
     getJobs();
+    getExecutors();
   }, []);
 
-  console.log(JSON.stringify(schedulerState));
-
   return (
     <Box>
       <Grid minH="100vh">
         <VStack alignItems={"flex-start"} spacing={0} width={"100%"}>
           <Header schedulerState={schedulerState} />
           <Summary schedulerState={schedulerState} />
-          <QueriesList queries={jobs} />
+          <Tabs width={"100%"}>
+            <TabList>
+              <Tab>Jobs</Tab>
+              <Tab>Executors</Tab>
+            </TabList>
+            <TabPanels>
+              <TabPanel>
+                <QueriesList queries={jobs} />
+              </TabPanel>
+              <TabPanel>
+                <ExecutorsList executors={executors} />
+              </TabPanel>
+            </TabPanels>
+          </Tabs>
           <Footer />
         </VStack>
       </Grid>
diff --git a/ballista/ui/scheduler/src/components/NodesList.tsx 
b/ballista/ui/scheduler/src/components/ExecutorsList.tsx
similarity index 79%
rename from ballista/ui/scheduler/src/components/NodesList.tsx
rename to ballista/ui/scheduler/src/components/ExecutorsList.tsx
index f437e46c..9fb2262d 100644
--- a/ballista/ui/scheduler/src/components/NodesList.tsx
+++ b/ballista/ui/scheduler/src/components/ExecutorsList.tsx
@@ -19,22 +19,27 @@ import React from "react";
 import { Box } from "@chakra-ui/react";
 import { Column, ElapsedCell, DataTable } from "./DataTable";
 
-export enum NodeStatus {
+export enum ExecutorStatus {
   RUNNING = "RUNNING",
   TERMINATED = "TERMINATED",
 }
 
-export interface NodeInfo {
+export interface ExecutorsListProps {
+  executors?: ExecutorMeta[];
+}
+
+export interface ExecutorMeta {
   id: string;
   host: string;
   port: number;
-  status: NodeStatus;
+  last_seen: number;
+  status: ExecutorStatus;
   started: string;
 }
 
 const columns: Column<any>[] = [
   {
-    Header: "Node",
+    Header: "ID",
     accessor: "id",
   },
   {
@@ -56,16 +61,12 @@ const columns: Column<any>[] = [
   },
 ];
 
-interface NodesListProps {
-  nodes: NodeInfo[];
-}
-
-export const NodesList: React.FunctionComponent<NodesListProps> = ({
-  nodes = [],
+export const ExecutorsList: React.FunctionComponent<ExecutorsListProps> = ({
+  executors = [],
 }) => {
   return (
     <Box flex={1}>
-      <DataTable maxW={960} columns={columns} data={nodes} pageSize={4} />
+      <DataTable columns={columns} data={executors} pageSize={4} />
     </Box>
   );
 };
diff --git a/ballista/ui/scheduler/src/components/QueriesList.tsx 
b/ballista/ui/scheduler/src/components/QueriesList.tsx
index 05a97802..6cdf2e88 100644
--- a/ballista/ui/scheduler/src/components/QueriesList.tsx
+++ b/ballista/ui/scheduler/src/components/QueriesList.tsx
@@ -163,12 +163,13 @@ const columns: Column<any>[] = [
   },
 ];
 
-const getSkeletion = () => (
+const getSkeleton = () => (
   <>
     <Skeleton height={5} />
     <Skeleton height={5} />
     <Skeleton height={5} />
     <Skeleton height={5} />
+    <Skeleton height={5} />
   </>
 );
 
@@ -178,20 +179,17 @@ export const QueriesList: 
React.FunctionComponent<QueriesListProps> = ({
   const isLoaded = typeof queries !== "undefined";
 
   return (
-    <VStack flex={1} p={4} w={"100%"} alignItems={"flex-start"}>
-      <Text mb={4}>Queries</Text>
-      <Stack w={"100%"} flex={1}>
-        {isLoaded ? (
-          <DataTable
-            columns={columns}
-            data={queries || []}
-            pageSize={10}
-            pb={10}
-          />
-        ) : (
-          getSkeletion()
-        )}
-      </Stack>
-    </VStack>
+    <Box w={"100%"} flex={1}>
+      {isLoaded ? (
+        <DataTable
+          columns={columns}
+          data={queries || []}
+          pageSize={10}
+          pb={10}
+        />
+      ) : (
+        getSkeleton()
+      )}
+    </Box>
   );
 };
diff --git a/ballista/ui/scheduler/src/components/Summary.tsx 
b/ballista/ui/scheduler/src/components/Summary.tsx
index cb334bb3..d9734d93 100644
--- a/ballista/ui/scheduler/src/components/Summary.tsx
+++ b/ballista/ui/scheduler/src/components/Summary.tsx
@@ -19,7 +19,6 @@ import React from "react";
 import { Box, Text, Flex, VStack } from "@chakra-ui/react";
 import { HiCheckCircle } from "react-icons/hi";
 import TimeAgo from "react-timeago";
-import { NodesList, NodeInfo } from "./NodesList";
 
 const Label: React.FunctionComponent<React.PropsWithChildren<any>> = ({
   children,
@@ -35,7 +34,6 @@ export interface SchedulerState {
   status: string;
   started: string;
   version: string;
-  executors: NodeInfo[];
 }
 
 export interface SummaryProps {
@@ -75,10 +73,6 @@ export const Summary: React.FunctionComponent<SummaryProps> 
= ({
                 <Text pl={1}>Active</Text>
               </Flex>
             </Flex>
-            <Flex>
-              <Label>Nodes</Label>
-              <Text>{schedulerState.executors?.length}</Text>
-            </Flex>
             <Flex>
               <Label>Started</Label>
               <Text>
@@ -90,7 +84,6 @@ export const Summary: React.FunctionComponent<SummaryProps> = 
({
               <Text>{schedulerState.version}</Text>
             </Flex>
           </VStack>
-          <NodesList nodes={schedulerState.executors} />
         </Flex>
       </Box>
     </Flex>
diff --git a/ballista/ui/scheduler/yarn.lock b/ballista/ui/scheduler/yarn.lock
index 4cec41e5..0c1a9611 100644
--- a/ballista/ui/scheduler/yarn.lock
+++ b/ballista/ui/scheduler/yarn.lock
@@ -5442,6 +5442,11 @@ execa@^4.0.0:
     signal-exit "^3.0.2"
     strip-final-newline "^2.0.0"
 
+exenv@^1.2.2:
+  version "1.2.2"
+  resolved 
"https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d";
+  integrity 
sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==
+
 exit@^0.1.2:
   version "0.1.2"
   resolved 
"https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c";
@@ -9770,11 +9775,24 @@ [email protected]:
     use-callback-ref "^1.2.1"
     use-sidecar "^1.0.1"
 
+react-from-dom@^0.6.2:
+  version "0.6.2"
+  resolved 
"https://registry.yarnpkg.com/react-from-dom/-/react-from-dom-0.6.2.tgz#9da903a508c91c013b55afcd59348b8b0a39bdb4";
+  integrity 
sha512-qvWWTL/4xw4k/Dywd41RBpLQUSq97csuv15qrxN+izNeLYlD9wn5W8LspbfYe5CWbaSdkZ72BsaYBPQf2x4VbQ==
+
 react-icons@^4.2.0:
   version "4.2.0"
   resolved 
"https://registry.yarnpkg.com/react-icons/-/react-icons-4.2.0.tgz#6dda80c8a8f338ff96a1851424d63083282630d0";
   integrity 
sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==
 
+react-inlinesvg@^3.0.1:
+  version "3.0.1"
+  resolved 
"https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-3.0.1.tgz#2133f5d2c770ac405060db2ce1c13eed30e7e83b";
+  integrity 
sha512-cBfoyfseNI2PkDA7ZKIlDoHq0eMfpoC3DhKBQNC+/X1M4ZQB+aXW+YiNPUDDDKXUsGDUIZWWiZWNFeauDIVdoA==
+  dependencies:
+    exenv "^1.2.2"
+    react-from-dom "^0.6.2"
+
 react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
   version "16.13.1"
   resolved 
"https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4";

Reply via email to