From da6d947a1da945591f4e07133e181b7aec3e11a2 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 29 Apr 2022 22:39:16 +0200 Subject: [PATCH] Add explicit option to disable thresholds from CLI Previously, when setting the project thresholds from the CLI, a value of `0` would automatically disable the specified threshold. This does not correctly represent the backend configuration, since some thresholds cannot be disabled and it is possible to have a threshold enabled with a value of `0`. To address both of these issues, an explicit `Disabled` option has been added to the interactive `set-thresholds` command, which allows controlling the `active` state of the threshold independently from its value. To ensure that the "Total Project" threshold is not disabled, a special case has been added which will inform the user that a value must be set for this threshold. Closes #288. --- Cargo.lock | 2 +- cli/src/commands/project.rs | 26 +++++++++-------- cli/src/prompt.rs | 58 +++++++++++++++++++++++++------------ 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 529464e7b..0def0c273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1266,7 +1266,7 @@ dependencies = [ [[package]] name = "phylum_types" version = "0.1.0" -source = "git+https://github.com/phylum-dev/phylum-types?branch=development#c9960ef94be75e5829d62eb3e2937e7e77dbc141" +source = "git+https://github.com/phylum-dev/phylum-types?branch=development#960c56abbced76fcffe5045af4b9b17df7bdc5dc" dependencies = [ "chrono", "log", diff --git a/cli/src/commands/project.rs b/cli/src/commands/project.rs index ddd05b56a..7d2b10a99 100644 --- a/cli/src/commands/project.rs +++ b/cli/src/commands/project.rs @@ -121,7 +121,10 @@ pub async fn handle_project(api: &mut PhylumApi, matches: &clap::ArgMatches) -> ); println!(); - println!("Specify the thresholds and actions for {}. A threshold of zero will disable the threshold.", format_args!("{}", White.paint(project_name))); + println!( + "Specify the thresholds and actions for {}. Accepted values are 0-100 or 'Disabled'.", + format_args!("{}", White.paint(project_name)) + ); println!(); let project_details = match api.get_project_details(project_name).await { @@ -140,17 +143,21 @@ pub async fn handle_project(api: &mut PhylumApi, matches: &clap::ArgMatches) -> } }; - for threshold_name in vec![ + for threshold_name in &[ "total project", "author", "engineering", "license", "malicious code", "vulnerability", - ] - .iter() - { - let (threshold, action) = prompt_threshold(threshold_name).unwrap_or((0, "none")); + ] { + let threshold = match prompt_threshold(threshold_name) { + Ok(threshold) => threshold, + Err(_) => { + print_user_failure!("Failed to read user input"); + continue; + } + }; // API expects slight key change for specific fields. let name = match *threshold_name { @@ -159,12 +166,7 @@ pub async fn handle_project(api: &mut PhylumApi, matches: &clap::ArgMatches) -> x => x.to_string(), }; - user_settings.set_threshold( - project_details.id.clone(), - name, - threshold, - action.to_string(), - ); + user_settings.set_threshold(project_details.id.clone(), name, threshold); } let resp = api.put_user_settings(&user_settings).await; diff --git a/cli/src/prompt.rs b/cli/src/prompt.rs index db8e0eb69..5bd2c019f 100644 --- a/cli/src/prompt.rs +++ b/cli/src/prompt.rs @@ -2,36 +2,52 @@ use ansi_term::Color::White; use dialoguer::theme::ColorfulTheme; use dialoguer::{Input, Select}; +use phylum_types::types::user_settings::Threshold; + +/// Project thresholds which cannot be disabled. +const ALWAYS_ENABLED_THRESHOLDS: [&str; 1] = ["total project"]; + /// Prompt the user for the threshold value and action associated with a given /// threshold. -pub fn prompt_threshold(name: &str) -> Result<(i32, &str), std::io::Error> { +pub fn prompt_threshold(name: &str) -> Result { let threshold = Input::with_theme(&ColorfulTheme::default()) .with_prompt(format!( "{} Threshold", format_args!("{}", White.paint(name.to_uppercase())) )) - .validate_with(|input: &String| -> Result<(), &str> { - if input.chars().all(char::is_numeric) { + .validate_with(|input: &String| -> Result<(), String> { + if input.eq_ignore_ascii_case("disabled") { + if ALWAYS_ENABLED_THRESHOLDS.contains(&name) { + Err(format!("Cannot disable {} threshold", name)) + } else { + Ok(()) + } + } else if input.chars().all(char::is_numeric) { let val = input.parse::().unwrap(); if (0..=100).contains(&val) { Ok(()) } else { - Err("Make sure to specify a number between 0-100") + Err("Make sure to specify a number between 0-100".into()) } } else { - Err("Threshold must be a number between 0-100") + Err("Threshold must be a number between 0-100 or 'Disabled'".into()) } }) .report(true) .interact_text()?; - if threshold == "0" { + if threshold.eq_ignore_ascii_case("disabled") { println!( "\nDisabling {} risk domain", format_args!("{}", White.paint(name)) ); println!("\n-----\n"); - return Ok((0, "none")); + + return Ok(Threshold { + action: "none".into(), + threshold: 0., + active: false, + }); } println!( @@ -55,15 +71,21 @@ pub fn prompt_threshold(name: &str) -> Result<(i32, &str), std::io::Error> { println!("✔ {} Action · {}", White.paint(name.to_uppercase()), action); println!("\n-----\n"); - Ok(( - threshold.parse::().unwrap(), - match selection { - // Convert the provided selection index into a string suitable for sending - // back to the API endpoint responsible for handling user settings. - 0 => "break", - 1 => "warn", - 2 => "none", - _ => "warn", // We shouldn't be able to make it here. - }, - )) + let threshold = threshold.parse::().unwrap() as f32 / 100.; + + let action = match selection { + // Convert the provided selection index into a string suitable for sending + // back to the API endpoint responsible for handling user settings. + 0 => "break", + 1 => "warn", + 2 => "none", + _ => "warn", // We shouldn't be able to make it here. + } + .to_owned(); + + Ok(Threshold { + active: true, + threshold, + action, + }) }