mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: group by date (#2641)
* feat: group by date * test: added more tests for group by date * fix: print month in abbrev format * chore: adapt group event changes * style: remove comment * fix: change date on changing group * fix: dont count time in relative group * fix: check beginning of month is within 30 days * refactor: unify group id date format --------- Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
9643315d5f
commit
2f8edf1fd1
@ -32,6 +32,7 @@ class FieldInfo with _$FieldInfo {
|
|||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
case FieldType.MultiSelect:
|
case FieldType.MultiSelect:
|
||||||
case FieldType.SingleSelect:
|
case FieldType.SingleSelect:
|
||||||
|
case FieldType.DateTime:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -2,7 +2,7 @@ use crate::services::group::Group;
|
|||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||||
pub struct UrlGroupConfigurationPB {
|
pub struct URLGroupConfigurationPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
hide_empty: bool,
|
hide_empty: bool,
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
|
||||||
|
use chrono_tz::Tz;
|
||||||
|
use collab::core::any_map::AnyMapExtension;
|
||||||
|
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||||
|
use collab_database::rows::Cell;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
|
|
||||||
use crate::entities::{DateCellDataPB, DateFilterPB, FieldType};
|
use crate::entities::{DateCellDataPB, DateFilterPB, FieldType};
|
||||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
@ -6,16 +18,6 @@ use crate::services::field::{
|
|||||||
TypeOptionTransform,
|
TypeOptionTransform,
|
||||||
};
|
};
|
||||||
use crate::services::sort::SortCondition;
|
use crate::services::sort::SortCondition;
|
||||||
use chrono::format::strftime::StrftimeItems;
|
|
||||||
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
|
|
||||||
use chrono_tz::Tz;
|
|
||||||
use collab::core::any_map::AnyMapExtension;
|
|
||||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
|
||||||
use collab_database::rows::Cell;
|
|
||||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
/// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime].
|
/// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime].
|
||||||
/// So, storing the field type is necessary to distinguish the field type.
|
/// So, storing the field type is necessary to distinguish the field type.
|
||||||
@ -121,9 +123,9 @@ impl DateTypeOption {
|
|||||||
let date_time = DateTime::<Local>::from_utc(naive, offset);
|
let date_time = DateTime::<Local>::from_utc(naive, offset);
|
||||||
|
|
||||||
let fmt = self.date_format.format_str();
|
let fmt = self.date_format.format_str();
|
||||||
let date = format!("{}", date_time.format_with_items(StrftimeItems::new(fmt)));
|
let date = format!("{}", date_time.format(fmt));
|
||||||
let fmt = self.time_format.format_str();
|
let fmt = self.time_format.format_str();
|
||||||
let time = format!("{}", date_time.format_with_items(StrftimeItems::new(fmt)));
|
let time = format!("{}", date_time.format(fmt));
|
||||||
|
|
||||||
(date, time)
|
(date, time)
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
#![allow(clippy::upper_case_acronyms)]
|
#![allow(clippy::upper_case_acronyms)]
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use collab::core::any_map::AnyMapExtension;
|
use collab::core::any_map::AnyMapExtension;
|
||||||
use collab_database::rows::{new_cell_builder, Cell};
|
use collab_database::rows::{new_cell_builder, Cell};
|
||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
use flowy_error::{internal_error, FlowyResult};
|
use flowy_error::{internal_error, FlowyResult};
|
||||||
@ -81,6 +80,15 @@ impl From<&Cell> for DateCellData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&DateCellDataPB> for DateCellData {
|
||||||
|
fn from(data: &DateCellDataPB) -> Self {
|
||||||
|
Self {
|
||||||
|
timestamp: Some(data.timestamp),
|
||||||
|
include_time: data.include_time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrapper for DateCellData that also contains the field type.
|
/// Wrapper for DateCellData that also contains the field type.
|
||||||
/// Handy struct to use when you need to convert a DateCellData to a Cell.
|
/// Handy struct to use when you need to convert a DateCellData to a Cell.
|
||||||
pub struct DateCellDataWrapper {
|
pub struct DateCellDataWrapper {
|
||||||
|
@ -379,6 +379,10 @@ where
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_setting_content(&self) -> String {
|
||||||
|
self.setting.content.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `mut_configuration_fn`: mutate the [GroupSetting] and return whether the [GroupSetting] is
|
/// * `mut_configuration_fn`: mutate the [GroupSetting] and return whether the [GroupSetting] is
|
||||||
|
@ -0,0 +1,598 @@
|
|||||||
|
use crate::entities::{
|
||||||
|
DateCellDataPB, FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB,
|
||||||
|
RowMetaPB,
|
||||||
|
};
|
||||||
|
use crate::services::cell::insert_date_cell;
|
||||||
|
use crate::services::field::{DateCellData, DateCellDataParser, DateTypeOption};
|
||||||
|
use crate::services::group::action::GroupCustomize;
|
||||||
|
use crate::services::group::configuration::GroupContext;
|
||||||
|
use crate::services::group::controller::{
|
||||||
|
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
|
||||||
|
};
|
||||||
|
use crate::services::group::{
|
||||||
|
make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group,
|
||||||
|
};
|
||||||
|
use chrono::{
|
||||||
|
DateTime, Datelike, Days, Duration, Local, NaiveDate, NaiveDateTime, Offset, TimeZone,
|
||||||
|
};
|
||||||
|
use chrono_tz::Tz;
|
||||||
|
use collab_database::database::timestamp;
|
||||||
|
use collab_database::fields::Field;
|
||||||
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
|
use flowy_error::FlowyResult;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
use std::format;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub trait GroupConfigurationContentSerde: Sized + Send + Sync {
|
||||||
|
fn from_json(s: &str) -> Result<Self, serde_json::Error>;
|
||||||
|
fn to_json(&self) -> Result<String, serde_json::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
|
pub struct DateGroupConfiguration {
|
||||||
|
pub hide_empty: bool,
|
||||||
|
pub condition: DateCondition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GroupConfigurationContentSerde for DateGroupConfiguration {
|
||||||
|
fn from_json(s: &str) -> Result<Self, serde_json::Error> {
|
||||||
|
serde_json::from_str(s)
|
||||||
|
}
|
||||||
|
fn to_json(&self) -> Result<String, serde_json::Error> {
|
||||||
|
serde_json::to_string(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum DateCondition {
|
||||||
|
Relative = 0,
|
||||||
|
Day = 1,
|
||||||
|
Week = 2,
|
||||||
|
Month = 3,
|
||||||
|
Year = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for DateCondition {
|
||||||
|
fn default() -> Self {
|
||||||
|
DateCondition::Relative
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DateGroupController = BaseGroupController<
|
||||||
|
DateGroupConfiguration,
|
||||||
|
DateTypeOption,
|
||||||
|
DateGroupGenerator,
|
||||||
|
DateCellDataParser,
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub type DateGroupContext = GroupContext<DateGroupConfiguration>;
|
||||||
|
|
||||||
|
impl GroupCustomize for DateGroupController {
|
||||||
|
type CellData = DateCellDataPB;
|
||||||
|
|
||||||
|
fn placeholder_cell(&self) -> Option<Cell> {
|
||||||
|
Some(
|
||||||
|
new_cell_builder(FieldType::DateTime)
|
||||||
|
.insert_str_value("data", "")
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
|
||||||
|
content
|
||||||
|
== group_id(
|
||||||
|
&cell_data.into(),
|
||||||
|
self.type_option.as_ref(),
|
||||||
|
&self.context.get_setting_content(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_or_delete_group_when_cell_changed(
|
||||||
|
&mut self,
|
||||||
|
row_detail: &RowDetail,
|
||||||
|
old_cell_data: Option<&Self::CellData>,
|
||||||
|
cell_data: &Self::CellData,
|
||||||
|
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
|
||||||
|
let setting_content = self.context.get_setting_content();
|
||||||
|
let mut inserted_group = None;
|
||||||
|
if self
|
||||||
|
.context
|
||||||
|
.get_group(&group_id(
|
||||||
|
&cell_data.into(),
|
||||||
|
self.type_option.as_ref(),
|
||||||
|
&setting_content,
|
||||||
|
))
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
let group = make_group_from_date_cell(
|
||||||
|
&cell_data.into(),
|
||||||
|
self.type_option.as_ref(),
|
||||||
|
&setting_content,
|
||||||
|
);
|
||||||
|
let mut new_group = self.context.add_new_group(group)?;
|
||||||
|
new_group.group.rows.push(RowMetaPB::from(&row_detail.meta));
|
||||||
|
inserted_group = Some(new_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the old group if there are no rows in that group
|
||||||
|
let deleted_group = match old_cell_data.and_then(|old_cell_data| {
|
||||||
|
self.context.get_group(&group_id(
|
||||||
|
&old_cell_data.into(),
|
||||||
|
self.type_option.as_ref(),
|
||||||
|
&setting_content,
|
||||||
|
))
|
||||||
|
}) {
|
||||||
|
None => None,
|
||||||
|
Some((_, group)) => {
|
||||||
|
if group.rows.len() == 1 {
|
||||||
|
Some(group.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let deleted_group = match deleted_group {
|
||||||
|
None => None,
|
||||||
|
Some(group) => {
|
||||||
|
self.context.delete_group(&group.id)?;
|
||||||
|
Some(GroupPB::from(group.clone()))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((inserted_group, deleted_group))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_or_remove_row_when_cell_changed(
|
||||||
|
&mut self,
|
||||||
|
row_detail: &RowDetail,
|
||||||
|
cell_data: &Self::CellData,
|
||||||
|
) -> Vec<GroupRowsNotificationPB> {
|
||||||
|
let mut changesets = vec![];
|
||||||
|
let setting_content = self.context.get_setting_content();
|
||||||
|
self.context.iter_mut_status_groups(|group| {
|
||||||
|
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||||
|
if group.id
|
||||||
|
== group_id(
|
||||||
|
&cell_data.into(),
|
||||||
|
self.type_option.as_ref(),
|
||||||
|
&setting_content,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if !group.contains_row(&row_detail.row.id) {
|
||||||
|
changeset
|
||||||
|
.inserted_rows
|
||||||
|
.push(InsertedRowPB::new(RowMetaPB::from(&row_detail.meta)));
|
||||||
|
group.add_row(row_detail.clone());
|
||||||
|
}
|
||||||
|
} else if group.contains_row(&row_detail.row.id) {
|
||||||
|
group.remove_row(&row_detail.row.id);
|
||||||
|
changeset
|
||||||
|
.deleted_rows
|
||||||
|
.push(row_detail.row.id.clone().into_inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !changeset.is_empty() {
|
||||||
|
changesets.push(changeset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
changesets
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_row(&mut self, row: &Row, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
|
||||||
|
let mut changesets = vec![];
|
||||||
|
self.context.iter_mut_groups(|group| {
|
||||||
|
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||||
|
if group.contains_row(&row.id) {
|
||||||
|
group.remove_row(&row.id);
|
||||||
|
changeset.deleted_rows.push(row.id.clone().into_inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !changeset.is_empty() {
|
||||||
|
changesets.push(changeset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
changesets
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_row(
|
||||||
|
&mut self,
|
||||||
|
_cell_data: &Self::CellData,
|
||||||
|
mut context: MoveGroupRowContext,
|
||||||
|
) -> Vec<GroupRowsNotificationPB> {
|
||||||
|
let mut group_changeset = vec![];
|
||||||
|
self.context.iter_mut_groups(|group| {
|
||||||
|
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||||
|
group_changeset.push(changeset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
group_changeset
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_group_when_move_row(
|
||||||
|
&mut self,
|
||||||
|
_row: &Row,
|
||||||
|
cell_data: &Self::CellData,
|
||||||
|
) -> Option<GroupPB> {
|
||||||
|
let mut deleted_group = None;
|
||||||
|
let setting_content = self.context.get_setting_content();
|
||||||
|
if let Some((_, group)) = self.context.get_group(&group_id(
|
||||||
|
&cell_data.into(),
|
||||||
|
self.type_option.as_ref(),
|
||||||
|
&setting_content,
|
||||||
|
)) {
|
||||||
|
if group.rows.len() == 1 {
|
||||||
|
deleted_group = Some(GroupPB::from(group.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if deleted_group.is_some() {
|
||||||
|
let _ = self
|
||||||
|
.context
|
||||||
|
.delete_group(&deleted_group.as_ref().unwrap().group_id);
|
||||||
|
}
|
||||||
|
deleted_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GroupController for DateGroupController {
|
||||||
|
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {}
|
||||||
|
|
||||||
|
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||||
|
match self.context.get_group(group_id) {
|
||||||
|
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||||
|
Some((_, _)) => {
|
||||||
|
let date = DateTime::parse_from_str(&group_id, GROUP_ID_DATE_FORMAT).unwrap();
|
||||||
|
let cell = insert_date_cell(date.timestamp(), None, field);
|
||||||
|
cells.insert(field.id.clone(), cell);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str) {
|
||||||
|
if let Some(group) = self.context.get_mut_group(group_id) {
|
||||||
|
group.add_row(row_detail.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DateGroupGenerator();
|
||||||
|
impl GroupsBuilder for DateGroupGenerator {
|
||||||
|
type Context = DateGroupContext;
|
||||||
|
type TypeOptionType = DateTypeOption;
|
||||||
|
|
||||||
|
fn build(
|
||||||
|
field: &Field,
|
||||||
|
context: &Self::Context,
|
||||||
|
type_option: &Option<Self::TypeOptionType>,
|
||||||
|
) -> GeneratedGroups {
|
||||||
|
// Read all the cells for the grouping field
|
||||||
|
let cells = futures::executor::block_on(context.get_all_cells());
|
||||||
|
|
||||||
|
// Generate the groups
|
||||||
|
let mut group_configs: Vec<GeneratedGroupConfig> = cells
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|value| value.into_date_field_cell_data())
|
||||||
|
.filter(|cell| cell.timestamp.is_some())
|
||||||
|
.map(|cell| {
|
||||||
|
let group =
|
||||||
|
make_group_from_date_cell(&cell, type_option.as_ref(), &context.get_setting_content());
|
||||||
|
GeneratedGroupConfig {
|
||||||
|
filter_content: group.id.clone(),
|
||||||
|
group,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
group_configs.sort_by(|a, b| a.filter_content.cmp(&b.filter_content));
|
||||||
|
|
||||||
|
let no_status_group = Some(make_no_status_group(field));
|
||||||
|
GeneratedGroups {
|
||||||
|
no_status_group,
|
||||||
|
group_configs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_group_from_date_cell(
|
||||||
|
cell_data: &DateCellData,
|
||||||
|
type_option: Option<&DateTypeOption>,
|
||||||
|
setting_content: &String,
|
||||||
|
) -> Group {
|
||||||
|
let group_id = group_id(cell_data, type_option, setting_content);
|
||||||
|
Group::new(
|
||||||
|
group_id.clone(),
|
||||||
|
group_name_from_id(&group_id, type_option, setting_content),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const GROUP_ID_DATE_FORMAT: &'static str = "%Y/%m/%d";
|
||||||
|
|
||||||
|
fn group_id(
|
||||||
|
cell_data: &DateCellData,
|
||||||
|
type_option: Option<&DateTypeOption>,
|
||||||
|
setting_content: &String,
|
||||||
|
) -> String {
|
||||||
|
let binding = DateTypeOption::default();
|
||||||
|
let type_option = type_option.unwrap_or(&binding);
|
||||||
|
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
|
||||||
|
let date_time = date_time_from_timestamp(cell_data.timestamp, &type_option.timezone_id);
|
||||||
|
|
||||||
|
let date_format = GROUP_ID_DATE_FORMAT;
|
||||||
|
let month_format = &date_format.replace("%d", "01");
|
||||||
|
let year_format = &month_format.replace("%m", "01");
|
||||||
|
|
||||||
|
let date = match config.condition {
|
||||||
|
DateCondition::Day => date_time.format(date_format),
|
||||||
|
DateCondition::Month => date_time.format(month_format),
|
||||||
|
DateCondition::Year => date_time.format(year_format),
|
||||||
|
DateCondition::Week => date_time
|
||||||
|
.checked_sub_days(Days::new(date_time.weekday().num_days_from_monday() as u64))
|
||||||
|
.unwrap()
|
||||||
|
.format(date_format),
|
||||||
|
DateCondition::Relative => {
|
||||||
|
let now = date_time_from_timestamp(Some(timestamp()), &type_option.timezone_id).date_naive();
|
||||||
|
let date_time = date_time.date_naive();
|
||||||
|
|
||||||
|
let diff = date_time.signed_duration_since(now).num_days();
|
||||||
|
let result = if diff == 0 {
|
||||||
|
Some(now)
|
||||||
|
} else if diff == -1 {
|
||||||
|
now.checked_add_signed(Duration::days(-1))
|
||||||
|
} else if diff == 1 {
|
||||||
|
now.checked_add_signed(Duration::days(1))
|
||||||
|
} else if diff >= -7 && diff < -1 {
|
||||||
|
now.checked_add_signed(Duration::days(-7))
|
||||||
|
} else if diff > 1 && diff <= 7 {
|
||||||
|
now.checked_add_signed(Duration::days(2))
|
||||||
|
} else if diff >= -30 && diff < -7 {
|
||||||
|
now.checked_add_signed(Duration::days(-30))
|
||||||
|
} else if diff > 7 && diff <= 30 {
|
||||||
|
now.checked_add_signed(Duration::days(8))
|
||||||
|
} else {
|
||||||
|
let mut res = date_time
|
||||||
|
.checked_sub_days(Days::new(date_time.day() as u64 - 1))
|
||||||
|
.unwrap();
|
||||||
|
// if beginning of the month is within next 30 days of current day, change to
|
||||||
|
// first day which is greater than 30 days far from current day.
|
||||||
|
let diff = res.signed_duration_since(now).num_days();
|
||||||
|
if diff > 7 && diff <= 30 {
|
||||||
|
res = res
|
||||||
|
.checked_add_days(Days::new((30 - diff + 1) as u64))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Some(res)
|
||||||
|
};
|
||||||
|
|
||||||
|
result.unwrap().format(GROUP_ID_DATE_FORMAT)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
date.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_name_from_id(
|
||||||
|
group_id: &String,
|
||||||
|
type_option: Option<&DateTypeOption>,
|
||||||
|
setting_content: &String,
|
||||||
|
) -> String {
|
||||||
|
let binding = DateTypeOption::default();
|
||||||
|
let type_option = type_option.unwrap_or(&binding);
|
||||||
|
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
|
||||||
|
let date = NaiveDate::parse_from_str(group_id, GROUP_ID_DATE_FORMAT).unwrap();
|
||||||
|
|
||||||
|
let tmp;
|
||||||
|
match config.condition {
|
||||||
|
DateCondition::Day => {
|
||||||
|
tmp = format!(
|
||||||
|
"{} {}, {}",
|
||||||
|
date.format("%b").to_string(),
|
||||||
|
date.day(),
|
||||||
|
date.year(),
|
||||||
|
);
|
||||||
|
tmp
|
||||||
|
},
|
||||||
|
DateCondition::Week => {
|
||||||
|
let begin_of_week = date
|
||||||
|
.checked_sub_days(Days::new(date.weekday().num_days_from_monday() as u64))
|
||||||
|
.unwrap()
|
||||||
|
.format("%d");
|
||||||
|
let end_of_week = date
|
||||||
|
.checked_add_days(Days::new(6 - date.weekday().num_days_from_monday() as u64))
|
||||||
|
.unwrap()
|
||||||
|
.format("%d");
|
||||||
|
|
||||||
|
tmp = format!(
|
||||||
|
"Week of {} {}-{} {}",
|
||||||
|
date.format("%b").to_string(),
|
||||||
|
begin_of_week.to_string(),
|
||||||
|
end_of_week.to_string(),
|
||||||
|
date.year()
|
||||||
|
);
|
||||||
|
tmp
|
||||||
|
},
|
||||||
|
DateCondition::Month => {
|
||||||
|
tmp = format!("{} {}", date.format("%b").to_string(), date.year(),);
|
||||||
|
tmp
|
||||||
|
},
|
||||||
|
DateCondition::Year => date.year().to_string(),
|
||||||
|
DateCondition::Relative => {
|
||||||
|
let now = date_time_from_timestamp(Some(timestamp()), &type_option.timezone_id);
|
||||||
|
|
||||||
|
let diff = date.signed_duration_since(now.date_naive());
|
||||||
|
let result = match diff.num_days() {
|
||||||
|
0 => "Today",
|
||||||
|
-1 => "Yesterday",
|
||||||
|
1 => "Tomorrow",
|
||||||
|
-7 => "Last 7 days",
|
||||||
|
2 => "Next 7 days",
|
||||||
|
-30 => "Last 30 days",
|
||||||
|
8 => "Next 30 days",
|
||||||
|
_ => {
|
||||||
|
tmp = format!("{} {}", date.format("%b").to_string(), date.year(),);
|
||||||
|
&tmp
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
result.to_string()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn date_time_from_timestamp(timestamp: Option<i64>, timezone_id: &String) -> DateTime<Local> {
|
||||||
|
match timestamp {
|
||||||
|
Some(timestamp) => {
|
||||||
|
let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap();
|
||||||
|
let offset = match Tz::from_str(timezone_id) {
|
||||||
|
Ok(timezone) => timezone.offset_from_utc_datetime(&naive).fix(),
|
||||||
|
Err(_) => *Local::now().offset(),
|
||||||
|
};
|
||||||
|
|
||||||
|
DateTime::<Local>::from_utc(naive, offset)
|
||||||
|
},
|
||||||
|
None => DateTime::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::services::{
|
||||||
|
field::{date_type_option::DateTypeOption, DateCellData},
|
||||||
|
group::controller_impls::date_controller::{
|
||||||
|
group_id, group_name_from_id, GROUP_ID_DATE_FORMAT,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use chrono::{offset, Days, Duration, NaiveDateTime};
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn group_id_name_test() {
|
||||||
|
struct GroupIDTest<'a> {
|
||||||
|
cell_data: DateCellData,
|
||||||
|
setting_content: String,
|
||||||
|
exp_group_id: String,
|
||||||
|
exp_group_name: String,
|
||||||
|
type_option: &'a DateTypeOption,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mar_14_2022 = NaiveDateTime::from_timestamp_opt(1647251762, 0).unwrap();
|
||||||
|
let mar_14_2022_cd = DateCellData {
|
||||||
|
timestamp: Some(mar_14_2022.timestamp()),
|
||||||
|
include_time: false,
|
||||||
|
};
|
||||||
|
let today = offset::Local::now();
|
||||||
|
let three_days_before = today.checked_add_signed(Duration::days(-3)).unwrap();
|
||||||
|
|
||||||
|
let mut local_date_type_option = DateTypeOption::default();
|
||||||
|
local_date_type_option.timezone_id = today.offset().to_string();
|
||||||
|
let mut default_date_type_option = DateTypeOption::default();
|
||||||
|
default_date_type_option.timezone_id = "".to_string();
|
||||||
|
|
||||||
|
let tests = vec![
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: mar_14_2022_cd.clone(),
|
||||||
|
type_option: &local_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 0, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: "2022/03/01".to_string(),
|
||||||
|
exp_group_name: "Mar 2022".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: DateCellData {
|
||||||
|
timestamp: Some(today.timestamp()),
|
||||||
|
include_time: false,
|
||||||
|
},
|
||||||
|
type_option: &local_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 0, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: today.format(GROUP_ID_DATE_FORMAT).to_string(),
|
||||||
|
exp_group_name: "Today".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: DateCellData {
|
||||||
|
timestamp: Some(three_days_before.timestamp()),
|
||||||
|
include_time: false,
|
||||||
|
},
|
||||||
|
type_option: &local_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 0, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: today
|
||||||
|
.checked_sub_days(Days::new(7))
|
||||||
|
.unwrap()
|
||||||
|
.format(GROUP_ID_DATE_FORMAT)
|
||||||
|
.to_string(),
|
||||||
|
exp_group_name: "Last 7 days".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: mar_14_2022_cd.clone(),
|
||||||
|
type_option: &local_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 1, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: "2022/03/14".to_string(),
|
||||||
|
exp_group_name: "Mar 14, 2022".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: DateCellData {
|
||||||
|
timestamp: Some(
|
||||||
|
mar_14_2022
|
||||||
|
.checked_add_signed(Duration::days(3))
|
||||||
|
.unwrap()
|
||||||
|
.timestamp(),
|
||||||
|
),
|
||||||
|
include_time: false,
|
||||||
|
},
|
||||||
|
type_option: &local_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 2, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: "2022/03/14".to_string(),
|
||||||
|
exp_group_name: "Week of Mar 14-20 2022".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: mar_14_2022_cd.clone(),
|
||||||
|
type_option: &local_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 3, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: "2022/03/01".to_string(),
|
||||||
|
exp_group_name: "Mar 2022".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: mar_14_2022_cd.clone(),
|
||||||
|
type_option: &local_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 4, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: "2022/01/01".to_string(),
|
||||||
|
exp_group_name: "2022".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: DateCellData {
|
||||||
|
timestamp: Some(1685715999),
|
||||||
|
include_time: false,
|
||||||
|
},
|
||||||
|
type_option: &default_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 1, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: "2023/06/02".to_string(),
|
||||||
|
exp_group_name: "".to_string(),
|
||||||
|
},
|
||||||
|
GroupIDTest {
|
||||||
|
cell_data: DateCellData {
|
||||||
|
timestamp: Some(1685802386),
|
||||||
|
include_time: false,
|
||||||
|
},
|
||||||
|
type_option: &default_date_type_option,
|
||||||
|
setting_content: r#"{"condition": 1, "hide_empty": false}"#.to_string(),
|
||||||
|
exp_group_id: "2023/06/03".to_string(),
|
||||||
|
exp_group_name: "".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (i, test) in tests.iter().enumerate() {
|
||||||
|
let group_id = group_id(
|
||||||
|
&test.cell_data,
|
||||||
|
Some(test.type_option),
|
||||||
|
&test.setting_content,
|
||||||
|
);
|
||||||
|
assert_eq!(test.exp_group_id, group_id, "test {}", i);
|
||||||
|
|
||||||
|
if test.exp_group_name != "" {
|
||||||
|
let group_name =
|
||||||
|
group_name_from_id(&group_id, Some(test.type_option), &test.setting_content);
|
||||||
|
assert_eq!(test.exp_group_name, group_name, "test {}", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
mod checkbox_controller;
|
mod checkbox_controller;
|
||||||
|
mod date_controller;
|
||||||
mod default_controller;
|
mod default_controller;
|
||||||
mod select_option_controller;
|
mod select_option_controller;
|
||||||
mod url_controller;
|
mod url_controller;
|
||||||
|
|
||||||
pub use checkbox_controller::*;
|
pub use checkbox_controller::*;
|
||||||
|
pub use date_controller::*;
|
||||||
pub use default_controller::*;
|
pub use default_controller::*;
|
||||||
pub use select_option_controller::*;
|
pub use select_option_controller::*;
|
||||||
pub use url_controller::*;
|
pub use url_controller::*;
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
use chrono::NaiveDateTime;
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{Cell, Row, RowDetail};
|
use collab_database::rows::{Cell, Row, RowDetail};
|
||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB, SelectOptionCellDataPB,
|
FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB, SelectOptionCellDataPB,
|
||||||
};
|
};
|
||||||
use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell, insert_url_cell};
|
use crate::services::cell::{
|
||||||
|
insert_checkbox_cell, insert_date_cell, insert_select_option_cell, insert_url_cell,
|
||||||
|
};
|
||||||
use crate::services::field::{SelectOption, CHECK};
|
use crate::services::field::{SelectOption, CHECK};
|
||||||
use crate::services::group::controller::MoveGroupRowContext;
|
use crate::services::group::controller::MoveGroupRowContext;
|
||||||
use crate::services::group::{GeneratedGroupConfig, Group, GroupData};
|
use crate::services::group::{GeneratedGroupConfig, Group, GroupData};
|
||||||
@ -170,6 +173,15 @@ pub fn make_inserted_cell(group_id: &str, field: &Field) -> Option<Cell> {
|
|||||||
let cell = insert_url_cell(group_id.to_owned(), field);
|
let cell = insert_url_cell(group_id.to_owned(), field);
|
||||||
Some(cell)
|
Some(cell)
|
||||||
},
|
},
|
||||||
|
FieldType::DateTime => {
|
||||||
|
let date = NaiveDateTime::parse_from_str(
|
||||||
|
&format!("{} 00:00:00", group_id).to_string(),
|
||||||
|
"%Y/%m/%d %H:%M:%S",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let cell = insert_date_cell(date.timestamp(), None, field);
|
||||||
|
Some(cell)
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("Unknown field type: {:?}", field_type);
|
tracing::warn!("Unknown field type: {:?}", field_type);
|
||||||
None
|
None
|
||||||
|
@ -7,10 +7,9 @@ use collab_database::views::DatabaseLayout;
|
|||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
|
|
||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
use crate::services::group::configuration::GroupSettingReader;
|
|
||||||
use crate::services::group::controller::GroupController;
|
|
||||||
use crate::services::group::{
|
use crate::services::group::{
|
||||||
CheckboxGroupContext, CheckboxGroupController, DefaultGroupController, Group, GroupSetting,
|
CheckboxGroupContext, CheckboxGroupController, DateGroupContext, DateGroupController,
|
||||||
|
DefaultGroupController, Group, GroupController, GroupSetting, GroupSettingReader,
|
||||||
GroupSettingWriter, MultiSelectGroupController, MultiSelectOptionGroupContext,
|
GroupSettingWriter, MultiSelectGroupController, MultiSelectOptionGroupContext,
|
||||||
SingleSelectGroupController, SingleSelectOptionGroupContext, URLGroupContext, URLGroupController,
|
SingleSelectGroupController, SingleSelectOptionGroupContext, URLGroupContext, URLGroupController,
|
||||||
};
|
};
|
||||||
@ -95,6 +94,17 @@ where
|
|||||||
let controller = URLGroupController::new(&grouping_field, configuration).await?;
|
let controller = URLGroupController::new(&grouping_field, configuration).await?;
|
||||||
group_controller = Box::new(controller);
|
group_controller = Box::new(controller);
|
||||||
},
|
},
|
||||||
|
FieldType::DateTime => {
|
||||||
|
let configuration = DateGroupContext::new(
|
||||||
|
view_id,
|
||||||
|
grouping_field.clone(),
|
||||||
|
configuration_reader,
|
||||||
|
configuration_writer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let controller = DateGroupController::new(&grouping_field, configuration).await?;
|
||||||
|
group_controller = Box::new(controller);
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
group_controller = Box::new(DefaultGroupController::new(&grouping_field));
|
group_controller = Box::new(DefaultGroupController::new(&grouping_field));
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,208 @@
|
|||||||
|
use crate::database::group_test::script::DatabaseGroupTest;
|
||||||
|
use crate::database::group_test::script::GroupScript::*;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use chrono::{offset, Duration};
|
||||||
|
use collab_database::database::gen_row_id;
|
||||||
|
use collab_database::rows::CreateRowParams;
|
||||||
|
use flowy_database2::entities::FieldType;
|
||||||
|
use flowy_database2::services::cell::CellBuilder;
|
||||||
|
use flowy_database2::services::field::DateCellData;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn group_by_date_test() {
|
||||||
|
let date_diffs = vec![-1, 0, 7, -15, -1];
|
||||||
|
let mut test = DatabaseGroupTest::new().await;
|
||||||
|
let date_field = test.get_field(FieldType::DateTime).await;
|
||||||
|
|
||||||
|
for diff in date_diffs {
|
||||||
|
let timestamp = offset::Local::now()
|
||||||
|
.checked_add_signed(Duration::days(diff))
|
||||||
|
.unwrap()
|
||||||
|
.timestamp()
|
||||||
|
.to_string();
|
||||||
|
let mut cells = HashMap::new();
|
||||||
|
cells.insert(date_field.id.clone(), timestamp);
|
||||||
|
let cells = CellBuilder::with_cells(cells, &[date_field.clone()]).build();
|
||||||
|
|
||||||
|
let params = CreateRowParams {
|
||||||
|
id: gen_row_id(),
|
||||||
|
cells,
|
||||||
|
height: 60,
|
||||||
|
visibility: true,
|
||||||
|
prev_row_id: None,
|
||||||
|
timestamp: 0,
|
||||||
|
};
|
||||||
|
let res = test.editor.create_row(&test.view_id, None, params).await;
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
let today = offset::Local::now();
|
||||||
|
let last_day = today
|
||||||
|
.checked_add_signed(Duration::days(-1))
|
||||||
|
.unwrap()
|
||||||
|
.format("%Y/%m/%d")
|
||||||
|
.to_string();
|
||||||
|
let last_30_days = today
|
||||||
|
.checked_add_signed(Duration::days(-30))
|
||||||
|
.unwrap()
|
||||||
|
.format("%Y/%m/%d")
|
||||||
|
.to_string();
|
||||||
|
let next_7_days = today
|
||||||
|
.checked_add_signed(Duration::days(2))
|
||||||
|
.unwrap()
|
||||||
|
.format("%Y/%m/%d")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let scripts = vec![
|
||||||
|
GroupByField {
|
||||||
|
field_id: date_field.id.clone(),
|
||||||
|
},
|
||||||
|
AssertGroupCount(7),
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 0,
|
||||||
|
row_count: 0,
|
||||||
|
},
|
||||||
|
// Added via `make_test_board`
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: 1,
|
||||||
|
group_id: "2022/03/01".to_string(),
|
||||||
|
group_name: "Mar 2022".to_string(),
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 1,
|
||||||
|
row_count: 3,
|
||||||
|
},
|
||||||
|
// Added via `make_test_board`
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: 2,
|
||||||
|
group_id: "2022/11/01".to_string(),
|
||||||
|
group_name: "Nov 2022".to_string(),
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 2,
|
||||||
|
row_count: 2,
|
||||||
|
},
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: 3,
|
||||||
|
group_id: last_30_days,
|
||||||
|
group_name: "Last 30 days".to_string(),
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 3,
|
||||||
|
row_count: 1,
|
||||||
|
},
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: 4,
|
||||||
|
group_id: last_day,
|
||||||
|
group_name: "Yesterday".to_string(),
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 4,
|
||||||
|
row_count: 2,
|
||||||
|
},
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: 5,
|
||||||
|
group_id: today.format("%Y/%m/%d").to_string(),
|
||||||
|
group_name: "Today".to_string(),
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 5,
|
||||||
|
row_count: 1,
|
||||||
|
},
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: 6,
|
||||||
|
group_id: next_7_days,
|
||||||
|
group_name: "Next 7 days".to_string(),
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 6,
|
||||||
|
row_count: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
test.run_scripts(scripts).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn change_row_group_on_date_cell_changed_test() {
|
||||||
|
let mut test = DatabaseGroupTest::new().await;
|
||||||
|
let date_field = test.get_field(FieldType::DateTime).await;
|
||||||
|
let scripts = vec![
|
||||||
|
GroupByField {
|
||||||
|
field_id: date_field.id.clone(),
|
||||||
|
},
|
||||||
|
AssertGroupCount(3),
|
||||||
|
// Nov 2, 2022
|
||||||
|
UpdateGroupedCellWithData {
|
||||||
|
from_group_index: 1,
|
||||||
|
row_index: 0,
|
||||||
|
cell_data: "1667408732".to_string(),
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 1,
|
||||||
|
row_count: 2,
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 2,
|
||||||
|
row_count: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
test.run_scripts(scripts).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn change_date_on_moving_row_to_another_group() {
|
||||||
|
let mut test = DatabaseGroupTest::new().await;
|
||||||
|
let date_field = test.get_field(FieldType::DateTime).await;
|
||||||
|
let scripts = vec![
|
||||||
|
GroupByField {
|
||||||
|
field_id: date_field.id.clone(),
|
||||||
|
},
|
||||||
|
AssertGroupCount(3),
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 1,
|
||||||
|
row_count: 3,
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 2,
|
||||||
|
row_count: 2,
|
||||||
|
},
|
||||||
|
MoveRow {
|
||||||
|
from_group_index: 1,
|
||||||
|
from_row_index: 0,
|
||||||
|
to_group_index: 2,
|
||||||
|
to_row_index: 0,
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 1,
|
||||||
|
row_count: 2,
|
||||||
|
},
|
||||||
|
AssertGroupRowCount {
|
||||||
|
group_index: 2,
|
||||||
|
row_count: 3,
|
||||||
|
},
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: 2,
|
||||||
|
group_id: "2022/11/01".to_string(),
|
||||||
|
group_name: "Nov 2022".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
test.run_scripts(scripts).await;
|
||||||
|
|
||||||
|
let group = test.group_at_index(2).await;
|
||||||
|
let rows = group.clone().rows;
|
||||||
|
let row_id = &rows.get(0).unwrap().id;
|
||||||
|
let row_detail = test
|
||||||
|
.get_rows()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.find(|r| r.row.id.to_string() == row_id.to_string())
|
||||||
|
.unwrap();
|
||||||
|
let cell = row_detail.row.cells.get(&date_field.id.clone()).unwrap();
|
||||||
|
let date_cell = DateCellData::from(cell);
|
||||||
|
|
||||||
|
let date_time =
|
||||||
|
NaiveDateTime::parse_from_str("2022/11/01 00:00:00", "%Y/%m/%d %H:%M:%S").unwrap();
|
||||||
|
assert_eq!(date_time.timestamp(), date_cell.timestamp.unwrap());
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
mod date_group_test;
|
||||||
mod script;
|
mod script;
|
||||||
mod test;
|
mod test;
|
||||||
mod url_group_test;
|
mod url_group_test;
|
||||||
|
@ -4,7 +4,7 @@ use collab_database::rows::{CreateRowParams, RowId};
|
|||||||
|
|
||||||
use flowy_database2::entities::{FieldType, GroupPB, RowMetaPB};
|
use flowy_database2::entities::{FieldType, GroupPB, RowMetaPB};
|
||||||
use flowy_database2::services::cell::{
|
use flowy_database2::services::cell::{
|
||||||
delete_select_option_cell, insert_select_option_cell, insert_url_cell,
|
delete_select_option_cell, insert_date_cell, insert_select_option_cell, insert_url_cell,
|
||||||
};
|
};
|
||||||
use flowy_database2::services::field::{
|
use flowy_database2::services::field::{
|
||||||
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
|
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
|
||||||
@ -62,6 +62,11 @@ pub enum GroupScript {
|
|||||||
GroupByField {
|
GroupByField {
|
||||||
field_id: String,
|
field_id: String,
|
||||||
},
|
},
|
||||||
|
AssertGroupIDName {
|
||||||
|
group_index: usize,
|
||||||
|
group_id: String,
|
||||||
|
group_name: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DatabaseGroupTest {
|
pub struct DatabaseGroupTest {
|
||||||
@ -203,6 +208,9 @@ impl DatabaseGroupTest {
|
|||||||
let field_type = FieldType::from(field.field_type);
|
let field_type = FieldType::from(field.field_type);
|
||||||
let cell = match field_type {
|
let cell = match field_type {
|
||||||
FieldType::URL => insert_url_cell(cell_data, &field),
|
FieldType::URL => insert_url_cell(cell_data, &field),
|
||||||
|
FieldType::DateTime => {
|
||||||
|
insert_date_cell(cell_data.parse::<i64>().unwrap(), Some(true), &field)
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
panic!("Unsupported group field type");
|
panic!("Unsupported group field type");
|
||||||
},
|
},
|
||||||
@ -252,6 +260,15 @@ impl DatabaseGroupTest {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
|
GroupScript::AssertGroupIDName {
|
||||||
|
group_index,
|
||||||
|
group_id,
|
||||||
|
group_name,
|
||||||
|
} => {
|
||||||
|
let group = self.group_at_index(group_index).await;
|
||||||
|
assert_eq!(group_id, group.group_id, "group index: {}", group_index);
|
||||||
|
assert_eq!(group_name, group.group_name, "group index: {}", group_index);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,27 +284,11 @@ impl DatabaseGroupTest {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn get_multi_select_field(&self) -> Field {
|
pub async fn get_multi_select_field(&self) -> Field {
|
||||||
self
|
self.get_field(FieldType::MultiSelect).await
|
||||||
.inner
|
|
||||||
.get_fields()
|
|
||||||
.into_iter()
|
|
||||||
.find(|field_rev| {
|
|
||||||
let field_type = FieldType::from(field_rev.field_type);
|
|
||||||
field_type.is_multi_select()
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_single_select_field(&self) -> Field {
|
pub async fn get_single_select_field(&self) -> Field {
|
||||||
self
|
self.get_field(FieldType::SingleSelect).await
|
||||||
.inner
|
|
||||||
.get_fields()
|
|
||||||
.into_iter()
|
|
||||||
.find(|field| {
|
|
||||||
let field_type = FieldType::from(field.field_type);
|
|
||||||
field_type.is_single_select()
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn edit_single_select_type_option(
|
pub async fn edit_single_select_type_option(
|
||||||
@ -306,13 +307,17 @@ impl DatabaseGroupTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_url_field(&self) -> Field {
|
pub async fn get_url_field(&self) -> Field {
|
||||||
|
self.get_field(FieldType::URL).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_field(&self, field_type: FieldType) -> Field {
|
||||||
self
|
self
|
||||||
.inner
|
.inner
|
||||||
.get_fields()
|
.get_fields()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|field| {
|
.find(|field| {
|
||||||
let field_type = FieldType::from(field.field_type);
|
let ft = FieldType::from(field.field_type);
|
||||||
field_type.is_url()
|
ft == field_type
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user