feat: created at and updated at field type (#2572)

* feat: created at and updated at field type

* style: context for rust asserts, change checks in flutter

* fix: mistake in if condition

* style: add comma end of array

* feat: created at and updated at field type

* fix: typo in const variable

* style: cargo fmt

* refactor: opti cell insert

* chore: remove redundant clone

* refactor: date type option

* fix: tauri build

---------

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Mohammad Zolfaghari 2023-05-26 14:04:17 +03:30 committed by GitHub
parent a85cc62a58
commit 9a213fa562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 598 additions and 188 deletions

View File

@ -280,6 +280,8 @@
"textFieldName": "Text",
"checkboxFieldName": "Checkbox",
"dateFieldName": "Date",
"updatedAtFieldName": "Updated At",
"createdAtFieldName": "Created At",
"numberFieldName": "Numbers",
"singleSelectFieldName": "Select",
"multiSelectFieldName": "Multiselect",

View File

@ -40,6 +40,8 @@ class CellControllerBuilder {
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: DateCellDataParser(),

View File

@ -353,7 +353,13 @@ class RowDataBuilder {
}
void insertDate(FieldInfo fieldInfo, DateTime date) {
assert(fieldInfo.fieldType == FieldType.DateTime);
assert(
[
FieldType.DateTime,
FieldType.UpdatedAt,
FieldType.CreatedAt,
].contains(fieldInfo.fieldType),
);
final timestamp = date.millisecondsSinceEpoch ~/ 1000;
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
}

View File

@ -84,10 +84,19 @@ class FilterBackendService {
required String fieldId,
String? filterId,
required DateFilterConditionPB condition,
required FieldType fieldType,
int? start,
int? end,
int? timestamp,
}) {
assert(
[
FieldType.DateTime,
FieldType.UpdatedAt,
FieldType.CreatedAt,
].contains(fieldType),
);
var filter = DateFilterPB();
if (timestamp != null) {
filter.timestamp = $fixnum.Int64(timestamp);
@ -105,7 +114,7 @@ class FilterBackendService {
return insertFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: FieldType.DateTime,
fieldType: fieldType,
data: filter.writeToBuffer(),
);
}

View File

@ -362,6 +362,8 @@ Widget? _buildHeaderIcon(GroupData customData) {
}
break;
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
break;
case FieldType.MultiSelect:
break;

View File

@ -93,11 +93,14 @@ class GridCreateFilterBloc
condition: CheckboxFilterConditionPB.IsChecked,
);
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
return _filterBackendSvc.insertDateFilter(
fieldId: fieldId,
condition: DateFilterConditionPB.DateIs,
timestamp: timestamp,
fieldType: field.fieldType,
);
case FieldType.MultiSelect:
return _filterBackendSvc.insertSelectOptionFilter(

View File

@ -23,7 +23,11 @@ class FilterInfo {
}
DateFilterPB? dateFilter() {
if (filter.fieldType != FieldType.DateTime) {
if (![
FieldType.DateTime,
FieldType.UpdatedAt,
FieldType.CreatedAt,
].contains(filter.fieldType)) {
return null;
}
return DateFilterPB.fromBuffer(filter.data);

View File

@ -25,6 +25,8 @@ Widget buildFilterChoicechip(FilterInfo filterInfo) {
case FieldType.Checkbox:
return CheckboxFilterChoicechip(filterInfo: filterInfo);
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return DateFilterChoicechip(filterInfo: filterInfo);
case FieldType.MultiSelect:
return SelectOptionFilterChoicechip(filterInfo: filterInfo);

View File

@ -8,6 +8,8 @@ extension FieldTypeListExtension on FieldType {
case FieldType.Checkbox:
return "grid/field/checkbox";
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return "grid/field/date";
case FieldType.MultiSelect:
return "grid/field/multi_select";
@ -31,6 +33,10 @@ extension FieldTypeListExtension on FieldType {
return LocaleKeys.grid_field_checkboxFieldName.tr();
case FieldType.DateTime:
return LocaleKeys.grid_field_dateFieldName.tr();
case FieldType.UpdatedAt:
return LocaleKeys.grid_field_updatedAtFieldName.tr();
case FieldType.CreatedAt:
return LocaleKeys.grid_field_createdAtFieldName.tr();
case FieldType.MultiSelect:
return LocaleKeys.grid_field_multiSelectFieldName.tr();
case FieldType.Number:

View File

@ -73,6 +73,8 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
),
);
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return DateTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
viewId: viewId,
@ -202,6 +204,8 @@ TypeOptionContext<T>
dataParser: CheckboxTypeOptionWidgetDataParser(),
) as TypeOptionContext<T>;
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return DateTypeOptionContext(
dataController: dataController,
dataParser: DateTypeOptionDataParser(),

View File

@ -39,6 +39,8 @@ class CardCellBuilder<CustomCardData> {
key: key,
);
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return DateCardCell<CustomCardData>(
renderHook: renderHook?.renderHook[FieldType.DateTime],
cellControllerBuilder: cellControllerBuilder,

View File

@ -34,6 +34,8 @@ class GridCellBuilder {
key: key,
);
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return GridDateCell(
cellControllerBuilder: cellControllerBuilder,
key: key,

View File

@ -335,6 +335,8 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
case FieldType.Checkbox:
return null;
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return DateCellStyle(
alignment: Alignment.centerLeft,
);

View File

@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"anyhow",
"collab",
@ -1023,7 +1023,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"anyhow",
"bytes",
@ -1040,7 +1040,7 @@ dependencies = [
[[package]]
name = "collab-client-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"bytes",
"collab-sync",
@ -1058,7 +1058,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"anyhow",
"async-trait",
@ -1083,7 +1083,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"proc-macro2",
"quote",
@ -1095,7 +1095,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"anyhow",
"collab",
@ -1112,7 +1112,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"anyhow",
"collab",
@ -1130,7 +1130,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"bincode",
"chrono",
@ -1150,7 +1150,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"anyhow",
"async-trait",
@ -1180,7 +1180,7 @@ dependencies = [
[[package]]
name = "collab-sync"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6052d5#6052d509982f705c6d43a859c937a722a8c3358b"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d6af3a#d6af3a7662a57202e4e5b0a297c28757a18f3372"
dependencies = [
"bytes",
"collab",

View File

@ -45,6 +45,8 @@ export class CellControllerBuilder {
case FieldType.Number:
return this.makeNumberCellController();
case FieldType.DateTime:
case FieldType.UpdatedAt:
case FieldType.CreatedAt:
return this.makeDateCellController();
case FieldType.URL:
return this.makeURLCellController();

View File

@ -492,6 +492,8 @@ pub enum FieldType {
Checkbox = 5,
URL = 6,
Checklist = 7,
UpdatedAt = 8,
CreatedAt = 9,
}
pub const RICH_TEXT_FIELD: FieldType = FieldType::RichText;
@ -502,6 +504,8 @@ pub const MULTI_SELECT_FIELD: FieldType = FieldType::MultiSelect;
pub const CHECKBOX_FIELD: FieldType = FieldType::Checkbox;
pub const URL_FIELD: FieldType = FieldType::URL;
pub const CHECKLIST_FIELD: FieldType = FieldType::Checklist;
pub const UPDATED_AT_FIELD: FieldType = FieldType::UpdatedAt;
pub const CREATED_AT_FIELD: FieldType = FieldType::CreatedAt;
impl std::default::Default for FieldType {
fn default() -> Self {
@ -529,9 +533,13 @@ impl From<&FieldType> for FieldType {
}
impl FieldType {
pub fn value(&self) -> i64 {
self.clone().into()
}
pub fn default_cell_width(&self) -> i32 {
match self {
FieldType::DateTime => 180,
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => 180,
_ => 150,
}
}
@ -546,6 +554,8 @@ impl FieldType {
FieldType::Checkbox => "Checkbox",
FieldType::URL => "URL",
FieldType::Checklist => "Checklist",
FieldType::UpdatedAt => "Updated At",
FieldType::CreatedAt => "Created At",
};
s.to_string()
}
@ -563,7 +573,7 @@ impl FieldType {
}
pub fn is_date(&self) -> bool {
self == &DATE_FIELD
self == &DATE_FIELD || self == &UPDATED_AT_FIELD || self == &CREATED_AT_FIELD
}
pub fn is_single_select(&self) -> bool {
@ -605,6 +615,8 @@ impl From<FieldType> for i64 {
FieldType::Checkbox => 5,
FieldType::URL => 6,
FieldType::Checklist => 7,
FieldType::UpdatedAt => 8,
FieldType::CreatedAt => 9,
}
}
}

View File

@ -35,7 +35,9 @@ impl std::convert::From<&Filter> for FilterPB {
let bytes: Bytes = match filter.field_type {
FieldType::RichText => TextFilterPB::from(filter).try_into().unwrap(),
FieldType::Number => NumberFilterPB::from(filter).try_into().unwrap(),
FieldType::DateTime => DateFilterPB::from(filter).try_into().unwrap(),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
DateFilterPB::from(filter).try_into().unwrap()
},
FieldType::SingleSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(),
FieldType::MultiSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(),
FieldType::Checklist => ChecklistFilterPB::from(filter).try_into().unwrap(),
@ -198,7 +200,7 @@ impl TryInto<AlterFilterParams> for AlterFilterPayloadPB {
condition = filter.condition as u8;
content = filter.content;
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
condition = filter.condition as u8;
content = DateFilterContentPB {

View File

@ -12,6 +12,8 @@ macro_rules! impl_into_field_type {
5 => FieldType::Checkbox,
6 => FieldType::URL,
7 => FieldType::Checklist,
8 => FieldType::UpdatedAt,
9 => FieldType::CreatedAt,
_ => {
tracing::error!("Can't parser FieldType from value: {}", ty);
FieldType::RichText

View File

@ -4,7 +4,7 @@ use strum_macros::EnumIter;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use crate::entities::CellIdPB;
use crate::entities::{CellIdPB, FieldType};
use crate::services::field::{DateFormat, DateTypeOption, TimeFormat};
#[derive(Clone, Debug, Default, ProtoBuf)]
@ -51,6 +51,9 @@ pub struct DateTypeOptionPB {
#[pb(index = 2)]
pub time_format: TimeFormatPB,
#[pb(index = 3)]
pub field_type: FieldType,
}
impl From<DateTypeOption> for DateTypeOptionPB {
@ -58,6 +61,7 @@ impl From<DateTypeOption> for DateTypeOptionPB {
Self {
date_format: data.date_format.into(),
time_format: data.time_format.into(),
field_type: data.field_type,
}
}
}
@ -67,6 +71,7 @@ impl From<DateTypeOptionPB> for DateTypeOption {
Self {
date_format: data.date_format.into(),
time_format: data.time_format.into(),
field_type: data.field_type,
}
}
}

View File

@ -3,6 +3,7 @@ use std::fmt::Debug;
use collab_database::fields::Field;
use collab_database::rows::{get_field_type_from_cell, Cell, Cells};
use lib_infra::util::timestamp;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
@ -205,11 +206,11 @@ pub fn insert_checkbox_cell(is_check: bool, field: &Field) -> Cell {
apply_cell_changeset(s, None, field, None).unwrap()
}
pub fn insert_date_cell(timestamp: i64, field: &Field) -> Cell {
pub fn insert_date_cell(timestamp: i64, include_time: Option<bool>, field: &Field) -> Cell {
let cell_data = serde_json::to_string(&DateCellChangeset {
date: Some(timestamp.to_string()),
time: None,
include_time: Some(false),
include_time,
timezone_id: None,
})
.unwrap();
@ -299,6 +300,7 @@ pub struct CellBuilder<'a> {
}
impl<'a> CellBuilder<'a> {
/// Build list of Cells from HashMap of cell string by field id.
pub fn with_cells(cell_by_field_id: HashMap<String, String>, fields: &'a [Field]) -> Self {
let field_maps = fields
.into_iter()
@ -306,7 +308,7 @@ impl<'a> CellBuilder<'a> {
.collect::<HashMap<String, &Field>>();
let mut cells = Cells::new();
for (field_id, cell_str) in cell_by_field_id {
for (field_id, cell_str) in cell_by_field_id.clone() {
if let Some(field) = field_maps.get(&field_id) {
let field_type = FieldType::from(field.field_type);
match field_type {
@ -318,9 +320,9 @@ impl<'a> CellBuilder<'a> {
cells.insert(field_id, insert_number_cell(num, field));
}
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
if let Ok(timestamp) = cell_str.parse::<i64>() {
cells.insert(field_id, insert_date_cell(timestamp, field));
cells.insert(field_id, insert_date_cell(timestamp, Some(false), field));
}
},
FieldType::SingleSelect | FieldType::MultiSelect => {
@ -345,6 +347,19 @@ impl<'a> CellBuilder<'a> {
}
}
// Auto insert the cell data if the field is not in the cell_by_field_id.
// Currently, the auto fill field type is `UpdatedAt` or `CreatedAt`.
for field in fields {
if !cell_by_field_id.contains_key(&field.id) {
let field_type = FieldType::from(field.field_type);
if field_type == FieldType::UpdatedAt || field_type == FieldType::CreatedAt {
cells.insert(
field.id.clone(),
insert_date_cell(timestamp(), Some(true), field),
);
}
}
}
CellBuilder { cells, field_maps }
}
@ -400,9 +415,10 @@ impl<'a> CellBuilder<'a> {
match self.field_maps.get(&field_id.to_owned()) {
None => tracing::warn!("Can't find the date field with id: {}", field_id),
Some(field) => {
self
.cells
.insert(field_id.to_owned(), insert_date_cell(timestamp, field));
self.cells.insert(
field_id.to_owned(),
insert_date_cell(timestamp, Some(false), field),
);
},
}
}

View File

@ -82,6 +82,8 @@ impl TypeCellData {
pub fn is_date(&self) -> bool {
self.field_type == FieldType::DateTime
|| self.field_type == FieldType::UpdatedAt
|| self.field_type == FieldType::CreatedAt
}
pub fn is_single_select(&self) -> bool {

View File

@ -23,15 +23,15 @@ use crate::entities::{
};
use crate::notification::{send_notification, DatabaseNotification};
use crate::services::cell::{
apply_cell_changeset, get_cell_protobuf, AnyTypeCache, CellBuilder, CellCache, ToCellChangeset,
apply_cell_changeset, get_cell_protobuf, insert_date_cell, AnyTypeCache, CellBuilder, CellCache,
ToCellChangeset,
};
use crate::services::database::util::database_view_setting_pb_from_view;
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
use crate::services::field::{
default_type_option_data_for_type, default_type_option_data_from_type,
select_type_option_from_field, transform_type_option, type_option_data_from_pb_or_default,
type_option_to_pb, SelectOptionCellChangeset, SelectOptionIds, TypeOptionCellDataHandler,
TypeOptionCellExt,
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
type_option_data_from_pb_or_default, type_option_to_pb, SelectOptionCellChangeset,
SelectOptionIds, TypeOptionCellDataHandler, TypeOptionCellExt,
};
use crate::services::filter::Filter;
use crate::services::group::{default_group_setting, GroupSetting, RowChangeset};
@ -237,7 +237,7 @@ impl DatabaseEditor {
let old_type_option = field.get_any_type_option(old_field_type.clone());
let new_type_option = field
.get_any_type_option(new_field_type)
.unwrap_or_else(|| default_type_option_data_for_type(new_field_type));
.unwrap_or_else(|| default_type_option_data_from_type(new_field_type));
let transformed_type_option = transform_type_option(
&new_type_option,
@ -352,7 +352,7 @@ impl DatabaseEditor {
) -> (Field, Bytes) {
let name = field_type.default_name();
let type_option_data = match type_option_data {
None => default_type_option_data_for_type(field_type),
None => default_type_option_data_from_type(field_type),
Some(type_option_data) => type_option_data_from_pb_or_default(type_option_data, field_type),
};
let (index, field) =
@ -492,9 +492,23 @@ impl DatabaseEditor {
) -> FlowyResult<()> {
// Get the old row before updating the cell. It would be better to get the old cell
let old_row = { self.database.lock().get_row(&row_id) };
// Get all the updated_at fields. We will update all of them.
let updated_at_fields = self
.database
.lock()
.get_fields(view_id, None)
.into_iter()
.filter(|f| FieldType::from(f.field_type) == FieldType::UpdatedAt)
.collect::<Vec<Field>>();
self.database.lock().update_row(&row_id, |row_update| {
row_update.update_cells(|cell_update| {
cell_update.insert(field_id, new_cell);
let mut cells_update = cell_update.insert(field_id, new_cell);
for field in &updated_at_fields {
cells_update =
cells_update.insert(&field.id, insert_date_cell(timestamp(), Some(true), field));
}
});
});
@ -505,12 +519,22 @@ impl DatabaseEditor {
}
}
notify_did_update_cell(vec![CellChangesetNotifyPB {
view_id: view_id.to_string(),
row_id: row_id.into_inner(),
field_id: field_id.to_string(),
}])
.await;
// Collect all the updated field's id. Notify the frontend that all of them have been updated.
let mut updated_field_ids = updated_at_fields
.into_iter()
.map(|field| field.id)
.collect::<Vec<String>>();
updated_field_ids.push(field_id.to_string());
let changeset = updated_field_ids
.into_iter()
.map(|field_id| CellChangesetNotifyPB {
view_id: view_id.to_string(),
row_id: row_id.clone().into_inner(),
field_id,
})
.collect();
notify_did_update_cell(changeset).await;
Ok(())
}

View File

@ -1,9 +1,7 @@
mod field_builder;
mod field_operation;
mod type_option_builder;
mod type_options;
pub use field_builder::*;
pub use field_operation::*;
pub use type_option_builder::*;
pub use type_options::*;

View File

@ -1,16 +0,0 @@
use crate::entities::FieldType;
use crate::services::field::type_options::*;
use collab_database::fields::TypeOptionData;
pub fn default_type_option_data_from_type(field_type: &FieldType) -> TypeOptionData {
match field_type {
FieldType::RichText => RichTextTypeOption::default().into(),
FieldType::Number => NumberTypeOption::default().into(),
FieldType::DateTime => DateTypeOption::default().into(),
FieldType::SingleSelect => SingleSelectTypeOption::default().into(),
FieldType::MultiSelect => MultiSelectTypeOption::default().into(),
FieldType::Checkbox => CheckboxTypeOption::default().into(),
FieldType::URL => URLTypeOption::default().into(),
FieldType::Checklist => ChecklistTypeOption::default().into(),
}
}

View File

@ -14,7 +14,7 @@ mod tests {
#[test]
fn date_type_option_date_format_test() {
let mut type_option = DateTypeOption::default();
let mut type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
for date_format in DateFormat::iter() {
type_option.date_format = date_format;
@ -95,7 +95,7 @@ mod tests {
#[test]
fn date_type_option_different_time_format_test() {
let mut type_option = DateTypeOption::default();
let mut type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
for time_format in TimeFormat::iter() {
@ -183,8 +183,8 @@ mod tests {
#[test]
fn date_type_option_invalid_date_str_test() {
let type_option = DateTypeOption::default();
let field_type = FieldType::DateTime;
let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build();
assert_date(
&type_option,
@ -203,8 +203,9 @@ mod tests {
#[test]
#[should_panic]
fn date_type_option_invalid_include_time_str_test() {
let type_option = DateTypeOption::new();
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let field_type = FieldType::DateTime;
let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build();
assert_date(
&type_option,
@ -223,8 +224,9 @@ mod tests {
#[test]
#[should_panic]
fn date_type_option_empty_include_time_str_test() {
let type_option = DateTypeOption::new();
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let field_type = FieldType::DateTime;
let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build();
assert_date(
&type_option,
@ -242,8 +244,8 @@ mod tests {
#[test]
fn date_type_midnight_include_time_str_test() {
let type_option = DateTypeOption::new();
let field_type = FieldType::DateTime;
let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build();
assert_date(
&type_option,
@ -264,7 +266,7 @@ mod tests {
#[test]
#[should_panic]
fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() {
let type_option = DateTypeOption::new();
let type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
assert_date(
&type_option,
@ -285,9 +287,10 @@ mod tests {
#[test]
#[should_panic]
fn date_type_option_twenty_four_hours_include_time_str_in_twelve_hours_format() {
let mut type_option = DateTypeOption::new();
let field_type = FieldType::DateTime;
let mut type_option = DateTypeOption::new(field_type.clone());
type_option.time_format = TimeFormat::TwelveHour;
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let field = FieldBuilder::from_field_type(field_type).build();
assert_date(
&type_option,
@ -335,7 +338,7 @@ mod tests {
#[test]
#[should_panic]
fn update_date_keep_time() {
let type_option = DateTypeOption::new();
let type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let old_cell_data = initialize_date_cell(
@ -363,7 +366,7 @@ mod tests {
#[test]
fn update_time_keep_date() {
let type_option = DateTypeOption::new();
let type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let old_cell_data = initialize_date_cell(
@ -391,7 +394,7 @@ mod tests {
#[test]
fn timezone_no_daylight_saving_time() {
let type_option = DateTypeOption::new();
let type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
assert_date(
@ -422,7 +425,7 @@ mod tests {
#[test]
fn timezone_with_daylight_saving_time() {
let type_option = DateTypeOption::new();
let type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
assert_date(
@ -453,7 +456,7 @@ mod tests {
#[test]
fn change_timezone() {
let type_option = DateTypeOption::new();
let type_option = DateTypeOption::new(FieldType::DateTime);
let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let old_cell_data = initialize_date_cell(

View File

@ -1,8 +1,9 @@
use crate::entities::{DateCellDataPB, DateFilterPB, FieldType};
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::{
default_order, DateCellChangeset, DateCellData, DateFormat, TimeFormat, TypeOption,
TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform,
default_order, DateCellChangeset, DateCellData, DateCellDataWrapper, DateFormat, TimeFormat,
TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
TypeOptionTransform,
};
use chrono::format::strftime::StrftimeItems;
use chrono::{DateTime, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
@ -15,11 +16,14 @@ use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::str::FromStr;
// Date
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
/// The [DateTypeOption] is used by [FieldType::Date], [FieldType::UpdatedAt], and [FieldType::CreatedAt].
/// So, storing the field type is necessary to distinguish the field type.
/// Most of the cases, each [FieldType] has its own [TypeOption] implementation.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DateTypeOption {
pub date_format: DateFormat,
pub time_format: TimeFormat,
pub field_type: FieldType,
}
impl TypeOption for DateTypeOption {
@ -39,9 +43,14 @@ impl From<TypeOptionData> for DateTypeOption {
.get_i64_value("time_format")
.map(TimeFormat::from)
.unwrap_or_default();
let field_type = data
.get_i64_value("field_type")
.map(FieldType::from)
.unwrap_or(FieldType::DateTime);
Self {
date_format,
time_format,
field_type,
}
}
}
@ -51,6 +60,7 @@ impl From<DateTypeOption> for TypeOptionData {
TypeOptionDataBuilder::new()
.insert_i64_value("data_format", data.date_format.value())
.insert_i64_value("time_format", data.time_format.value())
.insert_i64_value("field_type", data.field_type.value())
.build()
}
}
@ -69,9 +79,12 @@ impl TypeOptionCellData for DateTypeOption {
}
impl DateTypeOption {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
pub fn new(field_type: FieldType) -> Self {
Self {
date_format: Default::default(),
time_format: Default::default(),
field_type,
}
}
fn today_desc_from_timestamp(&self, cell_data: DateCellData) -> DateCellDataPB {
@ -179,8 +192,8 @@ impl CellDataChangeset for DateTypeOption {
// old date cell data
let (previous_timestamp, include_time, timezone_id) = match cell {
None => (None, false, "".to_owned()),
Some(type_cell_data) => {
let cell_data = DateCellData::from(&type_cell_data);
Some(cell) => {
let cell_data = DateCellData::from(&cell);
(
cell_data.timestamp,
cell_data.include_time,
@ -201,7 +214,6 @@ impl CellDataChangeset for DateTypeOption {
// in the changeset without an accompanying time string, the old timestamp
// will simply be overwritten. Meaning, in order to change the day without
// changing the time, the old time string should be passed in as well.
let changeset_timestamp = changeset.date_timestamp();
// parse the time string, which is in the timezone corresponding to
@ -241,12 +253,14 @@ impl CellDataChangeset for DateTypeOption {
changeset_timestamp,
);
let date_cell_data = DateCellData {
let cell_data = DateCellData {
timestamp,
include_time,
timezone_id,
};
Ok((Cell::from(date_cell_data.clone()), date_cell_data))
let cell_wrapper: DateCellDataWrapper = (self.field_type.clone(), cell_data.clone()).into();
Ok((Cell::from(cell_wrapper), cell_data))
}
}

View File

@ -70,13 +70,29 @@ impl From<&Cell> for DateCellData {
}
}
impl From<DateCellData> for Cell {
fn from(data: DateCellData) -> Self {
/// Wrapper for DateCellData that also contains the field type.
/// Handy struct to use when you need to convert a DateCellData to a Cell.
pub struct DateCellDataWrapper {
data: DateCellData,
field_type: FieldType,
}
impl From<(FieldType, DateCellData)> for DateCellDataWrapper {
fn from((field_type, data): (FieldType, DateCellData)) -> Self {
Self { data, field_type }
}
}
impl From<DateCellDataWrapper> for Cell {
fn from(wrapper: DateCellDataWrapper) -> Self {
let (field_type, data) = (wrapper.field_type, wrapper.data);
let timestamp_string = match data.timestamp {
Some(timestamp) => timestamp.to_string(),
None => "".to_owned(),
};
new_cell_builder(FieldType::DateTime)
// Most of the case, don't use these keys in other places. Otherwise, we should define
// constants for them.
new_cell_builder(field_type)
.insert_str_value(CELL_DATA, timestamp_string)
.insert_bool_value("include_time", data.include_time)
.insert_str_value("timezone_id", data.timezone_id)
@ -84,6 +100,13 @@ impl From<DateCellData> for Cell {
}
}
impl From<DateCellData> for Cell {
fn from(data: DateCellData) -> Self {
let data: DateCellDataWrapper = (FieldType::DateTime, data).into();
Cell::from(data)
}
}
impl<'de> serde::Deserialize<'de> for DateCellData {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where

View File

@ -146,7 +146,7 @@ pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
FieldType::Number => {
NumberTypeOptionPB::try_from(bytes).map(|pb| NumberTypeOption::from(pb).into())
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
DateTypeOptionPB::try_from(bytes).map(|pb| DateTypeOption::from(pb).into())
},
FieldType::SingleSelect => {
@ -164,7 +164,7 @@ pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
},
};
result.unwrap_or_else(|_| default_type_option_data_for_type(field_type))
result.unwrap_or_else(|_| default_type_option_data_from_type(field_type))
}
pub fn type_option_to_pb(type_option: TypeOptionData, field_type: &FieldType) -> Bytes {
@ -181,7 +181,7 @@ pub fn type_option_to_pb(type_option: TypeOptionData, field_type: &FieldType) ->
.try_into()
.unwrap()
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
let date_type_option: DateTypeOption = type_option.into();
DateTypeOptionPB::from(date_type_option).try_into().unwrap()
},
@ -216,11 +216,16 @@ pub fn type_option_to_pb(type_option: TypeOptionData, field_type: &FieldType) ->
}
}
pub fn default_type_option_data_for_type(field_type: &FieldType) -> TypeOptionData {
pub fn default_type_option_data_from_type(field_type: &FieldType) -> TypeOptionData {
match field_type {
FieldType::RichText => RichTextTypeOption::default().into(),
FieldType::Number => NumberTypeOption::default().into(),
FieldType::DateTime => DateTypeOption::default().into(),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => DateTypeOption {
date_format: Default::default(),
time_format: Default::default(),
field_type: field_type.clone(),
}
.into(),
FieldType::SingleSelect => SingleSelectTypeOption::default().into(),
FieldType::MultiSelect => MultiSelectTypeOption::default().into(),
FieldType::Checkbox => CheckboxTypeOption::default().into(),

View File

@ -350,7 +350,7 @@ impl<'a> TypeOptionCellExt<'a> {
self.cell_data_cache.clone(),
)
}),
FieldType::DateTime => self
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => self
.field
.get_type_option::<DateTypeOption>(field_type)
.map(|type_option| {
@ -470,7 +470,7 @@ fn get_type_option_transform_handler(
FieldType::Number => {
Box::new(NumberTypeOption::from(type_option_data)) as Box<dyn TypeOptionTransformHandler>
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
Box::new(DateTypeOption::from(type_option_data)) as Box<dyn TypeOptionTransformHandler>
},
FieldType::SingleSelect => Box::new(SingleSelectTypeOption::from(type_option_data))

View File

@ -323,7 +323,7 @@ impl FilterController {
.write()
.insert(field_id, NumberFilterPB::from_filter(filter.as_ref()));
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
self
.cell_filter_cache
.write()

View File

@ -139,17 +139,7 @@ pub fn find_new_grouping_field(
///
pub fn default_group_setting(field: &Field) -> GroupSetting {
let field_id = field.id.clone();
let field_type = FieldType::from(field.field_type);
match field_type {
FieldType::RichText => GroupSetting::new(field_id, field.field_type, "".to_owned()),
FieldType::Number => GroupSetting::new(field_id, field.field_type, "".to_owned()),
FieldType::DateTime => GroupSetting::new(field_id, field.field_type, "".to_owned()),
FieldType::SingleSelect => GroupSetting::new(field_id, field.field_type, "".to_owned()),
FieldType::MultiSelect => GroupSetting::new(field_id, field.field_type, "".to_owned()),
FieldType::Checklist => GroupSetting::new(field_id, field.field_type, "".to_owned()),
FieldType::Checkbox => GroupSetting::new(field_id, field.field_type, "".to_owned()),
FieldType::URL => GroupSetting::new(field_id, field.field_type, "".to_owned()),
}
GroupSetting::new(field_id, field.field_type, "".to_owned())
}
pub fn make_no_status_group(field: &Field) -> Group {

View File

@ -1,6 +1,6 @@
use crate::entities::FieldType;
use crate::services::cell::CellBuilder;
use crate::services::field::default_type_option_data_for_type;
use crate::services::field::default_type_option_data_from_type;
use collab_database::database::{gen_database_id, gen_database_view_id, gen_field_id, gen_row_id};
use collab_database::fields::Field;
use collab_database::rows::CreateRowParams;
@ -73,7 +73,7 @@ fn database_from_fields_and_rows(fields_and_rows: FieldsRows) -> CreateDatabaseP
Ok(field) => field,
Err(_) => {
let field_type = FieldType::RichText;
let type_option_data = default_type_option_data_for_type(&field_type);
let type_option_data = default_type_option_data_from_type(&field_type);
let is_primary = index == 0;
Field::new(
gen_field_id(),

View File

@ -0,0 +1,2 @@
mod row_test;
mod script;

View File

@ -0,0 +1,53 @@
use crate::database::block_test::script::DatabaseRowTest;
use crate::database::block_test::script::RowScript::*;
use flowy_database2::entities::FieldType;
use flowy_database2::services::field::DateCellData;
#[tokio::test]
async fn set_created_at_field_on_create_row() {
let mut test = DatabaseRowTest::new().await;
let row_count = test.rows.len();
let before_create_timestamp = chrono::offset::Utc::now().timestamp();
test
.run_scripts(vec![CreateEmptyRow, AssertRowCount(row_count + 1)])
.await;
let after_create_timestamp = chrono::offset::Utc::now().timestamp();
let mut rows = test.rows.clone();
rows.sort_by(|r1, r2| r1.created_at.cmp(&r2.created_at));
let row = rows.last().unwrap();
let fields = test.fields.clone();
let created_at_field = fields
.iter()
.find(|&f| FieldType::from(f.field_type) == FieldType::CreatedAt)
.unwrap();
let cell = row.cells.cell_for_field_id(&created_at_field.id).unwrap();
let created_at_timestamp = DateCellData::from(cell).timestamp.unwrap();
assert!(
created_at_timestamp >= before_create_timestamp
&& created_at_timestamp <= after_create_timestamp,
"timestamp: {}, before: {}, after: {}",
created_at_timestamp,
before_create_timestamp,
after_create_timestamp
);
let updated_at_field = fields
.iter()
.find(|&f| FieldType::from(f.field_type) == FieldType::UpdatedAt)
.unwrap();
let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap();
let updated_at_timestamp = DateCellData::from(cell).timestamp.unwrap();
assert!(
updated_at_timestamp >= before_create_timestamp
&& updated_at_timestamp <= after_create_timestamp,
"timestamp: {}, before: {}, after: {}",
updated_at_timestamp,
before_create_timestamp,
after_create_timestamp
);
}

View File

@ -0,0 +1,59 @@
use crate::database::database_editor::DatabaseEditorTest;
use flowy_database2::entities::CreateRowParams;
pub enum RowScript {
CreateEmptyRow,
AssertRowCount(usize),
}
pub struct DatabaseRowTest {
inner: DatabaseEditorTest,
}
impl DatabaseRowTest {
pub async fn new() -> Self {
let editor_test = DatabaseEditorTest::new_grid().await;
Self { inner: editor_test }
}
pub async fn run_scripts(&mut self, scripts: Vec<RowScript>) {
for script in scripts {
self.run_script(script).await;
}
}
pub async fn run_script(&mut self, script: RowScript) {
match script {
RowScript::CreateEmptyRow => {
let params = CreateRowParams {
view_id: self.view_id.clone(),
start_row_id: None,
group_id: None,
cell_data_by_field_id: None,
};
let row_order = self.editor.create_row(params).await.unwrap().unwrap();
self
.row_by_row_id
.insert(row_order.id.to_string(), row_order.into());
self.rows = self.get_rows().await;
},
RowScript::AssertRowCount(expected_row_count) => {
assert_eq!(expected_row_count, self.rows.len());
},
}
}
}
impl std::ops::Deref for DatabaseRowTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for DatabaseRowTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

View File

@ -1,8 +1,8 @@
use flowy_database2::entities::{CellChangesetPB, FieldType};
use flowy_database2::services::cell::ToCellChangeset;
use flowy_database2::services::field::{
ChecklistTypeOption, MultiSelectTypeOption, SelectOptionCellChangeset, SingleSelectTypeOption,
StrCellData, URLCellData,
ChecklistTypeOption, DateCellData, MultiSelectTypeOption, SelectOptionCellChangeset,
SingleSelectTypeOption, StrCellData, URLCellData,
};
use crate::database::cell_test::script::CellScript::UpdateCell;
@ -22,7 +22,9 @@ async fn grid_cell_update() {
let cell_changeset = match field_type {
FieldType::RichText => "".to_string(),
FieldType::Number => "123".to_string(),
FieldType::DateTime => make_date_cell_string("123"),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
make_date_cell_string("123")
},
FieldType::SingleSelect => {
let type_option = field
.get_type_option::<SingleSelectTypeOption>(field.field_type)
@ -64,7 +66,7 @@ async fn grid_cell_update() {
}
#[tokio::test]
async fn text_cell_date_test() {
async fn text_cell_data_test() {
let test = DatabaseCellTest::new().await;
let text_field = test.get_first_field(FieldType::RichText);
@ -88,7 +90,7 @@ async fn text_cell_date_test() {
}
#[tokio::test]
async fn url_cell_date_test() {
async fn url_cell_data_test() {
let test = DatabaseCellTest::new().await;
let url_field = test.get_first_field(FieldType::URL);
let cells = test
@ -103,3 +105,83 @@ async fn url_cell_date_test() {
}
}
}
#[tokio::test]
async fn update_updated_at_field_on_other_cell_update() {
let mut test = DatabaseCellTest::new().await;
let updated_at_field = test.get_first_field(FieldType::UpdatedAt);
let text_field = test
.fields
.iter()
.find(|&f| FieldType::from(f.field_type) == FieldType::RichText)
.unwrap();
let before_update_timestamp = chrono::offset::Utc::now().timestamp();
test
.run_script(UpdateCell {
changeset: CellChangesetPB {
view_id: test.view_id.clone(),
row_id: test.rows[0].id.to_string(),
field_id: text_field.id.clone(),
cell_changeset: "change".to_string(),
},
is_err: false,
})
.await;
let after_update_timestamp = chrono::offset::Utc::now().timestamp();
let cells = test
.editor
.get_cells_for_field(&test.view_id, &updated_at_field.id)
.await;
assert!(cells.len() > 0);
for (i, cell) in cells.into_iter().enumerate() {
let timestamp = DateCellData::from(cell.as_ref()).timestamp.unwrap();
println!(
"{}, bf: {}, af: {}",
timestamp, before_update_timestamp, after_update_timestamp
);
match i {
0 => assert!(
timestamp >= before_update_timestamp && timestamp <= after_update_timestamp,
"{} >= {} && {} <= {}",
timestamp,
before_update_timestamp,
timestamp,
after_update_timestamp
),
1 => assert!(
timestamp <= before_update_timestamp,
"{} <= {}",
timestamp,
before_update_timestamp
),
2 => assert!(
timestamp <= before_update_timestamp,
"{} <= {}",
timestamp,
before_update_timestamp
),
3 => assert!(
timestamp <= before_update_timestamp,
"{} <= {}",
timestamp,
before_update_timestamp
),
4 => assert!(
timestamp <= before_update_timestamp,
"{} <= {}",
timestamp,
before_update_timestamp
),
5 => assert!(
timestamp <= before_update_timestamp,
"{} <= {}",
timestamp,
before_update_timestamp
),
_ => {},
}
}
}

View File

@ -277,6 +277,7 @@ impl<'a> TestRowBuilder<'a> {
time: Option<String>,
include_time: Option<bool>,
timezone_id: Option<String>,
field_type: &FieldType,
) -> String {
let value = serde_json::to_string(&DateCellChangeset {
date: Some(data.to_string()),
@ -285,7 +286,7 @@ impl<'a> TestRowBuilder<'a> {
timezone_id,
})
.unwrap();
let date_field = self.field_with_type(&FieldType::DateTime);
let date_field = self.field_with_type(field_type);
self.cell_build.insert_text_cell(&date_field.id, value);
date_field.id.clone()
}

View File

@ -30,6 +30,16 @@ async fn grid_create_field() {
},
];
test.run_scripts(scripts).await;
let (params, field) = create_date_field(&test.view_id(), FieldType::CreatedAt);
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]

View File

@ -1,8 +1,8 @@
use collab_database::fields::Field;
use flowy_database2::entities::{CreateFieldParams, FieldType};
use flowy_database2::services::field::{
type_option_to_pb, DateCellChangeset, FieldBuilder, RichTextTypeOption, SelectOption,
SingleSelectTypeOption,
type_option_to_pb, DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder,
RichTextTypeOption, SelectOption, SingleSelectTypeOption, TimeFormat,
};
pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, Field) {
@ -42,6 +42,39 @@ pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, Field) {
(params, single_select_field)
}
pub fn create_date_field(grid_id: &str, field_type: FieldType) -> (CreateFieldParams, Field) {
let date_type_option = DateTypeOption {
date_format: DateFormat::US,
time_format: TimeFormat::TwentyFourHour,
field_type: field_type.clone(),
};
let field: Field = match field_type {
FieldType::DateTime => FieldBuilder::new(field_type.clone(), date_type_option.clone())
.name("Date")
.visibility(true)
.build(),
FieldType::UpdatedAt => FieldBuilder::new(field_type.clone(), date_type_option.clone())
.name("Updated At")
.visibility(true)
.build(),
FieldType::CreatedAt => FieldBuilder::new(field_type.clone(), date_type_option.clone())
.name("Created At")
.visibility(true)
.build(),
_ => panic!("Unsupported group field type"),
};
let type_option_data = type_option_to_pb(date_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),
};
(params, field)
}
// The grid will contains all existing field types and there are three empty rows in this grid.
pub fn make_date_cell_string(s: &str) -> String {

View File

@ -38,14 +38,21 @@ pub fn make_test_board() -> DatabaseData {
.build();
fields.push(number_field);
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
// Date
let date_type_option = DateTypeOption {
date_format: DateFormat::US,
time_format: TimeFormat::TwentyFourHour,
field_type: field_type.clone(),
};
let name = match field_type {
FieldType::DateTime => "Time",
FieldType::UpdatedAt => "Updated At",
FieldType::CreatedAt => "Created At",
_ => "",
};
let date_field = FieldBuilder::new(field_type.clone(), date_type_option)
.name("Time")
.name(name)
.visibility(true)
.build();
fields.push(date_field);
@ -119,12 +126,14 @@ pub fn make_test_board() -> DatabaseData {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
},
@ -142,12 +151,14 @@ pub fn make_test_board() -> DatabaseData {
FieldType::RichText => row_builder.insert_text_cell("B"),
FieldType::Number => row_builder.insert_number_cell("2"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
},
@ -164,12 +175,14 @@ pub fn make_test_board() -> DatabaseData {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
},
@ -189,12 +202,14 @@ pub fn make_test_board() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::DateTime => row_builder.insert_date_cell(
"1668704685",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1668704685",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
},
@ -209,12 +224,14 @@ pub fn make_test_board() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell(
"1668359085",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1668359085",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(2))
},

View File

@ -51,6 +51,7 @@ pub fn make_test_calendar() -> DatabaseData {
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
_ => "".to_owned(),
};
@ -65,6 +66,7 @@ pub fn make_test_calendar() -> DatabaseData {
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
_ => "".to_owned(),
};
@ -79,6 +81,7 @@ pub fn make_test_calendar() -> DatabaseData {
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
_ => "".to_owned(),
};
@ -93,6 +96,7 @@ pub fn make_test_calendar() -> DatabaseData {
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
_ => "".to_owned(),
};
@ -107,6 +111,7 @@ pub fn make_test_calendar() -> DatabaseData {
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
_ => "".to_owned(),
};

View File

@ -39,14 +39,21 @@ pub fn make_test_grid() -> DatabaseData {
.build();
fields.push(number_field);
},
FieldType::DateTime => {
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
// Date
let date_type_option = DateTypeOption {
date_format: DateFormat::US,
time_format: TimeFormat::TwentyFourHour,
field_type: field_type.clone(),
};
let name = match field_type {
FieldType::DateTime => "Time",
FieldType::UpdatedAt => "Updated At",
FieldType::CreatedAt => "Created At",
_ => "",
};
let date_field = FieldBuilder::new(field_type.clone(), date_type_option)
.name("Time")
.name(name)
.visibility(true)
.build();
fields.push(date_field);
@ -118,12 +125,14 @@ pub fn make_test_grid() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
FieldType::DateTime => row_builder.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
@ -140,12 +149,14 @@ pub fn make_test_grid() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell(""),
FieldType::Number => row_builder.insert_number_cell("2"),
FieldType::DateTime => row_builder.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
@ -158,12 +169,14 @@ pub fn make_test_grid() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
FieldType::DateTime => row_builder.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1647251762",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
},
@ -180,12 +193,14 @@ pub fn make_test_grid() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("14"),
FieldType::DateTime => row_builder.insert_date_cell(
"1668704685",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1668704685",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
},
@ -199,12 +214,14 @@ pub fn make_test_grid() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell(
"1668359085",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1668359085",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
},
@ -219,12 +236,14 @@ pub fn make_test_grid() -> DatabaseData {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell("5"),
FieldType::DateTime => row_builder.insert_date_cell(
"1671938394",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
),
FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => row_builder
.insert_date_cell(
"1671938394",
None,
None,
Some(chrono_tz::Tz::Etc__GMTPlus8.to_string()),
&field_type,
),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
},

View File

@ -1,3 +1,4 @@
mod block_test;
mod cell_test;
mod database_editor;
mod field_test;