-
-
Notifications
You must be signed in to change notification settings - Fork 326
feat: Add keys/strings as a metric #2942
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
096d9fb to
ac4afd9
Compare
64849b6 to
0a2939d
Compare
8facb49 to
7162292
Compare
|
Important Review skippedMore 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 WalkthroughThis 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
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
58e703c to
9cd5015
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
assertFilterfunction in test cases, but the import for this function appears to be missing. According to the AI summary, the import forassertFilterfrom '../../common/filters' has been removed.Please verify that the
assertFilterfunction is still accessible in this file. You'll need to either:
- Restore the import statement, or
- 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
assertFilterinnamespaces.cy.tsBased on our verification, similar test files (e.g.,
translationFilters.tags.cy.ts,translationFilters.states.cy.ts, andtranslationFilters.general.cy.ts) explicitly importassertFilterfrom'../../common/filters', whilenamespaces.cy.tsdoes 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): Languagemethod was removed, but the remainingfindLanguage(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 kotlinLength of output: 921
Action Required: Update and Handle Nullable Language Retrieval
It looks like there are still calls to the removed
getLanguagemethod in the machine translation modules. In the updatedProject.kt,findLanguage(tag: String)now returns a nullableLanguage?, 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
findLanguageand 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
nullexplicitly to avoid aNullPointerException.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 withdata-cyAttributeThe 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-cyattributes is consistent across all dialog components in your codebase. For instance, if similar dialogs in other modules follow a different naming scheme (likebilling-usage-dialogor 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:
- Adding KDoc comments to describe the purpose of this event class
- 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
LINKSconstant 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 instancesThere seems to be two ObjectMapper instances in this class:
mapper(line 107) and the newly addedobjectMapper. 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 utilityThe 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
KeysHintcomponent has identical structure toStringsHint, 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
KeysHintcomponent, thisSeatsHintcomponent 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 ButtonThe change updating the
data-cyattribute of theLoadingButtonon 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
handlePageChangefunction 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
isExclusiveprop. Consider adding similar documentation for thelinkprop 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-ignorecomments.The
@ts-ignorecomment suppresses TypeScript errors without addressing the underlying type issue. Consider properly typing thedata-cyattribute 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 attributesThe 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 elementFor 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
renderValuefunction 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
UseQueryResultfrom 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
PaginatedHateoasListcomponent 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 interfaceThe 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: Booleanwebapp/src/ee/billing/component/BillingProgress.tsx (1)
7-7: Remove unused importProgressDataThe
ProgressDatatype 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_publicstill 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
TestDataGeneratingServiceeven thoughAbstractE2eDataControlleralready 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
generatemethod properly uses@Transactionalto ensure data consistency and provides a flexible callback mechanism that aligns with theafterTestDataStoredmethod inAbstractE2eDataController. The method has a clear flow: save data, execute callback, return result.Consider adding some basic validation for the
testDataparameter to ensure it's properly initialized before processing.
23-46: Well-structured mapping logic for test data entities.The mapping logic in
getStandardResultis comprehensive and correctly handles all required entity types. The nullable types forprojectIdandorganizationIdproperly 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 typeThe props type is named
OrganizationCloudCustomPlansPropsbut is used for a component calledSubscriptionsPopoverSelfHostedCustomPlans. 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 nameUpdate 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 handlerIn the
onErrorhandler, theplan_seat_limit_exceededcase has areturnstatement after displaying the error message, but theseats_spending_limit_exceededcase 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 codesThe 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 planThe
onPlanChangefunction 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 valueThe component directly passes
field.valueto theSelfHostedEePlanSelector, 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 typoThe new
MetricTypeenum 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 consistencyThe props type is still named
CloudPlanSaveButtonPropswhile the component has been renamed toPlanSaveButton. 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 usingas anytype castingWhile 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
tableRowPropsinItemRowis typed asReact.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 patternThis 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
fillAndInvitemethod could returnthisfor 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 comparisonThe 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 parametersThe 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 CloudPlanEditFormThe 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 functionalityThis 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 implementationThis new component properly implements the
LimitsProviderinterface, 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 Reactand 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 implementationThe new
SelfHostedEePlanTemplateSelectorFieldcomponent 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
SelfHostedEePlanSelectorcomponent.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 codebackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/ProjectBuilder.kt (1)
81-92: Good addition for test data generationThe new
inviteUsermethod 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 implementationThe 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
selectAgencymethod 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 logicThe 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 ofO(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 testingThe
inviteUsermethod implementation looks good. It properly:
- Creates a new
InvitationBuilder- Adds it to the test data collection
- Sets up the organization role with OWNER type by default
- 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 implementationThe hook correctly uses the
useApiQueryhook 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 functionThe 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 optionCurrently, 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
PlanTypeSelectcomponent to display this tooltip when hovering over a disabled option.
24-33: Consider using a more type-safe approach for the switch statementThe 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 operatorThe 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 nameThe component has been renamed from
EstimatedCoststoExpectedUsage, but the props interface is still namedEstimatedCostsProps. For better consistency, consider renaming the props interface toExpectedUsagePropsto 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 goodThis component follows React best practices with clear separation of concerns and proper use of Formik for form management.
Consider these potential improvements:
- Memoize the
toggleFieldfunction 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]);
- 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 buttonThe 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 propsThe 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
metricTypeproperty 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 keysThe 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 wrapperThe 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
AssignCloudPlanValuesTypeon 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
onErrorcallback 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
AssignCloudPlanDialogFormcomponent:<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
assertMessageimport 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 /^_/uwebapp/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
Labelcomponent 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
_blankfor external links, it’s best practice to addrel="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 UnusedE2KeyCreateDialogImport.The static analysis tools flag
E2KeyCreateDialogas unused. If it’s only referenced byE2TranslationsView, 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 /^_/uwebapp/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.namemay produce undesired labels (e.g.,"Customized undefined"). For safety and clarity, ensureplanData.nameis 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 forwillOnlyMakeItVisible.The variable name
willOnlyMakeItVisiblemay 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 offalse | undefined.Passing
falseorundefinedforfilterPubliccan 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
loadingis 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 acrossIncludedStringsandIncludedKeys.
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
keysproperty 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 usingAverageProportionalUsageItemModelfor 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
📒 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
limitspackage and adding the necessary import forBadRequestExceptionimproves 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.
Thevalueattribute 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 thepublic/webappdirectory. 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 toio.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 ofio.tolgee.exceptions.BadRequestExceptionis 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:
TheTranslationSpendingLimitExceededclass correctly extendsBadRequestExceptionand passes the designated error message along with a list of parameters (requiredandlimit). 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 testingAdding the
data-cyattribute 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 SetupLicenceKeyThe import for
SetupLicenceKeyhas 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 ActiveEeLicenseThe updated import for
ActiveEeLicenseusing 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 EeLicenseHintThe import path for
EeLicenseHinthas 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 componentThe 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 viewChanging from
PaginatedHateoasListtoPaginatedHateoasTableenhances 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
PaginatedHateoasTableis a wrapper aroundPaginatedHateoasListthat provides table-specific rendering.backend/testing/src/main/kotlin/io/tolgee/AbstractSpringTest.kt (2)
4-4: LGTM: Appropriate imports for JSON handling and test utilitiesThe 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 utilityThis 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
./utilsto./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
UsagetoCriticalUsageCircleprovides 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
getProgressDatanow correctly passes an object with theusageproperty instead of passingusagedirectly, aligning with the updated function signature in thegetProgressData.tsfile.
115-118: Properly expanded props for UsageDetailed component.The props passed to
UsageDetailednow include all properties fromprogressDatausing the spread operator, plus the new requiredisPayAsYouGoproperty derived fromusage. This correctly fulfills the component's updated prop requirements as seen in theUsageDetailed.tsxfile.
124-125: Updated property names align with underlying implementation.The changes from
canGoOvertoisPayAsYouGoandmoreCriticalProgresstomostCriticalProgressproperly align with the properties expected by theCircularBillingProgresscomponent and returned by thegetProgressDatafunction.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
PaginatedHateoasTablePropstype effectively extends fromPaginatedHateoasListPropswhile omitting thelistComponentproperty 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:
- Using proper generic typing to maintain type safety
- Keeping the implementation simple - delegating most work to
PaginatedHateoasList- 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
isExclusiveprop. The pattern of wrapping the disabled button in aspanelement 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
isExclusiveprop.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 practicesThe 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 fieldsThe 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 componentThe 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
PlanTypeOptiontype 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 propsThe change from separate
valueandmaxValueprops to a consolidatedprogressItemprop enhances the component's clarity and maintainability. The renaming ofcanGoOvertoisPayAsYouGoalso makes the prop's purpose more explicit.
86-94: LGTM! Good adaptation of normalized value calculationThe calculation of the normalized value and critical state has been correctly adapted to use the new prop structure. The additional check for
isPayAsYouGoin 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 calculationsThe calculations for
extraandfullLengthhave been correctly updated to use the properties from theprogressItemobject, maintaining the same logic while adapting to the new structure.
114-124: LGTM! Updated label conditionThe condition for pushing labels into the
labelsarray has been properly updated to use the properties fromprogressItem. The translation parameters have also been correctly updated.
140-149: LGTM! Updated extra usage color conditionThe condition for determining the color of extra usage has been properly updated to use
isPayAsYouGoinstead ofcanGoOver, maintaining consistent naming throughout the component.
177-180: LGTM! Updated className assignmentThe className assignment has been properly updated to use
isPayAsYouGoinstead ofcanGoOver, 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
GenericPlanFormDatainstead ofCloudPlanFormData, 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
useFormikContextto useGenericPlanFormData, ensuring type consistency.
26-26:Details
❓ Verification inconclusive
Testing attribute updated to reflect generic usage.
The
data-cyattribute 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=tsxLength 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
--typeflag. I've added a revised search command that uses file globbing to cover both.tsand.tsxfiles. 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: 26data-cy="administration-plan-field-public"Comment:
Thedata-cyattribute 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
getStandardResulthelps 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
testDataGeneratingServicepromotes 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
BadRequestExceptionand 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
xpathfrom a mutableletto an immutableconstis 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
getInputUnderDataCymethod now properly uses the new immutable approach with method chaining.
65-65: Good addition of type definition.Adding the
XpathBuilderTypeimproves 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 goodThe implementation of the
getLoadablefunction correctly fetches self-hosted EE plans filtered by organization ID and excluding public plans.
25-38: LGTM - Component rendering looks goodThe component properly renders the
SubscriptionsPopoverCustomPlanswith appropriate props and renders each plan item correctly.webapp/src/component/security/AcceptInvitationView.tsx (3)
16-17: LGTM - Good addition of imports and hooksThe addition of the
ApiErrorimport anduseMessagehook improves error handling capabilities in the component.Also applies to: 49-49
91-91: LGTM - Good redirection updateChanging the redirect path to
LINKS.ROOT.build()instead ofLINKS.PROJECT.build()is a reasonable update to improve the user experience after an error occurs.
109-113: LGTM - Good addition of data-cy attributeAdding the
data-cyattribute to the success message improves testability.webapp/src/component/common/list/SimpleList.tsx (1)
5-17: LGTM - Good type improvementsThe changes to the
OverridableListWrapperstype 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:
- Add default types (
typeof Paperandtypeof List) for the generic parameters- 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 interfaceThe props interface is clear and properly typed, with the optional callback for plan changes.
17-23: LGTM - Good Formik integrationThe 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 useBillingApiQueryAdding 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 goodThis 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 appropriateThis 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 handlersSetting 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 addedAdding 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 featureThe 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 goodThe 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 appropriateThe 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 trackingThe addition of
keysproperty 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 propertyThe new
keysproperty 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 formattingSeparating the
@Suppressand@Lazyannotations 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
@Suppressand@Lazyannotations 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
InvitationBuilderimplementation 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
SeatSpendingLimitExceededexception is well-structured and follows the established pattern for limit-related exceptions. It properly extendsBadRequestExceptionand 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
perThousandKeysproperty 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
planproperty 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
TranslationsLimitProviderwith a more generic approach that can handle multiple types of limits (including the new keys metric) through theUsageLimitsreturn 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
ExpectedUsagecomponent and Material UIBoxcomponent, which aligns with the broader changes in the PR around usage metrics.
27-29: Component updated to use the new ExpectedUsage component.The previous
PlanUsageEstimatedCostscomponent has been replaced with theExpectedUsagecomponent wrapped in aBox. However, unlike theExpectedUsagecomponent definition shown in the relevant code snippets that accepts bothuseUsageandestimatedCostsprops, onlyuseUsageis being passed here.Please verify whether the
estimatedCostsprop is intentionally omitted. In theExpectedUsagecomponent 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 componentThe change replaces the
EstimatedCostscomponent with the newly createdExpectedUsagecomponent 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 goodThe new
KeysSpendingLimitExceededexception class properly extendsBadRequestExceptionand 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 generalizationThe component has been renamed from
CloudPlanSaveButtontoPlanSaveButtonand 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 testabilityThis 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 featuresThese new properties are well-structured for tracking included keys and seats in subscription plans, enabling pay-as-you-go pricing models. The use of
Longtype 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 signatureThe order of parameters has been correctly updated to align with changes in the
OrganizationRoleService.removeUsermethod signature, which now expectsuserIdfirst, followed byorganizationId.e2e/cypress/e2e/translations/batchJobs.cy.ts (1)
19-19: Import path updated to use new E2NamespaceSelector componentThe import was correctly updated to use the new
E2NamespaceSelectormodule, reflecting the refactoring where theselectNamespacefunction 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
parentNameprop- 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
Tcomponent- 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
PlanPublicChipreflects 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 testingThe addition of the
invitationsproperty to theTestDataStandardResponsetype 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 metricsThe new
UsageLimitsdata 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 nestedLimitclass with itsincludedandlimitproperties 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 typesThe React import is necessary for JSX usage and accessing React types.
14-14: Enhanced component flexibility with tableRowPropsGood addition of the optional
tableRowPropsproperty that allows passing additional props to the TableRow component. Using theReact.ComponentProps<typeof TableRow>type ensures proper type safety.
22-22: Correctly spreads tableRowProps to TableRowThe 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 componentThis 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.limitspackage 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
keysUsageproperty aligns with the PR objective of adding keys/strings as a metric.
15-19: Total calculation property updated correctly.The
totalproperty calculation now properly includes the sum ofkeysUsage, 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
revokemethod has been swapped. Since both parameters are of typeLong, 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 kotlinLength 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.ktwherepermissionService.revoke(userId, projectId)is used, the updated parameter order in therevokemethod (i.e.userIdfirst and thenprojectId) 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 plansThe changes add a new
metricTypeproperty 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 componentThe addition of the
metricTypeproperty with value'KEYS_SEATS'is consistent with the PR changes. According to the summary, this replaces the previousisLegacyproperty, indicating a transition from legacy checks to specific metric types.webapp/src/ee/billing/component/CircularBillingProgress.tsx (4)
48-48: Property renamed for clarityThe
canGoOverproperty has been renamed toisPayAsYouGo, which better reflects the billing model being used.
55-56: Updated prop name in component parametersThe property destructuring has been updated to use the new
isPayAsYouGoname.
60-60: Updated condition logicThe condition for determining whether the progress is critical now uses
!isPayAsYouGoinstead of the previous!canGoOver, maintaining the same logic with the renamed property.
103-104: Updated className in JSXThe class name in the JSX has been updated to use
isPayAsYouGoinstead ofcanGoOver, 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 componentThis new component follows good React practices:
- Fetches plans from the appropriate API endpoint
- Handles loading state gracefully
- Properly filters plans by organization when needed
- Reuses the generic selector component with appropriate type parameters
The component is well-typed using the
SelfHostedEePlanAdministrationModelfrom 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 definitionsThe 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 checkThe component properly checks if
planIdexists 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 structureThe import path has been updated to use the new
CloudPlanEditFormcomponent 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
keysproperty with default value-1Laligns 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
revokemethod call has been reversed frompermissionService.revoke(projectId, userId)topermissionService.revoke(userId, projectId). Verify that this matches the method signature in thePermissionServiceclass.
🏁 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.ktLength 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 thePermissionService.ktfile (expected atbackend/api/src/main/kotlin/io/tolgee/service/security/PermissionService.kt) did not locate it. Please verify—manually or via an alternative search—that the actualrevokemethod signature in thePermissionServiceimplementation 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
CloudPlanCreateFormfrom 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
CreateCloudPlanFormtoCloudPlanCreateFormto 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
UsageDialogButtontoExpectedUsageDialogButtonto 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-cyattribute has been updated frombilling-estimated-costs-open-buttontobilling-expected-usage-open-buttonto match the component's new name and purpose.
33-38: Dialog formatting and data-cy attribute added.The Dialog component now includes a
data-cyattribute 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 projectsThe new
findDeletedmethod complements the existingfindmethod, 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 classThis 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 correctlyThe import for
selectNamespacehas 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 descriptionChanged 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 implementationThis 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
E2ProjectMembersInvitationDialogclass 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_keyscolumn to theee_subscriptiontable with appropriate type, default value, and constraints.
77-83: Schema change looks good, adding included_seats column.This changeSet correctly adds the
included_seatscolumn to theee_subscriptiontable, 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_goboolean 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
EditCloudPlanFormtoCloudPlanEditForm, which better aligns with the file name and the component naming pattern.
88-88: Property renamed to improve clarity.The property
editPlanIdhas been changed toisUpdate={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 whenstripeProductIdis 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 handlingThe addition of
CurrentDateProviderandaddMonthsextension function enhances date handling, moving from direct manipulation ofInstantandDurationto a more reusable approach.Also applies to: 24-24
38-38: Added dependency injection for CurrentDateProviderExcellent refactoring to inject the
CurrentDateProviderwhich improves testability by making the class less dependent on system time, allowing for easier mocking in tests.
103-103: Refactored date calculation using CurrentDateProviderThe refactoring replaces a direct
Instant.now()andDurationcalculation with the more testablecurrentDateProvider.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 featuresThese 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 ProjectServiceImport aligns with the new dependency in the constructor.
18-18: Added ProjectService as a constructor dependencyGood practice to inject the required service as a constructor parameter.
29-31: Early exit when project doesn't exist anymoreExcellent 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_FIXEDhas been removed and there are no other enum values that setusesSlotsto 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 kotlinLength of output: 32
Below is an additional verification step to help ensure that no Kotlin file references the
usesSlotsproperty. 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
usesSlotsproperty 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 5Length of output: 1238
Safe to Remove
usesSlotsPropertyAfter a thorough search, the only occurrence of
usesSlotsis 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
usesSlotsproperty 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
usedCreditsprovides valuable context alongside the existingcreditBalance.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
isPayAsYouGoproperty is correctly set tofalsein 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
dataCyattribute 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 systemThe addition of
previousStateandpropertyNamesparameters to theonUpdatemethod 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 valuesThe onPlanChange handler correctly maintains the form's current name while updating other values from the selected plan template. Setting
public: falseensures consistency for derived plans.backend/data/src/main/kotlin/io/tolgee/service/machineTranslation/mtCreditsConsumption/MtCreditBucketService.kt (1)
173-173: Good addition to usage tracking metricsAdding the
usedCreditscalculation 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 goodThe import statement has been updated to reflect the reorganization of the codebase, moving
selectNamespacefrom 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 interfaceThe import of
PlanWithIncludedKeysAndSeatsis correct and aligns with the implementation.
22-26: Good documentation additionThe class documentation clearly explains the purpose of the entity and its role in the self-hosted instance.
29-29: Interface implementation is appropriateAdding
PlanWithIncludedKeysAndSeatsto the implemented interfaces correctly extends the functionality of theEeSubscriptionclass.
72-76: New properties align with PR objectivesThe addition of
includedKeys,includedSeats, andisPayAsYouGoproperties 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 hookThe 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 OrganizationRoleTypeGood addition of the import for
OrganizationRoleTypewhich is necessary for the newinviteUsermethod.webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/getSelfHostedPlanInitialValues.ts (3)
1-6: Appropriate imports and function signatureThe imports and function signature are correctly set up. The function accepts an optional
planDataparameter of typeSelfHostedEePlanAdministrationModeland returns aSelfHostedEePlanFormDataobject.
7-20: Proper handling of existing plan dataThe function correctly handles the case when
planDatais 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
keysfield inincludedUsageis properly initialized to 0, which aligns with the PR's purpose of adding keys/strings as a metric.One observation: the default
isPayAsYouGois set totrue, 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:
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].
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].
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:
- 1: https://stripe.com/resources/more/saas-pricing-models-101
- 2: https://betterproposals.io/blog/saas-pricing-models/
- 3: https://baremetrics.com/blog/saas-pricing-models
- 4: https://userpilot.com/blog/saas-pricing-models/
- 5: https://www.maxio.com/blog/why-fixed-subscriptions-no-longer-work
- 6: https://www.vendr.com/blog/saas-pricing-model
- 7: https://www.cobloom.com/blog/saas-pricing-models
- 8: https://mvix.com/blog/subscription-vs-one-time-cost/
- 9: https://docs.stripe.com/products-prices/pricing-models
- 10: https://cpl.thalesgroup.com/software-monetization/saas-pricing-models-examples
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
keysmetric is correctly set to 0. One point to verify is the default setting ofisPayAsYouGototrue. 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 importsAll necessary imports are correctly included for React, API hooks, types, and component dependencies.
7-16: Well-defined component typeThe component type is well-defined using TypeScript's utility types to extend the
GenericPlanSelectortype while adding additional props. This approach ensures type safety and proper prop validation.
16-24: Appropriate API query implementationThe component correctly uses the
useBillingApiQueryhook to fetch cloud plans from the API, passing thefilterPublicprop to filter results.
25-27: Proper null handling for missing dataThe 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 renderingThe component rendering is clean and correctly passes the fetched plans to the
GenericPlanSelectorcomponent along with any additional props.webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/useCreatingForOrganization.ts (5)
1-10: Good documentation and importsThe 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 definitionThe hook correctly accepts an
initialPlanNameparameter with appropriate TypeScript typing.
19-22: Safe parsing of organization IDThe 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 generationThe
getInitialPlanNamefunction 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 valueThe 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 annotationsThe new
keysproperty correctly includes the@Schemaannotation 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 importsAll necessary imports are included for React, Material-UI components, Formik context, and custom hooks.
7-10: LGTM! Well-defined props interfaceThe props interface is clearly defined with appropriate optional parameters.
26-34: LGTM! Clean return with proper accessibility attributesThe 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 loggingAdding the
@ActivityLoggedPropannotation to thedeletedAtproperty 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 typesThe imports and type definition are appropriate for the component's functionality.
43-49: LGTM! Clean component renderingThe component return statement is clean and appropriately passes props to the
PlanTypeSelectcomponent.webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanTemplateSelectorField.tsx (1)
6-7: Renaming and import changes look goodThe component has been renamed from
PlanTemplateSelectortoCloudPlanTemplateSelectorFieldand now usesCloudPlanSelectorinstead ofPlanSelector. 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 goodThe 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 loadingAdding
fetch = FetchType.LAZYto 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 goodThe component declaration and imports are well-organized. The type definition
OrganizationCloudCustomPlansPropsis clear and properly references the generated schema type from the API.
15-24: API query implementation is appropriateThe implementation of
getLoadablefollows 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 goodThe component properly passes the
getLoadablefunction andrenderItemimplementation to theSubscriptionsPopoverCustomPlanscomponent. The URL construction in theeditLinkprop 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 goodUpdated import path for
CloudPlanFormDatafrom './types' improves code organization by centralizing type definitions.
16-16: Added support for new 'keys' pricing metricThe addition of
perThousandKeysto 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 handlingThe refactoring to use the spread operator for
planData.includedUsagesimplifies 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 typeThe addition of
metricType: 'KEYS_SEATS'andkeys: 0to 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 viewThe 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 correctlyThe data-cy attribute has been properly updated to match the new component name, maintaining consistency for testing selectors.
39-45: Component renamed correctlyThe 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 classThe
E2NamespaceSelectorclass 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 exportExporting 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 contextAdding
previousStateandpropertyNamesparameters to theonUpdatemethod 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 queryThis 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 kotlinLength of output: 2072
Based on the grep results, the new
countAllOnInstance()method inKeyServicesimply delegates to the repository method. However, the repository definition inKeyRepository.ktonly 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.ktfor 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 hookImporting the useOrganization hook allows the component to dynamically get the organization context.
22-22: Correctly retrieving organization from contextUsing the organization from context is a much better approach than using a hardcoded ID.
30-30: Fixed hardcoded organization IDExcellent change replacing the hardcoded organization ID with the dynamic value from context.
51-51: Good addition of data-cy attributes for testingAdding these attributes improves testability.
54-59: Clean update to ItemRow component propsThe component now correctly passes data-cy attributes to the underlying TableRow.
71-73: Support for new KEYS usage typeThis 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
isActivefunction has been refactored with a clearer logical flow using early returns. The new implementation:
- First checks if plan IDs match
- Handles special cases (trial or free plans)
- Verifies billing period match only when necessary
This is more maintainable than the previous implementation that used a
planPeriodvariable andisPlanPeriodDependantcheck.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
metricTypeproperty instead of the previousisLegacyflag, 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
satisfiesoperator 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 fieldsThe validation rules for pricing fields have been updated to allow for zero values (
min(0)instead ofmoreThan(0)), and new fields forperSeatandperThousandKeyshave 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 componentThis 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 E2TranslationsViewRefactoring to use the
E2TranslationsViewclass 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 structureThe data structure for plural translations has been updated to use a more organized format with a dedicated
pluralproperty containingformValues, which better represents the hierarchical nature of plural translations.Also applies to: 109-112
129-136: Consistent use of E2TranslationsView for namespace testsThe namespace test case now also uses the
E2TranslationsViewclass, maintaining consistency across all test cases.webapp/src/ee/billing/administration/subscriptions/components/AdministrationSubscriptionsListItem.tsx (2)
7-9: Import structure updated for table-based layoutThe 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 layoutThe component has been refactored from using
ListItemto a more appropriateTableRowandTableCellstructure, 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 interfaceThe Props interface clearly defines the expected input parameters for the component with appropriate types.
18-50: Well-structured cloud plan popover componentThe 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 defaultsThe props interface is well-defined with optional parameters, allowing for flexible component usage.
23-69: Responsive grid layout with conditional rendering based on metric typeThe component implements a clean grid layout for form fields and properly conditionally renders different fields based on the
metricTypeprop. 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 fieldsThe interface is well-defined with appropriate types for the optional parameters.
16-31: Effective form state management with hooksThe 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 renderingThe 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 featuresThe new type definition
SelfHostedEePlanModelderived from the API schema ensures stronger type safety when working with plan features, particularly for the enterprise plan.
22-22: Simplified component type declarationChanging from
React.FC<BillingPlansProps>toFC<BillingPlansProps>makes the code cleaner and more consistent with the imports.
55-61: Added includedUsage property to enterprise planThe
includedUsageproperty with-2values 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 planThe 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 functionalityThe new imports support the enhanced organization-specific plan creation workflow, introducing the CloudPlanTemplateSelectorField and useCreatingForOrganization hook.
14-14: Consistent component namingRenamed from CreateCloudPlanForm to CloudPlanCreateForm for better naming consistency across the codebase.
25-30: Improved code organizationThe onSaveSuccess function has been moved before its first use, improving readability and code organization.
32-37: Enhanced organization integrationAdded the useCreatingForOrganization hook to streamline handling of organization-specific data and plan naming, which simplifies the component logic.
45-45: Improved organization ID handlingChanged 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 logicUpdated the disabled state to directly check for forOrganization.id existence, making the code more readable and maintainable.
69-72: Enhanced plan creation UIAdded 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 AdditionalTestDataSaverThese 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 importsThe 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 interfaceThe CloudPlanSelectorFieldProps interface is well-defined with appropriate types, including an optional organizationId, callback for plan changes, and filter flag.
16-30: Effective form field integrationThe 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 implementationThe 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
SelfHostedEePlanFormfor 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
@Configurableannotation 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
AssignCloudPlanValuesTypetype 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
satisfiesoperator 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 typescriptLength of output: 64
Action: Manually Verify TypeScript Version Compatibility
The initial script didn’t return a TypeScript version from the
devDependenciesinpackage.json. This might indicate that TypeScript isn’t explicitly defined there or is located elsewhere (e.g., independencies). Please manually confirm that your project is using TypeScript v4.9 or later to ensure compatibility with thesatisfiesoperator used in the snippet at lines 93-94 ofwebapp/src/ee/billing/administration/subscriptions/components/cloudPlan/assignPlan/AssignCloudPlanDialog.tsx.
- Check both
dependenciesanddevDependenciesinpackage.jsonfor 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
planas 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
E2TranslationsViewandkeyCreateDialogencapsulates 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
removeUserhas 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:
- Added @transactional annotation to provide explicit transaction management
- 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 kotlinLength of output: 752
Parameter Ordering and Transaction Management Verified
The changes are confirmed:
- The use of the
@Transactionalannotation now provides explicit transaction management.- The parameter ordering update (placing
userIdbeforeorganizationId) has been consistently applied across all call sites, including in:
backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/organization/OrganizationController.ktbackend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.ktbackend/app/src/test/kotlin/io/tolgee/service/organizationRole/OrganizationRoleCachingTest.ktAll 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
ProjectHardDeletingServiceinstead 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.hardDeleteProjectinstead ofprojectService.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
MetricTypeas 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
GenericPlanFormDatainterface 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
CloudPlanFormDatainterface properly extends the generic plan data with cloud-specific fields liketypeandmetricType. This creates a clear type hierarchy.
28-29: Direct use of schema type for self-hosted plans.Using the schema type directly for
SelfHostedEePlanFormDatais 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 )orisCurrentlyPaying. IfselectedPlanisnull, 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@Asyncevent listener.
onProjectSoftDeletedcallsself.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 ofKeysHintandStringsHintor the usage of React types.
12-19: Docstring anddata-cyprop usage looks appropriate.
Usingdata-cyis a common approach for Cypress testing. No issues here.
22-61: Logic forIncludedStringsis 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:IncludedCreditsimplementation is straightforward.
The usage ofMtHinthelps unify the hint concept for credits. Looks consistent with the other included items.
145-184:IncludedSeatsaligns 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.
StoringBigMetaDtoand 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:getSeatCountToCountSeatsmethod is succinct.
It directly counts distinct users, which is straightforward and maintainable. Ensure that the query’s behavior matches all membership scenarios.
43-53:getTranslationCountmethod 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:getKeyCountstrongly 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 forlanguageServiceis still a valid approach here.
91-93:findDeletedmethod for retrieving soft-deleted projects.
Explicitly referencing the project repository’sfindDeletedhelps maintain separation of concerns.
213-214: Soft-delete event publishing.
PublishingOnProjectSoftDeletedfosters 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
testWithBaseTestDatafunction eliminates repetitive code across multiple tests, making the test suite more maintainable and readable.
168-171: Good implementation of key assertion method.The
assertKeysmethod follows the same pattern asassertSeats, maintaining consistency in the codebase.
173-174: Good extraction of helper method for request parsing.Extracting
parseRequestArgsimproves 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
SetLicenseKeyLicensingDtohas 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
keysandseatsparameters 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
usedMtCreditsfield 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.removeUserhas 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
removeUsermethod signature in theOrganizationRoleServiceclass 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
removeUserMethodWe need to confirm that the
OrganizationRoleService.removeUsermethod now accepts parameters in the new order(userId: Long, organizationId: Long), matching the updated invocation inOrganizationController. 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.ktfile and inspect theremoveUsermethod 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" fiEnsure 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 newsaveProjectInvitationsmethod. The usage is correct and consistent.
13-13: New invitation service import
AddingInvitationServiceis necessary for the newly introduced invitation-saving methods. Looks good.
73-73: New constructor parameter
InjectingInvitationServiceinto 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 torunBeforeSaveMethodsOfAdditionalSaversensures 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 callingsaveOrganizationData(builder)early ensures organization and user data exist before project creation. This looks well-reasoned.
115-116: Invoking additional savers after saving
runAfterSaveMethodsOfAdditionalSaverscomplements the before-save logic, allowing post-save tasks to run. The approach is consistent and clear.
121-121: Invoking additional savers before cleaning
runBeforeCleanMethodsOfAdditionalSaversensures custom logic is executed prior to test data cleanup. The placement is logical.
139-139: Invoking additional savers after cleaning
runAfterCleanMethodsOfAdditionalSaverscompletes the before/after pattern for cleaning. The separation of responsibilities is clear and maintainable.
174-174: Saving organization invitations
CallingsaveOrganizationInvitations(builder)ensures that any invitations linked to organization roles are persisted. Implementation seems straightforward.
220-220: Saving project invitations
saveProjectInvitationspersistently stores invitations associated with a givenProject. 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 theisPayAsYouGoproperty 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" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| <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" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <T keyName="administartion_billing_assign-plan-save_button" /> | |
| <T keyName="administration_billing_assign-plan-save_button" /> |
...illing/administration/subscriptionPlans/components/planForm/genericFields/PlanTypeSelect.tsx
Outdated
Show resolved
Hide resolved
| export const SubscriptionsPopoverCustomPlans = <T,>( | ||
| props: SubscriptionsPopoverCustomPlansProps<any> | ||
| ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| export const SubscriptionsPopoverCustomPlans = <T,>( | |
| props: SubscriptionsPopoverCustomPlansProps<any> | |
| ) => { | |
| export const SubscriptionsPopoverCustomPlans = <T,>( | |
| props: SubscriptionsPopoverCustomPlansProps<T> | |
| ) => { |
...p/src/ee/billing/administration/subscriptions/components/generic/useSetZeroPricesWhenFree.ts
Outdated
Show resolved
Hide resolved
...c/ee/billing/administration/subscriptions/components/generic/SubscriptionCurrentPlanInfo.tsx
Show resolved
Hide resolved
backend/data/src/main/kotlin/io/tolgee/service/project/ProjectHardDeletingService.kt
Outdated
Show resolved
Hide resolved
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 functionThe 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
@Transientand@Column(insertable = false, updatable = false), which might be redundant.@Transientindicates the property should be excluded from persistence entirely, while the@Columnannotation 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
@Transientwould be sufficient. If it should map to an existing column but not be part of DML operations, then only the@Columnannotation 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:
- 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)
- 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
BaseTestDataimport appears to be unused in this file. Consider removing it if it's not needed.-import io.tolgee.development.testDataBuilder.data.BaseTestDatabackend/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:
- Adding
@field:NotNullor similar constraints if these fields are required- Adding documentation comments to explain the purpose of each field
- 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: Booleanee/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
saveSubscriptionmethod 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
saveTestDatamethod 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: EmptythenAnswerblock could be improved.The
thenAnswerblock 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
onActivitymethod,onOrganizationDeleteddoesn't userunSentryCatching. 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
NoActiveSubscriptionExceptionbut might miss other potential exceptions during reporting. Consider usingrunSentryCatchinghere 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
📒 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
@Autowiredto 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
includedUsageproperty with specific values forseatsandkeysaligns 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
isPayAsYouGoproperty 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 offalseis 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
checkSeatCountmethod likely throws exceptions if limits are exceeded, but there's no specific handling for this case.Verify that
checkSeatCountexceptions 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
KeyServiceare 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 inSetLicenseKeyLicensingDtoproperly 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
checkSeatCountmethod 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
keysparameter 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
BaseTestDataandEeLicensingMockRequestUtil, supporting the refactored test structure and JUnit 5 annotations.Also applies to: 6-7, 12-12
24-24: Class renaming enhances clarity.Renaming from
UsageReportingTesttoSeatUsageReportingTestprovides 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
eeLicenseMockRequestUtilto a dedicated@BeforeEachmethod 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 = truesupports 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 theassertSeatsmethod 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
@Autowiredand@MockBeanfor the RestTemplate.
29-38: Great test case validating key limit enforcement.This test confirms that the application correctly throws a
PlanKeysLimitExceededexception 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
@SpringBootTestis 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
assertKeysextension 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 3Length of output: 2688
Clarification on Billing Reporting Condition
After verifying similar implementations across the codebase, it’s clear that the check using
billingConfProvider().enabledis intentionally used to skip usage reporting in environments where billing is enabled (e.g., on Tolgee Cloud). We see this same pattern in bothEeOnUserCountChangedListener.ktandEeKeyCountLimitListener.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.ktee/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 5Length of output: 19846
Action Required: Reevaluate Key Counting Efficiency
The
onKeyCountChanged()method directly invokeskeyService.countAllOnInstance(), which simply delegates to a repository count query. Our verification shows that the implementation inKeyRepositoryexecutes a straightforward count (i.e., using a query similar toselect 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.
StoringkeyCountat the class level can be risky if multiple events occur within the same transaction. The code initializeskeyCountfrom 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 checkif (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.
Ifevent.getUsageIncreaseAmount()ever returns a negative value (e.g., for key removals), this logic will decrementkeyCount. Verify that negative usage is valid and tested. Also, updatingkeyCountonly 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 toeeSubscriptionService.checkKeyCount(keyCount!!)is straightforward and ensures that any limit violations surface before committing the transaction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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: ThetestDataproperty is never initialized or used.You've declared a
lateinit var testData: BaseTestDatabut 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
setupmethod, 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 commentThe 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
SelfHostedUsageLimitsModelclass looks good overall. However, unlikeLimitModel, the properties here don't have@Schemaannotations. 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
requiredandlimitparameters representExample:
/** * 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 parameterThe method is named
checkSeatLimits()(singular "Seat") while the parameter is namedseats(plural). Consider using consistent pluralization for better readability.- private fun checkSeatLimits() { + private fun checkSeatsLimits() {
34-35: Consider using constructor injection instead of ApplicationContextWhile 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 slotsThe 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 classThe 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
mtCreditsInCentsproperty to explain what MT credits are and their purpose in the system.
16-29: Consider adding utility methods and using constants for magic numbersThe Limit class is well-structured, but consider these improvements:
- 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() }
- 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
PlanLimitExceededStringsExceptionuses the term "Strings" while the error message constant isMessage.PLAN_TRANSLATION_LIMIT_EXCEEDEDwhich uses "TRANSLATION". For better consistency and maintainability, consider either:
- Renaming the class to
PlanLimitExceededTranslationsExceptionto match the message constant- Using a more consistent message constant like
PLAN_STRINGS_LIMIT_EXCEEDEDThis 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 forincludedKeys.The
includedKeysproperty 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.,
PlanSpendingLimitExceededKeysExceptionvsPlanLimitExceededKeysException). 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
requiredandlimitare 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
requiredwould 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
GenericLimitCheckerwith 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 ensuresrequiredwon'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 theincluded_keyscolumn as a BIGINT with a default value of-1and a non-null constraint appears intentional. Please verify that using-1correctly 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 toincluded_keys, theincluded_seatscolumn is added as a BIGINT with a default value of-1and 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 logicThe
saveSubscriptionmethod is similar to those in related test files likeKeyCountLimitTest.ktandSeatUsageReportingTest.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, andseatsLimit) 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
-1for some values (keys, strings, mtCreditsInCents) but10for 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
isCloudproperty is computed by invokingbillingConfProvider. 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 storingkeyCountin 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
📒 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
BadRequestExceptionand 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
BadRequestExceptionwhich 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 trackingAdding the
userSourceparameter 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 thecreateUsermethod.backend/api/src/main/kotlin/io/tolgee/hateoas/limits/LimitModel.kt (1)
11-26: Well-designed HATEOAS model for representing usage limits.The
LimitModelclass is well-structured with clear documentation. The use of-1as 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
BadRequestExceptionand 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
BadRequestExceptionand 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
eeSubscriptionfield should be deleted rather than commented out. This keeps the codebase clean and prevents confusion for future developers.Additionally, you should ensure the
InitialDataModelclass has been updated to make theeeSubscriptionfield 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 kotlinLength of output: 980
Clean Up: Remove Unused eeSubscription Code
The commented-out initialization in
InitialDataController.ktshould be deleted. We've confirmed that theInitialDataModel(inbackend/api/src/main/kotlin/io/tolgee/hateoas/InitialDataModel.kt) no longer includes theeeSubscriptionfield, ensuring consistency with the removal.
- Remove the commented block initializing
eeSubscriptioninInitialDataController.kt(lines 48-53).- No further updates are needed in
InitialDataModelsince 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
eeSubscriptionfrom 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 tsxLength 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
PlanWithIncludedKeysAndSeatsaligns 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.*tojakarta.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
EeSubscriptionentity, which improves code maintainability and onboarding for new developers.
23-23: Interface implementation updated.The class now implements
PlanWithIncludedKeysAndSeatsinstead 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
includedKeysandincludedSeatsproperties 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
keysLimitproperty, 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
seatsLimitproperty, which improves code maintainability. The default value of 0L is appropriate for initialization.
85-85: Pay-as-you-go flag added.The
isPayAsYouGoproperty 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
requiredis 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
requiredand delegates toGenericLimitCheckerwith 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
SeatsLimitCheckerare 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
SeatsLimitCheckerare 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
requiredisnull(i.e., that the check is properly bypassed).- Limit Exceedance: Conditions when
requiredbreaches 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".
Theis_pay_as_you_gocolumn is introduced as a BOOLEAN with a default value oftrueand a non-null constraint. Ensure that the default oftruecorrectly 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 tokeys_limit, theseats_limitcolumn 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-nullBIGINTwithout a default value—similar to"keys_limit". Please double-check that either the tableee_subscriptionis 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
@Autowiredto 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
includedUsagewith seats and keys properties, along with theisPayAsYouGoflag, properly supports the new keys/strings metric being added in this PR.
54-71: Well-structured limits implementation.The new
limitsproperty inmockedSubscriptionResponsecorrectly implements the SelfHostedUsageLimitsModel with appropriate LimitModel instances for keys, seats, and mtCreditsInCents. The structure properly reuses the values frommockedPlan.includedUsageto 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.selfHostedLimitsAndReportingimproves code organization by grouping related tests together.
23-23: Class name better reflects its purpose.Renaming from
UsageReportingTesttoSeatUsageReportingTestmore clearly describes what the test is focusing on.
33-36: Good test initialization pattern.Adding a
@BeforeEachsetup method to initialize theeeLicenseMockRequestUtilimproves test organization and follows standard testing practices.
39-39: More descriptive test method name.The renamed test method
it reports seat usageclearly 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
assertSeatsmethod 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 testsThe test class is well-organized and follows the established pattern of other usage reporting tests. The class is appropriately annotated with
@SpringBootTestand extendsAbstractSpringTest.
38-47: Clear test structure with appropriate assertionsThis 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 checkThis 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 coveredTesting 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 deletionThis 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 methodThe
testWithBaseTestDatamethod 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 helperThe
assertKeysextension function is a clean way to verify the reported key count in the HTTP requests, improving test readability.
131-133: Effective HTTP request parsingThe
parseRequestArgsmethod 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
keysparameter 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, andapplicationContextis 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
SetLicenseKeyLicensingDtois 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
SeatsLimitCheckeris 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
reportUsageRemotemethod 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 ofbillingConfProviderproperty call.It appears that
billingConfProvideris declared as a property but is being invoked like a function. Please confirm you intended to call aninvoke()operator method. Otherwise, you may want to replacebillingConfProvider()withbillingConfProvider.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.
backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/InitialDataController.kt
Outdated
Show resolved
Hide resolved
...nd/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/generic/KeysLimitChecker.kt
Show resolved
Hide resolved
...ackend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt
Show resolved
Hide resolved
| fun checkKeyCount(keys: Long) { | ||
| KeysLimitChecker( | ||
| required = keys, | ||
| limits = selfHostedLimitsProvider.getLimits(), | ||
| ).check() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| 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() | |
| } |
...end/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
requiredrepresents (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
requiredis somewhat ambiguous. A more descriptive name likerequiredStringsCountwould 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 thecheckmethod 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
📒 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/EeSubscriptionServiceImpl.kt (1)
252-266:⚠️ Potential issueWhere is the corresponding key count check method?
The PR adds support for keys as a metric, but there's no
checkKeyCountmethod analogous tocheckSeatCount. 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_EXCEEDEDin theMessageclass.ee/backend/app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountLimitListener.kt (1)
35-35: Consider thread safety or transactional integrity forkeyCount.Because
keyCountis read, initialized, and incremented across multiple calls, concurrent calls could lead to inconsistent updates. In prior reviews, a similar concern was raised. MarkingincreaseKeyCountas@Synchronizedor 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 issueFix 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
bypassSeatCountCheckproperty (line 65), consider adding abypassKeyCountCheckproperty 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 ofbillingConfProvideras a function vs. property.Here,
billingConfProvideris referenced as if it’s a function call (billingConfProvider()), but it was injected as a property. UnlessPublicBillingConfProviderimplementsoperator fun invoke(), this might be accidental. Consider changing it tobillingConfProvider.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 clearerThe test
does not throw when removing keyscould 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 namesWhile 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 testingIn 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
📒 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 ifinitialKeyCount > 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
keyCountis 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
saveSubscriptionmethod 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.
...ackend/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/SeatCountLimitTest.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
inviteUsermethod currently sets the role type toOWNERby 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 thebuildRolelambda.- 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
📒 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
projectsproperty 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:
- Converting from
data classtoopen classenables inheritance for specialized exceptions- Adding the
resourceIdparameter enables more specific error messages- Making
resourceIdnullable with a default value maintains backward compatibilityThis change appropriately supports the new
ProjectNotFoundExceptionclass 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 implementationThe addition of the
OnProjectSoftDeletedevent aligns with the move from hard deleting to soft deleting projects, which will help with tracking project lifecycle metrics.
17-17: Improved error handling specificityUsing
ProjectNotFoundExceptioninstead of generic exceptions provides clearer error information, making debugging and error handling more straightforward.
53-65: Dependency restructuring in constructorThe constructor has been refactored to:
- Remove unnecessary dependencies (
screenshotServiceandbatchJobService)- Add
applicationContextto support event publishing- Add
organizationServicefor organization-related operationsThis better aligns with the service's responsibilities and follows the principle of minimal dependencies.
77-77: Consistent use of specific exceptionThe consistent replacement of generic exceptions with
ProjectNotFoundExceptionacross multiple methods improves error clarity and debugging experience.Also applies to: 81-81, 101-101
92-94: New method to find deleted projectsThe addition of
findDeletedmethod 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 deletionPublishing the
OnProjectSoftDeletedevent 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 deletionThe
deleteProjectmethod 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🔭 Outside diff range comments (1)
backend/data/src/main/kotlin/io/tolgee/component/HttpClient.kt (1)
36-40:⚠️ Potential issuePotential NullPointerException risk
The code now directly accesses
response.bodywithout 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
LicenseKeyNotFoundExceptionis clean and correctly extendsNotFoundException. 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'sdataclass keywordThe class definition could be improved by using the
datakeyword for a Kotlin data class. This would automatically generate useful methods likeequals(),hashCode(),toString(), andcopy(), which are often needed for DTOs.-class GetEeSubscriptionUsageDto( +data class GetEeSubscriptionUsageDto( @field:NotBlank var licenseKey: String )
5-8: Add KDoc documentation for better maintainabilityConsider 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_SUPPORTEDseems to have a grammatical mismatch between the singular "METRIC" and the plural verb "ARE". Consider changing toSTRING_METRICS_ARE_NOT_SUPPORTEDorSTRINGS_METRIC_IS_NOT_SUPPORTEDfor 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 performanceThe
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.useMemoif 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.UsageModelwhere "uasge" should likely be "usage".-import io.tolgee.hateoas.ee.uasge.UsageModel +import io.tolgee.hateoas.ee.usage.UsageModelee/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
EeSubscriptionServiceImplinjected as separate dependencies (subscriptionServiceandeeSubscriptionServiceImpl), which seems redundant. Consider consolidating these to reduce confusion.private val subscriptionService: EeSubscriptionServiceImpl, -private val eeSubscriptionServiceImpl: EeSubscriptionServiceImpl, private val catchingService: EeSubscriptionErrorCatchingServiceThen update references to
eeSubscriptionServiceImplto usesubscriptionServiceinstead.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
onActivitymethod, 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
NoActiveSubscriptionExceptionis 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:
- Add method documentation to explain what this method does
- 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 strategyThe implementation looks clean and effective. However, for better maintainability:
- Consider adding KDoc comments to explain the method's purpose and potential exceptions
- 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 originalHttpClientErrorException.NotFoundbefore 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 thatvar bypassSeatCountCheck = falseis 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 immediateBadRequestException.
138-146: Review 2-day error transition threshold.
handleConstantlyFailingRemoteCheckmarks the subscription asERRORif 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 catchHttpClientErrorException.NotFoundand rethrow aBadRequestException. Consider applying a similar pattern in this method to handle the same error scenario uniformly.
41-42: Consider handling or logging the outcome.
Currently,reportErrorRemotedoes 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 catchHttpClientErrorExceptionor other exceptions and perform appropriate error handling or logging, rather than ignoring them.
62-71: Expand error handling for additional HTTP status codes.
Currently, onlyHttpClientErrorException.NotFoundis 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 withsetLicenseKeyRemote, 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 accessUsing
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 importsSeveral imports are not used in the current implementation:
Popoverfrom@mui/materialuseOrganizationUsagefromtg.globalContext/helpersUSAGE_ELEMENT_IDfrom../component/CriticalUsageCirclegetProgressDatafrom../component/getProgressDataClean 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 importThe
usePreferredOrganizationis 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 hooksBoth 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 (
!) withpreferredOrganization. This could lead to runtime errors ifpreferredOrganizationis 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
getProgressDatafunction is called without explicitly checking ifusageis defined. While the conditional rendering on line 42 handles nullprogressData, it would be clearer to explicitly check forusagebefore 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
popoverPropsto thePlanLimitPopoverWrapper, 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
progressDatadirectly passes all of its properties toUsageDetailed. IfprogressDatacontains properties that aren't expected byUsageDetailed, 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
⛔ Files ignored due to path filters (1)
.idea/icon.svgis 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 annotationGood job using the
@field:NotBlankannotation 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 maintainabilityThe refactored component uses
Partial<ProgressData>with an explicitisPayAsYouGoboolean flag, which provides better type safety and makes the component more flexible for future extensions.
10-35: Good refactoring to use an array-based approachOrganizing 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 dataThe conditional check for
item.progress?.isInUseensures the component gracefully handles cases where progress data might be undefined or not in use.
52-54: Consistent passing of props to BillingProgressThe 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
EeSubscriptionErrorCatchingServiceis correctly added to support the refactoring of error handling logic.
25-25: Good dependency injection for improved error handling.Adding the
EeSubscriptionErrorCatchingServiceas 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.catchingLicenseNotFoundwithcatchingService.catchingLicenseNotFoundproperly 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
runSentryCatchingensures 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 annotationThe
@Lazyannotation is properly used here to handle potential circular dependencies between this service andEeSubscriptionServiceImpl. This is a good practice in Spring applications when circular dependencies can't be avoided.
19-21: Effective null handling with Kotlin's Elvis operatorGood 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 handlingThe use of the
catchingService.catchingLicenseNotFoundhigher-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@Lazylook appropriate for Spring-based error handling services. No immediate concerns.
72-76: Subscription Status Update
The logic for settingKEY_USED_BY_ANOTHER_INSTANCEappears 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 scheduledcheckSubscription.
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 inreportUsage.
Whensubscriptionis 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 wrapsHttpClientcalls 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) toT(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.ktee/backend/app/src/main/kotlin/io/tolgee/ee/component/contentDelivery/EeTolgeeTranslateApiServiceImpl.ktbackend/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 thePlanLimitPopoverWrapper, 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=tsxLength 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 thePlanLimitPopoverWrapper, is being forwarded to the underlyingPopovercomponent. Please review the implementation in the file (likely in the same directory asSpendingLimitExceeded.tsx) to ensure that any extra props—such asdata-cy—are correctly spread onto thePopover. 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
PlanLimitPopoverbased on the availability ofprogressDataand 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
PlanLimitPopoverWrapperPropswith 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
PlanLimitPopoverWrapperPropswhich 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.
...app/src/main/kotlin/io/tolgee/ee/component/limitsAndReporting/EeKeyCountReportingListener.kt
Show resolved
Hide resolved
...ackend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionUsageService.kt
Outdated
Show resolved
Hide resolved
...pp/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionErrorCatchingService.kt
Show resolved
Hide resolved
...pp/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionErrorCatchingService.kt
Show resolved
Hide resolved
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionServiceImpl.kt
Outdated
Show resolved
Hide resolved
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/eeSubscription/EeSubscriptionServiceImpl.kt
Show resolved
Hide resolved
webapp/src/ee/billing/limitPopover/PlanLimitPopoverSelfHosted.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 suggestionConsider 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 issueCritical: Review Migration Strategy for
keys_limitColumn.
The newkeys_limitcolumn is added as aBIGINTwith a non-null constraint but without an explicit default value. As noted in previous reviews, this could lead to migration issues if theee_subscriptiontable contains existing records. Consider one of the following approaches:
- Add an explicit default value (e.g.,
-1if 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.
getRemoteSubscriptionInfouses a genericpostRequestbut only specifically handlesHttpClientErrorException.NotFoundwithin 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.
reportErrorRemoteappears straightforward. Consider whether you’d like to capture or log potential exceptions, if the remote call fails.
62-71: Ensure consistent exception mapping.
setLicenseKeyRemotethrows aBadRequestExceptionwhen 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.
postRequestdelegates tohttpClient.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
KeyCountLimitTestandSeatCountLimitTest.-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
📒 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
eeLicenseMockRequestUtilto 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:
isPayAsYouGoincludedKeysandkeysLimitfor the new keys/strings metricincludedSeatsandseatsLimitfor the existing seats metricThis 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.
reportUsageRemotediscards 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
AbstractSpringTestand uses Spring's dependency injection with@Autowiredand@MockBeanannotations. Thesetup()method with@BeforeEachensures 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:
- Creating a key and verifying it's reported as 1
- 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
executeInNewTransactionensures 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
testWithBaseTestDatamethod 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
assertKeysextension function andparseRequestArgshelper 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 newincluded_keyscolumn is added with a default value of-1and 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.
Theincluded_seatscolumn uses a default value of-1with a non-null constraint, which aligns with the intended design for handling default limits.
84-90: PAYG Flag Column Has Appropriate Defaults.
Theis_pay_as_you_gocolumn is defined with a default value oftrueand is non-nullable, properly reflecting the intended default behavior.
105-107: Validate Removal ofcancel_at_period_endColumn.
The removal of thecancel_at_period_endcolumn 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.
...end/tests/src/test/kotlin/io/tolgee/ee/selfHostedLimitsAndReporting/KeyUsageReportingTest.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 typeThe method name
handleFileSizeLimitExceededdoesn'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 handlerThe 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 classkeyword for such classes, which automatically provides useful methods likeequals(),hashCode(),toString(), andcopy().-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
includedandlimit.+/** + * 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 serialVersionUIDThe model class is well-structured with appropriate properties for tracking different usage metrics. Since this class implements
Serializable, consider adding aserialVersionUIDto 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 descriptionsThe 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.
OnlyHttpClientErrorException.NotFoundis 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
📒 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
UsageModelhas been updated to reflect its new location in theproportionalsubpackage. 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
keysproperty 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 importThe added import for
io.tolgee.util.debugis required for the debug logging functionality implemented on line 211.
211-211: Enhanced error logging for HttpRequestMethodNotSupportedExceptionAdding 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
invalidatePrefixparameter 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 catLength 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
openThe 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
openin 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 implicitlyfinal, preventing inheritance unless explicitly markedopen[1][2][4]. This enforces the "design for inheritance or prohibit it" principle from Effective Java[3].Key Scenarios for
open1. Intentional class hierarchy design:
- Mark classes
openwhen creating base classes meant for extension (e.g., abstract templates)[1][4]- Example:
[2][4]open class Animal { /* base functionality */ } class Dog : Animal() { /* specialization */ }2. Framework interoperability:
- Spring/Hibernate require
openclasses for proxy creation[2][3]- Two approaches:
- Manually mark classes/methods:
[2]@Service open class UserService { open fun register() { /* ... */ } }- Use kotlin-allopen plugin to automatically open annotated classes:
[2]@Service // Plugin adds 'open' during compilation class UserService { fun register() { /* ... */ } }3. Method overriding requirements:
- Methods/properties remain
finaleven inopenclasses unless explicitly markedopen:[2][4]open class Shape { open fun draw() { /* ... */ } // Overridable fun rotate() { /* ... */ } // Final by default }- Overrides stay open unless declared
final:[4]class Circle : Shape() { final override fun draw() { /* ... */ } // Prevents further overriding }Critical Design Considerations
- Initialization risks: Avoid using
openmembers in constructors/initializers, as derived class properties might not be initialized[4]- Transitive inheritance:
openisn't inherited - subclasses remainfinalunless explicitly markedopen[2]- Bytecode equivalence: Non-
openclasses compile tofinalin JVM bytecode[2]Citations:
- 1: https://discuss.kotlinlang.org/t/inconsistent-open-final-defaults-between-classes-and-functions/21050
- 2: https://www.baeldung.com/kotlin/open-keyword
- 3: https://discuss.kotlinlang.org/t/classes-final-by-default/166
- 4: https://kotlinlang.org/docs/inheritance.html
Review Update: Confirm the Intentional Use of
openThe current declaration:
open class CurrentUsageModel(allows subclassing. As a reminder, Kotlin classes are final by default and should only be marked as
openwhen 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
openmodifier 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.
6372662 to
bc6545c
Compare
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.
…bscription checks
…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.
Summary by CodeRabbit
Release Notes
Billing and Usage
Subscription Management
User Experience
Administration
Performance