mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: timer field (#5349)
* feat: wip timer field * feat: timer field fixing errors * feat: wip timer field frontend * fix: parsing TimerCellDataPB * feat: parse time string to minutes * fix: don't allow none number input * fix: timer filter * style: cargo fmt * fix: clippy errors * refactor: rename field type timer to time * refactor: missed some variable and files to rename * style: cargo fmt fix * feat: format time field type data in frontend * style: fix cargo fmt * fix: fixes after merge --------- Co-authored-by: Mathias Mogensen <mathiasrieckm@gmail.com>
This commit is contained in:
committed by
GitHub
parent
2d4300e931
commit
aa621a8d84
@ -450,6 +450,7 @@ pub enum FieldType {
|
||||
Relation = 10,
|
||||
Summary = 11,
|
||||
Translate = 12,
|
||||
Time = 13,
|
||||
}
|
||||
|
||||
impl Display for FieldType {
|
||||
@ -491,6 +492,7 @@ impl FieldType {
|
||||
FieldType::Relation => "Relation",
|
||||
FieldType::Summary => "Summarize",
|
||||
FieldType::Translate => "Translate",
|
||||
FieldType::Time => "Time",
|
||||
};
|
||||
s.to_string()
|
||||
}
|
||||
@ -547,6 +549,10 @@ impl FieldType {
|
||||
matches!(self, FieldType::Relation)
|
||||
}
|
||||
|
||||
pub fn is_time(&self) -> bool {
|
||||
matches!(self, FieldType::Time)
|
||||
}
|
||||
|
||||
pub fn can_be_group(&self) -> bool {
|
||||
self.is_select_option() || self.is_checkbox() || self.is_url()
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ mod number_filter;
|
||||
mod relation_filter;
|
||||
mod select_option_filter;
|
||||
mod text_filter;
|
||||
mod time_filter;
|
||||
mod util;
|
||||
|
||||
pub use checkbox_filter::*;
|
||||
@ -16,4 +17,5 @@ pub use number_filter::*;
|
||||
pub use relation_filter::*;
|
||||
pub use select_option_filter::*;
|
||||
pub use text_filter::*;
|
||||
pub use time_filter::*;
|
||||
pub use util::*;
|
||||
|
@ -0,0 +1,23 @@
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
use crate::entities::NumberFilterConditionPB;
|
||||
use crate::services::filter::ParseFilterData;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct TimeFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: NumberFilterConditionPB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl ParseFilterData for TimeFilterPB {
|
||||
fn parse(condition: u8, content: String) -> Self {
|
||||
TimeFilterPB {
|
||||
condition: NumberFilterConditionPB::try_from(condition)
|
||||
.unwrap_or(NumberFilterConditionPB::Equal),
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ use validator::Validate;
|
||||
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterPB, FieldType, NumberFilterPB, RelationFilterPB,
|
||||
SelectOptionFilterPB, TextFilterPB,
|
||||
SelectOptionFilterPB, TextFilterPB, TimeFilterPB,
|
||||
};
|
||||
use crate::services::filter::{Filter, FilterChangeset, FilterInner};
|
||||
|
||||
@ -109,6 +109,10 @@ impl From<&Filter> for FilterPB {
|
||||
.cloned::<TextFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
FieldType::Time => condition_and_content
|
||||
.cloned::<TimeFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
FieldType::Translate => condition_and_content
|
||||
.cloned::<TextFilterPB>()
|
||||
.unwrap()
|
||||
@ -160,6 +164,9 @@ impl TryFrom<FilterDataPB> for FilterInner {
|
||||
FieldType::Summary => {
|
||||
BoxAny::new(TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::Time => {
|
||||
BoxAny::new(TimeFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::Translate => {
|
||||
BoxAny::new(TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
|
@ -17,6 +17,7 @@ macro_rules! impl_into_field_type {
|
||||
10 => FieldType::Relation,
|
||||
11 => FieldType::Summary,
|
||||
12 => FieldType::Translate,
|
||||
13 => FieldType::Time,
|
||||
_ => {
|
||||
tracing::error!("🔴Can't parse FieldType from value: {}", ty);
|
||||
FieldType::RichText
|
||||
|
@ -6,6 +6,7 @@ mod relation_entities;
|
||||
mod select_option_entities;
|
||||
mod summary_entities;
|
||||
mod text_entities;
|
||||
mod time_entities;
|
||||
mod timestamp_entities;
|
||||
mod translate_entities;
|
||||
mod url_entities;
|
||||
@ -18,6 +19,7 @@ pub use relation_entities::*;
|
||||
pub use select_option_entities::*;
|
||||
pub use summary_entities::*;
|
||||
pub use text_entities::*;
|
||||
pub use time_entities::*;
|
||||
pub use timestamp_entities::*;
|
||||
pub use translate_entities::*;
|
||||
pub use url_entities::*;
|
||||
|
@ -0,0 +1,28 @@
|
||||
use crate::services::field::TimeTypeOption;
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
#[derive(Clone, Debug, Default, ProtoBuf)]
|
||||
pub struct TimeTypeOptionPB {
|
||||
#[pb(index = 1)]
|
||||
pub dummy: String,
|
||||
}
|
||||
|
||||
impl From<TimeTypeOption> for TimeTypeOptionPB {
|
||||
fn from(_data: TimeTypeOption) -> Self {
|
||||
Self {
|
||||
dummy: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimeTypeOptionPB> for TimeTypeOption {
|
||||
fn from(_data: TimeTypeOptionPB) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, ProtoBuf)]
|
||||
pub struct TimeCellDataPB {
|
||||
#[pb(index = 2)]
|
||||
pub time: i64,
|
||||
}
|
@ -222,7 +222,7 @@ impl<'a> CellBuilder<'a> {
|
||||
FieldType::RichText => {
|
||||
cells.insert(field_id, insert_text_cell(cell_str, field));
|
||||
},
|
||||
FieldType::Number => {
|
||||
FieldType::Number | FieldType::Time => {
|
||||
if let Ok(num) = cell_str.parse::<i64>() {
|
||||
cells.insert(field_id, insert_number_cell(num, field));
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ pub mod relation_type_option;
|
||||
pub mod selection_type_option;
|
||||
pub mod summary_type_option;
|
||||
pub mod text_type_option;
|
||||
pub mod time_type_option;
|
||||
pub mod timestamp_type_option;
|
||||
pub mod translate_type_option;
|
||||
mod type_option;
|
||||
@ -20,6 +21,7 @@ pub use number_type_option::*;
|
||||
pub use relation_type_option::*;
|
||||
pub use selection_type_option::*;
|
||||
pub use text_type_option::*;
|
||||
pub use time_type_option::*;
|
||||
pub use timestamp_type_option::*;
|
||||
pub use type_option::*;
|
||||
pub use type_option_cell::*;
|
||||
|
@ -79,13 +79,14 @@ impl CellDataDecoder for RichTextTypeOption {
|
||||
| FieldType::SingleSelect
|
||||
| FieldType::MultiSelect
|
||||
| FieldType::Checkbox
|
||||
| FieldType::URL => Some(StringCellData::from(stringify_cell(cell, field))),
|
||||
| FieldType::URL
|
||||
| FieldType::Summary
|
||||
| FieldType::Translate
|
||||
| FieldType::Time => Some(StringCellData::from(stringify_cell(cell, field))),
|
||||
FieldType::Checklist
|
||||
| FieldType::LastEditedTime
|
||||
| FieldType::CreatedTime
|
||||
| FieldType::Relation => None,
|
||||
FieldType::Summary => Some(StringCellData::from(stringify_cell(cell, field))),
|
||||
FieldType::Translate => Some(StringCellData::from(stringify_cell(cell, field))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
mod time;
|
||||
mod time_entities;
|
||||
mod time_filter;
|
||||
|
||||
pub use time::*;
|
||||
pub use time_entities::*;
|
@ -0,0 +1,115 @@
|
||||
use crate::entities::{TimeCellDataPB, TimeFilterPB};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
TimeCellData, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionCellDataSerde, TypeOptionTransform,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use flowy_error::FlowyResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct TimeTypeOption;
|
||||
|
||||
impl TypeOption for TimeTypeOption {
|
||||
type CellData = TimeCellData;
|
||||
type CellChangeset = TimeCellChangeset;
|
||||
type CellProtobufType = TimeCellDataPB;
|
||||
type CellFilter = TimeFilterPB;
|
||||
}
|
||||
|
||||
impl From<TypeOptionData> for TimeTypeOption {
|
||||
fn from(_data: TypeOptionData) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimeTypeOption> for TypeOptionData {
|
||||
fn from(_data: TimeTypeOption) -> Self {
|
||||
TypeOptionDataBuilder::new().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataSerde for TimeTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
) -> <Self as TypeOption>::CellProtobufType {
|
||||
if let Some(time) = cell_data.0 {
|
||||
return TimeCellDataPB { time };
|
||||
}
|
||||
TimeCellDataPB {
|
||||
time: i64::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
Ok(TimeCellData::from(cell))
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeTypeOption {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for TimeTypeOption {}
|
||||
|
||||
impl CellDataDecoder for TimeTypeOption {
|
||||
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
self.parse_cell(cell)
|
||||
}
|
||||
|
||||
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
|
||||
if let Some(time) = cell_data.0 {
|
||||
return time.to_string();
|
||||
}
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
|
||||
let time_cell_data = self.parse_cell(cell).ok()?;
|
||||
Some(time_cell_data.0.unwrap() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
pub type TimeCellChangeset = String;
|
||||
|
||||
impl CellDataChangeset for TimeTypeOption {
|
||||
fn apply_changeset(
|
||||
&self,
|
||||
changeset: <Self as TypeOption>::CellChangeset,
|
||||
_cell: Option<Cell>,
|
||||
) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
|
||||
let str = changeset.trim().to_string();
|
||||
let cell_data = TimeCellData(str.parse::<i64>().ok());
|
||||
|
||||
Ok((Cell::from(&cell_data), cell_data))
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataFilter for TimeTypeOption {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
filter: &<Self as TypeOption>::CellFilter,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
filter.is_visible(cell_data.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for TimeTypeOption {
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
let order = cell_data.0.cmp(&other_cell_data.0);
|
||||
sort_condition.evaluate_order(order)
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::field::{TypeOptionCellData, CELL_DATA};
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::rows::{new_cell_builder, Cell};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TimeCellData(pub Option<i64>);
|
||||
|
||||
impl TypeOptionCellData for TimeCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.0.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Cell> for TimeCellData {
|
||||
fn from(cell: &Cell) -> Self {
|
||||
Self(
|
||||
cell
|
||||
.get_str_value(CELL_DATA)
|
||||
.and_then(|data| data.parse::<i64>().ok()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<String> for TimeCellData {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s.trim().to_string().parse::<i64>().ok())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for TimeCellData {
|
||||
fn to_string(&self) -> String {
|
||||
if let Some(time) = self.0 {
|
||||
time.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TimeCellData> for Cell {
|
||||
fn from(data: &TimeCellData) -> Self {
|
||||
new_cell_builder(FieldType::Time)
|
||||
.insert_str_value(CELL_DATA, data.to_string())
|
||||
.build()
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::Cell;
|
||||
|
||||
use crate::entities::{NumberFilterConditionPB, TimeFilterPB};
|
||||
use crate::services::cell::insert_text_cell;
|
||||
use crate::services::filter::PreFillCellsWithFilter;
|
||||
|
||||
impl TimeFilterPB {
|
||||
pub fn is_visible(&self, cell_time: Option<i64>) -> bool {
|
||||
if self.content.is_empty() {
|
||||
match self.condition {
|
||||
NumberFilterConditionPB::NumberIsEmpty => {
|
||||
return cell_time.is_none();
|
||||
},
|
||||
NumberFilterConditionPB::NumberIsNotEmpty => {
|
||||
return cell_time.is_some();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if cell_time.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let time = cell_time.unwrap();
|
||||
let content_time = self.content.parse::<i64>().unwrap_or_default();
|
||||
match self.condition {
|
||||
NumberFilterConditionPB::Equal => time == content_time,
|
||||
NumberFilterConditionPB::NotEqual => time != content_time,
|
||||
NumberFilterConditionPB::GreaterThan => time > content_time,
|
||||
NumberFilterConditionPB::LessThan => time < content_time,
|
||||
NumberFilterConditionPB::GreaterThanOrEqualTo => time >= content_time,
|
||||
NumberFilterConditionPB::LessThanOrEqualTo => time <= content_time,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PreFillCellsWithFilter for TimeFilterPB {
|
||||
fn get_compliant_cell(&self, field: &Field) -> (Option<Cell>, bool) {
|
||||
let expected_decimal = || self.content.parse::<i64>().ok();
|
||||
|
||||
let text = match self.condition {
|
||||
NumberFilterConditionPB::Equal
|
||||
| NumberFilterConditionPB::GreaterThanOrEqualTo
|
||||
| NumberFilterConditionPB::LessThanOrEqualTo
|
||||
if !self.content.is_empty() =>
|
||||
{
|
||||
Some(self.content.clone())
|
||||
},
|
||||
NumberFilterConditionPB::GreaterThan if !self.content.is_empty() => {
|
||||
expected_decimal().map(|value| {
|
||||
let answer = value + 1;
|
||||
answer.to_string()
|
||||
})
|
||||
},
|
||||
NumberFilterConditionPB::LessThan if !self.content.is_empty() => {
|
||||
expected_decimal().map(|value| {
|
||||
let answer = value - 1;
|
||||
answer.to_string()
|
||||
})
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let open_after_create = matches!(self.condition, NumberFilterConditionPB::NumberIsNotEmpty);
|
||||
|
||||
// use `insert_text_cell` because self.content might not be a parsable i64.
|
||||
(text.map(|s| insert_text_cell(s, field)), open_after_create)
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ use flowy_error::FlowyResult;
|
||||
use crate::entities::{
|
||||
CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, FieldType,
|
||||
MultiSelectTypeOptionPB, NumberTypeOptionPB, RelationTypeOptionPB, RichTextTypeOptionPB,
|
||||
SingleSelectTypeOptionPB, SummarizationTypeOptionPB, TimestampTypeOptionPB,
|
||||
SingleSelectTypeOptionPB, SummarizationTypeOptionPB, TimeTypeOptionPB, TimestampTypeOptionPB,
|
||||
TranslateTypeOptionPB, URLTypeOptionPB,
|
||||
};
|
||||
use crate::services::cell::CellDataDecoder;
|
||||
@ -20,7 +20,7 @@ use crate::services::field::summary_type_option::summary::SummarizationTypeOptio
|
||||
use crate::services::field::translate_type_option::translate::TranslateTypeOption;
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RelationTypeOption,
|
||||
RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, URLTypeOption,
|
||||
RichTextTypeOption, SingleSelectTypeOption, TimeTypeOption, TimestampTypeOption, URLTypeOption,
|
||||
};
|
||||
use crate::services::filter::{ParseFilterData, PreFillCellsWithFilter};
|
||||
use crate::services::sort::SortCondition;
|
||||
@ -187,6 +187,7 @@ pub fn type_option_data_from_pb<T: Into<Bytes>>(
|
||||
FieldType::Summary => {
|
||||
SummarizationTypeOptionPB::try_from(bytes).map(|pb| SummarizationTypeOption::from(pb).into())
|
||||
},
|
||||
FieldType::Time => TimeTypeOptionPB::try_from(bytes).map(|pb| TimeTypeOption::from(pb).into()),
|
||||
FieldType::Translate => {
|
||||
TranslateTypeOptionPB::try_from(bytes).map(|pb| TranslateTypeOption::from(pb).into())
|
||||
},
|
||||
@ -257,6 +258,10 @@ pub fn type_option_to_pb(type_option: TypeOptionData, field_type: &FieldType) ->
|
||||
.try_into()
|
||||
.unwrap()
|
||||
},
|
||||
FieldType::Time => {
|
||||
let time_type_option: TimeTypeOption = type_option.into();
|
||||
TimeTypeOptionPB::from(time_type_option).try_into().unwrap()
|
||||
},
|
||||
FieldType::Translate => {
|
||||
let translate_type_option: TranslateTypeOption = type_option.into();
|
||||
TranslateTypeOptionPB::from(translate_type_option)
|
||||
@ -284,5 +289,6 @@ pub fn default_type_option_data_from_type(field_type: FieldType) -> TypeOptionDa
|
||||
FieldType::Relation => RelationTypeOption::default().into(),
|
||||
FieldType::Summary => SummarizationTypeOption::default().into(),
|
||||
FieldType::Translate => TranslateTypeOption::default().into(),
|
||||
FieldType::Time => TimeTypeOption.into(),
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ use crate::services::field::summary_type_option::summary::SummarizationTypeOptio
|
||||
use crate::services::field::translate_type_option::translate::TranslateTypeOption;
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOption, ChecklistTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
|
||||
RelationTypeOption, RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, TypeOption,
|
||||
TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
|
||||
TypeOptionTransform, URLTypeOption,
|
||||
RelationTypeOption, RichTextTypeOption, SingleSelectTypeOption, TimeTypeOption,
|
||||
TimestampTypeOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionCellDataSerde, TypeOptionTransform, URLTypeOption,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
@ -450,6 +450,16 @@ impl<'a> TypeOptionCellExt<'a> {
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::Time => self
|
||||
.field
|
||||
.get_type_option::<TimeTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
field_type,
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
}),
|
||||
FieldType::Translate => self
|
||||
.field
|
||||
.get_type_option::<TranslateTypeOption>(field_type)
|
||||
@ -563,6 +573,9 @@ fn get_type_option_transform_handler(
|
||||
},
|
||||
FieldType::Summary => Box::new(SummarizationTypeOption::from(type_option_data))
|
||||
as Box<dyn TypeOptionTransformHandler>,
|
||||
FieldType::Time => {
|
||||
Box::new(TimeTypeOption::from(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
},
|
||||
FieldType::Translate => {
|
||||
Box::new(TranslateTypeOption::from(type_option_data)) as Box<dyn TypeOptionTransformHandler>
|
||||
},
|
||||
|
@ -303,6 +303,10 @@ impl FilterController {
|
||||
let filter = condition_and_content.cloned::<ChecklistFilterPB>().unwrap();
|
||||
filter.get_compliant_cell(field)
|
||||
},
|
||||
FieldType::Time => {
|
||||
let filter = condition_and_content.cloned::<TimeFilterPB>().unwrap();
|
||||
filter.get_compliant_cell(field)
|
||||
},
|
||||
_ => (None, false),
|
||||
};
|
||||
|
||||
|
@ -12,6 +12,7 @@ use lib_infra::box_any::BoxAny;
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContent, DateFilterPB, FieldType, FilterType,
|
||||
InsertedRowPB, NumberFilterPB, RelationFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
TimeFilterPB,
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
|
||||
@ -282,6 +283,7 @@ impl FilterInner {
|
||||
FieldType::Relation => BoxAny::new(RelationFilterPB::parse(condition as u8, content)),
|
||||
FieldType::Summary => BoxAny::new(TextFilterPB::parse(condition as u8, content)),
|
||||
FieldType::Translate => BoxAny::new(TextFilterPB::parse(condition as u8, content)),
|
||||
FieldType::Time => BoxAny::new(TimeFilterPB::parse(condition as u8, content)),
|
||||
};
|
||||
|
||||
FilterInner::Data {
|
||||
@ -368,6 +370,10 @@ impl<'a> From<&'a Filter> for FilterMap {
|
||||
let filter = condition_and_content.cloned::<TextFilterPB>()?;
|
||||
(filter.condition as u8, filter.content)
|
||||
},
|
||||
FieldType::Time => {
|
||||
let filter = condition_and_content.cloned::<TimeFilterPB>()?;
|
||||
(filter.condition as u8, filter.content)
|
||||
},
|
||||
FieldType::Translate => {
|
||||
let filter = condition_and_content.cloned::<TextFilterPB>()?;
|
||||
(filter.condition as u8, filter.content)
|
||||
|
@ -4,7 +4,7 @@ use flowy_database2::entities::FieldType;
|
||||
use flowy_database2::services::field::{
|
||||
ChecklistCellChangeset, DateCellChangeset, DateCellData, MultiSelectTypeOption,
|
||||
RelationCellChangeset, SelectOptionCellChangeset, SingleSelectTypeOption, StringCellData,
|
||||
URLCellData,
|
||||
TimeCellData, URLCellData,
|
||||
};
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
@ -200,3 +200,20 @@ async fn update_updated_at_field_on_other_cell_update() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn time_cell_data_test() {
|
||||
let test = DatabaseCellTest::new().await;
|
||||
let time_field = test.get_first_field(FieldType::Time);
|
||||
let cells = test
|
||||
.editor
|
||||
.get_cells_for_field(&test.view_id, &time_field.id)
|
||||
.await;
|
||||
|
||||
if let Some(cell) = cells[0].cell.as_ref() {
|
||||
let cell = TimeCellData::from(cell);
|
||||
|
||||
assert!(cell.0.is_some());
|
||||
assert_eq!(cell.0.unwrap_or_default(), 75);
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,26 @@ async fn grid_create_field() {
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
||||
let (params, field) = create_time_field(&test.view_id());
|
||||
let scripts = vec![
|
||||
CreateField { params },
|
||||
AssertFieldTypeOptionEqual {
|
||||
field_index: test.field_count(),
|
||||
expected_type_option_data: field.get_any_type_option(field.field_type).unwrap(),
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
||||
let (params, field) = create_time_field(&test.view_id());
|
||||
let scripts = vec![
|
||||
CreateField { params },
|
||||
AssertFieldTypeOptionEqual {
|
||||
field_index: test.field_count(),
|
||||
expected_type_option_data: field.get_any_type_option(field.field_type).unwrap(),
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -4,7 +4,7 @@ use collab_database::views::OrderObjectPosition;
|
||||
use flowy_database2::entities::{CreateFieldParams, FieldType};
|
||||
use flowy_database2::services::field::{
|
||||
type_option_to_pb, DateFormat, DateTypeOption, FieldBuilder, RichTextTypeOption, SelectOption,
|
||||
SingleSelectTypeOption, TimeFormat, TimestampTypeOption,
|
||||
SingleSelectTypeOption, TimeFormat, TimeTypeOption, TimestampTypeOption,
|
||||
};
|
||||
|
||||
pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, Field) {
|
||||
@ -98,3 +98,21 @@ pub fn create_timestamp_field(grid_id: &str, field_type: FieldType) -> (CreateFi
|
||||
};
|
||||
(params, field)
|
||||
}
|
||||
|
||||
pub fn create_time_field(grid_id: &str) -> (CreateFieldParams, Field) {
|
||||
let field_type = FieldType::Time;
|
||||
let type_option = TimeTypeOption;
|
||||
let text_field = FieldBuilder::new(field_type, type_option.clone())
|
||||
.name("Time field")
|
||||
.build();
|
||||
|
||||
let type_option_data = type_option_to_pb(type_option.into(), &field_type).to_vec();
|
||||
let params = CreateFieldParams {
|
||||
view_id: grid_id.to_owned(),
|
||||
field_type,
|
||||
type_option_data: Some(type_option_data),
|
||||
field_name: None,
|
||||
position: OrderObjectPosition::default(),
|
||||
};
|
||||
(params, text_field)
|
||||
}
|
||||
|
@ -6,3 +6,4 @@ mod number_filter_test;
|
||||
mod script;
|
||||
mod select_option_filter_test;
|
||||
mod text_filter_test;
|
||||
mod time_filter_test;
|
||||
|
@ -0,0 +1,121 @@
|
||||
use flowy_database2::entities::{FieldType, NumberFilterConditionPB, TimeFilterPB};
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::database::filter_test::script::FilterScript::*;
|
||||
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_time_is_equal_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_details.len();
|
||||
let expected = 1;
|
||||
let scripts = vec![
|
||||
CreateDataFilter {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::Time,
|
||||
data: BoxAny::new(TimeFilterPB {
|
||||
condition: NumberFilterConditionPB::Equal,
|
||||
content: "75".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
showing_num_of_rows: 0,
|
||||
hiding_num_of_rows: row_count - expected,
|
||||
}),
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_time_is_less_than_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_details.len();
|
||||
let expected = 1;
|
||||
let scripts = vec![
|
||||
CreateDataFilter {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::Time,
|
||||
|
||||
data: BoxAny::new(TimeFilterPB {
|
||||
condition: NumberFilterConditionPB::LessThan,
|
||||
content: "80".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
showing_num_of_rows: 0,
|
||||
hiding_num_of_rows: row_count - expected,
|
||||
}),
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_time_is_less_than_or_equal_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_details.len();
|
||||
let expected = 1;
|
||||
let scripts = vec![
|
||||
CreateDataFilter {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::Time,
|
||||
data: BoxAny::new(TimeFilterPB {
|
||||
condition: NumberFilterConditionPB::LessThanOrEqualTo,
|
||||
content: "75".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
showing_num_of_rows: 0,
|
||||
hiding_num_of_rows: row_count - expected,
|
||||
}),
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_time_is_empty_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_details.len();
|
||||
let expected = 6;
|
||||
let scripts = vec![
|
||||
CreateDataFilter {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::Time,
|
||||
data: BoxAny::new(TimeFilterPB {
|
||||
condition: NumberFilterConditionPB::NumberIsEmpty,
|
||||
content: "".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
showing_num_of_rows: 0,
|
||||
hiding_num_of_rows: row_count - expected,
|
||||
}),
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_time_is_not_empty_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_details.len();
|
||||
let expected = 1;
|
||||
let scripts = vec![
|
||||
CreateDataFilter {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::Time,
|
||||
data: BoxAny::new(TimeFilterPB {
|
||||
condition: NumberFilterConditionPB::NumberIsNotEmpty,
|
||||
content: "".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
showing_num_of_rows: 0,
|
||||
hiding_num_of_rows: row_count - expected,
|
||||
}),
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -134,6 +134,12 @@ pub fn make_test_board() -> DatabaseData {
|
||||
.build();
|
||||
fields.push(relation_field);
|
||||
},
|
||||
FieldType::Time => {
|
||||
let time_field = FieldBuilder::from_field_type(field_type)
|
||||
.name("Estimated time")
|
||||
.build();
|
||||
fields.push(time_field);
|
||||
},
|
||||
FieldType::Translate => {},
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use flowy_database2::services::field::translate_type_option::translate::Translat
|
||||
use flowy_database2::services::field::{
|
||||
ChecklistTypeOption, DateFormat, DateTypeOption, FieldBuilder, MultiSelectTypeOption,
|
||||
NumberFormat, NumberTypeOption, RelationTypeOption, SelectOption, SelectOptionColor,
|
||||
SingleSelectTypeOption, TimeFormat, TimestampTypeOption,
|
||||
SingleSelectTypeOption, TimeFormat, TimeTypeOption, TimestampTypeOption,
|
||||
};
|
||||
use flowy_database2::services::field_settings::default_field_settings_for_fields;
|
||||
|
||||
@ -133,6 +133,13 @@ pub fn make_test_grid() -> DatabaseData {
|
||||
.build();
|
||||
fields.push(relation_field);
|
||||
},
|
||||
FieldType::Time => {
|
||||
let type_option = TimeTypeOption;
|
||||
let time_field = FieldBuilder::new(field_type, type_option)
|
||||
.name("Estimated time")
|
||||
.build();
|
||||
fields.push(time_field);
|
||||
},
|
||||
FieldType::Translate => {
|
||||
let type_option = TranslateTypeOption {
|
||||
auto_fill: false,
|
||||
@ -168,6 +175,7 @@ pub fn make_test_grid() -> DatabaseData {
|
||||
FieldType::Checklist => {
|
||||
row_builder.insert_checklist_cell(vec![("First thing".to_string(), false)])
|
||||
},
|
||||
FieldType::Time => row_builder.insert_time_cell(75),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ async fn export_and_then_import_meta_csv_test() {
|
||||
FieldType::CreatedTime => {},
|
||||
FieldType::Relation => {},
|
||||
FieldType::Summary => {},
|
||||
FieldType::Time => {},
|
||||
FieldType::Translate => {},
|
||||
}
|
||||
} else {
|
||||
@ -167,6 +168,7 @@ async fn history_database_import_test() {
|
||||
FieldType::CreatedTime => {},
|
||||
FieldType::Relation => {},
|
||||
FieldType::Summary => {},
|
||||
FieldType::Time => {},
|
||||
FieldType::Translate => {},
|
||||
}
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user