use std::fmt;
use std::rc::Rc;
use std::string::ToString;
use std::sync::Arc;
use bugzilla_query::Bug;
use color_eyre::eyre::{bail, Result};
use jira_query::Issue;
use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
use crate::config::{tracker, TicketQuery};
use crate::extra_fields::{DocTextStatus, DocsContact, ExtraFields};
use crate::tracker_access::{self, AnnotatedTicket};
#[derive(Clone, Debug)]
pub struct AbstractTicket {
pub id: Rc<TicketId>,
pub summary: String,
pub description: Option<String>,
pub doc_type: String,
pub doc_text: String,
pub docs_contact: DocsContact,
pub status: String,
pub resolution: Option<String>,
pub is_open: bool,
pub priority: String,
pub url: String,
pub assignee: Option<String>,
pub components: Vec<String>,
pub product: String,
pub labels: Option<Vec<String>>,
pub flags: Option<Vec<String>>,
pub target_releases: Vec<String>,
pub subsystems: Result<Vec<String>, String>,
pub groups: Option<Vec<String>>,
pub public: bool,
pub doc_text_status: DocTextStatus,
pub references: Option<Vec<String>>,
}
impl Serialize for AbstractTicket {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Color", 3)?;
state.serialize_field("id", &self.id.to_string())?;
state.serialize_field("summary", &self.summary)?;
state.serialize_field("description", &self.description)?;
state.serialize_field("doc_type", &self.doc_type)?;
state.serialize_field("doc_text", &self.doc_text)?;
state.serialize_field("docs_contact", &self.docs_contact.as_str())?;
state.serialize_field("doc_text_status", &self.doc_text_status.to_string())?;
state.serialize_field("status", &self.status)?;
state.serialize_field("is_open", &self.is_open)?;
state.serialize_field("priority", &self.priority)?;
state.serialize_field("url", &self.url)?;
state.serialize_field("assignee", &self.assignee)?;
state.serialize_field("components", &self.components)?;
state.serialize_field("product", &self.product)?;
state.serialize_field("labels", &self.labels)?;
state.serialize_field("flags", &self.flags)?;
state.serialize_field("target_releases", &self.target_releases)?;
state.serialize_field("subsystems", &self.subsystems)?;
state.serialize_field("groups", &self.groups)?;
state.serialize_field("public", &self.public)?;
state.serialize_field("references", &self.references)?;
state.end()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
pub struct TicketId {
pub key: String,
pub tracker: tracker::Service,
}
impl fmt::Display for TicketId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", &self.tracker, &self.key)
}
}
pub trait IntoAbstract {
fn into_abstract(
self,
references: Option<Vec<String>>,
config: &tracker::Config,
) -> Result<AbstractTicket>;
}
impl IntoAbstract for Bug {
fn into_abstract(
self,
references: Option<Vec<String>>,
config: &tracker::Config,
) -> Result<AbstractTicket> {
let bz_fields = &config.bugzilla;
let ticket = AbstractTicket {
id: Rc::new(TicketId {
key: self.id.to_string(),
tracker: tracker::Service::Bugzilla,
}),
description: None,
doc_type: self.doc_type(bz_fields)?,
doc_text: self.doc_text(bz_fields)?,
target_releases: self.target_releases(bz_fields),
subsystems: self.subsystems(bz_fields).map_err(|e| e.to_string()),
doc_text_status: self.doc_text_status(bz_fields),
docs_contact: self.docs_contact(bz_fields),
url: self.url(bz_fields),
summary: self.summary,
status: self.status,
resolution: Some(self.resolution),
is_open: self.is_open,
priority: self.priority,
assignee: Some(self.assigned_to),
components: self.component.into_vec(),
product: self.product,
labels: None,
flags: self
.flags
.map(|flags| flags.into_iter().map(|flag| flag.to_string()).collect()),
public: self.groups.is_empty(),
groups: Some(self.groups),
references,
};
Ok(ticket)
}
}
impl IntoAbstract for Issue {
fn into_abstract(
self,
references: Option<Vec<String>>,
config: &tracker::Config,
) -> Result<AbstractTicket> {
let jira_fields = &config.jira;
let ticket = AbstractTicket {
doc_type: self.doc_type(jira_fields)?,
doc_text: self.doc_text(jira_fields)?,
target_releases: self.target_releases(jira_fields),
doc_text_status: self.doc_text_status(jira_fields),
docs_contact: self.docs_contact(jira_fields),
subsystems: self.subsystems(jira_fields).map_err(|e| e.to_string()),
url: self.url(jira_fields),
id: Rc::new(TicketId {
key: self.key,
tracker: tracker::Service::Jira,
}),
summary: self.fields.summary,
description: self.fields.description,
is_open: &self.fields.status.name != "Closed",
status: self.fields.status.name,
resolution: self.fields.resolution.map(|resolution| resolution.name),
priority: self
.fields
.priority
.map_or_else(|| "Missing".to_string(), |p| p.name),
assignee: self.fields.assignee.and_then(|a| a.name),
components: self.fields.components.into_iter().map(|c| c.name).collect(),
product: self.fields.project.name,
labels: Some(self.fields.labels),
flags: None,
groups: None,
public: {
self.fields.security.is_none()
&& !config
.jira
.private_projects
.contains(&self.fields.project.key)
},
references,
};
Ok(ticket)
}
}
pub fn from_queries(
queries: &[Arc<TicketQuery>],
trackers: &tracker::Config,
) -> Result<Vec<AbstractTicket>> {
let annotated_tickets = tracker_access::unsorted_tickets(queries, trackers)?;
let sorted_tickets = sort_tickets(queries, &annotated_tickets)?;
Ok(sorted_tickets.into_iter().map(|at| at.ticket).collect())
}
pub fn sort_tickets(
queries: &[Arc<TicketQuery>],
tickets: &[AnnotatedTicket],
) -> Result<Vec<AnnotatedTicket>> {
let mut sorted_tickets: Vec<AnnotatedTicket> = Vec::new();
for query in queries {
let mut matching_tickets: Vec<AnnotatedTicket> = tickets
.iter()
.filter(|at| query == &at.query)
.cloned()
.collect();
if matching_tickets.is_empty() {
bail!("Query produced no tickets: {:#?}", query);
}
sorted_tickets.append(&mut matching_tickets);
}
Ok(sorted_tickets)
}