- Add SourceType::Note with extract_note_document() and ParentMetadataCache - Migration 022: composite indexes for notes queries + author_id column - Migration 024: table rebuild adding 'note' to CHECK constraints, defense triggers - Migration 025: backfill existing non-system notes into dirty queue - Add lore notes CLI command with 17 filter options (author, path, resolution, etc.) - Support table/json/jsonl/csv output formats with field selection - Wire note dirty tracking through discussion and MR discussion ingestion - Fix test_migration_024_preserves_existing_data off-by-one (tested wrong migration) - Fix upsert_document_inner returning false for label/path-only changes
111 lines
3.5 KiB
Rust
111 lines
3.5 KiB
Rust
use serde::Serialize;
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct RobotMeta {
|
|
pub elapsed_ms: u64,
|
|
}
|
|
|
|
/// Filter JSON object fields in-place for `--fields` support.
|
|
/// Retains only the specified field names on each item in the list array.
|
|
pub fn filter_fields(value: &mut serde_json::Value, list_key: &str, fields: &[String]) {
|
|
if fields.is_empty() {
|
|
return;
|
|
}
|
|
if let Some(items) = value
|
|
.get_mut("data")
|
|
.and_then(|d| d.get_mut(list_key))
|
|
.and_then(|v| v.as_array_mut())
|
|
{
|
|
for item in items {
|
|
if let Some(obj) = item.as_object_mut() {
|
|
obj.retain(|k, _| fields.iter().any(|f| f == k));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Expand the `minimal` preset into concrete field names.
|
|
pub fn expand_fields_preset(fields: &[String], entity: &str) -> Vec<String> {
|
|
if fields.len() == 1 && fields[0] == "minimal" {
|
|
match entity {
|
|
"issues" => ["iid", "title", "state", "updated_at_iso"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"mrs" => ["iid", "title", "state", "updated_at_iso"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"search" => ["document_id", "title", "source_type", "score"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"timeline" => ["timestamp", "type", "entity_iid", "detail"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"who_expert" => ["username", "score"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"who_workload" => ["iid", "title", "state"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"who_active" => ["entity_type", "iid", "title", "participants"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"who_overlap" => ["username", "touch_count"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"who_reviews" => ["name", "count", "percentage"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
"notes" => ["id", "author_username", "body", "created_at_iso"]
|
|
.iter()
|
|
.map(|s| (*s).to_string())
|
|
.collect(),
|
|
_ => fields.to_vec(),
|
|
}
|
|
} else {
|
|
fields.to_vec()
|
|
}
|
|
}
|
|
|
|
/// Strip `response_schema` from every command entry for `--brief` mode.
|
|
pub fn strip_schemas(commands: &mut serde_json::Value) {
|
|
if let Some(map) = commands.as_object_mut() {
|
|
for (_cmd_name, cmd) in map.iter_mut() {
|
|
if let Some(obj) = cmd.as_object_mut() {
|
|
obj.remove("response_schema");
|
|
obj.remove("example_output");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_expand_fields_preset_notes() {
|
|
let fields = vec!["minimal".to_string()];
|
|
let expanded = expand_fields_preset(&fields, "notes");
|
|
assert_eq!(
|
|
expanded,
|
|
["id", "author_username", "body", "created_at_iso"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_expand_fields_preset_passthrough() {
|
|
let fields = vec!["id".to_string(), "body".to_string()];
|
|
let expanded = expand_fields_preset(&fields, "notes");
|
|
assert_eq!(expanded, ["id", "body"]);
|
|
}
|
|
}
|