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"