chore: observe error

This commit is contained in:
nathan 2024-07-19 14:43:52 +08:00
parent a3f55d7fcf
commit 916d9be4f9
12 changed files with 95 additions and 111 deletions

View File

@ -39,7 +39,7 @@ class SettingsBillingBloc
.firstWhereOrNull((i) => i.workspaceId == workspaceId) ?? .firstWhereOrNull((i) => i.workspaceId == workspaceId) ??
WorkspaceSubscriptionPB( WorkspaceSubscriptionPB(
workspaceId: workspaceId, workspaceId: workspaceId,
subscriptionPlan: SubscriptionPlanPB.None, subscriptionPlan: SubscriptionPlanPB.Free,
isActive: true, isActive: true,
), ),
(e) { (e) {
@ -47,7 +47,7 @@ class SettingsBillingBloc
if (e.code == ErrorCode.InvalidParams) { if (e.code == ErrorCode.InvalidParams) {
return WorkspaceSubscriptionPB( return WorkspaceSubscriptionPB(
workspaceId: workspaceId, workspaceId: workspaceId,
subscriptionPlan: SubscriptionPlanPB.None, subscriptionPlan: SubscriptionPlanPB.Free,
isActive: true, isActive: true,
); );
} }

View File

@ -56,7 +56,7 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
.firstWhereOrNull((i) => i.workspaceId == workspaceId) ?? .firstWhereOrNull((i) => i.workspaceId == workspaceId) ??
WorkspaceSubscriptionPB( WorkspaceSubscriptionPB(
workspaceId: workspaceId, workspaceId: workspaceId,
subscriptionPlan: SubscriptionPlanPB.None, subscriptionPlan: SubscriptionPlanPB.Free,
isActive: true, isActive: true,
), ),
(f) { (f) {

View File

@ -5,22 +5,24 @@ import 'package:easy_localization/easy_localization.dart';
extension SubscriptionLabels on WorkspaceSubscriptionPB { extension SubscriptionLabels on WorkspaceSubscriptionPB {
String get label => switch (subscriptionPlan) { String get label => switch (subscriptionPlan) {
SubscriptionPlanPB.None => SubscriptionPlanPB.Free =>
LocaleKeys.settings_planPage_planUsage_currentPlan_freeTitle.tr(), LocaleKeys.settings_planPage_planUsage_currentPlan_freeTitle.tr(),
SubscriptionPlanPB.Pro => SubscriptionPlanPB.Pro =>
LocaleKeys.settings_planPage_planUsage_currentPlan_proTitle.tr(), LocaleKeys.settings_planPage_planUsage_currentPlan_proTitle.tr(),
SubscriptionPlanPB.Team => SubscriptionPlanPB.Team =>
LocaleKeys.settings_planPage_planUsage_currentPlan_teamTitle.tr(), LocaleKeys.settings_planPage_planUsage_currentPlan_teamTitle.tr(),
// TODO(Mathias): Add AI Local and AI Max
_ => 'N/A', _ => 'N/A',
}; };
String get info => switch (subscriptionPlan) { String get info => switch (subscriptionPlan) {
SubscriptionPlanPB.None => SubscriptionPlanPB.Free =>
LocaleKeys.settings_planPage_planUsage_currentPlan_freeInfo.tr(), LocaleKeys.settings_planPage_planUsage_currentPlan_freeInfo.tr(),
SubscriptionPlanPB.Pro => SubscriptionPlanPB.Pro =>
LocaleKeys.settings_planPage_planUsage_currentPlan_proInfo.tr(), LocaleKeys.settings_planPage_planUsage_currentPlan_proInfo.tr(),
SubscriptionPlanPB.Team => SubscriptionPlanPB.Team =>
LocaleKeys.settings_planPage_planUsage_currentPlan_teamInfo.tr(), LocaleKeys.settings_planPage_planUsage_currentPlan_teamInfo.tr(),
// TODO(Mathias): Add AI Local and AI Max
_ => 'N/A', _ => 'N/A',
}; };
} }

View File

@ -0,0 +1,43 @@
import 'dart:async';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'sidebar_billing_bloc.freezed.dart';
class SidebarBillingBloc
extends Bloc<SidebarBillingEvent, SidebarBillingState> {
SidebarBillingBloc() : super(const SidebarBillingState()) {
on<SidebarBillingEvent>(_handleEvent);
}
Future<void> _handleEvent(
SidebarBillingEvent event,
Emitter<SidebarBillingState> emit,
) async {
await event.when(receiveError: (FlowyError error) {});
}
}
@freezed
class SidebarBillingEvent with _$SidebarBillingEvent {
const factory SidebarBillingEvent.receiveError(FlowyError error) =
_ReceiveError;
}
@freezed
class SidebarBillingState with _$SidebarBillingState {
const factory SidebarBillingState({
FlowyError? error,
@Default(SidebarBillingPageIndicator.loading())
SidebarBillingPageIndicator pageIndicator,
}) = _SidebarBillingState;
}
@freezed
class SidebarBillingPageIndicator with _$SidebarBillingPageIndicator {
// when start downloading the model
const factory SidebarBillingPageIndicator.upgradeTier() = _UpgradeTier;
const factory SidebarBillingPageIndicator.readyToUse() = _ReadyToUse;
const factory SidebarBillingPageIndicator.loading() = _Loading;
}

View File

@ -181,10 +181,10 @@ class _SettingsPlanComparisonDialogState
.tr(), .tr(),
cells: _freeLabels, cells: _freeLabels,
isCurrent: currentSubscription.subscriptionPlan == isCurrent: currentSubscription.subscriptionPlan ==
SubscriptionPlanPB.None, SubscriptionPlanPB.Free,
canDowngrade: canDowngrade:
currentSubscription.subscriptionPlan != currentSubscription.subscriptionPlan !=
SubscriptionPlanPB.None, SubscriptionPlanPB.Free,
currentCanceled: currentSubscription.hasCanceled || currentCanceled: currentSubscription.hasCanceled ||
(context (context
.watch<SettingsPlanBloc>() .watch<SettingsPlanBloc>()
@ -197,7 +197,7 @@ class _SettingsPlanComparisonDialogState
false), false),
onSelected: () async { onSelected: () async {
if (currentSubscription.subscriptionPlan == if (currentSubscription.subscriptionPlan ==
SubscriptionPlanPB.None || SubscriptionPlanPB.Free ||
currentSubscription.hasCanceled) { currentSubscription.hasCanceled) {
return; return;
} }
@ -242,7 +242,7 @@ class _SettingsPlanComparisonDialogState
isCurrent: currentSubscription.subscriptionPlan == isCurrent: currentSubscription.subscriptionPlan ==
SubscriptionPlanPB.Pro, SubscriptionPlanPB.Pro,
canUpgrade: currentSubscription.subscriptionPlan == canUpgrade: currentSubscription.subscriptionPlan ==
SubscriptionPlanPB.None, SubscriptionPlanPB.Free,
currentCanceled: currentSubscription.hasCanceled, currentCanceled: currentSubscription.hasCanceled,
onSelected: () => onSelected: () =>
context.read<SettingsPlanBloc>().add( context.read<SettingsPlanBloc>().add(

View File

@ -53,7 +53,7 @@ class StackTraceError {
class ErrorCodeNotifier extends ChangeNotifier { class ErrorCodeNotifier extends ChangeNotifier {
// Static instance // Static instance
static final ErrorCodeNotifier _instance = ErrorCodeNotifier._(); static final ErrorCodeNotifier _instance = ErrorCodeNotifier();
// Factory constructor to return the same instance // Factory constructor to return the same instance
factory ErrorCodeNotifier() { factory ErrorCodeNotifier() {

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::anyhow; use anyhow::anyhow;
use client_api::entity::billing_dto::{ use client_api::entity::billing_dto::{
SubscriptionCancelRequest, SubscriptionPlan, SubscriptionStatus, WorkspaceSubscriptionStatus, SubscriptionCancelRequest, SubscriptionPlan, WorkspaceSubscriptionStatus,
}; };
use client_api::entity::workspace_dto::{ use client_api::entity::workspace_dto::{
CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset, WorkspaceMemberInvitation, CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset, WorkspaceMemberInvitation,
@ -20,11 +20,7 @@ use tracing::instrument;
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_user_pub::cloud::{UserCloudService, UserCollabParams, UserUpdate, UserUpdateReceiver}; use flowy_user_pub::cloud::{UserCloudService, UserCollabParams, UserUpdate, UserUpdateReceiver};
use flowy_user_pub::entities::{ use flowy_user_pub::entities::{AFCloudOAuthParams, AuthResponse, RecurringInterval, Role, UpdateUserProfileParams, UserCredentials, UserProfile, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember, WorkspaceUsage};
AFCloudOAuthParams, AuthResponse, Role, UpdateUserProfileParams, UserCredentials, UserProfile,
UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember,
WorkspaceSubscription, WorkspaceUsage,
};
use lib_infra::box_any::BoxAny; use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use uuid::Uuid; use uuid::Uuid;
@ -476,20 +472,19 @@ where
fn subscribe_workspace( fn subscribe_workspace(
&self, &self,
workspace_id: String, workspace_id: String,
recurring_interval: flowy_user_pub::entities::RecurringInterval, recurring_interval: RecurringInterval,
workspace_subscription_plan: flowy_user_pub::entities::SubscriptionPlan, workspace_subscription_plan: SubscriptionPlan,
success_url: String, success_url: String,
) -> FutureResult<String, FlowyError> { ) -> FutureResult<String, FlowyError> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
let workspace_id = workspace_id.to_string(); let workspace_id = workspace_id.to_string();
FutureResult::new(async move { FutureResult::new(async move {
let subscription_plan = to_workspace_subscription_plan(workspace_subscription_plan)?;
let client = try_get_client?; let client = try_get_client?;
let payment_link = client let payment_link = client
.create_subscription( .create_subscription(
&workspace_id, &workspace_id,
to_recurring_interval(recurring_interval), recurring_interval,
subscription_plan, workspace_subscription_plan,
&success_url, &success_url,
) )
.await?; .await?;
@ -525,16 +520,13 @@ where
}) })
} }
fn get_workspace_subscriptions(&self) -> FutureResult<Vec<WorkspaceSubscription>, FlowyError> { fn get_workspace_subscriptions(&self) -> FutureResult<Vec<WorkspaceSubscriptionStatus>, FlowyError> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let client = try_get_client?; let client = try_get_client?;
let workspace_subscriptions = client let workspace_subscriptions = client
.list_subscription() .list_subscription()
.await? .await?;
.into_iter()
.map(to_workspace_subscription)
.collect();
Ok(workspace_subscriptions) Ok(workspace_subscriptions)
}) })
} }
@ -701,46 +693,3 @@ fn oauth_params_from_box_any(any: BoxAny) -> Result<AFCloudOAuthParams, FlowyErr
sign_in_url: sign_in_url.to_string(), sign_in_url: sign_in_url.to_string(),
}) })
} }
fn to_recurring_interval(
r: flowy_user_pub::entities::RecurringInterval,
) -> client_api::entity::billing_dto::RecurringInterval {
match r {
flowy_user_pub::entities::RecurringInterval::Month => {
client_api::entity::billing_dto::RecurringInterval::Month
},
flowy_user_pub::entities::RecurringInterval::Year => {
client_api::entity::billing_dto::RecurringInterval::Year
},
}
}
fn to_workspace_subscription_plan(
s: flowy_user_pub::entities::SubscriptionPlan,
) -> Result<SubscriptionPlan, FlowyError> {
match s {
flowy_user_pub::entities::SubscriptionPlan::Pro => Ok(SubscriptionPlan::Pro),
flowy_user_pub::entities::SubscriptionPlan::Team => Ok(SubscriptionPlan::Team),
flowy_user_pub::entities::SubscriptionPlan::None => Err(FlowyError::new(
ErrorCode::InvalidParams,
"Invalid subscription plan",
)),
}
}
fn to_workspace_subscription(s: WorkspaceSubscriptionStatus) -> WorkspaceSubscription {
WorkspaceSubscription {
workspace_id: s.workspace_id,
subscription_plan: flowy_user_pub::entities::SubscriptionPlan::None,
recurring_interval: match s.recurring_interval {
client_api::entity::billing_dto::RecurringInterval::Month => {
flowy_user_pub::entities::RecurringInterval::Month
},
client_api::entity::billing_dto::RecurringInterval::Year => {
flowy_user_pub::entities::RecurringInterval::Year
},
},
is_active: matches!(s.subscription_status, SubscriptionStatus::Active),
canceled_at: s.cancel_at,
}
}

View File

@ -128,7 +128,7 @@ impl FileUploader {
} => { } => {
let record = BoxAny::new(record); let record = BoxAny::new(record);
if let Err(err) = self.storage_service.start_upload(&chunks, &record).await { if let Err(err) = self.storage_service.start_upload(&chunks, &record).await {
if (err.is_file_limit_exceeded()) { if err.is_file_limit_exceeded() {
error!("Failed to upload file: {}", err); error!("Failed to upload file: {}", err);
self.pause(); self.pause();
} }
@ -159,7 +159,7 @@ impl FileUploader {
.resume_upload(&workspace_id, &parent_dir, &file_id) .resume_upload(&workspace_id, &parent_dir, &file_id)
.await .await
{ {
if (err.is_file_limit_exceeded()) { if err.is_file_limit_exceeded() {
error!("Failed to upload file: {}", err); error!("Failed to upload file: {}", err);
self.pause(); self.pause();
} }

View File

@ -9,13 +9,14 @@ use std::collections::HashMap;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
pub use client_api::entity::billing_dto::{ WorkspaceSubscriptionStatus, SubscriptionStatus, SubscriptionPlan} ;
use tokio_stream::wrappers::WatchStream; use tokio_stream::wrappers::WatchStream;
use uuid::Uuid; use uuid::Uuid;
use crate::entities::{ use crate::entities::{
AuthResponse, Authenticator, RecurringInterval, Role, SubscriptionPlan, UpdateUserProfileParams, AuthResponse, Authenticator, RecurringInterval, Role, UpdateUserProfileParams,
UserCredentials, UserProfile, UserTokenState, UserWorkspace, WorkspaceInvitation, UserCredentials, UserProfile, UserTokenState, UserWorkspace, WorkspaceInvitation,
WorkspaceInvitationStatus, WorkspaceMember, WorkspaceSubscription, WorkspaceUsage, WorkspaceInvitationStatus, WorkspaceMember, WorkspaceUsage,
}; };
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -281,7 +282,7 @@ pub trait UserCloudService: Send + Sync + 'static {
FutureResult::new(async { Err(FlowyError::not_support()) }) FutureResult::new(async { Err(FlowyError::not_support()) })
} }
fn get_workspace_subscriptions(&self) -> FutureResult<Vec<WorkspaceSubscription>, FlowyError> { fn get_workspace_subscriptions(&self) -> FutureResult<Vec<WorkspaceSubscriptionStatus>, FlowyError> {
FutureResult::new(async { Err(FlowyError::not_support()) }) FutureResult::new(async { Err(FlowyError::not_support()) })
} }

View File

@ -1,6 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
pub use client_api::entity::billing_dto::RecurringInterval;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use serde_repr::*; use serde_repr::*;
@ -453,25 +454,6 @@ pub struct WorkspaceInvitation {
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
} }
pub enum RecurringInterval {
Month,
Year,
}
pub enum SubscriptionPlan {
None,
Pro,
Team,
}
pub struct WorkspaceSubscription {
pub workspace_id: String,
pub subscription_plan: SubscriptionPlan,
pub recurring_interval: RecurringInterval,
pub is_active: bool,
pub canceled_at: Option<i64>,
}
pub struct WorkspaceUsage { pub struct WorkspaceUsage {
pub member_count: usize, pub member_count: usize,
pub member_count_limit: usize, pub member_count_limit: usize,

View File

@ -2,10 +2,9 @@ use std::str::FromStr;
use validator::Validate; use validator::Validate;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_user_pub::cloud::{AFWorkspaceSettings, AFWorkspaceSettingsChange}; use flowy_user_pub::cloud::{AFWorkspaceSettings, AFWorkspaceSettingsChange, SubscriptionPlan, SubscriptionStatus, WorkspaceSubscriptionStatus};
use flowy_user_pub::entities::{ use flowy_user_pub::entities::{
RecurringInterval, Role, SubscriptionPlan, WorkspaceInvitation, WorkspaceMember, RecurringInterval, Role, WorkspaceInvitation, WorkspaceMember,
WorkspaceSubscription,
}; };
use lib_infra::validator_fn::required_not_empty_str; use lib_infra::validator_fn::required_not_empty_str;
@ -258,17 +257,22 @@ impl From<RecurringInterval> for RecurringIntervalPB {
#[derive(ProtoBuf_Enum, Clone, Default, Debug)] #[derive(ProtoBuf_Enum, Clone, Default, Debug)]
pub enum SubscriptionPlanPB { pub enum SubscriptionPlanPB {
#[default] #[default]
None = 0, Free = 0,
Pro = 1, Pro = 1,
Team = 2, Team = 2,
AIMax= 3,
AILocal= 4,
} }
impl From<SubscriptionPlanPB> for SubscriptionPlan { impl From<SubscriptionPlanPB> for SubscriptionPlan {
fn from(value: SubscriptionPlanPB) -> Self { fn from(value: SubscriptionPlanPB) -> Self {
match value { match value {
SubscriptionPlanPB::Pro => SubscriptionPlan::Pro, SubscriptionPlanPB::Free => SubscriptionPlan::Free,
SubscriptionPlanPB::Team => SubscriptionPlan::Team, SubscriptionPlanPB::Pro => SubscriptionPlan::Pro,
SubscriptionPlanPB::None => SubscriptionPlan::None, SubscriptionPlanPB::Team => SubscriptionPlan::Team,
SubscriptionPlanPB::AIMax => SubscriptionPlan::AiMax,
SubscriptionPlanPB::AILocal => SubscriptionPlan::AiLocal,
} }
} }
} }
@ -276,9 +280,11 @@ impl From<SubscriptionPlanPB> for SubscriptionPlan {
impl From<SubscriptionPlan> for SubscriptionPlanPB { impl From<SubscriptionPlan> for SubscriptionPlanPB {
fn from(value: SubscriptionPlan) -> Self { fn from(value: SubscriptionPlan) -> Self {
match value { match value {
SubscriptionPlan::Pro => SubscriptionPlanPB::Pro, SubscriptionPlan::Free => SubscriptionPlanPB::Free,
SubscriptionPlan::Team => SubscriptionPlanPB::Team, SubscriptionPlan::Pro => SubscriptionPlanPB::Pro,
SubscriptionPlan::None => SubscriptionPlanPB::None, SubscriptionPlan::Team => SubscriptionPlanPB::Team,
SubscriptionPlan::AiMax => SubscriptionPlanPB::AIMax,
SubscriptionPlan::AiLocal => SubscriptionPlanPB::AILocal,
} }
} }
} }
@ -316,15 +322,15 @@ pub struct WorkspaceSubscriptionPB {
pub canceled_at: i64, // value is valid only if has_canceled is true pub canceled_at: i64, // value is valid only if has_canceled is true
} }
impl From<WorkspaceSubscription> for WorkspaceSubscriptionPB { impl From<WorkspaceSubscriptionStatus> for WorkspaceSubscriptionPB {
fn from(s: WorkspaceSubscription) -> Self { fn from(s: WorkspaceSubscriptionStatus) -> Self {
Self { Self {
workspace_id: s.workspace_id, workspace_id: s.workspace_id,
subscription_plan: s.subscription_plan.into(), subscription_plan: s.workspace_plan.into(),
recurring_interval: s.recurring_interval.into(), recurring_interval: s.recurring_interval.into(),
is_active: s.is_active, is_active: matches!(s.subscription_status, SubscriptionStatus::Active),
has_canceled: s.canceled_at.is_some(), has_canceled: s.cancel_at.is_some(),
canceled_at: s.canceled_at.unwrap_or_default(), canceled_at: s.cancel_at.unwrap_or_default(),
} }
} }
} }

View File

@ -10,9 +10,10 @@ use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_folder_pub::entities::{AppFlowyData, ImportData}; use flowy_folder_pub::entities::{AppFlowyData, ImportData};
use flowy_sqlite::schema::user_workspace_table; use flowy_sqlite::schema::user_workspace_table;
use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods}; use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
use flowy_user_pub::cloud::WorkspaceSubscriptionStatus;
use flowy_user_pub::entities::{ use flowy_user_pub::entities::{
Role, UpdateUserProfileParams, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, Role, UpdateUserProfileParams, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus,
WorkspaceMember, WorkspaceSubscription, WorkspaceUsage, WorkspaceMember, WorkspaceUsage
}; };
use lib_dispatch::prelude::af_spawn; use lib_dispatch::prelude::af_spawn;
@ -446,7 +447,7 @@ impl UserManager {
} }
#[instrument(level = "info", skip(self), err)] #[instrument(level = "info", skip(self), err)]
pub async fn get_workspace_subscriptions(&self) -> FlowyResult<Vec<WorkspaceSubscription>> { pub async fn get_workspace_subscriptions(&self) -> FlowyResult<Vec<WorkspaceSubscriptionStatus>> {
let res = self let res = self
.cloud_services .cloud_services
.get_user_service()? .get_user_service()?