use std::fmt;
use crate::diagnostics::Diagnostic;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum AnalyticsDiagnosticKind {
MissingHistory,
UnclassifiedEvent,
MissingConversion,
InvalidPeriod,
ClosedSnapshot,
ProjectionUnknown,
}
impl AnalyticsDiagnosticKind {
pub const ALL: [AnalyticsDiagnosticKind; 6] = [
AnalyticsDiagnosticKind::MissingHistory,
AnalyticsDiagnosticKind::UnclassifiedEvent,
AnalyticsDiagnosticKind::MissingConversion,
AnalyticsDiagnosticKind::InvalidPeriod,
AnalyticsDiagnosticKind::ClosedSnapshot,
AnalyticsDiagnosticKind::ProjectionUnknown,
];
pub const fn as_str(self) -> &'static str {
match self {
AnalyticsDiagnosticKind::MissingHistory => "missing_history",
AnalyticsDiagnosticKind::UnclassifiedEvent => "unclassified_event",
AnalyticsDiagnosticKind::MissingConversion => "missing_conversion",
AnalyticsDiagnosticKind::InvalidPeriod => "invalid_period",
AnalyticsDiagnosticKind::ClosedSnapshot => "closed_snapshot",
AnalyticsDiagnosticKind::ProjectionUnknown => "projection_unknown",
}
}
pub fn parse(raw: &str) -> Option<Self> {
Self::ALL
.into_iter()
.find(|kind| kind.as_str() == raw.trim())
}
pub const fn default_remedy(self) -> Option<&'static str> {
match self {
AnalyticsDiagnosticKind::MissingHistory => Some(
"widen the period with an earlier `--from` so the recorded events that predate \
it are included, or read the coverage marker beside the value",
),
AnalyticsDiagnosticKind::UnclassifiedEvent => None,
AnalyticsDiagnosticKind::MissingConversion => Some(
"choose `--mode count`, or declare the unit under `[kanban.<board>.wip.units]` \
and state the estimate in it",
),
AnalyticsDiagnosticKind::InvalidPeriod => {
Some("pass a bounded period, or record at least one dated event to bound it")
}
AnalyticsDiagnosticKind::ClosedSnapshot => None,
AnalyticsDiagnosticKind::ProjectionUnknown => Some(
"record at least one completion inside the period before asking for a forecast",
),
}
}
}
impl fmt::Display for AnalyticsDiagnosticKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub struct AnalyticsDiagnostic {
pub kind: AnalyticsDiagnosticKind,
pub scope: String,
pub date: Option<String>,
pub event_id: Option<String>,
pub message: String,
pub remedy: Option<String>,
}
impl AnalyticsDiagnostic {
pub fn new(
kind: AnalyticsDiagnosticKind,
scope: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
kind,
scope: scope.into(),
date: None,
event_id: None,
message: message.into(),
remedy: kind.default_remedy().map(str::to_string),
}
}
pub fn on(mut self, date: impl Into<String>) -> Self {
self.date = Some(date.into());
self
}
pub fn about_event(mut self, event_id: impl Into<String>) -> Self {
self.event_id = Some(event_id.into());
self
}
pub fn with_remedy(mut self, remedy: impl Into<String>) -> Self {
self.remedy = Some(remedy.into());
self
}
pub fn render(&self) -> String {
let mut diagnostic = Diagnostic::warning("E-tix-analytics", self.message.clone())
.with_note(format!("{}: {}", self.kind.as_str(), self.scope));
if let Some(date) = self.date.as_deref() {
diagnostic = diagnostic.with_note(format!("date: {date}"));
}
if let Some(event_id) = self.event_id.as_deref() {
diagnostic = diagnostic.with_note(format!("event: {event_id}"));
}
match self.remedy.as_deref() {
Some(remedy) => diagnostic.with_help(remedy).render(),
None => diagnostic.render(),
}
}
}
impl fmt::Display for AnalyticsDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.kind, self.message)
}
}