chore: import history database (#2638)

This commit is contained in:
Nathan.fooo
2023-05-27 21:29:18 +08:00
committed by GitHub
parent 6935653e15
commit 45d0d41830
10 changed files with 302 additions and 106 deletions

View File

@ -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())
}
}

View File

@ -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,

View File

@ -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
}

View File

@ -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();

View File

@ -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()

View File

@ -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,

View File

@ -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()),
}
},
})

View File

@ -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);