فهرست منبع

chore: import history database (#2638)

Nathan.fooo 2 سال پیش
والد
کامیت
45d0d41830

+ 2 - 13
frontend/rust-lib/flowy-database2/src/entities/field_entities.rs

@@ -606,24 +606,13 @@ impl_into_field_type!(u8);
 
 impl From<FieldType> for i64 {
   fn from(ty: FieldType) -> Self {
-    match ty {
-      FieldType::RichText => 0,
-      FieldType::Number => 1,
-      FieldType::DateTime => 2,
-      FieldType::SingleSelect => 3,
-      FieldType::MultiSelect => 4,
-      FieldType::Checkbox => 5,
-      FieldType::URL => 6,
-      FieldType::Checklist => 7,
-      FieldType::UpdatedAt => 8,
-      FieldType::CreatedAt => 9,
-    }
+    (ty as u8) as i64
   }
 }
 
 impl From<&FieldType> for i64 {
   fn from(ty: &FieldType) -> Self {
-    ty.clone() as i64
+    i64::from(ty.clone())
   }
 }
 

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

@@ -12,6 +12,7 @@ use crate::manager::DatabaseManager2;
 use crate::services::field::{
   type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
 };
+use crate::services::share::csv::CSVFormat;
 
 #[tracing::instrument(level = "trace", skip(data, manager), err)]
 pub(crate) async fn get_database_data_handler(
@@ -613,9 +614,9 @@ pub(crate) async fn import_data_handler(
   match params.import_type {
     ImportTypePB::CSV => {
       if let Some(data) = params.data {
-        manager.import_csv(data).await?;
+        manager.import_csv(data, CSVFormat::META).await?;
       } else if let Some(uri) = params.uri {
-        manager.import_csv_data_from_uri(uri).await?;
+        manager.import_csv_from_uri(uri, CSVFormat::META).await?;
       } else {
         return Err(FlowyError::new(
           ErrorCode::InvalidData,

+ 13 - 9
frontend/rust-lib/flowy-database2/src/manager.rs

@@ -16,7 +16,7 @@ use flowy_task::TaskDispatcher;
 
 use crate::entities::{DatabaseDescriptionPB, DatabaseLayoutPB, RepeatedDatabaseDescriptionPB};
 use crate::services::database::{DatabaseEditor, MutexDatabase};
-use crate::services::share::csv::{CSVImporter, ExportStyle};
+use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
 
 pub trait DatabaseUser2: Send + Sync {
   fn user_id(&self) -> Result<i64, FlowyError>;
@@ -195,20 +195,24 @@ impl DatabaseManager2 {
     Ok(())
   }
 
-  pub async fn import_csv(&self, content: String) -> FlowyResult<String> {
-    let params = tokio::task::spawn_blocking(move || CSVImporter.import_csv_from_string(content))
-      .await
-      .map_err(internal_error)??;
-    let database_id = params.database_id.clone();
+  pub async fn import_csv(&self, content: String, format: CSVFormat) -> FlowyResult<ImportResult> {
+    let params =
+      tokio::task::spawn_blocking(move || CSVImporter.import_csv_from_string(content, format))
+        .await
+        .map_err(internal_error)??;
+    let result = ImportResult {
+      database_id: params.database_id.clone(),
+      view_id: params.view_id.clone(),
+    };
     self.create_database_with_params(params).await?;
-    Ok(database_id)
+    Ok(result)
   }
 
-  pub async fn import_csv_data_from_uri(&self, _uri: String) -> FlowyResult<()> {
+  pub async fn import_csv_from_uri(&self, _uri: String, _format: CSVFormat) -> FlowyResult<()> {
     Ok(())
   }
 
-  pub async fn export_csv(&self, view_id: &str, style: ExportStyle) -> FlowyResult<String> {
+  pub async fn export_csv(&self, view_id: &str, style: CSVFormat) -> FlowyResult<String> {
     let database = self.get_database_with_view_id(view_id).await?;
     database.export_csv(style).await
   }

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

@@ -35,7 +35,7 @@ use crate::services::field::{
 };
 use crate::services::filter::Filter;
 use crate::services::group::{default_group_setting, GroupSetting, RowChangeset};
-use crate::services::share::csv::{CSVExport, ExportStyle};
+use crate::services::share::csv::{CSVExport, CSVFormat};
 use crate::services::sort::Sort;
 
 #[derive(Clone)]
@@ -833,7 +833,7 @@ impl DatabaseEditor {
     }
   }
 
-  pub async fn export_csv(&self, style: ExportStyle) -> FlowyResult<String> {
+  pub async fn export_csv(&self, style: CSVFormat) -> FlowyResult<String> {
     let database = self.database.clone();
     let csv = tokio::task::spawn_blocking(move || {
       let database_guard = database.lock();

+ 2 - 2
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs

@@ -36,7 +36,7 @@ impl TypeOption for DateTypeOption {
 impl From<TypeOptionData> for DateTypeOption {
   fn from(data: TypeOptionData) -> Self {
     let date_format = data
-      .get_i64_value("data_format")
+      .get_i64_value("date_format")
       .map(DateFormat::from)
       .unwrap_or_default();
     let time_format = data
@@ -58,7 +58,7 @@ impl From<TypeOptionData> for DateTypeOption {
 impl From<DateTypeOption> for TypeOptionData {
   fn from(data: DateTypeOption) -> Self {
     TypeOptionDataBuilder::new()
-      .insert_i64_value("data_format", data.date_format.value())
+      .insert_i64_value("date_format", data.date_format.value())
       .insert_i64_value("time_format", data.time_format.value())
       .insert_i64_value("field_type", data.field_type.value())
       .build()

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

@@ -49,7 +49,9 @@ impl ToCellChangeset for DateCellChangeset {
 #[derive(Default, Clone, Debug, Serialize)]
 pub struct DateCellData {
   pub timestamp: Option<i64>,
+  #[serde(default)]
   pub include_time: bool,
+  #[serde(default)]
   pub timezone_id: String,
 }
 
@@ -61,7 +63,6 @@ impl From<&Cell> for DateCellData {
 
     let include_time = cell.get_bool_value("include_time").unwrap_or_default();
     let timezone_id = cell.get_str_value("timezone_id").unwrap_or_default();
-
     Self {
       timestamp,
       include_time,

+ 13 - 12
frontend/rust-lib/flowy-database2/src/services/share/csv/export.rs

@@ -3,12 +3,13 @@ use crate::services::cell::stringify_cell_data;
 use collab_database::database::Database;
 
 use flowy_error::{FlowyError, FlowyResult};
-use std::collections::HashMap;
+use indexmap::IndexMap;
 
-pub enum ExportStyle {
+#[derive(Debug, Clone, Copy)]
+pub enum CSVFormat {
   /// The export data will be pure data, without any meta data.
   /// Will lost the field type information.
-  SIMPLE,
+  Original,
   /// The export data contains meta data, such as field type.
   /// It can be used to fully restore the database.
   META,
@@ -16,7 +17,7 @@ pub enum ExportStyle {
 
 pub struct CSVExport;
 impl CSVExport {
-  pub fn export_database(&self, database: &Database, style: ExportStyle) -> FlowyResult<String> {
+  pub fn export_database(&self, database: &Database, style: CSVFormat) -> FlowyResult<String> {
     let mut wtr = csv::Writer::from_writer(vec![]);
     let inline_view_id = database.get_inline_view_id();
     let fields = database.get_fields(&inline_view_id, None);
@@ -25,8 +26,8 @@ impl CSVExport {
     let field_records = fields
       .iter()
       .map(|field| match &style {
-        ExportStyle::SIMPLE => field.name.clone(),
-        ExportStyle::META => serde_json::to_string(&field).unwrap(),
+        CSVFormat::Original => field.name.clone(),
+        CSVFormat::META => serde_json::to_string(&field).unwrap(),
       })
       .collect::<Vec<String>>();
     wtr
@@ -34,10 +35,10 @@ impl CSVExport {
       .map_err(|e| FlowyError::internal().context(e))?;
 
     // Write rows
-    let field_by_field_id = fields
-      .into_iter()
-      .map(|field| (field.id.clone(), field))
-      .collect::<HashMap<_, _>>();
+    let mut field_by_field_id = IndexMap::new();
+    fields.into_iter().for_each(|field| {
+      field_by_field_id.insert(field.id.clone(), field);
+    });
     let rows = database.get_rows_for_view(&inline_view_id);
     for row in rows {
       let cells = field_by_field_id
@@ -47,8 +48,8 @@ impl CSVExport {
           Some(cell) => {
             let field_type = FieldType::from(field.field_type);
             match style {
-              ExportStyle::SIMPLE => stringify_cell_data(cell, &field_type, &field_type, field),
-              ExportStyle::META => serde_json::to_string(cell).unwrap_or_else(|_| "".to_string()),
+              CSVFormat::Original => stringify_cell_data(cell, &field_type, &field_type, field),
+              CSVFormat::META => serde_json::to_string(cell).unwrap_or_else(|_| "".to_string()),
             }
           },
         })

+ 74 - 34
frontend/rust-lib/flowy-database2/src/services/share/csv/import.rs

@@ -1,31 +1,38 @@
 use crate::entities::FieldType;
-use crate::services::cell::CellBuilder;
-use crate::services::field::default_type_option_data_from_type;
+
+use crate::services::field::{default_type_option_data_from_type, CELL_DATA};
+use crate::services::share::csv::CSVFormat;
 use collab_database::database::{gen_database_id, gen_database_view_id, gen_field_id, gen_row_id};
 use collab_database::fields::Field;
-use collab_database::rows::CreateRowParams;
+use collab_database::rows::{new_cell_builder, Cell, CreateRowParams};
 use collab_database::views::{CreateDatabaseParams, DatabaseLayout};
 use flowy_error::{FlowyError, FlowyResult};
-use rayon::prelude::*;
-use std::collections::HashMap;
 use std::{fs::File, io::prelude::*};
 
 #[derive(Default)]
 pub struct CSVImporter;
 
 impl CSVImporter {
-  pub fn import_csv_from_file(&self, path: &str) -> FlowyResult<CreateDatabaseParams> {
+  pub fn import_csv_from_file(
+    &self,
+    path: &str,
+    style: CSVFormat,
+  ) -> FlowyResult<CreateDatabaseParams> {
     let mut file = File::open(path)?;
     let mut content = String::new();
     file.read_to_string(&mut content)?;
     let fields_with_rows = self.get_fields_and_rows(content)?;
-    let database_data = database_from_fields_and_rows(fields_with_rows);
+    let database_data = database_from_fields_and_rows(fields_with_rows, &style);
     Ok(database_data)
   }
 
-  pub fn import_csv_from_string(&self, content: String) -> FlowyResult<CreateDatabaseParams> {
+  pub fn import_csv_from_string(
+    &self,
+    content: String,
+    format: CSVFormat,
+  ) -> FlowyResult<CreateDatabaseParams> {
     let fields_with_rows = self.get_fields_and_rows(content)?;
-    let database_data = database_from_fields_and_rows(fields_with_rows);
+    let database_data = database_from_fields_and_rows(fields_with_rows, &format);
     Ok(database_data)
   }
 
@@ -60,7 +67,10 @@ impl CSVImporter {
   }
 }
 
-fn database_from_fields_and_rows(fields_and_rows: FieldsRows) -> CreateDatabaseParams {
+fn database_from_fields_and_rows(
+  fields_and_rows: FieldsRows,
+  format: &CSVFormat,
+) -> CreateDatabaseParams {
   let (fields, rows) = fields_and_rows.split();
   let view_id = gen_database_view_id();
   let database_id = gen_database_id();
@@ -68,36 +78,47 @@ fn database_from_fields_and_rows(fields_and_rows: FieldsRows) -> CreateDatabaseP
   let fields = fields
     .into_iter()
     .enumerate()
-    .map(
-      |(index, field_str)| match serde_json::from_str(&field_str) {
-        Ok(field) => field,
-        Err(_) => {
-          let field_type = FieldType::RichText;
-          let type_option_data = default_type_option_data_from_type(&field_type);
-          let is_primary = index == 0;
-          Field::new(
-            gen_field_id(),
-            field_str,
-            field_type.clone().into(),
-            is_primary,
-          )
-          .with_type_option_data(field_type, type_option_data)
-        },
+    .map(|(index, field_meta)| match format {
+      CSVFormat::Original => default_field(field_meta, index == 0),
+      CSVFormat::META => {
+        //
+        match serde_json::from_str(&field_meta) {
+          Ok(field) => {
+            //
+            field
+          },
+          Err(e) => {
+            dbg!(e);
+            default_field(field_meta, index == 0)
+          },
+        }
       },
-    )
+    })
     .collect::<Vec<Field>>();
 
   let created_rows = rows
-    .par_iter()
-    .map(|row| {
-      let mut cell_by_field_id = HashMap::new();
+    .iter()
+    .map(|cells| {
       let mut params = CreateRowParams::new(gen_row_id());
-      for (index, cell) in row.iter().enumerate() {
+      for (index, cell_content) in cells.iter().enumerate() {
         if let Some(field) = fields.get(index) {
-          cell_by_field_id.insert(field.id.clone(), cell.to_string());
+          let field_type = FieldType::from(field.field_type);
+
+          // Make the cell based on the style.
+          let cell = match format {
+            CSVFormat::Original => new_cell_builder(field_type)
+              .insert_str_value(CELL_DATA, cell_content.to_string())
+              .build(),
+            CSVFormat::META => match serde_json::from_str::<Cell>(cell_content) {
+              Ok(cell) => cell,
+              Err(_) => new_cell_builder(field_type)
+                .insert_str_value(CELL_DATA, "".to_string())
+                .build(),
+            },
+          };
+          params.cells.insert(field.id.clone(), cell);
         }
       }
-      params.cells = CellBuilder::with_cells(cell_by_field_id, &fields).build();
       params
     })
     .collect::<Vec<CreateRowParams>>();
@@ -116,6 +137,18 @@ fn database_from_fields_and_rows(fields_and_rows: FieldsRows) -> CreateDatabaseP
   }
 }
 
+fn default_field(field_str: String, is_primary: bool) -> Field {
+  let field_type = FieldType::RichText;
+  let type_option_data = default_type_option_data_from_type(&field_type);
+  Field::new(
+    gen_field_id(),
+    field_str,
+    field_type.clone().into(),
+    is_primary,
+  )
+  .with_type_option_data(field_type, type_option_data)
+}
+
 struct FieldsRows {
   fields: Vec<String>,
   rows: Vec<Vec<String>>,
@@ -126,9 +159,14 @@ impl FieldsRows {
   }
 }
 
+pub struct ImportResult {
+  pub database_id: String,
+  pub view_id: String,
+}
+
 #[cfg(test)]
 mod tests {
-  use crate::services::share::csv::CSVImporter;
+  use crate::services::share::csv::{CSVFormat, CSVImporter};
 
   #[test]
   fn test_import_csv_from_str() {
@@ -137,7 +175,9 @@ mod tests {
 2,tag 2,2,"May 22, 2023",No,
 ,,,,Yes,"#;
     let importer = CSVImporter;
-    let result = importer.import_csv_from_string(s.to_string()).unwrap();
+    let result = importer
+      .import_csv_from_string(s.to_string(), CSVFormat::Original)
+      .unwrap();
     assert_eq!(result.created_rows.len(), 3);
     assert_eq!(result.fields.len(), 6);
 

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

@@ -12,6 +12,7 @@ use flowy_database2::services::field::{
   CheckboxTypeOption, ChecklistTypeOption, DateCellChangeset, MultiSelectTypeOption, SelectOption,
   SelectOptionCellChangeset, SingleSelectTypeOption,
 };
+use flowy_database2::services::share::csv::{CSVFormat, ImportResult};
 use flowy_error::FlowyResult;
 use flowy_test::folder_event::ViewTest;
 use flowy_test::FlowyCoreTest;
@@ -224,8 +225,13 @@ impl DatabaseEditorTest {
     self.update_cell(&field.id, row_id, cell_changeset).await
   }
 
-  pub async fn import(&self, s: String) -> String {
-    self.sdk.database_manager.import_csv(s).await.unwrap()
+  pub async fn import(&self, s: String, format: CSVFormat) -> ImportResult {
+    self
+      .sdk
+      .database_manager
+      .import_csv(s, format)
+      .await
+      .unwrap()
   }
 
   pub async fn get_database(&self, database_id: &str) -> Option<Arc<DatabaseEditor>> {

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 109 - 30
frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است