Browse Source

refactor: updated at and created at (#2692)

* refactor: updated at and created at

* chore: update patch ref

* ci: fix tests
Nathan.fooo 1 year ago
parent
commit
edd58ede45

+ 6 - 6
frontend/appflowy_tauri/src-tauri/Cargo.toml

@@ -34,12 +34,12 @@ default = ["custom-protocol"]
 custom-protocol = ["tauri/custom-protocol"]
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
-collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
 
 #collab = { path = "../../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

+ 10 - 10
frontend/rust-lib/Cargo.lock

@@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "anyhow",
  "collab",
@@ -887,7 +887,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "anyhow",
  "bytes",
@@ -905,7 +905,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -923,7 +923,7 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -949,7 +949,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -961,7 +961,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "anyhow",
  "collab",
@@ -978,7 +978,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "anyhow",
  "collab",
@@ -997,7 +997,7 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "bincode",
  "chrono",
@@ -1017,7 +1017,7 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1047,7 +1047,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=13b178#13b17802de31e75255b4303914042bdbb04361b2"
 dependencies = [
  "bytes",
  "collab",

+ 5 - 5
frontend/rust-lib/Cargo.toml

@@ -33,11 +33,11 @@ opt-level = 3
 incremental = false
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373"  }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373"  }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178"  }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178"  }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "13b178" }
 
 #collab = { path = "../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }

+ 23 - 20
frontend/rust-lib/flowy-database2/src/entities/field_entities.rs

@@ -496,17 +496,6 @@ pub enum FieldType {
   CreatedTime = 9,
 }
 
-pub const RICH_TEXT_FIELD: FieldType = FieldType::RichText;
-pub const NUMBER_FIELD: FieldType = FieldType::Number;
-pub const DATE_FIELD: FieldType = FieldType::DateTime;
-pub const SINGLE_SELECT_FIELD: FieldType = FieldType::SingleSelect;
-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::LastEditedTime;
-pub const CREATED_AT_FIELD: FieldType = FieldType::CreatedTime;
-
 impl std::default::Default for FieldType {
   fn default() -> Self {
     FieldType::RichText
@@ -561,44 +550,58 @@ impl FieldType {
   }
 
   pub fn is_number(&self) -> bool {
-    self == &NUMBER_FIELD
+    matches!(self, FieldType::Number)
   }
 
   pub fn is_text(&self) -> bool {
-    self == &RICH_TEXT_FIELD
+    matches!(self, FieldType::RichText)
   }
 
   pub fn is_checkbox(&self) -> bool {
-    self == &CHECKBOX_FIELD
+    matches!(self, FieldType::Checkbox)
   }
 
   pub fn is_date(&self) -> bool {
-    self == &DATE_FIELD || self == &UPDATED_AT_FIELD || self == &CREATED_AT_FIELD
+    matches!(self, FieldType::DateTime)
+      || matches!(self, FieldType::LastEditedTime)
+      || matches!(self, FieldType::CreatedTime)
   }
 
   pub fn is_single_select(&self) -> bool {
-    self == &SINGLE_SELECT_FIELD
+    matches!(self, FieldType::SingleSelect)
   }
 
   pub fn is_multi_select(&self) -> bool {
-    self == &MULTI_SELECT_FIELD
+    matches!(self, FieldType::MultiSelect)
+  }
+
+  pub fn is_last_edited_time(&self) -> bool {
+    matches!(self, FieldType::LastEditedTime)
+  }
+
+  pub fn is_created_time(&self) -> bool {
+    matches!(self, FieldType::CreatedTime)
   }
 
   pub fn is_url(&self) -> bool {
-    self == &URL_FIELD
+    matches!(self, FieldType::URL)
   }
 
   pub fn is_select_option(&self) -> bool {
-    self == &MULTI_SELECT_FIELD || self == &SINGLE_SELECT_FIELD
+    self.is_single_select() || self.is_multi_select()
   }
 
   pub fn is_checklist(&self) -> bool {
-    self == &CHECKLIST_FIELD
+    matches!(self, FieldType::Checklist)
   }
 
   pub fn can_be_group(&self) -> bool {
     self.is_select_option() || self.is_checkbox() || self.is_url()
   }
+
+  pub fn is_auto_update(&self) -> bool {
+    self.is_last_edited_time()
+  }
 }
 
 impl_into_field_type!(i64);

+ 5 - 5
frontend/rust-lib/flowy-database2/src/event_handler.rs

@@ -1,16 +1,15 @@
-use collab_database::database::gen_row_id;
 use std::sync::Arc;
 
+use collab_database::database::gen_row_id;
 use collab_database::rows::RowId;
-use lib_infra::util::timestamp;
 
 use flowy_error::{FlowyError, FlowyResult};
 use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
+use lib_infra::util::timestamp;
 
 use crate::entities::*;
 use crate::manager::DatabaseManager2;
 use crate::services::cell::CellBuilder;
-
 use crate::services::field::checklist_type_option::ChecklistCellChangeset;
 use crate::services::field::{
   type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
@@ -373,8 +372,9 @@ pub(crate) async fn get_cell_handler(
   let params: CellIdParams = data.into_inner().try_into()?;
   let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
   let cell = database_editor
-    .get_cell(&params.field_id, params.row_id)
-    .await;
+    .get_cell_pb(&params.field_id, &params.row_id)
+    .await
+    .unwrap_or_else(|| CellPB::empty(&params.field_id, params.row_id.into_inner()));
   data_result_ok(cell)
 }
 

+ 4 - 20
frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs

@@ -3,7 +3,6 @@ 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};
 
@@ -298,11 +297,6 @@ where
     }
   }
 }
-// impl std::convert::From<String> for AnyCellChangeset<String> {
-//     fn from(s: String) -> Self {
-//         AnyCellChangeset(Some(s))
-//     }
-// }
 
 pub struct CellBuilder<'a> {
   cells: Cells,
@@ -330,11 +324,14 @@ impl<'a> CellBuilder<'a> {
               cells.insert(field_id, insert_number_cell(num, field));
             }
           },
-          FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => {
+          FieldType::DateTime => {
             if let Ok(timestamp) = cell_str.parse::<i64>() {
               cells.insert(field_id, insert_date_cell(timestamp, Some(false), field));
             }
           },
+          FieldType::LastEditedTime | FieldType::CreatedTime => {
+            tracing::warn!("Shouldn't insert cell data to cell whose field type is LastEditedTime or CreatedTime");
+          },
           FieldType::SingleSelect | FieldType::MultiSelect => {
             if let Ok(ids) = SelectOptionIds::from_cell_str(&cell_str) {
               cells.insert(field_id, insert_select_option_cell(ids.into_inner(), field));
@@ -357,19 +354,6 @@ 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::LastEditedTime || field_type == FieldType::CreatedTime {
-          cells.insert(
-            field.id.clone(),
-            insert_date_cell(timestamp(), Some(true), field),
-          );
-        }
-      }
-    }
     CellBuilder { cells, field_maps }
   }
 

+ 91 - 39
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs

@@ -3,7 +3,7 @@ use std::ops::Deref;
 use std::sync::Arc;
 
 use bytes::Bytes;
-use collab_database::database::{timestamp, Database as InnerDatabase};
+use collab_database::database::Database as InnerDatabase;
 use collab_database::fields::{Field, TypeOptionData};
 use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowId};
 use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
@@ -24,22 +24,20 @@ use crate::entities::{
 };
 use crate::notification::{send_notification, DatabaseNotification};
 use crate::services::cell::{
-  apply_cell_changeset, get_cell_protobuf, insert_date_cell, AnyTypeCache, CellCache,
-  ToCellChangeset,
+  apply_cell_changeset, get_cell_protobuf, AnyTypeCache, 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::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
 use crate::services::field::{
   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,
+  type_option_data_from_pb_or_default, type_option_to_pb, DateCellData, SelectOptionCellChangeset,
   SelectOptionIds, TypeOptionCellDataHandler, TypeOptionCellExt,
 };
 use crate::services::filter::Filter;
 use crate::services::group::{
   default_group_setting, GroupSetting, GroupSettingChangeset, RowChangeset,
 };
-
 use crate::services::share::csv::{CSVExport, CSVFormat};
 use crate::services::sort::Sort;
 
@@ -451,31 +449,84 @@ impl DatabaseEditor {
     }
   }
 
-  pub async fn get_cell(&self, field_id: &str, row_id: RowId) -> CellPB {
+  pub async fn get_cell(&self, field_id: &str, row_id: &RowId) -> Option<Cell> {
+    let database = self.database.lock();
+    let field = database.fields.get_field(field_id)?;
+    let field_type = FieldType::from(field.field_type);
+    // If the cell data is referenced, return the reference data. Otherwise, return an empty cell.
+    match field_type {
+      FieldType::LastEditedTime | FieldType::CreatedTime => database
+        .get_row(row_id)
+        .map(|row| {
+          if field_type.is_created_time() {
+            DateCellData::new(row.created_at, true)
+          } else {
+            DateCellData::new(row.modified_at, true)
+          }
+        })
+        .map(Cell::from),
+      _ => database.get_cell(field_id, row_id).cell,
+    }
+  }
+
+  pub async fn get_cell_pb(&self, field_id: &str, row_id: &RowId) -> Option<CellPB> {
     let (field, cell) = {
       let database = self.database.lock();
-      let field = database.fields.get_field(field_id);
-      let cell = database.get_cell(field_id, &row_id).cell;
+      let field = database.fields.get_field(field_id)?;
+      let field_type = FieldType::from(field.field_type);
+      // If the cell data is referenced, return the reference data. Otherwise, return an empty cell.
+      let cell = match field_type {
+        FieldType::LastEditedTime | FieldType::CreatedTime => database
+          .get_row(row_id)
+          .map(|row| {
+            if field_type.is_created_time() {
+              DateCellData::new(row.created_at, true)
+            } else {
+              DateCellData::new(row.modified_at, true)
+            }
+          })
+          .map(Cell::from),
+        _ => database.get_cell(field_id, row_id).cell,
+      }?;
+
       (field, cell)
     };
 
-    match (field, cell) {
-      (Some(field), Some(cell)) => {
-        let field_type = FieldType::from(field.field_type);
-        let cell_bytes = get_cell_protobuf(&cell, &field, Some(self.cell_cache.clone()));
-        CellPB {
-          field_id: field_id.to_string(),
-          row_id: row_id.into(),
-          data: cell_bytes.to_vec(),
-          field_type: Some(field_type),
-        }
-      },
-      _ => CellPB::empty(field_id, row_id.into_inner()),
-    }
+    let field_type = FieldType::from(field.field_type);
+    let cell_bytes = get_cell_protobuf(&cell, &field, Some(self.cell_cache.clone()));
+    Some(CellPB {
+      field_id: field_id.to_string(),
+      row_id: row_id.clone().into(),
+      data: cell_bytes.to_vec(),
+      field_type: Some(field_type),
+    })
   }
 
   pub async fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Vec<RowCell> {
-    self.database.lock().get_cells_for_field(view_id, field_id)
+    let database = self.database.lock();
+    if let Some(field) = database.fields.get_field(field_id) {
+      let field_type = FieldType::from(field.field_type);
+      match field_type {
+        FieldType::LastEditedTime | FieldType::CreatedTime => database
+          .get_rows_for_view(view_id)
+          .into_iter()
+          .map(|row| {
+            let data = if field_type.is_created_time() {
+              DateCellData::new(row.created_at, true)
+            } else {
+              DateCellData::new(row.modified_at, true)
+            };
+            RowCell {
+              row_id: row.id,
+              cell: Some(Cell::from(data)),
+            }
+          })
+          .collect(),
+        _ => database.get_cells_for_field(view_id, field_id),
+      }
+    } else {
+      vec![]
+    }
   }
 
   pub async fn update_cell_with_changeset<T>(
@@ -516,22 +567,13 @@ impl DatabaseEditor {
     // 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::LastEditedTime)
-      .collect::<Vec<Field>>();
+    // Get all auto updated fields. It will be used to notify the frontend
+    // that the fields have been updated.
+    let auto_updated_fields = self.get_auto_updated_fields(view_id);
 
     self.database.lock().update_row(&row_id, |row_update| {
       row_update.update_cells(|cell_update| {
-        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));
-        }
+        cell_update.insert(field_id, new_cell);
       });
     });
 
@@ -552,12 +594,12 @@ impl DatabaseEditor {
     }
 
     // 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
+    let mut auto_updated_field_ids = auto_updated_fields
       .into_iter()
       .map(|field| field.id)
       .collect::<Vec<String>>();
-    updated_field_ids.push(field_id.to_string());
-    let changeset = updated_field_ids
+    auto_updated_field_ids.push(field_id.to_string());
+    let changeset = auto_updated_field_ids
       .into_iter()
       .map(|field_id| CellChangesetNotifyPB {
         view_id: view_id.to_string(),
@@ -884,7 +926,7 @@ impl DatabaseEditor {
     let view = database_view
       .get_view()
       .await
-      .ok_or(FlowyError::record_not_found())?;
+      .ok_or_else(|| FlowyError::record_not_found())?;
     let rows = database_view.v_get_rows().await;
     let (database_id, fields) = {
       let database = self.database.lock();
@@ -921,6 +963,16 @@ impl DatabaseEditor {
     .map_err(internal_error)??;
     Ok(csv)
   }
+
+  fn get_auto_updated_fields(&self, view_id: &str) -> Vec<Field> {
+    self
+      .database
+      .lock()
+      .get_fields(view_id, None)
+      .into_iter()
+      .filter(|f| FieldType::from(f.field_type).is_auto_update())
+      .collect::<Vec<Field>>()
+  }
 }
 
 pub(crate) async fn notify_did_update_cell(changesets: Vec<CellChangesetNotifyPB>) {

+ 3 - 2
frontend/rust-lib/flowy-database2/src/services/database/util.rs

@@ -1,3 +1,5 @@
+use collab_database::views::DatabaseView;
+
 use crate::entities::{
   CalendarLayoutSettingPB, DatabaseLayoutPB, DatabaseLayoutSettingPB, DatabaseViewSettingPB,
   FilterPB, GroupSettingPB, SortPB,
@@ -6,10 +8,9 @@ use crate::services::filter::Filter;
 use crate::services::group::GroupSetting;
 use crate::services::setting::CalendarLayoutSetting;
 use crate::services::sort::Sort;
-use collab_database::views::DatabaseView;
 
 pub(crate) fn database_view_setting_pb_from_view(view: DatabaseView) -> DatabaseViewSettingPB {
-  let layout_type: DatabaseLayoutPB = view.layout.clone().into();
+  let layout_type: DatabaseLayoutPB = view.layout.into();
   let layout_setting = if let Some(layout_setting) = view.layout_settings.get(&view.layout) {
     let calendar_setting =
       CalendarLayoutSettingPB::from(CalendarLayoutSetting::from(layout_setting.clone()));

+ 9 - 0
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs

@@ -52,6 +52,15 @@ pub struct DateCellData {
   pub include_time: bool,
 }
 
+impl DateCellData {
+  pub fn new(timestamp: i64, include_time: bool) -> Self {
+    Self {
+      timestamp: Some(timestamp),
+      include_time,
+    }
+  }
+}
+
 impl From<&Cell> for DateCellData {
   fn from(cell: &Cell) -> Self {
     let timestamp = cell

+ 10 - 3
frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs

@@ -16,8 +16,8 @@ use crate::entities::{
 use crate::services::cell::{CellDataDecoder, FromCellChangeset, ToCellChangeset};
 use crate::services::field::checklist_type_option::ChecklistTypeOption;
 use crate::services::field::{
-  CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
-  SingleSelectTypeOption, URLTypeOption,
+  CheckboxTypeOption, DateFormat, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
+  RichTextTypeOption, SingleSelectTypeOption, TimeFormat, URLTypeOption,
 };
 use crate::services::filter::FromFilterString;
 
@@ -220,11 +220,18 @@ pub fn default_type_option_data_from_type(field_type: &FieldType) -> TypeOptionD
   match field_type {
     FieldType::RichText => RichTextTypeOption::default().into(),
     FieldType::Number => NumberTypeOption::default().into(),
-    FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => DateTypeOption {
+    FieldType::DateTime => DateTypeOption {
       field_type: field_type.clone(),
       ..Default::default()
     }
     .into(),
+    FieldType::LastEditedTime | FieldType::CreatedTime => DateTypeOption {
+      field_type: field_type.clone(),
+      date_format: DateFormat::Friendly,
+      time_format: TimeFormat::TwelveHour,
+      ..Default::default()
+    }
+    .into(),
     FieldType::SingleSelect => SingleSelectTypeOption::default().into(),
     FieldType::MultiSelect => MultiSelectTypeOption::default().into(),
     FieldType::Checkbox => CheckboxTypeOption::default().into(),

+ 27 - 12
frontend/rust-lib/flowy-database2/tests/database/block_test/row_test.rs

@@ -1,9 +1,12 @@
-use crate::database::block_test::script::DatabaseRowTest;
-use crate::database::block_test::script::RowScript::*;
+use std::time::Duration;
+
 use flowy_database2::entities::FieldType;
 use flowy_database2::services::field::DateCellData;
 use lib_infra::util::timestamp;
 
+use crate::database::block_test::script::DatabaseRowTest;
+use crate::database::block_test::script::RowScript::*;
+
 // Create a new row at the end of the grid and check the create time is valid.
 #[tokio::test]
 async fn created_at_field_test() {
@@ -16,11 +19,15 @@ async fn created_at_field_test() {
   // Get created time of the new row.
   let row = test.get_rows().await.last().cloned().unwrap();
   let updated_at_field = test.get_first_field(FieldType::CreatedTime);
-  let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap();
-  let created_at_timestamp = DateCellData::from(cell).timestamp.unwrap();
+  let cell = test
+    .editor
+    .get_cell(&updated_at_field.id, &row.id)
+    .await
+    .unwrap();
+  let created_at_timestamp = DateCellData::from(&cell).timestamp.unwrap();
 
   assert!(created_at_timestamp > 0);
-  assert!(created_at_timestamp < timestamp());
+  assert!(created_at_timestamp <= timestamp());
 }
 
 // Update row and check the update time is valid.
@@ -28,10 +35,15 @@ async fn created_at_field_test() {
 async fn update_at_field_test() {
   let mut test = DatabaseRowTest::new().await;
   let row = test.get_rows().await.remove(0);
-  let updated_at_field = test.get_first_field(FieldType::LastEditedTime);
-  let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap();
-  let old_updated_at = DateCellData::from(cell).timestamp.unwrap();
+  let last_edit_field = test.get_first_field(FieldType::LastEditedTime);
+  let cell = test
+    .editor
+    .get_cell(&last_edit_field.id, &row.id)
+    .await
+    .unwrap();
+  let old_updated_at = DateCellData::from(&cell).timestamp.unwrap();
 
+  tokio::time::sleep(Duration::from_millis(500)).await;
   test
     .run_script(UpdateTextCell {
       row_id: row.id.clone(),
@@ -41,9 +53,12 @@ async fn update_at_field_test() {
 
   // Get the updated time of the row.
   let row = test.get_rows().await.remove(0);
-  let updated_at_field = test.get_first_field(FieldType::LastEditedTime);
-  let cell = row.cells.cell_for_field_id(&updated_at_field.id).unwrap();
-  let new_updated_at = DateCellData::from(cell).timestamp.unwrap();
-
+  let last_edit_field = test.get_first_field(FieldType::LastEditedTime);
+  let cell = test
+    .editor
+    .get_cell(&last_edit_field.id, &row.id)
+    .await
+    .unwrap();
+  let new_updated_at = DateCellData::from(&cell).timestamp.unwrap();
   assert!(old_updated_at < new_updated_at);
 }

+ 5 - 1
frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs

@@ -1,3 +1,5 @@
+use std::time::Duration;
+
 use flowy_database2::entities::{CellChangesetPB, FieldType};
 use flowy_database2::services::cell::ToCellChangeset;
 use flowy_database2::services::field::checklist_type_option::ChecklistCellChangeset;
@@ -130,12 +132,14 @@ async fn update_updated_at_field_on_other_cell_update() {
       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;
+
+  tokio::time::sleep(Duration::from_millis(500)).await;
+  let after_update_timestamp = chrono::offset::Utc::now().timestamp();
   assert!(!cells.is_empty());
   for (i, row_cell) in cells.into_iter().enumerate() {
     let timestamp = DateCellData::from(row_cell.cell.as_ref().unwrap())

+ 2 - 2
frontend/rust-lib/flowy-database2/tests/database/database_editor.rs

@@ -1,7 +1,7 @@
-use collab_database::database::gen_database_view_id;
 use std::collections::HashMap;
 use std::sync::Arc;
 
+use collab_database::database::{gen_database_view_id, timestamp};
 use collab_database::fields::Field;
 use collab_database::rows::{CreateRowParams, Row, RowId};
 use strum::EnumCount;
@@ -418,7 +418,7 @@ impl<'a> TestRowBuilder<'a> {
       height: 60,
       visibility: true,
       prev_row_id: None,
-      timestamp: 0,
+      timestamp: timestamp(),
     }
   }
 }

+ 5 - 4
frontend/rust-lib/flowy-document2/src/parser/json/parser.rs

@@ -1,9 +1,10 @@
 use std::{collections::HashMap, vec};
 
-use flowy_error::FlowyResult;
 use indexmap::IndexMap;
 use nanoid::nanoid;
 
+use flowy_error::FlowyResult;
+
 use crate::entities::{BlockPB, ChildrenPB, DocumentDataPB, MetaPB};
 
 use super::block::Block;
@@ -69,12 +70,12 @@ impl JsonToDocumentParser {
   }
 
   fn block_to_block_pb(block: &Block, id: Option<String>, parent_id: String) -> BlockPB {
-    let id = id.unwrap_or(nanoid!(10));
+    let id = id.unwrap_or_else(|| nanoid!(10));
     BlockPB {
-      id: id.clone(),
+      id,
       ty: block.ty.clone(),
       data: serde_json::to_string(&block.data).unwrap(),
-      parent_id: parent_id.clone(),
+      parent_id,
       children_id: nanoid!(10),
     }
   }

+ 5 - 6
frontend/rust-lib/flowy-folder2/src/user_default.rs

@@ -1,9 +1,11 @@
+use std::sync::Arc;
+
 use collab_folder::core::{FolderData, RepeatedView, ViewIdentifier, Workspace};
-use lib_infra::util::timestamp;
 use nanoid::nanoid;
-use std::sync::Arc;
 use tokio::sync::RwLock;
 
+use lib_infra::util::timestamp;
+
 use crate::entities::{view_pb_with_child_views, ViewPB, WorkspacePB};
 use crate::view_operation::{
   FlattedViews, FolderOperationHandlers, ParentChildViews, WorkspaceViewBuilder,
@@ -49,10 +51,7 @@ impl DefaultFolderBuilder {
       created_at: timestamp(),
     };
 
-    let first_level_view_pbs = views
-      .iter()
-      .map(|value| ViewPB::from(value))
-      .collect::<Vec<_>>();
+    let first_level_view_pbs = views.iter().map(ViewPB::from).collect::<Vec<_>>();
 
     let workspace_pb = WorkspacePB {
       id: workspace.id.clone(),