diff --git a/crates/turborepo-lib/src/task_graph/visitor.rs b/crates/turborepo-lib/src/task_graph/visitor.rs index 605a392ba254ca..105ca671f0ad3d 100644 --- a/crates/turborepo-lib/src/task_graph/visitor.rs +++ b/crates/turborepo-lib/src/task_graph/visitor.rs @@ -766,7 +766,8 @@ impl ExecContext { // If the task resulted in an error, do not group in order to better highlight // the error. let is_error = matches!(result, Ok(ExecOutcome::Task { .. })); - let logs = match output_client.finish(is_error) { + let is_cache_hit = matches!(result, Ok(ExecOutcome::Success(SuccessOutcome::CacheHit))); + let logs = match output_client.finish(is_error, is_cache_hit) { Ok(logs) => logs, Err(e) => { telemetry.track_error(TrackedErrors::DaemonFailedToMarkOutputsAsCached); @@ -1136,11 +1137,11 @@ impl CacheOutput for TaskCacheOutput { /// Struct for displaying information about task impl TaskOutput { - pub fn finish(self, use_error: bool) -> std::io::Result>> { + pub fn finish(self, use_error: bool, is_cache_hit: bool) -> std::io::Result>> { match self { TaskOutput::Direct(client) => client.finish(use_error), TaskOutput::UI(client) if use_error => Ok(Some(client.failed())), - TaskOutput::UI(client) => Ok(Some(client.succeeded())), + TaskOutput::UI(client) => Ok(Some(client.succeeded(is_cache_hit))), } } diff --git a/crates/turborepo-ui/src/lib.rs b/crates/turborepo-ui/src/lib.rs index 6b9b1e5242298d..8bd277b64b1a59 100644 --- a/crates/turborepo-ui/src/lib.rs +++ b/crates/turborepo-ui/src/lib.rs @@ -151,7 +151,7 @@ impl UI { } // Ported from Go code. Converts an index to a color along the rainbow - fn rainbow_rgb(i: usize) -> (u8, u8, u8) { + pub fn rainbow_rgb(i: usize) -> (u8, u8, u8) { let f = 0.275; let r = (f * i as f64 + 4.0 * PI / 3.0).sin() * 127.0 + 128.0; let g = 45.0; diff --git a/crates/turborepo-ui/src/tui/event.rs b/crates/turborepo-ui/src/tui/event.rs index cdcac1d8803c99..8c92b40ce8e67b 100644 --- a/crates/turborepo-ui/src/tui/event.rs +++ b/crates/turborepo-ui/src/tui/event.rs @@ -42,6 +42,7 @@ pub enum Event { pub enum TaskResult { Success, Failure, + CacheHit, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] diff --git a/crates/turborepo-ui/src/tui/handle.rs b/crates/turborepo-ui/src/tui/handle.rs index 5bc2c52b6491b0..fe5ac0aa3a0f7d 100644 --- a/crates/turborepo-ui/src/tui/handle.rs +++ b/crates/turborepo-ui/src/tui/handle.rs @@ -99,8 +99,12 @@ impl TuiTask { } /// Mark the task as finished - pub fn succeeded(&self) -> Vec { - self.finish(TaskResult::Success) + pub fn succeeded(&self, is_cache_hit: bool) -> Vec { + if is_cache_hit { + self.finish(TaskResult::CacheHit) + } else { + self.finish(TaskResult::Success) + } } /// Mark the task as finished diff --git a/crates/turborepo-ui/src/tui/table.rs b/crates/turborepo-ui/src/tui/table.rs index b800abdfc8463e..c6602a98659588 100644 --- a/crates/turborepo-ui/src/tui/table.rs +++ b/crates/turborepo-ui/src/tui/table.rs @@ -45,13 +45,26 @@ impl<'b> TaskTable<'b> { fn finished_rows(&self) -> impl Iterator + '_ { self.tasks_by_type.finished.iter().map(move |task| { + let name = if matches!(task.result(), TaskResult::CacheHit) { + Cell::new(Text::styled(task.name(), Style::default().italic())) + } else { + Cell::new(task.name()) + }; + Row::new(vec![ - Cell::new(task.name()), - Cell::new(match task.result() { + name, + match task.result() { // matches Next.js (and many other CLI tools) https://github.com/vercel/next.js/blob/1a04d94aaec943d3cce93487fea3b8c8f8898f31/packages/next/src/build/output/log.ts - TaskResult::Success => Text::styled("✓", Style::default().green().bold()), - TaskResult::Failure => Text::styled("⨯", Style::default().red().bold()), - }), + TaskResult::Success => { + Cell::new(Text::styled("✓", Style::default().green().bold())) + } + TaskResult::CacheHit => { + Cell::new(Text::styled("⊙", Style::default().magenta())) + } + TaskResult::Failure => { + Cell::new(Text::styled("⨯", Style::default().red().bold())) + } + }, ]) }) }