use crate::models::{AttributeValue, EventLog};
use crate::state::{get_or_init_state, StoredObject};
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
fn store_filtered(log: EventLog) -> Result<JsValue, JsValue> {
let handle = get_or_init_state().store_object(StoredObject::EventLog(log))?;
Ok(crate::error::js_val(&handle))
}
#[wasm_bindgen]
pub fn filter_by_start_activity(
log_handle: &str,
activities_json: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
let keep: std::collections::HashSet<String> = serde_json::from_str(activities_json)
.map_err(|e| crate::error::js_val(&format!("Invalid JSON: {}", e)))?;
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|t| {
t.events
.first()
.and_then(|e| e.attributes.get(activity_key))
.and_then(|v| v.as_string())
.map_or(false, |a| keep.contains(a))
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_end_activity(
log_handle: &str,
activities_json: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
let keep: std::collections::HashSet<String> = serde_json::from_str(activities_json)
.map_err(|e| crate::error::js_val(&format!("Invalid JSON: {}", e)))?;
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|t| {
t.events
.last()
.and_then(|e| e.attributes.get(activity_key))
.and_then(|v| v.as_string())
.map_or(false, |a| keep.contains(a))
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_case_size(
log_handle: &str,
min_events: usize,
max_events: usize,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|t| t.events.len() >= min_events && t.events.len() <= max_events)
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_directly_follows(
log_handle: &str,
pairs_json: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
let pairs: Vec<[String; 2]> = serde_json::from_str(pairs_json)
.map_err(|e| crate::error::js_val(&format!("Invalid pairs JSON: {}", e)))?;
let pair_set: std::collections::HashSet<(String, String)> =
pairs.into_iter().map(|[f, t]| (f, t)).collect();
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
let acts: Vec<&str> = trace
.events
.iter()
.filter_map(|e| e.attributes.get(activity_key).and_then(|v| v.as_string()))
.collect();
acts.windows(2)
.any(|w| pair_set.contains(&(w[0].to_owned(), w[1].to_owned())))
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_variant_coverage(
log_handle: &str,
coverage_pct: f64,
activity_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let total = log.traces.len();
if total == 0 {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
return store_filtered(out);
}
let mut variant_counts: HashMap<Vec<String>, usize> = HashMap::new();
for trace in &log.traces {
let variant: Vec<String> = trace
.events
.iter()
.filter_map(|e| e.attributes.get(activity_key).and_then(|v| v.as_string()))
.map(str::to_owned)
.collect();
*variant_counts.entry(variant).or_insert(0) += 1;
}
let mut sorted: Vec<(Vec<String>, usize)> = variant_counts.into_iter().collect();
sorted.sort_by_key(|b| std::cmp::Reverse(b.1));
let target = (total as f64 * coverage_pct / 100.0).ceil() as usize;
let mut keep_variants: std::collections::HashSet<Vec<String>> =
std::collections::HashSet::new();
let mut covered = 0usize;
for (variant, cnt) in sorted {
keep_variants.insert(variant);
covered += cnt;
if covered >= target {
break;
}
}
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
let variant: Vec<String> = trace
.events
.iter()
.filter_map(|e| {
e.attributes.get(activity_key).and_then(|v| {
if let AttributeValue::String(s) = v {
Some(s.as_str())
} else {
v.as_string()
}
})
})
.map(str::to_owned)
.collect();
keep_variants.contains(&variant)
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_variants_top_k(
log_handle: &str,
k: usize,
activity_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut variant_counts: HashMap<Vec<String>, usize> = HashMap::new();
for trace in &log.traces {
let variant: Vec<String> = trace
.events
.iter()
.filter_map(|e| e.attributes.get(activity_key).and_then(|v| v.as_string()))
.map(str::to_owned)
.collect();
*variant_counts.entry(variant).or_insert(0) += 1;
}
let mut sorted: Vec<(Vec<String>, usize)> = variant_counts.into_iter().collect();
sorted.sort_by_key(|b| std::cmp::Reverse(b.1));
let keep_variants: std::collections::HashSet<Vec<String>> =
sorted.into_iter().take(k).map(|(v, _)| v).collect();
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
let variant: Vec<String> = trace
.events
.iter()
.filter_map(|e| {
e.attributes.get(activity_key).and_then(|v| {
if let AttributeValue::String(s) = v {
Some(s.as_str())
} else {
v.as_string()
}
})
})
.map(str::to_owned)
.collect();
keep_variants.contains(&variant)
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_traces_containing_activities(
log_handle: &str,
activities_json: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
let required: std::collections::HashSet<String> = serde_json::from_str(activities_json)
.map_err(|e| crate::error::js_val(&format!("Invalid JSON: {}", e)))?;
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
let activities: std::collections::HashSet<String> = trace
.events
.iter()
.filter_map(|e| e.attributes.get(activity_key).and_then(|v| v.as_string()))
.map(str::to_owned)
.collect();
required.is_subset(&activities)
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_traces_excluding_activities(
log_handle: &str,
activities_json: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
let excluded: std::collections::HashSet<String> = serde_json::from_str(activities_json)
.map_err(|e| crate::error::js_val(&format!("Invalid JSON: {}", e)))?;
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
!trace.events.iter().any(|e| {
e.attributes
.get(activity_key)
.and_then(|v| v.as_string())
.map_or(false, |a| excluded.contains(a))
})
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_time_range(
log_handle: &str,
min_dt: &str,
max_dt: &str,
timestamp_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
trace.events.iter().all(|e| {
if let Some(AttributeValue::String(ts)) = e.attributes.get(timestamp_key) {
ts.as_str() >= min_dt && ts.as_str() <= max_dt
} else {
false
}
})
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_case_performance(
log_handle: &str,
min_ms: i64,
max_ms: i64,
timestamp_key: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
if let (Some(first), Some(last)) = (
trace
.events
.first()
.and_then(|e| e.attributes.get(timestamp_key)),
trace
.events
.last()
.and_then(|e| e.attributes.get(timestamp_key)),
) {
if let (Some(start), Some(end)) = (first.as_string(), last.as_string()) {
if let (Ok(start_dt), Ok(end_dt)) = (
chrono::DateTime::parse_from_rfc3339(start),
chrono::DateTime::parse_from_rfc3339(end),
) {
let duration_ms =
end_dt.timestamp_millis() - start_dt.timestamp_millis();
return duration_ms >= min_ms && duration_ms <= max_ms;
}
}
}
false
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_rework_traces(log_handle: &str, activity_key: &str) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
let activities: Vec<String> = trace
.events
.iter()
.filter_map(|e| e.attributes.get(activity_key).and_then(|v| v.as_string()))
.map(str::to_owned)
.collect();
let mut seen = std::collections::HashSet::new();
for act in &activities {
if !seen.insert(act) {
return true; }
}
false
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_trace_attribute(
log_handle: &str,
attribute_key: &str,
attribute_value: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
trace
.attributes
.get(attribute_key)
.and_then(|v| v.as_string())
.map_or(false, |val| val == attribute_value)
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_event_attribute_value(
log_handle: &str,
attribute_key: &str,
attribute_value: &str,
) -> Result<JsValue, JsValue> {
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
trace.events.iter().any(|e| {
e.attributes
.get(attribute_key)
.and_then(|v| v.as_string())
.map_or(false, |val| val == attribute_value)
})
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_by_case_ids(
log_handle: &str,
case_ids_json: &str,
case_id_key: &str,
) -> Result<JsValue, JsValue> {
let keep_ids: std::collections::HashSet<String> = serde_json::from_str(case_ids_json)
.map_err(|e| crate::error::js_val(&format!("Invalid JSON: {}", e)))?;
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
trace
.attributes
.get(case_id_key)
.and_then(|v| v.as_string())
.map_or(false, |id| keep_ids.contains(id))
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_traces_starting_with_sequence(
log_handle: &str,
sequence_json: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
let sequence: Vec<String> = serde_json::from_str(sequence_json)
.map_err(|e| crate::error::js_val(&format!("Invalid JSON: {}", e)))?;
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
if trace.events.len() < sequence.len() {
return false;
}
trace
.events
.iter()
.take(sequence.len())
.enumerate()
.all(|(i, e)| {
e.attributes
.get(activity_key)
.and_then(|v| v.as_string())
.map_or(false, |act| act == sequence[i])
})
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}
#[wasm_bindgen]
pub fn filter_traces_ending_with_sequence(
log_handle: &str,
sequence_json: &str,
activity_key: &str,
) -> Result<JsValue, JsValue> {
let sequence: Vec<String> = serde_json::from_str(sequence_json)
.map_err(|e| crate::error::js_val(&format!("Invalid JSON: {}", e)))?;
get_or_init_state().with_object(log_handle, |obj| match obj {
Some(StoredObject::EventLog(log)) => {
let mut out = EventLog::new();
out.attributes = log.attributes.clone();
out.traces = log
.traces
.iter()
.filter(|trace| {
if trace.events.len() < sequence.len() {
return false;
}
let offset = trace.events.len() - sequence.len();
trace.events.iter().skip(offset).enumerate().all(|(i, e)| {
e.attributes
.get(activity_key)
.and_then(|v| v.as_string())
.map_or(false, |act| act == sequence[i])
})
})
.cloned()
.collect();
store_filtered(out)
}
Some(_) => Err(crate::error::js_val("Handle is not an EventLog")),
None => Err(crate::error::js_val("EventLog handle not found")),
})
}