Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retrieve the events list for a project for v2 #1307

Merged
merged 5 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
119 changes: 119 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,50 @@ impl Api {
Ok(rv)
}

/// List all events associated with an organization and a project
pub fn list_organization_project_events(
&self,
org: &str,
project: &str,
limit: u16,
) -> ApiResult<Vec<Event>> {
let mut rv = vec![];
let mut cursor = "".to_string();
let mut _limit = limit;

loop {
let resp = self.get(&format!(
"/projects/{}/{}/events/?cursor={}",
PathArg(org),
PathArg(project),
QueryArg(&cursor)
))?;

if resp.status() == 404 || (resp.status() == 400 && !cursor.is_empty()) {
if rv.is_empty() {
return Err(ApiErrorKind::OrganizationNotFound.into());
} else {
break;
}
}
let pagination = resp.pagination();
rv.extend(resp.convert::<Vec<Event>>()?.into_iter());

_limit -= 1;
if _limit == 0 {
break;
}

if let Some(next) = pagination.into_next_cursor() {
cursor = next;
} else {
break;
}
}

Ok(rv)
}

/// List all repos associated with an organization
pub fn list_organization_repos(&self, org: &str) -> ApiResult<Vec<Repo>> {
let mut rv = vec![];
Expand Down Expand Up @@ -2307,6 +2351,81 @@ pub struct Project {
pub team: Option<Team>,
}

#[derive(Debug, Deserialize)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have all those types in sentry-sdk crate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kamilogorek I found Event but I don't think you're talking about this one because it is not good for this JSON response.
For EventUser you were right because we can use sentry::protocol::User instead of EventUserOption.
For EventTags I suggest to keep the Vec<EventTag> due the JSON serialization. If we use the same way used for sentry-sdk we have to refactor tags in Vec<BTreeMap<String, String>> (or HashMap) but we always have a BTreeMap (or HashMap) with a tuple ("key", "...") and a ("value", "...") cause the nature of the response.

pub struct EventTag {
pub key: String,
pub value: String,
}

#[derive(Debug, Deserialize)]
pub struct EventTags(pub Vec<EventTag>);

impl fmt::Display for EventTags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for event in &self.0 {
write!(f, "{} = {}, ", event.key, event.value)?;
}

Ok(())
}
}

#[derive(Debug, Deserialize)]
pub struct EventUserOption {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ip_address: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct EventUser(pub Option<EventUserOption>);

impl fmt::Display for EventUser {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(event) = &self.0 {
if let Some(ref id) = event.id {
write!(f, "ID: {}, ", id)?;
}

if let Some(ref username) = event.username {
write!(f, "Username: {}, ", username)?;
}

if let Some(ref user) = event.user {
write!(f, "User: {}, ", user)?;
}

if let Some(ref email) = event.email {
write!(f, "Email: {}, ", email)?;
}

if let Some(ref ip_address) = event.ip_address {
write!(f, "IP: {}, ", ip_address)?;
}
}

Ok(())
}
}

#[derive(Debug, Deserialize)]
pub struct Event {
#[serde(rename = "eventID")]
pub event_id: String,
pub title: String,
#[serde(rename = "dateCreated")]
pub date_created: String,
pub tags: EventTags,
pub user: EventUser,
}

#[derive(Debug, Deserialize)]
pub struct Monitor {
pub id: String,
Expand Down
65 changes: 65 additions & 0 deletions src/commands/events/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use anyhow::Result;
use clap::{ArgMatches, Command};

use crate::api::Api;
use crate::config::Config;
use crate::utils::formatting::Table;

pub fn make_command(command: Command) -> Command {
command.about("List all events in your organization.")
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
let api = Api::current();

let config = Config::current();
let org = config.get_org(matches)?;
let project = config.get_project(matches)?;
let limit = matches.value_of("limit").unwrap();

let events =
api.list_organization_project_events(&org, &project, limit.parse::<u16>().unwrap())?;

let mut table = Table::new();
let row = table.title_row().add("Event ID").add("Date").add("Title");
if matches.is_present("user") {
row.add("User");
}

if matches.is_present("tags") {
row.add("Tags");
}

let max_rows = if matches.is_present("max-rows") {
matches
.value_of("max-rows")
.unwrap()
.parse::<usize>()
.unwrap()
} else {
events.len()
};

for event in &events[..max_rows] {
boozec marked this conversation as resolved.
Show resolved Hide resolved
let row = table.add_row();
row.add(&event.event_id)
.add(&event.date_created)
.add(&event.title);

if matches.is_present("user") {
row.add(&event.user);
}

if matches.is_present("tags") {
row.add(&event.tags);
}
}

if table.is_empty() {
println!("No events found");
} else {
table.print();
}

Ok(())
}
74 changes: 74 additions & 0 deletions src/commands/events/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use anyhow::Result;
use clap::{Arg, ArgMatches, Command};

use crate::utils::args::ArgExt;

pub mod list;

macro_rules! each_subcommand {
($mac:ident) => {
$mac!(list);
};
}

pub fn make_command(mut command: Command) -> Command {
macro_rules! add_subcommand {
($name:ident) => {{
command = command.subcommand(crate::commands::events::$name::make_command(
Command::new(stringify!($name).replace('_', "-")),
));
}};
}

command = command
boozec marked this conversation as resolved.
Show resolved Hide resolved
.about("Manage events on Sentry.")
.subcommand_required(true)
.arg_required_else_help(true)
.org_arg()
.project_arg(true)
.arg(
Arg::with_name("user")
.long("user")
.short('u')
.global(true)
.help("Include user's info into the list."),
)
.arg(
Arg::with_name("tags")
.long("tags")
.short('t')
.global(true)
.help("Include tags into the list."),
)
.arg(
Arg::new("max-rows")
.long("max-rows")
.value_name("MAX-ROWS")
.global(true)
.help("Max of rows for a table."),
)
.arg(
Arg::new("limit")
.long("limit")
.global(true)
.value_name("LIMIT")
.default_value("10")
.help("Limit of requests."),
);
each_subcommand!(add_subcommand);
command
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
macro_rules! execute_subcommand {
($name:ident) => {{
if let Some(sub_matches) =
matches.subcommand_matches(&stringify!($name).replace('_', "-"))
{
return crate::commands::events::$name::execute(&sub_matches);
}
}};
}
each_subcommand!(execute_subcommand);
unreachable!();
}
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ macro_rules! each_subcommand {
$mac!(bash_hook);
$mac!(debug_files);
$mac!(deploys);
$mac!(events);
$mac!(files);
$mac!(info);
$mac!(issues);
Expand Down Expand Up @@ -62,6 +63,7 @@ to learn more about them.";
const UPDATE_NAGGER_CMDS: &[&str] = &[
"debug-files",
"deploys",
"events",
"files",
"info",
"issues",
Expand Down