Skip to content

Conversation

@JanCizmar
Copy link
Contributor

@JanCizmar JanCizmar commented Feb 25, 2025

Summary by CodeRabbit

Release Notes

  • Billing and Usage

    • Introduced new usage tracking for keys and seats
    • Added support for pay-as-you-go and fixed subscription models
    • Enhanced usage limits and reporting mechanisms
  • Subscription Management

    • Improved self-hosted license key management
    • Added more granular tracking of usage metrics
    • Implemented new limits for keys, seats, and translations
  • User Experience

    • Updated billing and usage popover interfaces
    • Refined error handling for subscription and usage limits
    • Improved internationalization of billing-related messages
  • Administration

    • Streamlined plan creation and management
    • Added more detailed subscription information
    • Enhanced reporting and usage tracking capabilities
  • Performance

    • Optimized caching and error handling strategies
    • Improved database query efficiency
    • Refined transaction management

@JanCizmar JanCizmar changed the title feat: Add keys as metric > prepared entities & other parts of backend feat: Add keys/strings as a metric Feb 25, 2025
@JanCizmar JanCizmar force-pushed the jancizmar/add-keys-as-metric branch 2 times, most recently from 096d9fb to ac4afd9 Compare March 7, 2025 11:03
@JanCizmar JanCizmar force-pushed the jancizmar/add-keys-as-metric branch from 64849b6 to 0a2939d Compare March 15, 2025 10:46
@JanCizmar JanCizmar force-pushed the jancizmar/add-keys-as-metric branch from 8facb49 to 7162292 Compare March 31, 2025 12:18
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 31, 2025

Important

Review skipped

More than 25% of the files skipped due to max files limit. The review is being skipped to prevent a low-quality review.

190 files out of 297 files are above the max files limit of 100. Please upgrade to Pro plan to get higher limits.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces significant changes across multiple components of the Tolgee application, focusing on enhancing usage tracking, billing management, and subscription handling. The modifications span backend services, frontend components, and testing infrastructure. Key areas of improvement include introducing more granular usage metrics (keys, seats), refactoring subscription and licensing management, and updating error handling mechanisms.

Changes

File/Component Change Summary
.run/Frontend localhost.run.xml The path to the package.json file has been updated from "$PROJECT_DIR$/webapp/package.json" to "$PROJECT_DIR$/public/webapp/package.json". Additionally, the closing tag for the <component> element has been moved to a new line.
OrganizationController.kt The import statement for TranslationsLimitProvider has been replaced with LimitsProvider. The constructor parameter translationsLimitProvider has been renamed to limitsProvider. The removeUser method has had its parameter order reversed, and the getUsage method has been significantly modified to use the LimitsProvider and OrganizationStatsService for retrieving and calculating usage metrics.
ProjectsController.kt The order of parameters in the revokePermission method has been modified.
PlanIncludedUsageModel.kt The translationSlots property has been removed, and a new keys property has been added.
PlanPricesModel.kt A new perThousandKeys property of type BigDecimal has been added.
SelfHostedEePlanModel.kt A new isPayAsYouGo property of type Boolean has been added to the constructor.
SelfHostedEeSubscriptionModel.kt A new limits property of type SelfHostedUsageLimitsModel has been added, and the visibility of the plan property has been changed to open.
PublicUsageModel.kt New properties isPayAsYouGo, usedMtCredits, includedKeys, currentKeys, keysLimit, includedSeats, currentSeats, and seatsLimit have been added, while translationSlotsLimit, includedTranslationSlots, and currentTranslationSlots have been removed.
CloudSubscriptionPlanType.kt The SLOTS_FIXED plan has been removed from the enum.
MetricType.kt A new enum class MetricType has been introduced, with properties useSeats, useKeys, and useStrings.
PublicCloudPlanModel.kt A new metricType property has been added.
ActivityDatabaseInterceptor.kt The onFlushDirty method has been updated to include previousState and propertyNames parameters.
PreCommitEventPublisher.kt The onUpdate method has been modified to accept previousState and propertyNames parameters.
GenericAutoTranslationChunkProcessor.kt The exception handling logic has been updated to use PlanLimitExceededStringsException and PlanSpendingLimitExceededStringsException instead of the previous exceptions.
LanguageStatsListener.kt A new dependency on ProjectService has been added to check for the existence of a project before processing the event.
BaseLimitsProvider.kt A new BaseLimitsProvider class has been introduced to provide default usage limits.
BaseTranslationsLimitProvider.kt This class has been removed entirely.
LimitsProvider.kt A new LimitsProvider interface has been introduced to define the contract for retrieving usage limits.
TranslationsLimitProvider.kt This interface has been removed.
Message.kt Five new constants have been added to the Message enum: STRINGS_METRIC_ARE_NOT_SUPPORTED, PLAN_KEY_LIMIT_EXCEEDED, KEYS_SPENDING_LIMIT_EXCEEDED, PLAN_SEAT_LIMIT_EXCEEDED, and INSTANCE_NOT_USING_LICENSE_KEY.
AdditionalTestDataSaver.kt New methods beforeSave, afterSave, beforeClean, and afterClean have been added to the interface.
TestDataService.kt New methods have been added to manage the execution of additional savers during the test data saving and cleaning processes.
InvitationBuilder.kt A new InvitationBuilder class has been introduced to create Invitation instances.
OrganizationBuilder.kt A new inviteUser method has been added to create invitations for an organization.
ProjectBuilder.kt A new inviteUser method has been added to create invitations for a project.
TestDataBuilder.kt A new invitations list has been added to the DATA class.
MtCreditBalanceDto.kt Three new properties usedCredits, creditBalance (with a comment), and bucketSize have been added.
UsageLimits.kt A new UsageLimits data class has been introduced to represent usage limits, including properties for pay-as-you-go, trial status, and limits for strings, keys, seats, and MT credits.
OnEntityPreUpdate.kt Two new properties previousState and propertyNames have been added to the OnEntityPreUpdate class.
OnOrganizationNameUpdated.kt A new OnOrganizationNameUpdated class has been introduced to encapsulate organization name update events.
OnProjectSoftDeleted.kt A new OnProjectSoftDeleted class has been introduced to encapsulate project soft deletion events.
Permission.kt The userId and invitationId properties have been removed, and the PermissionListeners companion object has been annotated with @Configurable.
Project.kt The deletedAt property has been annotated with @ActivityLoggedProp, and the getLanguage method has been removed.
KeyRepository.kt A new countAllOnInstance method has been added to count all keys associated with non-deleted projects and organizations.
OrganizationRepository.kt Two new methods, getAllUsersOrganizationsToCountUsageFor and getAllUserIdsInOrganizationToCountSeats, have been added to facilitate seat usage counting.
ProjectRepository.kt A new findDeleted method has been added to retrieve deleted projects.
InvitationService.kt A new save method has been added, and the removeExpired method has been modified to use the CurrentDateProvider.
KeyService.kt The deleteAllByProject method has been updated to use a JPQL query instead of a native SQL query, and a new countAllOnInstance method has been added.
UserAccountService.kt The findActive method has been modified to remove the @Transactional annotation, and the findDto method has been updated to call findActive instead of the repository directly.
tryUntilItDoesntBreakContraint.kt The tryUntilItDoesntBreakConstraint function has been refactored to use a new tryUntil helper function, and a new RepeatedlyThrowingException class has been introduced.
CleanDbTestListener.kt The exception handling logic has been updated to include BatchUpdateException.
SsoOrganizationsTest.kt The @Transactional annotation has been removed from two test methods, and a new transaction execution context has been introduced in one of the tests.
waitFor.kt The waitForNotThrowing function has been modified to return the result of the provided function instead of just executing it.
EeSubscriptionDto.kt Five new properties have been added: includedKeys, includedSeats, isPayAsYouGo, keysLimit, and seatsLimit.
Organization.kt The Organization class now implements the EntityWithId interface, and a new disableActivityLogging property has been added.
entityPreCommitEventUsageUtil.kt A new getUsageIncreaseAmount extension function has been introduced to determine the change in usage amount based on the type of EntityPreCommitEvent.
EeKeyCountLimitListener.kt A new EeKeyCountLimitListener class has been introduced to monitor key count changes and enforce key limits.
EeKeyCountReportingListener.kt A new EeKeyCountReportingListener class has been introduced to monitor key count changes and report usage to the Tolgee Cloud.
EeOnUserCountChangedListener.kt A new EeOnUserCountChangedListener class has been introduced to handle user count changes and report usage.
FreePlanSeatLimitTest.kt The package name and class name have been updated to reflect the specific focus on free plan seat limits.
KeyCountLimitTest.kt A new test class has been introduced to validate key count limit behavior.
KeyUsageReportingTest.kt A new test class has been introduced to validate key usage reporting functionality.
SeatUsageReportingTest.kt The package name and class name have been updated, and the test methods have been refactored.
InitialDataController.kt References to EeSubscriptionProvider and IEeSubscriptionModelAssembler have been removed.
InitialDataModel.kt The eeSubscription property has been removed.
EeSubscriptionModel.kt A documentation comment has been added to clarify the purpose of the model.
LimitModel.kt A new LimitModel class has been introduced to represent usage limits.
SelfHostedUsageLimitsModel.kt A new SelfHostedUsageLimitsModel class has been introduced to represent usage limits for self-hosted instances.
PlanLimitExceededKeysException.kt A new PlanLimitExceededKeysException class has been introduced to handle key limit exceeded scenarios.
PlanLimitExceededSeatsException.kt A new PlanLimitExceededSeatsException class has been introduced to handle seat limit exceeded scenarios.
PlanLimitExceededStringsException.kt A new PlanLimitExceededStringsException class has been introduced to handle translation limit exceeded scenarios.
PlanSpendingLimitExceededKeysException.kt A new PlanSpendingLimitExceededKeysException class has been introduced to handle key spending limit exceeded scenarios.
PlanSpendingLimitExceededSeatsException.kt A new PlanSpendingLimitExceededSeatsException class has been introduced to handle seat spending limit exceeded scenarios.
PlanSpendingLimitExceededStringsException.kt A new PlanSpendingLimitExceededStringsException class has been introduced to handle translation spending limit exceeded scenarios.
SignUpProcessor.kt A new userSource parameter has been added to the constructor.
SelfHostedKeysSeatsLimitsChecker.kt A new SelfHostedKeysSeatsLimitsChecker class has been introduced to verify keys and seats limits for self-hosted instances.
SelfHostedLimitsProvider.kt A new SelfHostedLimitsProvider class has been introduced to provide usage limits for self-hosted instances.
GenericLimitChecker.kt A new GenericLimitChecker class has been introduced to enforce usage limits based on the payment model.
KeysLimitChecker.kt A new KeysLimitChecker class has been introduced to handle key usage limit checks.
SeatsLimitChecker.kt A new SeatsLimitChecker class has been introduced to handle seat usage limit checks.
PrepareSetLicenseKeyDto.kt A new keys property has been added to the PrepareSetLicenseKeyDto class.
SelfHostedEePlanModelTest.kt The package name has been updated to reflect the change in package structure.
StringsHint.tsx Two new functional components, KeysHint and SeatsHint, have been added.
PaginatedHateoasList.tsx A new PaginatedHateoasListProps type has been introduced to encapsulate the props for the PaginatedHateoasList component.
SimpleList.tsx The OverridableListWrappers type definition has been updated to include default types for the generic parameters.
PaginatedHateoasTable.tsx A new PaginatedHateoasTable component has been introduced to handle paginated HATEOAS data in a tabular format.
AcceptInvitationView.tsx New error handling logic has been added to display specific error messages based on the error code during invitation acceptance.
GlobalValidationSchema.tsx The validation rules for perThousandMtCredits and perThousandTranslations have been updated to allow zero as a valid input, and new properties perSeat and perThousandKeys have been added.
InvoiceUsage.tsx A data-cy attribute has been added to the Dialog component.
PlansCloudList.tsx The isActive function has been simplified by focusing on the plan ID and subscription status.
useCloudPlans.tsx New properties metricType and includedUsage have been added to the enterprise plan object.
PlansSelfHostedList.tsx A new SelfHostedEePlanModel type has been introduced, and a new includedUsage property has been added to the enterprise plan object.
SelfHostedEeActiveSubscription.tsx The isLegacy property has been removed, and a new metricType property has been added.
SelfHostedPlanAction.tsx The import path for PlanType has been updated, and a data-cy attribute has been changed.
AdministrationEeLicenseView.tsx The import paths for three components have been updated.
CloudPlanForm.tsx, CloudPlanOrganizations.tsx, EePlanForm.tsx, EePlanOrganizations.tsx These files have been removed entirely.
CloudPlanFields.tsx A new CloudPlanFields component has been introduced.
CloudPlanMetricTypeSelectField.tsx A new CloudPlanMetricTypeSelectField component has been introduced.
CloudPlanPricesAndLimits.tsx A new CloudPlanPricesAndLimits component has been introduced.
CloudPlanSelector.tsx A new CloudPlanSelector component has been introduced.
CloudPlanTemplateSelectorField.tsx The PlanTemplateSelector component has been renamed to CloudPlanTemplateSelectorField.
CloudPlanTypeSelectField.tsx A new CloudPlanTypeSelectField component has been introduced.
getCloudPlanInitialValues.ts The perThousandKeys property has been added to the returned object, and the includedUsage object has been simplified.
types.ts New types and interfaces have been introduced to manage cloud plan data.
usePlanFormValues.ts A new usePlanFormValues hook has been added.
AdministrationCloudPlanCreateView.tsx The CreateCloudPlanForm component has been replaced with CloudPlanCreateForm.
AdministrationCloudPlanEditView.tsx The EditCloudPlanForm component has been replaced with CloudPlanEditForm.
AdministrationCloudPlansView.tsx The import path for
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@JanCizmar JanCizmar force-pushed the jancizmar/add-keys-as-metric branch from 58e703c to 9cd5015 Compare April 3, 2025 09:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

🔭 Outside diff range comments (2)
e2e/cypress/e2e/translations/namespaces.cy.ts (1)

67-81: 💡 Verification agent

🧩 Analysis chain

Potential issue: Missing import for assertFilter

The code is using assertFilter function in test cases, but the import for this function appears to be missing. According to the AI summary, the import for assertFilter from '../../common/filters' has been removed.

Please verify that the assertFilter function is still accessible in this file. You'll need to either:

  1. Restore the import statement, or
  2. Replace the function calls with an alternative implementation

🏁 Script executed:

#!/bin/bash
# Check if assertFilter is imported elsewhere or defined in this file
rg "import.*assertFilter" e2e/cypress/e2e/translations/
rg "function assertFilter" e2e/cypress/e2e/translations/

Length of output: 457


Action Required: Add missing import for assertFilter in namespaces.cy.ts

Based on our verification, similar test files (e.g., translationFilters.tags.cy.ts, translationFilters.states.cy.ts, and translationFilters.general.cy.ts) explicitly import assertFilter from '../../common/filters', while namespaces.cy.ts does not. Please add the following import statement at the top of the file:

import { assertFilter } from '../../common/filters';

This ensures consistency across your tests and prevents potential runtime errors.

backend/data/src/main/kotlin/io/tolgee/model/Project.kt (1)

136-142: 💡 Verification agent

🧩 Analysis chain

Verify usage of removed getLanguage method

The AI summary mentions that a getLanguage(tag: String): Language method was removed, but the remaining findLanguage(tag: String): Language? returns a nullable type. This could affect error handling in code that previously relied on exceptions from the removed method.


🏁 Script executed:

#!/bin/bash
# Check for usages of the removed getLanguage method
rg -B 1 -A 1 "\.getLanguage\(" --type kotlin

# Also check for any error handling related to NotFoundException with language
rg -A 3 "NotFoundException.*language" --type kotlin

Length of output: 921


Action Required: Update and Handle Nullable Language Retrieval

It looks like there are still calls to the removed getLanguage method in the machine translation modules. In the updated Project.kt, findLanguage(tag: String) now returns a nullable Language?, which could lead to issues if the calling code assumes a non-null result.

  • MetadataProvider.kt:

    • Line showing: val targetLanguage = context.getLanguage(metadataKey.targetLanguageId)
    • Ensure that this call is updated to use findLanguage and that you add proper null checking or throw a custom exception if no language is found.
  • PluralTranslationUtil.kt:

    • Line showing: val targetLanguageTag = context.getLanguage(item.targetLanguageId).tag
    • Update this call by either safely accessing the nullable result (e.g. using Kotlin's safe call operator) or handling the potential null explicitly to avoid a NullPointerException.

Please verify and update these usages accordingly to maintain robust error handling in the new implementation.

🧹 Nitpick comments (102)
webapp/src/ee/billing/Invoices/InvoiceUsage.tsx (1)

56-61: Enhanced Testability with data-cy Attribute

The addition of the data-cy="invoice-details-dialog" attribute to the <Dialog> component is a great move to improve testability. This attribute will make it easier to target the dialog in your automated tests without relying on complex selectors.

A couple of points to consider:

  • Consistency Check: Ensure that the naming convention for data-cy attributes is consistent across all dialog components in your codebase. For instance, if similar dialogs in other modules follow a different naming scheme (like billing-usage-dialog or similar), align them to maintain uniformity.
  • Future Scalability: If you plan to add or modify more metrics like keys/strings in the future, consider using constants or a dedicated configuration file for test selectors. This approach can help minimize the risk of typos and make future updates easier.

Overall, the changes in this segment look sound and align with the broader objectives.

backend/data/src/main/kotlin/io/tolgee/events/OnOrganizationNameUpdated.kt (1)

5-8: Consider adding documentation and making this a data class.

The event class structure looks good, but to improve maintainability and clarity, consider:

  1. Adding KDoc comments to describe the purpose of this event class
  2. Making it a data class to get auto-generated equals(), hashCode(), and toString() methods
- class OnOrganizationNameUpdated(
+ /**
+  * Event triggered when an organization's name is updated.
+  *
+  * @property oldName The previous name of the organization
+  * @property organization The organization entity with updated data
+  */
+ data class OnOrganizationNameUpdated(
  val oldName: String,
  val organization: Organization,
)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCreateCustomPlanButton.tsx (1)

4-4: Remove unused import.

The LINKS constant is imported from 'tg.constants/links' but is not used anywhere in the component. Removing unused imports helps maintain code cleanliness and can slightly improve bundle size.

-import { LINKS } from 'tg.constants/links';
backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (2)

230-232: Consider consolidating ObjectMapper instances

There seems to be two ObjectMapper instances in this class: mapper (line 107) and the newly added objectMapper. Consider consolidating them to avoid duplication and potential inconsistencies.

- @Autowired
- lateinit var mapper: ObjectMapper
+ @Autowired
+ lateinit var objectMapper: ObjectMapper

- @Autowired
- lateinit var objectMapper: ObjectMapper

300-308: Improve error handling in ID extraction utility

The error handling could be more specific to help with debugging test failures.

protected fun ResultActions.getIdFromResponse(): Long {
  this.andIsOk
  val response: Map<String, Any> = getContent()
  try {
    return (response["id"] as Number).toLong()
  } catch (e: Exception) {
-   throw Error("Response does not contain id", e)
+   when (e) {
+     is ClassCastException -> throw Error("Response contains 'id' but it's not a number: ${response["id"]}", e)
+     is NullPointerException -> throw Error("Response does not contain 'id' field. Response: $response", e)
+     else -> throw Error("Failed to extract 'id' from response: $response", e)
+   }
  }
}
webapp/src/component/common/StringsHint.tsx (2)

14-21: Code duplication could be improved with a generic component.

The new KeysHint component has identical structure to StringsHint, only differing in the translation key used. Consider refactoring all three hint components into a single reusable component to reduce duplication.

-export const KeysHint: React.FC = ({ children }) => {
-  const { t } = useTranslate();
-  return (
-    <Tooltip disableInteractive title={t('global_keys_hint')}>
-      <StyledBillingHint>{children}</StyledBillingHint>
-    </Tooltip>
-  );
-};

A more maintainable approach would be:

interface GenericHintProps {
  translationKey: string;
  children: React.ReactNode;
}

export const GenericHint: React.FC<GenericHintProps> = ({ translationKey, children }) => {
  const { t } = useTranslate();
  return (
    <Tooltip disableInteractive title={t(translationKey)}>
      <StyledBillingHint>{children}</StyledBillingHint>
    </Tooltip>
  );
};

export const StringsHint: React.FC = (props) => <GenericHint translationKey="global_strings_hint" {...props} />;
export const KeysHint: React.FC = (props) => <GenericHint translationKey="global_keys_hint" {...props} />;
export const SeatsHint: React.FC = (props) => <GenericHint translationKey="global_seats_hint" {...props} />;

23-30: Same code duplication issue applies to SeatsHint component.

Like the KeysHint component, this SeatsHint component follows the same pattern and contributes to code duplication.

See the generic component suggestion in the previous comment that would apply here as well.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeOnUserCountChangedListener.kt (2)

34-36: Consider adding more detailed log message.

For better debugging, consider including the exception message in your log when subscription is not found:

- logger.debug("No active subscription, skipping usage reporting.")
+ logger.debug("No active subscription, skipping usage reporting. Reason: ${e.message}")

24-26: Consider adding a debug log for skipped execution.

For better observability, consider adding a debug log when skipping execution on Tolgee Cloud:

if (billingConfProvider().enabled) {
+  logger.debug("Billing enabled, skipping user count reporting.")
  return
}
webapp/src/ee/billing/administration/translationAgencies/TAEditForm.tsx (1)

131-136: Update Testing Identifier on Submit Button

The change updating the data-cy attribute of the LoadingButton on line 135 to "form-submit-button" is clear and helps standardize testing identifiers. Please ensure that this new, more generic identifier is intentional and aligns with the naming conventions used across similar components. Additionally, consider verifying that any tests referencing the old identifier have been updated accordingly.

webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)

41-47: Consider adding test attributes for easier E2E testing.

The table component implementation is clean and straightforward, but lacks test attributes (like data-cy) which could make E2E testing more difficult.

-const PaginatedHateoasTableListComponent: FC = ({ children }) => {
+const PaginatedHateoasTableListComponent: FC = ({ children }) => {
   return (
-    <Table>
+    <Table data-cy="paginated-hateoas-table">
       <TableBody>{children}</TableBody>
     </Table>
   );
 };
webapp/src/component/common/list/PaginatedHateoasList.tsx (1)

73-73: Add type annotation to the handlePageChange parameter.

The handlePageChange function is missing a type annotation for its parameter, which would improve type safety.

-const handlePageChange = (page) => props.onPageChange?.(page - 1);
+const handlePageChange = (page: number) => props.onPageChange?.(page - 1);
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsEditPlanButton.tsx (1)

7-13: Props type is well-defined with proper documentation.

Good use of JSDoc comments for the isExclusive prop. Consider adding similar documentation for the link prop for consistency.

  isExclusive: boolean;
+  /**
+   * URL to navigate to when the edit button is clicked
+   */
  link: string;
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanStripeProductSelectField.tsx (5)

32-33: Avoid using @ts-ignore comments.

The @ts-ignore comment suppresses TypeScript errors without addressing the underlying type issue. Consider properly typing the data-cy attribute or using a type assertion instead.

-            SelectProps={{
-              // @ts-ignore
-              'data-cy': 'administration-plan-field-stripe-product',
+            SelectProps={{
+              'data-cy': 'administration-plan-field-stripe-product' as string,

Alternatively, extend the SelectProps type definition to include the data-cy property.


38-38: Fix error handling type casting.

The error prop is casting the meta.error object to a string, which may not work as expected if meta.error is a complex object.

-              error: (meta.touched && meta.error) || '',
+              error: Boolean(meta.touched && meta.error),
+              helperText: meta.touched && meta.error ? meta.error : '',

14-19: Add loading and error handling for API data.

The current implementation doesn't handle loading or error states for the API query, which could lead to a poor user experience.

  const productsLoadable = useBillingApiQuery({
    url: '/v2/administration/billing/stripe-products',
    method: 'get',
  });

  const products = productsLoadable.data?._embedded?.stripeProducts;
+  const isLoading = productsLoadable.isLoading;
+  const error = productsLoadable.error;

  const { t } = useTranslate();

  return (
    <Field name={`${parentName}stripeProductId`}>
      {({ field, form, meta }: FieldProps) => {
        return (
          <SearchSelect
            compareFunction={(prompt, label) =>
              label.toLowerCase().includes(prompt.toLowerCase())
            }
            SelectProps={{
              // @ts-ignore
              'data-cy': 'administration-plan-field-stripe-product',
              label: t('administration_cloud_plan_field_stripe_product'),
              size: 'small',
              fullWidth: true,
              variant: 'outlined',
              error: (meta.touched && meta.error) || '',
+              disabled: isLoading,
+              helperText: error ? t('common_api_error') : '',
            }}

24-24: Ensure proper field name construction.

The field name is constructed by concatenating the parentName with 'stripeProductId' without any separator, which could lead to unexpected field names if parentName doesn't include a trailing separator.

-    <Field name={`${parentName}stripeProductId`}>
+    <Field name={`${parentName ? `${parentName}.` : ''}stripeProductId`}>

42-48: Consider improving the None option localization and handling.

The "None" option label is hardcoded rather than using the translation system, and there's no clear distinction between a valid selection and no selection.

            items={[
-              { value: '', name: 'None' },
+              { value: '', name: t('common_none') },
              ...(products?.map(({ id, name }) => ({
                value: id,
                name: `${id} ${name}`,
              })) || []),
            ]}
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignPlanDialogSaveButton.tsx (1)

13-19: Consider adding accessibility attributes

The button currently lacks accessibility attributes that would help users with assistive technologies. Consider adding an aria-label for better screen reader support.

    <LoadingButton
      type="submit"
      loading={saveMutation.isLoading}
      color="primary"
      variant="contained"
      data-cy="administration-subscriptions-assign-plan-save-button"
+     aria-label="Save plan assignment"
    >
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignCloudPlanDialogForm.tsx (1)

24-37: Consider adding a test identifier to the Form element

For better testability, it would be helpful to add a data-cy attribute to the Form element, similar to how it's done in the AssignPlanDialogSaveButton component.

-    <Form>
+    <Form data-cy="administration-subscriptions-assign-cloud-plan-form">
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanTypeSelect.tsx (1)

47-49: Handle potential undefined value.

The renderValue function might return undefined if no matching option is found, which could cause rendering issues.

  renderValue={(val) =>
-    enabledTypeOptions.find((o) => o.value === val)?.label
+    enabledTypeOptions.find((o) => o.value === val)?.label || ''
  }
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCustomPlans.tsx (6)

1-6: Consider adding explicit imports for required types.

The component uses UseQueryResult from react-query, which suggests this component is tightly coupled with react-query's data fetching pattern. Consider adding imports for any specific types needed from the query result, or adding a comment explaining the expected structure.


7-10: Add JSDoc comment to document this generic type.

Adding documentation for this type would help other developers understand how to use it correctly, especially since it's a generic type with specific requirements.

+/**
+ * Props for the SubscriptionsPopoverCustomPlans component.
+ * @template T The type of items in the custom plans list
+ */
type SubscriptionsPopoverCustomPlansProps<T> = {
  getLoadable: () => UseQueryResult<T, any>;
  renderItem: (item: T) => React.ReactNode;
};

15-15: Consider handling query states explicitly.

The component calls props.getLoadable() but doesn't explicitly handle loading, error, or empty states beyond relying on PaginatedHateoasList. Consider adding explicit handling or documenting that these states are managed by the list component.


25-27: Consider extracting text styles to theme or constants.

Typography with hardcoded fontSize and fontWeight could be replaced with a predefined variant or extracted styles for better consistency and maintainability.


29-39: Document expected structure of the query result.

The PaginatedHateoasList component likely expects a specific structure from the loadable query result. Consider adding a comment or type annotation to clarify what this structure should be.


17-41: Consider adding unit tests for this component.

This component seems to be an important part of the subscription management UI. Consider adding unit tests to verify it renders correctly with different data states (loading, error, empty, populated).

backend/data/src/main/kotlin/io/tolgee/util/BypassableListener.kt (1)

3-19: Add KDoc documentation to explain the purpose and usage of this interface

The interface lacks documentation explaining its purpose, usage patterns, and when to implement it. Adding KDoc would help developers understand when and how to use this interface.

 package io.tolgee.util

+/**
+ * Interface for components that support conditional execution based on a bypass flag.
+ * This allows certain operations to be skipped or executed based on the bypass state.
+ */
 interface BypassableListener {
+  /**
+   * Flag indicating whether the listener functionality should be bypassed.
+   * When true, operations may be skipped depending on implementation.
+   */
   var bypass: Boolean
webapp/src/ee/billing/component/BillingProgress.tsx (1)

7-7: Remove unused import ProgressData

The ProgressData type is imported but never used in this file. To maintain clean code, consider removing the unused import.

-import { ProgressData, ProgressItem } from './getProgressData';
+import { ProgressItem } from './getProgressData';
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPublicSwitchField.tsx (1)

1-37: Consider updating the translation key to match the component's generic nature.

While the component has been made more generic by replacing cloud-specific types and attributes, the translation key administration_cloud_plan_field_public still contains a reference to "cloud". For consistency, consider updating this translation key to also use a more generic name.

-          label={t('administration_cloud_plan_field_public')}
+          label={t('administration_plan_field_public')}
backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/TaskE2eDataController.kt (1)

21-23: Consider single-sourcing the data generation service.

You’re defining an additional property for TestDataGeneratingService even though AbstractE2eDataController already has a similarly injected service (albeit with a different name). If this duplication isn’t intentional, reusing the superclass’s property could simplify the code.

backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/AbstractE2eDataController.kt (1)

45-47: Autowired property for test data generation is clear.

This leverages dependency injection to streamline the generate flow. Consider naming consistency with any child controllers to avoid confusion.

backend/development/src/main/kotlin/io/tolgee/data/service/TestDataGeneratingService.kt (2)

13-21: Good transactional boundary with flexible callback support.

The generate method properly uses @Transactional to ensure data consistency and provides a flexible callback mechanism that aligns with the afterTestDataStored method in AbstractE2eDataController. The method has a clear flow: save data, execute callback, return result.

Consider adding some basic validation for the testData parameter to ensure it's properly initialized before processing.


23-46: Well-structured mapping logic for test data entities.

The mapping logic in getStandardResult is comprehensive and correctly handles all required entity types. The nullable types for projectId and organizationId properly account for potential null values.

Consider adding documentation comments to explain the purpose of this method and its relationship to the overall test data generation process. This would help future developers understand how these mappings are used.

+  /**
+   * Maps the entities in the TestDataBuilder to their corresponding model representations
+   * in StandardTestDataResult. This provides a standardized view of the test data
+   * that can be used by the E2E tests.
+   *
+   * @param data The TestDataBuilder containing the entities to be mapped
+   * @return A StandardTestDataResult with mapped projects, users, organizations, and invitations
+   */
   fun getStandardResult(data: TestDataBuilder): StandardTestDataResult {
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1)

13-15: Consider using optional chaining.

The conditional check can be simplified using optional chaining.

-  if (parentName && parentName.endsWith('.')) {
+  if (parentName?.endsWith('.')) {
     parentName = parentName.slice(0, -1);
   }
🧰 Tools
🪛 Biome (1.9.4)

[error] 13-13: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsPopoverSelfHostedCustomPlans.tsx (2)

8-10: Inconsistent naming in props type

The props type is named OrganizationCloudCustomPlansProps but is used for a component called SubscriptionsPopoverSelfHostedCustomPlans. This naming inconsistency could lead to confusion since "Cloud" and "SelfHosted" represent different concepts.

-type OrganizationCloudCustomPlansProps = {
+type SubscriptionsPopoverSelfHostedCustomPlansProps = {
  item: components['schemas']['OrganizationWithSubscriptionsModel'];
};

12-14: Update type reference to match the type name

Update the component's type annotation to use the renamed props type.

export const SubscriptionsPopoverSelfHostedCustomPlans: FC<
-  OrganizationCloudCustomPlansProps
+  SubscriptionsPopoverSelfHostedCustomPlansProps
> = ({ item }) => {
webapp/src/component/security/AcceptInvitationView.tsx (2)

59-81: Missing return statement in second error handler

In the onError handler, the plan_seat_limit_exceeded case has a return statement after displaying the error message, but the seats_spending_limit_exceeded case doesn't. This inconsistency could lead to unexpected behavior.

if (e.code == 'seats_spending_limit_exceeded') {
  message.error(
    <span data-cy="seat_spending_limit_exceeded_while_accepting_invitation_message">
      <T keyName="seat_spending_limit_exceeded_while_accepting_invitation_message" />
    </span>
  );
+  return;
}

63-79: Consider using constants for error codes

The error codes are hardcoded as strings. Using constants would prevent typos and ensure consistency across the codebase.

+const ERROR_CODES = {
+  PLAN_SEAT_LIMIT_EXCEEDED: 'plan_seat_limit_exceeded',
+  SEATS_SPENDING_LIMIT_EXCEEDED: 'seats_spending_limit_exceeded',
+};

options: {
  onError(e: ApiError) {
-    if (e.code == 'plan_seat_limit_exceeded') {
+    if (e.code == ERROR_CODES.PLAN_SEAT_LIMIT_EXCEEDED) {
      message.error(
        <span data-cy="plan_seat_limit_exceeded_while_accepting_invitation_message">
          <T keyName="plan_seat_limit_exceeded_while_accepting_invitation_message" />
        </span>
      );
      return;
    }
-    if (e.code == 'seats_spending_limit_exceeded') {
+    if (e.code == ERROR_CODES.SEATS_SPENDING_LIMIT_EXCEEDED) {
      message.error(
        <span data-cy="seat_spending_limit_exceeded_while_accepting_invitation_message">
          <T keyName="seat_spending_limit_exceeded_while_accepting_invitation_message" />
        </span>
      );
      return;
    }
  },
},
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SelfHostedPlanSelectorField.tsx (2)

20-23: Consider adding validation for the selected plan

The onPlanChange function doesn't check if the user selected the same plan again. Adding this check might prevent unnecessary rerenders.

function onPlanChange(plan: SelfHostedEePlanModel) {
+  if (field.value === plan.id) {
+    return;
+  }
  context.setFieldValue(name, plan.id);
  onPlanChangeProp?.(plan);
}

25-34: Handle possible undefined field value

The component directly passes field.value to the SelfHostedEePlanSelector, but there's no handling for potential undefined or null values, which could occur in an uninitialized form.

<SelfHostedEePlanSelector
-  value={field.value}
+  value={field.value ?? null}
  onPlanChange={onPlanChange}
  selectProps={{
    error: meta.error,
    label: t('administration_billing_trial_plan_assign_plan_label'),
  }}
/>
backend/data/src/main/kotlin/io/tolgee/repository/KeyRepository.kt (1)

249-257: Method implementation looks good, consider adding documentation.

The new countAllOnInstance() method provides a clean way to count all keys across the instance where neither the project nor its organization owner is deleted. This will be useful for metrics collection.

Consider adding a brief comment to document the method's purpose and what it counts (e.g., "Counts all keys in non-deleted projects from non-deleted organizations").

backend/api/src/main/kotlin/io/tolgee/publicBilling/MetricType.kt (1)

1-13: New MetricType enum looks good with a minor typo

The new MetricType enum clearly defines the available billing metrics with appropriate boolean flags. This addition supports the feature to add keys/strings as a metric.

There's a small typo in the documentation comment: "cay" should be "may" in "where user cay be charged by their usage".

/**
-   * Non-free plans, where user cay be charged by their usage
+   * Non-free plans, where user may be charged by their usage
   */
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanSaveButton.tsx (1)

6-8: Consider renaming the props type for consistency

The props type is still named CloudPlanSaveButtonProps while the component has been renamed to PlanSaveButton. For consistency, consider renaming the props type to match the component name.

-type CloudPlanSaveButtonProps = {
+type PlanSaveButtonProps = {
  loading: boolean | undefined;
};

-export const PlanSaveButton: FC<CloudPlanSaveButtonProps> = ({ loading }) => {
+export const PlanSaveButton: FC<PlanSaveButtonProps> = ({ loading }) => {
webapp/src/ee/billing/common/usage/SumUsageItemRow.tsx (1)

9-15: Avoid using as any type casting

While the implementation works, it's better to use proper TypeScript typing rather than casting to any, which bypasses type safety.

-  tableRowProps={{ 'data-cy': props.dataCy } as any}
+  tableRowProps={{ 'data-cy': props.dataCy }}

The tableRowProps in ItemRow is typed as React.ComponentProps<typeof TableRow>, which should accept the data-cy attribute without requiring type casting.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTypeSelectField.tsx (1)

19-22: Consider avoiding type casting if possible.

The type casting as PlanTypeOption[] might be avoidable with proper type definitions.

-  const typeOptions = [
-    { value: 'PAY_AS_YOU_GO', label: 'Pay as you go', enabled: !values.free },
-    { value: 'FIXED', label: 'Fixed', enabled: true },
-  ] as PlanTypeOption[];
+  const typeOptions: PlanTypeOption[] = [
+    { value: 'PAY_AS_YOU_GO', label: 'Pay as you go', enabled: !values.free },
+    { value: 'FIXED', label: 'Fixed', enabled: true },
+  ];
e2e/cypress/compounds/projectMembers/E2ProjectMembersInvitationDialog.ts (1)

1-18: Clean and well-structured implementation using Page Object Model pattern

This class encapsulates the functionality for interacting with the member invitation dialog in Cypress tests, making the tests more maintainable and readable. The methods are well-named and clearly describe their purpose.

Some enhancement suggestions:

  • Consider adding a method to verify successful invitation (such as checking success messages or UI state changes)
  • The fillAndInvite method could return this for method chaining
  • Add methods to handle error states (e.g., invalid email format)
 export class E2ProjectMembersInvitationDialog {
   getEmailField() {
     return cy.gcy('invitation-dialog-input-field');
   }

   typeEmail(email: string) {
     this.getEmailField().type(email);
   }

   clickInvite() {
     cy.gcy('invitation-dialog-invite-button').click();
   }

   fillAndInvite(email: string) {
     this.typeEmail(email);
     this.clickInvite();
+    return this;
   }
+
+  verifySuccessMessage() {
+    return cy.gcy('invitation-success-message').should('be.visible');
+  }
 }
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCloudEditPlanButton.tsx (2)

21-24: Use strict equality operator for comparison

The comparison uses == instead of ===. While this might work as expected in this case, using strict equality is recommended to avoid potential type coercion issues.

   isExclusive={
-    item.cloudSubscription?.plan.exclusiveForOrganizationId ==
+    item.cloudSubscription?.plan.exclusiveForOrganizationId ===
     item.organization.id
   }

16-20: Consider using a more structured approach for URL parameters

The code appends a query parameter using string concatenation. Consider using a more structured approach for cleaner and more maintainable code.

   link={
     LINKS.ADMINISTRATION_BILLING_CLOUD_PLAN_EDIT.build({
       [PARAMS.PLAN_ID]: planId,
-    }) + `?editingForOrganizationId=${item.organization.id}`
+    }) + new URLSearchParams({
+      editingForOrganizationId: item.organization.id.toString()
+    }).toString().replace('=', '=')
   }
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlanEditView.tsx (1)

40-40: Component correctly updated to use the new CloudPlanEditForm

The component name has been updated while maintaining the same prop interface, ensuring that functionality is preserved during refactoring.

Consider adding a data-cy attribute to this component for easier testing:

-       <CloudPlanEditForm planId={planId} />
+       <CloudPlanEditForm planId={planId} data-cy="cloud-plan-edit-form" />
webapp/src/ee/billing/Subscriptions/cloud/useCloudPlans.tsx (1)

56-62: New metric type and usage properties for enterprise plan.

Added metricType and includedUsage properties to support the new keys/strings metric feature.

The value of -2 for all includedUsage metrics likely has special meaning. Consider adding a comment to explain what -2 signifies (e.g., unlimited, contact us, etc.) to improve code clarity.

    metricType: 'KEYS_SEATS',
    includedUsage: {
+     // -2 indicates "contact us for pricing" for enterprise plan
      seats: -2,
      keys: -2,
      mtCredits: -2,
      translations: -2,
    },
backend/data/src/main/kotlin/io/tolgee/service/invitation/InvitationService.kt (1)

68-71: New save method provides direct access to repository functionality

This utility method exposes repository functionality through the service layer, maintaining proper layering. Consider adding a documentation comment to explain its purpose, similar to other methods in this class.

@Transactional
+/**
+ * Saves an invitation to the repository
+ */
fun save(invitation: Invitation): Invitation {
  return invitationRepository.save(invitation)
}
backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/BaseLimitsProvider.kt (1)

1-29: New BaseLimitsProvider implementation

This new component properly implements the LimitsProvider interface, providing a base implementation that sets unlimited defaults for all metrics. The class structure follows Spring component standards and provides a good foundation for limit calculations.

Consider adding a class-level KDoc comment explaining the purpose of this provider and that it's the default unlimited implementation.

@Component
+/**
+ * Default implementation of LimitsProvider that returns unlimited values for all metrics.
+ * This serves as the base implementation when no specific limits are defined.
+ */
class BaseLimitsProvider : LimitsProvider {
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionRowPlanInfo.tsx (2)

1-3: Consider optimizing imports for better readability.

The current import statements have redundancy by importing both * as React and specific named components (FC, ReactNode). Simplify to improve readability:

-import * as React from 'react';
-import { FC, ReactNode } from 'react';
+import React, { FC, ReactNode } from 'react';

16-27: Consider using theme-based sizing for better accessibility.

The hardcoded font size of '10px' might be problematic for accessibility and responsiveness. Consider using theme variables or relative units instead:

- <Typography sx={{ fontSize: '10px' }}>{label}</Typography>
+ <Typography variant="caption">{label}</Typography>

Or if you need a specific size:

- <Typography sx={{ fontSize: '10px' }}>{label}</Typography>
+ <Typography sx={{ fontSize: theme => theme.typography.pxToRem(10) }}>{label}</Typography>
backend/api/src/main/kotlin/io/tolgee/publicBilling/CloudSubscriptionPlanType.kt (1)

10-11: Enhance deprecation message with recommended alternative.

The deprecation annotation should indicate what to use instead of the deprecated enum value.

-@Deprecated("This is legacy and should not be used anymore")
+@Deprecated("This is legacy and should not be used anymore. Use FIXED instead.")
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlanEditView.tsx (1)

19-21: Consider adding error handling for missing planId.

Add validation to handle cases where the route parameter might be missing:

 const match = useRouteMatch();
 const { t } = useTranslate();

 const planId = match.params[PARAMS.PLAN_ID];
+
+if (!planId) {
+  return (
+    <DashboardPage>
+      <Typography color="error">{t('error_missing_plan_id')}</Typography>
+    </DashboardPage>
+  );
+}
backend/data/src/main/kotlin/io/tolgee/dtos/MtCreditBalanceDto.kt (1)

6-11: Consider using a common unit type for consistency.

You have three separate properties all measured in cents. For better type safety, consider creating a typealias or value class:

@JvmInline
value class Credits(val cents: Long)

data class MtCreditBalanceDto(
  /** Used credits */
  val usedCredits: Credits,
  /** Remaining credits */
  val creditBalance: Credits,
  /** Credits included in the plan (or in the Bucket size) */
  val bucketSize: Credits,
  // ...
)

This would make the code more type-safe and eliminate the need for comments about units.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTemplateSelectorField.tsx (1)

1-41: Well-structured React component implementation

The new SelfHostedEePlanTemplateSelectorField component follows React best practices with proper hooks usage, clean state management, and internationalization support. The component structure is clear and the rendering is straightforward.

One consideration: The component doesn't include any error handling for when plan fetching fails in the SelfHostedEePlanSelector component.

Consider adding error handling to gracefully manage scenarios where the plan data cannot be fetched:

+ import { Alert } from '@mui/material';
+ // In the return section, add conditional rendering
+ return (
+   <>
+     {errorFromSelector && (
+       <Alert severity="error" sx={{ mb: 2 }}>
+         {t('admin_billing_plan_template_selector_error')}
+       </Alert>
+     )}
+     <SelfHostedEePlanSelector
+       // Rest of the code
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/ProjectBuilder.kt (1)

81-92: Good addition for test data generation

The new inviteUser method enhances the test builder's capabilities for simulating user invitations. The implementation follows the builder pattern consistently with the rest of the class.

Consider making the permission type configurable rather than hard-coding to MANAGE:

- fun inviteUser(buildPermission: PermissionBuilder.() -> Unit = {}): InvitationBuilder {
+ fun inviteUser(
+   permissionType: ProjectPermissionType = ProjectPermissionType.MANAGE,
+   buildPermission: PermissionBuilder.() -> Unit = {}
+ ): InvitationBuilder {
  val invitationBuilder = InvitationBuilder()
  testDataBuilder.data.invitations.add(invitationBuilder)
  addPermission {
    this.project = this@ProjectBuilder.self
    this.invitation = invitationBuilder.self
-   this.type = ProjectPermissionType.MANAGE
+   this.type = permissionType
    invitationBuilder.self.permission = this
    this.user = null
  }.build(buildPermission)
  return invitationBuilder
}
e2e/cypress/compounds/tasks/E2OrderTranslationDialog.ts (1)

1-16: Well-structured Page Object Model implementation

The implementation follows good practices for Cypress testing by encapsulating dialog interactions in a dedicated class. This approach improves test maintainability and readability.

However, consider making the selectAgency method more flexible:

-  selectAgency() {
-    cy.gcy('translation-agency-item').contains('Agency 1').click();
+  selectAgency(agencyName = 'Agency 1') {
+    cy.gcy('translation-agency-item').contains(agencyName).click();
     cy.gcy('order-translation-next').click();
   }

This would allow tests to select different agencies when needed.

webapp/src/ee/billing/administration/subscriptions/components/generic/useSetZeroPricesWhenFree.ts (1)

20-28: Performance issue in price setting logic

The reducer implementation uses the spread operator on each iteration, which can lead to O(n²) time complexity as noted by the static analysis tool.

Consider refactoring to avoid this performance issue:

-  function setPriceValuesZero() {
-    setNonZeroPrices(prices);
-    const zeroPrices = Object.entries(prices).reduce(
-      (acc, [key]) => ({ ...acc, [key]: 0 }),
-      {}
-    );
-
-    setFieldValue(`prices`, zeroPrices);
-  }
+  function setPriceValuesZero() {
+    setNonZeroPrices(prices);
+    const zeroPrices = {};
+    Object.keys(prices).forEach(key => {
+      zeroPrices[key] = 0;
+    });
+
+    setFieldValue(`prices`, zeroPrices);
+  }
🧰 Tools
🪛 Biome (1.9.4)

[error] 23-23: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/OrganizationBuilder.kt (1)

61-70: Well-implemented inviteUser method for testing

The inviteUser method implementation looks good. It properly:

  1. Creates a new InvitationBuilder
  2. Adds it to the test data collection
  3. Sets up the organization role with OWNER type by default
  4. Returns the builder for further customization

This will be useful for testing invitation-related functionality in the system.

One potential enhancement could be to allow specifying a different default role type instead of hardcoding OrganizationRoleType.OWNER:

-fun inviteUser(buildRole: OrganizationRoleBuilder.() -> Unit = {}): InvitationBuilder {
+fun inviteUser(buildRole: OrganizationRoleBuilder.() -> Unit = {}, roleType: OrganizationRoleType = OrganizationRoleType.OWNER): InvitationBuilder {
  val invitationBuilder = InvitationBuilder()
  testDataBuilder.data.invitations.add(invitationBuilder)
  addRole {
    this.invitation = invitationBuilder.self
-   type = OrganizationRoleType.OWNER
+   type = roleType
    invitationBuilder.self.organizationRole = this
  }.build(buildRole)
  return invitationBuilder
}
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/useCreatingForOrganization.ts (1)

23-31: Conditional API query implementation

The hook correctly uses the useApiQuery hook to fetch organization details, with the query enabled only when a valid organization ID is present.

One potential improvement would be to add error handling for the API query to handle cases where the organization might not exist or the request fails:

 const organizationLoadable = useApiQuery({
   url: '/v2/organizations/{id}',
   method: 'get',
   path: { id: creatingForOrganizationId || 0 },
   options: {
     enabled: !!creatingForOrganizationId,
+    onError: (error) => {
+      console.error('Failed to load organization:', error);
+      // Consider adding a notification or fallback behavior
+    }
   },
 });
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanFreePlanSwitch.tsx (1)

12-25: Consider destructuring values in the onFreeChange function

The implementation is correct, but you can make the code slightly more concise by destructuring values in the function.

  function onFreeChange() {
-   setFieldValue(`${parentName}free`, !values.free);
+   const { free } = values;
+   setFieldValue(`${parentName}free`, !free);
  }
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTypeSelectField.tsx (3)

13-23: Consider adding disabled state for the Pay-as-you-go option

Currently, the "Pay as you go" option is enabled based on !values.free, but there's no visual feedback to the user about why it might be disabled. Consider adding a tooltip or helper text to explain this constraint.

-    { value: 'PAY_AS_YOU_GO', label: 'Pay as you go', enabled: !values.free },
+    { 
+      value: 'PAY_AS_YOU_GO', 
+      label: 'Pay as you go', 
+      enabled: !values.free,
+      tooltip: values.free ? 'Not available for free plans' : undefined 
+    },

Then update the PlanTypeSelect component to display this tooltip when hovering over a disabled option.


24-33: Consider using a more type-safe approach for the switch statement

The current implementation works, but you could make it more type-safe by defining a union type for the possible values.

+ type PlanTypeValue = 'PAY_AS_YOU_GO' | 'FIXED';

- function onChange(value: PlanTypeOption['value']) {
+ function onChange(value: PlanTypeValue) {
    switch (value) {
      case 'PAY_AS_YOU_GO':
        setFieldValue('isPayAsYouGo', true);
        break;
      case 'FIXED':
        setFieldValue('isPayAsYouGo', false);
        break;
    }
  }

35-41: Simplify getValue function with a ternary operator

The getValue function could be simplified for better readability.

  function getValue() {
-   if (values['isPayAsYouGo']) {
-     return 'PAY_AS_YOU_GO';
-   }
-
-   return 'FIXED';
+   return values['isPayAsYouGo'] ? 'PAY_AS_YOU_GO' : 'FIXED';
  }
webapp/src/ee/billing/common/usage/ExpectedUsage.tsx (1)

16-16: Consider renaming props interface to match component name

The component has been renamed from EstimatedCosts to ExpectedUsage, but the props interface is still named EstimatedCostsProps. For better consistency, consider renaming the props interface to ExpectedUsageProps to match the new component name.

-export type EstimatedCostsProps = {
+export type ExpectedUsageProps = {
  useUsage: (
    enabled: boolean
  ) => UseQueryResult<components['schemas']['UsageModel']>;
  estimatedCosts?: number;
};

-export const ExpectedUsage: FC<EstimatedCostsProps> = ({
+export const ExpectedUsage: FC<ExpectedUsageProps> = ({
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanEnabledFeaturesField.tsx (1)

1-60: New component for managing plan features looks good

This component follows React best practices with clear separation of concerns and proper use of Formik for form management.

Consider these potential improvements:

  1. Memoize the toggleField function to prevent unnecessary re-renders:
- const toggleField = () => {
+ const toggleField = React.useCallback(() => {
  let newValues: string[];
  if (values.includes(feature)) {
    newValues = values.filter((val) => val !== feature);
  } else {
    newValues = [...values, feature];
  }
  props.form.setFieldValue(props.field.name, newValues);
- };
+ }, [values, feature, props.field.name, props.form]);
  1. Add loading and error handling states:
+ if (featuresLoadable.isLoading) {
+   return <CircularProgress size={24} />;
+ }
+ 
+ if (featuresLoadable.error) {
+   return <Typography color="error">Error loading features</Typography>;
+ }
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedEditPlanButton.tsx (1)

6-28: Good implementation of the edit plan button

The component correctly handles the conditional rendering of the edit button based on the plan's exclusivity.

Consider a small improvement to the URL construction:

- link={
-   LINKS.ADMINISTRATION_BILLING_EE_PLAN_EDIT.build({
-     [PARAMS.PLAN_ID]: planId,
-   }) + `?editingForOrganizationId=${organizationId}`
- }
+ link={
+   LINKS.ADMINISTRATION_BILLING_EE_PLAN_EDIT.build({
+     [PARAMS.PLAN_ID]: planId,
+   }) + 
+   new URLSearchParams({ editingForOrganizationId: organizationId.toString() }).toString().replace('=', '?')
+ }

This approach provides better escaping of special characters in the organization ID.

webapp/src/ee/billing/common/usage/ProportionalUsageItemRow.tsx (1)

77-88: Well-structured function for generating data-cy props

The function matches the pattern of the existing useLabel function, maintaining code consistency.

Consider using an object lookup pattern for both functions to reduce repetition:

function getDataCyProps(type: ProportionalUsageType) {
-  if (type === 'SEATS') {
-    return { 'data-cy': 'billing-usage-table-seats' };
-  }
-  if (type === 'TRANSLATIONS') {
-    return { 'data-cy': 'billing-usage-table-translations' };
-  }
-  if (type === 'KEYS') {
-    return { 'data-cy': 'billing-usage-table-keys' };
-  }
-  return {};
+  const dataCyMap = {
+    SEATS: { 'data-cy': 'billing-usage-table-seats' },
+    TRANSLATIONS: { 'data-cy': 'billing-usage-table-translations' },
+    KEYS: { 'data-cy': 'billing-usage-table-keys' }
+  };
+  return dataCyMap[type] || {};
}

This approach is more maintainable as new types are added.

webapp/src/ee/billing/component/Plan/IncludedUsage.tsx (1)

32-58: Updated conditional rendering based on metric type.

The rendering logic has been refactored to use the new metricType property instead of the legacy flag, providing a more flexible approach for displaying different types of usage metrics. This implementation properly handles both 'STRINGS' and 'KEYS_SEATS' metric types.

However, there seems to be a minor inconsistency:

  <IncludedKeys
    data-cy={'billing-plan-included-keys'}
-   className="strings"
+   className="keys"
    count={includedUsage?.keys ?? -1}
    highlightColor={highlightColor}
  />

The className should be updated to "keys" instead of "strings" to maintain consistency with the metric type.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanPricesAndLimits.tsx (1)

25-39: Wrapper component needs type definitions.

The Wrapper component is missing TypeScript type definitions for its props. This could lead to type errors at runtime.

-const Wrapper = ({ children, canEditPrices }) => {
+const Wrapper: FC<{ children: React.ReactNode; canEditPrices: boolean }> = ({ children, canEditPrices }) => {
  const { t } = useTranslate();

This will ensure type safety and provide better IDE support.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionPerodInfo.tsx (1)

1-62: New subscription period information component with typos in translation keys

The new component effectively displays subscription period information with proper conditional rendering. However, there are typos in the translation keys.

Fix the typos in the translation keys:

-            keyName="admin_billing_current_perdiod_start"
+            keyName="admin_billing_current_period_start"
-            keyName="admin_billing_current_perdiod_end"
+            keyName="admin_billing_current_period_end"

Also note that the filename itself contains a typo: "SubscriptionPerodInfo.tsx" should be "SubscriptionPeriodInfo.tsx".

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPricesFields.tsx (1)

85-99: Remove unnecessary Fragment wrapper

The Fragment in the conditional rendering for 'STRINGS' metric type contains only a single TextField component, making the Fragment unnecessary.

-        {metricType == 'STRINGS' && (
-          <>
-            <TextField
-              name={`${parentName}prices.perThousandTranslations`}
-              size="small"
-              data-cy="administration-cloud-plan-field-price-per-thousand-translations"
-              label={t(
-                'administration_cloud_plan_field_price_per_thousand_translations'
-              )}
-              type="number"
-              fullWidth
-              disabled={!isPayAsYouGo}
-            />
-          </>
-        )}
+        {metricType == 'STRINGS' && (
+          <TextField
+            name={`${parentName}prices.perThousandTranslations`}
+            size="small"
+            data-cy="administration-cloud-plan-field-price-per-thousand-translations"
+            label={t(
+              'administration_cloud_plan_field_price_per_thousand_translations'
+            )}
+            type="number"
+            fullWidth
+            disabled={!isPayAsYouGo}
+          />
+        )}
🧰 Tools
🪛 Biome (1.9.4)

[error] 86-98: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AssignSelfHostedPlanDialog.tsx (3)

1-11: Remove unused import.

The import AssignCloudPlanValuesType on line 10 is not used anywhere in this component.

-import { AssignCloudPlanValuesType } from '../cloudPlan/assignPlan/AssignCloudPlanDialog';

22-26: Consider adding error handling for the mutation.

The mutation is set up correctly, but there's no error handling if the API call fails.

Consider adding an onError callback to display an error message to the user:

  const assignMutation = useBillingApiMutation({
    url: '/v2/administration/organizations/{organizationId}/billing/assign-self-hosted-plan',
    method: 'put',
    invalidatePrefix: '/v2/administration/billing',
+   options: {
+     onError: (error) => {
+       messaging.error(<T keyName="administration-subscription-plan-assigned-error-message" />);
+     }
+   }
  });

55-84: Consider adding loading state to the form submission.

When the user clicks the save button, there's no visual indication that the form is being submitted.

You could pass the loading state to the AssignCloudPlanDialogForm component:

<AssignCloudPlanDialogForm
  fields={
    <>
      <SelfHostedPlanSelectorField
        name="planId"
        onPlanChange={(plan) => setSelectedPlan(plan)}
      />
      {!selectedPlan?.free && (
        <Alert severity="info">
          <T keyName="administration-assign-subscription-self-hosted-plan-assign-plan-not-free-alert" />
        </Alert>
      )}
    </>
  }
  onClose={onClose}
  saveMutation={assignMutation}
+ isSubmitting={assignMutation.isLoading}
/>

Make sure to update the corresponding component to handle this prop appropriately.

webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedPopover.tsx (1)

42-47: Improve URL construction for better readability.

The URL is constructed using string concatenation, which could be improved for better readability and maintainability.

Consider using template literals:

-  link={
-    LINKS.ADMINISTRATION_BILLING_EE_PLAN_CREATE.build() +
-    '?creatingForOrganizationId=' +
-    item.organization.id
-  }
+  link={`${LINKS.ADMINISTRATION_BILLING_EE_PLAN_CREATE.build()}?creatingForOrganizationId=${item.organization.id}`}
e2e/cypress/compounds/E2KeyCreateDialog.ts (1)

1-1: Remove unused import.

The assertMessage import is defined but never used in this file, as indicated by the static analysis warning.

-import { assertMessage, gcyAdvanced } from '../common/shared';
+import { gcyAdvanced } from '../common/shared';
🧰 Tools
🪛 GitHub Check: E2E Static Check 🪲

[warning] 1-1:
'assertMessage' is defined but never used. Allowed unused vars must match /^_/u

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanMetricTypeSelectField.tsx (1)

18-21: Consider internationalizing option labels.

The option labels are hard-coded in English, while the rest of the component uses the translation hook.

Consider using translation keys for the option labels:

  const options = [
-    { value: 'KEYS_SEATS', label: 'Keys & Seats' },
-    { value: 'STRINGS', label: 'Strings' },
+    { value: 'KEYS_SEATS', label: t('administration_cloud_plan_metric_type_keys_seats') },
+    { value: 'STRINGS', label: t('administration_cloud_plan_metric_type_strings') },
  ] satisfies { value: MetricType; label: string }[];

This would require adding these translation keys to your translation files.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanEditForm.tsx (2)

39-41: Consider adding error state handling.

The component handles loading state and null data, but doesn't explicitly handle API error states. Consider adding error handling to improve the user experience when data fetching fails.

  if (!planData) {
+   if (planLoadable.error) {
+     return <Alert severity="error">Failed to load plan data</Alert>;
+   }
    return null;
  }

57-57: Avoid non-null assertion if possible.

Using the non-null assertion operator ! can lead to runtime errors if the value is actually null or undefined. Consider adding a validation check instead.

-               stripeProductId: values.stripeProductId!,
+               stripeProductId: values.stripeProductId || '',
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx (2)

74-78: Consider using a date utility library for better readability.

Date calculations like this can be made more readable and less error-prone using a library like date-fns.

- const currentDaPlus2weeks = new Date(
-   currentTimestamp + 14 * 24 * 60 * 60 * 1000
- );
+ import { addDays } from 'date-fns';
+ 
+ // elsewhere in code
+ const currentDaPlus2weeks = addDays(new Date(currentTimestamp), 14);

50-58: Consider adding additional validation for customPlan.

When a custom plan is being used, there's no validation to ensure all required fields are provided before submission.

Consider adding validation for the custom plan case:

if (value.customize && !value.customPlan.name) {
  throw new Error('Custom plan must have a name');
}
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AdministrationSubscriptionsSelfHostedEe.tsx (3)

27-33: Unnecessary Fragment can be removed.

The Fragment is redundant since it contains only one child element.

-          label={
-            <>
-              <T
-                keyName={
-                  'administration-subscriptions-active-self-hosted-ee-plan-cell'
-                }
-              />
-            </>
-          }
+          label={
+            <T
+              keyName={
+                'administration-subscriptions-active-self-hosted-ee-plan-cell'
+              }
+            />
+          }
🧰 Tools
🪛 Biome (1.9.4)

[error] 27-33: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


52-53: Use strict equality for comparison.

JavaScript's loose equality (==) can lead to unexpected behavior. Use strict equality (===) instead.

-  if (selfHostedSubscriptions.length == 0) {
+  if (selfHostedSubscriptions.length === 0) {
     return <>N/A</>;
   }

-  if (selfHostedSubscriptions.length == 1) {
+  if (selfHostedSubscriptions.length === 1) {
     const item = selfHostedSubscriptions[0];
     return <>{item.plan.name}</>;
   }

Also applies to: 56-57


48-62: Consider extracting the Label component to a separate file.

For better code organization and reusability, consider moving the Label component to its own file, especially if it might be used elsewhere in the future.

webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionCurrentPlanInfo.tsx (2)

5-5: Possible Typographical Error in Import Path.

The import path '../cloudPlan/SubscriptionPerodInfo' may contain a spelling mistake in the word "Perod." Consider renaming the file or correcting the path to maintain consistency and clarity.


74-85: Add Security Attributes to External Links.

When using _blank for external links, it’s best practice to add rel="noopener noreferrer" to prevent potential security risks. Consider adding this attribute to the Button linking to the Stripe dashboard.

- href={`https://dashboard.stripe.com/subscriptions/${props.subscription.stripeSubscriptionId}`}
+ href={`https://dashboard.stripe.com/subscriptions/${props.subscription.stripeSubscriptionId}`}
+ rel="noopener noreferrer"
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCustomPlanItem.tsx (1)

41-43: Consider Localized Currency Display.

Displaying raw currency values is fine for initial development. However, you might want to internationalize or localize them (e.g., using Intl.NumberFormat) to provide a better user and regional experience.

e2e/cypress/common/translations.ts (1)

13-16: Remove Unused E2KeyCreateDialog Import.

The static analysis tools flag E2KeyCreateDialog as unused. If it’s only referenced by E2TranslationsView, you can safely remove it here to keep imports lean and avoid warnings.

-import {
-  E2KeyCreateDialog,
   KeyDialogFillProps,
 } from '../compounds/E2KeyCreateDialog';
🧰 Tools
🪛 GitHub Check: E2E Static Check 🪲

[warning] 14-14:
'E2KeyCreateDialog' is defined but never used. Allowed unused vars must match /^_/u

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanFormFields.tsx (3)

34-42: Consider validating or sanitizing the plan name when setting custom values.

Currently, name: 'Customized ' + planData.name may produce undesired labels (e.g., "Customized undefined"). For safety and clarity, ensure planData.name is always a valid string or fallback to a default if undefined or empty.

-function setCustomPlanValues(
-  formikProps: FormikProps<any>,
-  planData: components['schemas']['AdministrationCloudPlanModel']
-) {
-  formikProps.setFieldValue('customPlan', {
-    ...getCloudPlanInitialValues(planData),
-    name: 'Customized ' + planData.name,
-  });
-}
+function setCustomPlanValues(
+  formikProps: FormikProps<any>,
+  planData: components['schemas']['AdministrationCloudPlanModel']
+) {
+  const finalName = planData.name || 'My Custom Plan';
+  formikProps.setFieldValue('customPlan', {
+    ...getCloudPlanInitialValues(planData),
+    name: 'Customized ' + finalName,
+  });
+}

48-52: Clarify naming for willOnlyMakeItVisible.

The variable name willOnlyMakeItVisible may not be self-explanatory. Consider renaming it to a more descriptive name (e.g., isPlanAlreadyVisible) to improve code readability and maintenance.


88-96: Use explicit boolean instead of false | undefined.

Passing false or undefined for filterPublic can be confusing. Consider converting this to a clear boolean to improve readability and keep the props predictable.

- filterPublic={willOnlyMakeItVisible ? false : undefined}
+ filterPublic={!willOnlyMakeItVisible}
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanForm.tsx (1)

63-72: Disable form fields while loading to prevent user confusion.

When loading is true, consider disabling the form fields to avoid user edits during an ongoing save operation. This helps ensure a consistent user experience and prevents partial changes.

<Form>
  {beforeFields}
  <Box mb={3} pt={2}>
    <PlanPublicSwitchField
+     disabled={loading}
      {...publicSwitchFieldProps}
    />
    <CloudPlanFields
-     isUpdate={isUpdate}
-     canEditPrices={canEditPrices}
+     isUpdate={isUpdate}
+     canEditPrices={canEditPrices}
+     disabled={loading}
    />
    <PlanSaveButton loading={loading} />
  </Box>
</Form>
webapp/src/ee/billing/component/IncludedItem.tsx (2)

63-102: Potential duplication across IncludedStrings and IncludedKeys.
These components share a very similar structure. Consider extracting common logic if repetition grows.


186-218: Container component’s conditional rendering is concise.
Consider using a switch statement for readability, but this is purely a style preference. The current if-block structure is clear enough.

backend/api/src/main/kotlin/io/tolgee/hateoas/organization/PublicUsageModel.kt (1)

77-78: Fix typo in schema description.

There's a small typo in the schema description - "How seats" should be "How many seats".

-  @Schema(
-    description = """How seats are currently used by organization""",
-  )
+  @Schema(
+    description = """How many seats are currently used by organization""",
+  )
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt (1)

543-589: Consider refactoring repeated code in before/after saver methods
These new methods (runBeforeSaveMethodsOfAdditionalSavers, runAfterSaveMethodsOfAdditionalSavers, etc.) share very similar structures. You can reduce duplicated code using a single helper that accepts a lambda, as shown below:

-// Example of repeated methods
 private fun runBeforeSaveMethodsOfAdditionalSavers(builder: TestDataBuilder) {
   executeInNewTransaction(transactionManager) {
     additionalTestDataSavers.forEach { it.beforeSave(builder) }
   }
 }

-// ...
+private fun runSaverMethodOnAllSavers(
+  builder: TestDataBuilder,
+  action: AdditionalTestDataSaver.(TestDataBuilder) -> Unit
+) {
+  executeInNewTransaction(transactionManager) {
+    additionalTestDataSavers.forEach { saver ->
+      saver.action(builder)
+    }
+  }
 }

This approach helps maintain the DRY principle and simplifies future updates.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/UsageModel.kt (1)

22-22: Add keys usage tracking to the usage model.

The addition of the keys property enables tracking and reporting keys usage metrics, aligning with the PR objective to add keys/strings as a metric. The implementation follows the existing pattern of using AverageProportionalUsageItemModel for usage metrics.

Note: The package name contains a typo: "uasge" instead of "usage".

Consider renaming the package from "uasge" to "usage" in a future refactoring.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e99dcd and 9cd5015.

📒 Files selected for processing (207)
  • .run/Frontend localhost.run.xml (2 hunks)
  • backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.kt (5 hunks)
  • backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/project/ProjectsController.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/PlanIncludedUsageModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/PlanPricesModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEePlanModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/UsageModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/organization/PublicUsageModel.kt (3 hunks)
  • backend/api/src/main/kotlin/io/tolgee/publicBilling/CloudSubscriptionPlanType.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/publicBilling/MetricType.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/publicBilling/PublicCloudPlanModel.kt (1 hunks)
  • backend/app/src/main/resources/application-e2e.yaml (1 hunks)
  • backend/app/src/test/kotlin/io/tolgee/service/organizationRole/OrganizationRoleCachingTest.kt (1 hunks)
  • backend/app/src/test/kotlin/io/tolgee/service/project/ProjectHardDeletingServiceTest.kt (1 hunks)
  • backend/app/src/test/kotlin/io/tolgee/service/project/ProjectServiceTest.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/api/IEeSubscription.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/api/PlanWithIncludedKeysAndSeats.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/batch/processors/GenericAutoTranslationChunkProcessor.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/eventListeners/LanguageStatsListener.kt (3 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/BaseLimitsProvider.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/BaseTranslationsLimitProvider.kt (0 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/LimitsProvider.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/TranslationsLimitProvider.kt (0 hunks)
  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/AdditionalTestDataSaver.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt (10 hunks)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/InvitationBuilder.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/OrganizationBuilder.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/ProjectBuilder.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/TestDataBuilder.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/dtos/MtCreditBalanceDto.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/dtos/UsageLimits.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/events/OnOrganizationNameUpdated.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/events/OnProjectSoftDeleted.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/KeysSpendingLimitExceeded.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanKeysLimitExceeded.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSeatLimitExceeded.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanTranslationLimitExceeded.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/SeatSpendingLimitExceeded.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/TranslationSpendingLimitExceeded.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/model/OrganizationRole.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/model/Permission.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/model/Project.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/repository/KeyRepository.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/repository/OrganizationRepository.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/repository/ProjectRepository.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/invitation/InvitationService.kt (5 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/key/KeyService.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/language/LanguageService.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/mtCreditsConsumption/MtCreditBucketService.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationStatsService.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/project/ProjectHardDeletingService.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt (5 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/security/PermissionService.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/util/BypassableListener.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/util/loggerExtension.kt (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/AbstractE2eDataController.kt (2 hunks)
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/PermissionsE2eDataController.kt (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/TaskE2eDataController.kt (3 hunks)
  • backend/development/src/main/kotlin/io/tolgee/data/StandardTestDataResult.kt (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/data/service/TestDataGeneratingService.kt (1 hunks)
  • backend/misc/src/main/kotlin/io/tolgee/fixtures/dateFromString.kt (1 hunks)
  • backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (5 hunks)
  • e2e/cypress/common/XpathBuilder.ts (2 hunks)
  • e2e/cypress/common/apiCalls/testData/generator.ts (1 hunks)
  • e2e/cypress/common/namespace.ts (0 hunks)
  • e2e/cypress/common/translations.ts (2 hunks)
  • e2e/cypress/compounds/E2KeyCreateDialog.ts (1 hunks)
  • e2e/cypress/compounds/E2NamespaceSelector.ts (1 hunks)
  • e2e/cypress/compounds/E2TranslationsView.ts (1 hunks)
  • e2e/cypress/compounds/projectMembers/E2ProjectMembersInvitationDialog.ts (1 hunks)
  • e2e/cypress/compounds/projectMembers/E2ProjectMembersView.ts (1 hunks)
  • e2e/cypress/compounds/tasks/E2OrderTranslationDialog.ts (1 hunks)
  • e2e/cypress/compounds/tasks/E2TaskForm.ts (1 hunks)
  • e2e/cypress/compounds/tasks/E2TasksView.ts (1 hunks)
  • e2e/cypress/e2e/translations/base.cy.ts (5 hunks)
  • e2e/cypress/e2e/translations/batchJobs.cy.ts (1 hunks)
  • e2e/cypress/e2e/translations/namespaces.cy.ts (1 hunks)
  • e2e/cypress/e2e/translations/with5translations/withViews.cy.ts (2 hunks)
  • e2e/cypress/support/dataCyType.d.ts (10 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/UsageModelAssembler.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeOnKeyCountChangedListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeOnUserCountChangedListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/OnUserCountChangedListener.kt (0 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReportUsageDto.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyLicensingDto.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/data/UsageData.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt (3 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (8 hunks)
  • ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (2 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/UsageReportingTest.kt (3 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (4 hunks)
  • webapp/src/component/common/StringsHint.tsx (1 hunks)
  • webapp/src/component/common/list/PaginatedHateoasList.tsx (2 hunks)
  • webapp/src/component/common/list/SimpleList.tsx (1 hunks)
  • webapp/src/component/common/table/PaginatedHateoasTable.tsx (1 hunks)
  • webapp/src/component/security/AcceptInvitationView.tsx (5 hunks)
  • webapp/src/constants/GlobalValidationSchema.tsx (1 hunks)
  • webapp/src/ee/billing/Invoices/InvoiceUsage.tsx (1 hunks)
  • webapp/src/ee/billing/Subscriptions/cloud/PlansCloudList.tsx (2 hunks)
  • webapp/src/ee/billing/Subscriptions/cloud/useCloudPlans.tsx (1 hunks)
  • webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHosted.tsx (1 hunks)
  • webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHostedList.tsx (2 hunks)
  • webapp/src/ee/billing/Subscriptions/selfHosted/SelfHostedEeActiveSubscription.tsx (1 hunks)
  • webapp/src/ee/billing/Subscriptions/selfHosted/SelfHostedPlanAction.tsx (1 hunks)
  • webapp/src/ee/billing/administration/AdministrationEeLicenseView.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/AdministrationEePlanCreateView.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/AdministrationEePlanEditView.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/CloudPlanForm.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/CloudPlanOrganizations.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/EePlanForm.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/EePlanOrganizations.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanCreateForm.tsx (3 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanEditForm.tsx (3 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanForm.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanFields.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanMetricTypeSelectField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanPricesAndLimits.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTemplateSelectorField.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTypeSelectField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/getCloudPlanInitialValues.ts (3 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/CloudPlanFields.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/CloudPlanPrices.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/CloudPlanPricesAndLimits.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/PlanOrganizationsMultiselectField.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/PlanSelectorField.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/CreatingPlanForOrganizationAlert.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanEnabledFeaturesField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanFreePlanSwitch.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanIncludedUsageFields.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanNameField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanNonCommercialSwitch.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPricesFields.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPublicSwitchField.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanSaveButton.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanStripeProductSelectField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanTypeSelect.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/useCreatingForOrganization.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanCreateForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanEditForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTemplateSelectorField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTypeSelectField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/getSelfHostedPlanInitialValues.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlanCreateView.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlanEditView.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlanCreateView.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlanEditView.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/AdministrationSubscriptionsView.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/AdministrationSubscriptionsCloudPlan.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/AdministrationSubscriptionsListItem.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/AssignCloudTrialDialog.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/OrganizationCloudCustomPlans.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/SubscriptionCloudPlanPopover.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/SubscriptionsCloudEditPlanButton.tsx (0 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/AdministrationSubscriptionsCloudPlan.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionCloudPlanPopover.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionPerodInfo.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCloudEditPlanButton.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCustomPlanItem.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsPopoverCloudCustomPlans.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanFormFields.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanSelectorField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionCurrentPlanInfo.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionRowPlanInfo.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsDetailPopover.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsEditPlanButton.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCreateCustomPlanButton.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCustomPlans.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignCloudPlanDialogForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignPlanDialogSaveButton.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/SubscriptionsPopoverAssignPlanButton.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/generic/useSetZeroPricesWhenFree.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AdministrationSubscriptionsSelfHostedEe.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AssignSelfHostedPlanDialog.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SelfHostedPlanSelectorField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsPopoverSelfHostedCustomPlans.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedEditPlanButton.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedPopover.tsx (1 hunks)
  • webapp/src/ee/billing/administration/translationAgencies/TAEditForm.tsx (1 hunks)
  • webapp/src/ee/billing/common/usage/ExpectedUsage.tsx (4 hunks)
  • webapp/src/ee/billing/common/usage/ExpectedUsageDialogButton.tsx (2 hunks)
  • webapp/src/ee/billing/common/usage/ItemRow.tsx (1 hunks)
  • webapp/src/ee/billing/common/usage/PlanUsageEstimatedCosts.tsx (1 hunks)
  • webapp/src/ee/billing/common/usage/ProportionalUsageItemRow.tsx (4 hunks)
  • webapp/src/ee/billing/common/usage/SumUsageItemRow.tsx (1 hunks)
  • webapp/src/ee/billing/common/usage/UsageTable.tsx (1 hunks)
  • webapp/src/ee/billing/component/ActiveSubscription/SelfHostedEeEstimatedCosts.tsx (2 hunks)
  • webapp/src/ee/billing/component/BillingProgress.tsx (5 hunks)
  • webapp/src/ee/billing/component/CircularBillingProgress.tsx (2 hunks)
  • webapp/src/ee/billing/component/CriticalUsageCircle.tsx (4 hunks)
  • webapp/src/ee/billing/component/IncludedItem.tsx (3 hunks)
  • webapp/src/ee/billing/component/Plan/IncludedUsage.tsx (2 hunks)
⛔ Files not processed due to max files limit (29)
  • webapp/src/ee/billing/component/Plan/Plan.tsx
  • webapp/src/ee/billing/component/Plan/freePlan/FreePlan.tsx
  • webapp/src/ee/billing/component/Plan/freePlan/FreePlanLimits.tsx
  • webapp/src/ee/billing/component/Plan/plansTools.ts
  • webapp/src/ee/billing/component/Price/PayAsYouGoPrices.tsx
  • webapp/src/ee/billing/component/UsageDetailed.tsx
  • webapp/src/ee/billing/component/UserMenu/BillingMenuItem.tsx
  • webapp/src/ee/billing/component/getProgressData.ts
  • webapp/src/ee/billing/component/topBar/TrialChipTooltip.tsx
  • webapp/src/ee/billing/component/utils.ts
  • webapp/src/ee/billing/currentCloudSubscription/CloudEstimatedCosts.tsx
  • webapp/src/ee/billing/currentCloudSubscription/CurrentCloudSubscriptionInfo.tsx
  • webapp/src/ee/billing/currentCloudSubscription/PlanMetric.tsx
  • webapp/src/ee/billing/currentCloudSubscription/subscriptionsTrialAlert/ReachingTheLimitMessage.tsx
  • webapp/src/ee/billing/currentCloudSubscription/subscriptionsTrialAlert/SubscriptionsTrialAlert.tsx
  • webapp/src/ee/billing/limitPopover/PlanLimitPopover.tsx
  • webapp/src/ee/billing/limitPopover/SpendingLimitExceeded.tsx
  • webapp/src/eeSetup/eeModule.ee.tsx
  • webapp/src/globalContext/useMessageService.tsx
  • webapp/src/hooks/useSuccessMessage.tsx
  • webapp/src/service/apiSchema.generated.ts
  • webapp/src/service/billingApiSchema.generated.ts
  • webapp/src/service/http/ApiHttpService.tsx
  • webapp/src/service/http/errorAction.ts
  • webapp/src/service/http/handleApiError.tsx
  • webapp/src/translationTools/useErrorTranslation.ts
  • webapp/src/views/organizations/components/BaseOrganizationSettingsView.tsx
  • webapp/src/views/projects/BaseProjectView.tsx
  • webapp/src/views/projects/ProjectListView.tsx
💤 Files with no reviewable changes (20)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/OnUserCountChangedListener.kt
  • e2e/cypress/common/namespace.ts
  • webapp/src/ee/billing/administration/subscriptions/components/SubscriptionCloudPlanPopover.tsx
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/TranslationsLimitProvider.kt
  • webapp/src/ee/billing/administration/subscriptions/components/AdministrationSubscriptionsCloudPlan.tsx
  • webapp/src/ee/billing/administration/subscriptions/components/OrganizationCloudCustomPlans.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/CloudPlanPrices.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/CloudPlanOrganizations.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/PlanOrganizationsMultiselectField.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/EePlanForm.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/EePlanOrganizations.tsx
  • webapp/src/ee/billing/administration/subscriptions/components/AssignCloudTrialDialog.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/AdministrationEePlanEditView.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/PlanSelectorField.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/CloudPlanPricesAndLimits.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/fields/CloudPlanFields.tsx
  • webapp/src/ee/billing/administration/subscriptions/components/SubscriptionsCloudEditPlanButton.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/AdministrationEePlanCreateView.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/CloudPlanForm.tsx
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/BaseTranslationsLimitProvider.kt
🧰 Additional context used
🧬 Code Definitions (62)
webapp/src/ee/billing/component/ActiveSubscription/SelfHostedEeEstimatedCosts.tsx (1)
webapp/src/ee/billing/common/usage/ExpectedUsage.tsx (1)
  • ExpectedUsage (16-50)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (2)
  • GenericPlanSelector (9-16)
  • GenericPlanSelector (18-52)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlanEditView.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanEditForm.tsx (1)
  • CloudPlanEditForm (17-126)
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlanCreateView.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanCreateForm.tsx (1)
  • CloudPlanCreateForm (14-77)
e2e/cypress/compounds/E2TranslationsView.ts (1)
e2e/cypress/compounds/E2KeyCreateDialog.ts (2)
  • E2KeyCreateDialog (4-93)
  • KeyDialogFillProps (95-105)
e2e/cypress/compounds/projectMembers/E2ProjectMembersView.ts (1)
e2e/cypress/compounds/projectMembers/E2ProjectMembersInvitationDialog.ts (1)
  • E2ProjectMembersInvitationDialog (1-18)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTemplateSelectorField.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1)
  • SelfHostedEePlanSelector (6-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/getSelfHostedPlanInitialValues.ts (1)
  • getSelfHostedPlanInitialValues (4-44)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPublicSwitchField.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)
  • GenericPlanFormData (11-21)
webapp/src/ee/billing/common/usage/ItemRow.tsx (1)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCloudEditPlanButton.tsx (2)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsEditPlanButton.tsx (1)
  • SubscriptionsEditPlanButton (15-40)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTypeSelectField.tsx (3)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1)
  • usePlanFormValues (4-22)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)
  • CloudPlanFormData (23-26)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanTypeSelect.tsx (2)
  • PlanTypeOption (7-11)
  • PlanTypeSelect (19-62)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanCreateForm.tsx (4)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/getSelfHostedPlanInitialValues.ts (1)
  • getSelfHostedPlanInitialValues (4-44)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/useCreatingForOrganization.ts (1)
  • useCreatingForOrganization (11-44)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/CreatingPlanForOrganizationAlert.tsx (1)
  • CreatingPlanForOrganizationAlert (6-30)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTemplateSelectorField.tsx (1)
  • SelfHostedEePlanTemplateSelectorField (9-41)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanFields.tsx (8)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanNameField.tsx (1)
  • PlanNameField (9-21)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanFreePlanSwitch.tsx (1)
  • PlanFreePlanSwitch (12-34)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTypeSelectField.tsx (1)
  • CloudPlanTypeSelectField (13-31)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanMetricTypeSelectField.tsx (1)
  • CloudPlanMetricTypeSelectField (11-45)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanStripeProductSelectField.tsx (1)
  • PlanStripeProductSelectField (11-54)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanPricesAndLimits.tsx (1)
  • CloudPlanPricesAndLimits (8-23)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanEnabledFeaturesField.tsx (1)
  • PlanEnabledFeaturesField (11-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanNonCommercialSwitch.tsx (1)
  • PlanNonCommercialSwitch (7-28)
e2e/cypress/e2e/translations/base.cy.ts (1)
e2e/cypress/compounds/E2TranslationsView.ts (1)
  • E2TranslationsView (4-22)
e2e/cypress/compounds/E2NamespaceSelector.ts (1)
e2e/cypress/compounds/E2KeyCreateDialog.ts (1)
  • selectNamespace (86-88)
webapp/src/ee/billing/common/usage/PlanUsageEstimatedCosts.tsx (1)
webapp/src/ee/billing/common/usage/ExpectedUsage.tsx (2)
  • EstimatedCostsProps (9-14)
  • ExpectedUsage (16-50)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTypeSelectField.tsx (1)
  • SelfHostedEePlanTypeSelectField (13-50)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPricesFields.tsx (1)
  • PlanPricesFields (16-127)
webapp/src/component/security/AcceptInvitationView.tsx (1)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AssignSelfHostedPlanDialog.tsx (3)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignCloudPlanDialogForm.tsx (1)
  • AssignCloudPlanDialogForm (18-39)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SelfHostedPlanSelectorField.tsx (1)
  • SelfHostedPlanSelectorField (12-35)
webapp/src/ee/billing/common/usage/SumUsageItemRow.tsx (1)
webapp/src/ee/billing/common/usage/ItemRow.tsx (1)
  • ItemRow (8-41)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsPopoverSelfHostedCustomPlans.tsx (3)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCustomPlans.tsx (1)
  • SubscriptionsPopoverCustomPlans (12-42)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCustomPlanItem.tsx (1)
  • SubscriptionsCustomPlanItem (19-55)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)
  • GenericPlanFormData (11-21)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanPricesAndLimits.tsx (3)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1)
  • usePlanFormValues (4-22)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPricesFields.tsx (1)
  • PlanPricesFields (16-127)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanIncludedUsageFields.tsx (1)
  • PlanIncludedUsageFields (12-72)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/getSelfHostedPlanInitialValues.ts (1)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/common/usage/ProportionalUsageItemRow.tsx (1)
webapp/src/ee/billing/common/usage/ItemRow.tsx (1)
  • ItemRow (8-41)
e2e/cypress/compounds/tasks/E2OrderTranslationDialog.ts (1)
e2e/cypress/compounds/tasks/E2TaskForm.ts (1)
  • E2TaskForm (3-26)
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlanCreateView.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanCreateForm.tsx (1)
  • SelfHostedEePlanCreateForm (15-70)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTemplateSelectorField.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)
  • CloudPlanSelector (7-35)
e2e/cypress/compounds/E2KeyCreateDialog.ts (1)
e2e/cypress/compounds/E2NamespaceSelector.ts (1)
  • E2NamespaceSelector (1-12)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanEditForm.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx (1)
  • SelfHostedEePlanForm (25-67)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedEditPlanButton.tsx (2)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsEditPlanButton.tsx (1)
  • SubscriptionsEditPlanButton (15-40)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsPopoverCloudCustomPlans.tsx (3)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCustomPlans.tsx (1)
  • SubscriptionsPopoverCustomPlans (12-42)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCustomPlanItem.tsx (1)
  • SubscriptionsCustomPlanItem (19-55)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionCloudPlanPopover.tsx (6)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsDetailPopover.tsx (1)
  • SubscriptionsDetailPopover (9-25)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionCurrentPlanInfo.tsx (1)
  • SubscriptionCurrentPlanInfo (25-88)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCloudEditPlanButton.tsx (1)
  • SubscriptionsCloudEditPlanButton (6-27)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsPopoverCloudCustomPlans.tsx (1)
  • SubscriptionsPopoverCloudCustomPlans (12-39)
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/SubscriptionsPopoverAssignPlanButton.tsx (1)
  • SubscriptionsPopoverAssignPlanButton (5-17)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCreateCustomPlanButton.tsx (1)
  • SubscriptionsPopoverCreateCustomPlanButton (11-25)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanTypeSelect.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)
  • CloudPlanFormData (23-26)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/getCloudPlanInitialValues.ts (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)
  • CloudPlanFormData (23-26)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanFreePlanSwitch.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1)
  • usePlanFormValues (4-22)
webapp/src/ee/billing/administration/subscriptions/AdministrationSubscriptionsView.tsx (1)
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)
  • PaginatedHateoasTable (24-39)
webapp/src/ee/billing/common/usage/ExpectedUsage.tsx (1)
webapp/src/ee/billing/common/usage/ExpectedUsageDialogButton.tsx (1)
  • ExpectedUsageDialogButton (10-52)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx (3)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/getCloudPlanInitialValues.ts (1)
  • getCloudPlanInitialValues (4-50)
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignCloudPlanDialogForm.tsx (1)
  • AssignCloudPlanDialogForm (18-39)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanFormFields.tsx (1)
  • AssignCloudPlanFormFields (25-135)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionCurrentPlanInfo.tsx (2)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionPerodInfo.tsx (1)
  • SubscriptionPeriodInfo (14-61)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCustomPlanItem.tsx (1)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignCloudPlanDialogForm.tsx (1)
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignPlanDialogSaveButton.tsx (1)
  • AssignPlanDialogSaveButton (9-23)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanMetricTypeSelectField.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1)
  • usePlanFormValues (4-22)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (2)
  • CloudPlanFormData (23-26)
  • MetricType (8-9)
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlanEditView.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanEditForm.tsx (1)
  • SelfHostedEePlanEditForm (15-76)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/AdministrationSubscriptionsCloudPlan.tsx (4)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionCloudPlanPopover.tsx (1)
  • SubscriptionCloudPlanPopover (18-50)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionRowPlanInfo.tsx (1)
  • SubscriptionRowPlanInfo (16-27)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx (1)
  • AssignCloudPlanDialog (18-119)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (2)
  • GenericPlanSelector (9-16)
  • GenericPlanSelector (18-52)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/component/Plan/IncludedUsage.tsx (1)
webapp/src/ee/billing/component/IncludedItem.tsx (4)
  • IncludedStrings (22-61)
  • IncludedKeys (63-102)
  • IncludedSeats (145-184)
  • IncludedCredits (104-143)
webapp/src/component/common/list/PaginatedHateoasList.tsx (1)
webapp/src/component/common/list/SimpleList.tsx (1)
  • OverridableListWrappers (5-17)
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)
webapp/src/component/common/list/PaginatedHateoasList.tsx (3)
  • InferItemType (17-21)
  • PaginatedHateoasListProps (23-43)
  • PaginatedHateoasList (45-146)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanCreateForm.tsx (3)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/useCreatingForOrganization.ts (1)
  • useCreatingForOrganization (11-44)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/CreatingPlanForOrganizationAlert.tsx (1)
  • CreatingPlanForOrganizationAlert (6-30)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTemplateSelectorField.tsx (1)
  • CloudPlanTemplateSelectorField (9-41)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedPopover.tsx (7)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsDetailPopover.tsx (1)
  • SubscriptionsDetailPopover (9-25)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionCurrentPlanInfo.tsx (1)
  • SubscriptionCurrentPlanInfo (25-88)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedEditPlanButton.tsx (1)
  • SubscriptionsSelfHostedEditPlanButton (6-28)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsPopoverSelfHostedCustomPlans.tsx (1)
  • SubscriptionsPopoverSelfHostedCustomPlans (12-39)
webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/SubscriptionsPopoverAssignPlanButton.tsx (1)
  • SubscriptionsPopoverAssignPlanButton (5-17)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCreateCustomPlanButton.tsx (1)
  • SubscriptionsPopoverCreateCustomPlanButton (11-25)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanSelectorField.tsx (2)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/service/apiSchema.generated.ts (1)
  • components (915-5039)
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt (1)
backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (1)
  • executeInNewTransaction (268-274)
webapp/src/ee/billing/administration/subscriptions/components/AdministrationSubscriptionsListItem.tsx (3)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/AdministrationSubscriptionsCloudPlan.tsx (1)
  • AdministrationSubscriptionsCloudPlan (13-42)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AdministrationSubscriptionsSelfHostedEe.tsx (1)
  • AdministrationSubscriptionsSelfHostedEe (13-46)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AdministrationSubscriptionsSelfHostedEe.tsx (4)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedPopover.tsx (1)
  • SubscriptionsSelfHostedPopover (18-56)
webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionRowPlanInfo.tsx (1)
  • SubscriptionRowPlanInfo (16-27)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AssignSelfHostedPlanDialog.tsx (1)
  • AssignSelfHostedPlanDialog (19-86)
webapp/src/ee/billing/component/IncludedItem.tsx (1)
webapp/src/component/common/StringsHint.tsx (2)
  • StringsHint (5-12)
  • KeysHint (14-21)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanFormFields.tsx (4)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx (1)
  • AssignCloudPlanValuesType (121-126)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/getCloudPlanInitialValues.ts (1)
  • getCloudPlanInitialValues (4-50)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignTrialDatePicker.tsx (1)
  • AssignTrialDatePicker (5-21)
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanSelectorField.tsx (1)
  • AssignCloudPlanSelectorField (16-44)
backend/development/src/main/kotlin/io/tolgee/data/service/TestDataGeneratingService.kt (1)
backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/AbstractE2eDataController.kt (1)
  • afterTestDataStored (48-48)
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SelfHostedPlanSelectorField.tsx (1)
webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHostedList.tsx (1)
  • SelfHostedEePlanModel (13-14)
webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHostedList.tsx (1)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (194-1312)
webapp/src/ee/billing/component/CriticalUsageCircle.tsx (3)
webapp/src/ee/billing/component/getProgressData.ts (1)
  • getProgressData (6-42)
webapp/src/ee/billing/component/UsageDetailed.tsx (1)
  • UsageDetailed (7-57)
webapp/src/ee/billing/component/CircularBillingProgress.tsx (1)
  • CircularBillingProgress (52-108)
🪛 Biome (1.9.4)
webapp/src/ee/billing/administration/subscriptions/components/generic/useSetZeroPricesWhenFree.ts

[error] 23-23: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts

[error] 13-13: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPricesFields.tsx

[error] 86-98: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AdministrationSubscriptionsSelfHostedEe.tsx

[error] 27-33: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🪛 GitHub Check: E2E Static Check 🪲
e2e/cypress/common/translations.ts

[warning] 14-14:
'E2KeyCreateDialog' is defined but never used. Allowed unused vars must match /^_/u

e2e/cypress/compounds/E2KeyCreateDialog.ts

[warning] 1-1:
'assertMessage' is defined but never used. Allowed unused vars must match /^_/u

🪛 detekt (1.23.7)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeOnKeyCountChangedListener.kt

[warning] 34-34: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms (22)
  • GitHub Check: E2E testing ‍🔎 (15, 14)
  • GitHub Check: E2E testing ‍🔎 (15, 13)
  • GitHub Check: E2E testing ‍🔎 (15, 12)
  • GitHub Check: E2E testing ‍🔎 (15, 11)
  • GitHub Check: E2E testing ‍🔎 (15, 10)
  • GitHub Check: E2E testing ‍🔎 (15, 9)
  • GitHub Check: E2E testing ‍🔎 (15, 8)
  • GitHub Check: E2E testing ‍🔎 (15, 7)
  • GitHub Check: E2E testing ‍🔎 (15, 6)
  • GitHub Check: E2E testing ‍🔎 (15, 5)
  • GitHub Check: BT ‍🔎 (security:test)
  • GitHub Check: E2E testing ‍🔎 (15, 4)
  • GitHub Check: BT ‍🔎 (data:test)
  • GitHub Check: E2E testing ‍🔎 (15, 3)
  • GitHub Check: BT ‍🔎 (ee-test:test)
  • GitHub Check: E2E testing ‍🔎 (15, 2)
  • GitHub Check: BT ‍🔎 (server-app:runWithoutEeTests)
  • GitHub Check: E2E testing ‍🔎 (15, 1)
  • GitHub Check: BT ‍🔎 (server-app:runWebsocketTests)
  • GitHub Check: E2E testing ‍🔎 (15, 0)
  • GitHub Check: BT ‍🔎 (server-app:runStandardTests)
  • GitHub Check: BT ‍🔎 (server-app:runContextRecreatingTests)
🔇 Additional comments (353)
backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanTranslationLimitExceeded.kt (1)

1-4: Good package refactoring for better organization.

Moving the exception class to a dedicated limits package and adding the necessary import for BadRequestException improves the organizational structure of the codebase. This change aligns with the broader restructuring mentioned in the PR summary and follows good software engineering practices by grouping related exception classes together.

.run/Frontend localhost.run.xml (2)

3-3: Update the package-json path to reflect the new project structure.
The value attribute has been changed from "$PROJECT_DIR$/webapp/package.json" to "$PROJECT_DIR$/public/webapp/package.json". This indicates that the project's package metadata has been repositioned into the public/webapp directory. Please verify that all build tools and scripts referencing the package file have been updated accordingly.


12-12: Minor formatting adjustment for improved readability.
The closing </component> tag has been moved to its own line. This change is purely cosmetic and should not affect the XML’s functionality or parsing.

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/TranslationSpendingLimitExceeded.kt (3)

1-1: Package Declaration Updated:
The package declaration has been updated to io.tolgee.exceptions.limits, which neatly organizes limit-related exceptions together. This refactor enhances maintainability by grouping similar exception types.


3-4: Proper Import Addition:
The new import of io.tolgee.exceptions.BadRequestException is appropriate since the class now extends this exception. This aligns well with the codebase reorganization and enforces consistency in exception handling.


6-10: Exception Class Implementation Review:
The TranslationSpendingLimitExceeded class correctly extends BadRequestException and passes the designated error message along with a list of parameters (required and limit). The implementation is straightforward and follows expected standards for custom exceptions.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/CreatingPlanForOrganizationAlert.tsx (1)

14-18: Good addition of the data-cy attribute for E2E testing

Adding the data-cy attribute to the Alert component improves testability by providing a consistent selector for automated E2E tests. This aligns with good testing practices and helps maintain reliable test coverage.

webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsPopoverCreateCustomPlanButton.tsx (1)

1-25: Component implementation looks good.

This is a well-structured React functional component with proper TypeScript typing. It follows good practices:

  • Clear and descriptive component and prop names
  • Proper use of Material UI components
  • Integration with React Router for navigation
  • Internationalization support via the T component
  • Cypress test attribute for automated testing

The button styling and functionality are appropriate for its purpose as a create custom plan action in a popover.

webapp/src/ee/billing/administration/AdministrationEeLicenseView.tsx (3)

6-6: Updated Import Path for SetupLicenceKey

The import for SetupLicenceKey has been updated to use a shallower relative path (../../eeLicense/SetupLicenceKey), which aligns with the recent module reorganization. Please ensure that this new path accurately reflects the new file structure.


7-7: Adjusted Import Path for ActiveEeLicense

The updated import for ActiveEeLicense using the path (../../eeLicense/ActiveEeLicense) is consistent with the restructured module hierarchy. Confirm that similar changes have been applied wherever this module is referenced.


10-10: Corrected Import Path for EeLicenseHint

The import path for EeLicenseHint has been modified to the shallower (../../eeLicense/EeLicenseHint) version, which reflects the updated organizational structure. This change should help in maintaining a cleaner and more intuitive module structure.

webapp/src/ee/billing/administration/subscriptions/AdministrationSubscriptionsView.tsx (2)

1-20: Import statements expanded to support the new table component

The imports have been updated to include necessary types and components for table-based rendering. These changes improve type safety and prepare for the component transition from list to table view.


66-75: Improved UI with transition from list to table view

Changing from PaginatedHateoasList to PaginatedHateoasTable enhances the presentation of subscription data. This is a good improvement that aligns with adding more metrics (keys/strings) that benefit from tabular display.

The existing props are compatible with the new component since PaginatedHateoasTable is a wrapper around PaginatedHateoasList that provides table-specific rendering.

backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (2)

4-4: LGTM: Appropriate imports for JSON handling and test utilities

The added imports support the new functionalities for JSON deserialization and HTTP response handling in tests.

Also applies to: 23-24, 62-62


295-298: LGTM: Well-implemented generic content deserialization utility

This extension function effectively leverages Kotlin's reified type parameters to provide a type-safe way to deserialize response content, which will simplify test code.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeOnUserCountChangedListener.kt (1)

1-38: Implementation looks good with appropriate error handling.

The implementation correctly listens for user count changes, reports usage metrics to the subscription service, and includes appropriate error handling for scenarios without active subscriptions. The early return for Tolgee Cloud environments (where billing is already enabled) is a good optimization.

Note that while the PR title mentions "keys/strings as a metric", this listener is specifically handling the "seats" metric based on user count. Is this intentional or should it also report keys/strings metrics?

webapp/src/ee/billing/component/CriticalUsageCircle.tsx (5)

13-13: Import path update improves code organization.

The import path has been updated from ./utils to ./getProgressData, indicating that the function has been moved to its own dedicated file. This change enhances maintainability by providing clearer module boundaries.


58-58: Improved component naming enhances code clarity.

Renaming from Usage to CriticalUsageCircle provides a more descriptive and specific name that better represents the component's purpose and functionality, making the codebase more intuitive to navigate.


83-83: Updated function call matches new parameter structure.

The call to getProgressData now correctly passes an object with the usage property instead of passing usage directly, aligning with the updated function signature in the getProgressData.ts file.


115-118: Properly expanded props for UsageDetailed component.

The props passed to UsageDetailed now include all properties from progressData using the spread operator, plus the new required isPayAsYouGo property derived from usage. This correctly fulfills the component's updated prop requirements as seen in the UsageDetailed.tsx file.


124-125: Updated property names align with underlying implementation.

The changes from canGoOver to isPayAsYouGo and moreCriticalProgress to mostCriticalProgress properly align with the properties expected by the CircularBillingProgress component and returned by the getProgressData function.

webapp/src/component/common/table/PaginatedHateoasTable.tsx (3)

1-11: LGTM: Clean imports and necessary dependencies.

The imports are well-organized and only include what's needed for this component.


13-22: Type definition is well-structured and maintains type safety.

The PaginatedHateoasTableProps type effectively extends from PaginatedHateoasListProps while omitting the listComponent property since it will be handled internally. This ensures proper type checking while preventing prop conflicts.


24-39: Nice implementation of the table component.

The component follows good practices by:

  1. Using proper generic typing to maintain type safety
  2. Keeping the implementation simple - delegating most work to PaginatedHateoasList
  3. Setting a specific list component while forwarding all other props
webapp/src/component/common/list/PaginatedHateoasList.tsx (2)

23-43: Good extraction of props type definition.

Extracting the props into a separate type definition improves code organization and reusability. The type is comprehensive and properly typed with appropriate generics.


55-60: Props type implementation is correct.

The component signature now uses the newly defined type, which maintains full type safety while improving code readability.

webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsEditPlanButton.tsx (3)

1-6: LGTM! Clean imports following best practices.

The imports are well-organized and include all necessary dependencies for the component's functionality.


24-39: Well-implemented component with proper accessibility considerations.

Good implementation of the component with conditional tooltip content based on the isExclusive prop. The pattern of wrapping the disabled button in a span element ensures the tooltip works correctly even when the button is disabled, which is an excellent accessibility practice.

The data-cy attribute for testing is also a good practice for maintainability.


15-40: Component is well-structured and follows React best practices.

The component is well-implemented as a functional component with TypeScript typing. It correctly uses conditional rendering for the tooltip content and properly handles the disabled state based on the isExclusive prop.

The usage of Material UI components and React Router's Link component is appropriate for the intended functionality.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanStripeProductSelectField.tsx (1)

1-55: Overall component looks well-structured.

The component is well-designed for selecting Stripe products with search functionality. It properly integrates with Formik for form state management and uses the API to fetch products data.

e2e/cypress/support/dataCyType.d.ts (10)

19-23: Refactoring from trial-specific to plan-generic selectors.

The changes implement a more flexible approach by replacing trial-specific selectors with generic plan selectors. This aligns with the PR objective of supporting more advanced metrics like keys/strings.


28-34: Added new metrics for cloud plans.

New selectors have been added to support the keys metric and metric type fields, which aligns with the PR objective of adding keys/strings as a metric. These selectors will enable E2E tests to properly interact with these new fields.


65-72: Consolidation of plan field selectors.

The code has been refactored to use generic administration-plan-field-* selectors rather than separate cloud and EE-specific selectors. This simplifies the selector structure and makes future changes more maintainable.


74-77: Updated subscription management selectors.

These selectors support the revised plan assignment workflow and provide better access to current plan information.


127-132: Added usage metrics selectors.

The new selectors for keys and seats usage align with the PR objective and will enable proper testing of these metrics in the UI.


140-146: Added plan inclusion selectors.

These selectors support the display and testing of what's included in a plan (credits, keys, seats, strings) and the pricing for additional usage.


156-159: Added usage table metric selectors.

These selectors will help test the display of various metrics (credits, keys, seats, translations) in the usage table component.


205-205: Added dialog and form submission selectors.

These selectors support testing of the expected usage dialog and form submission, which are important for the user flow related to plan management.

Also applies to: 224-224


304-304: Added success messages and invoice details selectors.

These new selectors support testing of invitation acceptance flows and invoice viewing, enhancing test coverage for billing-related features.

Also applies to: 313-313


437-437: Added limit exceeded message selectors.

These selectors will help test error scenarios when seat limits or spending limits are exceeded during invitation acceptance, ensuring a robust user experience.

Also applies to: 531-531

webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignPlanDialogSaveButton.tsx (1)

1-23: LGTM! Component follows best practices

The component is well-structured with proper typing, props destructuring, and clear separation of concerns. The LoadingButton is correctly configured to reflect the loading state from the saveMutation prop.

webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/AssignCloudPlanDialogForm.tsx (2)

28-30: Good use of grid layout for form fields

The DialogContent with grid layout and gap spacing is a nice approach for consistent form field spacing. This creates a clean visual hierarchy.


1-39: LGTM! Well-structured dialog form component

The component is well-organized with proper type definitions, appropriate use of Material-UI components, and good integration with the AssignPlanDialogSaveButton component. The form structure with DialogTitle, DialogContent, and DialogActions follows the Material-UI patterns correctly.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanTypeSelect.tsx (4)

1-5: Good organization of imports.

The imports are well-structured with a clear separation between React, external libraries, and internal components.


7-11: Well-defined type with good documentation.

The PlanTypeOption type is clearly defined with all necessary properties, which makes the component more maintainable.


13-17: Props interface is well-structured.

Good type definitions for component props with appropriate callback signature.


19-62: Well-structured component with good separation of concerns.

The component is well-organized with good separation between state management, effects, and rendering logic. Good use of data-cy attributes for testing.

webapp/src/ee/billing/component/BillingProgress.tsx (6)

72-77: LGTM! Good restructuring of props

The change from separate value and maxValue props to a consolidated progressItem prop enhances the component's clarity and maintainability. The renaming of canGoOver to isPayAsYouGo also makes the prop's purpose more explicit.


86-94: LGTM! Good adaptation of normalized value calculation

The calculation of the normalized value and critical state has been correctly adapted to use the new prop structure. The additional check for isPayAsYouGo in the critical condition enhances the logic by ensuring that pay-as-you-go plans don't trigger the critical state unnecessarily.


100-108: LGTM! Good adaptation of extra and fullLength calculations

The calculations for extra and fullLength have been correctly updated to use the properties from the progressItem object, maintaining the same logic while adapting to the new structure.


114-124: LGTM! Updated label condition

The condition for pushing labels into the labels array has been properly updated to use the properties from progressItem. The translation parameters have also been correctly updated.


140-149: LGTM! Updated extra usage color condition

The condition for determining the color of extra usage has been properly updated to use isPayAsYouGo instead of canGoOver, maintaining consistent naming throughout the component.


177-180: LGTM! Updated className assignment

The className assignment has been properly updated to use isPayAsYouGo instead of canGoOver, ensuring consistency with the prop naming changes.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPublicSwitchField.tsx (3)

5-5: Import updated to use more generic type definition.

The component has been refactored to use GenericPlanFormData instead of CloudPlanFormData, which aligns with the broader refactoring effort to make subscription plan components more generic and reusable.


13-13: Form context type updated to match import change.

This change correctly updates the type parameter for useFormikContext to use GenericPlanFormData, ensuring type consistency.


26-26:

Details

❓ Verification inconclusive

Testing attribute updated to reflect generic usage.

The data-cy attribute has been modified to remove the "cloud" reference, making it consistent with the more generic application of this component across different plan types.


🏁 Script executed:

#!/bin/bash
# Check if any Cypress tests still reference the old data-cy attribute
rg "administration-cloud-plan-field-public" --type=ts --type=tsx

Length of output: 92


Below is a revised review comment. Note that the original automatic check encountered an issue because the file type "tsx" wasn’t recognized by ripgrep with the --type flag. I've added a revised search command that uses file globbing to cover both .ts and .tsx files. Please run the following command manually to verify that no Cypress tests reference the outdated attribute:

rg "administration-cloud-plan-field-public" -g "*.ts" -g "*.tsx"

If the command produces no output, it confirms that the update removing the "cloud" reference is consistent across the codebase.

Below is the rewritten review comment:


Verify Testing Attribute Update for Generic Usage

File: webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPublicSwitchField.tsx
Lines: 26

          data-cy="administration-plan-field-public"

Comment:
The data-cy attribute has been updated to remove the "cloud" reference, making it generic and ensuring consistency across different plan types. Please verify that no Cypress tests reference the old attribute (administration-cloud-plan-field-public) by running:

rg "administration-cloud-plan-field-public" -g "*.ts" -g "*.tsx"
backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/PermissionsE2eDataController.kt (1)

4-4: Import looks fine.

No issues with the newly introduced import. It is consistent with the return type in this controller.

backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/TaskE2eDataController.kt (2)

4-5: New imports are consistent with usage.

They correctly bring in the classes needed for returning and generating test data.


32-32: Good delegation to the generating service.

Replacing any prior inlined logic with getStandardResult helps abstract away data-handling details.

backend/development/src/main/kotlin/io/tolgee/data/StandardTestDataResult.kt (1)

1-31: Well-structured data class.

This design neatly captures key test data entities. If usage grows, consider extracting the nested models into separate files for maintainability. Otherwise, this is perfectly fine as an all-in-one reference.

backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/AbstractE2eDataController.kt (2)

3-4: Imports introduced for standard test data generation.

These are aligned with the new service-based test data generation approach.


53-53: Good refactor to use the service layer.

Delegating the generation logic to testDataGeneratingService promotes cleaner code and better separation of concerns.

backend/development/src/main/kotlin/io/tolgee/data/service/TestDataGeneratingService.kt (1)

9-12: Clean service design with proper dependency injection.

The service is well-designed with clear separation of concerns and uses constructor injection for dependencies, following Spring best practices.

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSeatLimitExceeded.kt (1)

6-7: Well-structured exception class implementation.

This new exception class follows a good pattern for handling limit-related errors. It extends BadRequestException and properly passes the required parameters to construct a meaningful error message.

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanKeysLimitExceeded.kt (1)

6-7: Consistent exception implementation.

This exception class follows the same pattern as other limit-related exceptions, providing a clean interface for handling key limit exceeded scenarios.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/usePlanFormValues.ts (1)

4-22: Clean custom hook implementation for form value management.

The hook effectively handles form values with proper type safety through generics.

🧰 Tools
🪛 Biome (1.9.4)

[error] 13-13: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

e2e/cypress/common/XpathBuilder.ts (4)

2-2: Good transition to immutable pattern.

Converting xpath from a mutable let to an immutable const is a positive change, setting up the rest of the refactoring to an immutable approach.


5-5: Immutable method implementations.

All builder methods now correctly return new XPathBuilder instances instead of modifying state. This is a great improvement for predictability and maintainability.

Also applies to: 9-9, 13-13, 17-17, 21-21, 25-25, 29-29, 33-33


41-41: Updated method implementation with chaining.

The getInputUnderDataCy method now properly uses the new immutable approach with method chaining.


65-65: Good addition of type definition.

Adding the XpathBuilderType improves type safety when using the XPathBuilder elsewhere in the codebase.

webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsPopoverSelfHostedCustomPlans.tsx (2)

15-24: LGTM - Query implementation looks good

The implementation of the getLoadable function correctly fetches self-hosted EE plans filtered by organization ID and excluding public plans.


25-38: LGTM - Component rendering looks good

The component properly renders the SubscriptionsPopoverCustomPlans with appropriate props and renders each plan item correctly.

webapp/src/component/security/AcceptInvitationView.tsx (3)

16-17: LGTM - Good addition of imports and hooks

The addition of the ApiError import and useMessage hook improves error handling capabilities in the component.

Also applies to: 49-49


91-91: LGTM - Good redirection update

Changing the redirect path to LINKS.ROOT.build() instead of LINKS.PROJECT.build() is a reasonable update to improve the user experience after an error occurs.


109-113: LGTM - Good addition of data-cy attribute

Adding the data-cy attribute to the success message improves testability.

webapp/src/component/common/list/SimpleList.tsx (1)

5-17: LGTM - Good type improvements

The changes to the OverridableListWrappers type definition provide default types for the generic parameters and simplify the type definitions for component props. This makes the component easier to use and improves type safety.

These changes:

  1. Add default types (typeof Paper and typeof List) for the generic parameters
  2. Simplify the prop types to directly use React.ComponentProps<>
webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SelfHostedPlanSelectorField.tsx (2)

7-10: LGTM - Well-defined props interface

The props interface is clear and properly typed, with the optional callback for plan changes.


17-23: LGTM - Good Formik integration

The component correctly uses Formik hooks to manage form state and handle field value changes.

webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (1)

34-34: Empty query object added to useBillingApiQuery

Adding an empty query object doesn't change functionality but prepares the component for potential future query parameters. This is a good practice for maintaining consistent API call structures.

backend/data/src/main/kotlin/io/tolgee/events/OnProjectSoftDeleted.kt (1)

1-7: Event class structure looks good

This event class properly encapsulates the project that is being soft deleted. This will allow various components to react to project deletion events in a decoupled manner.

backend/data/src/main/kotlin/io/tolgee/api/PlanWithIncludedKeysAndSeats.kt (1)

1-6: Interface design looks appropriate

This interface provides a clean contract for plans that include keys and seats metrics. Using an interface here is good for polymorphism and will make it easier to add new plan types in the future.

backend/app/src/main/resources/application-e2e.yaml (1)

95-95: Debug logging added for exception handlers

Setting DEBUG level for exception handlers during E2E tests will provide more detailed information when issues occur, making debugging easier.

backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/TestDataBuilder.kt (1)

18-18: Support for invitation testing added

Adding the invitations list extends the test data builder pattern consistently with the existing approach. This will allow for proper testing of the new invitation functionality.

ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/UsageModelAssembler.kt (1)

21-21: Keys metric addition looks good!

The addition of the keys property to the UsageModel correctly implements the keys/strings metric by utilizing the existing periodToModel method with the keysUsage data. This follows the same pattern used for other metrics like seats and translations.

backend/api/src/main/kotlin/io/tolgee/publicBilling/PublicCloudPlanModel.kt (1)

15-15: New metricType property aligns with the keys/strings metric feature

The addition of the metricType property to the PublicCloudPlanModel interface is necessary to support the new metrics system. This change extends the interface contract to include information about what type of metric (seats, keys, strings) is used for billing calculations.

webapp/src/ee/billing/Subscriptions/selfHosted/SelfHostedPlanAction.tsx (1)

8-8: Import path update looks good

The import path for PlanType has been updated to use a relative path instead of an absolute path. This change maintains the same functionality while likely improving consistency with other imports in the codebase.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanNonCommercialSwitch.tsx (1)

5-5: Import path update is appropriate

The import path for CloudPlanFormData has been updated to point to the cloud/types module instead of CloudPlanFormBase. This change reflects a reorganization of types into a more logical structure, which improves maintainability while preserving functionality.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/UsageModel.kt (1)

22-22: LGTM! Good addition of keys usage tracking

The addition of keys property with a default value ensures backward compatibility while enabling tracking of key usage metrics as specified in the PR objectives.

ee/backend/app/src/main/kotlin/io/tolgee/ee/data/SetLicenseKeyLicensingDto.kt (2)

11-12: LGTM! Well-implemented keys property

The new keys property is properly added with input validation and a default value, ensuring backward compatibility. The @field:Min(0) annotation correctly enforces that the keys count cannot be negative.


11-12: Add keys parameter to the licensing DTO.

This change adds support for specifying the number of licensed keys when setting up a license. The @field:Min(0) constraint ensures the value is non-negative, while allowing zero keys (unlike seats which require at least 1).

This addition aligns with the PR objective to add keys/strings as a metric and supports the pay-as-you-go pricing model implementation.

backend/data/src/main/kotlin/io/tolgee/service/language/LanguageService.kt (2)

47-48: LGTM! Improved code formatting

Separating the @Suppress and @Lazy annotations improves readability by clarifying their distinct purposes - one addressing a code warning and the other managing dependency injection behavior.


47-48: Improve code formatting by separating annotations.

This is a minor formatting change that separates the @Suppress and @Lazy annotations onto separate lines, improving readability without changing functionality.

backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/InvitationBuilder.kt (1)

1-8: Good implementation!

The InvitationBuilder implementation follows the builder pattern consistently, generating a random 50-character alphanumeric code for invitations. This will be helpful for creating test data in development environments.

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/SeatSpendingLimitExceeded.kt (1)

1-10: Good implementation of exception class.

The SeatSpendingLimitExceeded exception is well-structured and follows the established pattern for limit-related exceptions. It properly extends BadRequestException and provides the required parameters to format the error message.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/PlanPricesModel.kt (1)

16-16: Added key-based pricing support.

The addition of perThousandKeys property enables a new pricing model based on the number of translation keys, complementing the existing per-seat and per-translation pricing models.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt (1)

18-18: Added 'open' modifier to plan property.

Making the plan property open allows it to be overridden in subclasses, which improves extensibility of the subscription model. This change aligns with the new pay-as-you-go functionality mentioned in the PR summary.

backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/LimitsProvider.kt (1)

1-7: Good abstraction with the new LimitsProvider interface.

This new interface replaces the previous TranslationsLimitProvider with a more generic approach that can handle multiple types of limits (including the new keys metric) through the UsageLimits return type.

The interface is clean, focused, and follows the single responsibility principle by only retrieving limits for a specific organization.

webapp/src/ee/billing/component/ActiveSubscription/SelfHostedEeEstimatedCosts.tsx (2)

5-6: Updated imports for the new usage component.

Replaced the previous imports with the new ExpectedUsage component and Material UI Box component, which aligns with the broader changes in the PR around usage metrics.


27-29: Component updated to use the new ExpectedUsage component.

The previous PlanUsageEstimatedCosts component has been replaced with the ExpectedUsage component wrapped in a Box. However, unlike the ExpectedUsage component definition shown in the relevant code snippets that accepts both useUsage and estimatedCosts props, only useUsage is being passed here.

Please verify whether the estimatedCosts prop is intentionally omitted. In the ExpectedUsage component definition, it appears to be used for displaying the formatted money value. If this is deliberate, ensure that the component handles the missing prop gracefully, perhaps by using a default value.

webapp/src/ee/billing/common/usage/PlanUsageEstimatedCosts.tsx (1)

2-2: Replaced EstimatedCosts with ExpectedUsage component

The change replaces the EstimatedCosts component with the newly created ExpectedUsage component while maintaining the same props structure. This is a clean refactoring that better reflects the component's purpose while keeping the API consistent.

Also applies to: 8-8

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/KeysSpendingLimitExceeded.kt (1)

1-11: New exception class looks good

The new KeysSpendingLimitExceeded exception class properly extends BadRequestException and follows the established pattern for limit-related exceptions by passing the appropriate message and parameters to the superclass constructor.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanSaveButton.tsx (1)

10-10: Component renamed for better generalization

The component has been renamed from CloudPlanSaveButton to PlanSaveButton and the data-cy attribute updated accordingly, making it more generic and reusable across different plan types.

Also applies to: 21-21

webapp/src/ee/billing/common/usage/SumUsageItemRow.tsx (1)

7-7: Adding dataCy prop improves testability

This addition enhances component testability by allowing Cypress tests to select the component using a data-cy attribute.

backend/data/src/main/kotlin/io/tolgee/api/IEeSubscription.kt (1)

13-15: New subscription properties align with pay-as-you-go metric features

These new properties are well-structured for tracking included keys and seats in subscription plans, enabling pay-as-you-go pricing models. The use of Long type is appropriate for potentially large values.

backend/app/src/test/kotlin/io/tolgee/service/organizationRole/OrganizationRoleCachingTest.kt (1)

82-82: Parameter order updated to match service method signature

The order of parameters has been correctly updated to align with changes in the OrganizationRoleService.removeUser method signature, which now expects userId first, followed by organizationId.

e2e/cypress/e2e/translations/batchJobs.cy.ts (1)

19-19: Import path updated to use new E2NamespaceSelector component

The import was correctly updated to use the new E2NamespaceSelector module, reflecting the refactoring where the selectNamespace function was moved for better organization and maintainability.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanNameField.tsx (1)

1-21: Clean, well-structured component!

This new component follows React best practices with:

  • Proper TypeScript typing for props
  • Default value for optional parentName prop
  • Internationalization support via useTranslate
  • Clear data-cy attribute for testing

The component is simple, focused, and reusable which will help maintain consistency across plan forms.

e2e/cypress/compounds/E2TranslationsView.ts (1)

1-22: Well-structured E2E test helper following the Page Object Model pattern!

This class provides a clean abstraction for translation view interactions:

  • Good encapsulation with logical method organization
  • Methods build on each other (createKey uses openKeyCreateDialog)
  • Clean separation of concerns
  • Clear, descriptive naming

This implementation will make translation tests more maintainable and readable.

webapp/src/ee/billing/administration/subscriptions/components/generic/assignPlan/SubscriptionsPopoverAssignPlanButton.tsx (1)

1-17: Clean, focused component implementation!

This button component follows best practices:

  • Simple, single-responsibility design
  • Proper internationalization via the T component
  • Clear data-cy attribute for automated testing
  • Descriptive naming that indicates its purpose

The component is well-structured and will be easy to reuse across the subscription management UI.

webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (1)

23-23: Import path update looks correct!

The change to the import path for PlanPublicChip reflects the component's new location in the directory structure. This is likely part of the larger reorganization of subscription plan components mentioned in the PR summary.

e2e/cypress/common/apiCalls/testData/generator.ts (1)

28-28: Type expansion adds proper support for invitation testing

The addition of the invitations property to the TestDataStandardResponse type aligns well with the PR objective of extending functionality. This change enables proper typing for invitation-related test data, making invitation testing more robust and type-safe.

backend/data/src/main/kotlin/io/tolgee/dtos/UsageLimits.kt (1)

1-22: Well-structured data classes for usage metrics

The new UsageLimits data class is well-designed with clear documentation and appropriately named properties. The inclusion of specific limit types (strings, keys, seats) alongside payment model flags (isPayAsYouGo, isTrial) provides a comprehensive structure for subscription plan limits. The nested Limit class with its included and limit properties effectively captures the metrics needed for usage tracking.

webapp/src/ee/billing/common/usage/ItemRow.tsx (3)

6-6: Added React import for JSX and types

The React import is necessary for JSX usage and accessing React types.


14-14: Enhanced component flexibility with tableRowProps

Good addition of the optional tableRowProps property that allows passing additional props to the TableRow component. Using the React.ComponentProps<typeof TableRow> type ensures proper type safety.


22-22: Correctly spreads tableRowProps to TableRow

The implementation properly spreads the tableRowProps to the TableRow component, completing the enhancement for customization.

webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionsDetailPopover.tsx (1)

1-25: Well-designed reusable tooltip component

This new component follows React best practices for composition and reusability. The TypeScript typing enhances code safety, and the component maintains a single responsibility (displaying a popover with custom content). The inclusion of the data-cy attribute is excellent for Cypress testing.

One note: setting maxWidth: 'none' on the tooltip could potentially lead to very wide popovers depending on content size, but this appears to be intentional based on design requirements.

backend/data/src/main/kotlin/io/tolgee/batch/processors/GenericAutoTranslationChunkProcessor.kt (1)

13-14: Import path refactoring looks good.

The update to import the exception classes from the io.tolgee.exceptions.limits package aligns with what appears to be a broader reorganization of exception classes in the codebase.

ee/backend/app/src/main/kotlin/io/tolgee/ee/data/UsageData.kt (2)

8-8: LGTM - Keys added as a new usage metric.

The addition of keysUsage property aligns with the PR objective of adding keys/strings as a metric.


15-19: Total calculation property updated correctly.

The total property calculation now properly includes the sum of keysUsage, ensuring that the overall usage calculation reflects all metrics including the newly added keys.

backend/data/src/main/kotlin/io/tolgee/service/security/PermissionService.kt (1)

403-406:

Details

✅ Verification successful

Parameter order change requires careful verification.

The parameter order in the revoke method has been swapped. Since both parameters are of type Long, any code that calls this method with positional arguments rather than named arguments could silently pass the wrong ID to each parameter.


🏁 Script executed:

#!/bin/bash
# Search for all callers of the revoke method to ensure they've been updated
echo "Looking for callers of PermissionService.revoke..."
rg -A 3 "revoke\(" --type kotlin

Length of output: 969


Verified: Parameter Ordering is Correct

After reviewing the call sites, notably in backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/project/ProjectsController.kt where permissionService.revoke(userId, projectId) is used, the updated parameter order in the revoke method (i.e. userId first and then projectId) is consistent and correct. There are no remaining issues regarding silent parameter misplacement.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTypeSelectField.tsx (1)

1-31: New component for cloud plan type selection looks good.

The component follows React best practices by using TypeScript for type safety, implementing as a functional component, using hooks for state management, and following the container/presentational pattern.

webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHosted.tsx (1)

149-152: Metric type added to all plans

The changes add a new metricType property with the value 'KEYS_SEATS' to each plan object, which is consistent with the PR objective of adding keys/strings as a metric.

webapp/src/ee/billing/Subscriptions/selfHosted/SelfHostedEeActiveSubscription.tsx (1)

76-76: Metric type added to IncludedUsage component

The addition of the metricType property with value 'KEYS_SEATS' is consistent with the PR changes. According to the summary, this replaces the previous isLegacy property, indicating a transition from legacy checks to specific metric types.

webapp/src/ee/billing/component/CircularBillingProgress.tsx (4)

48-48: Property renamed for clarity

The canGoOver property has been renamed to isPayAsYouGo, which better reflects the billing model being used.


55-56: Updated prop name in component parameters

The property destructuring has been updated to use the new isPayAsYouGo name.


60-60: Updated condition logic

The condition for determining whether the progress is critical now uses !isPayAsYouGo instead of the previous !canGoOver, maintaining the same logic with the renamed property.


103-104: Updated className in JSX

The class name in the JSX has been updated to use isPayAsYouGo instead of canGoOver, ensuring CSS styles are consistently applied.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1)

1-32: Well-structured new selector component

This new component follows good React practices:

  1. Fetches plans from the appropriate API endpoint
  2. Handles loading state gracefully
  3. Properly filters plans by organization when needed
  4. Reuses the generic selector component with appropriate type parameters

The component is well-typed using the SelfHostedEePlanAdministrationModel from the billing API schema, ensuring type safety.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCloudEditPlanButton.tsx (2)

6-8: Well-typed component props using schema definitions

The component correctly uses TypeScript types from the generated schema, which ensures type safety when working with API models.


9-12: Good defensive programming with null check

The component properly checks if planId exists before rendering, which prevents runtime errors.

webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlanEditView.tsx (1)

8-8: Import path updated to reflect new component structure

The import path has been updated to use the new CloudPlanEditForm component from the cloud-specific directory, which aligns with the architectural changes being made across the codebase.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/PlanIncludedUsageModel.kt (1)

13-13: Property added for tracking key metrics.

The addition of the keys property with default value -1L aligns with the PR objective of adding keys/strings as a metric. This is consistent with how other usage metrics are defined in this model.

backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/project/ProjectsController.kt (1)

272-272:

Details

❓ Verification inconclusive

Parameter order switched in revoke method call.

The parameter order in the revoke method call has been reversed from permissionService.revoke(projectId, userId) to permissionService.revoke(userId, projectId). Verify that this matches the method signature in the PermissionService class.


🏁 Script executed:

#!/bin/bash
# Check the method signature of the revoke method in PermissionService
rg -A 3 -B 3 "fun revoke" --type kotlin backend/api/src/main/kotlin/io/tolgee/service/security/PermissionService.kt

Length of output: 233


Action Required: Verify the Parameter Order in PermissionService.revoke

The change now calls permissionService.revoke(userId, projectId), but our initial search for the PermissionService.kt file (expected at backend/api/src/main/kotlin/io/tolgee/service/security/PermissionService.kt) did not locate it. Please verify—manually or via an alternative search—that the actual revoke method signature in the PermissionService implementation uses (userId, projectId). If the file is located elsewhere, confirm that the new parameter order in the controller call correctly matches that signature.


To assist further verification, you might run:

#!/bin/bash
# Locate the PermissionService file in the repository 
fd "PermissionService.kt"

and then inspect the signature in the located file with:

#!/bin/bash
# Search for the revoke method signature in the found PermissionService file(s)
rg -A 3 -B 3 "fun revoke" --type kotlin <located_file_path>
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlanCreateView.tsx (2)

7-7: Component import path updated.

The import for the form component has been updated to use CloudPlanCreateForm from a new path. This change is part of a larger restructuring of subscription plan components.


34-34: Component usage updated.

The form component has been changed from CreateCloudPlanForm to CloudPlanCreateForm to match the updated import.

webapp/src/ee/billing/common/usage/ExpectedUsageDialogButton.tsx (3)

10-10: Component renamed for clarity.

The component has been renamed from UsageDialogButton to ExpectedUsageDialogButton to better reflect its purpose. This change aligns with the PR objective of adding keys/strings as a metric and updating usage reporting.


28-28: Data-cy attribute updated.

The data-cy attribute has been updated from billing-estimated-costs-open-button to billing-expected-usage-open-button to match the component's new name and purpose.


33-38: Dialog formatting and data-cy attribute added.

The Dialog component now includes a data-cy attribute for testing and has been reformatted with each prop on a separate line for better readability.

backend/data/src/main/kotlin/io/tolgee/repository/ProjectRepository.kt (1)

160-165: Appropriate addition of method to find deleted projects

The new findDeleted method complements the existing find method, allowing retrieval of logically deleted projects. This is a clean implementation that follows the established pattern in the repository.

e2e/cypress/compounds/tasks/E2TasksView.ts (1)

1-16: Well-structured E2E testing class

This new class follows good practices for Cypress testing by encapsulating page-specific operations. The methods have clear purpose and appropriate documentation.

e2e/cypress/e2e/translations/with5translations/withViews.cy.ts (2)

21-21: Import path updated correctly

The import for selectNamespace has been updated to use the new path, which aligns with the codebase's move toward more structured organization of testing utilities.


47-47: Grammar improvement in test description

Changed from "insert base into translation" to "inserts base into translation" for better readability and proper grammar.

e2e/cypress/compounds/projectMembers/E2ProjectMembersView.ts (1)

1-13: Well-structured page object pattern implementation

This new class follows the page object pattern effectively, providing a clean API for navigating to the project permissions page and interacting with the invitation dialog. It works nicely with the E2ProjectMembersInvitationDialog class shown in the context.

ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (3)

70-76: Schema change looks good, adding included_keys column.

This changeSet correctly adds the included_keys column to the ee_subscription table with appropriate type, default value, and constraints.


77-83: Schema change looks good, adding included_seats column.

This changeSet correctly adds the included_seats column to the ee_subscription table, following the same pattern as the previous column addition.


84-90: Schema change looks good, adding is_pay_as_you_go column.

This changeSet adds the is_pay_as_you_go boolean column with appropriate default value and constraints.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanEditForm.tsx (3)

17-17: Component name update is consistent with file naming.

The component has been renamed from EditCloudPlanForm to CloudPlanEditForm, which better aligns with the file name and the component naming pattern.


88-88: Property renamed to improve clarity.

The property editPlanId has been changed to isUpdate={true}, which is more semantically clear about the component's purpose.


107-107: Null coalescing operator provides safe default.

Using values.stripeProductId ?? '' ensures a valid string even when stripeProductId is undefined.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanFields.tsx (4)

12-16: Well-defined interface for component props.

The component accepts clear props for configuration - parentName (optional), isUpdate (optional), and canEditPrices (required).


17-17: Safe handling of parentName for form field paths.

Adding a dot to parentName when it exists ensures proper field path construction for nested form fields.


25-36: Clean grid layout for form fields.

The Box component creates a well-organized 3-column grid layout with appropriate spacing for the form fields.


38-41: Proper prop passing to child components.

The component correctly passes necessary props to the CloudPlanPricesAndLimits component.

backend/data/src/main/kotlin/io/tolgee/service/invitation/InvitationService.kt (3)

3-3: Added new date-related imports to support improved date handling

The addition of CurrentDateProvider and addMonths extension function enhances date handling, moving from direct manipulation of Instant and Duration to a more reusable approach.

Also applies to: 24-24


38-38: Added dependency injection for CurrentDateProvider

Excellent refactoring to inject the CurrentDateProvider which improves testability by making the class less dependent on system time, allowing for easier mocking in tests.


103-103: Refactored date calculation using CurrentDateProvider

The refactoring replaces a direct Instant.now() and Duration calculation with the more testable currentDateProvider.date.addMonths(-1), which improves code clarity and makes testing time-based functionality easier.

backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1)

277-280: Added new message constants for limits-related features

These new message constants support the "keys/strings as a metric" feature by providing specific error messages for different limit scenarios. The naming is consistent with existing limit-related messages.

backend/data/src/main/kotlin/io/tolgee/component/eventListeners/LanguageStatsListener.kt (3)

9-9: Added import for ProjectService

Import aligns with the new dependency in the constructor.


18-18: Added ProjectService as a constructor dependency

Good practice to inject the required service as a constructor parameter.


29-31: Early exit when project doesn't exist anymore

Excellent defensive programming! This early check prevents unnecessary processing when the project has been deleted, improving robustness and performance.

webapp/src/ee/billing/administration/subscriptions/components/generic/SubscriptionRowPlanInfo.tsx (2)

5-9: Props interface looks well structured.

The interface is clearly defined with appropriate types and includes an optional property for testing purposes.


11-14: Styling implementation looks good.

The styled component is properly defined using Material-UI's styled API.

backend/api/src/main/kotlin/io/tolgee/publicBilling/CloudSubscriptionPlanType.kt (1)

3-6:

Details

✅ Verification successful

Consider removing unused property from enum class.

Since SLOTS_FIXED has been removed and there are no other enum values that set usesSlots to true, consider removing this property if it's no longer needed in the application.

enum class CloudSubscriptionPlanType(
  val payAsYouGo: Boolean,
-  val usesSlots: Boolean,
) {

Verify this change won't break existing code that might reference this property.


🏁 Script executed:

#!/bin/bash
# Search for usages of usesSlots property
rg "\.usesSlots" --type kotlin

Length of output: 32


Below is an additional verification step to help ensure that no Kotlin file references the usesSlots property. Please run the following shell script to perform a more robust search with context:

If this command produces no output, you can be confident that the usesSlots property is not in use, and the removal should be safe. If there is any output, please review the occurrences to ensure they aren’t critical.

Once you’ve verified the search results manually, you can update the review comment accordingly.


🏁 Script executed:

#!/bin/bash
# Re-run a robust search query for any occurrence of "usesSlots" in Kotlin files,
# including surrounding lines for context.
rg -i "usesSlots" --type kotlin -A 5 -B 5

Length of output: 1238


Safe to Remove usesSlots Property

After a thorough search, the only occurrence of usesSlots is its declaration in the enum class (backend/api/src/main/kotlin/io/tolgee/publicBilling/CloudSubscriptionPlanType.kt). No other references to this property were found, so removing it should not break any functionality.

  • Action: Remove the usesSlots property as shown below:
    enum class CloudSubscriptionPlanType(
      val payAsYouGo: Boolean,
    -  val usesSlots: Boolean,
    ) {
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlanEditView.tsx (1)

17-47: Component structure looks good.

The component is well organized with proper route parameter extraction and internationalized labels. The component structure follows good practices by delegating the form handling to a specialized component.

backend/data/src/main/kotlin/io/tolgee/dtos/MtCreditBalanceDto.kt (1)

5-14: Well-structured data class with clear documentation.

The additions to the data class are well-documented with clear comments indicating the units (cents) for each property. The introduction of usedCredits provides valuable context alongside the existing creditBalance.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (3)

42-44: Good addition of test data setup for the keys metric.

This change properly sets up a test project with a key, which allows testing the new keys metric functionality.


82-82: Appropriate assertion for keys count.

This assertion correctly verifies that the request contains the accurate count of keys (1) from the test data setup.


84-88: Comprehensive subscription property validation.

These assertions thoroughly validate the new subscription properties related to keys, seats, and payment model.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (3)

6-6: Import statement simplified.

The wildcard import makes sense here as it reduces verbosity when importing multiple related classes.


34-38: Keys usage metric properly added to mock plan.

The implementation correctly adds the new keys metric with a value of 10 to the test mock data.


41-41: Pay-as-you-go flag properly added.

The isPayAsYouGo property is correctly set to false in the mock plan, consistent with the subscription validation in the controller test.

webapp/src/ee/billing/common/usage/UsageTable.tsx (2)

43-50: Keys metric UI component added correctly.

The implementation follows the same pattern as the existing metrics (seats, translations) and properly conditionally renders the keys usage data when present.


54-54: Test selector added for credits row.

Adding the dataCy attribute improves testability of the component.

backend/data/src/main/kotlin/io/tolgee/util/loggerExtension.kt (1)

34-36: Useful logger utility function added.

This inline reified function provides a more concise and type-safe way to obtain loggers throughout the codebase. It's a good addition that follows Kotlin best practices.

backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt (1)

15-21: Good enhancement to the event publication system

The addition of previousState and propertyNames parameters to the onUpdate method provides valuable context for entity updates, enabling better tracking of changes. This improvement aligns well with the PR objective of adding keys/strings as metrics.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTemplateSelectorField.tsx (1)

25-33: Good form update pattern with preserved values

The onPlanChange handler correctly maintains the form's current name while updating other values from the selected plan template. Setting public: false ensures consistency for derived plans.

backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/mtCreditsConsumption/MtCreditBucketService.kt (1)

173-173: Good addition to usage tracking metrics

Adding the usedCredits calculation enhances the DTO with valuable usage information, making it easier for clients to display credit consumption. This aligns well with the PR objective of improving metric tracking.

e2e/cypress/e2e/translations/namespaces.cy.ts (1)

14-14: Import path refactoring looks good

The import statement has been updated to reflect the reorganization of the codebase, moving selectNamespace from a common utility to a structured compound class.

ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt (4)

6-6: Appropriate import for the new interface

The import of PlanWithIncludedKeysAndSeats is correct and aligns with the implementation.


22-26: Good documentation addition

The class documentation clearly explains the purpose of the entity and its role in the self-hosted instance.


29-29: Interface implementation is appropriate

Adding PlanWithIncludedKeysAndSeats to the implemented interfaces correctly extends the functionality of the EeSubscription class.


72-76: New properties align with PR objectives

The addition of includedKeys, includedSeats, and isPayAsYouGo properties properly implements the interface requirements and aligns with the PR objective of adding keys/strings as a metric.

However, I notice these properties lack JPA annotations like @Column. Verify whether these fields should be persisted in the database, and if so, consider adding appropriate annotations and ensuring database migration scripts are created.

webapp/src/ee/billing/administration/subscriptions/components/generic/useSetZeroPricesWhenFree.ts (1)

1-16: Good implementation of a specialized React hook

The hook is well-structured and has a clear purpose: managing pricing behavior when a subscription plan is marked as free.

backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/OrganizationBuilder.kt (1)

10-10: Import added correctly for OrganizationRoleType

Good addition of the import for OrganizationRoleType which is necessary for the new inviteUser method.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/getSelfHostedPlanInitialValues.ts (3)

1-6: Appropriate imports and function signature

The imports and function signature are correctly set up. The function accepts an optional planData parameter of type SelfHostedEePlanAdministrationModel and returns a SelfHostedEePlanFormData object.


7-20: Proper handling of existing plan data

The function correctly handles the case when planData is provided, mapping all the required fields from the input object to the return value.


22-43:

Details

❓ Verification inconclusive

Default values for new plan creation

The default values for new plan creation look good, with appropriate initial values for all fields. The new keys field in includedUsage is properly initialized to 0, which aligns with the PR's purpose of adding keys/strings as a metric.

One observation: the default isPayAsYouGo is set to true, but you might want to verify this is the intended default behavior since it appears to be a significant business logic decision.


🌐 Web query:

What are common default pricing models for SaaS products - pay as you go vs fixed subscription?

💡 Result:

SaaS companies typically default to two primary pricing models: fixed subscriptions and pay-as-you-go (usage-based) structures. These models balance predictability for customers with revenue stability for businesses, though each suits different customer needs and business strategies.

Fixed Subscription Models

These involve recurring payments (monthly/annual) for access to a product or service tier. Common variations include:

  1. Flat-rate pricing

    • Single price for full feature access (e.g., Basecamp’s $299/month plan for all features)[3][6].
    • Simplifies sales and budgeting but lacks flexibility for diverse user needs[3][7].
  2. Tiered pricing

    • Multiple packages with escalating features and prices (e.g., Zoom’s Pro, Business, and Enterprise tiers)[4][9].
    • Aligns with customer growth stages, enabling upselling[1][4].
  3. Per-user/seat pricing

    • Charges per active user (e.g., Slack or Google Workspace)[4][10].
    • Scales with team size but risks overcharging for inactive users[4].

Pay-As-You-Go (Usage-Based) Models

Customers pay based on consumption metrics like API calls, storage, or transactions:

  • Examples: Stripe (fees per transaction)[3][4], Twilio (charges per SMS sent)[2], AWS (costs per cloud resource)[4].
  • Pros: Aligns costs with value, attracts price-sensitive customers, and scales with usage spikes[3][7].
  • Cons: Unpredictable billing for customers and revenue volatility for businesses[3][7].

Key Comparisons

Model Predictability Flexibility Revenue Stability Best For
Fixed Subscription High Low High Products with consistent demand (e.g., CRMs, collaboration tools)[6]
Pay-As-You-Go Low High Variable Infrastructure, APIs, or products with fluctuating usage (e.g., cloud services)[3][5]

Trends and Hybrid Approaches

Many SaaS providers now blend models (e.g., tiered subscriptions with overage charges)[5][10] to balance stability and flexibility. For example, Postmark combines a base subscription fee with pay-per-email pricing for excess usage[3]. Meanwhile, platforms like Salesforce use hybrid tiers with per-user and feature-based add-ons[4][10].

While fixed subscriptions remain default for simplicity, adoption of usage-based pricing grew by 30% between 2022–2025 as demand for AI and cloud services surged[5]. Companies increasingly opt for adaptive billing systems to support hybrid models, ensuring pricing aligns with real-time value delivery[5].

Citations:


Action Required: Confirm Default Pricing Model Behavior

The default initialization for new plan creation is well-implemented overall—fields are initialized appropriately, and the new keys metric is correctly set to 0. One point to verify is the default setting of isPayAsYouGo to true. Given that common SaaS pricing strategies tend to favor fixed subscription models for predictability, even though pay-as-you-go models are widely used in usage-based scenarios, please confirm that this default aligns with the desired business logic for self-hosted plans.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (5)

1-6: Appropriate imports

All necessary imports are correctly included for React, API hooks, types, and component dependencies.


7-16: Well-defined component type

The component type is well-defined using TypeScript's utility types to extend the GenericPlanSelector type while adding additional props. This approach ensures type safety and proper prop validation.


16-24: Appropriate API query implementation

The component correctly uses the useBillingApiQuery hook to fetch cloud plans from the API, passing the filterPublic prop to filter results.


25-27: Proper null handling for missing data

The component correctly handles the case when no plans data is available by returning null, which prevents potential errors when trying to render with missing data.


29-35: Clean component rendering

The component rendering is clean and correctly passes the fetched plans to the GenericPlanSelector component along with any additional props.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/useCreatingForOrganization.ts (5)

1-10: Good documentation and imports

The hook has clear JSDoc comments explaining its purpose and functionality, which is a good practice. The necessary imports for API queries and URL parameter handling are correctly included.


11-18: Proper hook parameter definition

The hook correctly accepts an initialPlanName parameter with appropriate TypeScript typing.


19-22: Safe parsing of organization ID

The code safely parses the organization ID from the URL search parameters, converting it from string to number and defaulting to undefined if not present.


32-37: Flexible initial plan name generation

The getInitialPlanName function properly handles generating a default plan name based on the organization name when available, while falling back to the provided initial name.


39-44: Clean hook return value

The hook returns a clean object with the necessary data: organization ID, organization data, and the determined initial plan name.

ee/backend/app/src/main/kotlin/io/tolgee/ee/data/ReportUsageDto.kt (1)

11-14: Good addition of the keys metric with proper annotations

The new keys property correctly includes the @Schema annotation with a clear description and the @field:Min(0) constraint to ensure non-negative values. This aligns well with the PR objective of adding keys as a metric.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanFreePlanSwitch.tsx (3)

1-6: LGTM! Proper imports

All necessary imports are included for React, Material-UI components, Formik context, and custom hooks.


7-10: LGTM! Well-defined props interface

The props interface is clearly defined with appropriate optional parameters.


26-34: LGTM! Clean return with proper accessibility attributes

The component return statement is clean and includes the appropriate data-cy attribute for testing.

backend/data/src/main/kotlin/io/tolgee/model/Project.kt (1)

128-129: LGTM! Enhanced activity logging

Adding the @ActivityLoggedProp annotation to the deletedAt property improves audit logging capabilities, which is valuable for tracking when projects are soft-deleted.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanTypeSelectField.tsx (2)

1-11: LGTM! Clear component setup with well-defined types

The imports and type definition are appropriate for the component's functionality.


43-49: LGTM! Clean component rendering

The component return statement is clean and appropriately passes props to the PlanTypeSelect component.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTemplateSelectorField.tsx (1)

6-7: Renaming and import changes look good

The component has been renamed from PlanTemplateSelector to CloudPlanTemplateSelectorField and now uses CloudPlanSelector instead of PlanSelector. These changes make the component naming more specific and align with cloud-specific functionality. The updated imports from the reorganized file structure also maintain consistency with the broader refactoring effort.

Also applies to: 9-9, 20-20

backend/data/src/main/kotlin/io/tolgee/model/OrganizationRole.kt (2)

4-4: Import consolidation with wildcard looks good

The import statement has been simplified by replacing individual imports with a wildcard import for the Jakarta Persistence annotations. This is a common practice for reducing import clutter when using multiple annotations from the same package.


18-18: Improved database performance with lazy loading

Adding fetch = FetchType.LAZY to the OneToOne relationship with Invitation will help improve performance by only loading the invitation data when explicitly accessed rather than eagerly loading it with every OrganizationRole fetch operation.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsPopoverCloudCustomPlans.tsx (3)

1-14: New component structure looks good

The component declaration and imports are well-organized. The type definition OrganizationCloudCustomPlansProps is clear and properly references the generated schema type from the API.


15-24: API query implementation is appropriate

The implementation of getLoadable follows the pattern used elsewhere in the application. The query parameters properly filter plans that are assignable to the specific organization and excludes public plans.


25-39: Component composition and prop passing looks good

The component properly passes the getLoadable function and renderItem implementation to the SubscriptionsPopoverCustomPlans component. The URL construction in the editLink prop correctly includes both the plan ID and organization ID as query parameters.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/getCloudPlanInitialValues.ts (4)

2-2: Import path update looks good

Updated import path for CloudPlanFormData from './types' improves code organization by centralizing type definitions.


16-16: Added support for new 'keys' pricing metric

The addition of perThousandKeys to the prices object supports the new keys/strings metric feature that this PR is implementing.

Also applies to: 36-37


19-21: Simplified included usage handling

The refactoring to use the spread operator for planData.includedUsage simplifies the code while maintaining the specific override for translations. This approach is more maintainable as it will automatically include any new usage metrics added in the future.


27-27: Added support for new metric type

The addition of metricType: 'KEYS_SEATS' and keys: 0 to the default values supports the new keys/strings metric feature that this PR is introducing.

Also applies to: 42-43

webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlanCreateView.tsx (1)

1-36: Clean implementation of the plan creation view

The component follows best practices with proper structure, organization, and use of the translation system. It correctly integrates with the broader administration interface and provides a clear interface for plan creation.

webapp/src/ee/billing/common/usage/ExpectedUsage.tsx (2)

29-29: Data-cy attribute updated correctly

The data-cy attribute has been properly updated to match the new component name, maintaining consistency for testing selectors.


39-45: Component renamed correctly

The dialog button component has been correctly updated to the new naming convention, maintaining functionality while improving readability and consistency.

e2e/cypress/compounds/E2NamespaceSelector.ts (2)

1-12: Well-structured E2E test helper class

The E2NamespaceSelector class is well-designed with clear methods that encapsulate the namespace selection functionality. It follows good practices for Cypress test helpers by using data-cy selectors and providing a logical sequence of operations.


14-16: Good convenience function export

Exporting a standalone function makes it easy to use this functionality without having to instantiate the class directly, which improves the test code's readability.

backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt (1)

64-64: Enhancement to event publishing with additional context

Adding previousState and propertyNames parameters to the onUpdate method call provides valuable context about what changed during an update operation. This allows event handlers to access both the old values and identify which specific properties were modified, supporting more granular reactions to changes.

backend/data/src/main/kotlin/io/tolgee/service/key/KeyService.kt (2)

330-330: Good migration from native SQL to JPQL query

This change improves the code by replacing the raw SQL with a JPQL query. The new query is more type-safe, better aligned with the entity model, and more database-agnostic.


496-498:

Details

❓ Verification inconclusive

New method to count all keys on the instance

This new method provides a clean API to retrieve the total count of keys across the entire instance, which aligns with the new usage metric introduced in this PR.

Let's check if the repository implementation properly filters out keys from deleted projects:


🏁 Script executed:

#!/bin/bash
# Check the implementation of countAllOnInstance in KeyRepository
rg -A 5 "countAllOnInstance" --type kotlin

Length of output: 2072


Based on the grep results, the new countAllOnInstance() method in KeyService simply delegates to the repository method. However, the repository definition in KeyRepository.kt only shows the method signature (i.e. fun countAllOnInstance(): Long) without any visible filtering logic (such as excluding keys from deleted projects). This raises a concern that the filtering behavior might not be enforced at the repository level—unless it’s implicitly handled (for example, via a custom query annotation or through global filters defined outside this method).

Please verify that the actual repository implementation (or its query annotations/global filters) properly excludes keys from deleted projects before finalizing the API usage metric change.

  • Location(s) to check:
    • backend/data/src/main/kotlin/io/tolgee/repository/KeyRepository.kt for any query annotations or filtering logic.
    • Any global filters or configurations affecting key queries that might automatically exclude keys from deleted projects.
webapp/src/ee/billing/common/usage/ProportionalUsageItemRow.tsx (6)

5-5: Good addition of the useOrganization hook

Importing the useOrganization hook allows the component to dynamically get the organization context.


22-22: Correctly retrieving organization from context

Using the organization from context is a much better approach than using a hardcoded ID.


30-30: Fixed hardcoded organization ID

Excellent change replacing the hardcoded organization ID with the dynamic value from context.


51-51: Good addition of data-cy attributes for testing

Adding these attributes improves testability.


54-59: Clean update to ItemRow component props

The component now correctly passes data-cy attributes to the underlying TableRow.


71-73: Support for new KEYS usage type

This addition aligns with the new keys metrics introduced in this PR.

webapp/src/ee/billing/Subscriptions/cloud/PlansCloudList.tsx (2)

6-6: Import path updated correctly.

The import path for FreePlan has been updated to reflect the new directory structure, which follows a more organized pattern.


30-40: Code refactoring improves readability and maintainability.

The isActive function has been refactored with a clearer logical flow using early returns. The new implementation:

  1. First checks if plan IDs match
  2. Handles special cases (trial or free plans)
  3. Verifies billing period match only when necessary

This is more maintainable than the previous implementation that used a planPeriod variable and isPlanPeriodDependant check.

e2e/cypress/compounds/tasks/E2TaskForm.ts (1)

1-26: Good implementation of the E2TaskForm class.

This class properly encapsulates the task form interactions for E2E testing with well-structured methods that follow Cypress best practices. The implementation correctly handles optional fields and uses appropriate selectors.

I particularly like the separation of concerns between the different methods, making the tests more maintainable and easier to understand.

webapp/src/ee/billing/component/Plan/IncludedUsage.tsx (2)

4-5: Fixed import typo and added IncludedKeys import.

The import for IncludedCredits has been corrected (previously was IncludedCreadits) and the new IncludedKeys component has been imported correctly.


15-15: Type updated to support the new metric system.

The Props type now includes the metricType property instead of the previous isLegacy flag, which is more descriptive and aligns with the new feature for supporting different metrics (keys/strings).

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanPricesAndLimits.tsx (1)

8-23: Well-structured component with proper props.

The CloudPlanPricesAndLimits component is well-designed, effectively retrieving form values and conditionally rendering child components based on the metric type. It properly passes the parentName and derived metricType to the appropriate components.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/AdministrationSubscriptionsCloudPlan.tsx (1)

13-42: Clean implementation of the cloud plan subscription component.

The component is well-structured with appropriate state management for the dialog and correctly passes props to child components. The optional chaining in line 31 with the fallback to 'N/A' is a good defensive coding practice.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanCreateForm.tsx (2)

15-52: Well-implemented form submission logic.

The mutation handling and success flow are properly implemented. The code correctly handles the organization context when creating a custom plan.


54-69: Good component composition with clear separation of concerns.

The component properly composes the form with conditional rendering of organization-specific elements, making the code maintainable and easy to understand.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeOnKeyCountChangedListener.kt (3)

25-43: The runSentryCatching block is swallowing exceptions.

While the code properly reports key count changes, the exception handling in runSentryCatching is flagged by static analysis as potentially swallowing exceptions, which could hide important errors.

Consider adding explicit error handling or logging inside the runSentryCatching block:

runSentryCatching {
  val modifiedEntityClasses = event.modifiedEntities.keys.toSet()

  val isKeysChanged = modifiedEntityClasses.any { it == Key::class }

  val isProjectDeletedChanged =
    event.modifiedEntities[Project::class]?.any { it.value.modifications.contains("deletedAt") } == true

  if (isKeysChanged || isProjectDeletedChanged) {
    onKeyCountChanged()
  }
+} catch (e: Exception) {
+  logger.error("Error processing project activity event", e)
}
🧰 Tools
🪛 detekt (1.23.7)

[warning] 34-34: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


27-29: Verify the billing configuration condition.

The code skips key counting when billing is enabled, which seems counterintuitive. Normally, we would expect key counting to be more important when billing is enabled, not disabled.

Is this conditional check intended? Please verify that we should only count keys when billing is disabled.


50-59: Solid implementation of the key count change handler.

The method correctly counts keys, finds the subscription, and reports usage with proper exception handling for the case when no active subscription exists.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (2)

7-16: Well-defined generic interface for plan selector.

The interface is properly typed with TypeScript generics, allowing for flexible usage with different plan types while maintaining type safety.


18-52: Clean implementation using modern TypeScript features.

Good use of the TypeScript satisfies operator to ensure type compatibility while mapping plans to select items. The component properly handles plan selection and callback delegation.

webapp/src/constants/GlobalValidationSchema.tsx (1)

317-320: Updated validation rules for pricing fields

The validation rules for pricing fields have been updated to allow for zero values (min(0) instead of moreThan(0)), and new fields for perSeat and perThousandKeys have been added. This change appropriately supports plans where certain pricing metrics can be offered for free while others can be charged.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx (1)

1-67: LGTM - Well-structured form component

This new form component for self-hosted enterprise edition plans is well-organized, using appropriate form fields and layout. The component follows React best practices with clear props definition and proper use of Formik for form management.

e2e/cypress/e2e/translations/base.cy.ts (3)

17-17: Improved test structure using E2TranslationsView

Refactoring to use the E2TranslationsView class instead of direct function calls improves test organization and maintainability by encapsulating the translation creation logic.

Also applies to: 54-60


91-93: Updated plural translation structure

The data structure for plural translations has been updated to use a more organized format with a dedicated plural property containing formValues, which better represents the hierarchical nature of plural translations.

Also applies to: 109-112


129-136: Consistent use of E2TranslationsView for namespace tests

The namespace test case now also uses the E2TranslationsView class, maintaining consistency across all test cases.

webapp/src/ee/billing/administration/subscriptions/components/AdministrationSubscriptionsListItem.tsx (2)

7-9: Import structure updated for table-based layout

The imports have been properly updated to support the new table-based layout and include additional components.

Also applies to: 12-12, 15-16


22-45: Component structure improved with table layout

The component has been refactored from using ListItem to a more appropriate TableRow and TableCell structure, which is better suited for displaying subscription data in a tabular format. The new layout also includes both cloud and self-hosted EE subscription information.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionCloudPlanPopover.tsx (2)

12-16: Clear and descriptive props interface

The Props interface clearly defines the expected input parameters for the component with appropriate types.


18-50: Well-structured cloud plan popover component

The component is well-organized, providing a clear separation of concerns by delegating functionality to child components. It effectively combines current plan information, custom plans display, and action buttons in a logical layout.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanIncludedUsageFields.tsx (2)

7-10: Props interface with sensible defaults

The props interface is well-defined with optional parameters, allowing for flexible component usage.


23-69: Responsive grid layout with conditional rendering based on metric type

The component implements a clean grid layout for form fields and properly conditionally renders different fields based on the metricType prop. The layout will adapt well to different screen sizes due to the grid configuration.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanPricesFields.tsx (3)

11-14: Clear props interface for price fields

The interface is well-defined with appropriate types for the optional parameters.


16-31: Effective form state management with hooks

The component makes good use of custom hooks for form state management, including retrieving form values and handling the special case when a plan is free.


32-127: Well-structured pricing fields with conditional rendering

The component provides a comprehensive interface for pricing configuration, with appropriate sections for subscription and usage-based pricing. The conditional rendering based on plan type and pricing model is well implemented.

🧰 Tools
🪛 Biome (1.9.4)

[error] 86-98: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

webapp/src/ee/billing/administration/subscriptions/components/selfHosted/AssignSelfHostedPlanDialog.tsx (1)

13-86: LGTM! The component implementation is clean and well-structured.

The implementation correctly uses Formik for form management, Yup for validation, and handles the API interaction appropriately. The component also provides useful feedback to the user with a success message after the plan is assigned.

webapp/src/ee/billing/administration/subscriptions/components/selfHosted/SubscriptionsSelfHostedPopover.tsx (1)

12-56: LGTM! The component is well-structured and properly handles organization subscriptions.

The component correctly renders a popover with subscription information, provides buttons for common actions, and integrates well with the existing component structure.

e2e/cypress/compounds/E2KeyCreateDialog.ts (2)

4-93: LGTM! The E2KeyCreateDialog implementation provides a clean API for test automation.

The class is well-structured with methods that encapsulate different dialog interactions, making it reusable across test cases. The implementation includes all the necessary functionality for filling out fields, adding tags, handling pluralization, etc.


95-105: LGTM! The KeyDialogFillProps type is well-defined.

The type definition clearly specifies the input structure expected by the dialog filling methods, with appropriate optional fields.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanMetricTypeSelectField.tsx (1)

1-45: LGTM! The select field is implemented well.

The component correctly integrates with the form context, provides proper typing for the options, and handles the rendering of selected values.

webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHostedList.tsx (4)

9-14: Enhanced type safety for plan features

The new type definition SelfHostedEePlanModel derived from the API schema ensures stronger type safety when working with plan features, particularly for the enterprise plan.


22-22: Simplified component type declaration

Changing from React.FC<BillingPlansProps> to FC<BillingPlansProps> makes the code cleaner and more consistent with the imports.


55-61: Added includedUsage property to enterprise plan

The includedUsage property with -2 values for seats, keys, mtCredits, and translations properly aligns with the new schema requirements and ensures the enterprise plan matches the updated model.


66-66: Added metricType property to enterprise plan

The addition of metricType: 'KEYS_SEATS' property supports the new feature of keys/strings as a metric, as mentioned in the PR title.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanCreateForm.tsx (7)

5-12: Updated imports for enhanced organization functionality

The new imports support the enhanced organization-specific plan creation workflow, introducing the CloudPlanTemplateSelectorField and useCreatingForOrganization hook.


14-14: Consistent component naming

Renamed from CreateCloudPlanForm to CloudPlanCreateForm for better naming consistency across the codebase.


25-30: Improved code organization

The onSaveSuccess function has been moved before its first use, improving readability and code organization.


32-37: Enhanced organization integration

Added the useCreatingForOrganization hook to streamline handling of organization-specific data and plan naming, which simplifies the component logic.


45-45: Improved organization ID handling

Changed the logic for forOrganizationIds to be based on the presence of forOrganization.id rather than the public property, which is more direct and less error-prone.


62-62: Simplified disabled state logic

Updated the disabled state to directly check for forOrganization.id existence, making the code more readable and maintainable.


69-72: Enhanced plan creation UI

Added the CloudPlanTemplateSelectorField component and properly passed the organization data to CreatingPlanForOrganizationAlert, improving the user experience when creating plans.

backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/AdditionalTestDataSaver.kt (1)

10-16: Added lifecycle hook methods to AdditionalTestDataSaver

These new methods (beforeSave, afterSave, beforeClean, afterClean) provide essential extension points for the test data building process, allowing for more flexible and comprehensive test setup and teardown. Default empty implementations ensure backward compatibility with existing implementations.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanSelectorField.tsx (4)

1-7: Well-structured imports

The imports are appropriately organized, covering form management (Formik), error handling, types from the billing API schema, and the CloudPlanSelector component.


8-14: Clearly defined props interface

The CloudPlanSelectorFieldProps interface is well-defined with appropriate types, including an optional organizationId, callback for plan changes, and filter flag.


16-30: Effective form field integration

The component properly integrates with Formik using useField and handles errors with useFieldError. The onChange function correctly updates the field value when a plan is selected.


31-43: Well-structured CloudPlanSelector implementation

The CloudPlanSelector is properly configured with all necessary props including value binding, callbacks, organization context, and translated labels with error state.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanEditForm.tsx (1)

1-76: Form handling and API integration looks good.

This component properly fetches plan data and handles form submission with appropriate UI feedback. The component integrates well with the SelfHostedEePlanForm for rendering the actual form fields.

backend/data/src/main/kotlin/io/tolgee/model/Permission.kt (2)

9-9: Consolidating import statements is a good practice.

The change from multiple specific imports to a wildcard import for Jakarta Persistence classes helps reduce import clutter.


138-140: Added @configurable annotation to PermissionListeners.

The @Configurable annotation allows dependency injection for instances created outside of Spring container. This is appropriate for entity listeners.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx (4)

1-17: Import structure is well organized.

The imports are logically grouped between React/UI libraries and application-specific imports.


33-39: Good conditional logic for plan assignment eligibility.

The component correctly determines if an organization can be assigned a new plan based on its current subscription status, with helpful comments explaining the logic.


121-126: Well-defined type for form values.

The AssignCloudPlanValuesType type clearly defines the structure of the form values, making the code more maintainable.


93-94:

Details

❓ Verification inconclusive

Verify TypeScript version compatibility with 'satisfies' operator.

The satisfies operator is a newer TypeScript feature (4.9+). Ensure your project's TypeScript version supports it.


🏁 Script executed:

#!/bin/bash
# Check TypeScript version in package.json
cat package.json | grep -A 5 "devDependencies" | grep typescript

Length of output: 64


Action: Manually Verify TypeScript Version Compatibility

The initial script didn’t return a TypeScript version from the devDependencies in package.json. This might indicate that TypeScript isn’t explicitly defined there or is located elsewhere (e.g., in dependencies). Please manually confirm that your project is using TypeScript v4.9 or later to ensure compatibility with the satisfies operator used in the snippet at lines 93-94 of webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx.

  • Check both dependencies and devDependencies in package.json for the TypeScript version.
  • Verify that the TypeScript version is 4.9 or higher.
webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/SubscriptionsCustomPlanItem.tsx (1)

9-14: Strongly Typed Plan Property Looks Good.

Defining plan as an object with specific fields instead of relying on a broader model improves clarity and maintainability. This is a neat approach.

e2e/cypress/common/translations.ts (1)

41-45: Good Use of the New Class-based Approach.

Replacing direct logic with E2TranslationsView and keyCreateDialog encapsulates the translation creation process cleanly, improving readability and maintainability.

backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.kt (3)

212-212: Good addition of @transactional annotation.

Adding the @transactional annotation at the method level provides more explicit and fine-grained control over transaction boundaries compared to a class-level annotation.


230-231: Method call parameters reordered to match the new signature.

The call to removeUser has been updated to match the new parameter order (userId first, then organizationId).


234-237:

Details

✅ Verification successful

Improved parameter ordering and transaction control.

Good changes:

  1. Added @transactional annotation to provide explicit transaction management
  2. Reordered method parameters to have userId first, then organizationId, which is a more consistent pattern

This change affects all call sites of this method - ensure all have been updated to match the new parameter order.


🏁 Script executed:

#!/bin/bash
# Check if all calls to removeUser have been updated to the new parameter order

# Find all calls to removeUser method to verify parameter order
rg "removeUser\s*\(" --type kotlin

Length of output: 752


Parameter Ordering and Transaction Management Verified

The changes are confirmed:

  • The use of the @Transactional annotation now provides explicit transaction management.
  • The parameter ordering update (placing userId before organizationId) has been consistently applied across all call sites, including in:
    • backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.kt
    • backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.kt
    • backend/app/src/test/kotlin/io/tolgee/service/organizationRole/OrganizationRoleCachingTest.kt

All invocations reflect the new ordering.

backend/app/src/test/kotlin/io/tolgee/service/project/ProjectServiceTest.kt (2)

25-25: Service responsibility change for project deletion.

The dependency has been changed to use the specialized ProjectHardDeletingService instead of a more generic service. This is a good separation of concerns.


136-136: Method call updated to use dedicated deletion service.

The call has been updated to use projectHardDeletingService.hardDeleteProject instead of projectService.hardDeleteProject, which aligns with the dependency change.

backend/data/src/main/kotlin/io/tolgee/repository/OrganizationRepository.kt (3)

232-245: Good implementation for retrieving organizations for usage metrics.

This method efficiently retrieves organizations where a user is counted as a seat, properly excluding translation agencies. The query is well-structured with clear comments explaining its purpose.


247-252: Good implementation for seat counting.

The method efficiently retrieves all user IDs in an organization for seat counting purposes. It reuses the query constant defined in the companion object, which promotes code reuse.


254-265: Well-structured query constant for user counting.

The companion object with a constant for the query is a good approach for reusing complex JPQL queries. The query is properly documented and handles several important cases:

  • Excludes deleted organizations and projects
  • Excludes disabled users
  • Properly handles the relationship between users, organizations, and projects

This promotes code reuse and maintainability.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (5)

1-7: Well-structured type definitions based on API schema.

Good approach importing types from the generated API schema and defining more specific types for use in the application. This ensures type safety and consistency with the backend API.


8-10: Good extraction of MetricType from schema.

Exporting the MetricType as a separate type allows it to be reused throughout the application, which improves type safety and maintainability.


11-21: Well-defined interface for plan form data.

The GenericPlanFormData interface properly captures the common properties needed for all subscription plans. This creates a good foundation for more specific plan types.


23-26: Good extension of base interface for cloud plans.

The CloudPlanFormData interface properly extends the generic plan data with cloud-specific fields like type and metricType. This creates a clear type hierarchy.


28-29: Direct use of schema type for self-hosted plans.

Using the schema type directly for SelfHostedEePlanFormData is efficient. Consider adding a custom interface in the future if you need to extend this type with frontend-specific properties.

webapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanFormFields.tsx (2)

65-75: Recheck default trial date usage.

When toggling this switch on, the trial end date is force-set to defaultTrialDate. This could surprise users if they intend to choose a different future date. Confirm that forcing a static default is intended, or allow the user to pick right away.


115-132: Handle edge cases for alert conditions.

The code only checks ( !selectedPlan?.free && !isTrial ) or isCurrentlyPaying. If selectedPlan is null, or if certain states overlap, the user might see no alert or the wrong alert. Confirm all edge cases are covered (e.g., free plans plus partial trial).

backend/data/src/main/kotlin/io/tolgee/service/project/ProjectHardDeletingService.kt (1)

93-97: Ensure concurrency safety with the @Async event listener.

onProjectSoftDeleted calls self.hardDeleteProject(...) asynchronously. If there are multiple simultaneous deletions or concurrency with the same project, ensure race conditions or partial deletions do not occur. Consider additional locking or concurrency checks if needed.

webapp/src/ee/billing/component/IncludedItem.tsx (5)

4-5: Imports look good.
No issues spotted with the addition of KeysHint and StringsHint or the usage of React types.


12-19: Docstring and data-cy prop usage looks appropriate.
Using data-cy is a common approach for Cypress testing. No issues here.


22-61: Logic for IncludedStrings is clear and follows the new container approach.
The separation of labels into negotiable, unlimited, and numeric states is consistent. This increases clarity around plan usage display.


104-143: IncludedCredits implementation is straightforward.
The usage of MtHint helps unify the hint concept for credits. Looks consistent with the other included items.


145-184: IncludedSeats aligns well with the existing pattern.
Using <span /> for the hint is fine, but keep in mind consistency in styling if you expand seat hints in the future.

backend/app/src/test/kotlin/io/tolgee/service/project/ProjectHardDeletingServiceTest.kt (6)

34-45: Test for MT Settings deletion looks good.
Ensures the project is properly deleted even when machine translation is configured. Nice transactional usage.


47-77: Batch jobs deletion test is robust.
Verifying the batch job completes before hard deletion is an excellent check. This ensures no dangling references remain.


79-102: Comprehensive testing of big meta data deletion.
Storing BigMetaDto and verifying subsequent removal helps ensure the hard delete covers all data layers.


104-111: Content Delivery Config test is straightforward.
Good to see coverage for specialized configs. Ensures data consistency post-deletion.


113-120: Webhook deletion coverage is thorough.
Verifying that webhooks do not linger is critical for security and event-flow correctness.


122-124: refresh() extension method is a convenient helper.
Makes the test code more readable by simplifying entity reloading.

backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationStatsService.kt (4)

3-3: Import statement is clarified by referencing constants from the repository.
No issues with the new import.


35-41: getSeatCountToCountSeats method is succinct.
It directly counts distinct users, which is straightforward and maintainable. Ensure that the query’s behavior matches all membership scenarios.


43-53: getTranslationCount method helps clarify usage beyond just key counts.
Filtering out empty text is beneficial when evaluating usage-based metrics. Looks correct for counting stored translations.


55-63: getKeyCount strongly distinguishes project keys from translations.
Ensures only non-deleted project keys get counted. This aligns with the new usage metrics approach.

backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt (4)

14-14: New event import for soft deletion detection.
This indicates a move toward an event-driven handling of project lifecycle changes. No issues detected.


52-63: Constructor dependency adjustments enhance clarity.
Replacing old references and removing less-relevant services keeps this service more focused. Lazy injection for languageService is still a valid approach here.


91-93: findDeleted method for retrieving soft-deleted projects.
Explicitly referencing the project repository’s findDeleted helps maintain separation of concerns.


213-214: Soft-delete event publishing.
Publishing OnProjectSoftDeleted fosters an event-driven architecture and decouples deletion-related side effects from the service logic.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/UsageReportingTest.kt (7)

41-43: Good refactoring of the test method.

The test has been simplified by extracting common setup code into the saveSubscription() method, improving maintainability.


80-89: Well-structured test for keys usage reporting.

This test clearly validates both the creation and deletion of keys with appropriate assertions. The test follows the Arrange-Act-Assert pattern correctly.


91-106: Good optimization check for batched requests.

This test verifies an important optimization - ensuring that creating multiple keys doesn't result in excessive API requests. The assertion at line 105 is particularly valuable as it confirms the batching behavior.


109-117: Comprehensive coverage of deletion scenarios.

These tests ensure key usage reporting works correctly when projects and organizations are deleted, covering important edge cases in the reporting system.

Also applies to: 119-127


129-147: Excellent test helper for reducing code duplication.

The testWithBaseTestData function eliminates repetitive code across multiple tests, making the test suite more maintainable and readable.


168-171: Good implementation of key assertion method.

The assertKeys method follows the same pattern as assertSeats, maintaining consistency in the codebase.


173-174: Good extraction of helper method for request parsing.

Extracting parseRequestArgs improves code reuse between the assertion methods and provides a single point for any future parsing logic changes.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (6)

53-53: Appropriate dependency injection for key management.

Adding the KeyService dependency is necessary for the new key-related features and follows the existing dependency injection pattern.


82-82: Good implementation for key count retrieval.

Adding a call to keyService.countAllOnInstance() to get the key count ensures that license key setup includes the necessary key usage information.


98-102: Updated DTO to include key usage.

The SetLicenseKeyLicensingDto has been properly updated to include the key count and instance ID.


115-117: Appropriate handling of subscription model properties.

The subscription entity now stores additional information about included keys, seats, and the pay-as-you-go status from the response.


275-279: Good flexibility in the reportUsage method.

Making both keys and seats parameters optional with default values of null provides backward compatibility and flexibility.


319-319: Updated DTO construction for usage reporting.

The ReportUsageDto has been properly updated to include the keys and seats parameters.

backend/api/src/main/kotlin/io/tolgee/hateoas/organization/PublicUsageModel.kt (4)

11-16: Well-documented new pay-as-you-go property.

The new property clearly distinguishes between pay-as-you-go and fixed plans with comprehensive documentation.


30-31: Useful addition of total credits usage tracking.

The usedMtCredits field provides important information for tracking the total credits used, including those over the limit.


54-69: Comprehensive key usage metrics.

The addition of included, current, and limit fields for keys provides a complete picture of key usage status with clear documentation.


70-85: Comprehensive seat usage metrics.

The addition of included, current, and limit fields for seats provides a complete picture of seat usage status with clear documentation.

backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.kt (3)

10-11: Appropriately replaced TranslationsLimitProvider with LimitsProvider.

The import and constructor parameter have been updated to use the more comprehensive LimitsProvider, which aligns with the expanded usage metrics.

Also applies to: 92-92


316-339: Well-structured usage model construction.

The changes to retrieve and pass usage metrics for keys and seats are comprehensive and well-organized. The code correctly leverages the new LimitsProvider to access all necessary limits in a structured way.


262-262:

Details

❓ Verification inconclusive

Verify parameter order change in removeUser method.

The order of parameters in the call to organizationRoleService.removeUser has been reversed from (organizationId, userId) to (userId, organizationId). This change could potentially cause issues if the implementation expects parameters in a specific order.

Please confirm that the removeUser method signature in the OrganizationRoleService class has been updated to match this new parameter order. You can run the following script to check:


🏁 Script executed:

#!/bin/bash
# Check if the OrganizationRoleService.removeUser method has the correct parameter order

ast-grep --pattern $'fun removeUser($parameter1: Long, $parameter2: Long)' | grep "organizationRoleService"

Length of output: 106


Action: Verify Updated Parameter Order in removeUser Method

We need to confirm that the OrganizationRoleService.removeUser method now accepts parameters in the new order (userId: Long, organizationId: Long), matching the updated invocation in OrganizationController. The previous script did not return any output, so it's important to verify the method signature manually.

Please run the following shell script to locate the OrganizationRoleService.kt file and inspect the removeUser method signature:

#!/bin/bash
# Locate the OrganizationRoleService file and check the removeUser method signature

file=$(fd "OrganizationRoleService.kt" | head -n 1)
if [ -z "$file" ]; then
  echo "OrganizationRoleService.kt not found."
else
  echo "Found OrganizationRoleService file: $file"
  rg "fun removeUser\(" "$file"
fi

Ensure that the method signature shows the parameters in the order (userId: Long, organizationId: Long). If it does not, please update the signature accordingly.

backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt (11)

7-7: New import for Project
This import is required for the new saveProjectInvitations method. The usage is correct and consistent.


13-13: New invitation service import
Adding InvitationService is necessary for the newly introduced invitation-saving methods. Looks good.


73-73: New constructor parameter
Injecting InvitationService into this service makes sense given the invitation-saving methods. The dependency usage is clear and appropriate.


87-87: Additional savers invoked before saving
The call to runBeforeSaveMethodsOfAdditionalSavers ensures pre-save logic from each saver is applied. Verify that creating a separate transaction here aligns with your concurrency and rollback needs.


97-97: Saving organization data within a separate transaction
Refactoring and calling saveOrganizationData(builder) early ensures organization and user data exist before project creation. This looks well-reasoned.


115-116: Invoking additional savers after saving
runAfterSaveMethodsOfAdditionalSavers complements the before-save logic, allowing post-save tasks to run. The approach is consistent and clear.


121-121: Invoking additional savers before cleaning
runBeforeCleanMethodsOfAdditionalSavers ensures custom logic is executed prior to test data cleanup. The placement is logical.


139-139: Invoking additional savers after cleaning
runAfterCleanMethodsOfAdditionalSavers completes the before/after pattern for cleaning. The separation of responsibilities is clear and maintainable.


174-174: Saving organization invitations
Calling saveOrganizationInvitations(builder) ensures that any invitations linked to organization roles are persisted. Implementation seems straightforward.


220-220: Saving project invitations
saveProjectInvitations persistently stores invitations associated with a given Project. Please confirm invitations remain valid and properly referenced once this transaction completes.


391-392: Batch saving of permissions
Collecting and saving permissions in one go is both clear and efficient. This approach should reduce overhead.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEePlanModel.kt (1)

23-23: Add the isPayAsYouGo property to support the new pricing model.

The addition of this property aligns with the PR objective to add keys/strings as a metric. This Boolean flag will help differentiate between traditional fixed-price plans and the new pay-as-you-go pricing model.

Consider adding KDoc comments to document the purpose and behavior of this new property.

const tooltipTitle = !isExclusive ? (
<T keyName="admin-billing-cannot-edit-plan-tooltip" />
) : (
<T keyName="admin_bulling_edit_plan_button" />
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix typo in translation key.

There appears to be a typo in the translation key - "bulling" should likely be "billing".

-    <T keyName="admin_bulling_edit_plan_button" />
+    <T keyName="admin_billing_edit_plan_button" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<T keyName="admin_bulling_edit_plan_button" />
<T keyName="admin_billing_edit_plan_button" />

variant="contained"
data-cy="administration-subscriptions-assign-plan-save-button"
>
<T keyName="administartion_billing_assign-plan-save_button" />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in translation key

There's a typo in the translation key "administartion_billing_assign-plan-save_button" - it should be "administration" instead of "administartion".

-      <T keyName="administartion_billing_assign-plan-save_button" />
+      <T keyName="administration_billing_assign-plan-save_button" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<T keyName="administartion_billing_assign-plan-save_button" />
<T keyName="administration_billing_assign-plan-save_button" />

Comment on lines +12 to +14
export const SubscriptionsPopoverCustomPlans = <T,>(
props: SubscriptionsPopoverCustomPlansProps<any>
) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix generic type parameter usage in component definition.

The component is declared with a generic type parameter <T>, but the props type is hardcoded as SubscriptionsPopoverCustomPlansProps<any>. This defeats the purpose of making the component generic and reduces type safety.

-export const SubscriptionsPopoverCustomPlans = <T,>(
-  props: SubscriptionsPopoverCustomPlansProps<any>
+export const SubscriptionsPopoverCustomPlans = <T,>(
+  props: SubscriptionsPopoverCustomPlansProps<T>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const SubscriptionsPopoverCustomPlans = <T,>(
props: SubscriptionsPopoverCustomPlansProps<any>
) => {
export const SubscriptionsPopoverCustomPlans = <T,>(
props: SubscriptionsPopoverCustomPlansProps<T>
) => {

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (16)
backend/data/src/main/kotlin/io/tolgee/util/entityPreCommitEventUsageUtil.kt (1)

7-13: Add KDoc to clarify the purpose of this utility function

The function logic is sound and properly handles different event types, but lacks documentation explaining its purpose and usage. Consider adding KDoc to describe that this function determines usage change amounts based on entity events.

+/**
+ * Returns the usage increase amount based on the entity event type:
+ * - 1 for entity persistence (creation)
+ * - -1 for entity deletion
+ * - 0 for other event types
+ *
+ * This is used for tracking resource usage changes in the system.
+ */
fun EntityPreCommitEvent.getUsageIncreaseAmount(): Long {
  return when (this) {
    is OnEntityPrePersist -> 1
    is OnEntityPreDelete -> -1
    else -> 0
  }
}
backend/data/src/main/kotlin/io/tolgee/model/Organization.kt (1)

71-73: Consider annotation redundancy on disableActivityLogging property.

The property is marked with both @Transient and @Column(insertable = false, updatable = false), which might be redundant. @Transient indicates the property should be excluded from persistence entirely, while the @Column annotation with these parameters suggests it maps to a column but should not be included in SQL statements.

If the intention is to completely exclude this from database persistence, using only @Transient would be sufficient. If it should map to an existing column but not be part of DML operations, then only the @Column annotation would be appropriate.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (1)

6-6: Simplified imports with wildcard.

While wildcard imports can reduce the number of import statements, they may decrease code clarity by obscuring which specific classes are being used. Consider reverting to explicit imports if there aren't too many classes imported from this package.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeOnUserCountChangedListener.kt (2)

14-38: Listener implementation looks good but consider improving exception handling.

The listener correctly reports user seat usage and checks limits when user count changes. The early return for Tolgee Cloud environments (checking billingConfProvider().enabled) prevents duplicate reporting in cloud environments.

However, there's a minor issue with the exception handling:

Consider improving the exception handling by:

  1. Using a more informative log message that includes the exception details:
- logger.debug("No active subscription, skipping usage reporting.")
+ logger.debug("No active subscription, skipping usage reporting.", e)
  1. Adding a catch block for any other unexpected exceptions to ensure robust operation:
catch (e: Exception) {
  logger.error("Error while processing user count change", e)
}
🧰 Tools
🪛 detekt (1.23.7)

[warning] 34-34: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


24-26: Clarify the early return condition with a comment.

The current comment "we don't want this to happen on Tolgee Cloud" is somewhat ambiguous.

- // we don't want this to happen on Tolgee Cloud
+ // Skip usage reporting on Tolgee Cloud as it's handled differently there
  if (billingConfProvider().enabled) {
    return
  }
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (1)

6-6: Verify unused import.

The BaseTestData import appears to be unused in this file. Consider removing it if it's not needed.

-import io.tolgee.development.testDataBuilder.data.BaseTestData
backend/data/src/main/kotlin/io/tolgee/api/EeSubscriptionDto.kt (1)

21-23: Consider adding validation constraints and documentation to the new fields.

The new properties add key/seat tracking and pay-as-you-go functionality to the subscription model, which is a good addition. However, I noticed that unlike some of the other fields in this DTO, these new fields don't have any validation annotations.

Consider:

  1. Adding @field:NotNull or similar constraints if these fields are required
  2. Adding documentation comments to explain the purpose of each field
  3. Potentially providing default values for backwards compatibility if appropriate
+ // Number of translation keys included in the subscription plan
  var includedKeys: Long,
+ // Number of user seats included in the subscription plan
  var includedSeats: Long,
+ // Whether this subscription allows usage beyond the included limits for additional fees
  var isPayAsYouGo: Boolean
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (3)

40-47: Good test for unlimited keys scenario.

This test verifies that the application allows creating keys when the subscription has unlimited keys (indicated by -1).

- saveSubscription{
+ saveSubscription {

62-78: Well-designed subscription setup method with good defaults.

The saveSubscription method provides reasonable defaults while allowing test-specific customization through the lambda parameter. This is a good Kotlin pattern.

Consider setting the default values in the parameter rather than the method body for better clarity:

- private fun saveSubscription(build: EeSubscription.() -> Unit = {}) {
+ private fun saveSubscription(
+   build: EeSubscription.() -> Unit = {
+     // Default overrides can be specified here if needed
+   }
+ ) {

80-87: Good test data generation helper.

The saveTestData method provides a clean way to generate test data with a configurable number of keys, which helps make the tests more readable.

Consider adding a test case that verifies behavior when exactly at the limit (not just over or unlimited):

@Test
fun `does not throw when exactly at the limit`() {
  saveSubscription {
    includedKeys = 1
  }
  val testData = saveTestData(0) // Start with 0 keys
  createKey(testData) // Add 1 key, which should be exactly at the limit
}
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (4)

23-31: Consider adding class-level KDoc documentation.

Adding documentation about the purpose of this test class and what functionality it's testing would improve maintainability and help other developers understand the test coverage.

@SpringBootTest()
+/**
+ * Tests the key usage reporting functionality for subscription management.
+ * Verifies that key counts are correctly reported when keys are created, deleted,
+ * and when projects or organizations are deleted.
+ */
class KeyUsageReportingTest : AbstractSpringTest() {

88-106: Empty thenAnswer block could be improved.

The thenAnswer block in the mock setup is empty. If no response is needed, consider adding a comment to explain why, or provide a default response if appropriate.

thenAnswer {
+  // No specific response needed for this test
}

93-97: Consider extracting the API endpoint URL to a constant.

The URL "/v2/public/licensing/report-usage" is hard-coded in the test. For better maintainability, consider extracting it to a constant or configuration value.

+private companion object {
+  const val REPORT_USAGE_ENDPOINT = "/v2/public/licensing/report-usage"
+}

eeLicenseMockRequestUtil.mock {
  whenReq {
    this.method = { it == HttpMethod.POST }
-    this.url = { it.contains("/v2/public/licensing/report-usage") }
+    this.url = { it.contains(REPORT_USAGE_ENDPOINT) }
  }

108-121: Consider using fixed dates for better test reproducibility.

Using Date() creates a date with the current time, which might make tests non-deterministic. Consider using fixed dates for better test reproducibility.

+private companion object {
+  val FIXED_DATE = Date(1672531200000) // 2023-01-01
+}

private fun saveSubscription() {
  eeSubscriptionRepository.save(
    EeSubscription().apply {
      licenseKey = "mock"
      name = "Plaaan"
      status = SubscriptionStatus.ERROR
-      currentPeriodEnd = Date()
+      currentPeriodEnd = FIXED_DATE
      cancelAtPeriodEnd = false
      enabledFeatures = Feature.values()
-      lastValidCheck = Date()
+      lastValidCheck = FIXED_DATE
      isPayAsYouGo = true
    },
  )
}
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (2)

60-63: Consider adding error handling to onOrganizationDeleted.

Unlike the onActivity method, onOrganizationDeleted doesn't use runSentryCatching. Consider adding it for consistency in error handling across all event listeners.

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
fun onOrganizationDeleted(event: BeforeOrganizationDeleteEvent) {
-  onKeyCountChanged()
+  runSentryCatching {
+    onKeyCountChanged()
+  }
}

65-73: Consider more detailed error handling for reporting issues.

The method catches NoActiveSubscriptionException but might miss other potential exceptions during reporting. Consider using runSentryCatching here as well for consistency or adding more specific exception handling.

fun onKeyCountChanged() {
-  try {
+  runSentryCatching {
+    try {
      val keys = keyService.countAllOnInstance()
      val subscription = eeSubscriptionService.findSubscriptionDto()
      eeSubscriptionService.reportUsage(subscription = subscription, keys = keys)
-  } catch (e: NoActiveSubscriptionException) {
-    logger.debug("No active subscription, skipping usage reporting.")
-  }
+    } catch (e: NoActiveSubscriptionException) {
+      logger.debug("No active subscription, skipping usage reporting.")
+    }
+  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99022ca and 079465d.

📒 Files selected for processing (14)
  • backend/data/src/main/kotlin/io/tolgee/api/EeSubscriptionDto.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/model/Organization.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/util/entityPreCommitEventUsageUtil.kt (1 hunks)
  • backend/testing/src/main/kotlin/io/tolgee/CleanDbTestListener.kt (2 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeOnUserCountChangedListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt (3 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (8 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (2 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/FreePlanSeatLimitTest.kt (2 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/FreePlanSeatLimitTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt
  • backend/testing/src/main/kotlin/io/tolgee/CleanDbTestListener.kt
🧰 Additional context used
🧬 Code Graph Analysis (3)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (1)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1)
  • onKeyCountChanged (56-58)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (2)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1)
  • saveSubscription (108-121)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (1)
  • saveSubscription (79-92)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (2)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (1)
  • onKeyCountChanged (65-73)
backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (1)
  • executeInNewTransaction (268-274)
🪛 detekt (1.23.7)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeOnUserCountChangedListener.kt

[warning] 34-34: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (45)
backend/data/src/main/kotlin/io/tolgee/model/Organization.kt (1)

43-43: Implementation of EntityWithId interface looks good.

The Organization class now implements the EntityWithId interface, which is consistent with the existing id property at line 31 that was already marked with override. This enhancement improves entity identification capabilities while maintaining backward compatibility.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (3)

14-16: Good change to constructor injection.

Switching from field injection with @Autowired to constructor injection is a best practice. This approach makes dependencies explicit, enhances testability, and ensures the class cannot be instantiated without its required dependencies.


33-37: New includedUsage property aligns with system changes.

The addition of the includedUsage property with specific values for seats and keys aligns with the broader system changes from translation-slots to a seats-and-keys model. The hardcoded values (10 for both) seem appropriate for testing.


40-40: New pricing model flag added.

Adding the isPayAsYouGo property indicates whether the plan follows a pay-as-you-go pricing model, which aligns with the PR objective of adding keys/strings as a metric. The default value of false is reasonable for a mocked plan in tests.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeOnUserCountChangedListener.kt (1)

31-33: Consider adding error handling for the checkSeatCount operation.

The checkSeatCount method likely throws exceptions if limits are exceeded, but there's no specific handling for this case.

Verify that checkSeatCount exceptions are properly propagated or handled as expected. If this method is intended to throw exceptions that should interrupt the transaction, then the current implementation is correct.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (8)

12-13: LGTM: Clean dependency additions for key metrics.

The imports and constructor injection for KeyService are properly set up to support the new keys/strings metric functionality.

Also applies to: 18-19, 23-24, 50-51


78-80: Good implementation of keys count in license key setup.

The addition of keyService.countAllOnInstance() and including keys in SetLicenseKeyLicensingDto properly integrates the new metric into the license validation process.

Also applies to: 95-100


112-114: Clear storage of subscription limits.

The additional entity properties for keys, seats, and pay-as-you-go status are well-structured and properly saved from the response data.


246-262: Well-structured seat limit check logic.

The checkSeatCount method properly handles different scenarios (pay-as-you-go, cloud, etc.) and throws appropriate exceptions based on the subscription status.


264-282: The key count check implementation looks good.

This implementation properly verifies key limits based on the subscription type, with appropriate exception handling for limit exceedance.


284-296: Good enhancement to the reportUsage method.

The addition of the optional keys parameter with default value allows for backward compatibility while supporting the new metric.


321-329: Properly updated reportUsageRemote to handle keys.

The method now correctly accepts and includes the keys parameter in the reporting DTO.


357-358: Clean implementation of isCloud property.

The property provides a clear way to determine if the application is running in cloud mode, which is used in the limit check methods.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (7)

1-1: Import organization looks good.

The imports are properly organized with the new additions for BaseTestData and EeLicensingMockRequestUtil, supporting the refactored test structure and JUnit 5 annotations.

Also applies to: 6-7, 12-12


24-24: Class renaming enhances clarity.

Renaming from UsageReportingTest to SeatUsageReportingTest provides better specificity, clearly indicating the test's focus on seat usage reporting rather than general usage reporting. This aligns well with the PR objective of adding keys/strings as a separate metric.


34-37: Good test initialization practice.

Moving the initialization of eeLicenseMockRequestUtil to a dedicated @BeforeEach method follows best practices for test setup. This ensures proper initialization before each test and improves code organization.


40-41: Test method name and implementation improved.

The test method has been renamed from "it checks for subscription changes" to "it reports seat usage", better reflecting its specific purpose. The implementation is more focused by extracting the subscription setup to a separate method.


79-92: Good extraction of subscription setup.

Extracting the subscription creation logic to a dedicated method improves code organization and readability. The addition of isPayAsYouGo = true supports the new pay-as-you-go model for usage-based billing, which aligns with the PR objective of adding keys/strings as a metric.


95-95: Good refactoring of assertion logic.

Using the new parseRequestArgs() method in the assertSeats method separates parsing from assertion logic, making the code cleaner and more maintainable.


99-100: Helpful utility method added.

The addition of the parseRequestArgs() method is a good refactoring that encapsulates the JSON parsing logic, improving code reuse and separation of concerns.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (5)

1-18: LGTM! Good imports and class setup.

The imports are appropriate for the test class, and you've properly included the necessary types for testing key limits.


19-28: Class setup looks well-structured with proper dependency injection.

Good use of Spring's test annotations and dependency injection with @Autowired and @MockBean for the RestTemplate.


29-38: Great test case validating key limit enforcement.

This test confirms that the application correctly throws a PlanKeysLimitExceeded exception when attempting to exceed the subscription's key limit.


49-56: Nice test case for pay-as-you-go subscription model.

The test correctly verifies that pay-as-you-go subscriptions don't throw exceptions when exceeding the included key count.


58-60: LGTM! Helper method for key creation.

Clean and concise helper method for key creation that improves test readability.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (7)

1-24: Well-structured test class with appropriate imports and Spring Boot setup.

The test class is properly set up with the necessary imports, annotations, and inheritance. Using @SpringBootTest is appropriate for this type of integration testing.


32-37: Setup method is concise and focused.

The setup method correctly initializes the mock utility before each test.


39-48: Test correctly verifies key reporting lifecycle.

This test effectively verifies that keys are reported when created and the count is updated when keys are deleted.


50-66: Good performance test for request batching.

This test properly verifies that the system batches requests rather than sending one for each key operation, which is an important performance consideration.


68-76: Tests cover important edge cases.

Both tests validate that key counts are correctly updated when projects or organizations are deleted, ensuring proper cleanup in the reporting system.

Also applies to: 78-86


123-126: Clear and concise assertion helper method.

The assertKeys extension function is a good example of creating readable assertions in tests.


128-130: Well-implemented helper method for parsing request arguments.

This extension function cleanly parses the HTTP entity body into a map for easier assertion checking.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (5)

1-19: Well-organized imports and package structure.

The imports are well-organized and contain everything needed for the implementation.


20-32: Good class documentation and dependency injection.

The class documentation clearly explains the purpose and scenarios where reporting occurs. The constructor properly injects all necessary dependencies.


43-57: Good error handling with detailed condition checking.

The Sentry error catching and detailed condition checks for different entity modifications effectively determine when to trigger reporting.


37-58:

Details

✅ Verification successful

Verify condition for skipping usage reporting.

The method skips reporting if billing is enabled (lines 39-41). This seems counterintuitive - one would expect to report usage especially when billing is enabled. Is this intentional?


🏁 Script executed:

#!/bin/bash
# Let's look for other places that use the billingConfProvider().enabled check
rg "billingConfProvider\(\)\.enabled" -A 3 -B 3

Length of output: 2688


Clarification on Billing Reporting Condition

After verifying similar implementations across the codebase, it’s clear that the check using billingConfProvider().enabled is intentionally used to skip usage reporting in environments where billing is enabled (e.g., on Tolgee Cloud). We see this same pattern in both EeOnUserCountChangedListener.kt and EeKeyCountLimitListener.kt. While this may seem counterintuitive at first glance, it’s by design to prevent reporting in cloud environments. For clarity and long-term maintainability, consider adding a brief inline comment explaining that usage reporting is skipped when billing is enabled.

  • Files with similar usage:
    • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeOnUserCountChangedListener.kt
    • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt

65-73:

Details

❓ Verification inconclusive

Ensure efficient key counting for large instances.

The method calls keyService.countAllOnInstance() which might be expensive for instances with many keys. Ensure this operation is optimized for performance, especially if called frequently.


🏁 Script executed:

#!/bin/bash
# Let's examine the implementation of countAllOnInstance() to understand performance implications
rg "countAllOnInstance|countAll" --type kotlin -A 5 -B 5

Length of output: 19846


Action Required: Reevaluate Key Counting Efficiency

The onKeyCountChanged() method directly invokes keyService.countAllOnInstance(), which simply delegates to a repository count query. Our verification shows that the implementation in KeyRepository executes a straightforward count (i.e., using a query similar to select count(k) from Key k ...). While this approach is common and may perform adequately on moderate datasets, it can become a performance bottleneck when dealing with very large numbers of keys.

  • Review Query Efficiency: Confirm that the database efficiently handles the count query under heavy loads.
  • Consider Caching: If profiling reveals performance issues, consider integrating a caching strategy (similar to what’s used in EeKeyCountLimitListener) to avoid repeated expensive queries.
  • Database Optimization: Ensure that proper indexes and query optimizations are in place for the count operation.
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (8)

1-16: No issues found.


17-26: No issues found.


27-28: Confirm concurrency safety of transaction-scoped listener.
Using @Scope(TransactionScopeConfig.SCOPE_TRANSACTION) ensures a new bean instance per transaction, which can reduce concurrency concerns. However, please verify that multiple concurrent events or multiple entity changes within a single transaction won't pose any issues for your usage counting and exception-throwing logic.


29-34: No issues found.


35-35: Verify storing key count in a mutable property.
Storing keyCount at the class level can be risky if multiple events occur within the same transaction. The code initializes keyCount from the DB only once; subsequent increments may not reflect fresh DB state if multiple changes happen. If that use case is expected, consider re-checking the DB.


37-45: Double-check skip condition if billing is enabled.
The check if (billingConfProvider().enabled || event.entity !is Key) skips usage verification whenever billing is enabled. Ensure this is intended; if usage limits should also be enforced while billing is enabled, you might need to invert or adjust the condition.


47-54: Confirm handling of negative usage or multiple increments.
If event.getUsageIncreaseAmount() ever returns a negative value (e.g., for key removals), this logic will decrement keyCount. Verify that negative usage is valid and tested. Also, updating keyCount only once from the DB may harm accuracy if multiple increments/decrements happen within the same transaction.


56-58: Looks good: direct limit check ensures effective exception throwing.
The call to eeSubscriptionService.checkKeyCount(keyCount!!) is straightforward and ensures that any limit violations surface before committing the transaction.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🔭 Outside diff range comments (1)
ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt (1)

1-86: 💡 Verification agent

🧩 Analysis chain

Verify database migration for new fields.

The addition of new entity fields will require database migration scripts to ensure existing data remains compatible. Please confirm that appropriate migration scripts have been created.


🏁 Script executed:

#!/bin/bash
# Check for migration files that might handle these new fields

echo "Searching for migration files that might handle the new fields..."
find . -type f -path "*/resources/db/migration/*" -o -path "*/resources/db/specific/*" | xargs grep -l "includedKeys\|includedSeats\|keysLimit\|seatsLimit\|isPayAsYouGo" 2>/dev/null || echo "No explicit migration files found for the new fields."

echo "Checking Liquibase changelog files..."
find . -type f -name "*.xml" -path "*/resources/db/*" | xargs grep -l "includedKeys\|includedSeats\|keysLimit\|seatsLimit\|isPayAsYouGo" 2>/dev/null || echo "No Liquibase changelog entries found for the new fields."

Length of output: 776


Database migration scripts for the new entity fields not detected

The new fields (includedKeys, includedSeats, keysLimit, seatsLimit, and isPayAsYouGo) in the EeSubscription entity currently do not have corresponding migration scripts. No migration files (including Liquibase changelogs) were found to update the database schema accordingly. Please add the necessary migration scripts (or update existing ones) to ensure that these changes are properly propagated in production.

  • Action Required: Create migration scripts to alter the database table for the self-hosted instance, adding columns to support these new fields.
♻️ Duplicate comments (1)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (1)

31-31: The testData property is never initialized or used.

You've declared a lateinit var testData: BaseTestData but there's no initialization of this property anywhere in the test class, and it's not used in any of the test methods.

Either remove this property if it's not needed, or initialize it properly in the setup method, likely using the data created there.

🧹 Nitpick comments (29)
backend/data/src/main/kotlin/io/tolgee/service/security/SignUpProcessor.kt (1)

18-18: Fix incomplete KDoc comment

The KDoc comment is missing a closing quotation mark after "us".

-  /** The answer for the "Where did you hear about us? */
+  /** The answer for the "Where did you hear about us?" */
backend/api/src/main/kotlin/io/tolgee/hateoas/limits/SelfHostedUsageLimitsModel.kt (1)

11-15: Consider adding Schema annotations for API documentation consistency.

The SelfHostedUsageLimitsModel class looks good overall. However, unlike LimitModel, the properties here don't have @Schema annotations. For consistency in API documentation, consider adding these annotations to describe each property's purpose.

class SelfHostedUsageLimitsModel(
+  @Schema(description = "Usage limits for keys in the self-hosted instance")
  val keys: LimitModel,
+  @Schema(description = "Usage limits for seats in the self-hosted instance")
  val seats: LimitModel,
+  @Schema(description = "Usage limits for machine translation credits in cents")
  val mtCreditsInCents: LimitModel,
) : RepresentationModel<SelfHostedUsageLimitsModel>()
backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededSeatsException.kt (1)

6-10: Add KDoc documentation to explain the purpose of this exception.

The exception class is well-structured and follows good design principles. However, adding documentation would improve maintainability and help other developers understand when and how to use this exception.

Consider adding KDoc comments explaining:

  • The purpose of this exception
  • When it should be thrown
  • What the required and limit parameters represent

Example:

/**
 * Exception thrown when the number of seats required exceeds the plan's spending limit.
 *
 * @param required The number of seats that was requested/required
 * @param limit The maximum number of seats allowed by the current plan
 */
class PlanSpendingLimitExceededSeatsException(required: Long, limit: Long) :
  BadRequestException(
    Message.SEATS_SPENDING_LIMIT_EXCEEDED,
    params = listOf(required, limit),
  )
backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededStringsException.kt (1)

6-10: Consider adding documentation to clarify exception purpose.

While the class name is descriptive, adding KDoc comments would improve maintainability and help other developers understand when this exception should be thrown, what the parameters represent, and how it should be handled.

+/**
+ * Exception thrown when a plan's spending limit for translations is exceeded.
+ * 
+ * @param required The number of translations requested/required
+ * @param limit The maximum limit allowed by the plan
+ */
class PlanSpendingLimitExceededStringsException(required: Long, limit: Long) :
  BadRequestException(
    Message.TRANSLATION_SPENDING_LIMIT_EXCEEDED,
    params = listOf(required, limit),
  )
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedKeysSeatsLimitsChecker.kt (2)

25-28: Consider naming consistency between method and parameter

The method is named checkSeatLimits() (singular "Seat") while the parameter is named seats (plural). Consider using consistent pluralization for better readability.

-  private fun checkSeatLimits() {
+  private fun checkSeatsLimits() {

34-35: Consider using constructor injection instead of ApplicationContext

While using ApplicationContext works, Spring's recommended approach is to use constructor injection for dependencies. This would make the code more testable and reduce the tight coupling to Spring's ApplicationContext.

class SelfHostedKeysSeatsLimitsChecker(
  private val keys: Long?,
  private val seats: Long?,
-  private val applicationContext: ApplicationContext,
+  private val selfHostedLimitsProvider: SelfHostedLimitsProvider,
) {
  // ...
  
  private val limits by lazy {
    selfHostedLimitsProvider.getLimits()
  }
  
-  private val selfHostedLimitsProvider by lazy { applicationContext.getBean(SelfHostedLimitsProvider::class.java) }
}
backend/data/src/main/kotlin/io/tolgee/dtos/UsageLimits.kt (3)

3-7: Documentation could be improved for clarity about translation slots

The comment mentions "Translation slots are legacy unit" without explaining what translation slots are or how they relate to the new metrics (keys/strings). Consider adding more context to help developers understand the transition from the legacy unit to the new metrics.


8-15: LGTM - Good structure for the UsageLimits data class

The data class structure is clean and appropriate for representing subscription plan limits. The properties are well-named and their purpose is clear.

Consider adding documentation for the mtCreditsInCents property to explain what MT credits are and their purpose in the system.


16-29: Consider adding utility methods and using constants for magic numbers

The Limit class is well-structured, but consider these improvements:

  1. Add a utility method to check if a limit is unlimited:
data class Limit(
  /**
   * What's included in the plan
   *
   * -1 if unlimited
   */
  val included: Long,
  /**
   *  What's the maximum value before using all the usage from spending limit
   *
   * -1 if unlimited
   */
  val limit: Long,
+  ) {
+    companion object {
+      const val UNLIMITED_VALUE = -1L
+    }
+
+    fun isIncludedUnlimited(): Boolean = included == UNLIMITED_VALUE
+    fun isLimitUnlimited(): Boolean = limit == UNLIMITED_VALUE
+    fun isFullyUnlimited(): Boolean = isIncludedUnlimited() && isLimitUnlimited()
}
  1. Consider adding validation to ensure business rules are enforced (e.g., included ≤ limit when both are not unlimited).
backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanLimitExceededStringsException.kt (1)

6-7: Consider aligning class name and error message constant.

The class name PlanLimitExceededStringsException uses the term "Strings" while the error message constant is Message.PLAN_TRANSLATION_LIMIT_EXCEEDED which uses "TRANSLATION". For better consistency and maintainability, consider either:

  1. Renaming the class to PlanLimitExceededTranslationsException to match the message constant
  2. Using a more consistent message constant like PLAN_STRINGS_LIMIT_EXCEEDED

This would improve code readability and make the relationship between the exception class and the error message more intuitive.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/GenericLimitChecker.kt (2)

18-21: Consider making the unlimited case more explicit.

While using a negative value to indicate unlimited is a common pattern, it would improve readability to explicitly document this convention, perhaps with a constant or enum.

  fun checkLimit() {
    if (limit.limit < 0) {
+     // Negative limit indicates unlimited usage, no checking needed
      return
    }

1-34: Consider adding a non-exception-based limit check method.

The current implementation only provides exception-based limit validation. Consider adding an additional method that returns a boolean or result object instead of throwing exceptions for cases where callers need to check limits without error handling.

// Example addition:
fun isLimitExceeded(): Boolean {
  if (limit.limit < 0) {
    return false
  }

  return if (!isPayAsYouGo) {
    required > limit.included
  } else {
    required > limit.limit
  }
}
ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (1)

84-92: Fix duplicate assertion for includedKeys.

The includedKeys property is being asserted twice (lines 86 and 89), which appears to be a copy-paste error.

        val subscription = getSubscription()
        subscription.assert.isNotNull
        subscription!!.includedKeys.assert.isEqualTo(10)
        subscription.includedSeats.assert.isEqualTo(10)
        subscription.seatsLimit.assert.isEqualTo(10)
-       subscription.includedKeys.assert.isEqualTo(10)
        subscription.keysLimit.assert.isEqualTo(10)
        subscription.isPayAsYouGo.assert.isEqualTo(false)
backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededKeysException.kt (2)

6-10: Consider documenting the distinction between different types of key limit exceptions.

Based on the AI summary, there appear to be multiple exception types related to key limits (e.g., PlanSpendingLimitExceededKeysException vs PlanLimitExceededKeysException). Consider adding documentation comments to clarify when each exception type should be used, especially explaining the difference between a "spending limit" and a regular "limit" for keys.


6-7: Consider adding parameter validation for edge cases.

While the implementation is clean, consider adding validation to ensure that required and limit are non-negative values, or document any assumptions about these parameters. This would make the exception class more robust against potential edge cases.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/KeysLimitChecker.kt (2)

7-10: The class definition follows good practices.

The class is focused on a single responsibility and takes appropriate parameters. Consider adding KDoc comments to explain the purpose of this class and its parameters, especially documenting when required would be null.

+/**
+ * Checks if the number of keys exceeds the plan limits.
+ *
+ * @param required The number of keys required, or null if no check is needed
+ * @param limits The usage limits to check against
+ */
 class KeysLimitChecker(
   private val required: Long?,
   private val limits: UsageLimits,
 ) {

14-28: Consistent use of the GenericLimitChecker pattern.

Good use of delegation to the GenericLimitChecker with appropriate exception providers. However, the formatting is inconsistent between the two exception creations - one spans multiple lines while the other is on a single line.

     spendingLimitExceededExceptionProvider = {
-      PlanSpendingLimitExceededKeysException(required = required, limit = limits.keys.limit)
+      PlanSpendingLimitExceededKeysException(
+        required = required,
+        limit = limits.keys.limit,
+      )
     },
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/SeatsLimitChecker.kt (3)

8-11: Class design looks good, consider adding KDoc documentation.

The class has a clear purpose and a clean constructor signature. Consider adding KDoc to document the purpose of this class and its parameters for better maintainability.

+/**
+ * Checks if the required number of seats exceeds the allowed limits.
+ *
+ * @param required The number of seats that need to be checked against limits, can be null
+ * @param limits The usage limits configuration containing seat limits information
+ */
open class SeatsLimitChecker(
  private val required: Long?,
  private val limits: UsageLimits,
) {

28-30: Consider safer null handling in exception methods.

While the null check in the check() method ensures required won't be null when these methods are called, it would be more robust to handle potential null values directly in these methods too, rather than using the !! operator.

-  open fun getIncludedUsageExceededException(): BadRequestException {
-    return PlanLimitExceededSeatsException(required!!, limit = limits.seats.limit)
-  }
+  open fun getIncludedUsageExceededException(): BadRequestException {
+    return PlanLimitExceededSeatsException(
+      required ?: 0L, 
+      limit = limits.seats.limit
+    )
+  }

32-34: Consider safer null handling in exception methods.

Similar to the previous method, consider handling potential null values more safely here.

-  open fun getSpendingLimitExceededException(): BadRequestException {
-    return PlanSpendingLimitExceededSeatsException(required!!, limit = limits.seats.limit)
-  }
+  open fun getSpendingLimitExceededException(): BadRequestException {
+    return PlanSpendingLimitExceededSeatsException(
+      required ?: 0L, 
+      limit = limits.seats.limit
+    )
+  }
ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (2)

70-76: Review new column "included_keys".
The addition of the included_keys column as a BIGINT with a default value of -1 and a non-null constraint appears intentional. Please verify that using -1 correctly signifies the intended "unlimited" or special value in your application logic, and that any consumers of this column correctly interpret that value.


77-83: Review new column "included_seats".
Similar to included_keys, the included_seats column is added as a BIGINT with a default value of -1 and a not-null constraint. Confirm that this sentinel value (-1) aligns with your business logic and that it will not cause unexpected behavior in existing data or downstream processing.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1)

98-105: Ensure test data generation is robust.

The saveTestData method creates keys based on the keyCount parameter, but consider adding validation to ensure keyCount is a positive value to avoid potential edge cases.

-  private fun saveTestData(keyCount: Long = 1): BaseTestData {
+  private fun saveTestData(keyCount: Long = 1): BaseTestData {
+    require(keyCount >= 0) { "keyCount must be a non-negative value" }
     val testData = BaseTestData()
     (1..keyCount).forEach {
       testData.projectBuilder.addKey("$it")
     }
     testDataService.saveTestData(testData.root)
     return testData
   }
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1)

107-124: Consider extracting common subscription setup logic

The saveSubscription method is similar to those in related test files like KeyCountLimitTest.kt and SeatUsageReportingTest.kt. Consider extracting this to a common utility class to avoid duplication.

- private fun saveSubscription() {
-   eeSubscriptionRepository.save(
-     EeSubscription().apply {
-       licenseKey = "mock"
-       name = "Plaaan"
-       status = SubscriptionStatus.ERROR
-       currentPeriodEnd = Date()
-       cancelAtPeriodEnd = false
-       enabledFeatures = Feature.values()
-       lastValidCheck = Date()
-       isPayAsYouGo = true
-       includedKeys = 10
-       includedSeats = 10
-       keysLimit = 10
-       seatsLimit = 10
-     },
-   )
- }

You could create a common test utility class with methods for creating different subscription configurations, or use a builder pattern for more flexibility.

backend/data/src/main/kotlin/io/tolgee/api/EeSubscriptionDto.kt (1)

21-25: Consider adding default values to new properties.

The new properties (includedKeys, includedSeats, isPayAsYouGo, keysLimit, and seatsLimit) don't have default values, unlike some other properties in this class. This could break existing code that creates instances of this class without providing these values.

-  var includedKeys: Long,
-  var includedSeats: Long,
-  var isPayAsYouGo: Boolean,
-  var keysLimit: Long,
-  var seatsLimit: Long,
+  var includedKeys: Long = 0,
+  var includedSeats: Long = 0,
+  var isPayAsYouGo: Boolean = false,
+  var keysLimit: Long = -1,
+  var seatsLimit: Long = -1,
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedLimitsProvider.kt (2)

17-27: Consider adding error handling for subscription mapping.

The method correctly handles the null case when subscription is not found, but doesn't have any error handling if properties in the subscription are invalid.

fun getLimits(): UsageLimits {
  val subscription = eeSubscriptionServiceImpl.findSubscriptionDto() ?: return DEFAULT_LIMITS
+ try {
    return UsageLimits(
      isPayAsYouGo = subscription.isPayAsYouGo,
      keys = UsageLimits.Limit(included = subscription.includedKeys, limit = subscription.keysLimit),
      seats = UsageLimits.Limit(included = subscription.includedSeats, limit = subscription.seatsLimit),
      strings = DEFAULT_LIMITS.strings,
      mtCreditsInCents = DEFAULT_LIMITS.mtCreditsInCents,
      isTrial = false,
    )
+ } catch (e: Exception) {
+   // Log the error
+   return DEFAULT_LIMITS
+ }
}

29-39: Consider documenting the default limits meaning.

The default limits use -1 for some values (keys, strings, mtCreditsInCents) but 10 for seats. This inconsistency should be documented to clarify the meaning of these values.

companion object {
+  /**
+   * Default limits for self-hosted instances:
+   * - `-1` indicates unlimited for keys, strings, and MT credits
+   * - `10` for seats is the actual limit for free instances
+   */
  private val DEFAULT_LIMITS =
    UsageLimits(
      keys = UsageLimits.Limit(included = -1, limit = -1),
      seats = UsageLimits.Limit(included = 10, limit = 10),
      strings = UsageLimits.Limit(included = -1, limit = -1),
      mtCreditsInCents = UsageLimits.Limit(included = -1, limit = -1),
      isPayAsYouGo = false,
      isTrial = false,
    )
}
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (1)

348-350: Consider injecting isCloud as a property instead of computing it.

The isCloud property is computed by invoking billingConfProvider. Consider injecting this as a property or making it more explicit how this affects the class behavior.

-private val isCloud: Boolean
-  get() = billingConfProvider.invoke().enabled
+private val isCloud: Boolean by lazy {
+  billingConfProvider.invoke().enabled
+}

This avoids repeated calls to the provider and makes the intent clearer.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1)

35-35: Consider storing keyCount in a more permanent or synchronized manner.

Maintaining the key count in a private field risks desynchronization if other operations modify keys outside this listener, or if the application restarts. Persisting or recalculating the count more frequently can mitigate data inaccuracies.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 079465d and 5945600.

📒 Files selected for processing (36)
  • backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/InitialDataController.kt (3 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/InitialDataModel.kt (0 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt (2 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/eeSubscription/EeSubscriptionModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/limits/LimitModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/limits/SelfHostedUsageLimitsModel.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/api/EeSubscriptionDto.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/api/IEeSubscription.kt (0 hunks)
  • backend/data/src/main/kotlin/io/tolgee/batch/processors/GenericAutoTranslationChunkProcessor.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/BaseLimitsProvider.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/dtos/UsageLimits.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanLimitExceededKeysException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanLimitExceededSeatsException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanLimitExceededStringsException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededKeysException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededSeatsException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededStringsException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/security/SignUpProcessor.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt (2 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedKeysSeatsLimitsChecker.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedLimitsProvider.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/GenericLimitChecker.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/KeysLimitChecker.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/SeatsLimitChecker.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/data/PrepareSetLicenseKeyDto.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt (2 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (9 hunks)
  • ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (3 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (4 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/SelfHostedEePlanModelTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (3 hunks)
💤 Files with no reviewable changes (2)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/InitialDataModel.kt
  • backend/data/src/main/kotlin/io/tolgee/api/IEeSubscription.kt
✅ Files skipped from review due to trivial changes (2)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/eeSubscription/EeSubscriptionModel.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/SelfHostedEePlanModelTest.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • backend/data/src/main/kotlin/io/tolgee/batch/processors/GenericAutoTranslationChunkProcessor.kt
  • backend/data/src/main/kotlin/io/tolgee/component/translationsLimitProvider/BaseLimitsProvider.kt
  • backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt
🧰 Additional context used
🧬 Code Graph Analysis (5)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/SeatsLimitChecker.kt (1)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (1)
  • getIncludedUsageExceededException (260-264)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (1)
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/TestDataBuilder.kt (1)
  • addProject (53-61)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (3)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1)
  • saveSubscription (78-96)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (2)
  • saveSubscription (107-124)
  • parseRequestArgs (131-132)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1)
  • saveSubscription (82-100)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (3)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1)
  • saveSubscription (78-96)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (2)
  • saveSubscription (78-95)
  • parseRequestArgs (102-103)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1)
  • saveSubscription (82-100)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (3)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1)
  • saveSubscription (107-124)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (1)
  • saveSubscription (78-95)
backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt (1)
  • createUser (158-173)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (81)
backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanLimitExceededSeatsException.kt (1)

6-7: Well-structured exception class!

This exception class is well-designed to handle plan seat limit violations. It properly extends BadRequestException and passes the required parameters to construct a meaningful error message. The naming follows a consistent pattern with other similar exceptions mentioned in the summary.

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanLimitExceededKeysException.kt (1)

1-8: Well-structured exception class for handling key limit exceeded scenarios.

The implementation is clean, follows Kotlin conventions, and aligns properly with the PR objective of adding keys/strings as a metric. The class extends BadRequestException which is appropriate for this type of limit violation, and correctly passes the required parameters to display a meaningful error message to users.

backend/data/src/main/kotlin/io/tolgee/service/security/SignUpProcessor.kt (1)

18-19: Nice addition of user source tracking

Adding the userSource parameter with proper documentation is a good enhancement that will provide valuable marketing insights about how users discover the platform. This change is well integrated with the existing codebase, as it's properly passed to the createUser method.

backend/api/src/main/kotlin/io/tolgee/hateoas/limits/LimitModel.kt (1)

11-26: Well-designed HATEOAS model for representing usage limits.

The LimitModel class is well-structured with clear documentation. The use of -1 as a sentinel value for unlimited resources is clearly documented in the Schema annotations, which will be helpful for API consumers.

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededStringsException.kt (1)

1-10: Implementation looks correct and follows best practices.

The exception class is well-structured and follows Kotlin conventions. It properly extends BadRequestException and passes the required parameters to handle cases where translation string limits are exceeded.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedKeysSeatsLimitsChecker.kt (4)

1-14: Structure and dependency injection look good!

The class structure is well-organized with clear responsibilities for checking keys and seats limits in self-hosted instances. The class takes nullable parameters for keys and seats, allowing flexibility in usage.


15-18: LGTM!

The check() method provides a clear orchestration point, delegating to specific methods for checking different types of limits.


20-23: LGTM!

Good use of the null safety operator to exit early when keys is null, making the code more efficient and readable.


30-32: LGTM!

Appropriate use of lazy initialization for the limits property to improve performance by only loading when needed.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/GenericLimitChecker.kt (3)

5-17: Well-designed constructor with clear purpose and documentation.

The class constructor is well-structured with appropriate parameters for limit checking across different payment models. The documentation comments on the exception providers are clear and helpful.


23-28: Good use of early return pattern.

The implementation correctly handles the non-pay-as-you-go case, checking against the included amount and using an early return to improve code readability.


30-33: Logic for pay-as-you-go limit check is straightforward.

The implementation properly checks if the required usage exceeds the overall limit for pay-as-you-go plans.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (2)

42-44: LGTM! Good setup for the keys test.

The addition of a test project with a key is well-aligned with the new key metric assertions.


82-83: LGTM! Keys assertion added correctly.

This assertion correctly verifies that the request includes the keys count.

backend/data/src/main/kotlin/io/tolgee/exceptions/limits/PlanSpendingLimitExceededKeysException.kt (1)

1-10: Well-structured exception for handling key spending limit exceedance.

This exception class is well-designed to handle scenarios where a user exceeds their plan's key spending limit. It properly extends BadRequestException and passes the required parameters to create a meaningful error message.

The class follows good practices by:

  • Using a descriptive name that clearly indicates its purpose
  • Taking both the required and limit values to provide context
  • Using appropriate parameter types (Long) for counts that could be large
  • Passing these values to the error message for better user feedback
backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/InitialDataController.kt (2)

48-53:

Details

✅ Verification successful

Remove commented out initialization code for consistency.

Similar to the constructor parameters, this commented-out code for initializing the eeSubscription field should be deleted rather than commented out. This keeps the codebase clean and prevents confusion for future developers.

Additionally, you should ensure the InitialDataModel class has been updated to make the eeSubscription field optional or remove it entirely, to maintain consistency with this change.

-//        eeSubscription =
-//          eeSubscriptionProvider?.findSubscriptionDto()?.let {
-//            eeSubscriptionModelAssembler?.toModel(
-//              it,
-//            )
-//          },

🏁 Script executed:

#!/bin/bash
# Check if InitialDataModel has been updated to make eeSubscription optional
echo "Checking InitialDataModel definition..."
rg -A 10 "class InitialDataModel" --type kotlin

Length of output: 980


Clean Up: Remove Unused eeSubscription Code

The commented-out initialization in InitialDataController.kt should be deleted. We've confirmed that the InitialDataModel (in backend/api/src/main/kotlin/io/tolgee/hateoas/InitialDataModel.kt) no longer includes the eeSubscription field, ensuring consistency with the removal.

  • Remove the commented block initializing eeSubscription in InitialDataController.kt (lines 48-53).
  • No further updates are needed in InitialDataModel since it already reflects the removal.
-//        eeSubscription =
-//          eeSubscriptionProvider?.findSubscriptionDto()?.let {
-//            eeSubscriptionModelAssembler?.toModel(
-//              it,
-//            )
-//          },

18-18:

Details

❓ Verification inconclusive

Address the TODO comment before merging.

This TODO comment indicates uncertainty about removing the eeSubscription from initial data. You should verify that this removal won't break any client functionality, particularly frontend components that might expect this data to be present.


🏁 Script executed:

#!/bin/bash
# Check if any frontend code references eeSubscription
echo "Searching for frontend references to eeSubscription..."
rg -i "eeSubscription" --type ts --type tsx

Length of output: 190


Let's run a new script that explicitly registers TSX files so we can accurately check for any frontend references to eeSubscription. Could you please run the following shell script and share the output?

#!/bin/bash
echo "Searching for frontend references to eeSubscription (including TSX files)..."
rg -i "eeSubscription" --type-add "tsx:*.tsx" --type ts --type tsx || echo "No references found."

This will help verify whether any client code depends on eeSubscription.

ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt (9)

5-5: Import change reflects new interface implementation.

The import of PlanWithIncludedKeysAndSeats aligns with the class now implementing this interface, which is a good structural change that will allow for better type safety and consistency across the codebase.


9-9: Updated to Jakarta EE namespace.

The import has been updated from javax.persistence.* to jakarta.persistence.*, which is the correct namespace for Jakarta EE 9+ applications. This ensures compatibility with newer specifications.


16-20: Well-documented entity purpose.

The added documentation clearly explains the purpose of the EeSubscription entity, which improves code maintainability and onboarding for new developers.


23-23: Interface implementation updated.

The class now implements PlanWithIncludedKeysAndSeats instead of the previous interface. This change aligns with the new subscription model that includes keys and seats as metrics.


63-67: DTO properties added for new metrics.

The DTO mapping has been properly updated to include all new properties, ensuring API consistency.


71-73: Interface properties implemented with default values.

The includedKeys and includedSeats properties properly implement the interface with default values of 0L, which is a reasonable starting point for new subscriptions.


75-79: Well-documented keysLimit property.

The documentation clearly explains the purpose of the keysLimit property, which improves code maintainability. The default value of 0L is appropriate for initialization.


80-84: Well-documented seatsLimit property.

The documentation clearly explains the purpose of the seatsLimit property, which improves code maintainability. The default value of 0L is appropriate for initialization.


85-85: Pay-as-you-go flag added.

The isPayAsYouGo property indicates a new billing model option, which aligns with the PR's objective of adding keys/strings as a metric. The default value of false is appropriate.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/KeysLimitChecker.kt (2)

1-6: Imports and package structure look good.

The imports are appropriately defined for the class's functionality, including the necessary exception types for handling limit violations.


11-13: Proper null handling.

The early return when required is null is a good pattern to avoid unnecessary processing.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/SeatsLimitChecker.kt (2)

12-26: LGTM! The check method has good null handling.

The method properly handles the null case for required and delegates to GenericLimitChecker with appropriate parameters.


1-35:

Details

❓ Verification inconclusive

Ensure test coverage for this new limit checker.

This class plays an important role in enforcing subscription limits. Make sure you have adequate test coverage to validate both the basic functionality and edge cases, especially around null handling and different limit scenarios.


🏁 Script executed:

#!/bin/bash
# Check if there are tests for this new class
grep -r "SeatsLimitChecker" --include="*Test.kt" .

Length of output: 50


Critical: Additional Test Coverage Needed for SeatsLimitChecker

It appears that no dedicated unit tests for SeatsLimitChecker are currently found in the test files (the grep search did not return any results). Given the importance of this component in enforcing subscription limits, please verify that one of the following is true:

  • Existing Tests: If tests for SeatsLimitChecker are already provided (perhaps within larger integration tests or under a different naming scheme), please confirm their presence and ensure they thoroughly cover:

    • Null Handling: The scenario where required is null (i.e., that the check is properly bypassed).
    • Limit Exceedance: Conditions when required breaches the allowed seats, triggering the appropriate exceptions.
    • Behavior Consistency: Proper instantiation and operation of GenericLimitChecker, along with both exception providers.
  • New Tests Required: If such tests are missing, please add unit tests addressing the points above to ensure both basic functionality and edge cases are validated.

ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (2)

84-90: Review new column "is_pay_as_you_go".
The is_pay_as_you_go column is introduced as a BOOLEAN with a default value of true and a non-null constraint. Ensure that the default of true correctly reflects the desired subscription behavior and that downstream components correctly handle this boolean state.


98-104:

Details

❓ Verification inconclusive

Review new column "seats_limit".
Similar to keys_limit, the seats_limit column is added as a non-null BIGINT without a provided default value. Double-check whether existing data is being handled appropriately or if a default value should be specified to avoid migration failures.


Attention: Verify Migration Handling for "seats_limit" Column Addition

  • File: ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (Lines 98-104)
  • The new column "seats_limit" is added as a non-null BIGINT without a default value—similar to "keys_limit". Please double-check that either the table ee_subscription is empty at migration time or that a data migration (or default value assignment) is in place to safely handle existing rows.
ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (4)

6-6: Good refactoring using wildcard import.

The change from specific imports to a wildcard import for the io.tolgee.hateoas.ee.* package simplifies the import section while maintaining readability.


15-17: Improved dependency injection approach.

Switching from field injection with @Autowired to constructor injection is a good practice. This makes dependencies explicit, improves testability, and helps with immutability.


34-41: New plan properties for the keys/strings metric.

The addition of includedUsage with seats and keys properties, along with the isPayAsYouGo flag, properly supports the new keys/strings metric being added in this PR.


54-71: Well-structured limits implementation.

The new limits property in mockedSubscriptionResponse correctly implements the SelfHostedUsageLimitsModel with appropriate LimitModel instances for keys, seats, and mtCreditsInCents. The structure properly reuses the values from mockedPlan.includedUsage to maintain consistency.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (8)

1-21: Well-structured test class for key count limitations.

The test class and imports are well organized. The class appropriately extends AbstractSpringTest and uses the @SpringBootTest annotation for integration testing.


22-28: Good test setup with appropriate mocking.

The dependencies are properly injected, and RestTemplate is correctly mocked using @MockBean, which is essential for isolating tests from external services.


29-38: Good test case for the limit exceeded scenario.

This test case correctly verifies that a PlanLimitExceededKeysException is thrown when trying to create keys beyond the plan's limit.


40-48: Appropriate test for unlimited keys scenario.

This test correctly verifies that no exception is thrown when the keys limit is set to -1 (unlimited).


50-59: Good test for pay-as-you-go scenario.

This test properly verifies that no exception is thrown when the subscription is configured as pay-as-you-go, even when approaching the limit.


61-72: Clear test for pay-as-you-go limit exceeded scenario.

The test correctly verifies that a PlanSpendingLimitExceededKeysException is thrown when exceeding the limit with a pay-as-you-go subscription.


74-76: Concise helper method for key creation.

The createKey method provides a clean abstraction for the key creation operation being tested.


78-96: Well-designed subscription setup method.

The saveSubscription method allows for flexible subscription configuration with sensible defaults. The use of a lambda parameter for customization is an elegant approach.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (8)

1-1: Appropriate package reorganization.

Moving the class to a more specific package io.tolgee.ee.selfHostedLimitsAndReporting improves code organization by grouping related tests together.


23-23: Class name better reflects its purpose.

Renaming from UsageReportingTest to SeatUsageReportingTest more clearly describes what the test is focusing on.


33-36: Good test initialization pattern.

Adding a @BeforeEach setup method to initialize the eeLicenseMockRequestUtil improves test organization and follows standard testing practices.


39-39: More descriptive test method name.

The renamed test method it reports seat usage clearly describes what functionality is being tested.


40-40: Clean test setup with extracted method.

The extracted saveSubscription() method improves readability by encapsulating the subscription setup logic.


78-95: Well-structured subscription setup.

The saveSubscription() method correctly sets up all needed properties, including the new ones related to keys and seats, providing a solid foundation for testing.


97-100: Good use of assertion helper method.

The assertSeats method provides a clean way to verify the reported seat count.


102-103: Useful utility method extraction.

Extracting the parseRequestArgs() method improves code organization by separating the parsing logic from the assertion logic.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (8)

1-21: Well-structured test class for seat count limitations.

The class structure and imports are appropriate for a test that validates seat count limitations.


22-28: Good test setup with appropriate mocking.

The dependencies are properly injected, and RestTemplate is correctly mocked, following good testing practices.


29-38: Clear test for the seat limit exceeded scenario.

This test correctly verifies that a PlanLimitExceededSeatsException is thrown when attempting to create users beyond the plan's seat limit.


40-48: Appropriate test for unlimited seats scenario.

This test correctly verifies that no exception is thrown when the seats limit is set to -1 (unlimited).


50-58: Good test for pay-as-you-go scenario.

This test properly verifies that no exception is thrown when creating users with a pay-as-you-go subscription that has a defined seat limit.


60-71: Clear test for pay-as-you-go limit exceeded scenario.

The test correctly verifies that a PlanSpendingLimitExceededSeatsException is thrown when exceeding the seat limit with a pay-as-you-go subscription.


73-80: Simple helper method for user creation.

The createUser method provides a clean abstraction for the user creation operation being tested.


82-100: Well-designed subscription setup method.

The saveSubscription method follows the same pattern as in KeyCountLimitTest, providing a consistent approach across test classes.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (8)

1-23: Good implementation of key usage reporting tests

The test class is well-organized and follows the established pattern of other usage reporting tests. The class is appropriately annotated with @SpringBootTest and extends AbstractSpringTest.


38-47: Clear test structure with appropriate assertions

This test effectively validates that both key creation and deletion are correctly reported through API calls. The method name clearly communicates the test's purpose.


49-65: Good performance optimization check

This test verifies an important optimization - that the system doesn't make excessive HTTP requests when creating multiple keys. This is crucial for system performance when dealing with bulk operations.


67-75: Important edge case covered

Testing the reporting behavior when a project is deleted ensures that the system properly handles cleanup and prevents stale metrics. This is an essential test case for maintaining accurate usage data.


77-85: Comprehensive test for organization deletion

This test complements the project deletion test by verifying correct reporting behavior when an entire organization is removed. This ensures complete coverage of key deletion scenarios.


87-105: Well-structured test helper method

The testWithBaseTestData method effectively reduces code duplication across test methods and provides a clean way to setup and execute tests with consistent verification.


126-129: Well-implemented assertion helper

The assertKeys extension function is a clean way to verify the reported key count in the HTTP requests, improving test readability.


131-133: Effective HTTP request parsing

The parseRequestArgs method provides a clean way to extract and parse the HTTP request body for verification.

ee/backend/app/src/main/kotlin/io/tolgee/ee/data/PrepareSetLicenseKeyDto.kt (1)

11-12: LGTM! Proper constraint on new keys parameter.

The added keys parameter with @field:Min(0) constraint is correctly implemented with an appropriate default value.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedLimitsProvider.kt (1)

8-16: LGTM! Good class structure with proper dependency injection.

The class is well-structured with appropriate lazy injection of EeSubscriptionServiceImpl.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (9)

13-16: LGTM! Clear imports for the new components.

The imports for the new limit checking components are properly organized.


52-54: LGTM! Good addition of required dependencies.

The addition of keyService, selfHostedLimitsProvider, and applicationContext is appropriate for the new functionality.


83-83: LGTM! Proper key count retrieval.

The addition of key count retrieval for license setup is appropriate.


99-104: LGTM! Good update to DTO construction.

The SetLicenseKeyLicensingDto is now correctly constructed with all required parameters.


116-120: LGTM! Correct mapping of subscription properties.

The new fields from the response are properly mapped to the entity.


252-266: LGTM! Well-implemented seat count checking.

The refactoring of seat count checking to use the SeatsLimitChecker is clean and maintainable.


276-280: LGTM! Good update to reportUsage method signature.

The method signature now includes both keys and seats as optional parameters with appropriate default values.


284-284: LGTM! Proper parameter forwarding.

The reportUsageRemote method is now correctly called with both keys and seats parameters.


312-320: LGTM! Good update to reportUsageRemote method.

The method signature and implementation now include the keys parameter.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (2)

39-39: Verify usage of billingConfProvider property call.

It appears that billingConfProvider is declared as a property but is being invoked like a function. Please confirm you intended to call an invoke() operator method. Otherwise, you may want to replace billingConfProvider() with billingConfProvider.enabled.


43-43: Check for negative usage changes.

If getUsageIncreaseAmount() can return a negative value (e.g., when a key is removed), ensure that adding a negative amount will correctly update the key count and not destabilize the limit checks.

Comment on lines 269 to 274
fun checkKeyCount(keys: Long) {
KeysLimitChecker(
required = keys,
limits = selfHostedLimitsProvider.getLimits(),
).check()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding null subscription handling in checkKeyCount.

Unlike checkSeatCount, the checkKeyCount method doesn't have special handling for when the subscription is null.

@Transactional
fun checkKeyCount(keys: Long) {
-  KeysLimitChecker(
-    required = keys,
-    limits = selfHostedLimitsProvider.getLimits(),
-  ).check()
+  object : KeysLimitChecker(
+    required = keys,
+    limits = selfHostedLimitsProvider.getLimits(),
+  ) {
+    override fun getIncludedUsageExceededException(): BadRequestException {
+      self.findSubscriptionDto()
+        ?: return BadRequestException(Message.FREE_SELF_HOSTED_KEY_LIMIT_EXCEEDED)
+      return super.getIncludedUsageExceededException()
+    }
+  }.check()
}

Note: You'll need to define a new message constant FREE_SELF_HOSTED_KEY_LIMIT_EXCEEDED in the Message class.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun checkKeyCount(keys: Long) {
KeysLimitChecker(
required = keys,
limits = selfHostedLimitsProvider.getLimits(),
).check()
}
@Transactional
fun checkKeyCount(keys: Long) {
object : KeysLimitChecker(
required = keys,
limits = selfHostedLimitsProvider.getLimits(),
) {
override fun getIncludedUsageExceededException(): BadRequestException {
self.findSubscriptionDto()
?: return BadRequestException(Message.FREE_SELF_HOSTED_KEY_LIMIT_EXCEEDED)
return super.getIncludedUsageExceededException()
}
}.check()
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/StringsLimitChecker.kt (3)

7-10: Add KDoc documentation to improve maintainability.

The class is well-structured, but lacks documentation explaining its purpose and parameters. Consider adding KDoc comments to clarify what required represents (presumably the number of strings being requested) and explain the class's overall purpose.

+/**
+ * Validates if the requested number of strings exceeds the defined usage limits.
+ *
+ * @param required The number of strings being requested or used
+ * @param limits The usage limits configuration to check against
+ */
class StringsLimitChecker(
  private val required: Long?,
  private val limits: UsageLimits,
) {

11-13: Consider using a more descriptive parameter name.

The parameter name required is somewhat ambiguous. A more descriptive name like requiredStringsCount would make the code more self-documenting.

class StringsLimitChecker(
-  private val required: Long?,
+  private val requiredStringsCount: Long?,
  private val limits: UsageLimits,
) {
  fun check() {
-    required ?: return
+    requiredStringsCount ?: return

11-28: Consider enhancing the check method to return a result.

The current implementation relies solely on exceptions for error cases and returns nothing on success. Consider returning a boolean or a result object to make the success case more explicit, which would allow callers to handle the result programmatically.

-  fun check() {
+  /**
+   * Checks if the required strings count exceeds the defined limits.
+   * 
+   * @return true if within limits, false if no check was performed (null requirement)
+   * @throws PlanLimitExceededStringsException when included usage is exceeded
+   * @throws PlanSpendingLimitExceededStringsException when spending limit is exceeded
+   */
+  fun check(): Boolean {
-    required ?: return
+    required ?: return false

    GenericLimitChecker(
      required,
      limit = limits.strings,
      isPayAsYouGo = limits.isPayAsYouGo,
      includedUsageExceededExceptionProvider = {
        PlanLimitExceededStringsException(
          required = required,
          limit = limits.strings.limit,
        )
      },
      spendingLimitExceededExceptionProvider = {
        PlanSpendingLimitExceededStringsException(required = required, limit = limits.strings.limit)
      },
    ).checkLimit()
+    return true
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5945600 and dca5c66.

📒 Files selected for processing (1)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/StringsLimitChecker.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (1)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/StringsLimitChecker.kt (1)

1-30: Overall implementation looks solid.

The class is well-structured and follows good practices:

  • Proper null handling for the required parameter
  • Good separation of concerns by delegating to GenericLimitChecker
  • Appropriate exception types for different error scenarios
  • Clean, concise implementation

This fits well with the PR's objective of adding keys/strings as a metric.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (1)

252-266: ⚠️ Potential issue

Where is the corresponding key count check method?

The PR adds support for keys as a metric, but there's no checkKeyCount method analogous to checkSeatCount. This creates an inconsistency in how the two metrics are handled.

Implementing a dedicated method for checking key counts would maintain consistency with how seat counts are verified:

@Transactional
fun checkKeyCount(
  subscription: EeSubscriptionDto?,
  keys: Long,
) {
  object : KeysLimitChecker(
    required = keys,
    limits = selfHostedLimitsProvider.getLimits(),
  ) {
    override fun getIncludedUsageExceededException(): BadRequestException {
      self.findSubscriptionDto()
        ?: return BadRequestException(Message.FREE_SELF_HOSTED_KEY_LIMIT_EXCEEDED)
      return super.getIncludedUsageExceededException()
    }
  }.check()
}

Note: You'll need to define a new message constant FREE_SELF_HOSTED_KEY_LIMIT_EXCEEDED in the Message class.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1)

35-35: Consider thread safety or transactional integrity for keyCount.

Because keyCount is read, initialized, and incremented across multiple calls, concurrent calls could lead to inconsistent updates. In prior reviews, a similar concern was raised. Marking increaseKeyCount as @Synchronized or ensuring a single transactional block for both initialization and increment can help eliminate race conditions.

Also applies to: 53-58

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1)

121-128: ⚠️ Potential issue

Fix user creation in test data generation.

The saveTestData method iterates from 1 to userCount but doesn't actually create any users in the loop - it only accesses the userAccountBuilder without doing anything with it.

 private fun saveTestData(userCount: Long = 1): BaseTestData {
   val testData = BaseTestData()
   (1..userCount).forEach {
-    testData.userAccountBuilder
+    testData.userAccountBuilder.addUserAccount {
+      username = "user$it@example.com"
+      name = "Test User $it"
+    }
   }
   testDataService.saveTestData(testData.root)
   return testData
 }
🧹 Nitpick comments (5)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (1)

1-343: Consider adding a null subscription check bypass for keys.

Similar to the bypassSeatCountCheck property (line 65), consider adding a bypassKeyCountCheck property for consistency and to support similar use cases for keys.

var bypassSeatCountCheck = false
+var bypassKeyCountCheck = false
🧰 Tools
🪛 detekt (1.23.7)

[warning] 106-106: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 136-136: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 151-151: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 235-235: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1)

39-39: Clarify invocation of billingConfProvider as a function vs. property.

Here, billingConfProvider is referenced as if it’s a function call (billingConfProvider()), but it was injected as a property. Unless PublicBillingConfProvider implements operator fun invoke(), this might be accidental. Consider changing it to billingConfProvider.enabled.

- if (billingConfProvider().enabled || event.entity !is Key) {
+ if (billingConfProvider.enabled || event.entity !is Key) {
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (2)

50-64: Test case could be clearer

The test does not throw when removing keys could be more explicit. It sets up a subscription with 2 keys, then changes it to 0 keys limit before deleting a key. Consider updating the test description to clarify you're testing that key deletion is allowed even when the current limit is 0.

-fun `does not throw when removing keys`(){
+fun `does not throw when removing keys even with zero limit`(){

114-121: Consider adding more realistic key names

While the current implementation works, consider using more descriptive key names in the test data to improve test readability.

 private fun saveTestData(keyCount: Long = 1): BaseTestData {
   val testData = BaseTestData()
   (1..keyCount).forEach {
-    testData.projectBuilder.addKey("$it")
+    testData.projectBuilder.addKey("test_key_$it")
   }
   testDataService.saveTestData(testData.root)
   return testData
 }
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1)

63-73: Unclear test setup for pay-as-you-go limit testing

In this test, you set includedSeats=1 and seatsLimit=1, then create 1 user with saveTestData, and try to create another one with createUser(). The test description could be clearer about testing the creation of a user beyond the limit.

-fun `throws when pay as you go and over the limit`() {
+fun `throws when pay as you go and attempting to exceed seat limit`() {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dca5c66 and d03382a.

📒 Files selected for processing (4)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (9 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (3)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (2)
  • saveSubscription (101-119)
  • saveTestData (121-128)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (1)
  • saveSubscription (78-95)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1)
  • saveSubscription (107-124)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (11)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (8)

13-16: Appropriate imports added for keys/string metric functionality.

The new imports properly support the addition of keys as a metric, with specialized classes for limit checking and reporting.


52-54: New dependencies properly injected.

The KeyService, SelfHostedLimitsProvider, and WebApplicationContext dependencies are correctly added to support the new key-related functionality.


83-83: Keys count retrieval implemented correctly.

The addition of key counting through the KeyService is appropriate for the license key setup process.


99-104: License key DTO properly updated to include keys parameter.

The SetLicenseKeyLicensingDto now correctly includes the keys count alongside seats when reporting to the licensing server.


116-120: Subscription entity properly enhanced with key metrics.

The entity now stores key-related information from the licensing server response, including limits and usage details.


268-272: Usage reporting correctly extended to include keys parameter.

The reportUsage method has been properly updated to accept an optional keys parameter alongside seats.


304-312: Remote usage reporting correctly updated for keys.

The reportUsageRemote method and its associated DTO now properly include the keys parameter.


340-342: Good addition of isCloud property.

The isCloud property provides a clean way to determine if billing is enabled, which is useful for conditional logic related to subscription handling.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1)

60-63: Re-check logic for early return if initialKeyCount > keyCount.

If keys are removed or usage decreases, this condition correctly skips limit checks. However, ensure this aligns with your intent, as no limit checks occur if keyCount is still below or equal to the initial count. If you need to handle usage fluctuations more dynamically, consider removing or adjusting this condition.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1)

1-122: Well-structured test class with comprehensive test cases!

This is a well-organized test class that covers various key count limit scenarios including:

  • Limit enforcement for standard subscriptions
  • Unlimited keys handling
  • Key removal behavior
  • Pay-as-you-go subscription behaviors with and without limits

The helper methods are well-designed, especially the saveSubscription method with its lambda for customization.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1)

1-130: Well-structured test class with comprehensive test cases!

This test class covers various seat count limit scenarios including:

  • Limit enforcement for standard subscriptions
  • Unlimited seats handling
  • Pay-as-you-go subscription behaviors
  • User removal behavior

The tests are thorough and the helper methods support the tests well.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/OrganizationBuilder.kt (1)

61-70: Consider making the role type configurable.

The inviteUser method currently sets the role type to OWNER by default, which grants maximum privileges. For test data, it might be more flexible to allow configuring the role type directly from the method call rather than requiring the caller to modify it through the buildRole lambda.

- fun inviteUser(buildRole: OrganizationRoleBuilder.() -> Unit = {}): InvitationBuilder {
+ fun inviteUser(roleType: OrganizationRoleType = OrganizationRoleType.OWNER, buildRole: OrganizationRoleBuilder.() -> Unit = {}): InvitationBuilder {
    val invitationBuilder = InvitationBuilder()
    testDataBuilder.data.invitations.add(invitationBuilder)
    addRole {
      this.invitation = invitationBuilder.self
-     type = OrganizationRoleType.OWNER
+     type = roleType
      invitationBuilder.self.organizationRole = this
    }.build(buildRole)
    return invitationBuilder
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d03382a and db9197d.

📒 Files selected for processing (5)
  • backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/OrganizationBuilder.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/NotFoundException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/exceptions/ProjectNotFoundException.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt (6 hunks)
  • backend/security/src/main/kotlin/io/tolgee/security/authorization/ProjectAuthorizationInterceptor.kt (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (12)
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/OrganizationBuilder.kt (1)

72-72: LGTM!

The projects property provides a convenient way to access projects associated with this organization. The implementation is concise and uses an appropriate filter operation.

backend/data/src/main/kotlin/io/tolgee/exceptions/ProjectNotFoundException.kt (1)

1-5: New exception class looks good!

This implementation provides a specific exception type for project not found scenarios, which enhances error reporting by including the project ID. This is a good practice for more precise exception handling.

backend/security/src/main/kotlin/io/tolgee/security/authorization/ProjectAuthorizationInterceptor.kt (2)

23-23: Import added correctly for the new exception type.

The import for the newly created ProjectNotFoundException class has been properly added.


85-85: Enhanced error specificity while maintaining security principle.

Good change - replacing the generic NotFoundException with ProjectNotFoundException maintains the security principle (hiding project existence from unauthorized users) while providing more specific error information that can be useful for debugging and monitoring.

backend/data/src/main/kotlin/io/tolgee/exceptions/NotFoundException.kt (1)

8-9: Appropriate class modification for inheritance and enhanced error reporting.

Good changes:

  1. Converting from data class to open class enables inheritance for specialized exceptions
  2. Adding the resourceId parameter enables more specific error messages
  3. Making resourceId nullable with a default value maintains backward compatibility

This change appropriately supports the new ProjectNotFoundException class and potentially other specialized exception types to enhance error reporting throughout the system.

backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt (7)

14-14: New event import for soft deletion implementation

The addition of the OnProjectSoftDeleted event aligns with the move from hard deleting to soft deleting projects, which will help with tracking project lifecycle metrics.


17-17: Improved error handling specificity

Using ProjectNotFoundException instead of generic exceptions provides clearer error information, making debugging and error handling more straightforward.


53-65: Dependency restructuring in constructor

The constructor has been refactored to:

  1. Remove unnecessary dependencies (screenshotService and batchJobService)
  2. Add applicationContext to support event publishing
  3. Add organizationService for organization-related operations

This better aligns with the service's responsibilities and follows the principle of minimal dependencies.


77-77: Consistent use of specific exception

The consistent replacement of generic exceptions with ProjectNotFoundException across multiple methods improves error clarity and debugging experience.

Also applies to: 81-81, 101-101


92-94: New method to find deleted projects

The addition of findDeleted method supports the soft-deletion strategy by providing a way to retrieve projects after they've been marked as deleted.


214-214: Event-driven architecture for project deletion

Publishing the OnProjectSoftDeleted event allows other components to react to project deletion without tight coupling. This enables features like updating metrics when projects are deleted.


204-215: Migration to soft deletion

The deleteProject method has been refactored to implement soft deletion instead of hard deletion. This is a good practice as it allows for potential recovery and better data tracking while maintaining referential integrity.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🔭 Outside diff range comments (1)
backend/data/src/main/kotlin/io/tolgee/component/HttpClient.kt (1)

36-40: ⚠️ Potential issue

Potential NullPointerException risk

The code now directly accesses response.body without a null-safety check. This assumes the response body will never be null, which may not always be true for HTTP responses. Consider adding null handling or documenting this assumption.

-    return response.body.let { stringResponseBody ->
+    return response.body?.let { stringResponseBody ->
       jacksonObjectMapper()
         .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
         .readValue(stringResponseBody, result)
-    }
+    } ?: throw IllegalStateException("Received null response body from $url")
🧹 Nitpick comments (36)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/exceptions.kt (1)

5-5: LGTM! Consider adding documentation for completeness.

The implementation of LicenseKeyNotFoundException is clean and correctly extends NotFoundException. For better maintainability, consider adding a KDoc comment explaining when this exception is thrown and how it should be handled by consumers.

+/**
+ * Exception thrown when a license key cannot be found.
+ */
class LicenseKeyNotFoundException : NotFoundException()
ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetEeSubscriptionUsageDto.kt (2)

5-8: Consider using Kotlin's data class keyword

The class definition could be improved by using the data keyword for a Kotlin data class. This would automatically generate useful methods like equals(), hashCode(), toString(), and copy(), which are often needed for DTOs.

-class GetEeSubscriptionUsageDto(
+data class GetEeSubscriptionUsageDto(
  @field:NotBlank
  var licenseKey: String
)

5-8: Add KDoc documentation for better maintainability

Consider adding KDoc documentation to explain the purpose of this class and its property. This would improve code maintainability and help other developers understand how to use this DTO correctly.

+/**
+ * Data transfer object for retrieving EE subscription usage information.
+ * Used to pass the license key for identifying the subscription.
+ */
class GetEeSubscriptionUsageDto(
  @field:NotBlank
+ /**
+  * The license key used to identify the subscription.
+  * Must not be blank.
+  */
  var licenseKey: String
)
backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1)

277-277: Consider revising the grammar in this constant.

The constant name STRINGS_METRIC_ARE_NOT_SUPPORTED seems to have a grammatical mismatch between the singular "METRIC" and the plural verb "ARE". Consider changing to STRING_METRICS_ARE_NOT_SUPPORTED or STRINGS_METRIC_IS_NOT_SUPPORTED for grammatical consistency.

-  STRINGS_METRIC_ARE_NOT_SUPPORTED,
+  STRING_METRICS_ARE_NOT_SUPPORTED,
webapp/src/ee/billing/component/UsageDetailed.tsx (1)

47-50: Consider memoizing rounded values for performance

The Math.round() calls inside the render function will be executed on every render. For high-frequency updates, consider memoizing these calculations or moving them outside the render function.

- {item.getLabel({
-   limit: Math.round(item.progress.included),
-   used: Math.round(item.progress.used),
- })}
+ {(() => {
+   const limit = Math.round(item.progress.included);
+   const used = Math.round(item.progress.used);
+   return item.getLabel({ limit, used });
+ })()}

Alternatively, you could use React.useMemo if this component gets frequent updates:

const params = React.useMemo(() => ({
  limit: Math.round(item.progress.included),
  used: Math.round(item.progress.used),
}), [item.progress.included, item.progress.used]);

// Then use it
{item.getLabel(params)}
ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/EeSubscriptionUsageController.kt (2)

28-33: Concise endpoint implementation with proper documentation.

The getUsage() endpoint is properly annotated and follows the single responsibility principle by delegating to the service layer. Consider adding response status annotations to explicitly define expected HTTP status codes.

 @GetMapping("")
 @Operation(summary = "Get the info about the current EE subscription")
+@ResponseStatus(HttpStatus.OK)
 fun getUsage(): UsageModel? {
   return usageService.getUsage()
 }

9-9: Possible typo in import statement.

There appears to be a typo in the import path: io.tolgee.hateoas.ee.uasge.UsageModel where "uasge" should likely be "usage".

-import io.tolgee.hateoas.ee.uasge.UsageModel
+import io.tolgee.hateoas.ee.usage.UsageModel
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/EeTolgeeTranslateApiServiceImpl.kt (1)

23-25: Consider combining similar dependencies.

The class has two instances of EeSubscriptionServiceImpl injected as separate dependencies (subscriptionService and eeSubscriptionServiceImpl), which seems redundant. Consider consolidating these to reduce confusion.

 private val subscriptionService: EeSubscriptionServiceImpl,
-private val eeSubscriptionServiceImpl: EeSubscriptionServiceImpl,
 private val catchingService: EeSubscriptionErrorCatchingService

Then update references to eeSubscriptionServiceImpl to use subscriptionService instead.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (2)

60-63: Consider adding the same billing configuration check as in onActivity method.

For consistency with the onActivity method, you might want to add the same billing configuration check here to determine if key count reporting should proceed.

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
fun onOrganizationDeleted(event: BeforeOrganizationDeleteEvent) {
+  if (!billingConfProvider().enabled) {
+    return
+  }
  onKeyCountChanged()
}

65-73: Improve exception handling for the swallowed exception.

The caught NoActiveSubscriptionException is currently swallowed with only a debug level log. Consider adding more context to the log message or handling it differently if this represents an expected operational state.

try {
  val keys = keyService.countAllOnInstance()
  val subscription = eeSubscriptionService.findSubscriptionDto()
  eeSubscriptionService.reportUsage(subscription = subscription, keys = keys)
} catch (e: NoActiveSubscriptionException) {
-  logger.debug("No active subscription, skipping usage reporting.")
+  logger.debug("No active subscription, skipping usage reporting.", e)
}

Also, consider handling other potential exceptions that might occur during the reporting process:

try {
  val keys = keyService.countAllOnInstance()
  val subscription = eeSubscriptionService.findSubscriptionDto()
  eeSubscriptionService.reportUsage(subscription = subscription, keys = keys)
} catch (e: NoActiveSubscriptionException) {
  logger.debug("No active subscription, skipping usage reporting.", e)
+} catch (e: Exception) {
+  logger.error("Failed to report key count usage", e)
}
🧰 Tools
🪛 detekt (1.23.7)

[warning] 70-70: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/SubscriptionFromModelAssigner.kt (2)

7-11: Class purpose should be documented.

Consider adding KDoc comments to explain the purpose of this class. Good documentation would improve maintainability, especially for other developers working with this code.

+/**
+ * Handles the assignment of properties from a SelfHostedEeSubscriptionModel to an EeSubscription.
+ * This utility class is used during subscription creation or update operations.
+ */
class SubscriptionFromModelAssigner(
  private val subscription: EeSubscription,
  private val model: SelfHostedEeSubscriptionModel,
  private val currentDate: Date
) {

12-25: Document the assign() method and consider using modern time API.

The method implementation looks correct. Two suggestions for improvement:

  1. Add method documentation to explain what this method does
  2. Consider using java.time (JSR-310) classes instead of java.util.Date for better date/time handling
+  /**
+   * Assigns all relevant properties from the model to the subscription object.
+   * Updates subscription details including limits, features, and validity information.
+   */
  fun assign() {
    subscription.name = model.plan.name
-   subscription.currentPeriodEnd = model.currentPeriodEnd?.let { Date(it) }
+   subscription.currentPeriodEnd = model.currentPeriodEnd?.let { Date(it) } // Consider using Instant or ZonedDateTime in future refactoring
    subscription.cancelAtPeriodEnd = model.cancelAtPeriodEnd
    subscription.enabledFeatures = model.plan.enabledFeatures
    subscription.nonCommercial = model.plan.nonCommercial
    subscription.includedKeys = model.plan.includedUsage.keys
    subscription.includedSeats = model.plan.includedUsage.seats
    subscription.isPayAsYouGo = model.plan.isPayAsYouGo
    subscription.keysLimit = model.limits.keys.limit
    subscription.seatsLimit = model.limits.seats.limit
    subscription.status = model.status
    subscription.lastValidCheck = currentDate
  }
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionUsageService.kt (1)

17-25: Consider adding documentation and caching strategy

The implementation looks clean and effective. However, for better maintainability:

  1. Consider adding KDoc comments to explain the method's purpose and potential exceptions
  2. Since this calls a remote service, consider implementing caching to improve performance for repeated calls

Example of adding KDoc and caching:

+  /**
+   * Retrieves the current subscription usage information.
+   * @return The subscription usage model
+   * @throws NotFoundException if the instance is not using a license key
+   */
+  @Cacheable("subscriptionUsage", cacheManager = "shortLivedCacheManager")
   fun getUsage(): UsageModel {
     val eeSubscription =
       eeSubscriptionService.findSubscriptionDto()
         ?: throw NotFoundException(Message.INSTANCE_NOT_USING_LICENSE_KEY)

     return catchingService.catchingLicenseNotFound {
       client.getUsageRemote(eeSubscription.licenseKey)
     }
   }
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionErrorCatchingService.kt (2)

39-50: Consider Logging Errors
While the transaction rollback and status update are properly handled, logging the original HttpClientErrorException.NotFound before rethrowing could help with debugging and tracing production errors.


68-70: Guard Against Invalid JSON
jacksonObjectMapper().readValue(...) can fail if the error response isn't valid JSON. Safely handle malformed responses to prevent uncaught parse exceptions.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionServiceImpl.kt (3)

49-49: Unused bypassSeatCountCheck variable?
It appears that var bypassSeatCountCheck = false is declared but never utilized within the class. Consider removing it if it is not needed, or implementing logic to fully leverage this toggle if it’s intended for seat checks.


64-69: Consider clarifying pre-existing license scenarios.
The code throws an exception if the instance is already licensed. It may be helpful to clarify if the user can override the existing license, or handle such situations with an explicit workflow in the UI/API instead of an immediate BadRequestException.


138-146: Review 2-day error transition threshold.
handleConstantlyFailingRemoteCheck marks the subscription as ERROR if 2 days have passed since the last valid check. Ensure this is acceptable for your business logic and neither too strict nor too lenient.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (8)

18-18: Unify naming by correcting “Licencing” to “Licensing”.
The class name uses an unconventional spelling. Consider changing it to “TolgeeCloudLicensingClient” to maintain consistency and clarity across the codebase.

-class TolgeeCloudLicencingClient(
+class TolgeeCloudLicensingClient(

32-39: Unify error handling for consistency.
Other methods catch HttpClientErrorException.NotFound and rethrow a BadRequestException. Consider applying a similar pattern in this method to handle the same error scenario uniformly.


41-42: Consider handling or logging the outcome.
Currently, reportErrorRemote does not indicate or log whether the upstream server accepts the error report. It might be helpful to log or handle the server’s response to confirm the report was successful.


44-53: Add consistent error handling.
Like other remote calls, consider catching and handling server-side errors here. This keeps your client usage consistent and helps diagnose usage reporting failures.


55-60: Consider handling server-side errors.
Similar to other methods, you may wish to catch HttpClientErrorException or other exceptions and perform appropriate error handling or logging, rather than ignoring them.


62-71: Expand error handling for additional HTTP status codes.
Currently, only HttpClientErrorException.NotFound is caught. Consider handling other 4xx or 5xx responses if the remote licensing server could respond with different errors.


73-82: Handle other error scenarios similarly.
As with setLicenseKeyRemote, you might handle other exceptions here to avoid potential unhandled 4xx or 5xx situations from the remote licensing server.


91-96: Ensure consistent error handling approach.
For completeness, consider a similar try-catch strategy here if the remote server can return 404 or other error codes.

webapp/src/ee/billing/limitPopover/generic/PlanLimitPopoverWrapper.tsx (1)

17-17: Consider using React refs instead of direct DOM access

Using document.getElementById() directly in React components can cause issues in server-side rendering (SSR) environments and during testing. It's also less aligned with React's declarative approach.

Consider refactoring this to use React's ref system or context to access the element:

import { Popover } from '@mui/material';
-import React, { ComponentProps, FC } from 'react';
+import React, { ComponentProps, FC, useContext } from 'react';
import { USAGE_ELEMENT_ID } from '../../component/CriticalUsageCircle';
+import { UsageElementContext } from '../../context/UsageElementContext'; // Create this context

export type PlanLimitPopoverWrapperProps = {
  open: boolean;
  onClose: () => void;
  popoverProps?: Omit<ComponentProps<typeof Popover>, 'open'>;
};

export const PlanLimitPopoverWrapper: FC<PlanLimitPopoverWrapperProps> = ({
  open,
  onClose,
  children,
  popoverProps,
}) => {
-  const anchorEl = document.getElementById(USAGE_ELEMENT_ID);
+  const { elementRef } = useContext(UsageElementContext);
+  const anchorEl = elementRef?.current;
webapp/src/ee/billing/limitPopover/SpendingLimitExceeded.tsx (1)

1-16: Remove unused imports

Several imports are not used in the current implementation:

  • Popover from @mui/material
  • useOrganizationUsage from tg.globalContext/helpers
  • USAGE_ELEMENT_ID from ../component/CriticalUsageCircle
  • getProgressData from ../component/getProgressData

Clean up the imports to include only what's needed:

import {
  Button,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
- Popover,
  styled,
} from '@mui/material';
import { T } from '@tolgee/react';

-import { useOrganizationUsage } from 'tg.globalContext/helpers';
-import { USAGE_ELEMENT_ID } from '../component/CriticalUsageCircle';
import { SpendingLimitExceededDescription } from 'tg.component/security/SignUp/SpendingLimitExceededDesciption';
-import { getProgressData } from '../component/getProgressData';
import { PlanLimitPopoverWrapper } from './generic/PlanLimitPopoverWrapper';
webapp/src/ee/billing/limitPopover/GlobalLimitPopover.tsx (2)

1-4: Remove unused import

The usePreferredOrganization is imported but not used in the component.

Remove the unused import:

import {
  useOrganizationUsage,
- usePreferredOrganization,
} from 'tg.globalContext/helpers';
import React, { useEffect, useState } from 'react';

14-24: Consider consolidating effect hooks

Both effect hooks serve similar purposes - monitoring error states and setting open states. Consider consolidating them for better readability.

-  useEffect(() => {
-    if (planLimitErrors === 1) {
-      setPlanLimitErrOpen(true);
-    }
-  }, [planLimitErrors]);
-
-  useEffect(() => {
-    if (spendingLimitErrors > 0) {
-      setSpendingLimitErrOpen(true);
-    }
-  }, [spendingLimitErrors]);
+  useEffect(() => {
+    if (planLimitErrors === 1) {
+      setPlanLimitErrOpen(true);
+    }
+    
+    if (spendingLimitErrors > 0) {
+      setSpendingLimitErrOpen(true);
+    }
+  }, [planLimitErrors, spendingLimitErrors]);
webapp/src/ee/billing/limitPopover/PlanLimitPopoverCloud.tsx (3)

1-13: Improve import organization.

The React import at line 12 should be moved to the top of the file, following the convention of listing React first, then third-party libraries, and finally local imports.

+import React from 'react';
 import { Button, DialogContent, styled } from '@mui/material';
 import { T } from '@tolgee/react';
 import { useHistory } from 'react-router-dom';

 import { LINKS, PARAMS } from 'tg.constants/links';
 import {
   useOrganizationUsage,
   usePreferredOrganization,
 } from 'tg.globalContext/helpers';
 import { getProgressData } from '../component/getProgressData';
 import { PlanLimitPopover } from './generic/PlanLimitPopover';
-import React from 'react';

31-38: Avoid non-null assertion operator.

On line 35, you're using the non-null assertion operator (!) with preferredOrganization. This could lead to runtime errors if preferredOrganization is unexpectedly null.

 const handleConfirm = () => {
   onClose();
   history.push(
     LINKS.ORGANIZATION_BILLING.build({
-      [PARAMS.ORGANIZATION_SLUG]: preferredOrganization!.slug,
+      [PARAMS.ORGANIZATION_SLUG]: preferredOrganization?.slug || '',
     })
   );
 };

40-40: Ensure proper null checking.

The getProgressData function is called without explicitly checking if usage is defined. While the conditional rendering on line 42 handles null progressData, it would be clearer to explicitly check for usage before passing it.

-const progressData = usage && getProgressData({ usage });
+const progressData = usage ? getProgressData({ usage }) : null;
webapp/src/ee/billing/limitPopover/generic/PlanLimitPopover.tsx (2)

38-42: Include optional popoverProps.

The component doesn't forward the optional popoverProps to the PlanLimitPopoverWrapper, which could limit the customization of the wrapper component.

 <PlanLimitPopoverWrapper
   open={open}
   onClose={onClose}
+  popoverProps={popoverProps}
   data-cy="spending-limit-exceeded-popover"
 >

50-50: Consider destructuring progressData for component props.

Using the spread operator on progressData directly passes all of its properties to UsageDetailed. If progressData contains properties that aren't expected by UsageDetailed, they'll be passed as props, which could cause unexpected behavior.

-<UsageDetailed {...progressData} isPayAsYouGo={isPayAsYouGo} />
+<UsageDetailed 
+  stringsProgress={progressData.stringsProgress}
+  seatsProgress={progressData.seatsProgress}
+  keysProgress={progressData.keysProgress}
+  creditProgress={progressData.creditProgress}
+  isPayAsYouGo={isPayAsYouGo} 
+/>
webapp/src/ee/billing/limitPopover/PlanLimitPopover.tsx (1)

15-16: Consider adding fallback for missing configuration.

The component assumes that either the billing.enabled check passes or the self-hosted version should be shown. For improved robustness, consider adding a fallback case for when the configuration is missing or invalid.

export const PlanLimitPopover: React.FC<Props> = ({ open, onClose }) => {
  const config = useConfig();

  if (config?.billing?.enabled) {
    return <PlanLimitPopoverCloud onClose={onClose} open={open} />;
  }

-  return <PlanLimitPopoverSelfHosted onClose={onClose} open={open} />;
+  // Using self-hosted as fallback for missing config or when billing is disabled
+  return <PlanLimitPopoverSelfHosted onClose={onClose} open={open} />;
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f190286 and 5ebf8dc.

⛔ Files ignored due to path filters (1)
  • .idea/icon.svg is excluded by !**/*.svg
📒 Files selected for processing (32)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/UsageModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/publicBilling/CloudSubscriptionPlanType.kt (0 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/HttpClient.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1 hunks)
  • e2e/cypress/support/dataCyType.d.ts (9 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseController.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/EeSubscriptionUsageController.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/internal/BypassSeatCountCheckController.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeSubscriptionInfoProviderImpl.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/PublicEnabledFeaturesProvider.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/EeTolgeeTranslateApiServiceImpl.kt (3 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeOnUserCountChangedListener.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedLimitsProvider.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetEeSubscriptionUsageDto.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (0 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionErrorCatchingService.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionServiceImpl.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionUsageService.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/SubscriptionFromModelAssigner.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/exceptions.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (4 hunks)
  • webapp/src/ee/billing/component/UsageDetailed.tsx (1 hunks)
  • webapp/src/ee/billing/limitPopover/GlobalLimitPopover.tsx (3 hunks)
  • webapp/src/ee/billing/limitPopover/PlanLimitPopover.tsx (1 hunks)
  • webapp/src/ee/billing/limitPopover/PlanLimitPopoverCloud.tsx (1 hunks)
  • webapp/src/ee/billing/limitPopover/PlanLimitPopoverSelfHosted.tsx (1 hunks)
  • webapp/src/ee/billing/limitPopover/SpendingLimitExceeded.tsx (3 hunks)
  • webapp/src/ee/billing/limitPopover/generic/PlanLimitPopover.tsx (1 hunks)
  • webapp/src/ee/billing/limitPopover/generic/PlanLimitPopoverWrapper.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • backend/api/src/main/kotlin/io/tolgee/publicBilling/CloudSubscriptionPlanType.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt
✅ Files skipped from review due to trivial changes (4)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/EeSubscriptionInfoProviderImpl.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/internal/BypassSeatCountCheckController.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseController.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/PublicEnabledFeaturesProvider.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/UsageModel.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/SelfHostedLimitsProvider.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeOnUserCountChangedListener.kt
🧰 Additional context used
🧬 Code Graph Analysis (7)
webapp/src/ee/billing/limitPopover/PlanLimitPopoverSelfHosted.tsx (4)
webapp/src/ee/billing/limitPopover/generic/PlanLimitPopoverWrapper.tsx (1)
  • PlanLimitPopoverWrapperProps (5-9)
webapp/src/ee/billing/component/getProgressData.ts (1)
  • getProgressData (6-42)
webapp/src/ee/billing/limitPopover/PlanLimitPopover.tsx (1)
  • PlanLimitPopover (8-16)
webapp/src/ee/billing/limitPopover/generic/PlanLimitPopover.tsx (1)
  • PlanLimitPopover (30-63)
webapp/src/ee/billing/limitPopover/SpendingLimitExceeded.tsx (1)
webapp/src/ee/billing/limitPopover/generic/PlanLimitPopoverWrapper.tsx (1)
  • PlanLimitPopoverWrapper (11-40)
webapp/src/ee/billing/limitPopover/GlobalLimitPopover.tsx (1)
webapp/src/ee/billing/limitPopover/PlanLimitPopoverCloud.tsx (1)
  • PlanLimitPopoverCloud (25-61)
webapp/src/ee/billing/component/UsageDetailed.tsx (2)
webapp/src/ee/billing/component/getProgressData.ts (1)
  • ProgressData (44-44)
webapp/src/ee/billing/component/BillingProgress.tsx (1)
  • BillingProgress (79-185)
webapp/src/ee/billing/limitPopover/PlanLimitPopover.tsx (4)
webapp/src/ee/billing/limitPopover/generic/PlanLimitPopoverWrapper.tsx (1)
  • PlanLimitPopoverWrapperProps (5-9)
webapp/src/globalContext/helpers.tsx (1)
  • useConfig (10-11)
webapp/src/ee/billing/limitPopover/PlanLimitPopoverCloud.tsx (1)
  • PlanLimitPopoverCloud (25-61)
webapp/src/ee/billing/limitPopover/PlanLimitPopoverSelfHosted.tsx (1)
  • PlanLimitPopoverSelfHosted (8-21)
webapp/src/ee/billing/limitPopover/generic/PlanLimitPopover.tsx (3)
webapp/src/ee/billing/limitPopover/generic/PlanLimitPopoverWrapper.tsx (2)
  • PlanLimitPopoverWrapperProps (5-9)
  • PlanLimitPopoverWrapper (11-40)
webapp/src/ee/billing/component/getProgressData.ts (1)
  • ProgressData (44-44)
webapp/src/ee/billing/component/UsageDetailed.tsx (1)
  • UsageDetailed (7-61)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionErrorCatchingService.kt (2)
backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/TestDataBuilder.kt (1)
  • fn (8-73)
backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (1)
  • executeInNewTransaction (268-274)
🪛 detekt (1.23.7)
ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt

[warning] 70-70: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: E2E testing ‍🔎 (15, 14)
  • GitHub Check: E2E testing ‍🔎 (15, 13)
  • GitHub Check: E2E testing ‍🔎 (15, 12)
  • GitHub Check: E2E testing ‍🔎 (15, 11)
  • GitHub Check: E2E testing ‍🔎 (15, 10)
  • GitHub Check: E2E testing ‍🔎 (15, 9)
  • GitHub Check: E2E testing ‍🔎 (15, 8)
  • GitHub Check: E2E testing ‍🔎 (15, 7)
  • GitHub Check: E2E testing ‍🔎 (15, 6)
  • GitHub Check: E2E testing ‍🔎 (15, 5)
  • GitHub Check: E2E testing ‍🔎 (15, 4)
  • GitHub Check: E2E testing ‍🔎 (15, 2)
  • GitHub Check: BT ‍🔎 (server-app:runStandardTests)
  • GitHub Check: E2E testing ‍🔎 (15, 1)
  • GitHub Check: BT ‍🔎 (server-app:runContextRecreatingTests)
  • GitHub Check: E2E testing ‍🔎 (15, 0)
🔇 Additional comments (30)
ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetEeSubscriptionUsageDto.kt (1)

6-7: Proper use of validation annotation

Good job using the @field:NotBlank annotation to ensure the license key is validated. This is a best practice for input validation in DTOs.

e2e/cypress/support/dataCyType.d.ts (1)

19-24: Changes align with new billing metrics and UI restructuring.

The added and removed data-cy attributes properly reflect the shift from the previous billing system to one that now includes keys as a metric. The naming follows established patterns and maintains consistency.

Key improvements include:

  • New "administration-assign-plan-*" attributes replacing trial-specific ones
  • Added "keys" as a new metric type across administration and billing components
  • Updated UI elements for expected usage instead of estimated costs
  • New attributes for invitation acceptance and limit exceeded scenarios

Also applies to: 28-34, 65-73, 74-77, 127-133, 139-147, 154-157, 203-203, 222-222, 302-302, 311-311, 435-435, 529-529

backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1)

278-281: LGTM! Constants align well with feature scope.

These new constants follow the existing naming patterns and properly represent the error conditions related to keys/strings metrics and seats limits. The addition of these constants supports the PR objective of adding keys/strings as a metric.

webapp/src/ee/billing/component/UsageDetailed.tsx (4)

7-9: Well-typed props structure improves maintainability

The refactored component uses Partial<ProgressData> with an explicit isPayAsYouGo boolean flag, which provides better type safety and makes the component more flexible for future extensions.


10-35: Good refactoring to use an array-based approach

Organizing the different progress types in an array makes the code more maintainable and scalable. This approach eliminates repetition and makes it easier to add new progress types in the future.


39-42: Proper null handling for missing progress data

The conditional check for item.progress?.isInUse ensures the component gracefully handles cases where progress data might be undefined or not in use.


52-54: Consistent passing of props to BillingProgress

The refactored component correctly passes the required props to the BillingProgress component, ensuring consistent behavior across different progress types.

ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/EeSubscriptionUsageController.kt (3)

1-13: Well-structured imports and package declaration.

The imports are properly organized and include all necessary dependencies for Spring MVC controller, OpenAPI documentation, and Tolgee-specific classes.


14-20: Good documentation with clear scope definition.

The class-level documentation clearly explains the purpose and scope of this controller, specifying that it's only relevant for self-hosted instances and not for Tolgee Cloud. This helps prevent misuse.


21-27: Well-designed controller class with proper annotations.

The controller follows Spring best practices with appropriate annotations for REST controller definition, request mapping, and API documentation. Constructor injection is used correctly for dependency management.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/EeTolgeeTranslateApiServiceImpl.kt (3)

10-11: Proper import addition for new dependency.

The import for EeSubscriptionErrorCatchingService is correctly added to support the refactoring of error handling logic.


25-25: Good dependency injection for improved error handling.

Adding the EeSubscriptionErrorCatchingService as a constructor parameter follows dependency injection best practices and improves separation of concerns by delegating error handling to a specialized service.


37-37: Improved error handling with dedicated service.

Replacing direct use of subscriptionService.catchingLicenseNotFound with catchingService.catchingLicenseNotFound properly delegates error handling to a specialized service, improving separation of concerns.

ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt (1)

43-58: LGTM! Comprehensive event handling for key count changes.

The logic for detecting changes in keys, projects, and organizations is well-implemented. Using runSentryCatching ensures that any exceptions are properly captured in Sentry for monitoring.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionUsageService.kt (3)

13-14: Good use of @lazy annotation

The @Lazy annotation is properly used here to handle potential circular dependencies between this service and EeSubscriptionServiceImpl. This is a good practice in Spring applications when circular dependencies can't be avoided.


19-21: Effective null handling with Kotlin's Elvis operator

Good use of the Elvis operator (?:) to handle the null case and throw an appropriate exception with a clear message. This is a clean, idiomatic Kotlin pattern.


22-24: Well-structured error handling

The use of the catchingService.catchingLicenseNotFound higher-order function to wrap the remote call provides clean error handling, centralizing the license-not-found error handling logic.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionErrorCatchingService.kt (3)

1-15: Package and Imports Look Good
No issues appear in the package declaration or the import statements.


16-21: Class Declaration and Dependencies
The class structure and dependency injection approach with @Lazy look appropriate for Spring-based error handling services. No immediate concerns.


72-76: Subscription Status Update
The logic for setting KEY_USED_BY_ANOTHER_INSTANCE appears consistent with the rest of the subscription error handling. No issues found here.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionServiceImpl.kt (2)

102-107: Evaluate concurrency impact of scheduled checkSubscription.
This method runs periodically and might conflict with manual calls that also update subscription states (e.g., setLicenceKey). Make sure concurrency or transaction boundaries are handled carefully to avoid stale or inconsistent subscription data.


187-199: Null-check subscription in reportUsage.
When subscription is null, the method exits silently. Confirm whether usage must still be reported in that scenario for external analytics or internal logs, and consider adding logging or disclaimers to ensure the intended behavior is clear.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (2)

22-30: Constants look good.
These endpoint constant definitions are clear and well-scoped in the companion object.


84-90: HTTP client usage is concise and clear.
This private inline function neatly wraps HttpClient calls in a generic manner.

backend/data/src/main/kotlin/io/tolgee/component/HttpClient.kt (1)

22-22:

Details

✅ Verification successful

Breaking change: Return type is now non-nullable

The return type has been changed from T? (nullable) to T (non-nullable). This is a breaking change that could affect any code expecting null returns from this method. Ensure all calling code has been updated to handle this change.


🏁 Script executed:

#!/bin/bash
# Find all usages of requestForJson to verify they handle the non-nullable return type correctly
rg -A 3 -B 3 "requestForJson\(" --glob "*.kt"

Length of output: 3235


Breaking Change Verification: Non-Nullable Return Type Confirmed

The automated search confirms that all current call sites of requestForJson—including in:

  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/EeTolgeeTranslateApiServiceImpl.kt
  • backend/data/src/main/kotlin/io/tolgee/service/TelemetryService.kt

…are directly using the return value without any null-checks. This is consistent with the change from a nullable (T?) to a non-nullable (T) return type. Please ensure that runtime behavior (e.g., JSON parsing or deserialization failures) is covered by appropriate error handling elsewhere in the application.

webapp/src/ee/billing/limitPopover/SpendingLimitExceeded.tsx (1)

34-38:

Details

❓ Verification inconclusive

Verify data-cy attribute is properly passed to the Popover

The data-cy="spending-limit-exceeded-popover" attribute is applied to the PlanLimitPopoverWrapper, but make sure the wrapper component forwards this attribute to the actual Popover for proper test selection.


🏁 Script executed:

#!/bin/bash
# Check if PlanLimitPopoverWrapper passes data attributes to Popover
ast-grep --pattern $'export const PlanLimitPopoverWrapper: FC<PlanLimitPopoverWrapperProps> = ({
  $$$
}) => {
  $$$
  return (
    <Popover
      $$$
      {...popoverProps}
    >
      $$$
    </Popover>
  );
};'

# Check how data-cy attributes are used in tests
rg "spending-limit-exceeded-popover" --type=ts --type=tsx

Length of output: 301


Action Required: Manually Verify Data Attribute Forwarding

The automated search did not conclusively confirm that the data-cy="spending-limit-exceeded-popover" attribute, set on the PlanLimitPopoverWrapper, is being forwarded to the underlying Popover component. Please review the implementation in the file (likely in the same directory as SpendingLimitExceeded.tsx) to ensure that any extra props—such as data-cy—are correctly spread onto the Popover. If necessary, update the component or add a test to verify that the attribute appears on the final rendered element.

webapp/src/ee/billing/limitPopover/PlanLimitPopoverCloud.tsx (1)

42-60: Well-structured conditional rendering.

The component correctly conditionally renders the PlanLimitPopover based on the availability of progressData and only shows the action button if the user is an owner. The props passed to the child component are appropriate.

webapp/src/ee/billing/limitPopover/generic/PlanLimitPopover.tsx (1)

18-22: Well-defined type extension.

The props type extends PlanLimitPopoverWrapperProps with additional properties that are specific to this component. This is a good practice for type reuse and clarity.

webapp/src/ee/billing/limitPopover/PlanLimitPopover.tsx (2)

1-7: Clean and straightforward imports and type definition.

The imports are well-organized, and the type definition simply reuses PlanLimitPopoverWrapperProps which is a good practice for maintaining consistency.


11-13: Well-structured conditional rendering.

The component correctly determines which version of the popover to show based on the billing configuration. This approach simplifies the logic and makes it easy to understand.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (1)

23-103: 🛠️ Refactor suggestion

Consider adding key/string usage tests to match the PR objective.

While the test properly verifies seat usage reporting, the PR objective mentions adding keys/strings as a metric. Consider adding test coverage for keys/strings usage reporting as well to fully validate the new feature.

@Test
fun `it reports seat usage`() {
  // Existing seat testing implementation
}

+@Test
+fun `it reports keys usage`() {
+  saveSubscription()
+
+  eeLicenseMockRequestUtil.mock {
+    whenReq {
+      this.method = { it == HttpMethod.POST }
+      this.url = { it.contains("/v2/public/licensing/report-usage") }
+    }
+
+    thenAnswer {
+    }
+
+    verify {
+      // Create keys/strings through the appropriate service
+      // Assert on the reported keys count
+      captor.assertKeys(1)
+      // Add more keys/strings
+      captor.assertKeys(2)
+      // Remove keys/strings
+      captor.assertKeys(1)
+    }
+  }
+}
+
+fun KArgumentCaptor<HttpEntity<*>>.assertKeys(keys: Long) {
+  val data = parseRequestArgs()
+  data["keys"].toString().assert.isEqualTo(keys.toString())
+}
♻️ Duplicate comments (1)
ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (1)

91-97: ⚠️ Potential issue

Critical: Review Migration Strategy for keys_limit Column.
The new keys_limit column is added as a BIGINT with a non-null constraint but without an explicit default value. As noted in previous reviews, this could lead to migration issues if the ee_subscription table contains existing records. Consider one of the following approaches:

  • Add an explicit default value (e.g., -1 if that fits the model) to ensure backward compatibility.
  • Implement a data migration step to set valid values for existing rows prior to enforcing the non-null constraint.
🧹 Nitpick comments (9)
ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetMySubscriptionUsageRequest.kt (1)

5-8: Consider using immutable property for the request class.

The class uses a mutable property (var licenseKey) when immutability might be preferred. For request classes, immutable properties (val) are generally safer and prevent accidental modifications after object creation.

class GetMySubscriptionUsageRequest(
  @field:NotBlank
- var licenseKey: String
+ val licenseKey: String
)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (7)

17-21: Consider revising class name for consistency and clarity.

The word "Licencing" may be considered a variant spelling. Typically, in the context of software, "Licensing" is more commonly used and would maintain consistency throughout the codebase.

- class TolgeeCloudLicencingClient(
+ class TolgeeCloudLicensingClient(

32-39: Validate error handling.

getRemoteSubscriptionInfo uses a generic postRequest but only specifically handles HttpClientErrorException.NotFound within other functions, not here. You may want to ensure that unexpected exceptions or HTTP 4xx errors from the remote server are handled consistently across the board.


41-42: No immediate concerns with error reporting.

reportErrorRemote appears straightforward. Consider whether you’d like to capture or log potential exceptions, if the remote call fails.


62-71: Ensure consistent exception mapping.

setLicenseKeyRemote throws a BadRequestException when the server responds with 404 Not Found. Make sure this status is truly the correct application behavior. If the user fosters different handling for missing licenses vs. an invalid request, it could be more expressive to differentiate them.


73-82: Standard approach for preparing license key.

Same logic as in setLicenseKeyRemote—it might be worth unifying how exceptions are handled across all calls for consistency or specialized reporting should other HTTP codes appear.


84-90: Private inline function looks good, but be mindful of unhandled exceptions.

postRequest delegates to httpClient.requestForJson, which may raise an exception for non-2xx responses. Ensure callers are properly prepared to handle this, or add a robust error-handling mechanism here for a more uniform approach.


91-96: Usage retrieval is straightforward.

Similar to other endpoints, consider how you handle unexpected HTTP errors or 4xx/5xx responses to improve resilience. Otherwise, looks good.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1)

107-123: Consider making the subscription setup method more flexible.

The current implementation has hardcoded values for subscription properties. Consider implementing a more flexible approach by accepting a lambda parameter for customization, similar to what's done in other test classes like KeyCountLimitTest and SeatCountLimitTest.

-private fun saveSubscription() {
+private fun saveSubscription(build: EeSubscription.() -> Unit = {}) {
  eeSubscriptionRepository.save(
    EeSubscription().apply {
      licenseKey = "mock"
      name = "Plaaan"
      status = SubscriptionStatus.ERROR
      currentPeriodEnd = Date()
      enabledFeatures = Feature.values()
      lastValidCheck = Date()
      isPayAsYouGo = true
      includedKeys = 10
      includedSeats = 10
      keysLimit = 10
      seatsLimit = 10
+     build(this)
    },
  )
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ebf8dc and ca7288c.

📒 Files selected for processing (13)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt (2 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/data/GetMySubscriptionUsageRequest.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt (2 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/SubscriptionFromModelAssigner.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (1 hunks)
  • ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeTolgeeTranslatorControllerTest.kt (0 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt (4 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (3 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/service/EeSubscriptionProviderImplTest.kt (0 hunks)
💤 Files with no reviewable changes (2)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeTolgeeTranslatorControllerTest.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/service/EeSubscriptionProviderImplTest.kt
🚧 Files skipped from review as they are similar to previous changes (6)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEeSubscriptionModel.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/SubscriptionFromModelAssigner.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/model/EeSubscription.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/EeLicenseControllerTest.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (4)
backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (1)
  • executeInNewTransaction (268-274)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyCountLimitTest.kt (1)
  • saveSubscription (94-111)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (2)
  • saveSubscription (78-94)
  • parseRequestArgs (101-102)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt (1)
  • saveSubscription (101-118)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (19)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatUsageReportingTest.kt (6)

1-1: Better package structure reflecting the specific functionality.

The updated package name better organizes the code by clearly indicating this is related to self-hosted limits and reporting functionality.


33-36: Good test setup using @beforeeach.

Moving the initialization of eeLicenseMockRequestUtil to a dedicated setup method follows best testing practices by properly separating setup from test execution.


39-40: Improved test method name and structure.

The renamed test method more accurately describes what's being tested (seat usage reporting). Extracting the subscription setup into a separate method improves readability.


78-94: Comprehensive subscription configuration with new metrics properties.

The extracted saveSubscription() method properly sets up the test data with the new properties related to the keys/strings metric:

  • isPayAsYouGo
  • includedKeys and keysLimit for the new keys/strings metric
  • includedSeats and seatsLimit for the existing seats metric

This provides a clear foundation for testing both metrics.


97-97: Improved code structure with extracted parser method.

Extracting the parsing logic to a dedicated method parseRequestArgs() follows the single responsibility principle and makes the assertion method cleaner.


101-102: Well-implemented parsing utility.

The parseRequestArgs() method cleanly handles the conversion of the HTTP request body to a Map, making it easier to assert on specific fields.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (3)

22-30: Constants are well-organized.

Placing these endpoint paths in the companion object keeps this file more structured and centralized. No issues found.


44-53: Confirm if usage reporting requires a response.

reportUsageRemote discards the return value. If the remote server returns status or usage data that could be important for diagnostics, consider capturing it or logging the result.


55-60: Releasing the key is straightforward.

No issues found. The request is made, errors would be thrown if something goes wrong. Looks good.

ee/backend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt (6)

22-36: Well-structured test setup with proper dependency injection.

The test class properly extends AbstractSpringTest and uses Spring's dependency injection with @Autowired and @MockBean annotations. The setup() method with @BeforeEach ensures proper initialization of the mock utility before each test.


38-47: Good test for basic key usage reporting.

This test effectively validates the key lifecycle reporting by:

  1. Creating a key and verifying it's reported as 1
  2. Deleting the key and verifying it's reported as 0

The test is concise and focuses on a single aspect of functionality.


49-65: Effective test for request batching optimization.

This test validates an important performance optimization ensuring that multiple key operations don't result in excessive HTTP requests to the licensing service. The use of executeInNewTransaction ensures proper isolation of the batch operation.


67-85: Good coverage for key usage reporting after project/organization deletion.

Both tests verify that the system correctly reports zero key usage after either a project or organization is deleted, which is important for billing accuracy.


87-105: Well-implemented test helper method.

The testWithBaseTestData method provides a clean abstraction for test setup, mocking, and verification, which reduces duplication across test methods.


125-132: Good use of extension functions for assertions.

The assertKeys extension function and parseRequestArgs helper method make the tests more readable and maintainable by encapsulating the assertion logic.

ee/backend/app/src/main/resources/db/changelog/ee-schema.xml (4)

70-76: Included Keys Column is Configured Correctly.
The new included_keys column is added with a default value of -1 and a non-null constraint. This approach is consistent with the pattern used for similar columns elsewhere in the schema.


77-83: Included Seats Column is Configured Correctly.
The included_seats column uses a default value of -1 with a non-null constraint, which aligns with the intended design for handling default limits.


84-90: PAYG Flag Column Has Appropriate Defaults.
The is_pay_as_you_go column is defined with a default value of true and is non-nullable, properly reflecting the intended default behavior.


105-107: Validate Removal of cancel_at_period_end Column.
The removal of the cancel_at_period_end column should be carefully verified to ensure that no application logic depends on it. Confirm that all references in the codebase have been updated or removed accordingly to avoid runtime errors after migration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (12)
backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt (2)

209-213: Method name doesn't match exception type

The method name handleFileSizeLimitExceeded doesn't match the type of exception (HttpRequestMethodNotSupportedException) it's handling. This appears to be a naming inconsistency that should be fixed.

-  @ExceptionHandler(HttpRequestMethodNotSupportedException::class)
-  fun handleFileSizeLimitExceeded(ex: HttpRequestMethodNotSupportedException): ResponseEntity<Void> {
+  @ExceptionHandler(HttpRequestMethodNotSupportedException::class)
+  fun handleMethodNotSupported(ex: HttpRequestMethodNotSupportedException): ResponseEntity<Void> {
     logger.debug(ex.message, ex)
     return ResponseEntity(HttpStatus.METHOD_NOT_ALLOWED)
   }

215-226: Similar naming inconsistency in InvalidDataAccessApiUsageException handler

The method name also doesn't match the type of exception it's handling. Consider renaming it for consistency and clarity.

-  @ExceptionHandler(InvalidDataAccessApiUsageException::class)
-  fun handleFileSizeLimitExceeded(ex: InvalidDataAccessApiUsageException): ResponseEntity<ErrorResponseBody> {
+  @ExceptionHandler(InvalidDataAccessApiUsageException::class)
+  fun handleInvalidDataAccess(ex: InvalidDataAccessApiUsageException): ResponseEntity<ErrorResponseBody> {
     Sentry.captureException(ex)
     val contains = ex.message?.contains("could not resolve property", true) ?: false
     if (contains) {
       return ResponseEntity(
         ErrorResponseBody(Message.UNKNOWN_SORT_PROPERTY.code, null),
         HttpStatus.BAD_REQUEST,
       )
     }
     throw ex
   }
backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/current/CurrentUsageItemModel.kt (3)

1-1: Typo in package name: "uasge" should be "usage".

There's a typo in the package name io.tolgee.hateoas.ee.uasge.current - "uasge" should be "usage".

-package io.tolgee.hateoas.ee.uasge.current
+package io.tolgee.hateoas.ee.usage.current

3-7: Consider using a Kotlin data class for this DTO.

This class represents a data transfer object with three properties. In Kotlin, it's a common pattern to use the data class keyword for such classes, which automatically provides useful methods like equals(), hashCode(), toString(), and copy().

-class CurrentUsageItemModel(
+data class CurrentUsageItemModel(
  val current: Long,
  val included: Long,
  val limit: Long
)

3-7: Add KDoc documentation for better maintainability.

Consider adding KDoc comments to document the purpose of this class and its properties. This will improve code maintainability and help other developers understand the meaning of each field, especially the distinction between included and limit.

+/**
+ * Model representing current usage metrics for a specific feature or resource.
+ */
class CurrentUsageItemModel(
+  /** The current amount of the resource being used */
  val current: Long,
+  /** The amount included in the base subscription or plan */
  val included: Long,
+  /** The maximum allowed amount (may include additional purchased capacity) */
  val limit: Long
)
backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/current/CurrentUsageModel.kt (3)

1-1: Typo in package name: "uasge" should be "usage"

There's a typo in the package name - "uasge" should be "usage". Consider correcting this typo for consistency and to avoid confusion.

-package io.tolgee.hateoas.ee.uasge.current
+package io.tolgee.hateoas.ee.usage.current

11-17: Class implementation looks good, consider adding serialVersionUID

The model class is well-structured with appropriate properties for tracking different usage metrics. Since this class implements Serializable, consider adding a serialVersionUID to ensure version compatibility during serialization/deserialization.

@Suppress("unused")
open class CurrentUsageModel(
  val seats: CurrentUsageItemModel,
  val strings: CurrentUsageItemModel,
  val creditsInCents: CurrentUsageItemModel,
  val keys: CurrentUsageItemModel,
-) : RepresentationModel<CurrentUsageModel>(), Serializable
+) : RepresentationModel<CurrentUsageModel>(), Serializable {
+  companion object {
+    private const val serialVersionUID: Long = 1L
+  }
+}

6-10: Good documentation, consider enhancing with property descriptions

The class-level documentation is clear about the purpose of this model. Consider enhancing it by adding descriptions for each property to improve maintainability.

/**
 * Presents the current total usage for a subscription.
 *
 * It doesn't count proportional usage into consideration.
+ *
+ * @property seats Usage statistics for seats
+ * @property strings Usage statistics for strings
+ * @property creditsInCents Usage statistics for credits (in cents)
+ * @property keys Usage statistics for keys
 */
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (4)

1-16: Consider standardizing the class name spelling.
“Licencing” is non-standard spelling, and “Licensing” is typically preferred. Renaming may have a broad impact if this class name is already referenced in multiple places, so proceed only if consistent naming is important for your codebase style.


32-39: Handle additional HTTP errors.
This method only handles the happy path plus a null return for no result. Consider catch blocks or robust error handling for other 4xx or 5xx HTTP statuses, or at least logging them, to prevent silent failures.


62-71: Broaden error handling for setLicenseKeyRemote.
Only HttpClientErrorException.NotFound is captured, which is appropriate for missing license keys. However, consider handling or logging other possible client/server errors to provide clearer feedback or fallback behavior.


73-82: Similar consideration for prepareSetLicenseKeyRemote.
Same pattern as in setLicenseKeyRemote. To ensure comprehensive resilience, consider handling other error statuses or adding logs for unexpected exceptions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca7288c and d8ed83d.

📒 Files selected for processing (14)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/PrepareSetEeLicenceKeyModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/current/CurrentUsageItemModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/current/CurrentUsageModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/AverageProportionalUsageItemModel.kt (2 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/ProportionalUsageItemModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/SumUsageItemModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/UsageModel.kt (2 hunks)
  • backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt (2 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/EeSubscriptionUsageController.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/UsageModelAssembler.kt (2 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionUsageService.kt (1 hunks)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (1 hunks)
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt (3 hunks)
  • webapp/src/ee/eeLicense/SetupLicenceKey.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (3)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/ProportionalUsageItemModel.kt
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/AverageProportionalUsageItemModel.kt
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/SumUsageItemModel.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/hateoas/assemblers/UsageModelAssembler.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionUsageService.kt
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/EeSubscriptionUsageController.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/EeLicensingMockRequestUtil.kt
🔇 Additional comments (15)
backend/api/src/main/kotlin/io/tolgee/hateoas/ee/PrepareSetEeLicenceKeyModel.kt (1)

3-3: Import update aligns with new package structure.

The import path for UsageModel has been updated to reflect its new location in the proportional subpackage. This change is consistent with the PR objective of adding keys/strings as metrics, which likely required restructuring the usage models to accommodate more granular tracking of resources.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/proportional/UsageModel.kt (3)

1-1: Package structure change reflects the model's purpose.

The package has been modified to include "proportional" which accurately reflects the model's purpose as described in the updated documentation.


8-12: Documentation enhancement clarifies usage context.

The updated documentation clearly explains when this model should be used, specifically highlighting its application for proportional usage scenarios. This improvement helps developers understand the intended use case.


25-25: Keys metric added as required by feature.

The addition of the keys property implements the main objective of this PR - adding keys/strings as a metric. The implementation follows the same pattern as existing metrics (seats, translations), maintaining consistency in the codebase.

backend/app/src/main/kotlin/io/tolgee/ExceptionHandlers.kt (2)

14-14: Adding debug utility import

The added import for io.tolgee.util.debug is required for the debug logging functionality implemented on line 211.


211-211: Enhanced error logging for HttpRequestMethodNotSupportedException

Adding debug logging here improves observability and will help with troubleshooting issues related to unsupported HTTP methods.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/current/CurrentUsageItemModel.kt (1)

3-7: The core model structure looks good.

The model correctly uses immutable properties (with val) and appropriate types (Long) for usage metrics that could potentially grow large. The structure is simple and focused on its purpose of representing usage information.

webapp/src/ee/eeLicense/SetupLicenceKey.tsx (2)

19-19: Improved cache invalidation strategy.

Changing from a string to an array for the invalidatePrefix parameter provides more flexibility in cache invalidation after the mutation executes. This is a good practice when using React Query and similar libraries.


42-45:

Details

❓ Verification inconclusive

Verify that the generic confirmation message is sufficient.

The confirmation message has been simplified to use a generic translation key instead of dynamic content that previously might have included specific pricing and seat information. Please ensure this simplified message still provides users with all necessary context about their license activation.


🏁 Script executed:

#!/bin/bash
# Search for the new translation key to verify its content
echo "Checking for the new translation key in translation files:"
fd --extension json | xargs grep -l "ee-license-key-confirmation-message-generic" | xargs cat

Length of output: 220


Action Required: Confirm Presence and Sufficiency of Translation Content

It appears that our search for "ee-license-key-confirmation-message-generic" in translation files returned no content. Please verify that there is a corresponding translation entry for this key and that its message clearly conveys all necessary license activation details—especially since the previous dynamic content (with pricing/seat-specific information) has been removed.

  • Confirm Translation Key Definition: Ensure that the key exists in your locale JSON files and contains a complete, user-friendly message.
  • Sufficiency of Message: Validate that the generic confirmation message still provides users with all the context they need regarding their license activation.
backend/api/src/main/kotlin/io/tolgee/hateoas/ee/uasge/current/CurrentUsageModel.kt (1)

12-12:

Details

✅ Verification successful

Consider whether the class needs to be open

The class is marked as open, allowing it to be subclassed. Unless there's a specific requirement for extending this class, consider making it final for better encapsulation.


🌐 Web query:

When should Kotlin classes be marked as open?

💡 Result:

In Kotlin, classes should be marked as open in specific scenarios where extensibility is intentionally designed or required by external frameworks. Here's a structured breakdown:

Core Principles

Final by default:
All Kotlin classes are implicitly final, preventing inheritance unless explicitly marked open[1][2][4]. This enforces the "design for inheritance or prohibit it" principle from Effective Java[3].

Key Scenarios for open

1. Intentional class hierarchy design:

  • Mark classes open when creating base classes meant for extension (e.g., abstract templates)[1][4]
  • Example:
    open class Animal { /* base functionality */ }
    class Dog : Animal() { /* specialization */ }
    [2][4]

2. Framework interoperability:

  • Spring/Hibernate require open classes for proxy creation[2][3]
  • Two approaches:
    • Manually mark classes/methods:
      @Service
      open class UserService {
          open fun register() { /* ... */ }
      }
      [2]
    • Use kotlin-allopen plugin to automatically open annotated classes:
      @Service  // Plugin adds 'open' during compilation
      class UserService {
          fun register() { /* ... */ }
      }
      [2]

3. Method overriding requirements:

  • Methods/properties remain final even in open classes unless explicitly marked open:
    open class Shape {
        open fun draw() { /* ... */ }  // Overridable
        fun rotate() { /* ... */ }     // Final by default
    }
    [2][4]
  • Overrides stay open unless declared final:
    class Circle : Shape() {
        final override fun draw() { /* ... */ }  // Prevents further overriding
    }
    [4]

Critical Design Considerations

  • Initialization risks: Avoid using open members in constructors/initializers, as derived class properties might not be initialized[4]
  • Transitive inheritance: open isn't inherited - subclasses remain final unless explicitly marked open[2]
  • Bytecode equivalence: Non-open classes compile to final in JVM bytecode[2]

Citations:


Review Update: Confirm the Intentional Use of open

The current declaration:

open class CurrentUsageModel(

allows subclassing. As a reminder, Kotlin classes are final by default and should only be marked as open when you intentionally design them to be extensible—whether for creating a class hierarchy or to meet specific framework (e.g., Spring/Hibernate) requirements.

  • If no subclassing is planned and there’s no framework-imposed need: Consider removing the open modifier to enforce better encapsulation.
  • If extensibility is intended: It's appropriate to leave the class as open.

Please verify that the intended use (or future extension) of this model justifies it being open.

ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/cloudClient/TolgeeCloudLicencingClient.kt (5)

17-30: Constants look good.
The companion object organizes endpoints cleanly, enhancing code readability.


41-42: Straightforward reporting method.
No issues were found; the approach is consistent with other reporting methods.


44-53: Usage reporting logic looks solid.
The function usage is clear. The parameters (keys, seats) allow tracking new metrics.


84-90: Centralized request method is clear.
Using a private inline postRequest function promotes consistency. Ensure that handling POST for read-type operations is indeed the intended API design. If the remote server truly expects POST for fetch operations, this usage is valid.

Would you like to confirm with the remote server’s documentation or logs that POST is required for endpoints like “subscription”?


91-96: getUsageRemote endpoint is consistent with the existing design.
Though named “getUsageRemote,” it uses POST to the remote server. This remains acceptable if the upstream API dictates a POST-based usage query.

@JanCizmar JanCizmar force-pushed the jancizmar/add-keys-as-metric branch from 6372662 to bc6545c Compare April 11, 2025 13:17
Add `schedulingManager.cancelAll()` to ensure clean state in tests within `InvoiceTaskActionServiceTest`. Replace `forEach` with `repeat` in `SeatCountLimitTest` for better readability and correctness in handling user iteration.
Ensure `organizationOwner` is properly initialized before accessing it in `SeatUsageListener`. This prevents potential issues with uninitialized `organizationOwner` on older instances during seat usage processing.
…ser in test data

Integrates metricType prop into PlanPricesFields for improved configuration. Adds support for an "admin" user in test data and updates e2e tests to use explicit admin login.
…logic

- Moved E2E-related run configurations to the billing module.
- Consolidated and updated `setTestRetry` logic across projects.
- Improved `SavedSlackMessageService` to schedule message cleaning at runtime.
- moved the run logic to billing
- Replaced redundant MT provider disabling with unified configuration initialization.
- Enhanced debug logging in MtResultStreamer for better client communication tracing.
…UsageToReport

Modified `UsageReportingService` and `UsageToReportService` to ensure usage data is created if missing using `findOrCreateUsageToReport`. Added constraint handling to avoid conflicts when persisting data.
Logs added to improve visibility during subscription processing and handle prolonged remote check failures. Adjust test property and subscription values for consistency.
Add detailed debug logs for cache operations, subscription actions, and key count reporting to enhance traceability and diagnostics.
…figurations with new properties and refactor subscription handling

- Fixed the invoice task issue
- Introduced `clearLiquibaseChecksums` property for Liquibase configurations.
- Improved subscription handling with `isPayAsYouGo` logic and refactored price-related methods.
- Added synchronization and error handling in invoice task creation and deletion processes.
- Updated and added test cases for new billing logic and fixed plans.
@JanCizmar JanCizmar merged commit 4ba8027 into main Apr 29, 2025
38 checks passed
@JanCizmar JanCizmar deleted the jancizmar/add-keys-as-metric branch April 29, 2025 06:56
@coderabbitai coderabbitai bot mentioned this pull request Oct 3, 2025
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