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", "textFieldName": "Text",
"checkboxFieldName": "Checkbox", "checkboxFieldName": "Checkbox",
"dateFieldName": "Date", "dateFieldName": "Date",
"updatedAtFieldName": "Updated At",
"createdAtFieldName": "Created At",
"numberFieldName": "Numbers", "numberFieldName": "Numbers",
"singleSelectFieldName": "Select", "singleSelectFieldName": "Select",
"multiSelectFieldName": "Multiselect", "multiSelectFieldName": "Multiselect",

View File

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

View File

@ -353,7 +353,13 @@ class RowDataBuilder {
} }
void insertDate(FieldInfo fieldInfo, DateTime date) { 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; final timestamp = date.millisecondsSinceEpoch ~/ 1000;
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString(); _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]] [[package]]
name = "appflowy-integrate" name = "appflowy-integrate"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1023,7 +1023,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1040,7 +1040,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-client-ws" name = "collab-client-ws"
version = "0.1.0" 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 = [ dependencies = [
"bytes", "bytes",
"collab-sync", "collab-sync",
@ -1058,7 +1058,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1083,7 +1083,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1095,7 +1095,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1112,7 +1112,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1130,7 +1130,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" 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 = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
@ -1150,7 +1150,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1180,7 +1180,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-sync" name = "collab-sync"
version = "0.1.0" 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 = [ dependencies = [
"bytes", "bytes",
"collab", "collab",

View File

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

View File

@ -492,6 +492,8 @@ pub enum FieldType {
Checkbox = 5, Checkbox = 5,
URL = 6, URL = 6,
Checklist = 7, Checklist = 7,
UpdatedAt = 8,
CreatedAt = 9,
} }
pub const RICH_TEXT_FIELD: FieldType = FieldType::RichText; 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 CHECKBOX_FIELD: FieldType = FieldType::Checkbox;
pub const URL_FIELD: FieldType = FieldType::URL; pub const URL_FIELD: FieldType = FieldType::URL;
pub const CHECKLIST_FIELD: FieldType = FieldType::Checklist; 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 { impl std::default::Default for FieldType {
fn default() -> Self { fn default() -> Self {
@ -529,9 +533,13 @@ impl From<&FieldType> for FieldType {
} }
impl FieldType { impl FieldType {
pub fn value(&self) -> i64 {
self.clone().into()
}
pub fn default_cell_width(&self) -> i32 { pub fn default_cell_width(&self) -> i32 {
match self { match self {
FieldType::DateTime => 180, FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => 180,
_ => 150, _ => 150,
} }
} }
@ -546,6 +554,8 @@ impl FieldType {
FieldType::Checkbox => "Checkbox", FieldType::Checkbox => "Checkbox",
FieldType::URL => "URL", FieldType::URL => "URL",
FieldType::Checklist => "Checklist", FieldType::Checklist => "Checklist",
FieldType::UpdatedAt => "Updated At",
FieldType::CreatedAt => "Created At",
}; };
s.to_string() s.to_string()
} }
@ -563,7 +573,7 @@ impl FieldType {
} }
pub fn is_date(&self) -> bool { 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 { pub fn is_single_select(&self) -> bool {
@ -605,6 +615,8 @@ impl From<FieldType> for i64 {
FieldType::Checkbox => 5, FieldType::Checkbox => 5,
FieldType::URL => 6, FieldType::URL => 6,
FieldType::Checklist => 7, 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 { let bytes: Bytes = match filter.field_type {
FieldType::RichText => TextFilterPB::from(filter).try_into().unwrap(), FieldType::RichText => TextFilterPB::from(filter).try_into().unwrap(),
FieldType::Number => NumberFilterPB::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::SingleSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(),
FieldType::MultiSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(), FieldType::MultiSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(),
FieldType::Checklist => ChecklistFilterPB::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; condition = filter.condition as u8;
content = filter.content; content = filter.content;
}, },
FieldType::DateTime => { FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
condition = filter.condition as u8; condition = filter.condition as u8;
content = DateFilterContentPB { content = DateFilterContentPB {

View File

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

View File

@ -4,7 +4,7 @@ use strum_macros::EnumIter;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use crate::entities::CellIdPB; use crate::entities::{CellIdPB, FieldType};
use crate::services::field::{DateFormat, DateTypeOption, TimeFormat}; use crate::services::field::{DateFormat, DateTypeOption, TimeFormat};
#[derive(Clone, Debug, Default, ProtoBuf)] #[derive(Clone, Debug, Default, ProtoBuf)]
@ -51,6 +51,9 @@ pub struct DateTypeOptionPB {
#[pb(index = 2)] #[pb(index = 2)]
pub time_format: TimeFormatPB, pub time_format: TimeFormatPB,
#[pb(index = 3)]
pub field_type: FieldType,
} }
impl From<DateTypeOption> for DateTypeOptionPB { impl From<DateTypeOption> for DateTypeOptionPB {
@ -58,6 +61,7 @@ impl From<DateTypeOption> for DateTypeOptionPB {
Self { Self {
date_format: data.date_format.into(), date_format: data.date_format.into(),
time_format: data.time_format.into(), time_format: data.time_format.into(),
field_type: data.field_type,
} }
} }
} }
@ -67,6 +71,7 @@ impl From<DateTypeOptionPB> for DateTypeOption {
Self { Self {
date_format: data.date_format.into(), date_format: data.date_format.into(),
time_format: data.time_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::fields::Field;
use collab_database::rows::{get_field_type_from_cell, Cell, Cells}; use collab_database::rows::{get_field_type_from_cell, Cell, Cells};
use lib_infra::util::timestamp;
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; 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() 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 { let cell_data = serde_json::to_string(&DateCellChangeset {
date: Some(timestamp.to_string()), date: Some(timestamp.to_string()),
time: None, time: None,
include_time: Some(false), include_time,
timezone_id: None, timezone_id: None,
}) })
.unwrap(); .unwrap();
@ -299,6 +300,7 @@ pub struct CellBuilder<'a> {
} }
impl<'a> 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 { pub fn with_cells(cell_by_field_id: HashMap<String, String>, fields: &'a [Field]) -> Self {
let field_maps = fields let field_maps = fields
.into_iter() .into_iter()
@ -306,7 +308,7 @@ impl<'a> CellBuilder<'a> {
.collect::<HashMap<String, &Field>>(); .collect::<HashMap<String, &Field>>();
let mut cells = Cells::new(); 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) { if let Some(field) = field_maps.get(&field_id) {
let field_type = FieldType::from(field.field_type); let field_type = FieldType::from(field.field_type);
match field_type { match field_type {
@ -318,9 +320,9 @@ impl<'a> CellBuilder<'a> {
cells.insert(field_id, insert_number_cell(num, field)); 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>() { 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 => { 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 } CellBuilder { cells, field_maps }
} }
@ -400,9 +415,10 @@ impl<'a> CellBuilder<'a> {
match self.field_maps.get(&field_id.to_owned()) { match self.field_maps.get(&field_id.to_owned()) {
None => tracing::warn!("Can't find the date field with id: {}", field_id), None => tracing::warn!("Can't find the date field with id: {}", field_id),
Some(field) => { Some(field) => {
self self.cells.insert(
.cells field_id.to_owned(),
.insert(field_id.to_owned(), insert_date_cell(timestamp, field)); insert_date_cell(timestamp, Some(false), field),
);
}, },
} }
} }

View File

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

View File

@ -23,15 +23,15 @@ use crate::entities::{
}; };
use crate::notification::{send_notification, DatabaseNotification}; use crate::notification::{send_notification, DatabaseNotification};
use crate::services::cell::{ 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::util::database_view_setting_pb_from_view;
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews}; use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
use crate::services::field::{ use crate::services::field::{
default_type_option_data_for_type, default_type_option_data_from_type, default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
select_type_option_from_field, transform_type_option, type_option_data_from_pb_or_default, type_option_data_from_pb_or_default, type_option_to_pb, SelectOptionCellChangeset,
type_option_to_pb, SelectOptionCellChangeset, SelectOptionIds, TypeOptionCellDataHandler, SelectOptionIds, TypeOptionCellDataHandler, TypeOptionCellExt,
TypeOptionCellExt,
}; };
use crate::services::filter::Filter; use crate::services::filter::Filter;
use crate::services::group::{default_group_setting, GroupSetting, RowChangeset}; 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 old_type_option = field.get_any_type_option(old_field_type.clone());
let new_type_option = field let new_type_option = field
.get_any_type_option(new_field_type) .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( let transformed_type_option = transform_type_option(
&new_type_option, &new_type_option,
@ -352,7 +352,7 @@ impl DatabaseEditor {
) -> (Field, Bytes) { ) -> (Field, Bytes) {
let name = field_type.default_name(); let name = field_type.default_name();
let type_option_data = match type_option_data { 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), Some(type_option_data) => type_option_data_from_pb_or_default(type_option_data, field_type),
}; };
let (index, field) = let (index, field) =
@ -492,9 +492,23 @@ impl DatabaseEditor {
) -> FlowyResult<()> { ) -> FlowyResult<()> {
// Get the old row before updating the cell. It would be better to get the old cell // 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) }; 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| { self.database.lock().update_row(&row_id, |row_update| {
row_update.update_cells(|cell_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 { // Collect all the updated field's id. Notify the frontend that all of them have been updated.
view_id: view_id.to_string(), let mut updated_field_ids = updated_at_fields
row_id: row_id.into_inner(), .into_iter()
field_id: field_id.to_string(), .map(|field| field.id)
}]) .collect::<Vec<String>>();
.await; 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(()) Ok(())
} }

View File

@ -1,9 +1,7 @@
mod field_builder; mod field_builder;
mod field_operation; mod field_operation;
mod type_option_builder;
mod type_options; mod type_options;
pub use field_builder::*; pub use field_builder::*;
pub use field_operation::*; pub use field_operation::*;
pub use type_option_builder::*;
pub use type_options::*; 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] #[test]
fn date_type_option_date_format_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(); let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
for date_format in DateFormat::iter() { for date_format in DateFormat::iter() {
type_option.date_format = date_format; type_option.date_format = date_format;
@ -95,7 +95,7 @@ mod tests {
#[test] #[test]
fn date_type_option_different_time_format_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(); let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
for time_format in TimeFormat::iter() { for time_format in TimeFormat::iter() {
@ -183,8 +183,8 @@ mod tests {
#[test] #[test]
fn date_type_option_invalid_date_str_test() { fn date_type_option_invalid_date_str_test() {
let type_option = DateTypeOption::default();
let field_type = FieldType::DateTime; let field_type = FieldType::DateTime;
let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build(); let field = FieldBuilder::from_field_type(field_type).build();
assert_date( assert_date(
&type_option, &type_option,
@ -203,8 +203,9 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn date_type_option_invalid_include_time_str_test() { fn date_type_option_invalid_include_time_str_test() {
let type_option = DateTypeOption::new(); let field_type = FieldType::DateTime;
let field = FieldBuilder::from_field_type(FieldType::DateTime).build(); let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build();
assert_date( assert_date(
&type_option, &type_option,
@ -223,8 +224,9 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn date_type_option_empty_include_time_str_test() { fn date_type_option_empty_include_time_str_test() {
let type_option = DateTypeOption::new(); let field_type = FieldType::DateTime;
let field = FieldBuilder::from_field_type(FieldType::DateTime).build(); let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build();
assert_date( assert_date(
&type_option, &type_option,
@ -242,8 +244,8 @@ mod tests {
#[test] #[test]
fn date_type_midnight_include_time_str_test() { fn date_type_midnight_include_time_str_test() {
let type_option = DateTypeOption::new();
let field_type = FieldType::DateTime; let field_type = FieldType::DateTime;
let type_option = DateTypeOption::new(field_type.clone());
let field = FieldBuilder::from_field_type(field_type).build(); let field = FieldBuilder::from_field_type(field_type).build();
assert_date( assert_date(
&type_option, &type_option,
@ -264,7 +266,7 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() { 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(); let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
assert_date( assert_date(
&type_option, &type_option,
@ -285,9 +287,10 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn date_type_option_twenty_four_hours_include_time_str_in_twelve_hours_format() { 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; 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( assert_date(
&type_option, &type_option,
@ -335,7 +338,7 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn update_date_keep_time() { 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 field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let old_cell_data = initialize_date_cell( let old_cell_data = initialize_date_cell(
@ -363,7 +366,7 @@ mod tests {
#[test] #[test]
fn update_time_keep_date() { 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 field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let old_cell_data = initialize_date_cell( let old_cell_data = initialize_date_cell(
@ -391,7 +394,7 @@ mod tests {
#[test] #[test]
fn timezone_no_daylight_saving_time() { 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(); let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
assert_date( assert_date(
@ -422,7 +425,7 @@ mod tests {
#[test] #[test]
fn timezone_with_daylight_saving_time() { 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(); let field = FieldBuilder::from_field_type(FieldType::DateTime).build();
assert_date( assert_date(
@ -453,7 +456,7 @@ mod tests {
#[test] #[test]
fn change_timezone() { 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 field = FieldBuilder::from_field_type(FieldType::DateTime).build();
let old_cell_data = initialize_date_cell( let old_cell_data = initialize_date_cell(

View File

@ -1,8 +1,9 @@
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::{
default_order, DateCellChangeset, DateCellData, DateFormat, TimeFormat, TypeOption, default_order, DateCellChangeset, DateCellData, DateCellDataWrapper, DateFormat, TimeFormat,
TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
TypeOptionTransform,
}; };
use chrono::format::strftime::StrftimeItems; use chrono::format::strftime::StrftimeItems;
use chrono::{DateTime, Local, NaiveDateTime, NaiveTime, Offset, TimeZone}; use chrono::{DateTime, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
@ -15,11 +16,14 @@ use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::str::FromStr; use std::str::FromStr;
// Date /// The [DateTypeOption] is used by [FieldType::Date], [FieldType::UpdatedAt], and [FieldType::CreatedAt].
#[derive(Clone, Debug, Default, Serialize, Deserialize)] /// 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 struct DateTypeOption {
pub date_format: DateFormat, pub date_format: DateFormat,
pub time_format: TimeFormat, pub time_format: TimeFormat,
pub field_type: FieldType,
} }
impl TypeOption for DateTypeOption { impl TypeOption for DateTypeOption {
@ -39,9 +43,14 @@ impl From<TypeOptionData> for DateTypeOption {
.get_i64_value("time_format") .get_i64_value("time_format")
.map(TimeFormat::from) .map(TimeFormat::from)
.unwrap_or_default(); .unwrap_or_default();
let field_type = data
.get_i64_value("field_type")
.map(FieldType::from)
.unwrap_or(FieldType::DateTime);
Self { Self {
date_format, date_format,
time_format, time_format,
field_type,
} }
} }
} }
@ -51,6 +60,7 @@ impl From<DateTypeOption> for TypeOptionData {
TypeOptionDataBuilder::new() TypeOptionDataBuilder::new()
.insert_i64_value("data_format", data.date_format.value()) .insert_i64_value("data_format", data.date_format.value())
.insert_i64_value("time_format", data.time_format.value()) .insert_i64_value("time_format", data.time_format.value())
.insert_i64_value("field_type", data.field_type.value())
.build() .build()
} }
} }
@ -69,9 +79,12 @@ impl TypeOptionCellData for DateTypeOption {
} }
impl DateTypeOption { impl DateTypeOption {
#[allow(dead_code)] pub fn new(field_type: FieldType) -> Self {
pub fn new() -> Self { Self {
Self::default() date_format: Default::default(),
time_format: Default::default(),
field_type,
}
} }
fn today_desc_from_timestamp(&self, cell_data: DateCellData) -> DateCellDataPB { fn today_desc_from_timestamp(&self, cell_data: DateCellData) -> DateCellDataPB {
@ -179,8 +192,8 @@ impl CellDataChangeset for DateTypeOption {
// old date cell data // old date cell data
let (previous_timestamp, include_time, timezone_id) = match cell { let (previous_timestamp, include_time, timezone_id) = match cell {
None => (None, false, "".to_owned()), None => (None, false, "".to_owned()),
Some(type_cell_data) => { Some(cell) => {
let cell_data = DateCellData::from(&type_cell_data); let cell_data = DateCellData::from(&cell);
( (
cell_data.timestamp, cell_data.timestamp,
cell_data.include_time, cell_data.include_time,
@ -201,7 +214,6 @@ impl CellDataChangeset for DateTypeOption {
// in the changeset without an accompanying time string, the old timestamp // in the changeset without an accompanying time string, the old timestamp
// will simply be overwritten. Meaning, in order to change the day without // 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. // changing the time, the old time string should be passed in as well.
let changeset_timestamp = changeset.date_timestamp(); let changeset_timestamp = changeset.date_timestamp();
// parse the time string, which is in the timezone corresponding to // parse the time string, which is in the timezone corresponding to
@ -241,12 +253,14 @@ impl CellDataChangeset for DateTypeOption {
changeset_timestamp, changeset_timestamp,
); );
let date_cell_data = DateCellData { let cell_data = DateCellData {
timestamp, timestamp,
include_time, include_time,
timezone_id, 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 { /// Wrapper for DateCellData that also contains the field type.
fn from(data: DateCellData) -> Self { /// 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 { let timestamp_string = match data.timestamp {
Some(timestamp) => timestamp.to_string(), Some(timestamp) => timestamp.to_string(),
None => "".to_owned(), 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_str_value(CELL_DATA, timestamp_string)
.insert_bool_value("include_time", data.include_time) .insert_bool_value("include_time", data.include_time)
.insert_str_value("timezone_id", data.timezone_id) .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 { impl<'de> serde::Deserialize<'de> for DateCellData {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where where

View File

@ -146,7 +146,7 @@ pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
FieldType::Number => { FieldType::Number => {
NumberTypeOptionPB::try_from(bytes).map(|pb| NumberTypeOption::from(pb).into()) 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()) DateTypeOptionPB::try_from(bytes).map(|pb| DateTypeOption::from(pb).into())
}, },
FieldType::SingleSelect => { 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 { 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() .try_into()
.unwrap() .unwrap()
}, },
FieldType::DateTime => { FieldType::DateTime | FieldType::UpdatedAt | FieldType::CreatedAt => {
let date_type_option: DateTypeOption = type_option.into(); let date_type_option: DateTypeOption = type_option.into();
DateTypeOptionPB::from(date_type_option).try_into().unwrap() 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 { match field_type {
FieldType::RichText => RichTextTypeOption::default().into(), FieldType::RichText => RichTextTypeOption::default().into(),
FieldType::Number => NumberTypeOption::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::SingleSelect => SingleSelectTypeOption::default().into(),
FieldType::MultiSelect => MultiSelectTypeOption::default().into(), FieldType::MultiSelect => MultiSelectTypeOption::default().into(),
FieldType::Checkbox => CheckboxTypeOption::default().into(), FieldType::Checkbox => CheckboxTypeOption::default().into(),

View File

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

View File

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

View File

@ -139,17 +139,7 @@ pub fn find_new_grouping_field(
/// ///
pub fn default_group_setting(field: &Field) -> GroupSetting { pub fn default_group_setting(field: &Field) -> GroupSetting {
let field_id = field.id.clone(); let field_id = field.id.clone();
let field_type = FieldType::from(field.field_type); GroupSetting::new(field_id, field.field_type, "".to_owned())
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()),
}
} }
pub fn make_no_status_group(field: &Field) -> Group { pub fn make_no_status_group(field: &Field) -> Group {

View File

@ -1,6 +1,6 @@
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::services::cell::CellBuilder; 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::database::{gen_database_id, gen_database_view_id, gen_field_id, gen_row_id};
use collab_database::fields::Field; use collab_database::fields::Field;
use collab_database::rows::CreateRowParams; use collab_database::rows::CreateRowParams;
@ -73,7 +73,7 @@ fn database_from_fields_and_rows(fields_and_rows: FieldsRows) -> CreateDatabaseP
Ok(field) => field, Ok(field) => field,
Err(_) => { Err(_) => {
let field_type = FieldType::RichText; 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; let is_primary = index == 0;
Field::new( Field::new(
gen_field_id(), 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::entities::{CellChangesetPB, FieldType};
use flowy_database2::services::cell::ToCellChangeset; use flowy_database2::services::cell::ToCellChangeset;
use flowy_database2::services::field::{ use flowy_database2::services::field::{
ChecklistTypeOption, MultiSelectTypeOption, SelectOptionCellChangeset, SingleSelectTypeOption, ChecklistTypeOption, DateCellData, MultiSelectTypeOption, SelectOptionCellChangeset,
StrCellData, URLCellData, SingleSelectTypeOption, StrCellData, URLCellData,
}; };
use crate::database::cell_test::script::CellScript::UpdateCell; use crate::database::cell_test::script::CellScript::UpdateCell;
@ -22,7 +22,9 @@ async fn grid_cell_update() {
let cell_changeset = match field_type { let cell_changeset = match field_type {
FieldType::RichText => "".to_string(), FieldType::RichText => "".to_string(),
FieldType::Number => "123".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 => { FieldType::SingleSelect => {
let type_option = field let type_option = field
.get_type_option::<SingleSelectTypeOption>(field.field_type) .get_type_option::<SingleSelectTypeOption>(field.field_type)
@ -64,7 +66,7 @@ async fn grid_cell_update() {
} }
#[tokio::test] #[tokio::test]
async fn text_cell_date_test() { async fn text_cell_data_test() {
let test = DatabaseCellTest::new().await; let test = DatabaseCellTest::new().await;
let text_field = test.get_first_field(FieldType::RichText); let text_field = test.get_first_field(FieldType::RichText);
@ -88,7 +90,7 @@ async fn text_cell_date_test() {
} }
#[tokio::test] #[tokio::test]
async fn url_cell_date_test() { async fn url_cell_data_test() {
let test = DatabaseCellTest::new().await; let test = DatabaseCellTest::new().await;
let url_field = test.get_first_field(FieldType::URL); let url_field = test.get_first_field(FieldType::URL);
let cells = test 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>, time: Option<String>,
include_time: Option<bool>, include_time: Option<bool>,
timezone_id: Option<String>, timezone_id: Option<String>,
field_type: &FieldType,
) -> String { ) -> String {
let value = serde_json::to_string(&DateCellChangeset { let value = serde_json::to_string(&DateCellChangeset {
date: Some(data.to_string()), date: Some(data.to_string()),
@ -285,7 +286,7 @@ impl<'a> TestRowBuilder<'a> {
timezone_id, timezone_id,
}) })
.unwrap(); .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); self.cell_build.insert_text_cell(&date_field.id, value);
date_field.id.clone() date_field.id.clone()
} }

View File

@ -30,6 +30,16 @@ async fn grid_create_field() {
}, },
]; ];
test.run_scripts(scripts).await; 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] #[tokio::test]

View File

@ -1,8 +1,8 @@
use collab_database::fields::Field; use collab_database::fields::Field;
use flowy_database2::entities::{CreateFieldParams, FieldType}; use flowy_database2::entities::{CreateFieldParams, FieldType};
use flowy_database2::services::field::{ use flowy_database2::services::field::{
type_option_to_pb, DateCellChangeset, FieldBuilder, RichTextTypeOption, SelectOption, type_option_to_pb, DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder,
SingleSelectTypeOption, RichTextTypeOption, SelectOption, SingleSelectTypeOption, TimeFormat,
}; };
pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, Field) { 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) (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. // 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 { pub fn make_date_cell_string(s: &str) -> String {

View File

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

View File

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

View File

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