better charts

This commit is contained in:
Filipriec
2026-09-16 16:09:55 +02:00
parent 7b190b4455
commit 25ef91f221
10 changed files with 136 additions and 3 deletions

View File

@@ -8,6 +8,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// here makes the root common crate usable by TUI and Tauri alike.
.type_attribute(".komp_ac.accounting", serde)
.type_attribute(".komp_ac.analytics", serde)
.field_attribute(".komp_ac.analytics.ReportDashboardFilter.primary", "#[serde(default)]")
.type_attribute(".komp_ac.auth", serde)
.type_attribute(".komp_ac.common", serde)
.type_attribute(".komp_ac.document_data", serde)

View File

@@ -281,6 +281,28 @@ message ReportFilterTarget {
message ReportDashboardFilter {
ReportParameter parameter = 1;
repeated ReportFilterTarget targets = 2;
bool primary = 3;
}
enum ReportPeriodBoundary {
REPORT_PERIOD_BOUNDARY_UNSPECIFIED = 0;
REPORT_PERIOD_BOUNDARY_INCLUSIVE = 1;
REPORT_PERIOD_BOUNDARY_EXCLUSIVE = 2;
}
message ReportDateRange {
string start_filter = 1;
string end_filter = 2;
ReportPeriodBoundary end_boundary = 3;
}
message ReportPeriod {
string date_basis = 1;
oneof selection {
ReportDateRange date_range = 2;
string accounting_period_filter = 3;
string as_of_filter = 4;
}
}
message ReportActionBinding {
@@ -353,6 +375,7 @@ message ReportDashboardDefinition {
repeated ReportGrant grants = 5;
uint32 refresh_seconds = 6;
repeated ReportDatasetVersionRef dataset_versions = 7;
ReportPeriod period = 8;
}
message ReportDatasetVersionRef {

Binary file not shown.

View File

@@ -252,6 +252,40 @@ pub struct ReportDashboardFilter {
pub parameter: ::core::option::Option<ReportParameter>,
#[prost(message, repeated, tag = "2")]
pub targets: ::prost::alloc::vec::Vec<ReportFilterTarget>,
#[prost(bool, tag = "3")]
#[serde(default)]
pub primary: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ReportDateRange {
#[prost(string, tag = "1")]
pub start_filter: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub end_filter: ::prost::alloc::string::String,
#[prost(enumeration = "ReportPeriodBoundary", tag = "3")]
pub end_boundary: i32,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ReportPeriod {
#[prost(string, tag = "1")]
pub date_basis: ::prost::alloc::string::String,
#[prost(oneof = "report_period::Selection", tags = "2, 3, 4")]
pub selection: ::core::option::Option<report_period::Selection>,
}
/// Nested message and enum types in `ReportPeriod`.
pub mod report_period {
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)]
pub enum Selection {
#[prost(message, tag = "2")]
DateRange(super::ReportDateRange),
#[prost(string, tag = "3")]
AccountingPeriodFilter(::prost::alloc::string::String),
#[prost(string, tag = "4")]
AsOfFilter(::prost::alloc::string::String),
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
@@ -390,6 +424,8 @@ pub struct ReportDashboardDefinition {
pub refresh_seconds: u32,
#[prost(message, repeated, tag = "7")]
pub dataset_versions: ::prost::alloc::vec::Vec<ReportDatasetVersionRef>,
#[prost(message, optional, tag = "8")]
pub period: ::core::option::Option<ReportPeriod>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
@@ -1085,6 +1121,36 @@ impl ReportExecutionPurpose {
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ReportPeriodBoundary {
Unspecified = 0,
Inclusive = 1,
Exclusive = 2,
}
impl ReportPeriodBoundary {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unspecified => "REPORT_PERIOD_BOUNDARY_UNSPECIFIED",
Self::Inclusive => "REPORT_PERIOD_BOUNDARY_INCLUSIVE",
Self::Exclusive => "REPORT_PERIOD_BOUNDARY_EXCLUSIVE",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"REPORT_PERIOD_BOUNDARY_UNSPECIFIED" => Some(Self::Unspecified),
"REPORT_PERIOD_BOUNDARY_INCLUSIVE" => Some(Self::Inclusive),
"REPORT_PERIOD_BOUNDARY_EXCLUSIVE" => Some(Self::Exclusive),
_ => None,
}
}
}
/// Generated client implementations.
pub mod analytics_service_client {
#![allow(

View File

@@ -263,6 +263,33 @@ fn validate_dashboard(dashboard: &ReportDashboardDefinition) -> Result<(), Strin
}
}
}
if let Some(period) = &dashboard.period {
nonempty(&period.date_basis, "Period date basis", 160)?;
let parameter = |key: &str, kind: ReportDataType| -> Result<(), String> {
let parameter = dashboard.filters.iter().filter_map(|filter| filter.parameter.as_ref())
.find(|parameter| parameter.key == key).ok_or("Period refers to an unknown filter")?;
let actual_kind: ReportDataType = enum_value(parameter.data_type, "period filter type")?;
if parameter.multiple || actual_kind != kind {
return Err("Period filters must have the declared single-value type".into());
}
if !parameter.choices.is_empty() || parameter.lookup.is_some() {
return Err("Period filters use the period selector instead of custom choices".into());
}
Ok(())
};
match period.selection.as_ref().ok_or("A period selection is required")? {
report_period::Selection::DateRange(range) => {
parameter(&range.start_filter, ReportDataType::Date)?;
parameter(&range.end_filter, ReportDataType::Date)?;
if range.start_filter == range.end_filter {
return Err("A period needs distinct start and end filters".into());
}
let _: ReportPeriodBoundary = enum_value(range.end_boundary, "period end boundary")?;
}
report_period::Selection::AccountingPeriodFilter(key) => parameter(key, ReportDataType::Integer)?,
report_period::Selection::AsOfFilter(key) => parameter(key, ReportDataType::Date)?,
}
}
let mut panels = HashSet::new();
for panel in &dashboard.panels {
nonempty(&panel.id, "Panel ID", 64)?;