Skip to content

Commit 36cf653

Browse files
authored
chore: support import appflowy data into current workspace (#4254)
* chore: support import appflowy data into current workspace * refactor: code * chore: unused ref * chore: update url
1 parent 8ccd1ec commit 36cf653

File tree

25 files changed

+529
-288
lines changed

25 files changed

+529
-288
lines changed

frontend/appflowy_flutter/lib/workspace/application/settings/setting_file_importer_bloc.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:appflowy_backend/log.dart';
33
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
44
import 'package:appflowy_backend/protobuf/flowy-folder2/import.pb.dart';
55
import 'package:dartz/dartz.dart';
6+
import 'package:easy_localization/easy_localization.dart';
67
import 'package:flutter_bloc/flutter_bloc.dart';
78
import 'package:freezed_annotation/freezed_annotation.dart';
89

@@ -14,7 +15,11 @@ class SettingFileImporterBloc
1415
on<SettingFileImportEvent>((event, emit) async {
1516
await event.when(
1617
importAppFlowyDataFolder: (String path) async {
17-
final payload = ImportAppFlowyDataPB.create()..path = path;
18+
final formattedDate =
19+
DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
20+
final payload = ImportAppFlowyDataPB.create()
21+
..path = path
22+
..importContainerName = "appflowy_import_$formattedDate";
1823
final result =
1924
await FolderEventImportAppFlowyDataFolder(payload).send();
2025
result.fold(

frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import 'package:appflowy/generated/locale_keys.g.dart';
22
import 'package:appflowy/startup/startup.dart';
33
import 'package:appflowy/workspace/application/settings/setting_file_importer_bloc.dart';
44
import 'package:appflowy/workspace/presentation/home/toast.dart';
5+
import 'package:appflowy_backend/log.dart';
56
import 'package:easy_localization/easy_localization.dart';
67
import 'package:flowy_infra/file_picker/file_picker_service.dart';
78
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
9+
import 'package:flutter/gestures.dart';
810
import 'package:flutter/material.dart';
911
import 'package:flutter_bloc/flutter_bloc.dart';
1012
import 'package:fluttertoast/fluttertoast.dart';
13+
import 'package:url_launcher/url_launcher.dart';
1114

1215
class ImportAppFlowyData extends StatefulWidget {
1316
const ImportAppFlowyData({super.key});
@@ -46,20 +49,11 @@ class _ImportAppFlowyDataState extends State<ImportAppFlowyData> {
4649
},
4750
child: BlocBuilder<SettingFileImporterBloc, SettingFileImportState>(
4851
builder: (context, state) {
49-
return Column(
52+
return const Column(
5053
children: [
51-
const ImportAppFlowyDataButton(),
52-
const VSpace(6),
53-
IntrinsicHeight(
54-
child: Opacity(
55-
opacity: 0.6,
56-
child: FlowyText.medium(
57-
LocaleKeys.settings_menu_importAppFlowyDataDescription
58-
.tr(),
59-
maxLines: 13,
60-
),
61-
),
62-
),
54+
ImportAppFlowyDataButton(),
55+
VSpace(6),
56+
AppFlowyDataImportTip(),
6357
],
6458
);
6559
},
@@ -76,6 +70,45 @@ class _ImportAppFlowyDataState extends State<ImportAppFlowyData> {
7670
}
7771
}
7872

73+
class AppFlowyDataImportTip extends StatelessWidget {
74+
final url = "https://docs.appflowy.io/docs/appflowy/product/data-storage";
75+
const AppFlowyDataImportTip({super.key});
76+
77+
@override
78+
Widget build(BuildContext context) {
79+
return Opacity(
80+
opacity: 0.6,
81+
child: RichText(
82+
text: TextSpan(
83+
children: <TextSpan>[
84+
TextSpan(
85+
text: LocaleKeys.settings_menu_importAppFlowyDataDescription.tr(),
86+
style: Theme.of(context).textTheme.bodySmall!,
87+
),
88+
TextSpan(
89+
text: " ${LocaleKeys.settings_menu_importGuide.tr()} ",
90+
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
91+
color: Theme.of(context).colorScheme.primary,
92+
decoration: TextDecoration.underline,
93+
),
94+
recognizer: TapGestureRecognizer()..onTap = () => _launchURL(),
95+
),
96+
],
97+
),
98+
),
99+
);
100+
}
101+
102+
Future<void> _launchURL() async {
103+
final uri = Uri.parse(url);
104+
if (await canLaunchUrl(uri)) {
105+
await launchUrl(uri);
106+
} else {
107+
Log.error("Could not launch $url");
108+
}
109+
}
110+
}
111+
79112
class ImportAppFlowyDataButton extends StatefulWidget {
80113
const ImportAppFlowyDataButton({super.key});
81114

frontend/resources/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@
303303
"importAppFlowyData": "Import Data from External AppFlowy Folder",
304304
"importAppFlowyDataDescription": "Copy data from an external AppFlowy data folder and import it into the current AppFlowy data folder",
305305
"importSuccess": "Successfully imported the AppFlowy data folder",
306-
"importFailed": "Importing the AppFlowy data folder failed"
306+
"importFailed": "Importing the AppFlowy data folder failed",
307+
"importGuide": "For further details, please check the referenced document"
307308
},
308309
"notifications": {
309310
"enableNotifications": {

frontend/rust-lib/event-integration/src/user_event.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,14 @@ impl EventIntegrationTest {
190190
Ok(user_profile)
191191
}
192192

193-
pub async fn import_appflowy_data(&self, path: String, name: &str) -> Result<(), FlowyError> {
193+
pub async fn import_appflowy_data(
194+
&self,
195+
path: String,
196+
name: Option<String>,
197+
) -> Result<(), FlowyError> {
194198
let payload = ImportAppFlowyDataPB {
195199
path,
196-
import_container_name: name.to_string(),
200+
import_container_name: name,
197201
};
198202
match EventBuilder::new(self.clone())
199203
.event(FolderEvent::ImportAppFlowyDataFolder)

frontend/rust-lib/event-integration/tests/user/af_cloud_test/anon_user_test.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ async fn migrate_anon_user_data_to_af_cloud_test() {
7878
assert_eq!(user.authenticator, AuthenticatorPB::AppFlowyCloud);
7979

8080
let user_first_level_views = test.get_all_workspace_views().await;
81+
// assert_eq!(user_first_level_views.len(), 2);
82+
8183
println!("user first level views: {:?}", user_first_level_views);
8284
let user_second_level_views = test
8385
.get_views(&user_first_level_views[0].id)

frontend/rust-lib/event-integration/tests/user/af_cloud_test/import_af_data_folder_test.rs

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use serde_json::{json, Value};
99
use std::env::temp_dir;
1010

1111
#[tokio::test]
12-
async fn import_appflowy_data_folder_test() {
12+
async fn import_appflowy_data_folder_into_new_view_test() {
1313
let import_container_name = "040_local".to_string();
1414
let (cleaner, user_db_path) =
1515
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
@@ -29,7 +29,7 @@ async fn import_appflowy_data_folder_test() {
2929
test
3030
.import_appflowy_data(
3131
user_db_path.to_str().unwrap().to_string(),
32-
&import_container_name,
32+
Some(import_container_name.clone()),
3333
)
3434
.await
3535
.unwrap();
@@ -65,7 +65,55 @@ async fn import_appflowy_data_folder_test() {
6565
}
6666

6767
#[tokio::test]
68-
async fn import_appflowy_data_folder_test2() {
68+
async fn import_appflowy_data_folder_into_current_workspace_test() {
69+
let import_container_name = "040_local".to_string();
70+
let (cleaner, user_db_path) =
71+
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
72+
// In the 040_local, the structure is:
73+
// workspace:
74+
// view: Document1
75+
// view: Document2
76+
// view: Grid1
77+
// view: Grid2
78+
user_localhost_af_cloud().await;
79+
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
80+
let _ = test.af_cloud_sign_up().await;
81+
// after sign up, the initial workspace is created, so the structure is:
82+
// workspace:
83+
// view: Getting Started
84+
85+
test
86+
.import_appflowy_data(user_db_path.to_str().unwrap().to_string(), None)
87+
.await
88+
.unwrap();
89+
// after import, the structure is:
90+
// workspace:
91+
// view: Getting Started
92+
// view: Document1
93+
// view: Document2
94+
// view: Grid1
95+
// view: Grid2
96+
let views = test.get_all_workspace_views().await;
97+
assert_eq!(views.len(), 2);
98+
assert_eq!(views[1].name, "Document1");
99+
100+
let document_1_child_views = test.get_views(&views[1].id).await.child_views;
101+
assert_eq!(document_1_child_views.len(), 1);
102+
assert_eq!(document_1_child_views[0].name, "Document2");
103+
104+
let document2_child_views = test
105+
.get_views(&document_1_child_views[0].id)
106+
.await
107+
.child_views;
108+
assert_eq!(document2_child_views.len(), 2);
109+
assert_eq!(document2_child_views[0].name, "Grid1");
110+
assert_eq!(document2_child_views[1].name, "Grid2");
111+
112+
drop(cleaner);
113+
}
114+
115+
#[tokio::test]
116+
async fn import_appflowy_data_folder_into_new_view_test2() {
69117
let import_container_name = "040_local_2".to_string();
70118
let (cleaner, user_db_path) =
71119
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
@@ -75,7 +123,7 @@ async fn import_appflowy_data_folder_test2() {
75123
test
76124
.import_appflowy_data(
77125
user_db_path.to_str().unwrap().to_string(),
78-
&import_container_name,
126+
Some(import_container_name.clone()),
79127
)
80128
.await
81129
.unwrap();
@@ -95,7 +143,10 @@ async fn import_empty_appflowy_data_folder_test() {
95143
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
96144
let _ = test.af_cloud_sign_up().await;
97145
let error = test
98-
.import_appflowy_data(path.to_str().unwrap().to_string(), "empty_folder")
146+
.import_appflowy_data(
147+
path.to_str().unwrap().to_string(),
148+
Some("empty_folder".to_string()),
149+
)
99150
.await
100151
.unwrap_err();
101152
assert_eq!(error.code, ErrorCode::AppFlowyDataFolderImportError);
@@ -121,7 +172,7 @@ async fn import_appflowy_data_folder_multiple_times_test() {
121172
test
122173
.import_appflowy_data(
123174
user_db_path.to_str().unwrap().to_string(),
124-
&import_container_name,
175+
Some(import_container_name.clone()),
125176
)
126177
.await
127178
.unwrap();
@@ -137,7 +188,7 @@ async fn import_appflowy_data_folder_multiple_times_test() {
137188
test
138189
.import_appflowy_data(
139190
user_db_path.to_str().unwrap().to_string(),
140-
&import_container_name,
191+
Some(import_container_name.clone()),
141192
)
142193
.await
143194
.unwrap();

0 commit comments

Comments
 (0)