From 3fe83e83f1832f1b22569b7ec1baa58c81e9e480 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Tue, 16 Apr 2024 15:15:40 -0500 Subject: [PATCH] Logs to file --- Cargo.lock | 319 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 + flake.nix | 1 + readme.md | 47 ++++++++ src/client.rs | 1 + src/gitlab.rs | 1 + src/jira.rs | 1 + src/main.rs | 54 ++++++++- src/task.rs | 32 +++-- 9 files changed, 449 insertions(+), 12 deletions(-) create mode 100644 readme.md diff --git a/Cargo.lock b/Cargo.lock index 9890c43..ff19097 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -26,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -115,6 +133,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.90" @@ -178,6 +211,19 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -203,6 +249,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-epoch" version = "0.9.18" @@ -218,6 +273,40 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot 0.12.1", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "either" version = "1.9.0" @@ -446,6 +535,16 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -573,6 +672,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "instant" version = "0.1.12" @@ -591,6 +696,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -640,6 +754,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown", +] + [[package]] name = "matchers" version = "0.1.0" @@ -693,6 +816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -725,6 +849,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.18" @@ -863,6 +993,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -887,6 +1023,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -941,6 +1083,32 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "itertools", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "redact" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7301863b5e5486c9f18320ccc1aedce9a5fdf3056ae89fa021933d6f054430f" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1138,6 +1306,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" + [[package]] name = "ryu" version = "1.0.17" @@ -1234,6 +1408,27 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1284,6 +1479,44 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stability" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.53" @@ -1339,6 +1572,9 @@ dependencies = [ "chrono", "chrono-humanize", "color-eyre", + "crossterm", + "ratatui", + "redact", "regex", "reqwest", "reqwest-middleware", @@ -1349,7 +1585,9 @@ dependencies = [ "sled", "tokio", "tracing", + "tracing-appender", "tracing-subscriber", + "xdg", ] [[package]] @@ -1394,6 +1632,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1492,6 +1761,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.27" @@ -1588,6 +1869,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "url" version = "2.5.0" @@ -1895,3 +2188,29 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 2b0d62e..de4ae85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ anyhow = "1.0.81" chrono = { version = "0.4.35", features = ["serde"] } chrono-humanize = "0.2.3" color-eyre = "0.6.3" +crossterm = "0.27.0" +ratatui = "0.26.2" +redact = "0.1.9" regex = "1.10.3" reqwest = { version = "0.11.26", features = ["json", "socks"] } reqwest-middleware = "0.2.5" @@ -20,7 +23,9 @@ serde_json = "1.0.114" sled = "0.34.7" tokio = { version = "1.36.0", features = ["full"] } tracing = "0.1.40" +tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +xdg = "2.5.2" [profile.dev] opt-level = 1 diff --git a/flake.nix b/flake.nix index 882c015..7f46cc4 100644 --- a/flake.nix +++ b/flake.nix @@ -49,6 +49,7 @@ lldb # libs + libopus openssl pkg-config ]; diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..bbdb330 --- /dev/null +++ b/readme.md @@ -0,0 +1,47 @@ +# Task Management + +Currently, at my job, I have a number of layers of red tape to work through just +to actually make commits. + +- I must have a Jira ticket to work against. This is almost never provided to + me and is something I make myself. +- I must have a GitLab branch name that matches the Jira ticket. +- I must progress the Jira ticket separately from the GitLab branch. + +My goal is to unify these without having to page through these applications +which often breaks flow. Here's how I'm hoping to structure this: + +- I run a command indicating a new task that I am working on. + - Something like `task new 'fix slack token being logged'` + - Perhaps also prompts me with a fuzzy finder for a project as well? +- This gives me a ticket ID + - If I provided a project as well, it should checkout a new branch with the + correct branch name for that project branching off the latest default branch + from the origin + - Perhaps it also moves my current working directory into the project + - Perhaps it even further opens my text editor +- I make commits and push them up + - Each project needs to have hooks setup in the git config with GitLab that + create a draft MR +- I need those commits to be reviewed, so I need a way to ask for reviews + - Something like `task request-review $ID`? + - Marks the review as "ready" (not a "draft") + - This then has different flows depending on the project, but primarily would + mean sending a message in Slack with the format `!review $LINK_TO_MR - + $SUMMARY_OF_CHANGE` +- Once the MR is ready to merge, I need to be notified as I want to merge and + deploy it + - The code review bot does notify in the review thread. Perhaps I can listen + for this? + - I need to merge the MR + - If the project requires it, I need to manually confirm that I want to deploy + to production + - Once my change has been deployed, I need to close the related Jira ticket + +My goal is to codify this process as generically as possible and make way for +project-specific details as well so that I can minimize my personal overhead as +I go through this process and possibly even across the organization. + +It would also be nice to manage tasks across the team, dumping the state of my +Jira board and diffing that over intervals of time would be useful to see how +tasks progress over time. diff --git a/src/client.rs b/src/client.rs index a763863..94c0002 100644 --- a/src/client.rs +++ b/src/client.rs @@ -16,6 +16,7 @@ pub trait ResourceRequest { impl ResourceRequest for RequestBuilder { async fn res(self) -> Result { + // TODO: make this a field on this struct or something? /* use tracing::debug; let body = self.send().await?.text().await?; diff --git a/src/gitlab.rs b/src/gitlab.rs index 39002b4..b86a441 100644 --- a/src/gitlab.rs +++ b/src/gitlab.rs @@ -25,6 +25,7 @@ pub struct User { impl GitLab { pub fn try_new(url: &str, token: &str) -> Result { let mut headers = HeaderMap::new(); + // TODO: ensure this token cannot be leaked to logs? headers.insert("PRIVATE-TOKEN", HeaderValue::from_str(token)?); headers.insert("content-type", HeaderValue::from_str("application/json")?); headers.insert("accepts", HeaderValue::from_str("application/json")?); diff --git a/src/jira.rs b/src/jira.rs index bf55933..3b9f8e2 100644 --- a/src/jira.rs +++ b/src/jira.rs @@ -86,6 +86,7 @@ pub struct IssueSearch { impl Jira { pub fn try_new(url: &str, token: &str) -> Result { let mut headers = HeaderMap::new(); + // TODO: ensure this token cannot be leaked to logs? headers.insert( "Authorization", HeaderValue::from_str(&format!("Basic {}", token.trim()))?, diff --git a/src/main.rs b/src/main.rs index b06d01f..5b37c2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![warn(clippy::all)] + mod client; mod config; mod gitlab; @@ -11,17 +13,35 @@ use tasks::Tasks; use tracing::{error, info}; use tracing_subscriber::{filter::LevelFilter, EnvFilter}; +use crossterm::{ + event::{self, KeyCode, KeyEventKind}, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + ExecutableCommand, +}; +use ratatui::{ + prelude::{CrosstermBackend, Stylize, Terminal}, + widgets::Paragraph, +}; +use std::io::stdout; + #[allow(dead_code)] const ANSI_CLEAR: &'static str = "\x1b[2J\x1b[1;1H"; #[tokio::main] async fn main() -> Result<()> { + let logs_dir = xdg::BaseDirectories::new()?.create_cache_directory("taskr/logs")?; + let file_appender = tracing_appender::rolling::hourly(logs_dir, "log"); + let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); + color_eyre::install().expect("Failed to install color_eyre"); let filter = EnvFilter::builder() .with_default_directive(LevelFilter::TRACE.into()) .parse_lossy("info,tasks=trace"); - tracing_subscriber::fmt().with_env_filter(filter).init(); + tracing_subscriber::fmt() + .with_writer(non_blocking) + .with_env_filter(filter) + .init(); match run().await { Ok(()) => Ok(()), @@ -47,8 +67,38 @@ async fn run() -> Result<()> { } let mut vtasks: Vec<&task::Task> = tasks.values().collect(); vtasks.sort_unstable(); - for t in vtasks { + for t in &vtasks { info!("{}", t); } + info!("Number of tasks: {}", vtasks.len()); + tui().await +} + +async fn tui() -> Result<()> { + stdout().execute(EnterAlternateScreen)?; + enable_raw_mode()?; + let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; + terminal.clear()?; + + loop { + terminal.draw(|frame| { + let area = frame.size(); + frame.render_widget( + Paragraph::new("Hello Ratatui! (press 'q' to quit)").white(), + area, + ); + })?; + if event::poll(std::time::Duration::from_millis(10))? { + if let event::Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { + break; + } + } + } + } + // TODO main loop + + stdout().execute(LeaveAlternateScreen)?; + disable_raw_mode()?; Ok(()) } diff --git a/src/task.rs b/src/task.rs index 308d604..e6df9f2 100644 --- a/src/task.rs +++ b/src/task.rs @@ -25,26 +25,38 @@ pub struct Task { const STATUS_PRIORITY: [&str; 4] = ["Blocked", "InProgress", "DevReady", "Backlog"]; impl PartialOrd for Task { + /// We sort tasks first by their status. If those are equal, we sort by their priority, favoring the `local_priority`. fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Task { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { if self.eq(other) { - return Some(Ordering::Equal); + return Ordering::Equal; } - let a = STATUS_PRIORITY.iter().position(self.status); - let b = STATUS_PRIORITY.iter().position(other.status); - if Some(o) = a.partial_cmp(b) { + + { + let a = STATUS_PRIORITY.iter().position(|s| **s == self.status); + let b = STATUS_PRIORITY.iter().position(|s| **s == other.status); + let o = a.cmp(&b); if o != Ordering::Equal { return o; } } + + { + let o = self.local_priority.cmp(&other.local_priority); + if o != Ordering::Equal { + return o; + } + } + + self.jira_priority.cmp(&other.jira_priority) } } -// impl Ord for Task { -// fn cmp(&self, other: &Self) -> std::cmp::Ordering { -// todo!() -// } -// } - impl Display for Task { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self {