Skip to content

Commit 6d4e655

Browse files
refactor: Add query resolvers into web UI (#9182)
### Description Adds the query resolvers such as `packages` and `file` into the web UI GraphQL server. This will allow us to display repo general info in studio. You can review this commit by commit ### Testing Instructions <!-- Give a quick description of steps to test your changes. -->
1 parent e4f73a0 commit 6d4e655

File tree

14 files changed

+115
-97
lines changed

14 files changed

+115
-97
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turborepo-lib/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ tokio-stream = { version = "0.1.12", features = ["net"] }
109109
tokio-util = { version = "0.7.7", features = ["compat"] }
110110
tonic = { version = "0.11.0", features = ["transport"] }
111111
tower = "0.4.13"
112+
tower-http = { version = "0.5.2", features = ["cors"] }
112113
tracing-appender = "0.2.2"
113114
tracing-chrome = "0.7.1"
114115
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }

crates/turborepo-lib/src/commands/query.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fs;
1+
use std::{fs, sync::Arc};
22

33
use async_graphql::{EmptyMutation, EmptySubscription, Schema, ServerError};
44
use miette::{Diagnostic, Report, SourceSpan};
@@ -10,7 +10,7 @@ use crate::{
1010
cli::Command,
1111
commands::{run::get_signal, CommandBase},
1212
query,
13-
query::{Error, Query},
13+
query::{Error, RepositoryQuery},
1414
run::builder::RunBuilder,
1515
signal::SignalHandler,
1616
};
@@ -84,7 +84,11 @@ pub async fn run(
8484
fs::read_to_string(AbsoluteSystemPathBuf::from_unknown(run.repo_root(), query))?
8585
};
8686

87-
let schema = Schema::new(Query::new(run), EmptyMutation, EmptySubscription);
87+
let schema = Schema::new(
88+
RepositoryQuery::new(Arc::new(run)),
89+
EmptyMutation,
90+
EmptySubscription,
91+
);
8892

8993
let result = schema.execute(&query).await;
9094
if result.errors.is_empty() {

crates/turborepo-lib/src/commands/run.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::future::Future;
1+
use std::{future::Future, sync::Arc};
22

33
use tracing::error;
44
use turborepo_telemetry::events::command::CommandEventBuilder;
@@ -40,10 +40,12 @@ pub async fn run(base: CommandBase, telemetry: CommandEventBuilder) -> Result<i3
4040

4141
let run_fut = async {
4242
let (analytics_sender, analytics_handle) = run_builder.start_analytics();
43-
let mut run = run_builder
44-
.with_analytics_sender(analytics_sender)
45-
.build(&handler, telemetry)
46-
.await?;
43+
let run = Arc::new(
44+
run_builder
45+
.with_analytics_sender(analytics_sender)
46+
.build(&handler, telemetry)
47+
.await?,
48+
);
4749

4850
let (sender, handle) = run.start_ui()?.unzip();
4951

crates/turborepo-lib/src/query/mod.rs

+11-7
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ pub enum Error {
4141
Path(#[from] turbopath::PathError),
4242
}
4343

44-
pub struct Query {
44+
pub struct RepositoryQuery {
4545
run: Arc<Run>,
4646
}
4747

48-
impl Query {
49-
pub fn new(run: Run) -> Self {
50-
Self { run: Arc::new(run) }
48+
impl RepositoryQuery {
49+
pub fn new(run: Arc<Run>) -> Self {
50+
Self { run }
5151
}
5252
}
5353

@@ -267,7 +267,7 @@ impl PackagePredicate {
267267
}
268268

269269
#[Object]
270-
impl Query {
270+
impl RepositoryQuery {
271271
async fn affected_packages(
272272
&self,
273273
base: Option<String>,
@@ -339,12 +339,16 @@ impl Query {
339339
}
340340
}
341341

342-
async fn graphiql() -> impl IntoResponse {
342+
pub async fn graphiql() -> impl IntoResponse {
343343
response::Html(GraphiQLSource::build().endpoint("/").finish())
344344
}
345345

346346
pub async fn run_server(run: Run, signal: SignalHandler) -> Result<(), Error> {
347-
let schema = Schema::new(Query::new(run), EmptyMutation, EmptySubscription);
347+
let schema = Schema::new(
348+
RepositoryQuery::new(Arc::new(run)),
349+
EmptyMutation,
350+
EmptySubscription,
351+
);
348352
let app = Router::new().route("/", get(graphiql).post_service(GraphQL::new(schema)));
349353

350354
let subscriber = signal.subscribe().ok_or(Error::NoSignalHandler)?;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

crates/turborepo-lib/src/run/mod.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod scope;
1010
pub(crate) mod summary;
1111
pub mod task_access;
1212
pub mod task_id;
13+
mod ui;
1314
pub mod watch;
1415

1516
use std::{
@@ -211,7 +212,7 @@ impl Run {
211212
&& tui::terminal_big_enough()?)
212213
}
213214

214-
pub fn start_ui(&self) -> UIResult<UISender> {
215+
pub fn start_ui(self: &Arc<Self>) -> UIResult<UISender> {
215216
// Print prelude here as this needs to happen before the UI is started
216217
if self.should_print_prelude {
217218
self.print_run_prelude();
@@ -227,10 +228,10 @@ impl Run {
227228
.map(|res| res.map(|(sender, handle)| (UISender::Wui(sender), handle))),
228229
}
229230
}
230-
fn start_web_ui(&self) -> WuiResult {
231+
fn start_web_ui(self: &Arc<Self>) -> WuiResult {
231232
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
232233

233-
let handle = tokio::spawn(turborepo_ui::wui::server::start_server(rx));
234+
let handle = tokio::spawn(ui::start_web_ui_server(rx, self.clone()));
234235

235236
Ok(Some((WebUISender { tx }, handle)))
236237
}
@@ -260,7 +261,7 @@ impl Run {
260261
}
261262
}
262263

263-
pub async fn run(&mut self, ui_sender: Option<UISender>, is_watch: bool) -> Result<i32, Error> {
264+
pub async fn run(&self, ui_sender: Option<UISender>, is_watch: bool) -> Result<i32, Error> {
264265
let skip_cache_writes = self.opts.runcache_opts.skip_writes;
265266
if let Some(subscriber) = self.signal_handler.subscribe() {
266267
let run_cache = self.run_cache.clone();
@@ -356,7 +357,7 @@ impl Run {
356357
self.engine.task_definitions(),
357358
&self.repo_root,
358359
&self.run_telemetry,
359-
&mut self.daemon,
360+
&self.daemon,
360361
)?;
361362

362363
let root_workspace = self

crates/turborepo-lib/src/run/ui.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::sync::Arc;
2+
3+
use async_graphql::{EmptyMutation, EmptySubscription, MergedObject, Schema};
4+
use async_graphql_axum::GraphQL;
5+
use axum::{http::Method, routing::get, Router};
6+
use tokio::net::TcpListener;
7+
use tower_http::cors::{Any, CorsLayer};
8+
use turborepo_ui::wui::{event::WebUIEvent, server::SharedState};
9+
10+
use crate::{query, query::graphiql, run::Run};
11+
12+
pub async fn start_web_ui_server(
13+
rx: tokio::sync::mpsc::UnboundedReceiver<WebUIEvent>,
14+
run: Arc<Run>,
15+
) -> Result<(), turborepo_ui::Error> {
16+
let state = SharedState::default();
17+
let subscriber = turborepo_ui::wui::subscriber::Subscriber::new(rx);
18+
tokio::spawn(subscriber.watch(state.clone()));
19+
20+
run_server(state.clone(), run).await?;
21+
22+
Ok(())
23+
}
24+
25+
#[derive(MergedObject)]
26+
struct Query(turborepo_ui::wui::RunQuery, query::RepositoryQuery);
27+
28+
async fn run_server(state: SharedState, run: Arc<Run>) -> Result<(), turborepo_ui::Error> {
29+
let cors = CorsLayer::new()
30+
// allow `GET` and `POST` when accessing the resource
31+
.allow_methods([Method::GET, Method::POST])
32+
.allow_headers(Any)
33+
// allow requests from any origin
34+
.allow_origin(Any);
35+
36+
let web_ui_query = turborepo_ui::wui::RunQuery::new(state.clone());
37+
let turbo_query = query::RepositoryQuery::new(run);
38+
let combined_query = Query(web_ui_query, turbo_query);
39+
40+
let schema = Schema::new(combined_query, EmptyMutation, EmptySubscription);
41+
let app = Router::new()
42+
.route("/", get(graphiql).post_service(GraphQL::new(schema)))
43+
.layer(cors);
44+
45+
axum::serve(
46+
TcpListener::bind("127.0.0.1:8000")
47+
.await
48+
.map_err(turborepo_ui::wui::Error::Server)?,
49+
app,
50+
)
51+
.await
52+
.map_err(turborepo_ui::wui::Error::Server)?;
53+
54+
Ok(())
55+
}

crates/turborepo-lib/src/run/watch.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ impl ChangedPackages {
4646
}
4747

4848
pub struct WatchClient {
49-
run: Run,
49+
run: Arc<Run>,
5050
watched_packages: HashSet<PackageName>,
5151
persistent_tasks_handle: Option<PersistentRunHandle>,
5252
connector: DaemonConnector,
@@ -130,9 +130,11 @@ impl WatchClient {
130130
execution_args: execution_args.clone(),
131131
});
132132

133-
let run = RunBuilder::new(new_base)?
134-
.build(&handler, telemetry.clone())
135-
.await?;
133+
let run = Arc::new(
134+
RunBuilder::new(new_base)?
135+
.build(&handler, telemetry.clone())
136+
.await?,
137+
);
136138

137139
let watched_packages = run.get_relevant_packages();
138140

@@ -288,7 +290,7 @@ impl WatchClient {
288290
let signal_handler = self.handler.clone();
289291
let telemetry = self.telemetry.clone();
290292

291-
let mut run = RunBuilder::new(new_base)?
293+
let run = RunBuilder::new(new_base)?
292294
.with_entrypoint_packages(packages)
293295
.hide_prelude()
294296
.build(&signal_handler, telemetry)
@@ -331,7 +333,8 @@ impl WatchClient {
331333
self.run = RunBuilder::new(base.clone())?
332334
.hide_prelude()
333335
.build(&self.handler, self.telemetry.clone())
334-
.await?;
336+
.await?
337+
.into();
335338

336339
self.watched_packages = self.run.get_relevant_packages();
337340

@@ -357,7 +360,7 @@ impl WatchClient {
357360
self.persistent_tasks_handle.is_none(),
358361
"persistent handle should be empty before creating a new one"
359362
);
360-
let mut persistent_run = self.run.create_run_for_persistent_tasks();
363+
let persistent_run = self.run.create_run_for_persistent_tasks();
361364
let ui_sender = self.ui_sender.clone();
362365
// If we have persistent tasks, we run them on a separate thread
363366
// since persistent tasks don't finish
@@ -369,7 +372,7 @@ impl WatchClient {
369372
});
370373

371374
// But we still run the regular tasks blocking
372-
let mut non_persistent_run = self.run.create_run_without_persistent_tasks();
375+
let non_persistent_run = self.run.create_run_without_persistent_tasks();
373376
Ok(non_persistent_run.run(self.ui_sender.clone(), true).await?)
374377
} else {
375378
Ok(self.run.run(self.ui_sender.clone(), true).await?)

crates/turborepo-lib/src/task_hash.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl PackageInputsHashes {
7676
task_definitions: &HashMap<TaskId<'static>, TaskDefinition>,
7777
repo_root: &AbsoluteSystemPath,
7878
telemetry: &GenericEventBuilder,
79-
daemon: &mut Option<DaemonClient<DaemonConnector>>,
79+
daemon: &Option<DaemonClient<DaemonConnector>>,
8080
) -> Result<PackageInputsHashes, Error> {
8181
tracing::trace!(scm_manual=%scm.is_manual(), "scm running in {} mode", if scm.is_manual() { "manual" } else { "git" });
8282

crates/turborepo-ui/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ serde_json = { workspace = true }
3535
thiserror = { workspace = true }
3636
tokio = { workspace = true }
3737

38-
tower-http = { version = "0.5.2", features = ["cors"] }
3938
tracing = { workspace = true }
4039
tui-term = { workspace = true }
4140
turbopath = { workspace = true }

crates/turborepo-ui/src/wui/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
//! Web UI for Turborepo. Creates a WebSocket server that can be subscribed to
22
//! by a web client to display the status of tasks.
33
4-
mod event;
4+
pub mod event;
55
pub mod sender;
66
pub mod server;
7-
mod subscriber;
7+
pub mod subscriber;
88

99
use event::WebUIEvent;
10+
pub use server::RunQuery;
1011
use thiserror::Error;
1112

1213
#[derive(Debug, Error)]

0 commit comments

Comments
 (0)