fix: query authoritative Ticket content and relations
This commit is contained in:
@@ -428,8 +428,23 @@ impl TicketAuthority for SqliteWorkspaceAuthority {
|
|||||||
event_cursor: None,
|
event_cursor: None,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
if ticket_matches_query(&summary, &detail, &query) {
|
let authoritative = self
|
||||||
items.push(ticket_query_item(summary, &detail, &query));
|
.ticket_backend
|
||||||
|
.show(TicketIdOrSlug::Id(summary.id.clone()))?;
|
||||||
|
if ticket_matches_query(
|
||||||
|
&summary,
|
||||||
|
&detail,
|
||||||
|
authoritative.document.body.as_str(),
|
||||||
|
&authoritative.events,
|
||||||
|
&query,
|
||||||
|
) {
|
||||||
|
items.push(ticket_query_item(
|
||||||
|
summary,
|
||||||
|
&detail,
|
||||||
|
authoritative.document.body.as_str(),
|
||||||
|
&authoritative.events,
|
||||||
|
&query,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort_ticket_query_items(&mut items, sort);
|
sort_ticket_query_items(&mut items, sort);
|
||||||
@@ -1189,6 +1204,8 @@ fn parse_query_cursor(cursor: &str) -> Result<(String, String)> {
|
|||||||
fn ticket_matches_query(
|
fn ticket_matches_query(
|
||||||
summary: &TicketSummary,
|
summary: &TicketSummary,
|
||||||
detail: &TicketDetail,
|
detail: &TicketDetail,
|
||||||
|
authoritative_body: &str,
|
||||||
|
authoritative_events: &[TicketEvent],
|
||||||
query: &TicketQueryRequest,
|
query: &TicketQueryRequest,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if !query.states.is_empty() && !query.states.iter().any(|state| state == &summary.state) {
|
if !query.states.is_empty() && !query.states.iter().any(|state| state == &summary.state) {
|
||||||
@@ -1206,10 +1223,12 @@ fn ticket_matches_query(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if !query.event_kinds.is_empty()
|
if !query.event_kinds.is_empty()
|
||||||
&& !detail
|
&& !authoritative_events.iter().any(|event| {
|
||||||
.events
|
query
|
||||||
.iter()
|
.event_kinds
|
||||||
.any(|event| query.event_kinds.iter().any(|kind| kind == &event.kind))
|
.iter()
|
||||||
|
.any(|kind| kind == event.kind.as_str())
|
||||||
|
})
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1275,34 +1294,69 @@ fn ticket_matches_query(
|
|||||||
}) {
|
}) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if let Some(ticket_id) = &query.related_ticket_id {
|
if let Some(ticket_id) = &query.related_ticket_id
|
||||||
let relation_json = serde_json::to_string(&detail.relations).unwrap_or_default();
|
&& !detail
|
||||||
if !relation_json.contains(ticket_id) {
|
.relations
|
||||||
return false;
|
.outgoing
|
||||||
}
|
.iter()
|
||||||
|
.any(|relation| &relation.target == ticket_id)
|
||||||
|
&& !detail
|
||||||
|
.relations
|
||||||
|
.incoming
|
||||||
|
.iter()
|
||||||
|
.any(|relation| &relation.source_ticket == ticket_id)
|
||||||
|
&& !detail
|
||||||
|
.relations
|
||||||
|
.blockers
|
||||||
|
.iter()
|
||||||
|
.any(|relation| &relation.blocking_ticket == ticket_id)
|
||||||
|
&& !detail
|
||||||
|
.relations
|
||||||
|
.notices
|
||||||
|
.iter()
|
||||||
|
.any(|relation| &relation.related_ticket == ticket_id)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if let Some(kind) = &query.relation_kind {
|
if let Some(kind) = &query.relation_kind
|
||||||
let relation_json = serde_json::to_string(&detail.relations).unwrap_or_default();
|
&& !detail
|
||||||
if !relation_json.contains(kind) {
|
.relations
|
||||||
return false;
|
.outgoing
|
||||||
}
|
.iter()
|
||||||
|
.any(|relation| &relation.kind == kind)
|
||||||
|
&& !detail
|
||||||
|
.relations
|
||||||
|
.incoming
|
||||||
|
.iter()
|
||||||
|
.any(|relation| &relation.forward_kind == kind)
|
||||||
|
&& !detail
|
||||||
|
.relations
|
||||||
|
.blockers
|
||||||
|
.iter()
|
||||||
|
.any(|relation| &relation.relation_kind == kind)
|
||||||
|
&& !detail
|
||||||
|
.relations
|
||||||
|
.notices
|
||||||
|
.iter()
|
||||||
|
.any(|relation| &relation.kind == kind)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
query.query.as_ref().is_none_or(|text| {
|
query.query.as_ref().is_none_or(|text| {
|
||||||
let needle = text.to_lowercase();
|
let needle = text.to_lowercase();
|
||||||
summary.title.to_lowercase().contains(&needle)
|
summary.title.to_lowercase().contains(&needle)
|
||||||
|| detail.body.to_lowercase().contains(&needle)
|
|| authoritative_body.to_lowercase().contains(&needle)
|
||||||
|| detail.events.iter().any(|event| {
|
|| authoritative_events
|
||||||
event
|
.iter()
|
||||||
.body
|
.any(|event| event.body.as_str().to_lowercase().contains(&needle))
|
||||||
.as_deref()
|
|
||||||
.is_some_and(|body| body.to_lowercase().contains(&needle))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ticket_query_item(
|
fn ticket_query_item(
|
||||||
summary: TicketSummary,
|
summary: TicketSummary,
|
||||||
detail: &TicketDetail,
|
detail: &TicketDetail,
|
||||||
|
authoritative_body: &str,
|
||||||
|
authoritative_events: &[TicketEvent],
|
||||||
query: &TicketQueryRequest,
|
query: &TicketQueryRequest,
|
||||||
) -> TicketQueryItem {
|
) -> TicketQueryItem {
|
||||||
let mut matched_fields = Vec::new();
|
let mut matched_fields = Vec::new();
|
||||||
@@ -1314,26 +1368,18 @@ fn ticket_query_item(
|
|||||||
matched_fields.push("title".to_string());
|
matched_fields.push("title".to_string());
|
||||||
snippet = Some(summary.title.clone());
|
snippet = Some(summary.title.clone());
|
||||||
}
|
}
|
||||||
if detail.body.to_lowercase().contains(&needle) {
|
if authoritative_body.to_lowercase().contains(&needle) {
|
||||||
matched_fields.push("body".to_string());
|
matched_fields.push("body".to_string());
|
||||||
snippet.get_or_insert_with(|| matching_snippet(&detail.body, text));
|
snippet.get_or_insert_with(|| matching_snippet(authoritative_body, text));
|
||||||
}
|
}
|
||||||
if let Some(event) = detail.events.iter().find(|event| {
|
if let Some((sequence, event)) = authoritative_events
|
||||||
event
|
.iter()
|
||||||
.body
|
.enumerate()
|
||||||
.as_deref()
|
.find(|(_, event)| event.body.as_str().to_lowercase().contains(&needle))
|
||||||
.is_some_and(|body| body.to_lowercase().contains(&needle))
|
{
|
||||||
}) {
|
|
||||||
matched_fields.push("event".to_string());
|
matched_fields.push("event".to_string());
|
||||||
matching_event = Some(TicketEvidenceEvent {
|
matching_event = Some(ticket_evidence_event(sequence, event));
|
||||||
event_ref: event.event_ref.clone(),
|
snippet.get_or_insert_with(|| event.body.as_str().to_string());
|
||||||
sequence: event.sequence,
|
|
||||||
kind: event.kind.clone(),
|
|
||||||
at: event.at.clone(),
|
|
||||||
author: event.author.clone(),
|
|
||||||
excerpt: event.body.clone().unwrap_or_default(),
|
|
||||||
});
|
|
||||||
snippet.get_or_insert_with(|| event.body.clone().unwrap_or_default());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TicketQueryItem {
|
TicketQueryItem {
|
||||||
@@ -1826,6 +1872,42 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let authority = SqliteWorkspaceAuthority::new(&db_path, "workspace-test").unwrap();
|
let authority = SqliteWorkspaceAuthority::new(&db_path, "workspace-test").unwrap();
|
||||||
|
authority
|
||||||
|
.ticket_backend
|
||||||
|
.add_event(
|
||||||
|
TicketIdOrSlug::Id("00000000001J2".to_string()),
|
||||||
|
ticket::NewTicketEvent::new(
|
||||||
|
ticket::TicketEventKind::Other("historical_signal".to_string()),
|
||||||
|
"Historical event marker.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
for index in 0..110 {
|
||||||
|
authority
|
||||||
|
.ticket_backend
|
||||||
|
.add_event(
|
||||||
|
TicketIdOrSlug::Id("00000000001J2".to_string()),
|
||||||
|
ticket::NewTicketEvent::new(
|
||||||
|
ticket::TicketEventKind::Comment,
|
||||||
|
format!("Filler event {index}."),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
authority
|
||||||
|
.ticket_backend
|
||||||
|
.add_ticket_relation(
|
||||||
|
TicketIdOrSlug::Id("00000000001J2".to_string()),
|
||||||
|
ticket::NewTicketRelation {
|
||||||
|
kind: ticket::TicketRelationKind::Related,
|
||||||
|
target: "00000000001J5".to_string(),
|
||||||
|
note: Some(
|
||||||
|
"mentions unrelated id 00000000001J9 and kind depends_on".to_string(),
|
||||||
|
),
|
||||||
|
author: Some("tester".to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let tickets = authority.list_tickets(20).unwrap();
|
let tickets = authority.list_tickets(20).unwrap();
|
||||||
assert_eq!(tickets.record_authority, "workspace-sqlite");
|
assert_eq!(tickets.record_authority, "workspace-sqlite");
|
||||||
assert_eq!(tickets.items[0].record_source, "sqlite_yoi_ticket");
|
assert_eq!(tickets.items[0].record_source, "sqlite_yoi_ticket");
|
||||||
@@ -1838,12 +1920,14 @@ mod tests {
|
|||||||
|
|
||||||
let ticket = authority.ticket("00000000001J2").unwrap();
|
let ticket = authority.ticket("00000000001J2").unwrap();
|
||||||
assert!(ticket.body.contains("Ticket body"));
|
assert!(ticket.body.contains("Ticket body"));
|
||||||
|
assert!(ticket.body_truncated);
|
||||||
|
assert!(!ticket.body.contains("Deep Ticket marker"));
|
||||||
assert!(!ticket.item_revision.is_empty());
|
assert!(!ticket.item_revision.is_empty());
|
||||||
assert_eq!(ticket.linked_objectives[0].id, "00000000001J3");
|
assert_eq!(ticket.linked_objectives[0].id, "00000000001J3");
|
||||||
assert_eq!(ticket.event_page.returned, ticket.events.len());
|
assert_eq!(ticket.event_page.returned, ticket.events.len());
|
||||||
let ticket_query = authority
|
let ticket_query = authority
|
||||||
.query_tickets(TicketQueryRequest {
|
.query_tickets(TicketQueryRequest {
|
||||||
query: Some("Ticket body".to_string()),
|
query: Some("Deep Ticket marker".to_string()),
|
||||||
states: vec!["ready".to_string()],
|
states: vec!["ready".to_string()],
|
||||||
linked_objective_id: Some("00000000001J3".to_string()),
|
linked_objective_id: Some("00000000001J3".to_string()),
|
||||||
attention: vec!["unblocked".to_string(), "missing_commit".to_string()],
|
attention: vec!["unblocked".to_string(), "missing_commit".to_string()],
|
||||||
@@ -1859,6 +1943,44 @@ mod tests {
|
|||||||
.contains(&"body".to_string())
|
.contains(&"body".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(ticket_query.page.limit, 1);
|
assert_eq!(ticket_query.page.limit, 1);
|
||||||
|
let historical_event_query = authority
|
||||||
|
.query_tickets(TicketQueryRequest {
|
||||||
|
query: Some("Historical event marker".to_string()),
|
||||||
|
event_kinds: vec!["historical_signal".to_string()],
|
||||||
|
limit: Some(1),
|
||||||
|
..TicketQueryRequest::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(historical_event_query.items.len(), 1);
|
||||||
|
let matching_event = historical_event_query.items[0]
|
||||||
|
.matching_event
|
||||||
|
.as_ref()
|
||||||
|
.expect("matching historical event");
|
||||||
|
assert_eq!(matching_event.kind, "historical_signal");
|
||||||
|
assert!(matching_event.excerpt.contains("Historical event marker"));
|
||||||
|
let note_only_id = authority
|
||||||
|
.query_tickets(TicketQueryRequest {
|
||||||
|
related_ticket_id: Some("00000000001J9".to_string()),
|
||||||
|
..TicketQueryRequest::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(note_only_id.items.is_empty());
|
||||||
|
let note_only_kind = authority
|
||||||
|
.query_tickets(TicketQueryRequest {
|
||||||
|
relation_kind: Some("depends_on".to_string()),
|
||||||
|
..TicketQueryRequest::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(note_only_kind.items.is_empty());
|
||||||
|
let exact_relation = authority
|
||||||
|
.query_tickets(TicketQueryRequest {
|
||||||
|
related_ticket_id: Some("00000000001J5".to_string()),
|
||||||
|
relation_kind: Some("related".to_string()),
|
||||||
|
..TicketQueryRequest::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(exact_relation.items.len(), 1);
|
||||||
|
assert_eq!(exact_relation.items[0].id, "00000000001J2");
|
||||||
let first_page = authority
|
let first_page = authority
|
||||||
.query_tickets(TicketQueryRequest {
|
.query_tickets(TicketQueryRequest {
|
||||||
sort: Some("title".to_string()),
|
sort: Some("title".to_string()),
|
||||||
@@ -2052,7 +2174,10 @@ updated_at: "2026-01-02T00:00:00Z"
|
|||||||
---
|
---
|
||||||
|
|
||||||
Ticket body.
|
Ticket body.
|
||||||
|
{padding}
|
||||||
|
Deep Ticket marker.
|
||||||
"#,
|
"#,
|
||||||
|
padding = "x".repeat(70_000),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user