mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: add tauri database group test (#1924)
* chore: add tauri database group test * chore: add more tests * chore: enable run all tests * chore: rename test folder
This commit is contained in:
parent
8e22ef2230
commit
7e7cee4bf4
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
||||
|
||||
@ -60,6 +60,6 @@ class DatabaseBackendService {
|
||||
|
||||
Future<Either<RepeatedGroupPB, FlowyError>> loadGroups() {
|
||||
final payload = DatabaseViewIdPB(value: viewId);
|
||||
return DatabaseEventGetGroup(payload).send();
|
||||
return DatabaseEventGetGroups(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
|
||||
import '../grid/presentation/widgets/filter/filter_info.dart';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'field_service.freezed.dart';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@ -6,7 +7,6 @@ import 'package:appflowy_backend/protobuf/flowy-database/checkbox_filter.pbserve
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/checklist_filter.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/date_filter.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/number_filter.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/select_option_filter.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/grid_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/sort_entities.pb.dart';
|
||||
|
||||
|
@ -68,9 +68,8 @@ class GroupController {
|
||||
|
||||
if (index != -1) {
|
||||
group.rows[index] = updatedRow;
|
||||
delegate.updateRow(group, updatedRow);
|
||||
}
|
||||
|
||||
delegate.updateRow(group, updatedRow);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
@ -78,29 +77,6 @@ class GroupController {
|
||||
});
|
||||
}
|
||||
|
||||
// GroupChangesetPB _transformChangeset(GroupChangesetPB changeset) {
|
||||
// final insertedRows = changeset.insertedRows
|
||||
// .where(
|
||||
// (delete) => !changeset.deletedRows.contains(delete.row.id),
|
||||
// )
|
||||
// .toList();
|
||||
|
||||
// final deletedRows = changeset.deletedRows
|
||||
// .where((deletedRowId) =>
|
||||
// changeset.insertedRows
|
||||
// .indexWhere((insert) => insert.row.id == deletedRowId) ==
|
||||
// -1)
|
||||
// .toList();
|
||||
|
||||
// return changeset.rebuild((rebuildChangeset) {
|
||||
// rebuildChangeset.insertedRows.clear();
|
||||
// rebuildChangeset.insertedRows.addAll(insertedRows);
|
||||
|
||||
// rebuildChangeset.deletedRows.clear();
|
||||
// rebuildChangeset.deletedRows.addAll(deletedRows);
|
||||
// });
|
||||
// }
|
||||
|
||||
Future<void> dispose() async {
|
||||
_listener.stop();
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import { SignUpPage } from './views/SignUpPage';
|
||||
import { ConfirmAccountPage } from './views/ConfirmAccountPage';
|
||||
import { ErrorHandlerPage } from './components/error/ErrorHandlerPage';
|
||||
import initializeI18n from './stores/i18n/initializeI18n';
|
||||
import { TestAPI } from './components/TestApiButton/TestAPI';
|
||||
import { TestAPI } from './components/tests/TestAPI';
|
||||
import { GetStarted } from './components/auth/GetStarted/GetStarted';
|
||||
|
||||
initializeI18n();
|
||||
|
@ -1,334 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
FieldType,
|
||||
NumberFormat,
|
||||
NumberTypeOptionPB,
|
||||
SelectOptionCellDataPB,
|
||||
SingleSelectTypeOptionPB,
|
||||
ViewLayoutTypePB,
|
||||
} from '../../../services/backend';
|
||||
import { Log } from '../../utils/log';
|
||||
import {
|
||||
assertFieldName,
|
||||
assertNumberOfFields,
|
||||
assertNumberOfRows,
|
||||
assertTextCell,
|
||||
createTestDatabaseView,
|
||||
editTextCell,
|
||||
findFirstFieldInfoWithFieldType,
|
||||
makeMultiSelectCellController,
|
||||
makeSingleSelectCellController,
|
||||
makeTextCellController,
|
||||
openTestDatabase,
|
||||
} from './DatabaseTestHelper';
|
||||
import {
|
||||
SelectOptionBackendService,
|
||||
SelectOptionCellBackendService,
|
||||
} from '../../stores/effects/database/cell/select_option_bd_svc';
|
||||
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
|
||||
import { None, Some } from 'ts-results';
|
||||
import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
|
||||
import {
|
||||
makeNumberTypeOptionContext,
|
||||
makeSingleSelectTypeOptionContext,
|
||||
} from '../../stores/effects/database/field/type_option/type_option_context';
|
||||
|
||||
export const TestCreateGrid = () => {
|
||||
async function createBuildInGrid() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
databaseController.subscribe({
|
||||
onViewChanged: (databasePB) => {
|
||||
Log.debug('Did receive database:' + databasePB);
|
||||
},
|
||||
// onRowsChanged: async (rows) => {
|
||||
// if (rows.length !== 3) {
|
||||
// throw Error('Expected number of rows is 3, but receive ' + rows.length);
|
||||
// }
|
||||
// },
|
||||
onFieldsChanged: (fields) => {
|
||||
if (fields.length !== 3) {
|
||||
throw Error('Expected number of fields is 3, but receive ' + fields.length);
|
||||
}
|
||||
},
|
||||
});
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test create build-in grid', createBuildInGrid);
|
||||
};
|
||||
|
||||
export const TestEditCell = () => {
|
||||
async function testGridRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||
const cellContent = index.toString();
|
||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
|
||||
await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||
await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||
}
|
||||
}
|
||||
|
||||
return TestButton('Test editing cell', testGridRow);
|
||||
};
|
||||
|
||||
export const TestCreateRow = () => {
|
||||
async function testCreateRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
await assertNumberOfRows(view.id, 3);
|
||||
|
||||
// Create a row from a DatabaseController or create using the RowBackendService
|
||||
await databaseController.createRow();
|
||||
await assertNumberOfRows(view.id, 4);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test create row', testCreateRow);
|
||||
};
|
||||
export const TestDeleteRow = () => {
|
||||
async function testDeleteRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
const rows = databaseController.databaseViewCache.rowInfos;
|
||||
const svc = new RowBackendService(view.id);
|
||||
await svc.deleteRow(rows[0].row.id);
|
||||
await assertNumberOfRows(view.id, 2);
|
||||
|
||||
// Wait the databaseViewCache get the change notification and
|
||||
// update the rows.
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
if (databaseController.databaseViewCache.rowInfos.length !== 2) {
|
||||
throw Error('The number of rows is not match');
|
||||
}
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test delete row', testDeleteRow);
|
||||
};
|
||||
export const TestCreateSelectOptionInCell = () => {
|
||||
async function testCreateOptionInCell() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||
if (index === 0) {
|
||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
|
||||
const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
await cellController.subscribeChanged({
|
||||
onCellChanged: (value) => {
|
||||
if (value.some) {
|
||||
const option: SelectOptionCellDataPB = value.unwrap();
|
||||
console.log(option);
|
||||
}
|
||||
},
|
||||
});
|
||||
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
|
||||
await backendSvc.createOption({ name: 'option' + index });
|
||||
await cellController.dispose();
|
||||
}
|
||||
}
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test create a select option in cell', testCreateOptionInCell);
|
||||
};
|
||||
|
||||
export const TestGetSingleSelectFieldData = () => {
|
||||
async function testGetSingleSelectFieldData() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Find the single select column
|
||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||
)!;
|
||||
const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
|
||||
const singleSelectTypeOptionContext = makeSingleSelectTypeOptionContext(typeOptionController);
|
||||
|
||||
// Create options
|
||||
const singleSelectTypeOptionPB: SingleSelectTypeOptionPB = await singleSelectTypeOptionContext
|
||||
.getTypeOption()
|
||||
.then((result) => result.unwrap());
|
||||
const backendSvc = new SelectOptionBackendService(view.id, singleSelect.field.id);
|
||||
const option1 = await backendSvc.createOption({ name: 'Task 1' }).then((result) => result.unwrap());
|
||||
singleSelectTypeOptionPB.options.splice(0, 0, option1);
|
||||
const option2 = await backendSvc.createOption({ name: 'Task 2' }).then((result) => result.unwrap());
|
||||
singleSelectTypeOptionPB.options.splice(0, 0, option2);
|
||||
const option3 = await backendSvc.createOption({ name: 'Task 3' }).then((result) => result.unwrap());
|
||||
singleSelectTypeOptionPB.options.splice(0, 0, option3);
|
||||
await singleSelectTypeOptionContext.setTypeOption(singleSelectTypeOptionPB);
|
||||
|
||||
// Read options
|
||||
const options = singleSelectTypeOptionPB.options;
|
||||
console.log(options);
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
|
||||
};
|
||||
|
||||
export const TestSwitchFromSingleSelectToNumber = () => {
|
||||
async function testSwitchFromSingleSelectToNumber() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Find the single select column
|
||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||
)!;
|
||||
const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
|
||||
await typeOptionController.switchToField(FieldType.Number);
|
||||
|
||||
// Check the number type option
|
||||
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||
const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
|
||||
.getTypeOption()
|
||||
.then((result) => result.unwrap());
|
||||
const format: NumberFormat = numberTypeOption.format;
|
||||
if (format !== NumberFormat.Num) {
|
||||
throw Error('The default format should be number');
|
||||
}
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
|
||||
};
|
||||
|
||||
export const TestSwitchFromMultiSelectToText = () => {
|
||||
async function testSwitchFromMultiSelectToRichText() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Create multi-select field
|
||||
const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
|
||||
await typeOptionController.initialize();
|
||||
|
||||
// Insert options to first row
|
||||
const row = databaseController.databaseViewCache.rowInfos[0];
|
||||
const multiSelectField = typeOptionController.getFieldInfo();
|
||||
// const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
|
||||
const selectOptionCellController = await makeMultiSelectCellController(
|
||||
multiSelectField.field.id,
|
||||
row,
|
||||
databaseController
|
||||
).then((result) => result.unwrap());
|
||||
const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
|
||||
await backendSvc.createOption({ name: 'A' });
|
||||
await backendSvc.createOption({ name: 'B' });
|
||||
await backendSvc.createOption({ name: 'C' });
|
||||
|
||||
const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
|
||||
if (selectOptionCellData.options.length !== 3) {
|
||||
throw Error('The options should equal to 3');
|
||||
}
|
||||
|
||||
if (selectOptionCellData.select_options.length !== 3) {
|
||||
throw Error('The selected options should equal to 3');
|
||||
}
|
||||
await selectOptionCellController.dispose();
|
||||
|
||||
// Switch to RichText field type
|
||||
await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
|
||||
if (typeOptionController.fieldType !== FieldType.RichText) {
|
||||
throw Error('The field type should be text');
|
||||
}
|
||||
|
||||
const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
const cellContent = await textCellController.getCellData();
|
||||
if (cellContent.unwrap() !== 'A,B,C') {
|
||||
throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
|
||||
}
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
|
||||
};
|
||||
|
||||
export const TestEditField = () => {
|
||||
async function testEditField() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
const fieldInfos = databaseController.fieldController.fieldInfos;
|
||||
|
||||
// Modify the name of the field
|
||||
const firstFieldInfo = fieldInfos[0];
|
||||
const controller = new TypeOptionController(view.id, Some(firstFieldInfo));
|
||||
await controller.initialize();
|
||||
const newName = 'hello world';
|
||||
await controller.setFieldName(newName);
|
||||
|
||||
await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test edit the column name', testEditField);
|
||||
};
|
||||
|
||||
export const TestCreateNewField = () => {
|
||||
async function testCreateNewField() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
await assertNumberOfFields(view.id, 3);
|
||||
|
||||
// Modify the name of the field
|
||||
const controller = new TypeOptionController(view.id, None);
|
||||
await controller.initialize();
|
||||
await assertNumberOfFields(view.id, 4);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test create a new column', testCreateNewField);
|
||||
};
|
||||
|
||||
export const TestDeleteField = () => {
|
||||
async function testDeleteField() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Modify the name of the field.
|
||||
// The fieldInfos[0] is the primary field by default, we can't delete it.
|
||||
// So let choose the second fieldInfo.
|
||||
const fieldInfo = databaseController.fieldController.fieldInfos[1];
|
||||
const controller = new TypeOptionController(view.id, Some(fieldInfo));
|
||||
await controller.initialize();
|
||||
await assertNumberOfFields(view.id, 3);
|
||||
await controller.deleteField();
|
||||
await assertNumberOfFields(view.id, 2);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
return TestButton('Test delete a new column', testDeleteField);
|
||||
};
|
||||
|
||||
const TestButton = (title: string, onClick: () => void) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<button className='rounded-md bg-gray-300 p-4' type='button' onClick={() => onClick()}>
|
||||
{title}
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
@ -1,4 +1,10 @@
|
||||
import { FieldType, ViewLayoutTypePB, ViewPB, WorkspaceSettingPB } from '../../../services/backend';
|
||||
import {
|
||||
FieldType,
|
||||
SingleSelectTypeOptionPB,
|
||||
ViewLayoutTypePB,
|
||||
ViewPB,
|
||||
WorkspaceSettingPB,
|
||||
} from '../../../services/backend';
|
||||
import { FolderEventReadCurrentWorkspace } from '../../../services/backend/events/flowy-folder';
|
||||
import { AppBackendService } from '../../stores/effects/folder/app/app_bd_svc';
|
||||
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||
@ -14,6 +20,10 @@ import {
|
||||
import { None, Option, Some } from 'ts-results';
|
||||
import { TypeOptionBackendService } from '../../stores/effects/database/field/type_option/type_option_bd_svc';
|
||||
import { DatabaseBackendService } from '../../stores/effects/database/database_bd_svc';
|
||||
import { FieldInfo } from '../../stores/effects/database/field/field_controller';
|
||||
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
|
||||
import { makeSingleSelectTypeOptionContext } from '../../stores/effects/database/field/type_option/type_option_context';
|
||||
import { SelectOptionBackendService } from '../../stores/effects/database/cell/select_option_bd_svc';
|
||||
|
||||
// Create a database view for specific layout type
|
||||
// Do not use it production code. Just for testing
|
||||
@ -168,3 +178,36 @@ export async function assertNumberOfRows(viewId: string, expected: number) {
|
||||
throw Error('Expect number of rows:' + expected + 'but receive:' + databasePB.rows.length);
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertNumberOfRowsInGroup(viewId: string, groupId: string, expected: number) {
|
||||
const svc = new DatabaseBackendService(viewId);
|
||||
await svc.openDatabase();
|
||||
|
||||
const group = await svc.getGroup(groupId).then((result) => result.unwrap());
|
||||
if (group.rows.length !== expected) {
|
||||
throw Error('Expect number of rows in group:' + expected + 'but receive:' + group.rows.length);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSingleSelectOptions(viewId: string, fieldInfo: FieldInfo, optionNames: string[]) {
|
||||
assert(fieldInfo.field.field_type === FieldType.SingleSelect, 'Only work on single select');
|
||||
const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
|
||||
const singleSelectTypeOptionContext = makeSingleSelectTypeOptionContext(typeOptionController);
|
||||
const singleSelectTypeOptionPB: SingleSelectTypeOptionPB = await singleSelectTypeOptionContext
|
||||
.getTypeOption()
|
||||
.then((result) => result.unwrap());
|
||||
|
||||
const backendSvc = new SelectOptionBackendService(viewId, fieldInfo.field.id);
|
||||
for (const optionName of optionNames) {
|
||||
const option = await backendSvc.createOption({ name: optionName }).then((result) => result.unwrap());
|
||||
singleSelectTypeOptionPB.options.splice(0, 0, option);
|
||||
}
|
||||
await singleSelectTypeOptionContext.setTypeOption(singleSelectTypeOptionPB);
|
||||
return singleSelectTypeOptionContext;
|
||||
}
|
||||
|
||||
export function assert(condition: boolean, msg?: string) {
|
||||
if (!condition) {
|
||||
throw Error(msg);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RunAllGridTests,
|
||||
TestCreateGrid,
|
||||
TestCreateNewField,
|
||||
TestCreateRow,
|
||||
@ -12,12 +13,21 @@ import {
|
||||
TestSwitchFromMultiSelectToText,
|
||||
TestSwitchFromSingleSelectToNumber,
|
||||
} from './TestGrid';
|
||||
import {
|
||||
TestCreateKanbanBoard,
|
||||
TestCreateKanbanBoardColumn,
|
||||
TestCreateKanbanBoardRowInNoStatusGroup,
|
||||
TestAllKanbanTests,
|
||||
TestMoveKanbanBoardColumn,
|
||||
TestMoveKanbanBoardRow,
|
||||
} from './TestGroup';
|
||||
|
||||
export const TestAPI = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ul className='m-6, space-y-2'>
|
||||
{/*<TestApiButton></TestApiButton>*/}
|
||||
{/*<tests></tests>*/}
|
||||
<RunAllGridTests></RunAllGridTests>
|
||||
<TestCreateGrid></TestCreateGrid>
|
||||
<TestCreateRow></TestCreateRow>
|
||||
<TestDeleteRow></TestDeleteRow>
|
||||
@ -29,6 +39,13 @@ export const TestAPI = () => {
|
||||
<TestDeleteField></TestDeleteField>
|
||||
<TestSwitchFromSingleSelectToNumber></TestSwitchFromSingleSelectToNumber>
|
||||
<TestSwitchFromMultiSelectToText></TestSwitchFromMultiSelectToText>
|
||||
{/*kanban board */}
|
||||
<TestAllKanbanTests></TestAllKanbanTests>
|
||||
<TestCreateKanbanBoard></TestCreateKanbanBoard>
|
||||
<TestCreateKanbanBoardRowInNoStatusGroup></TestCreateKanbanBoardRowInNoStatusGroup>
|
||||
<TestMoveKanbanBoardRow></TestMoveKanbanBoardRow>
|
||||
<TestMoveKanbanBoardColumn></TestMoveKanbanBoardColumn>
|
||||
<TestCreateKanbanBoardColumn></TestCreateKanbanBoardColumn>
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
);
|
@ -0,0 +1,349 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
FieldType,
|
||||
NumberFormat,
|
||||
NumberTypeOptionPB,
|
||||
SelectOptionCellDataPB,
|
||||
ViewLayoutTypePB,
|
||||
} from '../../../services/backend';
|
||||
import { Log } from '../../utils/log';
|
||||
import {
|
||||
assertFieldName,
|
||||
assertNumberOfFields,
|
||||
assertNumberOfRows,
|
||||
assertTextCell,
|
||||
createSingleSelectOptions,
|
||||
createTestDatabaseView,
|
||||
editTextCell,
|
||||
findFirstFieldInfoWithFieldType,
|
||||
makeMultiSelectCellController,
|
||||
makeSingleSelectCellController,
|
||||
makeTextCellController,
|
||||
openTestDatabase,
|
||||
} from './DatabaseTestHelper';
|
||||
import { SelectOptionCellBackendService } from '../../stores/effects/database/cell/select_option_bd_svc';
|
||||
import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
|
||||
import { None, Some } from 'ts-results';
|
||||
import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
|
||||
import { makeNumberTypeOptionContext } from '../../stores/effects/database/field/type_option/type_option_context';
|
||||
|
||||
export const RunAllGridTests = () => {
|
||||
async function run() {
|
||||
await createBuildInGrid();
|
||||
await testEditGridRow();
|
||||
await testCreateRow();
|
||||
await testDeleteRow();
|
||||
await testCreateOptionInCell();
|
||||
await testGetSingleSelectFieldData();
|
||||
await testSwitchFromSingleSelectToNumber();
|
||||
await testSwitchFromMultiSelectToRichText();
|
||||
await testEditField();
|
||||
await testCreateNewField();
|
||||
await testDeleteField();
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<button className='rounded-md bg-red-400 p-4' type='button' onClick={() => run()}>
|
||||
Run all grid tests
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
async function createBuildInGrid() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
databaseController.subscribe({
|
||||
onViewChanged: (databasePB) => {
|
||||
Log.debug('Did receive database:' + databasePB);
|
||||
},
|
||||
// onRowsChanged: async (rows) => {
|
||||
// if (rows.length !== 3) {
|
||||
// throw Error('Expected number of rows is 3, but receive ' + rows.length);
|
||||
// }
|
||||
// },
|
||||
onFieldsChanged: (fields) => {
|
||||
if (fields.length !== 3) {
|
||||
throw Error('Expected number of fields is 3, but receive ' + fields.length);
|
||||
}
|
||||
},
|
||||
});
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testEditGridRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||
const cellContent = index.toString();
|
||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
|
||||
await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||
await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
|
||||
}
|
||||
}
|
||||
|
||||
async function testCreateRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
await assertNumberOfRows(view.id, 3);
|
||||
|
||||
// Create a row from a DatabaseController or create using the RowBackendService
|
||||
await databaseController.createRow();
|
||||
await assertNumberOfRows(view.id, 4);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testDeleteRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
const rows = databaseController.databaseViewCache.rowInfos;
|
||||
const svc = new RowBackendService(view.id);
|
||||
await svc.deleteRow(rows[0].row.id);
|
||||
await assertNumberOfRows(view.id, 2);
|
||||
|
||||
// Wait the databaseViewCache get the change notification and
|
||||
// update the rows.
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
if (databaseController.databaseViewCache.rowInfos.length !== 2) {
|
||||
throw Error('The number of rows is not match');
|
||||
}
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testCreateOptionInCell() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
|
||||
if (index === 0) {
|
||||
const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
|
||||
const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
await cellController.subscribeChanged({
|
||||
onCellChanged: (value) => {
|
||||
if (value.some) {
|
||||
const option: SelectOptionCellDataPB = value.unwrap();
|
||||
console.log(option);
|
||||
}
|
||||
},
|
||||
});
|
||||
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
|
||||
await backendSvc.createOption({ name: 'option' + index });
|
||||
await cellController.dispose();
|
||||
}
|
||||
}
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testGetSingleSelectFieldData() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Find the single select column
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||
)!;
|
||||
|
||||
// Create options
|
||||
const singleSelectTypeOptionContext = await createSingleSelectOptions(view.id, singleSelect, [
|
||||
'Task 1',
|
||||
'Task 2',
|
||||
'Task 3',
|
||||
]);
|
||||
|
||||
// Read options
|
||||
const options = await singleSelectTypeOptionContext.getTypeOption().then((result) => result.unwrap());
|
||||
console.log(options);
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testSwitchFromSingleSelectToNumber() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Find the single select column
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||
)!;
|
||||
const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
|
||||
await typeOptionController.switchToField(FieldType.Number);
|
||||
|
||||
// Check the number type option
|
||||
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||
const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
|
||||
.getTypeOption()
|
||||
.then((result) => result.unwrap());
|
||||
const format: NumberFormat = numberTypeOption.format;
|
||||
if (format !== NumberFormat.Num) {
|
||||
throw Error('The default format should be number');
|
||||
}
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testSwitchFromMultiSelectToRichText() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Create multi-select field
|
||||
const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
|
||||
await typeOptionController.initialize();
|
||||
|
||||
// Insert options to first row
|
||||
const row = databaseController.databaseViewCache.rowInfos[0];
|
||||
const multiSelectField = typeOptionController.getFieldInfo();
|
||||
// const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
|
||||
const selectOptionCellController = await makeMultiSelectCellController(
|
||||
multiSelectField.field.id,
|
||||
row,
|
||||
databaseController
|
||||
).then((result) => result.unwrap());
|
||||
const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
|
||||
await backendSvc.createOption({ name: 'A' });
|
||||
await backendSvc.createOption({ name: 'B' });
|
||||
await backendSvc.createOption({ name: 'C' });
|
||||
|
||||
const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
|
||||
if (selectOptionCellData.options.length !== 3) {
|
||||
throw Error('The options should equal to 3');
|
||||
}
|
||||
|
||||
if (selectOptionCellData.select_options.length !== 3) {
|
||||
throw Error('The selected options should equal to 3');
|
||||
}
|
||||
await selectOptionCellController.dispose();
|
||||
|
||||
// Switch to RichText field type
|
||||
await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
|
||||
if (typeOptionController.fieldType !== FieldType.RichText) {
|
||||
throw Error('The field type should be text');
|
||||
}
|
||||
|
||||
const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
|
||||
(result) => result.unwrap()
|
||||
);
|
||||
const cellContent = await textCellController.getCellData();
|
||||
if (cellContent.unwrap() !== 'A,B,C') {
|
||||
throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
|
||||
}
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testEditField() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
const fieldInfos = databaseController.fieldController.fieldInfos;
|
||||
|
||||
// Modify the name of the field
|
||||
const firstFieldInfo = fieldInfos[0];
|
||||
const controller = new TypeOptionController(view.id, Some(firstFieldInfo));
|
||||
await controller.initialize();
|
||||
const newName = 'hello world';
|
||||
await controller.setFieldName(newName);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testCreateNewField() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
await assertNumberOfFields(view.id, 3);
|
||||
|
||||
// Modify the name of the field
|
||||
const controller = new TypeOptionController(view.id, None);
|
||||
await controller.initialize();
|
||||
await assertNumberOfFields(view.id, 4);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function testDeleteField() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Modify the name of the field.
|
||||
// The fieldInfos[0] is the primary field by default, we can't delete it.
|
||||
// So let choose the second fieldInfo.
|
||||
const fieldInfo = databaseController.fieldController.fieldInfos[1];
|
||||
const controller = new TypeOptionController(view.id, Some(fieldInfo));
|
||||
await controller.initialize();
|
||||
await assertNumberOfFields(view.id, 3);
|
||||
await controller.deleteField();
|
||||
await assertNumberOfFields(view.id, 2);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
export const TestCreateGrid = () => {
|
||||
return TestButton('Test create build-in grid', createBuildInGrid);
|
||||
};
|
||||
|
||||
export const TestEditCell = () => {
|
||||
return TestButton('Test editing cell', testEditGridRow);
|
||||
};
|
||||
|
||||
export const TestCreateRow = () => {
|
||||
return TestButton('Test create row', testCreateRow);
|
||||
};
|
||||
export const TestDeleteRow = () => {
|
||||
return TestButton('Test delete row', testDeleteRow);
|
||||
};
|
||||
export const TestCreateSelectOptionInCell = () => {
|
||||
return TestButton('Test create a select option in cell', testCreateOptionInCell);
|
||||
};
|
||||
|
||||
export const TestGetSingleSelectFieldData = () => {
|
||||
return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
|
||||
};
|
||||
|
||||
export const TestSwitchFromSingleSelectToNumber = () => {
|
||||
return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
|
||||
};
|
||||
|
||||
export const TestSwitchFromMultiSelectToText = () => {
|
||||
return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
|
||||
};
|
||||
|
||||
export const TestEditField = () => {
|
||||
return TestButton('Test edit the column name', testEditField);
|
||||
};
|
||||
|
||||
export const TestCreateNewField = () => {
|
||||
return TestButton('Test create a new column', testCreateNewField);
|
||||
};
|
||||
|
||||
export const TestDeleteField = () => {
|
||||
return TestButton('Test delete a new column', testDeleteField);
|
||||
};
|
||||
|
||||
export const TestButton = (title: string, onClick: () => void) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<button className='rounded-md bg-blue-400 p-4' type='button' onClick={() => onClick()}>
|
||||
{title}
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
@ -0,0 +1,150 @@
|
||||
import {
|
||||
assert,
|
||||
assertNumberOfRowsInGroup,
|
||||
createSingleSelectOptions,
|
||||
createTestDatabaseView,
|
||||
openTestDatabase,
|
||||
} from './DatabaseTestHelper';
|
||||
import { FieldType, ViewLayoutTypePB } from '../../../services/backend';
|
||||
import React from 'react';
|
||||
|
||||
export const TestAllKanbanTests = () => {
|
||||
async function run() {
|
||||
await createBuildInBoard();
|
||||
await createKanbanBoardRow();
|
||||
await moveKanbanBoardRow();
|
||||
await createKanbanBoardColumn();
|
||||
await createColumnInBoard();
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<button className='rounded-md bg-red-400 p-4' type='button' onClick={() => run()}>
|
||||
Run all kanban board tests
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
async function createBuildInBoard() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
databaseController.subscribe({
|
||||
onGroupByField: (groups) => {
|
||||
console.log(groups);
|
||||
if (groups.length !== 4) {
|
||||
throw Error('The build-in board should have 4 groups');
|
||||
}
|
||||
|
||||
assert(groups[0].rows.length === 0, 'The no status group should have 0 rows');
|
||||
assert(groups[1].rows.length === 3, 'The first group should have 3 rows');
|
||||
assert(groups[2].rows.length === 0, 'The second group should have 0 rows');
|
||||
assert(groups[3].rows.length === 0, 'The third group should have 0 rows');
|
||||
},
|
||||
});
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function createKanbanBoardRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Create row in no status group
|
||||
const noStatusGroup = databaseController.groups.getValue()[0];
|
||||
await noStatusGroup.createRow().then((result) => result.unwrap());
|
||||
await assertNumberOfRowsInGroup(view.id, noStatusGroup.groupId, 1);
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function moveKanbanBoardRow() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Create row in no status group
|
||||
const firstGroup = databaseController.groups.getValue()[1];
|
||||
const secondGroup = databaseController.groups.getValue()[2];
|
||||
const row = firstGroup.rowAtIndex(0).unwrap();
|
||||
await databaseController.moveRow(row.id, secondGroup.groupId);
|
||||
|
||||
assert(firstGroup.rows.length === 2);
|
||||
await assertNumberOfRowsInGroup(view.id, firstGroup.groupId, 2);
|
||||
|
||||
assert(secondGroup.rows.length === 1);
|
||||
await assertNumberOfRowsInGroup(view.id, secondGroup.groupId, 1);
|
||||
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function createKanbanBoardColumn() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// Create row in no status group
|
||||
const firstGroup = databaseController.groups.getValue()[1];
|
||||
const secondGroup = databaseController.groups.getValue()[2];
|
||||
await databaseController.moveGroup(firstGroup.groupId, secondGroup.groupId);
|
||||
|
||||
assert(databaseController.groups.getValue()[1].groupId === secondGroup.groupId);
|
||||
assert(databaseController.groups.getValue()[2].groupId === firstGroup.groupId);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
async function createColumnInBoard() {
|
||||
const view = await createTestDatabaseView(ViewLayoutTypePB.Board);
|
||||
const databaseController = await openTestDatabase(view.id);
|
||||
await databaseController.open().then((result) => result.unwrap());
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const singleSelect = databaseController.fieldController.fieldInfos.find(
|
||||
(fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
|
||||
)!;
|
||||
|
||||
// Create a option which will cause creating a new group
|
||||
const name = 'New column';
|
||||
await createSingleSelectOptions(view.id, singleSelect, [name]);
|
||||
|
||||
// Wait the backend posting the notification to update the groups
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
assert(databaseController.groups.value.length === 5, 'expect number of groups is 5');
|
||||
assert(databaseController.groups.value[4].name === name, 'expect the last group name is ' + name);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
export const TestCreateKanbanBoard = () => {
|
||||
return TestButton('Test create build-in board', createBuildInBoard);
|
||||
};
|
||||
|
||||
export const TestCreateKanbanBoardRowInNoStatusGroup = () => {
|
||||
return TestButton('Test create row in build-in kanban board', createKanbanBoardRow);
|
||||
};
|
||||
|
||||
export const TestMoveKanbanBoardRow = () => {
|
||||
return TestButton('Test move row in build-in kanban board', moveKanbanBoardRow);
|
||||
};
|
||||
|
||||
export const TestMoveKanbanBoardColumn = () => {
|
||||
return TestButton('Test move column in build-in kanban board', createKanbanBoardColumn);
|
||||
};
|
||||
|
||||
export const TestCreateKanbanBoardColumn = () => {
|
||||
return TestButton('Test create column in build-in kanban board', createColumnInBoard);
|
||||
};
|
||||
|
||||
export const TestButton = (title: string, onClick: () => void) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<button className='rounded-md bg-yellow-200 p-4' type='button' onClick={() => onClick()}>
|
||||
{title}
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
@ -1,7 +1,16 @@
|
||||
import {
|
||||
CreateBoardCardPayloadPB,
|
||||
DatabaseEventCreateBoardCard,
|
||||
DatabaseEventCreateRow,
|
||||
DatabaseEventGetDatabase,
|
||||
DatabaseEventGetFields,
|
||||
DatabaseEventGetGroup,
|
||||
DatabaseEventGetGroups,
|
||||
DatabaseEventMoveGroup,
|
||||
DatabaseEventMoveGroupRow,
|
||||
DatabaseGroupIdPB,
|
||||
MoveGroupPayloadPB,
|
||||
MoveGroupRowPayloadPB,
|
||||
} from '../../../../services/backend/events/flowy-database';
|
||||
import {
|
||||
GetFieldPayloadPB,
|
||||
@ -37,6 +46,31 @@ export class DatabaseBackendService {
|
||||
return DatabaseEventCreateRow(payload);
|
||||
};
|
||||
|
||||
createGroupRow = async (groupId: string, startRowId?: string) => {
|
||||
const payload = CreateBoardCardPayloadPB.fromObject({ view_id: this.viewId, group_id: groupId });
|
||||
if (startRowId !== undefined) {
|
||||
payload.start_row_id = startRowId;
|
||||
}
|
||||
return DatabaseEventCreateBoardCard(payload);
|
||||
};
|
||||
|
||||
moveRow = (rowId: string, groupId?: string) => {
|
||||
const payload = MoveGroupRowPayloadPB.fromObject({ view_id: this.viewId, from_row_id: rowId });
|
||||
if (groupId !== undefined) {
|
||||
payload.to_group_id = groupId;
|
||||
}
|
||||
return DatabaseEventMoveGroupRow(payload);
|
||||
};
|
||||
|
||||
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
||||
const payload = MoveGroupPayloadPB.fromObject({
|
||||
view_id: this.viewId,
|
||||
from_group_id: fromGroupId,
|
||||
to_group_id: toGroupId,
|
||||
});
|
||||
return DatabaseEventMoveGroup(payload);
|
||||
};
|
||||
|
||||
getFields = async (fieldIds?: FieldIdPB[]) => {
|
||||
const payload = GetFieldPayloadPB.fromObject({ view_id: this.viewId });
|
||||
|
||||
@ -46,4 +80,14 @@ export class DatabaseBackendService {
|
||||
|
||||
return DatabaseEventGetFields(payload).then((result) => result.map((value) => value.items));
|
||||
};
|
||||
|
||||
getGroup = (groupId: string) => {
|
||||
const payload = DatabaseGroupIdPB.fromObject({ view_id: this.viewId, group_id: groupId });
|
||||
return DatabaseEventGetGroup(payload);
|
||||
};
|
||||
|
||||
loadGroups = () => {
|
||||
const payload = DatabaseViewIdPB.fromObject({ value: this.viewId });
|
||||
return DatabaseEventGetGroups(payload);
|
||||
};
|
||||
}
|
||||
|
@ -1,55 +1,150 @@
|
||||
import { DatabaseBackendService } from './database_bd_svc';
|
||||
import { FieldController, FieldInfo } from './field/field_controller';
|
||||
import { DatabaseViewCache } from './view/database_view_cache';
|
||||
import { DatabasePB } from '../../../../services/backend';
|
||||
import { DatabasePB, GroupPB } from '../../../../services/backend';
|
||||
import { RowChangedReason, RowInfo } from './row/row_cache';
|
||||
import { Err, Ok } from 'ts-results';
|
||||
import { Err } from 'ts-results';
|
||||
import { DatabaseGroupController } from './group/group_controller';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DatabaseGroupObserver } from './group/group_observer';
|
||||
import { Log } from '../../../utils/log';
|
||||
|
||||
export type SubscribeCallbacks = {
|
||||
export type DatabaseSubscriberCallbacks = {
|
||||
onViewChanged?: (data: DatabasePB) => void;
|
||||
onRowsChanged?: (rowInfos: readonly RowInfo[], reason: RowChangedReason) => void;
|
||||
onFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void;
|
||||
onGroupByField?: (groups: GroupPB[]) => void;
|
||||
|
||||
onNumOfGroupChanged?: {
|
||||
onUpdateGroup: (value: GroupPB[]) => void;
|
||||
onDeleteGroup: (value: GroupPB[]) => void;
|
||||
onInsertGroup: (value: GroupPB[]) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export class DatabaseController {
|
||||
private backendService: DatabaseBackendService;
|
||||
private readonly backendService: DatabaseBackendService;
|
||||
fieldController: FieldController;
|
||||
databaseViewCache: DatabaseViewCache;
|
||||
private _callback?: SubscribeCallbacks;
|
||||
private _callback?: DatabaseSubscriberCallbacks;
|
||||
public groups: BehaviorSubject<DatabaseGroupController[]>;
|
||||
private groupsObserver: DatabaseGroupObserver;
|
||||
|
||||
constructor(public readonly viewId: string) {
|
||||
this.backendService = new DatabaseBackendService(viewId);
|
||||
this.fieldController = new FieldController(viewId);
|
||||
this.databaseViewCache = new DatabaseViewCache(viewId, this.fieldController);
|
||||
this.groups = new BehaviorSubject<DatabaseGroupController[]>([]);
|
||||
this.groupsObserver = new DatabaseGroupObserver(viewId);
|
||||
}
|
||||
|
||||
subscribe = (callbacks: SubscribeCallbacks) => {
|
||||
subscribe = (callbacks: DatabaseSubscriberCallbacks) => {
|
||||
this._callback = callbacks;
|
||||
this.fieldController.subscribeOnNumOfFieldsChanged(callbacks.onFieldsChanged);
|
||||
this.databaseViewCache.getRowCache().subscribeOnRowsChanged((reason) => {
|
||||
this._callback?.onRowsChanged?.(this.databaseViewCache.rowInfos, reason);
|
||||
this.fieldController.subscribe({ onNumOfFieldsChanged: callbacks.onFieldsChanged });
|
||||
this.databaseViewCache.getRowCache().subscribe({
|
||||
onRowsChanged: (reason) => {
|
||||
this._callback?.onRowsChanged?.(this.databaseViewCache.rowInfos, reason);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
open = async () => {
|
||||
const result = await this.backendService.openDatabase();
|
||||
if (result.ok) {
|
||||
const database: DatabasePB = result.val;
|
||||
this._callback?.onViewChanged?.(database);
|
||||
const openDatabaseResult = await this.backendService.openDatabase();
|
||||
if (openDatabaseResult.ok) {
|
||||
const database: DatabasePB = openDatabaseResult.val;
|
||||
await this.databaseViewCache.initialize();
|
||||
await this.fieldController.initialize();
|
||||
|
||||
// subscriptions
|
||||
await this.subscribeOnGroupsChanged();
|
||||
|
||||
// load database initial data
|
||||
await this.fieldController.loadFields(database.fields);
|
||||
await this.databaseViewCache.listenOnRowsChanged();
|
||||
await this.fieldController.listenOnFieldChanges();
|
||||
const loadGroupResult = await this.loadGroup();
|
||||
|
||||
this.databaseViewCache.initializeWithRows(database.rows);
|
||||
return Ok.EMPTY;
|
||||
|
||||
this._callback?.onViewChanged?.(database);
|
||||
return loadGroupResult;
|
||||
} else {
|
||||
return Err(result.val);
|
||||
return Err(openDatabaseResult.val);
|
||||
}
|
||||
};
|
||||
|
||||
createRow = async () => {
|
||||
createRow = () => {
|
||||
return this.backendService.createRow();
|
||||
};
|
||||
|
||||
moveRow = (rowId: string, groupId: string) => {
|
||||
return this.backendService.moveRow(rowId, groupId);
|
||||
};
|
||||
|
||||
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
||||
return this.backendService.moveGroup(fromGroupId, toGroupId);
|
||||
};
|
||||
|
||||
private loadGroup = async () => {
|
||||
const result = await this.backendService.loadGroups();
|
||||
if (result.ok) {
|
||||
const groups = result.val.items;
|
||||
await this.initialGroups(groups);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
private initialGroups = async (groups: GroupPB[]) => {
|
||||
this.groups.getValue().forEach((controller) => {
|
||||
void controller.dispose();
|
||||
});
|
||||
|
||||
const controllers: DatabaseGroupController[] = [];
|
||||
for (const groupPB of groups) {
|
||||
const controller = new DatabaseGroupController(groupPB, this.backendService);
|
||||
await controller.initialize();
|
||||
controllers.push(controller);
|
||||
}
|
||||
this.groups.next(controllers);
|
||||
this.groups.value;
|
||||
};
|
||||
|
||||
private subscribeOnGroupsChanged = async () => {
|
||||
await this.groupsObserver.subscribe({
|
||||
onGroupBy: async (result) => {
|
||||
if (result.ok) {
|
||||
await this.initialGroups(result.val);
|
||||
}
|
||||
},
|
||||
onGroupChangeset: (result) => {
|
||||
if (result.err) {
|
||||
Log.error(result.val);
|
||||
return;
|
||||
}
|
||||
const changeset = result.val;
|
||||
let existControllers = [...this.groups.getValue()];
|
||||
for (const deleteId of changeset.deleted_groups) {
|
||||
existControllers = existControllers.filter((c) => c.groupId !== deleteId);
|
||||
}
|
||||
|
||||
for (const update of changeset.update_groups) {
|
||||
const index = existControllers.findIndex((c) => c.groupId === update.group_id);
|
||||
if (index !== -1) {
|
||||
existControllers[index].updateGroup(update);
|
||||
}
|
||||
}
|
||||
|
||||
for (const insert of changeset.inserted_groups) {
|
||||
const controller = new DatabaseGroupController(insert.group, this.backendService);
|
||||
if (insert.index > existControllers.length) {
|
||||
existControllers.push(controller);
|
||||
} else {
|
||||
existControllers.splice(insert.index, 0, controller);
|
||||
}
|
||||
}
|
||||
this.groups.next(existControllers);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
dispose = async () => {
|
||||
await this.backendService.closeDatabase();
|
||||
await this.fieldController.dispose();
|
||||
|
@ -6,17 +6,17 @@ import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
|
||||
export class FieldController {
|
||||
private backendService: DatabaseBackendService;
|
||||
private numOfFieldsObserver: DatabaseFieldChangesetObserver;
|
||||
private fieldChangesetObserver: DatabaseFieldChangesetObserver;
|
||||
private numOfFieldsNotifier = new NumOfFieldsNotifier([]);
|
||||
|
||||
constructor(public readonly viewId: string) {
|
||||
this.backendService = new DatabaseBackendService(viewId);
|
||||
this.numOfFieldsObserver = new DatabaseFieldChangesetObserver(viewId);
|
||||
this.fieldChangesetObserver = new DatabaseFieldChangesetObserver(viewId);
|
||||
}
|
||||
|
||||
dispose = async () => {
|
||||
this.numOfFieldsNotifier.unsubscribe();
|
||||
await this.numOfFieldsObserver.unsubscribe();
|
||||
await this.fieldChangesetObserver.unsubscribe();
|
||||
};
|
||||
|
||||
get fieldInfos(): readonly FieldInfo[] {
|
||||
@ -36,14 +36,14 @@ export class FieldController {
|
||||
}
|
||||
};
|
||||
|
||||
subscribeOnNumOfFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
|
||||
return this.numOfFieldsNotifier.observer.subscribe((fieldInfos) => {
|
||||
callback?.(fieldInfos);
|
||||
subscribe = (callbacks: { onNumOfFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void}) => {
|
||||
this.numOfFieldsNotifier.observer.subscribe((fieldInfos) => {
|
||||
callbacks.onNumOfFieldsChanged?.(fieldInfos);
|
||||
});
|
||||
};
|
||||
|
||||
listenOnFieldChanges = async () => {
|
||||
await this.numOfFieldsObserver.subscribe({
|
||||
initialize = async () => {
|
||||
await this.fieldChangesetObserver.subscribe({
|
||||
onFieldsChanged: (result) => {
|
||||
if (result.ok) {
|
||||
const changeset = result.val;
|
||||
|
@ -3,16 +3,15 @@ import { DatabaseNotification, DatabaseFieldChangesetPB, FlowyError, FieldPB } f
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||
|
||||
type UpdateFieldNotifiedValue = Result<DatabaseFieldChangesetPB, FlowyError>;
|
||||
export type DatabaseNotificationCallback = (value: UpdateFieldNotifiedValue) => void;
|
||||
export type FieldChangesetSubscribeCallback = (value: Result<DatabaseFieldChangesetPB, FlowyError>) => void;
|
||||
|
||||
export class DatabaseFieldChangesetObserver {
|
||||
private notifier?: ChangeNotifier<UpdateFieldNotifiedValue>;
|
||||
private notifier?: ChangeNotifier<Result<DatabaseFieldChangesetPB, FlowyError>>;
|
||||
private listener?: DatabaseNotificationObserver;
|
||||
|
||||
constructor(public readonly viewId: string) {}
|
||||
|
||||
subscribe = async (callbacks: { onFieldsChanged: DatabaseNotificationCallback }) => {
|
||||
subscribe = async (callbacks: { onFieldsChanged: FieldChangesetSubscribeCallback }) => {
|
||||
this.notifier = new ChangeNotifier();
|
||||
this.notifier?.observer.subscribe(callbacks.onFieldsChanged);
|
||||
|
||||
@ -41,16 +40,15 @@ export class DatabaseFieldChangesetObserver {
|
||||
};
|
||||
}
|
||||
|
||||
type FieldNotifiedValue = Result<FieldPB, FlowyError>;
|
||||
export type FieldNotificationCallback = (value: FieldNotifiedValue) => void;
|
||||
export type FieldSubscribeCallback = (value: Result<FieldPB, FlowyError>) => void;
|
||||
|
||||
export class DatabaseFieldObserver {
|
||||
private _notifier?: ChangeNotifier<FieldNotifiedValue>;
|
||||
private _notifier?: ChangeNotifier<Result<FieldPB, FlowyError>>;
|
||||
private _listener?: DatabaseNotificationObserver;
|
||||
|
||||
constructor(public readonly fieldId: string) {}
|
||||
|
||||
subscribe = async (callbacks: { onFieldChanged: FieldNotificationCallback }) => {
|
||||
subscribe = async (callbacks: { onFieldChanged: FieldSubscribeCallback }) => {
|
||||
this._notifier = new ChangeNotifier();
|
||||
this._notifier?.observer.subscribe(callbacks.onFieldChanged);
|
||||
|
||||
|
@ -0,0 +1,149 @@
|
||||
import {
|
||||
DatabaseNotification,
|
||||
FlowyError,
|
||||
GroupPB,
|
||||
GroupRowsNotificationPB,
|
||||
RowPB,
|
||||
} from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { None, Ok, Option, Result, Some } from 'ts-results';
|
||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||
import { Log } from '../../../../utils/log';
|
||||
import { DatabaseBackendService } from '../database_bd_svc';
|
||||
|
||||
export type GroupDataCallbacks = {
|
||||
onRemoveRow: (groupId: string, rowId: string) => void;
|
||||
onInsertRow: (groupId: string, row: RowPB, index?: number) => void;
|
||||
onUpdateRow: (groupId: string, row: RowPB) => void;
|
||||
|
||||
onCreateRow: (groupId: string, row: RowPB) => void;
|
||||
};
|
||||
|
||||
export class DatabaseGroupController {
|
||||
private dataObserver: GroupDataObserver;
|
||||
private callbacks?: GroupDataCallbacks;
|
||||
|
||||
constructor(private group: GroupPB, private databaseBackendSvc: DatabaseBackendService) {
|
||||
this.dataObserver = new GroupDataObserver(group.group_id);
|
||||
}
|
||||
|
||||
get groupId() {
|
||||
return this.group.group_id;
|
||||
}
|
||||
|
||||
get rows() {
|
||||
return this.group.rows;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.group.desc;
|
||||
}
|
||||
|
||||
updateGroup = (group: GroupPB) => {
|
||||
this.group = group;
|
||||
};
|
||||
|
||||
rowAtIndex = (index: number): Option<RowPB> => {
|
||||
if (this.group.rows.length < index) {
|
||||
return None;
|
||||
}
|
||||
return Some(this.group.rows[index]);
|
||||
};
|
||||
|
||||
initialize = async () => {
|
||||
await this.dataObserver.subscribe({
|
||||
onRowsChanged: (result) => {
|
||||
if (result.ok) {
|
||||
const changeset = result.val;
|
||||
// Delete
|
||||
changeset.deleted_rows.forEach((deletedRowId) => {
|
||||
this.group.rows = this.group.rows.filter((row) => row.id !== deletedRowId);
|
||||
this.callbacks?.onRemoveRow(this.group.group_id, deletedRowId);
|
||||
});
|
||||
|
||||
// Insert
|
||||
changeset.inserted_rows.forEach((insertedRow) => {
|
||||
let index: number | undefined = insertedRow.index;
|
||||
if (insertedRow.has_index && this.group.rows.length > insertedRow.index) {
|
||||
this.group.rows.splice(index, 0, insertedRow.row);
|
||||
} else {
|
||||
index = undefined;
|
||||
this.group.rows.push(insertedRow.row);
|
||||
}
|
||||
|
||||
if (insertedRow.is_new) {
|
||||
this.callbacks?.onCreateRow(this.group.group_id, insertedRow.row);
|
||||
} else {
|
||||
this.callbacks?.onInsertRow(this.group.group_id, insertedRow.row, index);
|
||||
}
|
||||
});
|
||||
|
||||
// Update
|
||||
changeset.updated_rows.forEach((updatedRow) => {
|
||||
const index = this.group.rows.findIndex((row) => row.id === updatedRow.id);
|
||||
if (index !== -1) {
|
||||
this.group.rows[index] = updatedRow;
|
||||
this.callbacks?.onUpdateRow(this.group.group_id, updatedRow);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.error(result.val);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
createRow = async () => {
|
||||
return this.databaseBackendSvc.createGroupRow(this.group.group_id);
|
||||
};
|
||||
|
||||
subscribe = (callbacks: GroupDataCallbacks) => {
|
||||
this.callbacks = callbacks;
|
||||
};
|
||||
|
||||
unsubscribe = () => {
|
||||
this.callbacks = undefined;
|
||||
};
|
||||
|
||||
dispose = async () => {
|
||||
await this.dataObserver.unsubscribe();
|
||||
this.callbacks = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
type GroupRowsSubscribeCallback = (value: Result<GroupRowsNotificationPB, FlowyError>) => void;
|
||||
|
||||
class GroupDataObserver {
|
||||
private notifier?: ChangeNotifier<Result<GroupRowsNotificationPB, FlowyError>>;
|
||||
private listener?: DatabaseNotificationObserver;
|
||||
|
||||
constructor(public readonly groupId: string) {}
|
||||
|
||||
subscribe = async (callbacks: { onRowsChanged: GroupRowsSubscribeCallback }) => {
|
||||
this.notifier = new ChangeNotifier();
|
||||
this.notifier?.observer.subscribe(callbacks.onRowsChanged);
|
||||
|
||||
this.listener = new DatabaseNotificationObserver({
|
||||
id: this.groupId,
|
||||
parserHandler: (notification, result) => {
|
||||
switch (notification) {
|
||||
case DatabaseNotification.DidUpdateGroupRow:
|
||||
if (result.ok) {
|
||||
this.notifier?.notify(Ok(GroupRowsNotificationPB.deserializeBinary(result.val)));
|
||||
} else {
|
||||
this.notifier?.notify(result);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
await this.listener.start();
|
||||
};
|
||||
|
||||
unsubscribe = async () => {
|
||||
await this.listener?.stop();
|
||||
this.notifier?.unsubscribe();
|
||||
};
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { Ok, Result } from 'ts-results';
|
||||
import { DatabaseNotification, FlowyError, GroupChangesetPB, GroupPB } from '../../../../../services/backend';
|
||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||
|
||||
export type GroupByFieldCallback = (value: Result<GroupPB[], FlowyError>) => void;
|
||||
export type GroupChangesetSubscribeCallback = (value: Result<GroupChangesetPB, FlowyError>) => void;
|
||||
|
||||
export class DatabaseGroupObserver {
|
||||
private groupByNotifier?: ChangeNotifier<Result<GroupPB[], FlowyError>>;
|
||||
private groupChangesetNotifier?: ChangeNotifier<Result<GroupChangesetPB, FlowyError>>;
|
||||
private listener?: DatabaseNotificationObserver;
|
||||
|
||||
constructor(public readonly viewId: string) {}
|
||||
|
||||
subscribe = async (callbacks: {
|
||||
onGroupBy: GroupByFieldCallback;
|
||||
onGroupChangeset: GroupChangesetSubscribeCallback;
|
||||
}) => {
|
||||
this.groupByNotifier = new ChangeNotifier();
|
||||
this.groupByNotifier?.observer.subscribe(callbacks.onGroupBy);
|
||||
|
||||
this.groupChangesetNotifier = new ChangeNotifier();
|
||||
this.groupChangesetNotifier?.observer.subscribe(callbacks.onGroupChangeset);
|
||||
|
||||
this.listener = new DatabaseNotificationObserver({
|
||||
id: this.viewId,
|
||||
parserHandler: (notification, result) => {
|
||||
switch (notification) {
|
||||
case DatabaseNotification.DidGroupByField:
|
||||
if (result.ok) {
|
||||
this.groupByNotifier?.notify(Ok(GroupChangesetPB.deserializeBinary(result.val).initial_groups));
|
||||
} else {
|
||||
this.groupByNotifier?.notify(result);
|
||||
}
|
||||
break;
|
||||
case DatabaseNotification.DidUpdateGroups:
|
||||
if (result.ok) {
|
||||
this.groupChangesetNotifier?.notify(Ok(GroupChangesetPB.deserializeBinary(result.val)));
|
||||
} else {
|
||||
this.groupChangesetNotifier?.notify(result);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await this.listener.start();
|
||||
};
|
||||
|
||||
unsubscribe = async () => {
|
||||
this.groupByNotifier?.unsubscribe();
|
||||
this.groupChangesetNotifier?.unsubscribe();
|
||||
await this.listener?.stop();
|
||||
};
|
||||
}
|
@ -53,12 +53,14 @@ export class RowCache {
|
||||
}
|
||||
};
|
||||
|
||||
subscribeOnRowsChanged = (callback: (reason: RowChangedReason, cellMap?: Map<string, CellIdentifier>) => void) => {
|
||||
subscribe = (callbacks: {
|
||||
onRowsChanged: (reason: RowChangedReason, cellMap?: Map<string, CellIdentifier>) => void;
|
||||
}) => {
|
||||
return this.notifier.observer.subscribe((change) => {
|
||||
if (change.rowId !== undefined) {
|
||||
callback(change.reason, this._toCellMap(change.rowId, this.getFieldInfos()));
|
||||
callbacks.onRowsChanged(change.reason, this._toCellMap(change.rowId, this.getFieldInfos()));
|
||||
} else {
|
||||
callback(change.reason);
|
||||
callbacks.onRowsChanged(change.reason);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -7,16 +7,17 @@ import { Subscription } from 'rxjs';
|
||||
export class DatabaseViewCache {
|
||||
private readonly rowsObserver: DatabaseViewRowsObserver;
|
||||
private readonly rowCache: RowCache;
|
||||
private readonly fieldSubscription?: Subscription;
|
||||
|
||||
constructor(public readonly viewId: string, fieldController: FieldController) {
|
||||
this.rowsObserver = new DatabaseViewRowsObserver(viewId);
|
||||
this.rowCache = new RowCache(viewId, () => fieldController.fieldInfos);
|
||||
this.fieldSubscription = fieldController.subscribeOnNumOfFieldsChanged((fieldInfos) => {
|
||||
fieldInfos.forEach((fieldInfo) => {
|
||||
this.rowCache.onFieldUpdated(fieldInfo);
|
||||
});
|
||||
this.rowCache.onNumberOfFieldsUpdated(fieldInfos);
|
||||
fieldController.subscribe({
|
||||
onNumOfFieldsChanged: (fieldInfos) => {
|
||||
fieldInfos.forEach((fieldInfo) => {
|
||||
this.rowCache.onFieldUpdated(fieldInfo);
|
||||
});
|
||||
this.rowCache.onNumberOfFieldsUpdated(fieldInfos);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -33,12 +34,11 @@ export class DatabaseViewCache {
|
||||
};
|
||||
|
||||
dispose = async () => {
|
||||
this.fieldSubscription?.unsubscribe();
|
||||
await this.rowsObserver.unsubscribe();
|
||||
await this.rowCache.dispose();
|
||||
};
|
||||
|
||||
listenOnRowsChanged = async () => {
|
||||
initialize = async () => {
|
||||
await this.rowsObserver.subscribe({
|
||||
onRowsVisibilityChanged: (result) => {
|
||||
if (result.ok) {
|
||||
|
@ -1,4 +1,158 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{FieldIdPB, RowPB};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
/// [DatabasePB] describes how many fields and blocks the grid has
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct DatabasePB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub fields: Vec<FieldIdPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub rows: Vec<RowPB>,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct CreateDatabasePayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, ProtoBuf, Default, Debug)]
|
||||
pub struct DatabaseViewIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl AsRef<str> for DatabaseViewIdPB {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub from_index: i32,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub to_index: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MoveFieldParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub from_index: i32,
|
||||
pub to_index: i32,
|
||||
}
|
||||
|
||||
impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveFieldParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?;
|
||||
Ok(MoveFieldParams {
|
||||
view_id: view_id.0,
|
||||
field_id: item_id.0,
|
||||
from_index: self.from_index,
|
||||
to_index: self.to_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveRowPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub from_row_id: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub to_row_id: String,
|
||||
}
|
||||
|
||||
pub struct MoveRowParams {
|
||||
pub view_id: String,
|
||||
pub from_row_id: String,
|
||||
pub to_row_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<MoveRowParams> for MoveRowPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveRowParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
|
||||
Ok(MoveRowParams {
|
||||
view_id: view_id.0,
|
||||
from_row_id: from_row_id.0,
|
||||
to_row_id: to_row_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveGroupRowPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub from_row_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub to_group_id: String,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub to_row_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct MoveGroupRowParams {
|
||||
pub view_id: String,
|
||||
pub from_row_id: String,
|
||||
pub to_group_id: String,
|
||||
pub to_row_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveGroupRowParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
let to_group_id =
|
||||
NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
|
||||
|
||||
let to_row_id = match self.to_row_id {
|
||||
None => None,
|
||||
Some(to_row_id) => Some(
|
||||
NotEmptyStr::parse(to_row_id)
|
||||
.map_err(|_| ErrorCode::RowIdIsEmpty)?
|
||||
.0,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(MoveGroupRowParams {
|
||||
view_id: view_id.0,
|
||||
from_row_id: from_row_id.0,
|
||||
to_group_id: to_group_id.0,
|
||||
to_row_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct DatabaseDescPB {
|
||||
@ -14,3 +168,30 @@ pub struct RepeatedDatabaseDescPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<DatabaseDescPB>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct DatabaseGroupIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
pub struct DatabaseGroupIdParams {
|
||||
pub view_id: String,
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<DatabaseGroupIdParams> for DatabaseGroupIdPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<DatabaseGroupIdParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
|
||||
Ok(DatabaseGroupIdParams {
|
||||
view_id: view_id.0,
|
||||
group_id: group_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,155 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{FieldIdPB, RowPB};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
/// [DatabasePB] describes how many fields and blocks the grid has
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct DatabasePB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub fields: Vec<FieldIdPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub rows: Vec<RowPB>,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct CreateDatabasePayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, ProtoBuf, Default, Debug)]
|
||||
pub struct DatabaseViewIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl AsRef<str> for DatabaseViewIdPB {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub from_index: i32,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub to_index: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MoveFieldParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub from_index: i32,
|
||||
pub to_index: i32,
|
||||
}
|
||||
|
||||
impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveFieldParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?;
|
||||
Ok(MoveFieldParams {
|
||||
view_id: view_id.0,
|
||||
field_id: item_id.0,
|
||||
from_index: self.from_index,
|
||||
to_index: self.to_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveRowPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub from_row_id: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub to_row_id: String,
|
||||
}
|
||||
|
||||
pub struct MoveRowParams {
|
||||
pub view_id: String,
|
||||
pub from_row_id: String,
|
||||
pub to_row_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<MoveRowParams> for MoveRowPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveRowParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
|
||||
Ok(MoveRowParams {
|
||||
view_id: view_id.0,
|
||||
from_row_id: from_row_id.0,
|
||||
to_row_id: to_row_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveGroupRowPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub from_row_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub to_group_id: String,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub to_row_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct MoveGroupRowParams {
|
||||
pub view_id: String,
|
||||
pub from_row_id: String,
|
||||
pub to_group_id: String,
|
||||
pub to_row_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveGroupRowParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
let to_group_id =
|
||||
NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
|
||||
|
||||
let to_row_id = match self.to_row_id {
|
||||
None => None,
|
||||
Some(to_row_id) => Some(
|
||||
NotEmptyStr::parse(to_row_id)
|
||||
.map_err(|_| ErrorCode::RowIdIsEmpty)?
|
||||
.0,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(MoveGroupRowParams {
|
||||
view_id: view_id.0,
|
||||
from_row_id: from_row_id.0,
|
||||
to_group_id: to_group_id.0,
|
||||
to_row_id,
|
||||
})
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ mod cell_entities;
|
||||
mod database_entities;
|
||||
mod field_entities;
|
||||
pub mod filter_entities;
|
||||
mod grid_entities;
|
||||
mod group_entities;
|
||||
pub mod parser;
|
||||
mod row_entities;
|
||||
@ -14,9 +13,9 @@ mod view_entities;
|
||||
pub use calendar_entities::*;
|
||||
pub use cell_entities::*;
|
||||
pub use database_entities::*;
|
||||
pub use database_entities::*;
|
||||
pub use field_entities::*;
|
||||
pub use filter_entities::*;
|
||||
pub use grid_entities::*;
|
||||
pub use group_entities::*;
|
||||
pub use row_entities::*;
|
||||
pub use setting_entities::*;
|
||||
|
@ -538,6 +538,17 @@ pub(crate) async fn get_groups_handler(
|
||||
data_result_ok(groups)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn get_group_handler(
|
||||
data: AFPluginData<DatabaseGroupIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<GroupPB, FlowyError> {
|
||||
let params: DatabaseGroupIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let group = editor.get_group(¶ms.view_id, ¶ms.group_id).await?;
|
||||
data_result_ok(group)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn create_board_card_handler(
|
||||
data: AFPluginData<CreateBoardCardPayloadPB>,
|
||||
|
@ -47,7 +47,8 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::CreateBoardCard, create_board_card_handler)
|
||||
.event(DatabaseEvent::MoveGroup, move_group_handler)
|
||||
.event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
|
||||
.event(DatabaseEvent::GetGroup, get_groups_handler)
|
||||
.event(DatabaseEvent::GetGroups, get_groups_handler)
|
||||
.event(DatabaseEvent::GetGroup, get_group_handler)
|
||||
// Database
|
||||
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
||||
// Calendar
|
||||
@ -221,7 +222,10 @@ pub enum DatabaseEvent {
|
||||
UpdateDateCell = 80,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB", output = "RepeatedGroupPB")]
|
||||
GetGroup = 100,
|
||||
GetGroups = 100,
|
||||
|
||||
#[event(input = "DatabaseGroupIdPB", output = "GroupPB")]
|
||||
GetGroup = 101,
|
||||
|
||||
#[event(input = "CreateBoardCardPayloadPB", output = "RowPB")]
|
||||
CreateBoardCard = 110,
|
||||
|
@ -207,14 +207,14 @@ impl DatabaseManager {
|
||||
let create_view_editor = |database_editor: Arc<DatabaseEditor>| async move {
|
||||
let user_id = user.user_id()?;
|
||||
let (view_pad, view_rev_manager) = make_database_view_revision_pad(view_id, user).await?;
|
||||
return DatabaseViewEditor::from_pad(
|
||||
DatabaseViewEditor::from_pad(
|
||||
&user_id,
|
||||
database_editor.database_view_data.clone(),
|
||||
database_editor.cell_data_cache.clone(),
|
||||
view_rev_manager,
|
||||
view_pad,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
};
|
||||
|
||||
let database_editor = self
|
||||
@ -224,7 +224,7 @@ impl DatabaseManager {
|
||||
.get(database_id)
|
||||
.cloned();
|
||||
|
||||
return match database_editor {
|
||||
match database_editor {
|
||||
None => {
|
||||
let mut editors_by_database_id = self.editors_by_database_id.write().await;
|
||||
let db_pool = self.database_user.db_pool()?;
|
||||
@ -241,7 +241,7 @@ impl DatabaseManager {
|
||||
|
||||
Ok(database_editor)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self, pool), err)]
|
||||
|
@ -924,6 +924,11 @@ impl DatabaseEditor {
|
||||
self.database_views.load_groups(view_id).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub async fn get_group(&self, view_id: &str, group_id: &str) -> FlowyResult<GroupPB> {
|
||||
self.database_views.get_group(view_id, group_id).await
|
||||
}
|
||||
|
||||
async fn create_row_rev(&self) -> FlowyResult<RowRevision> {
|
||||
let field_revs = self.database_pad.read().await.get_field_revs(None)?;
|
||||
let block_id = self.block_id().await?;
|
||||
|
@ -24,7 +24,7 @@ use database_model::{
|
||||
use flowy_client_sync::client_database::{
|
||||
make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad,
|
||||
};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_revision::RevisionManager;
|
||||
use flowy_sqlite::ConnectionPool;
|
||||
use flowy_task::TaskDispatcher;
|
||||
@ -379,7 +379,7 @@ impl DatabaseViewEditor {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Only call once after grid view editor initialized
|
||||
/// Only call once after database view editor initialized
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub async fn v_load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
|
||||
let groups = self
|
||||
@ -394,6 +394,14 @@ impl DatabaseViewEditor {
|
||||
Ok(groups.into_iter().map(GroupPB::from).collect())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub async fn v_get_group(&self, group_id: &str) -> FlowyResult<GroupPB> {
|
||||
match self.group_controller.read().await.get_group(group_id) {
|
||||
None => Err(FlowyError::record_not_found().context("Can't find the group")),
|
||||
Some((_, group)) => Ok(GroupPB::from(group)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn v_move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||
self
|
||||
|
@ -1,7 +1,8 @@
|
||||
#![allow(clippy::while_let_loop)]
|
||||
use crate::entities::{
|
||||
AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams,
|
||||
DeleteGroupParams, DeleteSortParams, InsertGroupParams, MoveGroupParams, RepeatedGroupPB, RowPB,
|
||||
DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, MoveGroupParams,
|
||||
RepeatedGroupPB, RowPB,
|
||||
};
|
||||
use crate::manager::DatabaseUser;
|
||||
use crate::services::cell::AtomicCellDataCache;
|
||||
@ -201,6 +202,11 @@ impl DatabaseViews {
|
||||
Ok(RepeatedGroupPB { items: groups })
|
||||
}
|
||||
|
||||
pub async fn get_group(&self, view_id: &str, group_id: &str) -> FlowyResult<GroupPB> {
|
||||
let view_editor = self.get_view_editor(view_id).await?;
|
||||
view_editor.v_get_group(group_id).await
|
||||
}
|
||||
|
||||
pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||
let view_editor = self.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.v_initialize_new_group(params).await
|
||||
|
Loading…
Reference in New Issue
Block a user