-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PM-14366] Deprecated active user state from billing state service #12273
base: main
Are you sure you want to change the base?
[PM-14366] Deprecated active user state from billing state service #12273
Conversation
No New Or Fixed Issues Found |
Co-authored-by: ✨ Audrey ✨ <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just added a few suggestions and pointed out some failing tests. Thanks for working on this
apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts
Outdated
Show resolved
Hide resolved
apps/cli/src/vault/create.command.ts
Outdated
cipher.organizationId == null && | ||
!(await firstValueFrom(this.accountProfileService.hasPremiumFromAnySource$)) | ||
) { | ||
const account = await firstValueFrom(this.accountService.activeAccount$); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can delete this and move the activeUserId
declared on L155 out of the try scope
...c/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts
Outdated
Show resolved
Hide resolved
…e-provider # Conflicts: # apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻 Tools code LGTM!
🤔 I wonder whether there's places that of
will fail because it completes. Where firstValueFrom
or combineLatest
is used it shouldn't cause any issues since those keep the "singularity" of the last (only) emitted value. If there are places that expect the observable to change value instead of completing, then there could be a lot of non-responsive UI.
I guess what I'm saying is, please run the regression suite on this before merge, if we can, since this PR affects so much.
private accountService: AccountService, | ||
) { | ||
this.showSubscription$ = this.accountService.activeAccount$.pipe( | ||
switchMap((account) => | ||
this.billingAccountProfileStateService.canViewSubscription$(account.id), | ||
), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better, thank you! Any reason to not move the AccountService
dependency inside the service as well? Such that we could do:
this.showSubscription$ = this.billingAccountProfileStateService.activeAccountCanViewSubscription$;
or, the active account could be the default if no account ID is provided:
this.showSubscription$ = this.billingAccountProfileStateService.canViewSubscription()$;
There might be reasons to not do this! Curious what you think @cturnbull-bitwarden
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good question! I decided to keep BillingAccountProfileStateService
independent of AccountService
for a couple of reasons:
- Simply speaking, I wanted to maintain a clear dependency direction and avoid introducing
AccountService
as a hard dependency - My thinking for
BillingAccountProfileStateService
was that it should have a single responsibility - managing the state of billing's premium flags using theStateProvider
pattern
That said, after implementing this, I've still had to map AccountService.activeAccount$
in a bunch places to avoid using ActiveUserStateProvider
. I'm open to revisiting this design in a follow-up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid introducing AccountService as a hard dependency
🎨
IMO, AccountService
is already a hard dependency of the service as written. Consumers can't use the methods unless they have an account ID (often, the active account ID). This is evident by the number of times we needed to import AccountService
in the PR. My gut says: if we always need to import a peer dep, maybe that dep should be brought into the service.
Larger questions at play here: when is it appropriate to push dependencies onto the consumer of a service? Should services that act on accounts provide convenience methods that target the active account, since it is most often interacted with? Gonna share in Slack to see if others have strong opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auth changes look good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some non-blocking ⛏️'s. Otherwise looks good!
switchMap((account) => | ||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), | ||
), | ||
takeUntil(this.componentIsDestroyed$), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non blocking ⛏️ this pattern for unsubscribing is outdated and we should favor using takeUntilDestroyed()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion! I created a tech debt task here for us to handle this in a follow-up PR
switchMap((account) => | ||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), | ||
), | ||
takeUntil(this.componentIsDestroyed$), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same ⛏️ here
? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) | ||
: of(false), | ||
), | ||
takeUntil(this.directiveIsDestroyed$), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same ⛏️ here too.
switchMap((account) => | ||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), | ||
), | ||
takeUntil(this.destroy$), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same ⛏️ here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes look good. Thanks.
switchMap((account) => | ||
account | ||
? billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) | ||
: of(false), | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to do this on this PR but I worry that some of the routes might navigate to a route protected with this right after logging in. Since active user state wouldn't emit until an account is set as active it would have delayed until that happened. I think we should give it a grace period to allow the account to come in and if it doesn't happen in a reasonable time throw an error because I think it would likely be a developer error.
switchMap((account) => | |
account | |
? billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) | |
: of(false), | |
), | |
filter((account) => account != null), | |
timeout({ | |
first: 2000, | |
with: () => { | |
throw new Error("An active user is expected when checking if a user has premium."); | |
}, | |
}), | |
switchMap((account) => | |
billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), | |
), |
Please don't worry about doing this here though, you've got a lot of approvals and I can throw up a small PR doing it after you merge.
const hasPremium = await firstValueFrom( | ||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oo, moving this out of the loop was a good idea; thanks 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved for Autofill concerns
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-14366
📔 Objective
This PR updates the
BillingAccountProfileStateService
to remove its dependency onActiveUserStateProvider
by requiring explicit user IDs when checking premium status. This change:userId
parameter instead of relying on the active user stateAccountService.activeAccount$
, often via aswitchMap
, or otherwise,firstValueFrom
📸 Screenshots
⏰ Reminders before review
🦮 Reviewer guidelines
:+1:
) or similar for great changes:memo:
) or ℹ️ (:information_source:
) for notes or general info:question:
) for questions:thinking:
) or 💭 (:thought_balloon:
) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion:art:
) for suggestions / improvements:x:
) or:warning:
) for more significant problems or concerns needing attention:seedling:
) or ♻️ (:recycle:
) for future improvements or indications of technical debt:pick:
) for minor or nitpick changes