diff --git a/.cursor/rules.md b/.cursor/rules.md new file mode 100644 index 0000000000..4ee2be4ad3 --- /dev/null +++ b/.cursor/rules.md @@ -0,0 +1,5 @@ +When making a code review never link to any issue or a pull request +in any GitHub repository! + +Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! + diff --git a/.gemini/rules.md b/.gemini/rules.md new file mode 100644 index 0000000000..4ee2be4ad3 --- /dev/null +++ b/.gemini/rules.md @@ -0,0 +1,5 @@ +When making a code review never link to any issue or a pull request +in any GitHub repository! + +Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! + diff --git a/.github/workflows/web-tui.yml b/.github/workflows/web-tui.yml index ba7b17e981..58600166d6 100644 --- a/.github/workflows/web-tui.yml +++ b/.github/workflows/web-tui.yml @@ -86,7 +86,7 @@ jobs: --no-default-features --features web - name: Deploy at nightlies.a.o - if: ${{ github.event == 'push' && github.ref == 'refs/heads/main' }} + if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} run: | TUI_DIR="datafusion/ballista/tui/${{ steps.cargo_metadata.outputs.ballista_version }}" REMOTE_TARGET_DIR="${{ secrets.NIGHTLIES_RSYNC_PATH }}/${TUI_DIR}/" @@ -97,7 +97,7 @@ jobs: ssh -p ${{ secrets.NIGHTLIES_RSYNC_PORT }} -l ${{ secrets.NIGHTLIES_RSYNC_USER }} ${{ secrets.NIGHTLIES_RSYNC_HOST }} "mkdir -p ${REMOTE_TARGET_DIR}" - rsync --times --compress --delete --verbose -e "ssh -p ${{ secrets.NIGHTLIES_RSYNC_PORT }} -l ${{ secrets.NIGHTLIES_RSYNC_USER }}" ./target/web-tui/* ${{ secrets.NIGHTLIES_RSYNC_HOST }}:${REMOTE_TARGET_DIR} + rsync --times --compress --delete --recursive --verbose -e "ssh -p ${{ secrets.NIGHTLIES_RSYNC_PORT }} -l ${{ secrets.NIGHTLIES_RSYNC_USER }}" ./target/web-tui/* ${{ secrets.NIGHTLIES_RSYNC_HOST }}:${REMOTE_TARGET_DIR} - name: Upload WASM32 application uses: actions/upload-artifact@v7 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..4ee2be4ad3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +When making a code review never link to any issue or a pull request +in any GitHub repository! + +Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..4ee2be4ad3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +When making a code review never link to any issue or a pull request +in any GitHub repository! + +Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! + diff --git a/ballista/scheduler/src/api/handlers.rs b/ballista/scheduler/src/api/handlers.rs index 8da872253a..013c24136a 100644 --- a/ballista/scheduler/src/api/handlers.rs +++ b/ballista/scheduler/src/api/handlers.rs @@ -17,6 +17,7 @@ use crate::state::execution_graph_dot::ExecutionGraphDot; use crate::state::execution_stage::TaskInfo; use crate::{api::SchedulerErrorResponse, scheduler_server::SchedulerServer}; use axum::extract::Query; +use axum::response::Redirect; use axum::{ Json, extract::{Path, State}, @@ -46,7 +47,7 @@ use graphviz_rust::{ exec, printer::PrinterContext, }; -use http::{StatusCode, header::CONTENT_TYPE}; +use http::{HeaderMap, StatusCode, header::CONTENT_TYPE}; use serde::Serialize; use std::sync::Arc; use std::time::Duration; @@ -227,6 +228,36 @@ pub enum PlanFormat { Metrics, } +/// A handler for GET requests to the root (`/`). +/// It redirects to https://nightlies.apache.org/datafusion/ballista/tui// +/// forwarding any query parameters +pub async fn get_root( + header_map: HeaderMap, + Query(mut params): Query>, +) -> Result { + const NIGHTLIES_URL: &str = "https://nightlies.apache.org/datafusion/ballista/tui"; + + let ballista_scheduler_url = + params.remove("ballista_scheduler_url").unwrap_or_else(|| { + let default_scheduler_url = "localhost:50050"; + let scheduler_url = header_map + .get("host") + .map(|hv| hv.to_str().unwrap_or(default_scheduler_url)) + .unwrap_or(default_scheduler_url); + format!("http://{scheduler_url}") + }); + + let mut target = format!( + "{NIGHTLIES_URL}/{BALLISTA_VERSION}/?ballista_scheduler_url={ballista_scheduler_url}", + ); + + for (k, v) in params.iter() { + target.push_str(format!("&{}={}", k, v).as_str()); + } + + Ok(Redirect::temporary(&target)) +} + pub async fn get_scheduler_state< T: AsLogicalPlan + Clone + Send + Sync + 'static, U: AsExecutionPlan + Send + Sync + 'static, @@ -546,7 +577,7 @@ pub async fn get_query_stages< let metrics = running_stage.stage_metrics.as_deref().unwrap_or(&[]); summary.stage_plan = Some(match plan_format { PlanFormat::Default => displayable(running_stage.plan.as_ref()).indent(false).to_string(), - PlanFormat::Tree => displayable(running_stage.plan.as_ref()).tree_render().to_string(), + PlanFormat::Tree => displayable(running_stage.plan.as_ref()).tree_render().to_string(), PlanFormat::Metrics => format_stage_metrics(running_stage.plan.as_ref(), metrics), }); summary.input_rows = running_stage @@ -591,7 +622,7 @@ pub async fn get_query_stages< finish_time: info.finish_time as u64, input_rows, output_rows, - status: task_status + status: task_status, } }) }) @@ -600,7 +631,7 @@ pub async fn get_query_stages< ExecutionStage::Successful(completed_stage) => { summary.stage_plan = Some(match plan_format { PlanFormat::Default => displayable(completed_stage.plan.as_ref()).indent(false).to_string(), - PlanFormat::Tree => displayable(completed_stage.plan.as_ref()).tree_render().to_string(), + PlanFormat::Tree => displayable(completed_stage.plan.as_ref()).tree_render().to_string(), PlanFormat::Metrics => format_stage_metrics(completed_stage.plan.as_ref(), &completed_stage.stage_metrics), }); summary.input_rows = get_combined_count( @@ -638,7 +669,7 @@ pub async fn get_query_stages< finish_time: task_info.finish_time as u64, input_rows, output_rows, - status: task_status + status: task_status, }) }) .collect(); @@ -908,7 +939,7 @@ pub async fn get_job_dot_graph< })? { ExecutionGraphDot::generate(graph.as_ref()) - .map_err(|e| { + .map_err(|e| { tracing::error!("Error occurred while getting the dot graph for job '{job_id}' reason: {e:?}"); SchedulerErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR) }) @@ -953,10 +984,10 @@ pub async fn get_job_svg_graph< &mut PrinterContext::default(), vec![CommandArg::Format(Format::Svg)], ) - .map_err(|e| { - tracing::error!("Error occurred while getting job svg graph for job '{job_id}' reason: {e:?}"); - SchedulerErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR) - })?; + .map_err(|e| { + tracing::error!("Error occurred while getting job svg graph for job '{job_id}' reason: {e:?}"); + SchedulerErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + })?; let svg = String::from_utf8_lossy(&result).to_string(); Ok(Response::builder() diff --git a/ballista/scheduler/src/api/routes.rs b/ballista/scheduler/src/api/routes.rs index 76f2b78518..b3950a48b1 100644 --- a/ballista/scheduler/src/api/routes.rs +++ b/ballista/scheduler/src/api/routes.rs @@ -28,6 +28,7 @@ pub fn get_routes< scheduler_server: Arc>, ) -> Router { let router = Router::new() + .route("/", get(handlers::get_root)) .route("/api/state", get(handlers::get_scheduler_state::)) .route("/api/version", get(handlers::get_scheduler_version)) .route("/api/executors", get(handlers::get_executors::))