Feat/sort after change (#1607)

* chore: generate task after row was changed

* chore: config task

* chore: add task test

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Nathan.fooo 2022-12-26 20:28:18 +08:00 committed by GitHub
parent 5a30f46b85
commit f5b7d3951f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 970 additions and 575 deletions

View File

@ -17,6 +17,8 @@ pub enum GridDartNotification {
DidGroupByNewField = 62,
DidUpdateFilter = 63,
DidUpdateSort = 64,
DidReorderRows = 65,
DidReorderSingleRow = 66,
DidUpdateGridSetting = 70,
}

View File

@ -4,7 +4,7 @@ use crate::services::sort::SortType;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use grid_rev_model::FieldTypeRevision;
use grid_rev_model::{FieldTypeRevision, SortCondition, SortRevision};
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSortPB {
@ -21,6 +21,17 @@ pub struct GridSortPB {
pub condition: GridSortConditionPB,
}
impl std::convert::From<&SortRevision> for GridSortPB {
fn from(sort_rev: &SortRevision) -> Self {
Self {
id: sort_rev.id.clone(),
field_id: sort_rev.field_id.clone(),
field_type: sort_rev.field_type.into(),
condition: sort_rev.condition.clone().into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum GridSortConditionPB {
@ -32,6 +43,16 @@ impl std::default::Default for GridSortConditionPB {
Self::Ascending
}
}
impl std::convert::From<SortCondition> for GridSortConditionPB {
fn from(condition: SortCondition) -> Self {
match condition {
SortCondition::Ascending => GridSortConditionPB::Ascending,
SortCondition::Descending => GridSortConditionPB::Descending,
}
}
}
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct AlterSortPayloadPB {
#[pb(index = 1)]
@ -151,3 +172,30 @@ pub struct SortChangesetNotificationPB {
#[pb(index = 4)]
pub update_sorts: Vec<GridSortPB>,
}
impl SortChangesetNotificationPB {
pub fn extend(&mut self, other: SortChangesetNotificationPB) {
self.insert_sorts.extend(other.insert_sorts);
self.delete_sorts.extend(other.delete_sorts);
self.update_sorts.extend(other.update_sorts);
}
pub fn is_empty(&self) -> bool {
self.insert_sorts.is_empty() && self.delete_sorts.is_empty() && self.update_sorts.is_empty()
}
}
#[derive(Debug, Default, ProtoBuf)]
pub struct ReorderAllRowsPB {
#[pb(index = 1)]
pub row_orders: Vec<String>,
}
#[derive(Debug, Default, ProtoBuf)]
pub struct ReorderSingleRowPB {
#[pb(index = 1)]
pub old_index: i32,
#[pb(index = 2)]
pub new_index: i32,
}

View File

@ -1,6 +1,6 @@
use crate::entities::*;
use crate::manager::GridManager;
use crate::services::cell::{FromCellString, TypeCellData};
use crate::services::cell::{FromCellString, ToCellChangesetString, TypeCellData};
use crate::services::field::{
default_type_option_builder_from_type, select_type_option_from_field_rev, type_option_builder_from_json_str,
DateCellChangeset, DateChangesetPB, SelectOptionCellChangeset, SelectOptionCellChangesetPB,
@ -317,7 +317,9 @@ pub(crate) async fn update_cell_handler(
) -> Result<(), FlowyError> {
let changeset: CellChangesetPB = data.into_inner();
let editor = manager.get_grid_editor(&changeset.grid_id).await?;
let _ = editor.update_cell_with_changeset(changeset).await?;
let _ = editor
.update_cell_with_changeset(&changeset.row_id, &changeset.field_id, changeset.type_cell_data)
.await?;
Ok(())
}
@ -344,16 +346,17 @@ pub(crate) async fn update_select_option_handler(
manager: AFPluginState<Arc<GridManager>>,
) -> Result<(), FlowyError> {
let changeset: SelectOptionChangeset = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(&changeset.cell_identifier.view_id).await?;
let editor = manager.get_grid_editor(&changeset.cell_path.view_id).await?;
let field_id = changeset.cell_path.field_id.clone();
let _ = editor
.modify_field_rev(&changeset.cell_identifier.field_id, |field_rev| {
.modify_field_rev(&field_id, |field_rev| {
let mut type_option = select_type_option_from_field_rev(field_rev)?;
let mut cell_content_changeset = None;
let mut cell_changeset_str = None;
let mut is_changed = None;
for option in changeset.insert_options {
cell_content_changeset = Some(SelectOptionCellChangeset::from_insert_option_id(&option.id).to_str());
cell_changeset_str =
Some(SelectOptionCellChangeset::from_insert_option_id(&option.id).to_cell_changeset_str());
type_option.insert_option(option);
is_changed = Some(());
}
@ -364,7 +367,8 @@ pub(crate) async fn update_select_option_handler(
}
for option in changeset.delete_options {
cell_content_changeset = Some(SelectOptionCellChangeset::from_delete_option_id(&option.id).to_str());
cell_changeset_str =
Some(SelectOptionCellChangeset::from_delete_option_id(&option.id).to_cell_changeset_str());
type_option.delete_option(option);
is_changed = Some(());
}
@ -373,16 +377,17 @@ pub(crate) async fn update_select_option_handler(
field_rev.insert_type_option(&*type_option);
}
if let Some(cell_content_changeset) = cell_content_changeset {
let changeset = CellChangesetPB {
grid_id: changeset.cell_identifier.view_id,
row_id: changeset.cell_identifier.row_id,
field_id: changeset.cell_identifier.field_id.clone(),
type_cell_data: cell_content_changeset,
};
if let Some(cell_changeset_str) = cell_changeset_str {
let cloned_editor = editor.clone();
tokio::spawn(async move {
match cloned_editor.update_cell_with_changeset(changeset).await {
match cloned_editor
.update_cell_with_changeset(
&changeset.cell_path.row_id,
&changeset.cell_path.field_id,
cell_changeset_str,
)
.await
{
Ok(_) => {}
Err(e) => tracing::error!("{}", e),
}
@ -432,7 +437,18 @@ pub(crate) async fn update_select_option_cell_handler(
) -> Result<(), FlowyError> {
let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(&params.cell_identifier.view_id).await?;
let _ = editor.update_cell_with_changeset(params.into()).await?;
let changeset = SelectOptionCellChangeset {
insert_option_ids: params.insert_option_ids,
delete_option_ids: params.delete_option_ids,
};
let _ = editor
.update_cell_with_changeset(
&params.cell_identifier.row_id,
&params.cell_identifier.field_id,
changeset,
)
.await?;
Ok(())
}
@ -443,7 +459,7 @@ pub(crate) async fn update_date_cell_handler(
) -> Result<(), FlowyError> {
let data = data.into_inner();
let cell_path: CellPathParams = data.cell_path.try_into()?;
let content = DateCellChangeset {
let cell_changeset = DateCellChangeset {
date: data.date,
time: data.time,
is_utc: data.is_utc,
@ -451,7 +467,7 @@ pub(crate) async fn update_date_cell_handler(
let editor = manager.get_grid_editor(&cell_path.view_id).await?;
let _ = editor
.update_cell(cell_path.view_id, cell_path.row_id, cell_path.field_id, content)
.update_cell(cell_path.row_id, cell_path.field_id, cell_changeset)
.await?;
Ok(())
}

View File

@ -118,8 +118,13 @@ impl GridBlockRevisionEditor {
}
pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult<Option<(usize, Arc<RowRevision>)>> {
let row_rev = self.pad.read().await.get_row_rev(row_id);
Ok(row_rev)
if self.pad.try_read().is_err() {
tracing::error!("Required GridBlockRevisionPad's read lock failed");
Ok(None)
} else {
let row_rev = self.pad.read().await.get_row_rev(row_id);
Ok(row_rev)
}
}
pub async fn get_row_revs<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<Arc<RowRevision>>>

View File

@ -34,7 +34,7 @@ pub trait CellDataDecoder: TypeOption {
pub trait CellDataChangeset: TypeOption {
/// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset`
/// implements the `FromCellChangeset` trait.
/// implements the `FromCellChangesetString` trait.
/// For example,the SelectOptionCellChangeset,DateCellChangeset. etc.
///
fn apply_changeset(
@ -50,14 +50,14 @@ pub trait CellDataChangeset: TypeOption {
/// FieldType::SingleSelect => SelectOptionChangeset
///
/// cell_rev: It will be None if the cell does not contain any data.
pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
pub fn apply_cell_data_changeset<C: ToCellChangesetString, T: AsRef<FieldRevision>>(
changeset: C,
cell_rev: Option<CellRevision>,
field_rev: T,
cell_data_cache: Option<AtomicCellDataCache>,
) -> Result<String, FlowyError> {
let field_rev = field_rev.as_ref();
let changeset = changeset.to_string();
let changeset = changeset.to_cell_changeset_str();
let field_type: FieldType = field_rev.ty.into();
let type_cell_data = cell_rev.and_then(|cell_rev| match TypeCellData::try_from(cell_rev) {
@ -108,7 +108,8 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
///
/// # Arguments
///
/// * `cell_str`: the opaque cell string
/// * `cell_str`: the opaque cell string that can be decoded by corresponding structs that implement the
/// `FromCellString` trait.
/// * `from_field_type`: the original field type of the passed-in cell data. Check the `TypeCellData`
/// that is used to save the origin field type of the cell data.
/// * `to_field_type`: decode the passed-in cell data to this field type. It will use the to_field_type's
@ -155,7 +156,7 @@ pub fn insert_text_cell(s: String, field_rev: &FieldRevision) -> CellRevision {
}
pub fn insert_number_cell(num: i64, field_rev: &FieldRevision) -> CellRevision {
let data = apply_cell_data_changeset(num, None, field_rev, None).unwrap();
let data = apply_cell_data_changeset(num.to_string(), None, field_rev, None).unwrap();
CellRevision::new(data)
}
@ -186,14 +187,14 @@ pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevisi
}
pub fn insert_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
let cell_data = SelectOptionCellChangeset::from_insert_options(option_ids).to_str();
let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
let changeset = SelectOptionCellChangeset::from_insert_options(option_ids).to_cell_changeset_str();
let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap();
CellRevision::new(data)
}
pub fn delete_select_option_cell(option_ids: Vec<String>, field_rev: &FieldRevision) -> CellRevision {
let cell_data = SelectOptionCellChangeset::from_delete_options(option_ids).to_str();
let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap();
let changeset = SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str();
let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap();
CellRevision::new(data)
}
@ -252,13 +253,13 @@ impl std::convert::From<IntoCellData<String>> for String {
/// If the changeset applying to the cell is not String type, it should impl this trait.
/// Deserialize the string into cell specific changeset.
pub trait FromCellChangeset {
pub trait FromCellChangesetString {
fn from_changeset(changeset: String) -> FlowyResult<Self>
where
Self: Sized;
}
impl FromCellChangeset for String {
impl FromCellChangesetString for String {
fn from_changeset(changeset: String) -> FlowyResult<Self>
where
Self: Sized,
@ -267,6 +268,16 @@ impl FromCellChangeset for String {
}
}
pub trait ToCellChangesetString: Debug {
fn to_cell_changeset_str(&self) -> String;
}
impl ToCellChangesetString for String {
fn to_cell_changeset_str(&self) -> String {
self.clone()
}
}
pub struct AnyCellChangeset<T>(pub Option<T>);
impl<T> AnyCellChangeset<T> {
@ -280,7 +291,7 @@ impl<T> AnyCellChangeset<T> {
impl<T, C: ToString> std::convert::From<C> for AnyCellChangeset<T>
where
T: FromCellChangeset,
T: FromCellChangesetString,
{
fn from(changeset: C) -> Self {
match T::from_changeset(changeset.to_string()) {

View File

@ -1,5 +1,4 @@
use crate::entities::{DateFilterConditionPB, DateFilterPB};
use chrono::NaiveDateTime;
impl DateFilterPB {

View File

@ -1,5 +1,7 @@
use crate::entities::CellPathPB;
use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString};
use crate::services::cell::{
CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, FromCellString, ToCellChangesetString,
};
use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::{internal_error, FlowyResult};
@ -33,7 +35,7 @@ pub struct DateChangesetPB {
pub is_utc: bool,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DateCellChangeset {
pub date: Option<String>,
pub time: Option<String>,
@ -53,7 +55,7 @@ impl DateCellChangeset {
}
}
impl FromCellChangeset for DateCellChangeset {
impl FromCellChangesetString for DateCellChangeset {
fn from_changeset(changeset: String) -> FlowyResult<Self>
where
Self: Sized,
@ -62,9 +64,9 @@ impl FromCellChangeset for DateCellChangeset {
}
}
impl ToString for DateCellChangeset {
fn to_string(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "".to_string())
impl ToCellChangesetString for DateCellChangeset {
fn to_cell_changeset_str(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}

View File

@ -1,7 +1,8 @@
use crate::entities::parser::NotEmptyStr;
use crate::entities::{CellChangesetPB, CellPathPB, CellPathParams, FieldType};
use crate::entities::{CellPathPB, CellPathParams, FieldType};
use crate::services::cell::{
CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString,
CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, FromCellString,
ToCellChangesetString,
};
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper;
@ -379,22 +380,6 @@ pub struct SelectOptionCellChangesetParams {
pub delete_option_ids: Vec<String>,
}
impl std::convert::From<SelectOptionCellChangesetParams> for CellChangesetPB {
fn from(params: SelectOptionCellChangesetParams) -> Self {
let changeset = SelectOptionCellChangeset {
insert_option_ids: params.insert_option_ids,
delete_option_ids: params.delete_option_ids,
};
let content = serde_json::to_string(&changeset).unwrap();
CellChangesetPB {
grid_id: params.cell_identifier.view_id,
row_id: params.cell_identifier.row_id,
field_id: params.cell_identifier.field_id,
type_cell_data: content,
}
}
}
impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPB {
type Error = ErrorCode;
@ -432,13 +417,13 @@ impl TryInto<SelectOptionCellChangesetParams> for SelectOptionCellChangesetPB {
}
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SelectOptionCellChangeset {
pub insert_option_ids: Vec<String>,
pub delete_option_ids: Vec<String>,
}
impl FromCellChangeset for SelectOptionCellChangeset {
impl FromCellChangesetString for SelectOptionCellChangeset {
fn from_changeset(changeset: String) -> FlowyResult<Self>
where
Self: Sized,
@ -447,6 +432,12 @@ impl FromCellChangeset for SelectOptionCellChangeset {
}
}
impl ToCellChangesetString for SelectOptionCellChangeset {
fn to_cell_changeset_str(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}
impl SelectOptionCellChangeset {
pub fn from_insert_option_id(option_id: &str) -> Self {
SelectOptionCellChangeset {
@ -475,10 +466,6 @@ impl SelectOptionCellChangeset {
delete_option_ids: option_ids,
}
}
pub fn to_str(&self) -> String {
serde_json::to_string(self).unwrap()
}
}
/// [SelectOptionCellDataPB] contains a list of user's selected options and a list of all the options
@ -512,7 +499,7 @@ pub struct SelectOptionChangesetPB {
}
pub struct SelectOptionChangeset {
pub cell_identifier: CellPathParams,
pub cell_path: CellPathParams,
pub insert_options: Vec<SelectOptionPB>,
pub update_options: Vec<SelectOptionPB>,
pub delete_options: Vec<SelectOptionPB>,
@ -524,7 +511,7 @@ impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPB {
fn try_into(self) -> Result<SelectOptionChangeset, Self::Error> {
let cell_identifier = self.cell_identifier.try_into()?;
Ok(SelectOptionChangeset {
cell_identifier,
cell_path: cell_identifier,
insert_options: self.insert_options,
update_options: self.update_options,
delete_options: self.delete_options,

View File

@ -1,5 +1,5 @@
use crate::entities::FieldType;
use crate::services::cell::{CellDataDecoder, FromCellChangeset, FromCellString};
use crate::services::cell::{CellDataDecoder, FromCellChangesetString, FromCellString, ToCellChangesetString};
use crate::services::filter::FromFilterString;
use bytes::Bytes;
@ -23,10 +23,10 @@ pub trait TypeOption {
type CellData: FromCellString + ToString + Default + Send + Sync + Clone + 'static;
/// Represents as the corresponding field type cell changeset.
/// The changeset must implements the `FromCellChangeset` trait. The `CellChangeset` is implemented
/// for `String`.
/// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait.
/// These two traits are auto implemented for `String`.
///
type CellChangeset: FromCellChangeset;
type CellChangeset: FromCellChangesetString + ToCellChangesetString;
/// For the moment, the protobuf type only be used in the FFI of `Dart`. If the decoded cell
/// struct is just a `String`, then use the `StrCellData` as its `CellProtobufType`.

View File

@ -1,7 +1,7 @@
use crate::entities::FieldType;
use crate::services::cell::{
AtomicCellDataCache, AtomicCellFilterCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob,
FromCellChangeset, FromCellString, TypeCellData,
FromCellChangesetString, FromCellString, TypeCellData,
};
use crate::services::field::{
default_order, CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB,

View File

@ -77,7 +77,7 @@ impl FilterController {
}
#[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))]
async fn gen_task(&mut self, task_type: FilterEvent, qos: QualityOfService) {
async fn gen_task(&self, task_type: FilterEvent, qos: QualityOfService) {
let task_id = self.task_scheduler.read().await.next_task_id();
let task = Task::new(&self.handler_id, task_id, TaskContent::Text(task_type.to_string()), qos);
self.task_scheduler.write().await.add_task(task);
@ -148,9 +148,7 @@ impl FilterController {
}
}
let _ = self
.notifier
.send(GridViewChanged::DidReceiveFilterResult(notification));
let _ = self.notifier.send(GridViewChanged::FilterNotification(notification));
}
Ok(())
}
@ -186,14 +184,12 @@ impl FilterController {
visible_rows,
};
tracing::Span::current().record("filter_result", &format!("{:?}", &notification).as_str());
let _ = self
.notifier
.send(GridViewChanged::DidReceiveFilterResult(notification));
let _ = self.notifier.send(GridViewChanged::FilterNotification(notification));
}
Ok(())
}
pub async fn did_receive_row_changed(&mut self, row_id: &str) {
pub async fn did_receive_row_changed(&self, row_id: &str) {
self.gen_task(
FilterEvent::RowDidChanged(row_id.to_string()),
QualityOfService::UserInteractive,

View File

@ -24,6 +24,10 @@ impl TaskHandler for FilterTaskHandler {
&self.handler_id
}
fn handler_name(&self) -> &str {
"FilterTaskHandler"
}
fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> {
let filter_controller = self.filter_controller.clone();
Box::pin(async move {
@ -33,7 +37,7 @@ impl TaskHandler for FilterTaskHandler {
.await
.process(&predicate)
.await
.map_err(anyhow::Error::from);
.map_err(anyhow::Error::from)?;
}
Ok(())
})

View File

@ -5,7 +5,7 @@ use crate::manager::GridUser;
use crate::services::block_manager::GridBlockManager;
use crate::services::cell::{
apply_cell_data_changeset, decode_type_cell_data, stringify_cell_data, AnyTypeCache, AtomicCellDataCache,
CellProtobufBlob, TypeCellData,
CellProtobufBlob, ToCellChangesetString, TypeCellData,
};
use crate::services::field::{
default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, FieldBuilder,
@ -31,6 +31,7 @@ use grid_rev_model::*;
use lib_infra::future::{to_fut, FutureResult};
use lib_ot::core::EmptyAttributes;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{broadcast, RwLock};
@ -405,16 +406,20 @@ impl GridRevisionEditor {
/// Returns all the rows in this block.
pub async fn get_row_pbs(&self, view_id: &str, block_id: &str) -> FlowyResult<Vec<RowPB>> {
let rows = self.view_manager.get_row_revs(view_id, block_id).await?;
let rows = self
.view_manager
.filter_rows(view_id, block_id, rows)
.await?
.into_iter()
.map(|row_rev| RowPB::from(&row_rev))
.collect();
let rows = rows.into_iter().map(|row_rev| RowPB::from(&row_rev)).collect();
Ok(rows)
}
pub async fn get_all_row_revs(&self, view_id: &str) -> FlowyResult<Vec<Arc<RowRevision>>> {
let mut all_rows = vec![];
let blocks = self.block_manager.get_blocks(None).await?;
for block in blocks {
let rows = self.view_manager.get_row_revs(view_id, &block.block_id).await?;
all_rows.extend(rows);
}
Ok(all_rows)
}
pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult<Option<Arc<RowRevision>>> {
match self.block_manager.get_row_rev(row_id).await? {
None => Ok(None),
@ -458,7 +463,7 @@ impl GridRevisionEditor {
))
};
display_str().await.unwrap_or("".to_string())
display_str().await.unwrap_or_else(|| "".to_string())
}
pub async fn get_cell_bytes(&self, params: &CellPathParams) -> Option<CellProtobufBlob> {
@ -488,52 +493,45 @@ impl GridRevisionEditor {
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn update_cell_with_changeset(&self, cell_changeset: CellChangesetPB) -> FlowyResult<()> {
let CellChangesetPB {
grid_id,
row_id,
field_id,
type_cell_data: mut content,
} = cell_changeset;
match self.grid_pad.read().await.get_field_rev(&field_id) {
pub async fn update_cell_with_changeset<T: ToCellChangesetString>(
&self,
row_id: &str,
field_id: &str,
cell_changeset: T,
) -> FlowyResult<()> {
match self.grid_pad.read().await.get_field_rev(field_id) {
None => {
let msg = format!("Field:{} not found", &field_id);
Err(FlowyError::internal().context(msg))
}
Some((_, field_rev)) => {
tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, content);
let cell_rev = self.get_cell_rev(&row_id, &field_id).await?;
tracing::trace!("Cell changeset: id:{} / value:{:?}", &field_id, cell_changeset);
let cell_rev = self.get_cell_rev(row_id, field_id).await?;
// Update the changeset.data property with the return value.
content = apply_cell_data_changeset(content, cell_rev, field_rev, Some(self.cell_data_cache.clone()))?;
let type_cell_data =
apply_cell_data_changeset(cell_changeset, cell_rev, field_rev, Some(self.cell_data_cache.clone()))?;
let cell_changeset = CellChangesetPB {
grid_id,
row_id: row_id.clone(),
field_id: field_id.clone(),
type_cell_data: content,
grid_id: self.grid_id.clone(),
row_id: row_id.to_owned(),
field_id: field_id.to_owned(),
type_cell_data,
};
let _ = self.block_manager.update_cell(cell_changeset).await?;
self.view_manager.did_update_cell(&row_id).await;
self.view_manager.did_update_cell(row_id).await;
Ok(())
}
}
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn update_cell<T: ToString>(
pub async fn update_cell<T: ToCellChangesetString>(
&self,
grid_id: String,
row_id: String,
field_id: String,
content: T,
cell_changeset: T,
) -> FlowyResult<()> {
self.update_cell_with_changeset(CellChangesetPB {
grid_id,
row_id,
field_id,
type_cell_data: content.to_string(),
})
.await
self.update_cell_with_changeset(&row_id, &field_id, cell_changeset)
.await
}
pub async fn get_block_meta_revs(&self) -> FlowyResult<Vec<Arc<GridBlockMetaRevision>>> {

View File

@ -92,7 +92,7 @@ impl RevisionSnapshotDiskCache for SQLiteGridRevisionSnapshotPersistence {
Ok(Some(latest_record.into()))
}
}
//noinspection ALL
#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
#[table_name = "grid_rev_snapshot"]
#[primary_key("snapshot_id")]

View File

@ -1,52 +1,61 @@
#![allow(clippy::all)]
use crate::entities::FieldType;
#[allow(unused_attributes)]
use crate::entities::SortChangesetNotificationPB;
use crate::services::sort::{SortChangeset, SortType};
use flowy_task::TaskDispatcher;
use grid_rev_model::{CellRevision, FieldRevision, RowRevision, SortCondition, SortRevision};
use lib_infra::future::Fut;
use crate::services::cell::{AtomicCellDataCache, TypeCellData};
use crate::services::field::{default_order, TypeOptionCellExt};
use crate::services::sort::{ReorderAllRowsResult, ReorderSingleRowResult, SortChangeset, SortType};
use crate::services::view_editor::{GridViewChanged, GridViewChangedNotifier};
use flowy_error::FlowyResult;
use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher};
use grid_rev_model::{CellRevision, FieldRevision, RowRevision, SortCondition, SortRevision};
use lib_infra::future::Fut;
use rayon::prelude::ParallelSliceMut;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
pub trait SortDelegate: Send + Sync {
fn get_sort_rev(&self, sort_type: SortType) -> Fut<Option<Arc<SortRevision>>>;
/// Returns all the rows after applying grid's filter
fn get_row_revs(&self) -> Fut<Vec<Arc<RowRevision>>>;
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>>;
fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<FieldRevision>>>;
}
pub struct SortController {
view_id: String,
handler_id: String,
delegate: Box<dyn SortDelegate>,
task_scheduler: Arc<RwLock<TaskDispatcher>>,
sorts: Vec<Arc<SortRevision>>,
cell_data_cache: AtomicCellDataCache,
row_index_cache: HashMap<String, usize>,
notifier: GridViewChangedNotifier,
}
impl SortController {
pub fn new<T>(
_view_id: &str,
view_id: &str,
handler_id: &str,
delegate: T,
task_scheduler: Arc<RwLock<TaskDispatcher>>,
cell_data_cache: AtomicCellDataCache,
notifier: GridViewChangedNotifier,
) -> Self
where
T: SortDelegate + 'static,
{
Self {
view_id: view_id.to_string(),
handler_id: handler_id.to_string(),
delegate: Box::new(delegate),
task_scheduler,
sorts: vec![],
cell_data_cache,
row_index_cache: Default::default(),
notifier,
}
}
@ -58,65 +67,139 @@ impl SortController {
.await;
}
pub async fn sort_rows(&self, rows: &mut Vec<Arc<RowRevision>>) {
pub async fn did_receive_row_changed(&self, row_id: &str) {
let task_type = SortEvent::RowDidChanged(row_id.to_string());
self.gen_task(task_type, QualityOfService::Background).await;
}
#[tracing::instrument(name = "receive_sort_task_result", level = "trace", skip_all, err)]
pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> {
let event_type = SortEvent::from_str(predicate).unwrap();
let mut row_revs = self.delegate.get_row_revs().await;
match event_type {
SortEvent::SortDidChanged => {
self.sort_rows(&mut row_revs).await;
let row_orders = row_revs
.iter()
.map(|row_rev| row_rev.id.clone())
.collect::<Vec<String>>();
let notification = ReorderAllRowsResult {
view_id: self.view_id.clone(),
row_orders,
};
let _ = self
.notifier
.send(GridViewChanged::ReorderAllRowsNotification(notification));
}
SortEvent::RowDidChanged(row_id) => {
let old_row_index = self.row_index_cache.get(&row_id).cloned();
self.sort_rows(&mut row_revs).await;
let new_row_index = self.row_index_cache.get(&row_id).cloned();
match (old_row_index, new_row_index) {
(Some(old_row_index), Some(new_row_index)) => {
if old_row_index == new_row_index {
return Ok(());
}
let notification = ReorderSingleRowResult {
view_id: self.view_id.clone(),
old_index: old_row_index,
new_index: new_row_index,
};
let _ = self
.notifier
.send(GridViewChanged::ReorderSingleRowNotification(notification));
}
_ => tracing::trace!("The row index cache is outdated"),
}
}
}
Ok(())
}
#[tracing::instrument(name = "schedule_sort_task", level = "trace", skip(self))]
async fn gen_task(&self, task_type: SortEvent, qos: QualityOfService) {
if self.sorts.is_empty() {
return;
}
let task_id = self.task_scheduler.read().await.next_task_id();
let task = Task::new(&self.handler_id, task_id, TaskContent::Text(task_type.to_string()), qos);
self.task_scheduler.write().await.add_task(task);
}
pub async fn sort_rows(&mut self, rows: &mut Vec<Arc<RowRevision>>) {
if self.sorts.is_empty() {
return;
}
let field_revs = self.delegate.get_field_revs(None).await;
rows.par_sort_by(|left, right| cmp_row(left, right, &self.sorts, &field_revs, &self.cell_data_cache));
for sort in self.sorts.iter() {
rows.par_sort_by(|left, right| cmp_row(left, right, sort, &field_revs, &self.cell_data_cache));
}
rows.iter().enumerate().for_each(|(index, row)| {
self.row_index_cache.insert(row.id.to_string(), index);
});
}
#[tracing::instrument(level = "trace", skip(self))]
pub async fn did_receive_changes(&mut self, changeset: SortChangeset) -> Option<SortChangesetNotificationPB> {
pub async fn did_receive_changes(&mut self, changeset: SortChangeset) -> SortChangesetNotificationPB {
let mut notification = SortChangesetNotificationPB::default();
if let Some(insert_sort) = changeset.insert_sort {
if let Some(sort) = self.delegate.get_sort_rev(insert_sort).await {
notification.insert_sorts.push(sort.as_ref().into());
self.sorts.push(sort);
}
}
if let Some(delete_sort_type) = changeset.delete_sort {
if let Some(index) = self.sorts.iter().position(|sort| sort.id == delete_sort_type.sort_id) {
self.sorts.remove(index);
let sort = self.sorts.remove(index);
notification.delete_sorts.push(sort.as_ref().into());
}
}
if let Some(_update_sort) = changeset.update_sort {
//
if let Some(update_sort) = changeset.update_sort {
if let Some(updated_sort) = self.delegate.get_sort_rev(update_sort).await {
notification.update_sorts.push(updated_sort.as_ref().into());
if let Some(index) = self.sorts.iter().position(|sort| sort.id == updated_sort.id) {
self.sorts[index] = updated_sort;
}
}
}
None
if !notification.insert_sorts.is_empty() || !notification.delete_sorts.is_empty() {
self.gen_task(SortEvent::SortDidChanged, QualityOfService::Background)
.await;
}
notification
}
}
fn cmp_row(
left: &Arc<RowRevision>,
right: &Arc<RowRevision>,
sorts: &[Arc<SortRevision>],
sort: &Arc<SortRevision>,
field_revs: &[Arc<FieldRevision>],
cell_data_cache: &AtomicCellDataCache,
) -> Ordering {
let mut order = default_order();
for sort in sorts.iter() {
let cmp_order = match (left.cells.get(&sort.field_id), right.cells.get(&sort.field_id)) {
(Some(left_cell), Some(right_cell)) => {
let field_type: FieldType = sort.field_type.into();
match field_revs.iter().find(|field_rev| field_rev.id == sort.field_id) {
None => default_order(),
Some(field_rev) => cmp_cell(left_cell, right_cell, field_rev, field_type, cell_data_cache),
}
let order = match (left.cells.get(&sort.field_id), right.cells.get(&sort.field_id)) {
(Some(left_cell), Some(right_cell)) => {
let field_type: FieldType = sort.field_type.into();
match field_revs.iter().find(|field_rev| field_rev.id == sort.field_id) {
None => default_order(),
Some(field_rev) => cmp_cell(left_cell, right_cell, field_rev, field_type, cell_data_cache),
}
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => default_order(),
};
if cmp_order.is_ne() {
// If the cmp_order is not Ordering::Equal, then break the loop.
order = match sort.condition {
SortCondition::Ascending => cmp_order,
SortCondition::Descending => cmp_order.reverse(),
};
break;
}
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => default_order(),
};
match sort.condition {
SortCondition::Ascending => order,
SortCondition::Descending => order.reverse(),
}
order
}
fn cmp_cell(
@ -142,3 +225,21 @@ fn cmp_cell(
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
enum SortEvent {
SortDidChanged,
RowDidChanged(String),
}
impl ToString for SortEvent {
fn to_string(&self) -> String {
serde_json::to_string(self).unwrap()
}
}
impl FromStr for SortEvent {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}

View File

@ -32,7 +32,25 @@ impl std::convert::From<&Arc<FieldRevision>> for SortType {
}
}
#[allow(dead_code)]
#[derive(Clone)]
pub struct ReorderAllRowsResult {
pub view_id: String,
pub row_orders: Vec<String>,
}
impl ReorderAllRowsResult {
pub fn new(view_id: String, row_orders: Vec<String>) -> Self {
Self { view_id, row_orders }
}
}
#[derive(Clone)]
pub struct ReorderSingleRowResult {
pub view_id: String,
pub old_index: usize,
pub new_index: usize,
}
#[derive(Debug)]
pub struct SortChangeset {
pub(crate) insert_sort: Option<SortType>,

View File

@ -24,7 +24,22 @@ impl TaskHandler for SortTaskHandler {
&self.handler_id
}
fn run(&self, _content: TaskContent) -> BoxResultFuture<(), anyhow::Error> {
todo!();
fn handler_name(&self) -> &str {
"SortTaskHandler"
}
fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> {
let sort_controller = self.sort_controller.clone();
Box::pin(async move {
if let TaskContent::Text(predicate) = content {
let _ = sort_controller
.write()
.await
.process(&predicate)
.await
.map_err(anyhow::Error::from)?;
}
Ok(())
})
}
}

View File

@ -1,13 +1,16 @@
use crate::dart_notification::{send_dart_notification, GridDartNotification};
use crate::entities::GridRowsVisibilityChangesetPB;
use crate::entities::{GridRowsVisibilityChangesetPB, ReorderAllRowsPB, ReorderSingleRowPB};
use crate::services::filter::FilterResultNotification;
use crate::services::sort::{ReorderAllRowsResult, ReorderSingleRowResult};
use async_stream::stream;
use futures::stream::StreamExt;
use tokio::sync::broadcast;
#[derive(Clone)]
pub enum GridViewChanged {
DidReceiveFilterResult(FilterResultNotification),
FilterNotification(FilterResultNotification),
ReorderAllRowsNotification(ReorderAllRowsResult),
ReorderSingleRowNotification(ReorderSingleRowResult),
}
pub type GridViewChangedNotifier = broadcast::Sender<GridViewChanged>;
@ -27,7 +30,7 @@ impl GridViewChangedReceiverRunner {
stream
.for_each(|changed| async {
match changed {
GridViewChanged::DidReceiveFilterResult(notification) => {
GridViewChanged::FilterNotification(notification) => {
let changeset = GridRowsVisibilityChangesetPB {
view_id: notification.view_id,
visible_rows: notification.visible_rows,
@ -41,6 +44,23 @@ impl GridViewChangedReceiverRunner {
.payload(changeset)
.send()
}
GridViewChanged::ReorderAllRowsNotification(notification) => {
let row_orders = ReorderAllRowsPB {
row_orders: notification.row_orders,
};
send_dart_notification(&notification.view_id, GridDartNotification::DidReorderRows)
.payload(row_orders)
.send()
}
GridViewChanged::ReorderSingleRowNotification(notification) => {
let reorder_row = ReorderSingleRowPB {
old_index: notification.old_index as i32,
new_index: notification.new_index as i32,
};
send_dart_notification(&notification.view_id, GridDartNotification::DidReorderSingleRow)
.payload(reorder_row)
.send()
}
}
})
.await;

View File

@ -112,24 +112,26 @@ impl GridViewRevisionEditor {
)
.await?;
let sort_controller = make_sort_controller(
&view_id,
delegate.clone(),
view_rev_pad.clone(),
cell_data_cache.clone(),
)
.await;
let user_id = user_id.to_owned();
let group_controller = Arc::new(RwLock::new(group_controller));
let filter_controller = make_filter_controller(
&view_id,
delegate.clone(),
notifier.clone(),
cell_data_cache,
cell_data_cache.clone(),
view_rev_pad.clone(),
)
.await;
let sort_controller = make_sort_controller(
&view_id,
delegate.clone(),
notifier.clone(),
filter_controller.clone(),
view_rev_pad.clone(),
cell_data_cache,
)
.await;
Ok(Self {
pad: view_rev_pad,
user_id,
@ -181,12 +183,11 @@ impl GridViewRevisionEditor {
}
pub async fn sort_rows(&self, rows: &mut Vec<Arc<RowRevision>>) {
self.sort_controller.read().await.sort_rows(rows).await
self.sort_controller.write().await.sort_rows(rows).await
}
pub async fn filter_rows(&self, _block_id: &str, mut rows: Vec<Arc<RowRevision>>) -> Vec<Arc<RowRevision>> {
self.filter_controller.write().await.filter_row_revs(&mut rows).await;
rows
pub async fn filter_rows(&self, _block_id: &str, rows: &mut Vec<Arc<RowRevision>>) {
self.filter_controller.write().await.filter_row_revs(rows).await;
}
pub async fn duplicate_view_data(&self) -> FlowyResult<String> {
@ -260,9 +261,11 @@ impl GridViewRevisionEditor {
}
let filter_controller = self.filter_controller.clone();
let sort_controller = self.sort_controller.clone();
let row_id = row_rev.id.clone();
tokio::spawn(async move {
filter_controller.write().await.did_receive_row_changed(&row_id).await;
filter_controller.read().await.did_receive_row_changed(&row_id).await;
sort_controller.read().await.did_receive_row_changed(&row_id).await;
});
}
@ -423,10 +426,9 @@ impl GridViewRevisionEditor {
.did_receive_changes(SortChangeset::from_insert(sort_type))
.await
};
drop(sort_controller);
if let Some(changeset) = changeset {
self.notify_did_update_sort(changeset).await;
}
self.notify_did_update_sort(changeset).await;
Ok(sort_rev)
}
@ -446,9 +448,7 @@ impl GridViewRevisionEditor {
})
.await?;
if changeset.is_some() {
self.notify_did_update_sort(changeset.unwrap()).await;
}
self.notify_did_update_sort(changeset).await;
Ok(())
}
@ -495,6 +495,7 @@ impl GridViewRevisionEditor {
.did_receive_changes(FilterChangeset::from_insert(filter_type))
.await
};
drop(filter_controller);
if let Some(changeset) = changeset {
self.notify_did_update_filter(changeset).await;
@ -612,9 +613,11 @@ impl GridViewRevisionEditor {
}
pub async fn notify_did_update_sort(&self, changeset: SortChangesetNotificationPB) {
send_dart_notification(&changeset.view_id, GridDartNotification::DidUpdateSort)
.payload(changeset)
.send();
if !changeset.is_empty() {
send_dart_notification(&changeset.view_id, GridDartNotification::DidUpdateSort)
.payload(changeset)
.send();
}
}
async fn notify_did_update_view(&self, changeset: GroupViewChangesetPB) {
@ -756,6 +759,8 @@ async fn make_filter_controller(
async fn make_sort_controller(
view_id: &str,
delegate: Arc<dyn GridViewEditorDelegate>,
notifier: GridViewChangedNotifier,
filter_controller: Arc<RwLock<FilterController>>,
pad: Arc<RwLock<GridViewRevisionPad>>,
cell_data_cache: AtomicCellDataCache,
) -> Arc<RwLock<SortController>> {
@ -763,6 +768,7 @@ async fn make_sort_controller(
let sort_delegate = GridViewSortDelegateImpl {
editor_delegate: delegate.clone(),
view_revision_pad: pad,
filter_controller,
};
let task_scheduler = delegate.get_task_scheduler();
let sort_controller = Arc::new(RwLock::new(SortController::new(
@ -771,6 +777,7 @@ async fn make_sort_controller(
sort_delegate,
task_scheduler.clone(),
cell_data_cache,
notifier,
)));
task_scheduler
.write()

View File

@ -60,22 +60,11 @@ impl GridViewManager {
pub async fn get_row_revs(&self, view_id: &str, block_id: &str) -> FlowyResult<Vec<Arc<RowRevision>>> {
let mut row_revs = self.delegate.get_row_revs(Some(vec![block_id.to_owned()])).await;
if let Ok(view_editor) = self.get_view_editor(view_id).await {
view_editor.filter_rows(block_id, &mut row_revs).await;
view_editor.sort_rows(&mut row_revs).await;
}
Ok(row_revs)
}
pub async fn filter_rows(
&self,
view_id: &str,
block_id: &str,
rows: Vec<Arc<RowRevision>>,
) -> FlowyResult<Vec<Arc<RowRevision>>> {
let rows = match self.get_view_editor(view_id).await {
Ok(view_editor) => view_editor.filter_rows(block_id, rows).await,
Err(_) => rows,
};
Ok(rows)
Ok(row_revs)
}
pub async fn duplicate_grid_view(&self) -> FlowyResult<String> {

View File

@ -1,5 +1,5 @@
use crate::entities::{GridLayout, GridLayoutPB, GridSettingPB};
use crate::services::filter::{FilterDelegate, FilterType};
use crate::services::filter::{FilterController, FilterDelegate, FilterType};
use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter};
use crate::services::row::GridBlockRowRevision;
use crate::services::sort::{SortDelegate, SortType};
@ -169,6 +169,7 @@ impl FilterDelegate for GridViewFilterDelegateImpl {
pub(crate) struct GridViewSortDelegateImpl {
pub(crate) editor_delegate: Arc<dyn GridViewEditorDelegate>,
pub(crate) view_revision_pad: Arc<RwLock<GridViewRevisionPad>>,
pub(crate) filter_controller: Arc<RwLock<FilterController>>,
}
impl SortDelegate for GridViewSortDelegateImpl {
@ -187,6 +188,16 @@ impl SortDelegate for GridViewSortDelegateImpl {
})
}
fn get_row_revs(&self) -> Fut<Vec<Arc<RowRevision>>> {
let filter_controller = self.filter_controller.clone();
let editor_delegate = self.editor_delegate.clone();
to_fut(async move {
let mut row_revs = editor_delegate.get_row_revs(None).await;
filter_controller.write().await.filter_row_revs(&mut row_revs).await;
row_revs
})
}
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>> {
self.editor_delegate.get_field_rev(field_id)
}

View File

@ -9,13 +9,13 @@ use grid_rev_model::RowChangeset;
async fn grid_create_row_count_test() {
let mut test = GridRowTest::new().await;
let scripts = vec![
AssertRowCount(5),
AssertRowCount(6),
CreateEmptyRow,
CreateEmptyRow,
CreateRow {
row_rev: test.row_builder().build(),
},
AssertRowCount(8),
AssertRowCount(9),
];
test.run_scripts(scripts).await;
}
@ -30,12 +30,12 @@ async fn grid_update_row() {
visibility: None,
cell_by_field_id: Default::default(),
};
let scripts = vec![AssertRowCount(5), CreateRow { row_rev }, UpdateRow { changeset }];
let row_count = test.row_revs.len();
let scripts = vec![CreateRow { row_rev }, UpdateRow { changeset }];
test.run_scripts(scripts).await;
let expected_row = test.last_row().unwrap();
let scripts = vec![AssertRow { expected_row }, AssertRowCount(6)];
let scripts = vec![AssertRow { expected_row }, AssertRowCount(row_count + 1)];
test.run_scripts(scripts).await;
}
@ -45,20 +45,20 @@ async fn grid_delete_row() {
let row_1 = test.row_builder().build();
let row_2 = test.row_builder().build();
let row_ids = vec![row_1.id.clone(), row_2.id.clone()];
let row_count = test.row_revs.len() as i32;
let scripts = vec![
AssertRowCount(5),
CreateRow { row_rev: row_1 },
CreateRow { row_rev: row_2 },
AssertBlockCount(1),
AssertBlock {
block_index: 0,
row_count: 7,
row_count: row_count + 2,
start_row_index: 0,
},
DeleteRows { row_ids },
AssertBlock {
block_index: 0,
row_count: 5,
row_count,
start_row_index: 0,
},
];

View File

@ -113,7 +113,7 @@ impl GridRowTest {
expected,
} => {
let id = CellPathParams {
view_id: self.grid_id.clone(),
view_id: self.view_id.clone(),
field_id,
row_id,
};

View File

@ -24,17 +24,19 @@ impl GridCellTest {
pub async fn run_script(&mut self, script: CellScript) {
// let grid_manager = self.sdk.grid_manager.clone();
// let pool = self.sdk.user_session.db_pool().unwrap();
let rev_manager = self.editor.rev_manager();
let _cache = rev_manager.revision_cache().await;
// let rev_manager = self.editor.rev_manager();
// let _cache = rev_manager.revision_cache().await;
match script {
CellScript::UpdateCell { changeset, is_err } => {
let result = self.editor.update_cell_with_changeset(changeset).await;
let result = self
.editor
.update_cell_with_changeset(&changeset.row_id, &changeset.field_id, changeset.type_cell_data)
.await;
if is_err {
assert!(result.is_err())
} else {
let _ = result.unwrap();
self.row_revs = self.get_row_revs().await;
}
} // CellScript::AssertGridRevisionPad => {
// sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;

View File

@ -2,6 +2,7 @@ use crate::grid::cell_test::script::CellScript::*;
use crate::grid::cell_test::script::GridCellTest;
use crate::grid::field_test::util::make_date_cell_string;
use flowy_grid::entities::{CellChangesetPB, FieldType};
use flowy_grid::services::cell::ToCellChangesetString;
use flowy_grid::services::field::selection_type_option::SelectOptionCellChangeset;
use flowy_grid::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
@ -25,15 +26,18 @@ async fn grid_cell_update() {
FieldType::DateTime => make_date_cell_string("123"),
FieldType::SingleSelect => {
let type_option = SingleSelectTypeOptionPB::from(field_rev);
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id)
.to_cell_changeset_str()
}
FieldType::MultiSelect => {
let type_option = MultiSelectTypeOptionPB::from(field_rev);
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id)
.to_cell_changeset_str()
}
FieldType::Checklist => {
let type_option = ChecklistTypeOptionPB::from(field_rev);
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id)
.to_cell_changeset_str()
}
FieldType::Checkbox => "1".to_string(),
FieldType::URL => "1".to_string(),

View File

@ -49,7 +49,7 @@ impl GridFieldTest {
}
pub fn view_id(&self) -> String {
self.grid_id.clone()
self.view_id.clone()
}
pub fn field_count(&self) -> usize {
@ -100,7 +100,7 @@ impl GridFieldTest {
FieldScript::UpdateTypeOption { field_id, type_option } => {
//
self.editor
.update_field_type_option(&self.grid_id, &field_id, type_option, None)
.update_field_type_option(&self.view_id, &field_id, type_option, None)
.await
.unwrap();
self.field_revs = self.editor.get_field_revs(None).await.unwrap();

View File

@ -9,7 +9,7 @@ async fn grid_filter_checklist_is_incomplete_test() {
CreateChecklistFilter {
condition: ChecklistFilterConditionPB::IsIncomplete,
},
AssertNumberOfVisibleRows { expected: 4 },
AssertNumberOfVisibleRows { expected: 5 },
];
test.run_scripts(scripts).await;
}

View File

@ -27,7 +27,7 @@ async fn grid_filter_date_after_test() {
end: None,
timestamp: Some(1647251762),
},
AssertNumberOfVisibleRows { expected: 2 },
AssertNumberOfVisibleRows { expected: 3 },
];
test.run_scripts(scripts).await;
}
@ -42,7 +42,7 @@ async fn grid_filter_date_on_or_after_test() {
end: None,
timestamp: Some(1668359085),
},
AssertNumberOfVisibleRows { expected: 2 },
AssertNumberOfVisibleRows { expected: 3 },
];
test.run_scripts(scripts).await;
}

View File

@ -76,7 +76,7 @@ async fn grid_filter_number_is_not_empty_test() {
condition: NumberFilterConditionPB::NumberIsNotEmpty,
content: "".to_string(),
},
AssertNumberOfVisibleRows { expected: 4 },
AssertNumberOfVisibleRows { expected: 5 },
];
test.run_scripts(scripts).await;
}

View File

@ -19,11 +19,11 @@ use crate::grid::grid_editor::GridEditorTest;
pub enum FilterScript {
UpdateTextCell {
row_index: usize,
row_id: String,
text: String,
},
UpdateSingleSelectCell {
row_index: usize,
row_id: String,
option_id: String,
},
InsertFilter {
@ -103,7 +103,7 @@ impl GridFilterTest {
}
pub fn view_id(&self) -> String {
self.grid_id.clone()
self.view_id.clone()
}
pub async fn get_all_filters(&self) -> Vec<FilterPB> {
@ -118,13 +118,13 @@ impl GridFilterTest {
pub async fn run_script(&mut self, script: FilterScript) {
match script {
FilterScript::UpdateTextCell { row_index, text} => {
FilterScript::UpdateTextCell { row_id, text} => {
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
self.update_text_cell(row_index, &text).await;
self.update_text_cell(row_id, &text).await;
}
FilterScript::UpdateSingleSelectCell { row_index, option_id} => {
FilterScript::UpdateSingleSelectCell { row_id, option_id} => {
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
self.update_single_select_cell(row_index, &option_id).await;
self.update_single_select_cell(row_id, &option_id).await;
}
FilterScript::InsertFilter { payload } => {
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
@ -241,13 +241,15 @@ impl GridFilterTest {
match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await {
Ok(changed) => {
//
match changed.unwrap() { GridViewChanged::DidReceiveFilterResult(changed) => {
match changed.unwrap() { GridViewChanged::FilterNotification(changed) => {
assert_eq!(changed.visible_rows.len(), visible_row_len, "visible rows not match");
assert_eq!(changed.invisible_rows.len(), hide_row_len, "invisible rows not match");
} }
}
_ => {}
}
},
Err(e) => {
panic!("Process task timeout: {:?}", e);
panic!("Process filter task timeout: {:?}", e);
}
}
}
@ -268,38 +270,6 @@ impl GridFilterTest {
let _ = self.editor.create_or_update_filter(params).await.unwrap();
}
async fn update_text_cell(&self, row_index: usize, content: &str) {
let row_rev = &self.inner.row_revs[row_index];
let field_rev = self.inner.field_revs.iter().find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::RichText
}).unwrap();
let changeset =CellChangesetPB {
grid_id: self.grid_id.clone(),
row_id: row_rev.id.clone(),
field_id: field_rev.id.clone(),
type_cell_data: content.to_string(),
};
self.editor.update_cell_with_changeset(changeset).await.unwrap();
}
async fn update_single_select_cell(&self, row_index: usize, option_id: &str) {
let row_rev = &self.inner.row_revs[row_index];
let field_rev = self.inner.field_revs.iter().find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::SingleSelect
}).unwrap();
let content = SelectOptionCellChangeset::from_insert_option_id(&option_id).to_str();
let changeset =CellChangesetPB {
grid_id: self.grid_id.clone(),
row_id: row_rev.id.clone(),
field_id: field_rev.id.clone(),
type_cell_data: content,
};
self.editor.update_cell_with_changeset(changeset).await.unwrap();
}
}

View File

@ -10,7 +10,7 @@ async fn grid_filter_multi_select_is_empty_test() {
condition: SelectOptionConditionPB::OptionIsEmpty,
option_ids: vec![],
},
AssertNumberOfVisibleRows { expected: 2 },
AssertNumberOfVisibleRows { expected: 3 },
];
test.run_scripts(scripts).await;
}
@ -90,6 +90,7 @@ async fn grid_filter_single_select_is_test() {
async fn grid_filter_single_select_is_test2() {
let mut test = GridFilterTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::SingleSelect);
let row_revs = test.get_row_revs().await;
let mut options = test.get_single_select_type_option(&field_rev.id).options;
let option = options.remove(0);
let scripts = vec![
@ -99,12 +100,12 @@ async fn grid_filter_single_select_is_test2() {
},
AssertNumberOfVisibleRows { expected: 2 },
UpdateSingleSelectCell {
row_index: 1,
row_id: row_revs[1].id.clone(),
option_id: option.id.clone(),
},
AssertNumberOfVisibleRows { expected: 3 },
UpdateSingleSelectCell {
row_index: 1,
row_id: row_revs[1].id.clone(),
option_id: "".to_string(),
},
AssertFilterChanged {

View File

@ -14,7 +14,7 @@ async fn grid_filter_text_is_empty_test() {
AssertFilterCount { count: 1 },
AssertFilterChanged {
visible_row_len: 0,
hide_row_len: 4,
hide_row_len: 5,
},
];
test.run_scripts(scripts).await;
@ -65,7 +65,7 @@ async fn grid_filter_is_text_test() {
},
AssertFilterChanged {
visible_row_len: 0,
hide_row_len: 4,
hide_row_len: 5,
},
];
test.run_scripts(scripts).await;
@ -90,6 +90,8 @@ async fn grid_filter_contain_text_test() {
#[tokio::test]
async fn grid_filter_contain_text_test2() {
let mut test = GridFilterTest::new().await;
let row_revs = test.row_revs.clone();
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::Contains,
@ -100,7 +102,7 @@ async fn grid_filter_contain_text_test2() {
hide_row_len: 2,
},
UpdateTextCell {
row_index: 1,
row_id: row_revs[1].id.clone(),
text: "ABC".to_string(),
},
AssertFilterChanged {
@ -206,7 +208,7 @@ async fn grid_filter_delete_test() {
filter_type: FilterType::from(&field_rev),
},
AssertFilterCount { count: 0 },
AssertNumberOfVisibleRows { expected: 5 },
AssertNumberOfVisibleRows { expected: 6 },
])
.await;
}
@ -214,6 +216,7 @@ async fn grid_filter_delete_test() {
#[tokio::test]
async fn grid_filter_update_empty_text_cell_test() {
let mut test = GridFilterTest::new().await;
let row_revs = test.row_revs.clone();
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::TextIsEmpty,
@ -222,10 +225,10 @@ async fn grid_filter_update_empty_text_cell_test() {
AssertFilterCount { count: 1 },
AssertFilterChanged {
visible_row_len: 0,
hide_row_len: 4,
hide_row_len: 5,
},
UpdateTextCell {
row_index: 0,
row_id: row_revs[0].id.clone(),
text: "".to_string(),
},
AssertFilterChanged {

View File

@ -5,6 +5,7 @@ use crate::grid::block_test::util::GridRowTestBuilder;
use bytes::Bytes;
use flowy_error::FlowyResult;
use flowy_grid::entities::*;
use flowy_grid::services::cell::ToCellChangesetString;
use flowy_grid::services::field::SelectOptionPB;
use flowy_grid::services::field::*;
use flowy_grid::services::grid_editor::{GridRevisionEditor, GridRevisionSerde};
@ -24,7 +25,7 @@ use tokio::time::sleep;
pub struct GridEditorTest {
pub sdk: FlowySDKTest,
pub grid_id: String,
pub view_id: String,
pub editor: Arc<GridRevisionEditor>,
pub field_revs: Vec<Arc<FieldRevision>>,
pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
@ -61,7 +62,7 @@ impl GridEditorTest {
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
let field_revs = editor.get_field_revs(None).await.unwrap();
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
let row_revs = editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs;
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
assert_eq!(block_meta_revs.len(), 1);
// It seems like you should add the field in the make_test_grid() function.
@ -71,18 +72,18 @@ impl GridEditorTest {
let grid_id = test.view.id;
Self {
sdk,
grid_id,
view_id: grid_id,
editor,
field_revs,
block_meta_revs,
row_revs,
row_revs: row_pbs,
field_count: FieldType::COUNT,
row_by_row_id: HashMap::default(),
}
}
pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
self.editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs
self.editor.get_all_row_revs(&self.view_id).await.unwrap()
}
pub async fn grid_filters(&self) -> Vec<FilterPB> {
@ -153,6 +154,48 @@ impl GridEditorTest {
pub fn block_id(&self) -> &str {
&self.block_meta_revs.last().unwrap().block_id
}
pub async fn update_cell<T: ToCellChangesetString>(&mut self, field_id: &str, row_id: String, cell_changeset: T) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| field_rev.id == field_id)
.unwrap();
self.editor
.update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset)
.await
.unwrap();
}
pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::RichText
})
.unwrap()
.clone();
self.update_cell(&field_rev.id, row_id, content.to_string()).await;
}
pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::SingleSelect
})
.unwrap()
.clone();
let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(&option_id);
self.update_cell(&field_rev.id, row_id, cell_changeset).await;
}
}
pub const GOOGLE: &str = "Google";
@ -243,7 +286,7 @@ fn make_test_grid() -> BuildGridContext {
}
}
for i in 0..5 {
for i in 0..6 {
let block_id = grid_builder.block_id().to_owned();
let field_revs = grid_builder.field_revs();
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
@ -321,6 +364,20 @@ fn make_test_grid() -> BuildGridContext {
};
}
}
5 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell("5"),
FieldType::DateTime => row_builder.insert_date_cell("1671938394"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
_ => {}
}

View File

@ -90,7 +90,7 @@ impl GridGroupTest {
let to_group = groups.get(to_group_index).unwrap();
let to_row = to_group.rows.get(to_row_index).unwrap();
let params = MoveGroupRowParams {
view_id: self.inner.grid_id.clone(),
view_id: self.inner.view_id.clone(),
from_row_id: from_row.id.clone(),
to_group_id: to_group.group_id.clone(),
to_row_id: Some(to_row.id.clone()),

View File

@ -43,7 +43,7 @@ impl GridSnapshotTest {
}
pub fn grid_id(&self) -> String {
self.grid_id.clone()
self.view_id.clone()
}
pub async fn grid_pad(&self) -> GridRevisionPad {

View File

@ -1,2 +1,3 @@
mod multi_sort_test;
mod script;
mod sort_test;
mod single_sort_test;

View File

@ -0,0 +1,46 @@
use crate::grid::sort_test::script::GridSortTest;
use crate::grid::sort_test::script::SortScript::*;
use flowy_grid::entities::FieldType;
use grid_rev_model::SortCondition;
#[tokio::test]
async fn sort_text_with_checkbox_by_ascending_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox).clone();
let scripts = vec![
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["A", "", "C", "DA", "AE", "AE"],
},
AssertCellContentOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "No", "No", "No"],
},
InsertSort {
field_rev: text_field.clone(),
condition: SortCondition::Ascending,
},
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["", "A", "AE", "AE", "C", "DA"],
},
];
test.run_scripts(scripts).await;
let scripts = vec![
InsertSort {
field_rev: checkbox_field.clone(),
condition: SortCondition::Descending,
},
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["", "A", "AE", "AE", "C", "DA"],
},
AssertCellContentOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "Yes", "No", "No"],
},
];
test.run_scripts(scripts).await;
}

View File

@ -1,23 +1,45 @@
use crate::grid::grid_editor::GridEditorTest;
use async_stream::stream;
use flowy_grid::entities::{AlterSortParams, CellPathParams, DeleteSortParams};
use grid_rev_model::SortRevision;
use flowy_grid::services::sort::SortType;
use flowy_grid::services::view_editor::GridViewChanged;
use futures::stream::StreamExt;
use grid_rev_model::{FieldRevision, SortCondition, SortRevision};
use std::cmp::min;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::broadcast::Receiver;
pub enum SortScript {
InsertSort {
params: AlterSortParams,
field_rev: Arc<FieldRevision>,
condition: SortCondition,
},
DeleteSort {
params: DeleteSortParams,
field_rev: Arc<FieldRevision>,
sort_id: String,
},
AssertTextOrder {
AssertCellContentOrder {
field_id: String,
orders: Vec<&'static str>,
},
UpdateTextCell {
row_id: String,
text: String,
},
AssertSortChanged {
old_row_orders: Vec<&'static str>,
new_row_orders: Vec<&'static str>,
},
Wait {
millis: u64,
},
}
pub struct GridSortTest {
inner: GridEditorTest,
pub current_sort_rev: Option<SortRevision>,
recv: Option<Receiver<GridViewChanged>>,
}
impl GridSortTest {
@ -26,6 +48,7 @@ impl GridSortTest {
Self {
inner: editor_test,
current_sort_rev: None,
recv: None,
}
}
pub async fn run_scripts(&mut self, scripts: Vec<SortScript>) {
@ -36,32 +59,101 @@ impl GridSortTest {
pub async fn run_script(&mut self, script: SortScript) {
match script {
SortScript::InsertSort { params } => {
SortScript::InsertSort { condition, field_rev } => {
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id).await.unwrap());
let params = AlterSortParams {
view_id: self.view_id.clone(),
field_id: field_rev.id.clone(),
sort_id: None,
field_type: field_rev.ty,
condition: condition.into(),
};
let sort_rev = self.editor.create_or_update_sort(params).await.unwrap();
self.current_sort_rev = Some(sort_rev);
}
SortScript::DeleteSort { params } => {
//
SortScript::DeleteSort { field_rev, sort_id } => {
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id).await.unwrap());
let params = DeleteSortParams {
view_id: self.view_id.clone(),
sort_type: SortType::from(&field_rev),
sort_id,
};
self.editor.delete_sort(params).await.unwrap();
self.current_sort_rev = None;
}
SortScript::AssertTextOrder { field_id, orders } => {
SortScript::AssertCellContentOrder { field_id, orders } => {
let mut cells = vec![];
let rows = self.editor.get_grid(&self.grid_id).await.unwrap().rows;
let rows = self.editor.get_grid(&self.view_id).await.unwrap().rows;
for row in rows {
let params = CellPathParams {
view_id: self.grid_id.clone(),
view_id: self.view_id.clone(),
field_id: field_id.clone(),
row_id: row.id,
};
let cell = self.editor.get_cell_display_str(&params).await;
cells.push(cell);
}
assert_eq!(cells, orders)
if orders.is_empty() {
assert_eq!(cells, orders);
} else {
let len = min(cells.len(), orders.len());
assert_eq!(cells.split_at(len).0, orders);
}
}
SortScript::UpdateTextCell { row_id, text } => {
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id).await.unwrap());
self.update_text_cell(row_id, &text).await;
}
SortScript::AssertSortChanged {
new_row_orders,
old_row_orders,
} => {
if let Some(receiver) = self.recv.take() {
assert_sort_changed(
receiver,
new_row_orders.into_iter().map(|order| order.to_owned()).collect(),
old_row_orders.into_iter().map(|order| order.to_owned()).collect(),
)
.await;
}
}
SortScript::Wait { millis } => {
tokio::time::sleep(Duration::from_millis(millis)).await;
}
}
}
}
async fn assert_sort_changed(
mut receiver: Receiver<GridViewChanged>,
new_row_orders: Vec<String>,
old_row_orders: Vec<String>,
) {
let stream = stream! {
loop {
tokio::select! {
changed = receiver.recv() => yield changed.unwrap(),
_ = tokio::time::sleep(Duration::from_secs(2)) => break,
};
}
};
stream
.for_each(|changed| async {
match changed {
GridViewChanged::ReorderAllRowsNotification(_changed) => {}
GridViewChanged::ReorderSingleRowNotification(changed) => {
let mut old_row_orders = old_row_orders.clone();
let old = old_row_orders.remove(changed.old_index);
old_row_orders.insert(changed.new_index, old);
assert_eq!(old_row_orders, new_row_orders);
}
_ => {}
}
})
.await;
}
impl std::ops::Deref for GridSortTest {
type Target = GridEditorTest;

View File

@ -0,0 +1,257 @@
use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
use flowy_grid::entities::FieldType;
use grid_rev_model::SortCondition;
#[tokio::test]
async fn sort_text_by_ascending_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let scripts = vec![
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["A", "", "C", "DA", "AE", "AE"],
},
InsertSort {
field_rev: text_field.clone(),
condition: SortCondition::Ascending,
},
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["", "A", "AE", "AE", "C", "DA"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_change_notification_by_update_text_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
let scripts = vec![
InsertSort {
field_rev: text_field.clone(),
condition: SortCondition::Ascending,
},
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["", "A", "AE", "AE", "C", "DA"],
},
// Wait the insert task to finish. The cost of time should be less than 200 milliseconds.
Wait { millis: 200 },
];
test.run_scripts(scripts).await;
let row_revs = test.get_row_revs().await;
let scripts = vec![
UpdateTextCell {
row_id: row_revs[2].id.clone(),
text: "E".to_string(),
},
AssertSortChanged {
old_row_orders: vec!["", "A", "E", "AE", "C", "DA"],
new_row_orders: vec!["", "A", "AE", "C", "DA", "E"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_text_by_ascending_and_delete_sort_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
let scripts = vec![InsertSort {
field_rev: text_field.clone(),
condition: SortCondition::Ascending,
}];
test.run_scripts(scripts).await;
let sort_rev = test.current_sort_rev.as_ref().unwrap();
let scripts = vec![
DeleteSort {
field_rev: text_field.clone(),
sort_id: sort_rev.id.clone(),
},
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["A", "", "C", "DA", "AE"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_text_by_descending_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let scripts = vec![
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["A", "", "C", "DA", "AE", "AE"],
},
InsertSort {
field_rev: text_field.clone(),
condition: SortCondition::Descending,
},
AssertCellContentOrder {
field_id: text_field.id.clone(),
orders: vec!["DA", "C", "AE", "AE", "A", ""],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_checkbox_by_ascending_test() {
let mut test = GridSortTest::new().await;
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
let scripts = vec![
AssertCellContentOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "No", "No", "No"],
},
InsertSort {
field_rev: checkbox_field.clone(),
condition: SortCondition::Ascending,
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_checkbox_by_descending_test() {
let mut test = GridSortTest::new().await;
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
let scripts = vec![
AssertCellContentOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
},
InsertSort {
field_rev: checkbox_field.clone(),
condition: SortCondition::Descending,
},
AssertCellContentOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_date_by_ascending_test() {
let mut test = GridSortTest::new().await;
let date_field = test.get_first_field_rev(FieldType::DateTime);
let scripts = vec![
AssertCellContentOrder {
field_id: date_field.id.clone(),
orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/17", "2022/11/13"],
},
InsertSort {
field_rev: date_field.clone(),
condition: SortCondition::Ascending,
},
AssertCellContentOrder {
field_id: date_field.id.clone(),
orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/13", "2022/11/17"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_date_by_descending_test() {
let mut test = GridSortTest::new().await;
let date_field = test.get_first_field_rev(FieldType::DateTime);
let scripts = vec![
AssertCellContentOrder {
field_id: date_field.id.clone(),
orders: vec![
"2022/03/14",
"2022/03/14",
"2022/03/14",
"2022/11/17",
"2022/11/13",
"2022/12/25",
],
},
InsertSort {
field_rev: date_field.clone(),
condition: SortCondition::Descending,
},
AssertCellContentOrder {
field_id: date_field.id.clone(),
orders: vec![
"2022/12/25",
"2022/11/17",
"2022/11/13",
"2022/03/14",
"2022/03/14",
"2022/03/14",
],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_number_by_descending_test() {
let mut test = GridSortTest::new().await;
let number_field = test.get_first_field_rev(FieldType::Number);
let scripts = vec![
AssertCellContentOrder {
field_id: number_field.id.clone(),
orders: vec!["$1", "$2", "$3", "$4", "", "$5"],
},
InsertSort {
field_rev: number_field.clone(),
condition: SortCondition::Descending,
},
AssertCellContentOrder {
field_id: number_field.id.clone(),
orders: vec!["$5", "$4", "$3", "$2", "$1", ""],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_single_select_by_descending_test() {
let mut test = GridSortTest::new().await;
let single_select = test.get_first_field_rev(FieldType::SingleSelect);
let scripts = vec![
AssertCellContentOrder {
field_id: single_select.id.clone(),
orders: vec!["", "", "Completed", "Completed", "Planned", "Planned"],
},
InsertSort {
field_rev: single_select.clone(),
condition: SortCondition::Descending,
},
AssertCellContentOrder {
field_id: single_select.id.clone(),
orders: vec!["Planned", "Planned", "Completed", "Completed", "", ""],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_multi_select_by_ascending_test() {
let mut test = GridSortTest::new().await;
let multi_select = test.get_first_field_rev(FieldType::MultiSelect);
let scripts = vec![
AssertCellContentOrder {
field_id: multi_select.id.clone(),
orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", "", ""],
},
InsertSort {
field_rev: multi_select.clone(),
condition: SortCondition::Ascending,
},
AssertCellContentOrder {
field_id: multi_select.id.clone(),
orders: vec!["", "", "", "Facebook", "Google,Facebook", "Google,Twitter"],
},
];
test.run_scripts(scripts).await;
}

View File

@ -1,279 +0,0 @@
use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
use flowy_grid::entities::{AlterSortParams, DeleteSortParams, FieldType};
use flowy_grid::services::sort::SortType;
use grid_rev_model::SortCondition;
#[tokio::test]
async fn sort_text_by_ascending_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: text_field.id.clone(),
orders: vec!["A", "", "C", "DA", "AE"],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: text_field.id.clone(),
sort_id: None,
field_type: FieldType::RichText.into(),
condition: SortCondition::Ascending.into(),
},
},
AssertTextOrder {
field_id: text_field.id.clone(),
orders: vec!["", "A", "AE", "C", "DA"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_text_by_ascending_and_delete_sort_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
let view_id = test.grid_id.clone();
let scripts = vec![InsertSort {
params: AlterSortParams {
view_id: view_id.clone(),
field_id: text_field.id.clone(),
sort_id: None,
field_type: FieldType::RichText.into(),
condition: SortCondition::Ascending.into(),
},
}];
test.run_scripts(scripts).await;
let sort_rev = test.current_sort_rev.as_ref().unwrap();
let scripts = vec![
DeleteSort {
params: DeleteSortParams {
view_id,
sort_type: SortType::from(&text_field),
sort_id: sort_rev.id.clone(),
},
},
AssertTextOrder {
field_id: text_field.id.clone(),
orders: vec!["A", "", "C", "DA", "AE"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_text_by_descending_test() {
let mut test = GridSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: text_field.id.clone(),
orders: vec!["A", "", "C", "DA", "AE"],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: text_field.id.clone(),
sort_id: None,
field_type: FieldType::RichText.into(),
condition: SortCondition::Descending.into(),
},
},
AssertTextOrder {
field_id: text_field.id.clone(),
orders: vec!["DA", "C", "AE", "A", ""],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_checkbox_by_ascending_test() {
let mut test = GridSortTest::new().await;
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "No", "No", "No"],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: checkbox_field.id.clone(),
sort_id: None,
field_type: FieldType::Checkbox.into(),
condition: SortCondition::Ascending.into(),
},
},
// AssertTextOrder {
// field_id: checkbox_field.id.clone(),
// orders: vec!["No", "No", "No", "Yes", "Yes"],
// },
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_checkbox_by_descending_test() {
let mut test = GridSortTest::new().await;
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "No", "No", "No"],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: checkbox_field.id.clone(),
sort_id: None,
field_type: FieldType::Checkbox.into(),
condition: SortCondition::Descending.into(),
},
},
AssertTextOrder {
field_id: checkbox_field.id.clone(),
orders: vec!["Yes", "Yes", "No", "No", "No"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_date_by_ascending_test() {
let mut test = GridSortTest::new().await;
let date_field = test.get_first_field_rev(FieldType::DateTime);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: date_field.id.clone(),
orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/17", "2022/11/13"],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: date_field.id.clone(),
sort_id: None,
field_type: FieldType::DateTime.into(),
condition: SortCondition::Ascending.into(),
},
},
AssertTextOrder {
field_id: date_field.id.clone(),
orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/13", "2022/11/17"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_date_by_descending_test() {
let mut test = GridSortTest::new().await;
let date_field = test.get_first_field_rev(FieldType::DateTime);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: date_field.id.clone(),
orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/17", "2022/11/13"],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: date_field.id.clone(),
sort_id: None,
field_type: FieldType::DateTime.into(),
condition: SortCondition::Descending.into(),
},
},
AssertTextOrder {
field_id: date_field.id.clone(),
orders: vec!["2022/11/17", "2022/11/13", "2022/03/14", "2022/03/14", "2022/03/14"],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_number_by_descending_test() {
let mut test = GridSortTest::new().await;
let number_field = test.get_first_field_rev(FieldType::Number);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: number_field.id.clone(),
orders: vec!["$1", "$2", "$3", "$4", ""],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: number_field.id.clone(),
sort_id: None,
field_type: FieldType::Number.into(),
condition: SortCondition::Descending.into(),
},
},
AssertTextOrder {
field_id: number_field.id.clone(),
orders: vec!["$4", "$3", "$2", "$1", ""],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_single_select_by_descending_test() {
let mut test = GridSortTest::new().await;
let single_select = test.get_first_field_rev(FieldType::SingleSelect);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: single_select.id.clone(),
orders: vec!["", "", "Completed", "Completed", "Planned"],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: single_select.id.clone(),
sort_id: None,
field_type: FieldType::SingleSelect.into(),
condition: SortCondition::Descending.into(),
},
},
AssertTextOrder {
field_id: single_select.id.clone(),
orders: vec!["Planned", "Completed", "Completed", "", ""],
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn sort_multi_select_by_ascending_test() {
let mut test = GridSortTest::new().await;
let multi_select = test.get_first_field_rev(FieldType::MultiSelect);
let view_id = test.grid_id.clone();
let scripts = vec![
AssertTextOrder {
field_id: multi_select.id.clone(),
orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", ""],
},
InsertSort {
params: AlterSortParams {
view_id,
field_id: multi_select.id.clone(),
sort_id: None,
field_type: FieldType::MultiSelect.into(),
condition: SortCondition::Ascending.into(),
},
},
AssertTextOrder {
field_id: multi_select.id.clone(),
orders: vec!["", "", "Facebook", "Google,Facebook", "Google,Twitter"],
},
];
test.run_scripts(scripts).await;
}

View File

@ -71,12 +71,12 @@ impl TaskDispatcher {
Ok(result) => match result {
Ok(_) => task.set_state(TaskState::Done),
Err(e) => {
tracing::error!("Process {} task failed: {:?}", handler.handler_id(), e);
tracing::error!("Process {} task failed: {:?}", handler.handler_name(), e);
task.set_state(TaskState::Failure);
}
},
Err(e) => {
tracing::error!("Process {} task timeout: {:?}", handler.handler_id(), e);
tracing::error!("Process {} task timeout: {:?}", handler.handler_name(), e);
task.set_state(TaskState::Timeout);
}
}
@ -144,6 +144,10 @@ impl TaskRunner {
pub trait TaskHandler: Send + Sync + 'static {
fn handler_id(&self) -> &str;
fn handler_name(&self) -> &str {
""
}
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error>;
}
@ -155,6 +159,10 @@ where
(**self).handler_id()
}
fn handler_name(&self) -> &str {
(**self).handler_name()
}
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> {
(**self).run(content)
}
@ -168,6 +176,10 @@ where
(**self).handler_id()
}
fn handler_name(&self) -> &str {
(**self).handler_name()
}
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> {
(**self).run(content)
}