diff --git a/Cargo.lock b/Cargo.lock index b885346..907f413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,12 +32,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -47,6 +41,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -140,6 +143,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -227,6 +241,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -245,6 +265,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "basic-toml" version = "0.1.10" @@ -266,6 +292,15 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.18.1" @@ -281,6 +316,39 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "case_insensitive_string" version = "0.2.10" @@ -323,17 +391,16 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -357,6 +424,21 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -381,6 +463,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const_format" version = "0.2.35" @@ -438,6 +526,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -462,6 +559,55 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.102", +] + [[package]] name = "darling" version = "0.21.3" @@ -482,7 +628,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.102", ] @@ -511,6 +657,17 @@ dependencies = [ "parking_lot_core 0.9.11", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.5" @@ -521,6 +678,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + [[package]] name = "dirs" version = "6.0.0" @@ -565,12 +734,71 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "email_address" version = "0.2.9" @@ -634,6 +862,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fnv" version = "1.0.7" @@ -764,6 +1008,17 @@ dependencies = [ "windows", ] +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -898,6 +1153,17 @@ dependencies = [ "web-time", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "gsuite-api" version = "0.7.0" @@ -1000,12 +1266,39 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.12" @@ -1090,6 +1383,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "hyper" version = "0.14.32" @@ -1464,16 +1763,24 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.1" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ "base64 0.22.1", + "ed25519-dalek", + "getrandom 0.2.16", + "hmac", "js-sys", + "p256", + "p384", "pem 3.0.5", - "ring 0.17.14", + "rand 0.8.5", + "rsa", "serde", "serde_json", + "sha2", + "signature", "simple_asn1", ] @@ -1482,6 +1789,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "libc" @@ -1489,6 +1799,12 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.4" @@ -1667,6 +1983,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1682,6 +2014,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1689,6 +2032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1711,19 +2055,20 @@ dependencies = [ [[package]] name = "octocrab" -version = "0.44.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86996964f8b721067b6ed238aa0ccee56ecad6ee5e714468aa567992d05d2b91" +version = "0.49.5" +source = "git+https://github.com/XAMPPRocky/octocrab.git?rev=460733dd3f49863b159ffb48050ab9523300ff84#460733dd3f49863b159ffb48050ab9523300ff84" dependencies = [ "arc-swap", "async-trait", "base64 0.22.1", "bytes", + "cargo_metadata", "cfg-if", "chrono", "either", "futures", "futures-util", + "getrandom 0.2.16", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -1731,7 +2076,7 @@ dependencies = [ "hyper-rustls 0.27.7", "hyper-timeout", "hyper-util", - "jsonwebtoken 9.3.1", + "jsonwebtoken 10.3.0", "once_cell", "percent-encoding", "pin-project", @@ -1751,13 +2096,12 @@ dependencies = [ [[package]] name = "octocrab-rate-limiter" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eac43250538e6091f1104f527619b0a48e58bf67c4a956fd1e9d36ca469819" +checksum = "38e4cd44f47461ef425c19c6e985fb58dd5ae4d1c22dd2e3ccb463fc6bdf6546" dependencies = [ "http 1.3.1", "moka", - "octocrab", "tokio", "tower", ] @@ -1786,6 +2130,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -1879,6 +2247,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1955,6 +2332,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -1985,6 +2383,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2421,6 +2828,16 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -2430,7 +2847,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted 0.7.1", "web-sys", "winapi", @@ -2450,6 +2867,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.25" @@ -2697,6 +3134,20 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secrecy" version = "0.10.3" @@ -2747,6 +3198,9 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -2826,14 +3280,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -2890,6 +3345,17 @@ dependencies = [ "syn 2.0.102", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2949,6 +3415,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simple_asn1" version = "0.6.3" @@ -3039,6 +3515,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spinning_top" version = "0.3.0" @@ -3048,6 +3530,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3060,6 +3552,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stats-cli" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8786c4fc8a91bc4fcd90aed33413f79e4dc9811f24ba14d1d59adf57cf1c871" +dependencies = [ + "clap", + "num-traits", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strsim" version = "0.11.1" @@ -3167,6 +3675,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3541,6 +4058,7 @@ dependencies = [ "gsuite-api", "http 1.3.1", "http-serde", + "humantime", "hyper-rustls 0.27.7", "hyper-util", "indexmap 2.9.0", @@ -3559,6 +4077,7 @@ dependencies = [ "serde_urlencoded", "sheets", "slack-with-types", + "stats-cli", "strum_macros", "time", "tokio", @@ -3583,6 +4102,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicase" version = "2.8.1" @@ -3595,6 +4120,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -3649,6 +4180,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" @@ -3832,7 +4369,7 @@ dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.2", "windows-numerics", ] @@ -3853,7 +4390,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.2", "windows-result", "windows-strings", ] @@ -3865,7 +4402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.2", "windows-threading", ] @@ -3897,6 +4434,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -3904,7 +4447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.2", ] [[package]] @@ -3913,7 +4456,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.2", ] [[package]] @@ -3922,7 +4465,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.2", ] [[package]] @@ -3989,7 +4532,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.2", ] [[package]] @@ -4273,3 +4816,9 @@ dependencies = [ "quote", "syn 2.0.102", ] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/Cargo.toml b/Cargo.toml index 6b099bc..b0d9283 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1" askama = "0.14.0" axum = { version = "0.8.4", features = ["macros", "original-uri"] } case_insensitive_string = { version = "0.2.10", features = ["serde"] } -chrono = "0.4.41" +chrono = "0.4.43" chrono-tz = "0.10.3" const_format = "0.2.35" dotenv = "0.15.0" @@ -21,6 +21,7 @@ google-sheets4 = "6.0.0" gsuite-api = "0.7.0" http = "1.3.1" http-serde = "2.1.1" +humantime = "2.3.0" hyper-rustls = { version = "0.27.7", default-features = false, features = [ "http1", "http2", @@ -35,10 +36,14 @@ itertools = "0.14.0" maplit = "1.0.2" md5 = "0.8.0" moka = { version = "0.12.10", features = ["future"] } -octocrab = "0.44.1" -octocrab-rate-limiter = "0.1.0" +# Until https://github.com/XAMPPRocky/octocrab/pull/854 is released. +octocrab = { git = "https://github.com/XAMPPRocky/octocrab.git", rev = "460733dd3f49863b159ffb48050ab9523300ff84" } +octocrab-rate-limiter = "0.1.1" regex = "1.11.1" -reqwest = { version = "0.12.20", default-features = false, features = ["json", "rustls-tls"] } +reqwest = { version = "0.12.20", default-features = false, features = [ + "json", + "rustls-tls", +] } secrecy = "0.10" serde = { version = "1", features = ["derive"] } serde-env-field = "0.4.0" @@ -46,6 +51,7 @@ serde_json = "1" serde_urlencoded = "0.7.1" sheets = "0.7.0" slack-with-types = "0.1.1" +stats-cli = "3.0.1" strum_macros = "0.27.2" time = "0.3.44" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/src/bin/trainee-tracker.rs b/src/bin/trainee-tracker.rs index 8eea6db..e6e51da 100644 --- a/src/bin/trainee-tracker.rs +++ b/src/bin/trainee-tracker.rs @@ -95,6 +95,10 @@ async fn main() { "/courses/{course}/reviewers", get(trainee_tracker::frontend::get_reviewers), ) + .route( + "/courses/{course}/review-metrics", + get(trainee_tracker::frontend::get_review_metrics), + ) .route( "/groups/google", get(trainee_tracker::frontend::list_google_groups), diff --git a/src/frontend.rs b/src/frontend.rs index fef7bea..bc47228 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -6,6 +6,7 @@ use axum::{ extract::{OriginalUri, Path, Query, State}, response::{Html, IntoResponse, Response}, }; +use chrono::TimeDelta; use futures::future::join_all; use http::{HeaderMap, StatusCode, Uri, header::CONTENT_TYPE}; use serde::Deserialize; @@ -20,7 +21,10 @@ use crate::{ }, google_groups::{GoogleGroup, get_groups, groups_client}, octocrab::octocrab, - prs::{MaybeReviewerStaffOnlyDetails, PrState, ReviewerInfo}, + prs::{ + AggregatePrMetrics, MaybeReviewerStaffOnlyDetails, PrMetrics, PrState, ReviewerInfo, + get_prs, + }, reviewer_staff_info::get_reviewer_staff_info, sheets::sheets_client, slack::list_groups_with_members, @@ -258,6 +262,93 @@ struct ReviewersTemplate { pub now: chrono::DateTime, } +pub async fn get_review_metrics( + session: Session, + State(server_state): State, + OriginalUri(original_uri): OriginalUri, + Path(course_name): Path, +) -> Result, Error> { + let module_names = server_state + .config + .get_course_module_names(&course_name) + .ok_or(Error::UserFacing("Unknown course".to_owned()))?; + + let octocrab = octocrab(&session, &server_state, original_uri).await?; + + let module_futures = module_names + .into_iter() + .map(async |module_name| { + let prs = get_prs( + &octocrab, + &server_state.config.github_org, + &module_name, + false, + ) + .await?; + let metrics_futures: Vec<_> = prs + .into_iter() + .map(async |pr| { + crate::prs::get_review_metrics(&octocrab, &server_state.config.github_org, pr) + .await + }) + .collect(); + let metrics = join_all(metrics_futures).await; + let metrics = metrics.into_iter().collect::, _>>()?; + let aggregate_metrics = AggregatePrMetrics::new(&metrics); + + Ok::<_, Error>(ModuleReviewMetrics { + name: module_name, + metrics, + aggregate_metrics, + }) + }) + .collect::>(); + let modules: Vec<_> = join_all(module_futures).await; + let modules = modules.into_iter().collect::, _>>()?; + let aggregate_metrics = AggregatePrMetrics::new( + &modules + .iter() + .flat_map(|m| m.metrics.iter().cloned()) + .collect::>(), + ); + Ok(Html( + ReviewMetricsTemplate { + course_name, + modules, + aggregate_metrics, + } + .render() + .unwrap(), + )) +} + +#[derive(Template)] +#[template(path = "review-metrics.html")] +struct ReviewMetricsTemplate { + pub course_name: String, + pub modules: Vec, + pub aggregate_metrics: AggregatePrMetrics, +} + +pub struct ModuleReviewMetrics { + pub name: String, + pub metrics: Vec, + pub aggregate_metrics: AggregatePrMetrics, +} + +impl ReviewMetricsTemplate { + pub fn format_duration(&self, duration: &Option) -> String { + if let Some(duration) = duration { + let secs = duration.to_std().unwrap().as_secs(); + let secs_without_hours = secs - (secs % (60 * 60)); + humantime::format_duration(std::time::Duration::from_secs(secs_without_hours)) + .to_string() + } else { + "Not yet".to_owned() + } + } +} + pub async fn index() -> Html { Html(Index {}.render().unwrap()) } diff --git a/src/prs.rs b/src/prs.rs index f726925..e9dca79 100644 --- a/src/prs.rs +++ b/src/prs.rs @@ -5,12 +5,14 @@ use chrono::{DateTime, TimeDelta}; use futures::future::join_all; use octocrab::Octocrab; use octocrab::models::pulls::{Comment, PullRequest, Review as OctoReview}; -use octocrab::models::{Author, IssueState}; +use octocrab::models::timelines::TimelineEvent; +use octocrab::models::{Author, Event, IssueState}; use octocrab::params::State; use serde::Serialize; use crate::Error; use crate::newtypes::GithubLogin; +use crate::octocrab::all_pages; #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct Pr { @@ -21,6 +23,7 @@ pub struct Pr { pub author: GithubLogin, pub body: String, pub state: PrState, + pub created_at: DateTime, pub updated_at: DateTime, pub is_closed: bool, pub labels: BTreeSet, @@ -89,6 +92,7 @@ pub async fn get_prs( number, user, labels, + created_at, updated_at, title, state, @@ -116,6 +120,7 @@ pub async fn get_prs( // Unclear when they API would return None for these, ignore them. let updated_at = updated_at?; + let created_at = created_at?; let url = html_url?.to_string(); let title = title?; let body = body.unwrap_or_default(); @@ -125,6 +130,7 @@ pub async fn get_prs( url, author, state: pr_state, + created_at, updated_at, repo_name, title, @@ -232,6 +238,154 @@ pub(crate) struct ReviewerInfo { pub staff_only_details: MaybeReviewerStaffOnlyDetails, } +#[derive(Clone, PartialEq, Eq, Serialize)] +pub struct AggregatePrMetrics { + pub p50_needs_review_to_first_review: Option, + pub p90_needs_review_to_first_review: Option, + pub p100_needs_review_to_first_review: Option, + + pub p50_created_to_complete: Option, + pub p90_created_to_complete: Option, + pub p100_created_to_complete: Option, + + pub p50_needs_review_to_complete: Option, + pub p90_needs_review_to_complete: Option, + pub p100_needs_review_to_complete: Option, +} + +impl AggregatePrMetrics { + pub(crate) fn new(metrics: &[PrMetrics]) -> AggregatePrMetrics { + let ( + p50_needs_review_to_first_review, + p90_needs_review_to_first_review, + p100_needs_review_to_first_review, + ) = AggregatePrMetrics::calculate_precentiles(&metrics, |m: &PrMetrics| { + m.needs_review_to_first_review() + }); + + let (p50_created_to_complete, p90_created_to_complete, p100_created_to_complete) = + AggregatePrMetrics::calculate_precentiles(&metrics, |m: &PrMetrics| { + m.created_to_complete() + }); + + let ( + p50_needs_review_to_complete, + p90_needs_review_to_complete, + p100_needs_review_to_complete, + ) = AggregatePrMetrics::calculate_precentiles(&metrics, |m: &PrMetrics| { + m.needs_review_to_complete() + }); + + AggregatePrMetrics { + p50_needs_review_to_first_review, + p90_needs_review_to_first_review, + p100_needs_review_to_first_review, + p50_created_to_complete, + p90_created_to_complete, + p100_created_to_complete, + p50_needs_review_to_complete, + p90_needs_review_to_complete, + p100_needs_review_to_complete, + } + } + + fn calculate_precentiles Option>( + metrics: &[PrMetrics], + f: F, + ) -> (Option, Option, Option) { + let p: inc_stats::Percentiles = metrics + .iter() + .filter_map(f) + .map(|m| m.as_seconds_f64()) + .collect(); + if let Some(v) = p.percentiles([0.5, 0.9, 1.0]).unwrap() { + ( + Some(TimeDelta::seconds(v[0] as i64)), + Some(TimeDelta::seconds(v[1] as i64)), + Some(TimeDelta::seconds(v[2] as i64)), + ) + } else { + (None, None, None) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct PrMetrics { + pub pr: Pr, + pub created_at: chrono::DateTime, + pub label_add_events: Vec, + + pub first_needs_review: Option>, + pub first_reviewed: Option>, + pub first_complete: Option>, + pub iterations: usize, +} + +impl PrMetrics { + fn new( + pr: Pr, + created_at: chrono::DateTime, + label_add_events: Vec, + ) -> PrMetrics { + let mut first_needs_review = None; + let mut first_reviewed = None; + let mut first_complete = None; + let mut iterations = 0; + + for event in &label_add_events { + if event.label == "Needs Review" { + if first_needs_review.is_none() { + first_needs_review = Some(event.time); + } + } else if event.label == "Reviewed" { + iterations += 1; + if first_reviewed.is_none() { + first_reviewed = Some(event.time); + } + } else if event.label == "Complete" { + iterations += 1; + if first_complete.is_none() { + first_complete = Some(event.time); + } + } + } + + PrMetrics { + pr, + created_at, + label_add_events, + first_needs_review, + first_reviewed, + first_complete, + iterations, + } + } + + pub(crate) fn needs_review_to_first_review(&self) -> Option { + Some(self.first_complete? - self.created_at) + } + + pub(crate) fn created_to_complete(&self) -> Option { + Some(self.first_complete? - self.created_at) + } + + pub(crate) fn needs_review_to_complete(&self) -> Option { + Some(self.first_complete? - self.first_needs_review?) + } + + pub(crate) fn time_since_created(&self) -> TimeDelta { + chrono::Utc::now() - self.created_at + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct LabelAddEvent { + pub actor: GithubLogin, + pub label: String, + pub time: chrono::DateTime, +} + #[derive(PartialEq, Eq, Serialize)] pub(crate) enum MaybeReviewerStaffOnlyDetails { Some(ReviewerStaffOnlyDetails), @@ -357,6 +511,53 @@ enum CommentsOrReviews { Reviews, } +pub(crate) async fn get_review_metrics( + octocrab: &Octocrab, + github_org: &str, + pr: Pr, +) -> Result { + let events = all_pages("timeline events", octocrab, async || { + octocrab + .issues(github_org, &pr.repo_name) + .list_timeline_events(pr.number) + .send() + .await + }) + .await?; + let label_add_events = events + .into_iter() + .filter_map( + |TimelineEvent { + event, + actor, + label, + created_at, + .. + }| { + if event != Event::Labeled { + return None; + } + let Some(label) = label else { + return None; + }; + let Some(created_at) = created_at else { + return None; + }; + let Some(actor) = actor else { + return None; + }; + Some(LabelAddEvent { + actor: GithubLogin::from(actor.login), + label: label.name, + time: created_at, + }) + }, + ) + .collect(); + let created_at = pr.created_at; + Ok(PrMetrics::new(pr, created_at, label_add_events)) +} + // Ideally this would be a more general shared function, but async closures aren't super stable yet. async fn get_full_page, S2: AsRef>( octocrab: Octocrab, diff --git a/templates/list-courses.html b/templates/list-courses.html index e2eebd3..295f5ca 100644 --- a/templates/list-courses.html +++ b/templates/list-courses.html @@ -11,6 +11,9 @@

{{ cwbm.course.name }}

  • Reviewers
  • +
  • + Review metrics +
  • {% endfor %} diff --git a/templates/review-metrics.html b/templates/review-metrics.html new file mode 100644 index 0000000..9e86729 --- /dev/null +++ b/templates/review-metrics.html @@ -0,0 +1,97 @@ + + + + Review Metrics + + + + + +

    {{ course_name }}

    +
    +
    +

    Needs review to first review

    +
      +
    • p50: {{format_duration(aggregate_metrics.p50_needs_review_to_first_review)}}
    • +
    • p90: {{format_duration(aggregate_metrics.p90_needs_review_to_first_review)}}
    • +
    • p100: {{format_duration(aggregate_metrics.p100_needs_review_to_first_review)}}
    • +
    +
    +
    +

    Created to complete

    +
      +
    • p50: {{format_duration(aggregate_metrics.p50_created_to_complete)}}
    • +
    • p90: {{format_duration(aggregate_metrics.p90_created_to_complete)}}
    • +
    • p100: {{format_duration(aggregate_metrics.p100_created_to_complete)}}
    • +
    +
    +
    +

    Needs review to complete

    +
      +
    • p50: {{format_duration(aggregate_metrics.p50_needs_review_to_complete)}}
    • +
    • p90: {{format_duration(aggregate_metrics.p90_needs_review_to_complete)}}
    • +
    • p100: {{format_duration(aggregate_metrics.p100_needs_review_to_complete)}}
    • +
    +
    +
    + + {% for module in modules %} +

    {{module.name}}

    +
    +
    +

    Needs review to first review

    +
      +
    • p50: {{format_duration(module.aggregate_metrics.p50_needs_review_to_first_review)}}
    • +
    • p90: {{format_duration(module.aggregate_metrics.p90_needs_review_to_first_review)}}
    • +
    • p100: {{format_duration(module.aggregate_metrics.p100_needs_review_to_first_review)}}
    • +
    +
    +
    +

    Created to complete

    +
      +
    • p50: {{format_duration(module.aggregate_metrics.p50_created_to_complete)}}
    • +
    • p90: {{format_duration(module.aggregate_metrics.p90_created_to_complete)}}
    • +
    • p100: {{format_duration(module.aggregate_metrics.p100_created_to_complete)}}
    • +
    +
    +
    +

    Needs review to complete

    +
      +
    • p50: {{format_duration(module.aggregate_metrics.p50_needs_review_to_complete)}}
    • +
    • p90: {{format_duration(module.aggregate_metrics.p90_needs_review_to_complete)}}
    • +
    • p100: {{format_duration(module.aggregate_metrics.p100_needs_review_to_complete)}}
    • +
    +
    +
    +
    + {% for pr in module.metrics %} +
    + +
    Created to Complete: {{format_duration(&pr.created_to_complete())}}
    +
    Needs Review to Complete: {{format_duration(&pr.needs_review_to_complete())}}
    +
    {{pr.iterations}} iterations
    +
    Time since created: {{format_duration(&Some(pr.time_since_created()))}}
    +
    + {% endfor %} +
    + {% endfor %} + +