Skip to content

Commit

Permalink
Add explicit option to disable thresholds from CLI (#329)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cd-work authored May 3, 2022
1 parent 5ae4ae4 commit a319d09
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 32 deletions.
27 changes: 13 additions & 14 deletions cli/src/commands/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use anyhow::anyhow;
use chrono::Local;
use uuid::Uuid;

use phylum_types::types::user_settings::Threshold;

use super::{CommandResult, ExitCode};
use crate::api::PhylumApi;
use crate::config::{get_current_project, save_config, ProjectConfig, PROJ_CONF_FILE};
Expand Down Expand Up @@ -123,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 {
Expand All @@ -142,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 {
Expand All @@ -161,12 +166,6 @@ pub async fn handle_project(api: &mut PhylumApi, matches: &clap::ArgMatches) ->
x => x.to_string(),
};

let threshold = Threshold {
threshold: threshold as f32 / 100.,
active: threshold != 0,
action: action.into(),
};

user_settings.set_threshold(project_details.id.clone(), name, threshold);
}

Expand Down
58 changes: 40 additions & 18 deletions cli/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Threshold, std::io::Error> {
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::<i32>().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!(
Expand All @@ -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::<i32>().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::<i32>().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,
})
}

0 comments on commit a319d09

Please sign in to comment.