use crate::client::KibanaClient;
use crate::etl::Extractor;
use crate::{Error, Result, ResultContext};
use serde_json::Value;
pub struct SavedObjectsExtractor {
client: KibanaClient,
manifest: super::SavedObjectsManifest,
}
impl SavedObjectsExtractor {
pub fn new(client: KibanaClient, manifest: super::SavedObjectsManifest) -> Self {
Self { client, manifest }
}
async fn export_objects(&self) -> Result<Vec<Value>> {
let path = "api/saved_objects/_export";
tracing::debug!(
"Exporting {} object(s) from space '{}'",
self.manifest.count(),
self.client.space_id()
);
let response = self
.client
.post_json_value(path, &serde_json::to_value(&self.manifest)?)
.await
.with_context(|| "Failed to export saved objects from Kibana")?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(Error::api_response(status, body));
}
let body = response
.text()
.await
.with_context(|| "Failed to read export response")?;
let objects: Vec<Value> = body
.lines()
.filter(|line| !line.trim().is_empty())
.map(serde_json::from_str)
.collect::<std::result::Result<Vec<_>, serde_json::Error>>()
.with_context(|| "Failed to parse NDJSON response")?;
tracing::info!("Exported {} object(s) from Kibana", objects.len());
Ok(objects)
}
}
impl Extractor for SavedObjectsExtractor {
type Item = Value;
async fn extract(&self) -> Result<Vec<Self::Item>> {
let objects = self.export_objects().await?;
tracing::info!("Extracted {} saved object(s)", objects.len());
Ok(objects)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::{Auth, KibanaClient};
use crate::kibana::saved_objects::SavedObjectsManifest;
use crate::kibana::saved_objects::manifest::SavedObject;
use url::Url;
#[test]
fn test_extractor_creation() {
let url = Url::parse("http://localhost:5601").unwrap();
let client = KibanaClient::new(url, Auth::None).unwrap();
let space_client = client.space("default").unwrap();
let manifest = SavedObjectsManifest::new();
let extractor = SavedObjectsExtractor::new(space_client, manifest);
assert_eq!(extractor.client.space_id(), "default");
}
#[test]
fn test_extractor_with_manifest() {
let url = Url::parse("http://localhost:5601").unwrap();
let client = KibanaClient::builder(url)
.spaces([
("default".to_string(), "Default".to_string()),
("marketing".to_string(), "Marketing".to_string()),
])
.build()
.unwrap();
let space_client = client.space("marketing").unwrap();
let mut so_manifest = SavedObjectsManifest::new();
so_manifest.add_object(SavedObject::new("dashboard", "test-dashboard"));
so_manifest.add_object(SavedObject::new("visualization", "test-viz"));
let extractor = SavedObjectsExtractor::new(space_client, so_manifest.clone());
assert_eq!(extractor.client.space_id(), "marketing");
assert_eq!(extractor.manifest.count(), 2);
}
}