feat: generic calculations (#4794)

* feat: add generic calculations

* chore: remove row count at bottom of grid

* fix: code review
This commit is contained in:
Mathias Mogensen
2024-03-05 19:16:56 +01:00
committed by GitHub
parent 3b0d82287d
commit 66aea29ab7
15 changed files with 222 additions and 127 deletions

View File

@ -6,7 +6,7 @@ use std::{
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{impl_into_calculation_type, services::calculations::Calculation};
use crate::{entities::FieldType, impl_into_calculation_type, services::calculations::Calculation};
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct CalculationPB {
@ -56,10 +56,13 @@ impl std::convert::From<&Arc<Calculation>> for CalculationPB {
pub enum CalculationType {
#[default]
Average = 0, // Number
Max = 1, // Number
Median = 2, // Number
Min = 3, // Number
Sum = 4, // Number
Max = 1, // Number
Median = 2, // Number
Min = 3, // Number
Sum = 4, // Number
Count = 5, // All
CountEmpty = 6, // All
CountNonEmpty = 7, // All
}
impl Display for CalculationType {
@ -102,6 +105,23 @@ impl From<&CalculationType> for i64 {
}
}
impl CalculationType {
pub fn is_allowed(&self, field_type: FieldType) -> bool {
match self {
// Number fields only
CalculationType::Max
| CalculationType::Min
| CalculationType::Average
| CalculationType::Median
| CalculationType::Sum => {
matches!(field_type, FieldType::Number)
},
// All fields
CalculationType::Count | CalculationType::CountEmpty | CalculationType::CountNonEmpty => true,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedCalculationsPB {
#[pb(index = 1)]

View File

@ -55,6 +55,9 @@ macro_rules! impl_into_calculation_type {
2 => CalculationType::Median,
3 => CalculationType::Min,
4 => CalculationType::Sum,
5 => CalculationType::Count,
6 => CalculationType::CountEmpty,
7 => CalculationType::CountNonEmpty,
_ => {
tracing::error!("🔴 Can't parse CalculationType from value: {}", ty);
CalculationType::Average

View File

@ -23,6 +23,7 @@ pub trait CalculationsDelegate: Send + Sync + 'static {
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
fn get_field(&self, field_id: &str) -> Option<Field>;
fn get_calculation(&self, view_id: &str, field_id: &str) -> Fut<Option<Arc<Calculation>>>;
fn get_all_calculations(&self, view_id: &str) -> Fut<Arc<Vec<Arc<Calculation>>>>;
fn update_calculation(&self, view_id: &str, calculation: Calculation);
fn remove_calculation(&self, view_id: &str, calculation_id: &str);
}
@ -76,7 +77,7 @@ impl CalculationsController {
}
}
#[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))]
#[tracing::instrument(name = "schedule_calculation_task", level = "trace", skip(self))]
async fn gen_task(&self, task_type: CalculationEvent, qos: QualityOfService) {
let task_id = self.task_scheduler.read().await.next_task_id();
let task = Task::new(
@ -89,10 +90,10 @@ impl CalculationsController {
}
#[tracing::instrument(
name = "process_filter_task",
name = "process_calculation_task",
level = "trace",
skip_all,
fields(filter_result),
fields(calculation_result),
err
)]
pub async fn process(&self, predicate: &str) -> FlowyResult<()> {
@ -160,7 +161,8 @@ impl CalculationsController {
.await;
if let Some(calculation) = calculation {
if new_field_type != FieldType::Number {
let calc_type: CalculationType = calculation.calculation_type.into();
if !calc_type.is_allowed(new_field_type) {
self
.delegate
.remove_calculation(&self.view_id, &calculation.id);
@ -228,6 +230,19 @@ impl CalculationsController {
let cells = row.cells.iter();
let mut updates = vec![];
// In case there are calculations where empty cells are counted
// as a contribution to the value.
if cells.len() == 0 {
let calculations = self.delegate.get_all_calculations(&self.view_id).await;
for calculation in calculations.iter() {
let update = self.get_updated_calculation(calculation.clone()).await;
if let Some(update) = update {
updates.push(CalculationPB::from(&update));
self.delegate.update_calculation(&self.view_id, update);
}
}
}
// Iterate each cell in the row
for cell in cells {
let field_id = cell.0;
@ -260,17 +275,13 @@ impl CalculationsController {
.await;
let field = self.delegate.get_field(&calculation.field_id)?;
if field_cells.is_empty() {
return Some(calculation.with_value(String::new()));
} else {
let value =
self
.calculations_service
.calculate(&field, calculation.calculation_type, field_cells);
let value =
self
.calculations_service
.calculate(&field, calculation.calculation_type, field_cells);
if value != calculation.value {
return Some(calculation.with_value(value));
}
if value != calculation.value {
return Some(calculation.with_value(value));
}
None

View File

@ -26,6 +26,9 @@ impl CalculationsService {
CalculationType::Median => self.calculate_median(field, row_cells),
CalculationType::Min => self.calculate_min(field, row_cells),
CalculationType::Sum => self.calculate_sum(field, row_cells),
CalculationType::Count => self.calculate_count(row_cells),
CalculationType::CountEmpty => self.calculate_count_empty(row_cells),
CalculationType::CountNonEmpty => self.calculate_count_non_empty(row_cells),
}
}
@ -62,7 +65,7 @@ impl CalculationsService {
if !values.is_empty() {
format!("{:.5}", Self::median(&values))
} else {
"".to_owned()
String::new()
}
}
@ -89,7 +92,7 @@ impl CalculationsService {
}
}
"".to_owned()
String::new()
}
fn calculate_max(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
@ -105,7 +108,7 @@ impl CalculationsService {
}
}
"".to_owned()
String::new()
}
fn calculate_sum(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
@ -114,7 +117,45 @@ impl CalculationsService {
if !values.is_empty() {
format!("{:.5}", values.iter().sum::<f64>())
} else {
"".to_owned()
String::new()
}
}
fn calculate_count(&self, row_cells: Vec<Arc<RowCell>>) -> String {
if !row_cells.is_empty() {
format!("{}", row_cells.len())
} else {
String::new()
}
}
fn calculate_count_empty(&self, row_cells: Vec<Arc<RowCell>>) -> String {
if !row_cells.is_empty() {
format!(
"{}",
row_cells
.iter()
.filter(|c| c.is_none())
.collect::<Vec<_>>()
.len()
)
} else {
String::new()
}
}
fn calculate_count_non_empty(&self, row_cells: Vec<Arc<RowCell>>) -> String {
if !row_cells.is_empty() {
format!(
"{}",
row_cells
.iter()
.filter(|c| c.is_some())
.collect::<Vec<_>>()
.len()
)
} else {
String::new()
}
}

View File

@ -66,4 +66,9 @@ impl CalculationsDelegate for DatabaseViewCalculationsDelegateImpl {
fn remove_calculation(&self, view_id: &str, calculation_id: &str) {
self.0.remove_calculation(view_id, calculation_id)
}
fn get_all_calculations(&self, view_id: &str) -> Fut<Arc<Vec<Arc<Calculation>>>> {
let calculations = Arc::new(self.0.get_all_calculations(view_id));
to_fut(async move { calculations })
}
}

View File

@ -161,14 +161,14 @@ impl DatabaseViewEditor {
.payload(changes)
.send();
self
.gen_did_create_row_view_tasks(row_detail.row.id.clone())
.gen_did_create_row_view_tasks(row_detail.row.clone())
.await;
}
pub async fn v_did_duplicate_row(&self, row_detail: &RowDetail) {
self
.calculations_controller
.did_receive_row_changed(row_detail.clone().row)
.did_receive_row_changed(row_detail.row.clone())
.await;
}
@ -1090,7 +1090,7 @@ impl DatabaseViewEditor {
sort_controller
.read()
.await
.did_receive_row_changed(row_id)
.did_receive_row_changed(row_id.clone())
.await;
}
if let Some(calculations_controller) = weak_calculations_controller.upgrade() {
@ -1101,11 +1101,20 @@ impl DatabaseViewEditor {
});
}
async fn gen_did_create_row_view_tasks(&self, row_id: RowId) {
async fn gen_did_create_row_view_tasks(&self, row: Row) {
let weak_sort_controller = Arc::downgrade(&self.sort_controller);
let weak_calculations_controller = Arc::downgrade(&self.calculations_controller);
af_spawn(async move {
if let Some(sort_controller) = weak_sort_controller.upgrade() {
sort_controller.read().await.did_create_row(row_id).await;
sort_controller
.read()
.await
.did_create_row(row.id.clone())
.await;
}
if let Some(calculations_controller) = weak_calculations_controller.upgrade() {
calculations_controller.did_receive_row_changed(row).await;
}
});
}