fix: import data (#6483)

* chore: import into space

* chore: par insert

* fix: migrate from anon

* chore: fix test

* chore: update test

* chore: add test

* chore: update test

* chore: update test

* chore: update docs

* fix: space collab

* chore: update test
This commit is contained in:
Nathan.fooo 2024-10-06 20:08:17 +08:00 committed by GitHub
parent 1a82f3fff1
commit fd9b01ca27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1171 additions and 789 deletions

View File

@ -39,7 +39,7 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
@ -73,7 +73,7 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [windows-2019]
os: [ windows-2019 ]
include:
- os: windows-2019
flutter_profile: development-windows-x86
@ -100,7 +100,7 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [macos-latest]
os: [ macos-latest ]
include:
- os: macos-latest
flutter_profile: development-mac-x86_64
@ -122,12 +122,12 @@ jobs:
flutter_profile: ${{ matrix.flutter_profile }}
unit_test:
needs: [prepare-linux]
needs: [ prepare-linux ]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
@ -216,11 +216,11 @@ jobs:
shell: bash
cloud_integration_test:
needs: [prepare-linux]
needs: [ prepare-linux ]
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
@ -241,12 +241,15 @@ jobs:
cp deploy.env .env
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
- name: Run Docker-Compose
working-directory: AppFlowy-Cloud
env:
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
APPFLOWY_CLOUD_VERSION: 0.6.29-amd64
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
run: |
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
if [ -z "$container_id" ]; then
@ -322,12 +325,12 @@ jobs:
# split the integration tests into different machines to minimize the time
integration_test_1:
needs: [prepare-linux]
needs: [ prepare-linux ]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
@ -352,12 +355,12 @@ jobs:
rust_target: ${{ matrix.target }}
integration_test_2:
needs: [prepare-linux]
needs: [ prepare-linux ]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
@ -382,12 +385,12 @@ jobs:
rust_target: ${{ matrix.target }}
integration_test_3:
needs: [prepare-linux]
needs: [ prepare-linux ]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"

View File

@ -47,6 +47,8 @@ jobs:
working-directory: AppFlowy-Cloud
env:
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
run: |
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
if [ -z "$container_id" ]; then
@ -127,12 +129,15 @@ jobs:
run: |
cp deploy.env .env
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
- name: Ensure AppFlowy-Cloud is Running with Correct Version
working-directory: AppFlowy-Cloud
env:
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
run: |
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
if [ -z "$container_id" ]; then

View File

@ -3,8 +3,10 @@ import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
import 'uncategorized/uncategorized_test_runner.dart'
as uncategorized_test_runner;
import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
import 'data_migration/data_migration_test_runner.dart' as data_migration_test_runner;
Future<void> main() async {
data_migration_test_runner.main();
// uncategorized
uncategorized_test_runner.main();
@ -16,4 +18,5 @@ Future<void> main() async {
// sidebar
sidebar_move_page_test.main();
}

View File

@ -33,7 +33,7 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('appflowy cloud', () {
testWidgets('anon user and then sign in', (tester) async {
testWidgets('anon user -> sign in -> open imported space', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
@ -42,6 +42,10 @@ void main() {
await tester.tapAnonymousSignInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Test Document';
await tester.createNewPageWithNameUnderParent(name: pageName);
tester.expectToSeePageName(pageName);
// rename the name of the anon user
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.account);
@ -60,20 +64,16 @@ void main() {
// sign up with Google
await tester.tapGoogleLoginInButton();
// await tester.pumpAndSettle(const Duration(seconds: 16));
// sign out
// open the imported space
await tester.expectToSeeHomePage();
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.account);
await tester.clickSpaceHeader();
// Scroll to sign-out
await tester.scrollUntilVisible(
find.byType(AccountSignInOutButton),
100,
scrollable: find.findSettingsScrollable(),
);
// After import the anon user data, we will create a new space for it
await tester.openSpace("Getting started");
await tester.openPage(pageName);
await tester.logout();
await tester.pumpAndSettle();
});
});

View File

@ -0,0 +1,5 @@
import 'anon_user_data_migration_test.dart' as anon_user_test;
void main() async {
anon_user_test.main();
}

View File

@ -1,4 +1,3 @@
import 'anon_user_continue_test.dart' as anon_user_continue_test;
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
import 'empty_test.dart' as preset_af_cloud_env_test;
import 'user_setting_sync_test.dart' as user_sync_test;
@ -7,5 +6,4 @@ void main() async {
preset_af_cloud_env_test.main();
appflowy_cloud_auth_test.main();
user_sync_test.main();
anon_user_continue_test.main();
}

View File

@ -10,9 +10,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'util.dart';
extension AppFlowyAuthTest on WidgetTester {
Future<void> tapGoogleLoginInButton() async {
Future<void> tapGoogleLoginInButton({bool pumpAndSettle = true}) async {
await tapButton(
find.byKey(signInWithGoogleButtonKey),
pumpAndSettle: pumpAndSettle,
);
}

View File

@ -174,7 +174,7 @@ extension AppFlowyTestBase on WidgetTester {
await this.pumpAndSettle(
Duration(milliseconds: milliseconds),
EnginePhase.sendSemanticsUpdate,
const Duration(seconds: 5),
const Duration(seconds: 15),
);
}
}

View File

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@ -411,6 +412,19 @@ extension CommonOperations on WidgetTester {
await tapButton(addPageButton);
}
/// Click the + button in the space header
Future<void> clickSpaceHeader() async {
await tapButton(find.byType(SidebarSpaceHeader));
}
Future<void> openSpace(String spaceName) async {
final space = find.descendant(
of: find.byType(SidebarSpaceMenuItem),
matching: find.text(spaceName),
);
await tapButton(space);
}
/// Create a new page on the top level
Future<void> createNewPage({
ViewLayoutPB layout = ViewLayoutPB.Document,

View File

@ -1,4 +1,7 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/plugins/database/application/defines.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -19,9 +22,17 @@ class SettingFileImportBloc
importAppFlowyDataFolder: (String path) async {
final formattedDate =
DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
final spaceId =
await getIt<KeyValueStorage>().get(KVKeys.lastOpenedSpaceId);
final payload = ImportAppFlowyDataPB.create()
..path = path
..importContainerName = "appflowy_import_$formattedDate";
..importContainerName = "import_$formattedDate";
if (spaceId != null) {
payload.parentViewId = spaceId;
}
emit(
state.copyWith(loadingState: const LoadingState.loading()),
);

View File

@ -31,7 +31,7 @@ class SidebarSpaceMenu extends StatelessWidget {
for (final space in state.spaces)
SizedBox(
height: HomeSpaceViewSizes.viewHeight,
child: _SidebarSpaceMenuItem(
child: SidebarSpaceMenuItem(
space: space,
isSelected: state.currentSpace?.id == space.id,
),
@ -53,8 +53,9 @@ class SidebarSpaceMenu extends StatelessWidget {
}
}
class _SidebarSpaceMenuItem extends StatelessWidget {
const _SidebarSpaceMenuItem({
class SidebarSpaceMenuItem extends StatelessWidget {
const SidebarSpaceMenuItem({
super.key,
required this.space,
required this.isSelected,
});

View File

@ -1535,10 +1535,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
version: "3.1.4"
plugin_platform_interface:
dependency: "direct dev"
description:
@ -1933,10 +1933,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.2.0"
string_validator:
dependency: "direct main"
description:
@ -2238,10 +2238,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.1"
watcher:
dependency: transitive
description:

View File

@ -121,23 +121,23 @@ custom-protocol = ["tauri/custom-protocol"]
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
<<<<<<< Updated upstream
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
= = = = = = =
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
>>>>>>> Stashed changes

View File

@ -986,7 +986,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"arc-swap",
@ -1011,7 +1011,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"async-trait",
@ -1050,7 +1050,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"arc-swap",
@ -1071,7 +1071,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"bytes",
@ -1091,7 +1091,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"arc-swap",
@ -1113,7 +1113,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"async-recursion",
@ -1168,7 +1168,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"async-stream",
@ -1248,7 +1248,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"collab",
@ -2738,6 +2738,7 @@ dependencies = [
"lib-infra",
"once_cell",
"protobuf",
"rayon",
"semver",
"serde",
"serde_json",

View File

@ -118,14 +118,14 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
# Working directory: frontend

View File

@ -849,7 +849,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"arc-swap",
@ -874,7 +874,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"async-trait",
@ -913,7 +913,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"arc-swap",
@ -934,7 +934,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"bytes",
@ -954,7 +954,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"arc-swap",
@ -976,7 +976,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"async-recursion",
@ -1031,7 +1031,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"async-stream",
@ -1111,7 +1111,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d7dc26a906e3ce5d72a309e933f853f1e75da1cb#d7dc26a906e3ce5d72a309e933f853f1e75da1cb"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d73579b0269b33856690c4b4d68bf22b4eb17a99#d73579b0269b33856690c4b4d68bf22b4eb17a99"
dependencies = [
"anyhow",
"collab",
@ -2538,6 +2538,7 @@ dependencies = [
"quickcheck_macros",
"rand 0.8.5",
"rand_core 0.6.4",
"rayon",
"semver",
"serde",
"serde_json",

View File

@ -142,14 +142,14 @@ rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "1710120
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d7dc26a906e3ce5d72a309e933f853f1e75da1cb" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d73579b0269b33856690c4b4d68bf22b4eb17a99" }
# Working directory: frontend
# To update the commit ID, run:

View File

@ -77,7 +77,7 @@ impl EventIntegrationTest {
let path = path_buf.to_str().unwrap().to_string();
let device_id = uuid::Uuid::new_v4().to_string();
let config = AppFlowyCoreConfig::new(
Version::new(0, 5, 8),
Version::new(0, 7, 0),
path.clone(),
path,
device_id,

View File

@ -175,6 +175,7 @@ impl EventIntegrationTest {
let payload = ImportAppFlowyDataPB {
path,
import_container_name: name,
parent_view_id: None,
};
match EventBuilder::new(self.clone())
.event(UserEvent::ImportAppFlowyDataFolder)
@ -431,7 +432,7 @@ pub struct SignUpContext {
pub password: String,
}
pub async fn user_localhost_af_cloud() {
pub async fn use_localhost_af_cloud() {
AuthenticatorType::AppFlowyCloud.write_env();
let base_url =
std::env::var("af_cloud_test_base_url").unwrap_or("http://localhost:8000".to_string());
@ -454,5 +455,5 @@ pub async fn user_localhost_af_cloud_with_nginx() {
std::env::set_var("af_cloud_test_base_url", "http://localhost");
std::env::set_var("af_cloud_test_ws_url", "ws://localhost/ws/v1");
std::env::set_var("af_cloud_test_gotrue_url", "http://localhost/gotrue");
user_localhost_af_cloud().await
use_localhost_af_cloud().await
}

View File

@ -1,4 +1,4 @@
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_ai::entities::CompletionTypePB;
@ -6,7 +6,7 @@ use std::time::Duration;
#[tokio::test]
async fn af_cloud_complete_text_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
test.af_cloud_sign_up().await;

View File

@ -1,5 +1,5 @@
use crate::util::receive_with_timeout;
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_ai::entities::ChatMessageListPB;
use flowy_ai::notification::ChatNotification;
@ -10,7 +10,7 @@ use std::time::Duration;
#[tokio::test]
async fn af_cloud_create_chat_message_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
test.af_cloud_sign_up().await;
@ -65,7 +65,7 @@ async fn af_cloud_create_chat_message_test() {
#[tokio::test]
async fn af_cloud_load_remote_system_message_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
test.af_cloud_sign_up().await;

View File

@ -1,18 +1,17 @@
use crate::util::{receive_with_timeout, unzip};
use collab_document::blocks::DocumentData;
use serde_json::json;
use std::time::Duration;
use collab_folder::SpaceInfo;
use event_integration_test::document_event::assert_document_data_equal;
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_document::entities::{DocumentSyncState, DocumentSyncStatePB};
use crate::util::{receive_with_timeout, unzip};
use serde_json::json;
use std::time::Duration;
#[tokio::test]
async fn af_cloud_edit_document_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
test.af_cloud_sign_up().await;
test.wait_ws_connected().await;
@ -44,7 +43,7 @@ async fn af_cloud_edit_document_test() {
#[tokio::test]
async fn af_cloud_sync_anon_user_document_test() {
let (cleaner, user_db_path) = unzip("./tests/asset", "040_sync_local_document").unwrap();
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path.clone(), DEFAULT_NAME.to_string())
.await;
@ -56,6 +55,11 @@ async fn af_cloud_sync_anon_user_document_test() {
// view: SyncDocument
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3);
for view in views.iter() {
let space_info = serde_json::from_str::<SpaceInfo>(view.extra.as_ref().unwrap()).unwrap();
assert!(space_info.is_space);
}
let document_id = views[2].id.clone();
test.open_document(document_id.clone()).await;

View File

@ -1,5 +1,5 @@
use crate::document::generate_random_bytes;
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_storage_pub::storage::FileUploadState;
use lib_infra::util::md5;
@ -14,7 +14,7 @@ use tokio::time::timeout;
#[tokio::test]
async fn af_cloud_upload_big_file_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let mut test = EventIntegrationTest::new().await;
test.af_cloud_sign_up().await;
tokio::time::sleep(Duration::from_secs(6)).await;
@ -78,7 +78,7 @@ async fn af_cloud_upload_big_file_test() {
#[tokio::test]
async fn af_cloud_upload_6_files_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
test.af_cloud_sign_up().await;
let workspace_id = test.get_current_workspace().await.id;

View File

@ -1,4 +1,4 @@
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_user::entities::AuthenticatorPB;
@ -49,7 +49,7 @@ async fn migrate_anon_user_data_to_af_cloud_test() {
// view: Document2
// view: Grid1
// view: Grid2
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path.clone(), DEFAULT_NAME.to_string())
.await;
@ -103,7 +103,6 @@ async fn migrate_anon_user_data_to_af_cloud_test() {
);
// check second level
assert_eq!(anon_second_level_views.len(), user_second_level_views.len());
assert_ne!(anon_second_level_views[0].id, user_second_level_views[0].id);
assert_eq!(
anon_second_level_views[0].name,

View File

@ -1,4 +1,4 @@
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_user::entities::UpdateUserProfilePayloadPB;
@ -7,7 +7,7 @@ use crate::util::generate_test_email;
#[tokio::test]
async fn af_cloud_sign_up_test() {
// user_localhost_af_cloud_with_nginx().await;
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
let email = generate_test_email();
let user = test.af_cloud_sign_in_with_email(&email).await.unwrap();
@ -16,7 +16,7 @@ async fn af_cloud_sign_up_test() {
#[tokio::test]
async fn af_cloud_update_user_metadata() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
let user = test.af_cloud_sign_up().await;

View File

@ -1,70 +1,40 @@
use crate::util::unzip;
use assert_json_diff::assert_json_include;
use collab_database::rows::database_row_document_id_from_row_id;
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_user::errors::ErrorCode;
use serde_json::{json, Value};
use std::env::temp_dir;
#[tokio::test]
async fn import_appflowy_data_need_migration_test() {
// In 037, the workspace array will be migrated to view.
let import_container_name = "037_local".to_string();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
// Getting started
// Document1
// Document2(fav)
user_localhost_af_cloud().await;
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
let _ = test.af_cloud_sign_up().await;
test
.import_appflowy_data(
user_db_path.to_str().unwrap().to_string(),
Some(import_container_name.clone()),
)
.await
.unwrap();
// after import, the structure is:
// workspace:
// view: Generate
// view: Shared
// view: 037_local
// view: Getting Started
// view: Document1
// view: Document2
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3);
assert_eq!(views[2].name, import_container_name);
let child_views = test.get_view(&views[2].id).await.child_views;
assert_eq!(child_views.len(), 1);
let child_views = test.get_view(&child_views[0].id).await.child_views;
assert_eq!(child_views.len(), 2);
assert_eq!(child_views[0].name, "Document1");
assert_eq!(child_views[1].name, "Document2");
drop(cleaner);
}
#[tokio::test]
async fn import_appflowy_data_folder_into_new_view_test() {
let import_container_name = "040_local".to_string();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
// In the 040_local, the structure is:
// workspace:
// view: Document1
// view: Document2
// view: Grid1
// view: Grid2
user_localhost_af_cloud().await;
// Document1
// Document2
// Grid1
// Grid2
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
let _ = test.af_cloud_sign_up().await;
let views = test.get_all_workspace_views().await;
assert_eq!(views[0].name, "General");
assert_eq!(views[1].name, "Shared");
assert_eq!(views.len(), 2);
let shared_space_id = views[1].id.clone();
let shared_space = test.get_view(&shared_space_id).await;
// by default, shared space is empty
assert!(shared_space.child_views.is_empty());
// after sign up, the initial workspace is created, so the structure is:
// workspace:
// view: Getting Started
// General
// template_document
// template_document
// Shared
test
.import_appflowy_data(
@ -75,24 +45,26 @@ async fn import_appflowy_data_folder_into_new_view_test() {
.unwrap();
// after import, the structure is:
// workspace:
// view: Getting Started
// view: 040_local
// view: Document1
// view: Document2
// view: Grid1
// view: Grid2
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3);
assert_eq!(views[2].name, import_container_name);
// General
// template_document
// template_document
// 040_local
// Shared
let general_space = test.get_view(&shared_space_id).await;
let shared_sub_views = &general_space.child_views;
assert_eq!(shared_sub_views.len(), 1);
assert_eq!(shared_sub_views[0].name, import_container_name);
// the 040_local should be an empty document, so try to get the document data
let _ = test.get_document_data(&views[2].id).await;
let _ = test.get_document_data(&shared_sub_views[0].id).await;
let local_child_views = test.get_view(&views[2].id).await.child_views;
assert_eq!(local_child_views.len(), 1);
assert_eq!(local_child_views[0].name, "Document1");
let t_040_local_child_views = test.get_view(&shared_sub_views[0].id).await.child_views;
assert_eq!(t_040_local_child_views[0].name, "Document1");
let document1_child_views = test.get_view(&local_child_views[0].id).await.child_views;
let document1_child_views = test
.get_view(&t_040_local_child_views[0].id)
.await
.child_views;
assert_eq!(document1_child_views.len(), 1);
assert_eq!(document1_child_views[0].name, "Document2");
@ -122,12 +94,11 @@ async fn import_appflowy_data_folder_into_current_workspace_test() {
let import_container_name = "040_local".to_string();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
// In the 040_local, the structure is:
// workspace:
// view: Document1
// view: Document2
// view: Grid1
// view: Grid2
user_localhost_af_cloud().await;
// Document1
// Document2
// Grid1
// Grid2
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
let _ = test.af_cloud_sign_up().await;
// after sign up, the initial workspace is created, so the structure is:
@ -138,20 +109,25 @@ async fn import_appflowy_data_folder_into_current_workspace_test() {
.import_appflowy_data(user_db_path.to_str().unwrap().to_string(), None)
.await
.unwrap();
let views = test.get_all_workspace_views().await;
assert_eq!(views[0].name, "General");
assert_eq!(views[1].name, "Shared");
assert_eq!(views.len(), 2);
let shared_space_id = views[1].id.clone();
let shared_space_child_views = test.get_view(&shared_space_id).await.child_views;
assert_eq!(shared_space_child_views.len(), 1);
// after import, the structure is:
// workspace:
// view: General
// view: Shared
// view: Document1
// view: Document2
// view: Grid1
// view: Grid2
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3);
assert_eq!(views[2].name, "Document1");
let document_1_child_views = test.get_view(&views[2].id).await.child_views;
assert_eq!(document_1_child_views.len(), 1);
// General
// Shared
// Document1
// Document2
// Grid1
// Grid2
let document_1 = test.get_view(&shared_space_child_views[0].id).await;
assert_eq!(document_1.name, "Document1");
let document_1_child_views = test.get_view(&document_1.id).await.child_views;
assert_eq!(document_1_child_views[0].name, "Document2");
let document2_child_views = test
@ -165,33 +141,10 @@ async fn import_appflowy_data_folder_into_current_workspace_test() {
drop(cleaner);
}
#[tokio::test]
async fn import_appflowy_data_folder_into_new_view_test2() {
let import_container_name = "040_local_2".to_string();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
user_localhost_af_cloud().await;
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
let _ = test.af_cloud_sign_up().await;
test
.import_appflowy_data(
user_db_path.to_str().unwrap().to_string(),
Some(import_container_name.clone()),
)
.await
.unwrap();
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3);
assert_eq!(views[2].name, import_container_name);
assert_040_local_2_import_content(&test, &views[2].id).await;
drop(cleaner);
}
#[tokio::test]
async fn import_empty_appflowy_data_folder_test() {
let path = temp_dir();
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
let _ = test.af_cloud_sign_up().await;
let error = test
@ -217,9 +170,18 @@ async fn import_appflowy_data_folder_multiple_times_test() {
// Doc3_grid_1
// Doc3_grid_2
// Doc3_calendar_1
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
let _ = test.af_cloud_sign_up().await;
let views = test.get_all_workspace_views().await;
assert_eq!(views[0].name, "General");
assert_eq!(views[1].name, "Shared");
assert_eq!(views.len(), 2);
let shared_space_id = views[1].id.clone();
let shared_space = test.get_view(&shared_space_id).await;
// by default, shared space is empty
assert!(shared_space.child_views.is_empty());
test
.import_appflowy_data(
user_db_path.to_str().unwrap().to_string(),
@ -230,12 +192,22 @@ async fn import_appflowy_data_folder_multiple_times_test() {
// after import, the structure is:
// General
// Shared
// 040_local_2
// 040_local_2
// Getting Started
// Doc1
// Doc2
// Grid1
// Doc3
// Doc3_grid_1
// Doc3_grid_2
// Doc3_calendar_1
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3);
assert_eq!(views[2].name, import_container_name);
assert_040_local_2_import_content(&test, &views[2].id).await;
let shared_space_children_views = test.get_view(&shared_space_id).await.child_views;
assert_eq!(shared_space_children_views.len(), 1);
let _040_local_view_id = shared_space_children_views[0].id.clone();
let _040_local_view = test.get_view(&_040_local_view_id).await;
assert_eq!(_040_local_view.name, import_container_name);
assert_040_local_2_import_content(&test, &_040_local_view_id).await;
test
.import_appflowy_data(
@ -247,15 +219,13 @@ async fn import_appflowy_data_folder_multiple_times_test() {
// after import, the structure is:
// Generate
// Shared
// 040_local_2
// Getting started
// 040_local_2
// Getting started
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 4);
assert_eq!(views[3].name, import_container_name);
assert_040_local_2_import_content(&test, &views[2].id).await;
assert_040_local_2_import_content(&test, &views[3].id).await;
// 040_local_2
// 040_local_2
let shared_space_children_views = test.get_view(&shared_space_id).await.child_views;
assert_eq!(shared_space_children_views.len(), 2);
for view in shared_space_children_views {
assert_040_local_2_import_content(&test, &view.id).await;
}
drop(cleaner);
}

View File

@ -1,5 +1,5 @@
use crate::user::af_cloud_test::util::get_synced_workspaces;
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
#[tokio::test]
@ -35,7 +35,7 @@ async fn af_cloud_invite_workspace_member() {
#[tokio::test]
async fn af_cloud_add_workspace_member_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test_1 = EventIntegrationTest::new().await;
let user_1 = test_1.af_cloud_sign_up().await;
let workspace_id_1 = test_1.get_current_workspace().await.id;
@ -57,7 +57,7 @@ async fn af_cloud_add_workspace_member_test() {
#[tokio::test]
async fn af_cloud_delete_workspace_member_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test_1 = EventIntegrationTest::new().await;
let user_1 = test_1.af_cloud_sign_up().await;
let workspace_id_1 = test_1.get_current_workspace().await.id;
@ -78,7 +78,7 @@ async fn af_cloud_delete_workspace_member_test() {
#[tokio::test]
async fn af_cloud_leave_workspace_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test_1 = EventIntegrationTest::new().await;
test_1.af_cloud_sign_up().await;
let workspace_id_1 = test_1.get_current_workspace().await.id;

View File

@ -2,7 +2,7 @@ use collab::core::collab::DataSource::DocStateV1;
use collab::core::origin::CollabOrigin;
use collab_entity::CollabType;
use collab_folder::Folder;
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use std::time::Duration;
use tokio::task::LocalSet;
@ -12,7 +12,7 @@ use crate::user::af_cloud_test::util::get_synced_workspaces;
#[tokio::test]
async fn af_cloud_workspace_delete() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
let user_profile_pb = test.af_cloud_sign_up().await;
let workspaces = get_synced_workspaces(&test, user_profile_pb.id).await;
@ -33,7 +33,7 @@ async fn af_cloud_workspace_delete() {
#[tokio::test]
async fn af_cloud_workspace_change_name_and_icon() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
let user_profile_pb = test.af_cloud_sign_up().await;
let workspaces = test.get_all_workspaces().await;
@ -58,7 +58,7 @@ async fn af_cloud_workspace_change_name_and_icon() {
#[tokio::test]
async fn af_cloud_create_workspace_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
let user_profile_pb = test.af_cloud_sign_up().await;
@ -98,7 +98,7 @@ async fn af_cloud_create_workspace_test() {
#[tokio::test]
async fn af_cloud_open_workspace_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test = EventIntegrationTest::new().await;
let _ = test.af_cloud_sign_up().await;
let default_document_name = "General";
@ -160,7 +160,7 @@ async fn af_cloud_open_workspace_test() {
#[tokio::test]
async fn af_cloud_different_open_same_workspace_test() {
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
// Set up the primary client and sign them up to the cloud.
let test_runner = EventIntegrationTest::new().await;

View File

@ -1,5 +1,5 @@
use crate::util::unzip;
use event_integration_test::user_event::user_localhost_af_cloud;
use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use std::time::Duration;
@ -11,7 +11,7 @@ async fn import_appflowy_data_folder_into_new_view_test() {
let (imported_af_folder_cleaner, imported_af_data_path) =
unzip("./tests/asset", &import_container_name).unwrap();
user_localhost_af_cloud().await;
use_localhost_af_cloud().await;
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path.clone(), DEFAULT_NAME.to_string())
.await;
@ -34,15 +34,22 @@ async fn import_appflowy_data_folder_into_new_view_test() {
// after import, the structure is:
// workspace:
// view: Getting Started
// view: 040_local
// view: Document1
// view: Document2
// view: Grid1
// view: Grid2
// Document1
// Document2
// Grid1
// Grid2
// 040_local
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 2);
assert_eq!(views[1].name, import_container_name);
assert_eq!(views.len(), 1);
assert_eq!(views[0].name, "Document1");
assert_eq!(views[0].child_views.len(), 2);
for (index, view) in views[0].child_views.iter().enumerate() {
let view = test.get_view(&view.id).await;
if index == 1 {
assert_eq!(view.name, import_container_name);
}
}
drop(cleaner);
drop(imported_af_folder_cleaner);

View File

@ -1,20 +0,0 @@
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use crate::util::unzip;
#[tokio::test]
async fn collab_db_restore_test() {
let (cleaner, user_db_path) = unzip(
"./tests/user/migration_test/history_user_db",
"038_collab_db_corrupt_restore",
)
.unwrap();
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()).await;
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 1);
drop(cleaner);
}

View File

@ -1,4 +1,2 @@
mod document_test;
mod version_test;
mod collab_db_restore;

View File

@ -138,7 +138,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
let json_str = include_str!("../../assets/read_me.json");
let document_pb = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
manager
.create_document(uid, &view.parent_view.id, Some(document_pb.into()))
.create_document(uid, &view.view.id, Some(document_pb.into()))
.await
.unwrap();
view
@ -355,6 +355,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
let database_row_encoded_collabs =
load_collab_by_object_ids(uid, &collab_read_txn, &row_oids)
.0
.into_iter()
.map(|(oid, collab)| {
collab
@ -380,6 +381,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
let database_row_document_encoded_collabs =
load_collab_by_object_ids(uid, &collab_read_txn, &row_document_ids)
.0
.into_iter()
.map(|(oid, collab)| {
collab

View File

@ -4,6 +4,7 @@ use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use flowy_database2::DatabaseManager;
use flowy_error::FlowyResult;
use flowy_folder::manager::FolderManager;
use flowy_folder_pub::entities::ImportFrom;
use flowy_sqlite::kv::KVStorePreferences;
use flowy_user::services::authenticate_user::AuthenticateUser;
use flowy_user::user_manager::UserManager;
@ -45,12 +46,31 @@ pub struct UserWorkspaceServiceImpl {
#[async_trait]
impl UserWorkspaceService for UserWorkspaceServiceImpl {
async fn did_import_views(&self, views: Vec<ParentChildViews>) -> FlowyResult<()> {
self.folder_manager.insert_parent_child_views(views).await?;
async fn import_views(
&self,
source: &ImportFrom,
views: Vec<ParentChildViews>,
orphan_views: Vec<ParentChildViews>,
parent_view_id: Option<String>,
) -> FlowyResult<()> {
match source {
ImportFrom::AnonUser => {
self
.folder_manager
.insert_views_as_spaces(views, orphan_views)
.await?;
},
ImportFrom::AppFlowyDataFolder => {
self
.folder_manager
.insert_views_with_parent(views, orphan_views, parent_view_id)
.await?;
},
}
Ok(())
}
async fn did_import_database_views(
async fn import_database_views(
&self,
ids_by_database_id: HashMap<String, Vec<String>>,
) -> FlowyResult<()> {

View File

@ -322,12 +322,10 @@ impl DatabaseViewEditor {
} else {
(None, None, vec![])
}
} else if let Ok(result) = controller.did_delete_row(row) {
(None, result.deleted_group, result.row_changesets)
} else {
if let Ok(result) = controller.did_delete_row(&row) {
(None, result.deleted_group, result.row_changesets)
} else {
(None, None, vec![])
}
(None, None, vec![])
};
if let Some(inserted_group) = inserted_group {

View File

@ -18,7 +18,7 @@ impl DateFilterPB {
let timestamp = if self.condition.is_filter_on_start_timestamp() {
cell_data.timestamp
} else {
cell_data.end_timestamp.or_else(|| cell_data.timestamp)
cell_data.end_timestamp.or(cell_data.timestamp)
};
Some(strategy.filter(timestamp))

View File

@ -17,8 +17,8 @@ use collab_entity::CollabType;
use collab_plugins::CollabKVDB;
use dashmap::DashMap;
use lib_infra::util::timestamp;
use tracing::trace;
use tracing::{event, instrument};
use tracing::{info, trace};
use crate::document::{
subscribe_document_changed, subscribe_document_snapshot_state, subscribe_document_sync_state,
@ -214,6 +214,10 @@ impl DocumentManager {
// If the document does not exist in local disk, try get the doc state from the cloud. This happens
// When user_device_a create a document and user_device_b open the document.
if !self.is_doc_exist(doc_id).await? {
info!(
"document {} not found in local disk, try to get the doc state from the cloud",
doc_id
);
doc_state = DataSource::DocStateV1(
self
.cloud_service

View File

@ -3,21 +3,28 @@ use collab_folder::{ViewIcon, ViewLayout};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub enum ImportData {
AppFlowyDataFolder { items: Vec<AppFlowyData> },
pub struct ImportedAppFlowyData {
pub source: ImportFrom,
pub folder_data: ImportedFolderData,
pub collab_data: ImportedCollabData,
pub parent_view_id: Option<String>,
}
pub enum AppFlowyData {
Folder {
views: Vec<ParentChildViews>,
/// Used to update the [DatabaseViewTrackerList] when importing the database.
database_view_ids_by_database_id: HashMap<String, Vec<String>>,
},
CollabObject {
row_object_ids: Vec<String>,
document_object_ids: Vec<String>,
database_object_ids: Vec<String>,
},
pub enum ImportFrom {
AnonUser,
AppFlowyDataFolder,
}
pub struct ImportedFolderData {
pub views: Vec<ParentChildViews>,
pub orphan_views: Vec<ParentChildViews>,
/// Used to update the [DatabaseViewTrackerList] when importing the database.
pub database_view_ids_by_database_id: HashMap<String, Vec<String>>,
}
pub struct ImportedCollabData {
pub row_object_ids: Vec<String>,
pub document_object_ids: Vec<String>,
pub database_object_ids: Vec<String>,
}
pub struct ImportViews {

View File

@ -14,9 +14,7 @@ use crate::notification::{
};
use crate::publish_util::{generate_publish_name, view_pb_to_publish_view};
use crate::share::{ImportParams, ImportValue};
use crate::util::{
folder_not_init_error, insert_parent_child_views, workspace_data_not_sync_error,
};
use crate::util::{folder_not_init_error, workspace_data_not_sync_error};
use crate::view_operation::{
create_view, EncodedCollabWrapper, FolderOperationHandler, FolderOperationHandlers,
};
@ -24,7 +22,7 @@ use arc_swap::ArcSwapOption;
use collab::core::collab::DataSource;
use collab::lock::RwLock;
use collab_entity::{CollabType, EncodedCollab};
use collab_folder::hierarchy_builder::ParentChildViews;
use collab_folder::hierarchy_builder::{ParentChildViews, SpacePermission, ViewExtraBuilder};
use collab_folder::{
Folder, FolderData, FolderNotify, Section, SectionItem, TrashInfo, View, ViewLayout, ViewUpdate,
Workspace,
@ -346,20 +344,99 @@ impl FolderManager {
})
}
pub async fn insert_parent_child_views(
/// All the views will become a space under the workspace.
pub async fn insert_views_as_spaces(
&self,
views: Vec<ParentChildViews>,
mut views: Vec<ParentChildViews>,
orphan_views: Vec<ParentChildViews>,
) -> Result<(), FlowyError> {
match self.mutex_folder.load_full() {
None => Err(FlowyError::internal().with_context("The folder is not initialized")),
Some(lock) => {
let mut folder = lock.write().await;
for view in views {
insert_parent_child_views(&mut folder, view);
}
Ok(())
},
let lock = self
.mutex_folder
.load_full()
.ok_or_else(|| FlowyError::internal().with_context("The folder is not initialized"))?;
let mut folder = lock.write().await;
let workspace_id = folder
.get_workspace_id()
.ok_or_else(|| FlowyError::internal().with_context("Cannot find the workspace ID"))?;
views.iter_mut().for_each(|view| {
view.view.parent_view_id = workspace_id.clone();
view.view.extra = Some(
serde_json::to_string(
&ViewExtraBuilder::new()
.is_space(true, SpacePermission::PublicToAll)
.build(),
)
.unwrap(),
);
});
let all_views = views.into_iter().chain(orphan_views.into_iter()).collect();
folder.insert_nested_views(all_views);
Ok(())
}
/// Inserts parent-child views into the folder. If a `parent_view_id` is provided,
/// it will be used to set the `parent_view_id` for all child views. If not, the latest
/// view (by `last_edited_time`) from the workspace will be used as the parent view.
///
pub async fn insert_views_with_parent(
&self,
mut views: Vec<ParentChildViews>,
orphan_views: Vec<ParentChildViews>,
parent_view_id: Option<String>,
) -> Result<(), FlowyError> {
let lock = self
.mutex_folder
.load_full()
.ok_or_else(|| FlowyError::internal().with_context("The folder is not initialized"))?;
// Obtain a write lock on the folder.
let mut folder = lock.write().await;
// Set the parent view ID for the child views.
if let Some(parent_view_id) = parent_view_id {
// If a valid parent_view_id is provided, set it for each child view.
if folder.get_view(&parent_view_id).is_some() {
info!(
"[AppFlowyData]: Attach parent-child views with the latest view: {:?}",
parent_view_id
);
views.iter_mut().for_each(|child_view| {
child_view.view.parent_view_id = parent_view_id.clone();
});
}
} else {
// If no parent_view_id is provided, find the latest view in the workspace.
let workspace_id = folder
.get_workspace_id()
.ok_or_else(|| FlowyError::internal().with_context("Cannot find the workspace ID"))?;
// Get the latest view based on the last_edited_time in the workspace.
match folder
.get_views_belong_to(&workspace_id)
.iter()
.max_by_key(|view| view.last_edited_time)
{
None => info!("[AppFlowyData]: No views found in the workspace"),
Some(latest_view) => {
info!(
"[AppFlowyData]: Attach parent-child views with the latest view: {}:{}, is_space: {:?}",
latest_view.id,
latest_view.name,
latest_view.space_info(),
);
views.iter_mut().for_each(|child_view| {
child_view.view.parent_view_id = latest_view.id.clone();
});
},
}
}
// Insert the views into the folder.
let all_views = views.into_iter().chain(orphan_views.into_iter()).collect();
folder.insert_nested_views(all_views);
Ok(())
}
pub async fn get_workspace_pb(&self) -> FlowyResult<WorkspacePB> {

View File

@ -28,12 +28,12 @@ impl DefaultFolderBuilder {
let views = workspace_view_builder.write().await.build();
// Safe to unwrap because we have at least one view. check out the DocumentFolderOperation.
let first_view = views.first().unwrap().parent_view.clone();
let first_view = views.first().unwrap().view.clone();
let first_level_views = views
.iter()
.map(|value| ViewIdentifier {
id: value.parent_view.id.clone(),
id: value.view.id.clone(),
})
.collect::<Vec<_>>();
@ -62,11 +62,11 @@ impl DefaultFolderBuilder {
impl From<&ParentChildViews> for ViewPB {
fn from(value: &ParentChildViews) -> Self {
view_pb_with_child_views(
Arc::new(value.parent_view.clone()),
Arc::new(value.view.clone()),
value
.child_views
.children
.iter()
.map(|v| Arc::new(v.parent_view.clone()))
.map(|v| Arc::new(v.view.clone()))
.collect(),
)
}

View File

@ -1,8 +1,5 @@
use crate::entities::UserFolderPB;
use collab_folder::hierarchy_builder::ParentChildViews;
use collab_folder::Folder;
use flowy_error::{ErrorCode, FlowyError};
use tracing::{event, instrument};
pub(crate) fn folder_not_init_error() -> FlowyError {
FlowyError::internal().with_context("Folder not initialized")
@ -14,17 +11,3 @@ pub(crate) fn workspace_data_not_sync_error(uid: i64, workspace_id: &str) -> Flo
workspace_id: workspace_id.to_string(),
})
}
#[instrument(level = "debug", skip(folder, view))]
pub(crate) fn insert_parent_child_views(folder: &mut Folder, view: ParentChildViews) {
event!(
tracing::Level::DEBUG,
"Inserting view: {}, view children: {}",
view.parent_view.id,
view.child_views.len()
);
folder.insert_view(view.parent_view, None);
for child_view in view.child_views {
insert_parent_child_views(folder, child_view);
}
}

View File

@ -60,7 +60,6 @@ pub fn user_profile_from_af_profile(
icon_url: icon_url.unwrap_or_default(),
openai_key: openai_key.unwrap_or_default(),
stability_ai_key: stability_ai_key.unwrap_or_default(),
workspace_id: profile.latest_workspace_id.to_string(),
authenticator: Authenticator::AppFlowyCloud,
encryption_type,
uid: profile.uid,

View File

@ -167,7 +167,6 @@ pub struct UserProfile {
pub icon_url: String,
pub openai_key: String,
pub stability_ai_key: String,
pub workspace_id: String,
pub authenticator: Authenticator,
// If the encryption_sign is not empty, which means the user has enabled the encryption.
pub encryption_type: EncryptionType,
@ -246,7 +245,6 @@ where
token: value.user_token().unwrap_or_default(),
icon_url,
openai_key,
workspace_id: value.latest_workspace().id.to_owned(),
authenticator: auth_type.clone(),
encryption_type: value.encryption_type(),
stability_ai_key,

View File

@ -1,12 +1,19 @@
use collab_folder::hierarchy_builder::ParentChildViews;
use flowy_error::FlowyResult;
use flowy_folder_pub::entities::ImportFrom;
use lib_infra::async_trait::async_trait;
use std::collections::HashMap;
#[async_trait]
pub trait UserWorkspaceService: Send + Sync {
async fn did_import_views(&self, views: Vec<ParentChildViews>) -> FlowyResult<()>;
async fn did_import_database_views(
async fn import_views(
&self,
source: &ImportFrom,
views: Vec<ParentChildViews>,
orphan_views: Vec<ParentChildViews>,
parent_view_id: Option<String>,
) -> FlowyResult<()>;
async fn import_database_views(
&self,
ids_by_database_id: HashMap<String, Vec<String>>,
) -> FlowyResult<()>;

View File

@ -49,6 +49,7 @@ base64 = "^0.21"
tokio-stream = "0.1.14"
semver = "1.0.22"
validator = { workspace = true, features = ["derive"] }
rayon = "1.10.0"
[dev-dependencies]
nanoid = "0.4.0"

View File

@ -9,4 +9,7 @@ pub struct ImportAppFlowyDataPB {
#[pb(index = 2, one_of)]
pub import_container_name: Option<String>,
#[pb(index = 3, one_of)]
pub parent_view_id: Option<String>,
}

View File

@ -277,7 +277,7 @@ pub async fn import_appflowy_data_folder_handler(
af_spawn(async move {
let result = async {
let manager = upgrade_manager(manager)?;
let imported_folder = prepare_import(&data.path)
let imported_folder = prepare_import(&data.path, data.parent_view_id)
.map_err(|err| FlowyError::new(ErrorCode::AppFlowyDataFolderImportError, err.to_string()))?
.with_container_name(data.import_container_name);

View File

@ -1,34 +1,35 @@
use collab::preclude::Collab;
use collab_integrate::{CollabKVAction, PersistenceError};
use std::collections::HashMap;
use tracing::instrument;
/// This function loads collab objects by their object_ids.
#[instrument(level = "debug", skip_all)]
pub fn load_collab_by_object_ids<'a, R>(
uid: i64,
collab_read_txn: &R,
object_ids: &[String],
) -> HashMap<String, Collab>
) -> (HashMap<String, Collab>, Vec<String>)
where
R: CollabKVAction<'a>,
PersistenceError: From<R::Error>,
{
let mut invalid_object_ids = vec![];
let mut collab_by_oid = HashMap::new();
for object_id in object_ids {
match load_collab_by_object_id(uid, collab_read_txn, object_id) {
Ok(collab) => {
collab_by_oid.insert(object_id.clone(), collab);
},
Err(err) => tracing::error!("🔴load collab: {} failed: {:?} ", object_id, err),
Err(err) => {
invalid_object_ids.push(object_id.clone());
tracing::error!("🔴load collab: {} failed: {:?} ", object_id, err)
},
}
}
collab_by_oid
(collab_by_oid, invalid_object_ids)
}
/// This function loads single collab object by its object_id.
#[instrument(level = "debug", skip_all)]
pub fn load_collab_by_object_id<'a, R>(
uid: i64,
collab_read_txn: &R,

View File

@ -44,18 +44,8 @@ impl UserDB {
}
/// Performs a conditional backup or restoration of the collaboration database (CollabDB) for a specific user.
///
/// This function takes a user ID and conducts the following operations:
///
/// **Backup or Restoration**:
/// - If the CollabDB exists, it tries to open the database:
/// - **Successful Open**: If the database opens successfully, it attempts to back it up.
/// - **Failed Open**: If the database cannot be opened, it indicates a potential issue, and the function
/// attempts to restore the database from the latest backup.
/// - If the CollabDB does not exist, it immediately attempts to restore from the latest backup.
///
#[instrument(level = "debug", skip_all)]
pub fn backup_or_restore(&self, uid: i64, workspace_id: &str) {
pub fn backup(&self, uid: i64, workspace_id: &str) {
// Obtain the path for the collaboration database.
let collab_db_path = self.paths.collab_db_path(uid);
@ -63,12 +53,10 @@ impl UserDB {
if let Ok(history_folder) = self.paths.collab_db_history(uid, true) {
// Initialize the backup utility for the collaboration database.
let zip_backup = CollabDBZipBackup::new(collab_db_path.clone(), history_folder);
if collab_db_path.exists() {
// Validate the existing collaboration database.
let result = self.open_collab_db(collab_db_path, uid);
let is_ok = validate_collab_db(result, uid, workspace_id);
if is_ok {
// If database is valid, update the shared map and initiate backup.
// Asynchronous backup operation.
@ -77,9 +65,6 @@ impl UserDB {
error!("Backup of collab db failed: {:?}", err);
}
});
} else if let Err(err) = zip_backup.restore_latest_backup() {
// If validation fails, attempt to restore from the latest backup.
error!("Restoring collab db failed: {:?}", err);
}
}
}
@ -96,21 +81,6 @@ impl UserDB {
vec![]
}
#[instrument(level = "debug", skip_all)]
pub fn restore_if_need(&self, uid: i64, workspace_id: &str) {
if let Ok(history_folder) = self.paths.collab_db_history(uid, false) {
let collab_db_path = self.paths.collab_db_path(uid);
let result = self.open_collab_db(&collab_db_path, uid);
let is_ok = validate_collab_db(result, uid, workspace_id);
if !is_ok {
let zip_backup = CollabDBZipBackup::new(collab_db_path, history_folder);
if let Err(err) = zip_backup.restore_latest_backup() {
error!("restore collab db failed, {:?}", err);
}
}
}
}
/// Close the database connection for the user.
pub(crate) fn close(&self, user_id: i64) -> Result<(), FlowyError> {
if self.sqlite_map.remove(&user_id).is_some() {
@ -277,8 +247,9 @@ impl CollabDBZipBackup {
Ok(backups)
}
#[instrument(skip_all, err)]
pub fn restore_latest_backup(&self) -> io::Result<()> {
#[deprecated(note = "This function is deprecated", since = "0.7.1")]
#[allow(dead_code)]
fn restore_latest_backup(&self) -> io::Result<()> {
let mut latest_zip: Option<(String, PathBuf)> = None;
// When the history folder does not exist, there is no backup to restore
if !self.history_folder.exists() {

View File

@ -15,6 +15,9 @@ use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
pub struct UserTable {
pub(crate) id: String,
pub(crate) name: String,
#[deprecated(
note = "The workspace_id is deprecated, please use the [Session::UserWorkspace] instead"
)]
pub(crate) workspace: String,
pub(crate) icon_url: String,
pub(crate) openai_key: String,
@ -27,13 +30,7 @@ pub struct UserTable {
pub(crate) ai_model: String,
}
impl UserTable {
pub fn set_workspace(mut self, workspace: String) -> Self {
self.workspace = workspace;
self
}
}
#[allow(deprecated)]
impl From<(UserProfile, Authenticator)> for UserTable {
fn from(value: (UserProfile, Authenticator)) -> Self {
let (user_profile, auth_type) = value;
@ -41,7 +38,8 @@ impl From<(UserProfile, Authenticator)> for UserTable {
UserTable {
id: user_profile.uid.to_string(),
name: user_profile.name,
workspace: user_profile.workspace_id,
#[allow(deprecated)]
workspace: "".to_string(),
icon_url: user_profile.icon_url,
openai_key: user_profile.openai_key,
token: user_profile.token,
@ -64,7 +62,6 @@ impl From<UserTable> for UserProfile {
token: table.token,
icon_url: table.icon_url,
openai_key: table.openai_key,
workspace_id: table.workspace,
authenticator: Authenticator::from(table.auth_type),
encryption_type: EncryptionType::from_str(&table.encryption_type).unwrap_or_default(),
stability_ai_key: table.stability_ai_key,

View File

@ -554,7 +554,7 @@ impl UserManager {
self
.authenticate_user
.database
.backup_or_restore(session.user_id, &session.user_workspace.id);
.backup(session.user_id, &session.user_workspace.id);
}
/// Fetches the user profile for the given user ID.
@ -873,7 +873,7 @@ pub(crate) fn run_collab_data_migration(
sqlite_pool: Arc<ConnectionPool>,
version: Option<Version>,
) {
trace!("Run collab data migration: {:?}", version);
trace!("[AppflowyData]:Run collab data migration: {:?}", version);
let migrations = collab_migration_list();
match UserLocalDataMigration::new(session.clone(), collab_db, sqlite_pool).run(
migrations,
@ -882,10 +882,13 @@ pub(crate) fn run_collab_data_migration(
) {
Ok(applied_migrations) => {
if !applied_migrations.is_empty() {
info!("Did apply migrations: {:?}", applied_migrations);
info!(
"[AppflowyData]:Did apply migrations: {:?}",
applied_migrations
);
}
},
Err(e) => error!("User data migration failed: {:?}", e),
Err(e) => error!("[AppflowyData]:User data migration failed: {:?}", e),
}
}

View File

@ -10,7 +10,7 @@ use collab_integrate::CollabKVDB;
use tracing::{error, info, instrument, trace, warn};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_folder_pub::entities::{AppFlowyData, ImportData};
use flowy_folder_pub::entities::{ImportFrom, ImportedCollabData, ImportedFolderData};
use flowy_sqlite::schema::user_workspace_table;
use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
use flowy_user_pub::entities::{
@ -63,89 +63,87 @@ impl UserManager {
})
.await??;
match import_data {
ImportData::AppFlowyDataFolder { items } => {
for item in items {
self
.upload_appflowy_data_item(&current_session, item)
.await?;
}
},
}
info!(
"[AppflowyData]: upload {} document, {} database, {}, rows",
import_data.collab_data.document_object_ids.len(),
import_data.collab_data.database_object_ids.len(),
import_data.collab_data.row_object_ids.len()
);
self
.upload_collab_data(&current_session, import_data.collab_data)
.await?;
self
.upload_folder_data(
&current_session,
&import_data.source,
import_data.parent_view_id,
import_data.folder_data,
)
.await?;
Ok(())
}
async fn upload_appflowy_data_item(
async fn upload_folder_data(
&self,
_current_session: &Session,
source: &ImportFrom,
parent_view_id: Option<String>,
folder_data: ImportedFolderData,
) -> Result<(), FlowyError> {
let ImportedFolderData {
views,
orphan_views,
database_view_ids_by_database_id,
} = folder_data;
self
.user_workspace_service
.import_database_views(database_view_ids_by_database_id)
.await?;
self
.user_workspace_service
.import_views(source, views, orphan_views, parent_view_id)
.await?;
Ok(())
}
async fn upload_collab_data(
&self,
current_session: &Session,
item: AppFlowyData,
collab_data: ImportedCollabData,
) -> Result<(), FlowyError> {
match item {
AppFlowyData::Folder {
views,
database_view_ids_by_database_id,
} => {
// Since `async_trait` does not implement `Sync`, and the handler requires `Sync`, we use a
// channel to synchronize the operation. This approach allows asynchronous trait methods to be compatible
// with synchronous handler requirements."
let (tx, rx) = tokio::sync::oneshot::channel();
let cloned_workspace_service = self.user_workspace_service.clone();
af_spawn(async move {
let result = async {
cloned_workspace_service
.did_import_database_views(database_view_ids_by_database_id)
.await?;
cloned_workspace_service.did_import_views(views).await?;
Ok::<(), FlowyError>(())
}
.await;
let _ = tx.send(result);
})
.await?;
rx.await??;
},
AppFlowyData::CollabObject {
row_object_ids,
document_object_ids,
database_object_ids,
} => {
let user = self
.get_user_profile_from_disk(current_session.user_id)
.await?;
let user_collab_db = self
.get_collab_db(current_session.user_id)?
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Collab db not found"))?;
let user = self
.get_user_profile_from_disk(current_session.user_id)
.await?;
let user_collab_db = self
.get_collab_db(current_session.user_id)?
.upgrade()
.ok_or_else(|| FlowyError::internal().with_context("Collab db not found"))?;
let user_id = current_session.user_id;
let weak_user_collab_db = Arc::downgrade(&user_collab_db);
let weak_user_cloud_service = self.cloud_services.get_user_service()?;
match upload_collab_objects_data(
user_id,
weak_user_collab_db,
&user.workspace_id,
&user.authenticator,
AppFlowyData::CollabObject {
row_object_ids,
document_object_ids,
database_object_ids,
},
weak_user_cloud_service,
)
.await
{
Ok(_) => info!(
"Successfully uploaded collab objects data for user:{}",
user_id
),
Err(err) => {
error!(
"Failed to upload collab objects data: {:?} for user:{}",
err, user_id
);
// TODO(nathan): retry uploading the collab objects data.
},
}
let user_id = current_session.user_id;
let weak_user_collab_db = Arc::downgrade(&user_collab_db);
let weak_user_cloud_service = self.cloud_services.get_user_service()?;
match upload_collab_objects_data(
user_id,
weak_user_collab_db,
&current_session.user_workspace.id,
&user.authenticator,
collab_data,
weak_user_cloud_service,
)
.await
{
Ok(_) => info!(
"Successfully uploaded collab objects data for user:{}",
user_id
),
Err(err) => {
error!(
"Failed to upload collab objects data: {:?} for user:{}",
err, user_id
);
},
}
Ok(())
@ -160,6 +158,7 @@ impl UserManager {
imported_session: old_user.session.as_ref().clone(),
imported_collab_db: old_collab_db.clone(),
container_name: None,
parent_view_id: None,
source: ImportedSource::AnonUser,
};
self.perform_import(import_context).await?;