Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions src/extra_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ struct BzTeam {
/// from a custom Bugzilla or Jira field.
///
/// Returns an error is the field is missing or if it is not a string.
fn extract_field(field_name: Field, extra: &Value, fields: &[String], id: Id) -> Result<String> {
fn extract_field(field_name: Field, extra: &Value, fields: &[String], id: Id, tracker: &impl tracker::FieldsConfig) -> Result<String> {
// Record all errors that occur with tried fields that exist.
let mut errors = Vec::new();
// Record all empty but potentially okay fields.
Expand Down Expand Up @@ -193,7 +193,7 @@ fn extract_field(field_name: Field, extra: &Value, fields: &[String], id: Id) ->

// If all we've got are errors, return an error with the complete errors report.
if empty_fields.is_empty() {
let report = error_chain(errors, field_name, fields, id);
let report = error_chain(errors, field_name, fields, id, tracker);
Err(report)
// If we at least got an existing but empty field, return an empty string.
// I think it's safe to treat it as such.
Expand All @@ -219,13 +219,26 @@ impl fmt::Display for Id<'_> {
}
}

impl Id<'_> {
/// Construct a URL to the ticket.
fn url(&self, tracker: &impl tracker::FieldsConfig) -> String {
match self {
Self::BZ(id) => format!("{}/show_bug.cgi?id={}", tracker.host(), id),
Self::Jira(key) => format!("{}/browse/{}", tracker.host(), key),
}
}
}

/// Prepare a user-readable list of errors, reported in the order that they occurred.
fn error_chain(mut errors: Vec<Report>, field_name: Field, fields: &[String], id: Id) -> Report {
fn error_chain(mut errors: Vec<Report>, field_name: Field, fields: &[String], id: Id, tracker: &impl tracker::FieldsConfig) -> Report {
let url = id.url(tracker);
let top_error = eyre!(
"The {} field is missing or malformed in {}. \
The configured fields are: {:?}",
"The {} field is missing or malformed in {} ({}).\n\
The configured fields for '{}' are: {:?}",
field_name,
id,
url,
field_name,
fields
);

Expand All @@ -242,20 +255,20 @@ fn error_chain(mut errors: Vec<Report>, field_name: Field, fields: &[String], id
impl ExtraFields for Bug {
fn doc_type(&self, config: &impl tracker::FieldsConfig) -> Result<String> {
let fields = config.doc_type();
extract_field(Field::DocType, &self.extra, fields, Id::BZ(self.id))
extract_field(Field::DocType, &self.extra, fields, Id::BZ(self.id), config)
}

fn doc_text(&self, config: &impl tracker::FieldsConfig) -> Result<String> {
let fields = config.doc_text();
extract_field(Field::DocText, &self.extra, fields, Id::BZ(self.id))
extract_field(Field::DocText, &self.extra, fields, Id::BZ(self.id), config)
}

fn target_releases(&self, config: &impl tracker::FieldsConfig) -> Vec<String> {
let fields = config.target_release();
let mut errors = Vec::new();

// Try the custom overrides, if any.
match extract_field(Field::TargetRelease, &self.extra, fields, Id::BZ(self.id)) {
match extract_field(Field::TargetRelease, &self.extra, fields, Id::BZ(self.id), config) {
Ok(release) => {
// Bugzilla uses the "---" placeholder to represent an unset release.
// TODO: Are there any more placeholder?
Expand All @@ -280,7 +293,7 @@ impl ExtraFields for Bug {
match &self.target_release {
Some(versions) => versions.clone().into_vec(),
None => {
let report = error_chain(errors, Field::TargetRelease, fields, Id::BZ(self.id));
let report = error_chain(errors, Field::TargetRelease, fields, Id::BZ(self.id), config);
log::warn!("{report}");

// Finally, return an empty list if everything else failed.
Expand Down Expand Up @@ -323,7 +336,7 @@ impl ExtraFields for Bug {
}
}

let report = error_chain(errors, Field::Subsystems, fields, Id::BZ(self.id));
let report = error_chain(errors, Field::Subsystems, fields, Id::BZ(self.id), config);
Err(report)
}

Expand Down Expand Up @@ -360,7 +373,7 @@ impl ExtraFields for Bug {

// If all we've got are errors, report an error with the complete errors report.
if empty_fields.is_empty() {
let report = error_chain(errors, Field::DocTextStatus, fields, Id::BZ(self.id));
let report = error_chain(errors, Field::DocTextStatus, fields, Id::BZ(self.id), config);
log::warn!("{}", report);
// If we at least got an existing but empty field, report the empty flags.
} else {
Expand All @@ -379,7 +392,7 @@ impl ExtraFields for Bug {
let mut errors = Vec::new();

// Try the custom overrides, if any.
let docs_contact = extract_field(Field::DocsContact, &self.extra, fields, Id::BZ(self.id));
let docs_contact = extract_field(Field::DocsContact, &self.extra, fields, Id::BZ(self.id), config);

match docs_contact {
Ok(docs_contact) => {
Expand All @@ -392,7 +405,7 @@ impl ExtraFields for Bug {

// No override succeeded. See if there's a value in the standard field.
if self.docs_contact.is_none() {
let report = error_chain(errors, Field::DocsContact, fields, Id::BZ(self.id));
let report = error_chain(errors, Field::DocsContact, fields, Id::BZ(self.id), config);
log::warn!("{}", report);
}

Expand Down Expand Up @@ -461,7 +474,7 @@ impl ExtraFields for Issue {
};
}

let report = error_chain(errors, Field::DocType, fields, Id::Jira(&self.key));
let report = error_chain(errors, Field::DocType, fields, Id::Jira(&self.key), config);
Err(report)
}

Expand All @@ -472,6 +485,7 @@ impl ExtraFields for Issue {
&self.fields.extra,
fields,
Id::Jira(&self.key),
config,
)
}

Expand Down Expand Up @@ -513,6 +527,7 @@ impl ExtraFields for Issue {
&self.extra,
&[field.clone()],
Id::Jira(&self.key),
config,
);
match string {
Ok(string) => {
Expand All @@ -530,7 +545,7 @@ impl ExtraFields for Issue {
// If any errors occurred, report them as warnings and continue.
if !errors.is_empty() {
let id = Id::Jira(&self.key);
let report = error_chain(errors, Field::TargetRelease, fields, id);
let report = error_chain(errors, Field::TargetRelease, fields, id, config);
log::warn!("The custom target releases failed in {}. Falling back on the standard fix versions field.", id);

// Provide this additional information on demand.
Expand Down Expand Up @@ -599,7 +614,7 @@ impl ExtraFields for Issue {

// No field produced a `Some` value.
// Prepare a user-readable list of errors, if any occurred.
let report = error_chain(errors, Field::Subsystems, fields, Id::Jira(&self.key));
let report = error_chain(errors, Field::Subsystems, fields, Id::Jira(&self.key), config);

// Return the combined error.
Err(report)
Expand Down Expand Up @@ -648,7 +663,7 @@ impl ExtraFields for Issue {
}

// No field produced a `Some` value.
let report = error_chain(errors, Field::DocTextStatus, fields, Id::Jira(&self.key));
let report = error_chain(errors, Field::DocTextStatus, fields, Id::Jira(&self.key), config);
// Report all errors.
log::warn!("{}", report);

Expand All @@ -674,7 +689,7 @@ impl ExtraFields for Issue {
}

// No field produced a `Some` value.
let report = error_chain(Vec::new(), Field::DocsContact, fields, Id::Jira(&self.key));
let report = error_chain(Vec::new(), Field::DocsContact, fields, Id::Jira(&self.key), config);
// This field is non-critical.
log::warn!("{}", report);

Expand Down
13 changes: 11 additions & 2 deletions src/references.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,20 @@ impl From<&[Arc<TicketQuery>]> for ReferenceQueries {

// I don't know how to accomplish this in a functional style, unfortunately.
for query in item {
for reference in &query.references {
reference_queries.push(Arc::clone(reference));
if !query.references.is_empty() {
log::info!(
"Query {:?} has {} reference(s)",
query.using,
query.references.len()
);
for reference in &query.references {
log::info!(" Reference: {:?}", reference.using);
reference_queries.push(Arc::clone(reference));
}
}
}

log::info!("Total reference queries extracted: {}", reference_queries.len());
Self(reference_queries)
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/status_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ fn extract_number(caps: &regex::Captures, index: usize) -> Option<u32> {
}

/// List the most common release set in the tickets.
fn most_common_release(tickets: &[AbstractTicket]) -> Option<Version> {
fn most_common_release(tickets: &[AbstractTicket]) -> Option<Version<'_>> {
let mut releases: Counter<Version> = Counter::new();

// Releases are a list, and each ticket can have several of them.
Expand Down
34 changes: 24 additions & 10 deletions src/tracker_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ async fn issues(

let mut all_issues = Vec::new();

let issues_from_ids = issues_from_ids(&queries_by_id, &jira_instance);
let jira_host = &trackers.jira.host;
let issues_from_ids = issues_from_ids(&queries_by_id, &jira_instance, jira_host);
let issues_from_searches = issues_from_searches(&queries_by_search, &jira_instance);

let (mut issues_from_ids, mut issues_from_searches) =
Expand All @@ -403,14 +404,13 @@ async fn issues(
async fn issues_from_ids(
queries: &[(&str, Arc<TicketQuery>)],
jira_instance: &jira_query::JiraInstance,
jira_host: &str,
) -> Result<Vec<(Arc<TicketQuery>, Issue)>> {
let issue_keys: Vec<&str> = queries.iter().map(|(key, _query)| *key).collect();
log::info!("Jira query by IDs: {:?}", issue_keys);

let issues = jira_instance
.issues(
&queries
.iter()
.map(|(key, _query)| *key)
.collect::<Vec<&str>>(),
)
.issues(&issue_keys)
// This enables the download concurrency:
.await
.wrap_err("Failed to download tickets from Jira.")?;
Expand All @@ -421,9 +421,23 @@ async fn issues_from_ids(
let matching_query = queries
.iter()
.find(|(key, _query)| key == &issue.key.as_str())
.map(|(_key, query)| Arc::clone(query))
.ok_or_else(|| eyre!("Issue {} doesn't match any configured query.", issue.key))?;
annotated_issues.push((matching_query, issue));
.map(|(_key, query)| Arc::clone(query));

if let Some(query) = matching_query {
annotated_issues.push((query, issue));
} else {
// When we can't find a match, it's likely because the ticket was moved to another project
// and now has a different ID than what was configured.
let ticket_url = format!("{}/browse/{}", jira_host.trim_end_matches('/'), issue.key);

bail!(
"Ticket ID mismatch: Jira returned '{}' ({}) which doesn't match any configured query. \
This ticket was likely moved from another project. Check the logs above to see which \
ticket IDs were requested, then update your tickets.yaml with the new ID.",
issue.key,
ticket_url
);
}
}

Ok(annotated_issues)
Expand Down