Skip to content
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-11901] Refactoring self-hosting license file uploader #11083

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

jonashendrickx
Copy link
Member

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-11901

📔 Objective

Deduplicate the code where self-hosting licenses are being uploaded for organizations. Some additional work was done for individual licenses.

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 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

Copy link

codecov bot commented Sep 16, 2024

Codecov Report

Attention: Patch coverage is 24.77876% with 85 lines in your changes missing coverage. Please review.

Project coverage is 35.07%. Comparing base (651f1e5) to head (43cb011).
Report is 33 commits behind head on main.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ization-self-hosting-license-uploader.component.ts 29.54% 23 Missing and 8 partials ⚠️
...bstract-self-hosting-license-uploader.component.ts 9.52% 19 Missing ⚠️
...ividual-self-hosting-license-uploader.component.ts 35.71% 12 Missing and 6 partials ⚠️
...ling/organizations/organization-plans.component.ts 0.00% 9 Missing ⚠️
...billing/individual/premium/premium-v2.component.ts 0.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11083      +/-   ##
==========================================
+ Coverage   35.01%   35.07%   +0.06%     
==========================================
  Files        2698     2712      +14     
  Lines       84179    84642     +463     
  Branches    16004    16088      +84     
==========================================
+ Hits        29476    29689     +213     
- Misses      53734    53968     +234     
- Partials      969      985      +16     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

github-actions bot commented Sep 16, 2024

Logo
Checkmarx One – Scan Summary & Detailsdde6260a-7b69-4b77-8e54-4631e0b9c99f

New Issues

Severity Issue Source File / Package Checkmarx Insight
MEDIUM Unpinned Actions Full Length Commit SHA /build-desktop.yml: 1296 Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps...
MEDIUM Unpinned Actions Full Length Commit SHA /build-desktop.yml: 1342 Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps...

Fixed Issues

Severity Issue Source File / Package
MEDIUM Unpinned Actions Full Length Commit SHA /build-desktop.yml: 1328
MEDIUM Unpinned Actions Full Length Commit SHA /build-desktop.yml: 1282

@jonashendrickx jonashendrickx changed the title initial commit for individual plans [PM-11901] Refactoring self-hosting license file uploader Sep 16, 2024
@jonashendrickx jonashendrickx marked this pull request as ready for review September 17, 2024 09:11
@jonashendrickx jonashendrickx requested a review from a team as a code owner September 17, 2024 09:11
jonashendrickx and others added 4 commits September 17, 2024 11:11
…e-from-organization-plans-change-plan-dialog-components
…sting-license-from-organization-plans-change-plan-dialog-components' into PM-11901-Refactor-Upload-self-hosting-license-from-organization-plans-change-plan-dialog-components
Copy link
Contributor

@amorask-bitwarden amorask-bitwarden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great first pass! Please fix up the comments marked with ❌ and let me know what you think about the ⛏️ s.

@@ -0,0 +1,3 @@
export class LicenseUploadedEvent {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ Since this only encapsulates an organizationId, I'd argue could probably just do away with it to reduce our file count. The naming of the event emitter itself already clarifies what event your listening for in the parent component.

// Uploader .ts
@Output() onLicenseFileUploaded = new EventEmitter<string>();

// Parent .ts
protected onLicenseFileUploaded = async (organizationId: string) => {}

Copy link
Member Author

@jonashendrickx jonashendrickx Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally like the verbosity, so if the code needs to be extended it would be easier, or simply easier to read if you want to know what the string is for, but I will remove it.

@@ -0,0 +1,3 @@
export interface LicenseUploaderFormModel {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ This can also probably be removed since it's a single property encapsulation.

If you want to access the value of the formGroup's file, you can use this.form.value.file. The derived uploader classes will have access to it since it's a protected property.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amorask-bitwarden Don't we want type safety for certain things?

this.form = this.formBuilder.group({
file: [null, [Validators.required]],
});
this.submit = this.submit.bind(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is this necessary here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't get it to work without it.

* Shared implementation for processing license file uploads.
* @remarks Requires self-hosting.
*/
export abstract class AbstractSelfHostingLicenseUploaderComponent {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ I'll let you ultimately decide this because it's only a matter of style, but a more functional approach would be to combine all 3 of the license uploading components and then pass in what you need as inputs.

For example:

export class SelfHostLicenseUploaderComponent {
    @Input({ required: true }) private description: string;
    @Input({ required: true }) private hintFileName: string;
    @Input({ required: true }) private onSubmit: File => Promise<void>;
}

The onSubmit input can be passed in from the parent to tell the license uploader what specifically we want to do with the File if the existing validation passes.

submit = async () => {
  this.form.markAllAsTouched();

  if (this.form.invalid) {
    return this.toastService.showToast({
      variant: "error",
      title: this.i18nService.t("errorOccurred"),
      message: this.i18nService.t("selectFile"),
    });
  }

  const emailVerified = await this.tokenService.getEmailVerified();
  if (!emailVerified) {
    return this.toastService.showToast({
      variant: "error",
      title: this.i18nService.t("errorOccurred"),
      message: this.i18nService.t("verifyEmailFirst"),
    });
  }

  await this.onSubmit(this.form.value.file);

  // You can either include the necessary post-processing in the `onSubmit` or emit an event to handle it in the parent.
}

Example of this approach in the manage-tax-information.component.

libs/common/src/enums/feature-flag.enum.ts Outdated Show resolved Hide resolved
finalizeUpgrade = async () => {
await this.apiService.refreshIdentityToken();
await this.syncService.fullSync(true);
};

postFinalizeUpgrade = async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ What was the purpose of splitting finalizeUpgrade into two methods?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bit that remains in the finalizeUpgrade was already executed by the license uploader component. Because I figured it might always be required to run. And then it executes the remainder here in the postFinalization method.

@@ -75,9 +80,18 @@ export class PremiumV2Component {
.subscribe();
}

async ngOnInit(): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngOnInit is not necessary here. You can grab an Observable of the feature flag using:

// Properties
protected useLicenseUploaderComponent$: Observable<boolean>;

// Constructor
this.useLicenseUploaderComponent$ = this.configService.getFeatureFlag$(FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader);

Once you have an observable, in your HTML you can subscribe to it using then async pipe:

<individual-self-hosting-license-uploader
    *ngIf="useLicenseUploaderComponent | async"
    (onLicenseFileUploaded)="(onLicenseFileSelectedChanged)"
  />

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants