Skip to content

Conversation

@dkrizan
Copy link
Contributor

@dkrizan dkrizan commented Aug 22, 2025

Summary by CodeRabbit

  • New Features

    • Full plan migration management: create/edit/delete migrations, view details, history, and subscriptions; new public API endpoints and migration schemas; new error codes.
  • UI

    • Migration status chips, detailed migration panels/tooltips, record list, plan selectors with migration filtering, full‑width tooltips, header custom buttons, optional table headers, dedicated create/edit admin pages.
  • Validation

    • Form validation for monthly/yearly offset fields.
  • i18n / Email

    • Email templates and translation keys for plan migration notifications; new error translation strings.
  • Tests

    • Email send-count verifier and new data‑cy identifiers for migration flows.
  • Background / Date

    • Improved date handling (system local date) and cron-based task scheduling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

Walkthrough

Adds backend plan-migration APIs, scheduling/date helpers, an internal properties setter, i18n/email keys and test helpers; and extensive frontend EE billing UI for plan migrations including selectors, forms, chips, lists, pages, routes, validation, generated API schemas, Cypress typings, and small UI utilities.

Changes

Cohort / File(s) Summary
Backend date & scheduling
backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt, backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt
Adds val localDate: LocalDate computed from date using system default zone; adds scheduleWithCron(runnable: Runnable, cron: String): String that schedules via CronTrigger, stores future by UUID and returns id.
Backend messages, i18n & email tests
backend/data/src/main/kotlin/io/tolgee/constants/Message.kt, backend/data/src/main/resources/I18n_en.properties, backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt
Adds enum values PLAN_MIGRATION_NOT_FOUND, PLAN_HAS_MIGRATIONS; adds plan-migration email subject/body i18n keys; adds verifyTimesEmailSent(num: Int) test helper.
Backend internal properties facade & controller
backend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.kt, backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt
New InternalPropertiesSetterFacade.setProperty(...) that traverses dot-paths via reflection and enforces mutability; PropertiesController delegates to the facade and makes tolgeeProperties private.
Billing API schemas (generated)
webapp/src/service/billingApiSchema.generated.ts, webapp/src/service/apiSchema.generated.ts
Adds plan-migration endpoints (POST/GET/PUT/DELETE, subscriptions), new migration schemas (CloudPlanMigrationModel, PlanMigrationRecordModel, history/request/paged models), adds activeMigration/migrationId fields, filterHasMigration param; expands ErrorResponseTyped.code with plan_migration_not_found/plan_has_migrations.
Webapp migration UI (components / forms / pages / routing)
webapp/src/ee/billing/**/migration/**, webapp/src/eeSetup/eeModule.ee.tsx, webapp/src/ee/billing/component/Plan/migration/**
Adds generic create/edit bases, PlanMigrationForm (generic), Create/Edit wrappers, Cloud/Self‑hosted edit forms, chips, detail/price components, paginated record list, status chip, create/edit admin pages, lazy-loading chips, and registers routes in EE module.
Plan selectors & related changes
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx, .../cloud/fields/CloudPlanSelector.tsx, .../selfHostedEe/fields/SelfHostedEePlanSelector.tsx, .../migration/fields/PlanSelectorField.tsx, .../migration/types.ts
GenericPlanType now includes free; GenericPlanSelector gains planProps (hiddenIds, free), dataCy, and returns full plan in handler; cloud/self-hosted selectors accept filterHasMigration; new PlanSelectorField binds to Formik; new PlanType alias added.
Forms wrappers & validation
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx, .../CreatePlanMigrationForm.tsx, .../EditPlanMigrationForm.tsx, webapp/src/constants/GlobalValidationSchema.tsx
Adds generic PlanMigrationForm with validation, offsets, source/target selectors and delete confirmation; Create/Edit wrapper components; adds PLAN_MIGRATION_FORM Yup schema and localized min-number message.
UI utilities & common props
webapp/src/component/common/FullWidthTooltip.tsx, webapp/src/component/common/table/PaginatedHateoasTable.tsx, webapp/src/component/layout/HeaderBar.tsx
Adds FullWidthTooltip; PaginatedHateoasTable accepts optional tableHead?: ReactNode; HeaderBar adds optional customButtons?: ReactNode[].
Plans views & UI integration
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx, .../viewsSelfHostedEe/AdministrationEePlansView.tsx
Injects migration-create custom button in plans list headers and renders migrating chips per plan row.
Lists, records & status components
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/*
Adds AdministrationPlanMigrationCreateBase, AdministrationPlanMigrationEditBase, PlanMigrationRecordList, PlanMigrationStatus and supporting components to display record lists, statuses and edit flows.
Cypress typings & error translation & links
e2e/cypress/support/dataCyType.d.ts, webapp/src/translationTools/useErrorTranslation.ts, webapp/src/constants/links.tsx, webapp/src/service/apiSchema.generated.ts
Adds several new data-cy keys; maps new error codes to translation keys; adds PLAN_MIGRATION_ID param and migration route constants; updates generated API error unions accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin as Admin User
  participant UI as Webapp UI
  participant API as Billing API
  participant DB as Storage

  rect #f0f8ff
    note over UI: Create migration flow
    Admin->>UI: Open Create Migration UI
    UI->>API: POST /v2/administration/billing/{cloud|self-hosted}-plans/migration
    API->>DB: Persist migration
    DB-->>API: id
    API-->>UI: 201 Created
    UI-->>Admin: Show success + navigate to list
  end

  rect #f5fff0
    note over UI: Edit/Delete with subscriptions
    Admin->>UI: Open Edit Migration
    UI->>API: GET /.../migration/{id}
    API-->>UI: migration detail
    UI->>API: GET /.../migration/{id}/subscriptions?page=N
    API-->>UI: paged records
    Admin->>UI: Save
    UI->>API: PUT /.../migration/{id}
    API->>DB: Update
    API-->>UI: 200 OK
    Admin->>UI: Delete
    UI->>API: DELETE /.../migration/{id}
    API->>DB: Remove
    API-->>UI: 204 No Content
  end
Loading
sequenceDiagram
  autonumber
  participant List as Plans List
  participant Chip as Migrating Chip
  participant API as Billing API

  List->>Chip: Render with migrationId
  Chip-->>List: Return null if no id
  Chip->>Chip: onOpen => opened=true
  Chip->>API: GET /.../migration/{id} (enabled by opened)
  API-->>Chip: migration detail
  Chip-->>List: Tooltip shows PlanMigrationDetail with edit link
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • JanCizmar
  • ZuzanaOdstrcilova

Poem

Hop-hop I bound through routes and forms,
Chips that glow where server warms.
I nudge cron ticks and dates align,
Migrations queued by paw and sign.
A rabbit cheers — migrations shine! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: plans migration" directly relates to the main changes in the changeset. The entire pull request is cohesively focused on implementing plan migration functionality across multiple layers: backend services (CurrentDateProvider, SchedulingManager), API schema definitions for migration endpoints and models, frontend form components (CreatePlanMigrationForm, EditPlanMigrationForm, PlanMigrationForm), UI components for displaying migration status and details, routing/linking for migration pages, and testing utilities. The title clearly communicates that this is a feature addition for plan migration, which is the unifying theme across all the changes. While the title doesn't enumerate every component involved, it appropriately summarizes the primary purpose without unnecessary verbosity.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch danielkrizan/plan-migration

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42559b8 and 9d162ab.

📒 Files selected for processing (4)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (4)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)
  • AdministrationPlanMigrationEditBase (32-94)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1)
  • CloudPlanEditPlanMigrationForm (15-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build backend 🏗️
  • GitHub Check: Frontend static check 🪲

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dkrizan dkrizan force-pushed the danielkrizan/plan-migration branch from d6dccb9 to af75b99 Compare August 22, 2025 13:24
@github-actions
Copy link
Contributor

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Sep 22, 2025
@dkrizan dkrizan force-pushed the danielkrizan/plan-migration branch from 72ae86a to bf9215a Compare September 29, 2025 13:06
@github-actions github-actions bot removed the stale label Sep 30, 2025
@dkrizan dkrizan marked this pull request as ready for review September 30, 2025 09:51
@dkrizan dkrizan requested a review from JanCizmar September 30, 2025 09:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (8)
backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt (1)

89-92: Simplify by reusing the existing date property.

The expression (forcedDate ?: date) is redundant because the date property (lines 84-87) already performs the null check forcedDate ?: Date(). Directly use date to maintain consistency with the existing pattern.

Apply this diff:

   val localDate: LocalDate
       get() {
-        return (forcedDate ?: date).toInstant().atZone(systemDefault()).toLocalDate()
+        return date.toInstant().atZone(systemDefault()).toLocalDate()
       }
backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt (1)

54-57: Consider validating the cron expression and document the null check.

The cron string is passed directly to CronTrigger without validation. Invalid cron expressions will throw IllegalArgumentException at runtime. Additionally, the null check for the future is good defensive programming (Spring's TaskScheduler.schedule can return null if the scheduler is shut down or the trigger produces no next execution time), but this pattern isn't used in the existing scheduleWithFixedDelay function (line 44), creating an inconsistency.

Recommendations:

  1. Validate the cron expression before constructing the CronTrigger to provide clearer error messages:
 fun scheduleWithCron(
   runnable: Runnable,
   cron: String,
 ): String {
+  // Validate cron expression early for better error messages
+  try {
+    CronTrigger(cron).nextExecutionTime(org.springframework.scheduling.TriggerContext { 
+      java.time.Instant.now() 
+    })
+  } catch (e: IllegalArgumentException) {
+    throw IllegalArgumentException("Invalid cron expression: $cron", e)
+  }
+  
   val future = taskScheduler.schedule(runnable, CronTrigger(cron))
   if (future == null) {
     throw IllegalStateException("Future from scheduler was null")
   }
   val id = UUID.randomUUID().toString()
   scheduledTasks[id] = future
   return id
 }
  1. For consistency, consider adding the same null check to scheduleWithFixedDelay:
 fun scheduleWithFixedDelay(
   runnable: Runnable,
   period: Duration,
 ): String {
   val future = taskScheduler.scheduleWithFixedDelay(runnable, period)
+  if (future == null) {
+    throw IllegalStateException("Future from scheduler was null")
+  }
   val id = UUID.randomUUID().toString()
   scheduledTasks[id] = future
   return id
 }
backend/data/src/main/resources/I18n_en.properties (1)

117-117: Consider the appropriateness of the mouse emoji in the subject line.

The 🐁 emoji in the subject line seems unusual for a professional plan migration email. Most email clients will render this emoji, but it may come across as unprofessional or confusing to users. Consider either:

  • Removing the emoji entirely for a more professional tone
  • Using a more business-appropriate emoji like 📋 or 📅 if an icon is desired
  • Clarifying the intent if the mouse emoji has specific brand significance

</invite_end -->

webapp/src/component/layout/HeaderBar.tsx (1)

85-90: Consider a more stable key for custom buttons.

Using array index as a React key can cause issues if the customButtons array is dynamically updated, reordered, or filtered, leading to incorrect component reconciliation and potential state bugs.

If the buttons have stable identifiers, pass objects with an id or key property. Otherwise, consider wrapping each button in a Fragment with a unique key at the call site, or accept an array of { key: string; element: ReactNode } objects:

-  customButtons?: ReactNode[];
+  customButtons?: Array<{ key: string; element: ReactNode }>;

Then render:

-              {props.customButtons &&
-                props.customButtons.map((button, index) => (
-                  <Box key={index} display="flex" alignItems="center">
-                    {button}
-                  </Box>
-                ))}
+              {props.customButtons &&
+                props.customButtons.map(({ key, element }) => (
+                  <Box key={key} display="flex" alignItems="center">
+                    {element}
+                  </Box>
+                ))}

Alternatively, if the array is truly static per render and never reordered, document that assumption in a comment.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1)

13-15: Consider typing the colors mapping more strictly.

The colors object only maps 'COMPLETED' to 'success'. On Line 35, (colors as any)[status] uses a type assertion to access this mapping dynamically. While this works, a more type-safe approach would define a proper index signature or use a Record type.

Apply this diff for better type safety:

-const colors = {
-  COMPLETED: 'success',
-};
+const colors: Partial<Record<Status, 'success' | 'default'>> = {
+  COMPLETED: 'success',
+};

Then on Line 35:

-      color={(colors as any)[status] || 'default'}
+      color={colors[status] || 'default'}
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1)

97-186: Consider extracting shared migration edit logic.

This file and AdministrationSelfHostedEePlanMigrationEdit.tsx contain nearly identical logic (queries, mutations, submit handlers, table rendering). The only differences are API URLs and navigation links. Consider abstracting the shared logic into a reusable component or hook to reduce duplication and improve maintainability.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)

55-60: Simplify redundant null check.

Line 57 checks migration && migration.sourcePlan.id, but migration is already checked on line 55. The inner check is redundant.

Apply this diff:

   const defaultSourcePlan = migration
     ? {
-        id: migration && migration.sourcePlan.id,
+        id: migration.sourcePlan.id,
         free: migration.sourcePlan.free,
       }
     : undefined;
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (1)

44-60: Filter logic is correct.

The filtering implementation correctly:

  • Excludes plans in hiddenIds
  • Filters by free status when specified
  • Includes all plans when no filters apply

The logic is sound and handles all edge cases properly.

For improved readability, consider extracting the filter predicate:

- const selectItems = plans
-   .filter((plan) => {
-     if (planProps?.hiddenIds?.includes(plan.id)) {
-       return false;
-     }
-     if (planProps?.free !== undefined) {
-       return planProps.free === plan.free;
-     }
-     return true;
-   })
-   .map(
+ const shouldIncludePlan = (plan: T) => {
+   if (planProps?.hiddenIds?.includes(plan.id)) return false;
+   if (planProps?.free !== undefined) return planProps.free === plan.free;
+   return true;
+ };
+
+ const selectItems = plans
+   .filter(shouldIncludePlan)
+   .map(
      (plan) =>
        ({
          value: plan.id,
          name: plan.name,
        } satisfies SelectItem<number>)
    );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4825bbe and bf9215a.

📒 Files selected for processing (31)
  • backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1 hunks)
  • backend/data/src/main/resources/I18n_en.properties (1 hunks)
  • backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt (1 hunks)
  • e2e/cypress/support/dataCyType.d.ts (5 hunks)
  • webapp/src/component/common/FullWidthTooltip.tsx (1 hunks)
  • webapp/src/component/common/table/PaginatedHateoasTable.tsx (4 hunks)
  • webapp/src/component/layout/HeaderBar.tsx (2 hunks)
  • webapp/src/constants/GlobalValidationSchema.tsx (2 hunks)
  • webapp/src/constants/links.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (4 hunks)
  • webapp/src/ee/billing/component/Plan/PlanMigratingChip.tsx (1 hunks)
  • webapp/src/eeSetup/eeModule.ee.tsx (3 hunks)
  • webapp/src/service/apiSchema.generated.ts (2 hunks)
  • webapp/src/service/billingApiSchema.generated.ts (16 hunks)
  • webapp/src/translationTools/useErrorTranslation.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-11T14:43:26.099Z
Learnt from: stepan662
PR: tolgee/tolgee-platform#3124
File: backend/data/src/main/kotlin/io/tolgee/batch/MtProviderCatching.kt:38-39
Timestamp: 2025-06-11T14:43:26.099Z
Learning: In Tolgee Platform, backend Message enum keys are not looked up via `messages*.properties`; localization is performed in the frontend, so adding new enum constants does not require backend properties entries.

Applied to files:

  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt
🧬 Code graph analysis (17)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (10)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/service/http/useQueryApi.ts (2)
  • useBillingApiQuery (288-293)
  • useBillingApiMutation (295-300)
webapp/src/constants/links.tsx (2)
  • LINKS (69-455)
  • Link (1-45)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1)
  • EditPlanMigrationForm (17-32)
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)
  • PaginatedHateoasTable (26-47)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1)
  • PlanMigrationStatus (17-52)
webapp/src/component/common/EmptyState.tsx (1)
  • EmptyState (24-42)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)
  • CreatePlanMigrationFormData (34-35)
  • PlanMigrationForm (42-193)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (7)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)
  • CreatePlanMigrationFormData (34-35)
  • PlanMigrationFormData (29-32)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)
  • CreatePlanMigrationForm (21-28)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (5)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/constants/GlobalValidationSchema.tsx (1)
  • Validation (52-538)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1)
  • PlanSelectorField (8-31)
webapp/src/component/common/LabelHint.tsx (1)
  • LabelHint (17-26)
webapp/src/hooks/confirmation.tsx (1)
  • confirmation (5-7)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)
  • PlanMigrationFormData (29-32)
  • PlanMigrationForm (42-193)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (4)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)
  • CloudPlanSelector (7-33)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1)
  • SelfHostedEePlanSelector (6-29)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (10)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/service/http/useQueryApi.ts (2)
  • useBillingApiQuery (288-293)
  • useBillingApiMutation (295-300)
webapp/src/constants/links.tsx (2)
  • LINKS (69-455)
  • Link (1-45)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1)
  • EditPlanMigrationForm (17-32)
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)
  • PaginatedHateoasTable (26-47)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1)
  • PlanMigrationStatus (17-52)
webapp/src/component/common/EmptyState.tsx (1)
  • EmptyState (24-42)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (7)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)
  • CreatePlanMigrationFormData (34-35)
  • PlanMigrationFormData (29-32)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)
  • CreatePlanMigrationForm (21-28)
webapp/src/ee/billing/component/Plan/PlanMigratingChip.tsx (5)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/constants/links.tsx (2)
  • LINKS (69-455)
  • Link (1-45)
webapp/src/component/common/FullWidthTooltip.tsx (1)
  • FullWidthTooltip (3-11)
webapp/src/ee/billing/component/Price/PricePrimary.tsx (1)
  • PricePrimary (28-66)
webapp/src/eeSetup/eeModule.ee.tsx (6)
webapp/src/component/common/PrivateRoute.tsx (1)
  • PrivateRoute (8-14)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1)
  • AdministrationCloudPlanMigrationCreate (17-74)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1)
  • AdministrationCloudPlanMigrationEdit (22-187)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1)
  • AdministrationSelfHostedEePlanMigrationCreate (17-75)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1)
  • AdministrationSelfHostedEePlanMigrationEdit (22-188)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (1)
webapp/src/component/searchSelect/SearchSelect.tsx (2)
  • SearchSelect (41-131)
  • SelectItem (8-11)
webapp/src/component/common/FullWidthTooltip.tsx (1)
webapp/src/colors.tsx (1)
  • Tooltip (37-40)
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (2)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/component/Plan/PlanMigratingChip.tsx (1)
  • PlanMigratingChip (30-228)
webapp/src/service/billingApiSchema.generated.ts (1)
webapp/src/service/apiSchema.generated.ts (1)
  • components (1065-6030)
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (2)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/component/Plan/PlanMigratingChip.tsx (1)
  • PlanMigratingChip (30-228)
🪛 Biome (2.1.2)
webapp/src/ee/billing/component/Plan/PlanMigratingChip.tsx

[error] 42-42: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 43-43: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 52-52: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 71-71: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (58)
backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt (1)

69-71: LGTM!

The new verifyTimesEmailSent method correctly uses Mockito's times() verification to check exact invocation count. It follows the same pattern as the existing verifyEmailSent() method and provides useful test utility functionality for count-based email verification.

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

19-20: LGTM! Imports are correct.

The imports for LocalDate and ZoneId.systemDefault are standard Java time API classes needed for the new localDate property.

backend/data/src/main/resources/I18n_en.properties (1)

118-128: LGTM! Well-structured email body with clear communication.

The email body is well-formatted with:

  • Clear explanation of the migration with before/after plan details
  • Actionable options for users (change plan or contact support)
  • Proper HTML formatting and placeholder usage
  • Professional yet friendly tone

The 7 placeholders ({0}-{6}) are used appropriately and the message structure follows the established notification pattern in this file.

</invite_end -->

webapp/src/constants/links.tsx (2)

66-66: LGTM! Consistent parameter naming.

The new PLAN_MIGRATION_ID parameter follows the existing naming conventions and integrates well with the routing structure.


250-268: LGTM! Well-structured migration routes.

The four new link constants for plan migrations (cloud and self-hosted EE, create and edit) follow the established patterns and are properly nested under their respective parent links. The edit routes correctly use the PLAN_MIGRATION_ID parameter.

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

311-312: LGTM! New error constants properly integrated.

The two new enum constants PLAN_MIGRATION_NOT_FOUND and PLAN_HAS_MIGRATIONS follow the existing naming conventions and are correctly placed. The frontend translation mapping for plan_has_migrations is already in place in useErrorTranslation.ts.

Based on learnings

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

15-23: LGTM! Clean prop addition for migration filtering.

The filterHasMigration prop is well-integrated following the same pattern as filterPublic. The optional type ensures backward compatibility, and it's correctly passed through to the API query.

webapp/src/translationTools/useErrorTranslation.ts (1)

110-111: LGTM! Consistent error translation mapping.

The new error case for plan_has_migrations follows the established pattern and is logically placed near the related plan_has_subscribers error.

webapp/src/eeSetup/eeModule.ee.tsx (2)

71-74: LGTM! Migration component imports are properly structured.

The four new imports for cloud and self-hosted EE plan migration components follow the existing import patterns and are clearly named.


146-184: LGTM! Migration routes are well-integrated.

The new routes for plan migrations (both cloud and self-hosted EE, create and edit) are properly configured:

  • Use PrivateRoute for authentication
  • Have exact matching to prevent conflicts
  • Are logically placed near their related plan routes
  • Reference the correct link templates and components
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)

1-1: LGTM!

The PlanType union type is clean, minimal, and correctly typed for the two plan variants.

webapp/src/constants/GlobalValidationSchema.tsx (1)

42-49: LGTM!

The number.min locale configuration correctly integrates the validation message with i18n translation, consistent with the existing string.min and string.max patterns.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (2)

1-28: Note: This component depends on the PLAN_MIRATION_FORM typo fix.

The PlanMigrationForm component (imported at line 3) uses the misspelled PLAN_MIRATION_FORM validation schema. Once the typo in GlobalValidationSchema.tsx is fixed (as noted in that file's review), ensure this component and PlanMigrationForm are updated accordingly.


7-13: Verify that 0 is a valid sentinel value for unselected plan IDs. If your Plan IDs are guaranteed to start at 1, 0 can safely represent “none selected”; otherwise, switch sourcePlanId/targetPlanId to undefined (update CreatePlanMigrationFormData to number | undefined) and tighten your Yup schema to use .min(1) for planId.

webapp/src/component/common/FullWidthTooltip.tsx (1)

1-11: LGTM!

The FullWidthTooltip component correctly overrides the default Tooltip maxWidth using MUI's styled API and tooltipClasses. The pattern of spreading className to popper is the correct approach for styled Tooltip customization.

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

13-13: LGTM! Icon import added correctly.

The Settings01 icon import is used correctly in the migration action button below.


155-159: LGTM! Migration chip integrated correctly.

The PlanMigratingChip is properly integrated with the correct planType="self-hosted" and migration data from the plan model.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (3)

1-11: LGTM! Types and imports are well-structured.

The Status type correctly references the API schema, and the Props interface is clear.


21-30: LGTM! Status label mapping is correct.

The switch statement properly maps known statuses to translation keys with a sensible fallback.


32-51: LGTM! Conditional tooltip rendering is well-implemented.

The component correctly wraps the chip in a tooltip when a date is provided, with proper UTC formatting.

webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (5)

1-15: LGTM! Imports are clean and correctly organized.

All imports are used and follow the project's module path conventions.


17-20: LGTM! Component initialization follows standard patterns.

Hooks are correctly initialized and used throughout the component.


22-38: LGTM! Submit handler is correctly implemented.

The type casting and success flow (message + navigation) are appropriate for the create operation.


40-43: LGTM! Mutation configuration is correct.

The billing API mutation is properly configured for creating cloud plan migrations.


45-73: LGTM! Component rendering is well-structured.

The navigation breadcrumb, form integration, and loading state management are all correctly implemented.

webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (5)

1-15: LGTM! Imports are correctly organized.

All necessary imports are present and follow project conventions.


17-20: LGTM! Hook initialization is standard.


22-38: LGTM! Submit handler correctly targets self-hosted endpoint.

The endpoint and navigation target are appropriate for self-hosted EE plan migrations.


40-43: LGTM! Mutation is correctly configured for self-hosted migrations.


62-75: LGTM! Form configuration is correct for self-hosted.

The planType="self-hosted" prop ensures the form behaves correctly for self-hosted EE migrations.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (3)

1-8: LGTM! Type definitions and imports are clean.

The type aliases correctly reference the API schema models for both cloud and self-hosted migrations.


9-15: LGTM! Props interface is well-defined.

The Props type correctly supports both cloud and self-hosted migration models via a union type.


17-32: LGTM! Initial values mapping is correct.

The component correctly maps migration data to the form's expected structure and forwards all props to PlanMigrationForm.

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

12-12: LGTM!

The new imports for Settings01 icon and PlanMigratingChip are correctly placed and used in the component.

Also applies to: 29-29


148-151: PlanMigratingChip props match CloudPlanModel schema
CloudPlanModel in billingApiSchema.generated.ts defines both migrationId?: number and activeMigration?: boolean, so passing these props is correct.

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

1-1: LGTM!

The import additions and type extension to support an optional tableHead prop are well-structured and maintain backward compatibility.

Also applies to: 11-11, 22-24


49-63: LGTM!

The internal PaginatedHateoasTableListComponent cleanly handles the conditional rendering of the table header. The implementation is straightforward and correct.


35-46: Approve listComponent function usage – The listComponent prop is defined as a JSXElementConstructor<any>, so supplying a function that receives props and returns JSX correctly satisfies the expected component type; no changes needed.

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

12-13: LGTM!

The addition of the optional filterHasMigration prop is clean and maintains backward compatibility. The prop follows the same pattern as organizationId.


14-21: filterHasMigration is supported by the backend API schema
The generated billingApiSchema.generated.ts defines filterHasMigration?: boolean alongside filterAssignableToOrganization for the getPlans operation on /v2/administration/billing/self-hosted-ee-plans, so no further changes are needed.

webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1)

78-78: Non-null assertion is safe here.

The non-null assertion is acceptable because the loading check on line 74 guarantees that migrationLoadable.data is defined at this point.

webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1)

78-78: Non-null assertion is safe here.

The non-null assertion is acceptable because the loading check on line 74 guarantees that migrationLoadable.data is defined at this point.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1)

8-31: LGTM!

The component provides a clean abstraction over cloud and self-hosted plan selectors, correctly integrates with Formik, and forwards props appropriately. The use of any in the generic type (line 17) is acceptable here for flexibility, given that the actual plan types are determined by the underlying selector components.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)

42-192: Well-structured generic form component.

The component properly abstracts create and edit modes, handles plan selection with interdependent constraints, integrates validation, and provides clear UX for delete confirmation. The generic type constraints appropriately restrict usage to the two supported form data types.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (8)

9-14: LGTM! Type extensions support the filtering feature.

The addition of the free field to GenericPlanType and the new PlansProps type cleanly support the plan filtering functionality introduced in this PR.


44-60: LGTM! Filtering logic is correct and well-structured.

The plan filtering implementation correctly:

  • Excludes plans whose IDs are in hiddenIds
  • Filters by free status only when explicitly defined
  • Uses satisfies for type safety

The logic properly handles both filter conditions and maintains the original mapping structure.


78-78: LGTM! The dataCy prop enables better test flexibility.

Passing the dataCy prop to SearchSelect allows customization of data-cy attributes for different plan selector instances while maintaining backward compatibility through the default value.


18-18: Ensure all GenericPlanSelector usages handle the new onPlanChange(plan: T) signature.
The callback now receives the full plan object instead of an ID—update every handler accordingly.


9-14: LGTM! Type definitions are well-structured.

The addition of free to GenericPlanType and the new PlansProps type for filtering configuration are clear and appropriately typed.


16-25: API change improves developer experience.

The change to onPlanChange receiving the full plan object (line 18) instead of just the ID is a breaking change but provides better DX by reducing the need for consumers to perform lookups. The new optional props planProps and dataCy enhance filtering capabilities and testability.


27-35: LGTM! Props destructuring is correct.

The component signature correctly destructures the new props with an appropriate default for dataCy.


76-85: LGTM! dataCy properly propagated for testing.

The dataCy prop is correctly passed through to the SearchSelect component, enabling test targeting with appropriate defaults.

webapp/src/service/billingApiSchema.generated.ts (6)

22-32: LGTM! Migration endpoints are well-structured and consistent.

The new plan migration endpoints for both cloud and self-hosted plans follow REST conventions and maintain consistency across both plan types. The CRUD operations (create, get, update, delete) and subscription history retrieval are properly defined.

Also applies to: 58-68


243-243: LGTM! Plan model extensions support migration tracking.

The addition of activeMigration and migrationId fields to both AdministrationCloudPlanModel and SelfHostedEePlanAdministrationModel enables proper tracking of plan migrations. Both fields are correctly optional and consistently applied across plan types.

Also applies to: 281-282, 1240-1240, 1278-1279


318-330: LGTM! Migration model schemas are complete and consistent.

Both CloudPlanMigrationModel and AdministrationSelfHostedEePlanMigrationModel have well-structured schemas with:

  • Proper fields for migration scheduling (offset days)
  • Source and target plan references
  • Enabled flag for controlling migration status
  • Optional subscriptions count for tracking impact

The consistency between cloud and self-hosted variants is excellent.

Also applies to: 379-391


546-556: LGTM! Request and response schemas are well-designed.

The migration request schemas correctly differentiate between:

  • CreatePlanMigrationRequest: includes sourcePlanId for creating new migrations
  • PlanMigrationRequest: excludes sourcePlanId for updates (source plan is immutable)

The PlanMigrationHistoryModel provides comprehensive tracking with organization details, plan names, scheduling/finalization timestamps, and status. The paged wrapper follows standard patterns.

Also applies to: 1153-1172, 1038-1043


680-680: LGTM! Query parameters and error codes properly support migrations.

The addition of:

  • filterHasMigration query parameter on plan listing endpoints enables filtering by migration status
  • Error codes plan_migration_not_found and plan_has_migrations provide appropriate error handling for migration operations

These additions are consistent with the migration feature requirements.

Also applies to: 2314-2314, 911-913


1755-1958: LGTM! Operation definitions are complete and follow API conventions.

All plan migration operations (create, get, update, delete, list subscriptions) are properly defined for both cloud and self-hosted variants with:

  • Correct request/response body types
  • Proper path parameters (migrationId)
  • Standard HTTP status codes
  • Pagination support for subscription listings

The consistency between cloud and self-hosted operation definitions is excellent.

Also applies to: 2389-2592

Copy link
Contributor

@JanCizmar JanCizmar left a comment

Choose a reason for hiding this comment

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

Thanks a lot! ❤️

Would be nice if we kept the frontend components smaller.

notifications.email.password-changed=Password has been changed for your account.

notifications.email.plan-migration-subject=Your 🐁 plan will be updated on {0}
notifications.email.plan-migration-body=Hello! 👋<br/><br/>\
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, the encoding is somehow broken. I believe utf-8 is now supported for properties file. Can we fix it? In the meantime, I am asking marketa to provide proper e-mail.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
backend/data/src/main/resources/I18n_en.properties (1)

118-132: Verify UTF‑8 loading and placeholder/URL handling.

The body uses Unicode apostrophes (’), HTML, and placeholders {0..4} (with {4} embedded in a URL). Please confirm:

  • The MessageSource loads properties as UTF‑8 (to avoid mojibake). This echoes a previous review note.
  • The parameter order matches the placeholders.
  • {4} values are URL‑encoded when interpolated.
🧹 Nitpick comments (2)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationHistoryList.tsx (1)

50-50: Remove trailing space in table cell.

The trailing space {' '} after the closing </Link> tag serves no purpose since there's no adjacent content in the cell.

Apply this diff:

-            </Link>{' '}
+            </Link>
backend/data/src/main/resources/I18n_en.properties (1)

117-117: Align subject key with existing naming convention.

Consider notifications.email.subject.PLAN_MIGRATION for consistency with other subjects.

-notifications.email.plan-migration-subject=Upcoming update to your Tolgee subscription plan
+notifications.email.subject.PLAN_MIGRATION=Upcoming update to your Tolgee subscription plan

If renamed, update all usages accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 361612e and 8ad58c8.

📒 Files selected for processing (5)
  • backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt (2 hunks)
  • backend/data/src/main/resources/I18n_en.properties (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationHistoryList.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx
  • backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationHistoryList.tsx (5)
webapp/src/service/response.types.ts (1)
  • HateoasListData (99-101)
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)
  • PaginatedHateoasTable (26-47)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1)
  • PlanMigrationStatus (17-52)
webapp/src/component/common/EmptyState.tsx (1)
  • EmptyState (24-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build backend 🏗️
  • GitHub Check: Build frontend 🏗️

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (1)

116-129: customButtons prop not supported downstream.

This issue was already identified in a previous review.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationHistoryList.tsx (2)

8-8: Import source for React Query.

This issue was already identified in a previous review.


54-60: Use migration completion date.

This issue was already identified in a previous review.

🧹 Nitpick comments (1)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (1)

73-73: Remove redundant optional chaining.

The optional chaining operators (?.) on migration.monthlyOffsetDays (line 73) and migration.yearlyOffsetDays (line 79) are redundant. The early return at line 32 ensures migration is truthy at this point.

Apply this diff:

             <TooltipText>
               <T
                 keyName="administration_plan_migration_monthly_timing"
-                params={{ days: migration?.monthlyOffsetDays, b: <b /> }}
+                params={{ days: migration.monthlyOffsetDays, b: <b /> }}
               />
             </TooltipText>
             <TooltipText>
               <T
                 keyName="administration_plan_migration_yearly_timing"
-                params={{ days: migration?.yearlyOffsetDays, b: <b /> }}
+                params={{ days: migration.yearlyOffsetDays, b: <b /> }}
               />
             </TooltipText>

Also applies to: 79-79

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ad58c8 and 9c17988.

📒 Files selected for processing (13)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationHistoryList.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (4 hunks)
  • webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (3)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1)
  • EditPlanMigrationForm (17-32)
webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (3)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1)
  • PlanMigrationChip (24-67)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1)
webapp/src/ee/billing/component/Price/PricePrimary.tsx (1)
  • PricePrimary (28-66)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (6)
webapp/src/service/response.types.ts (1)
  • HateoasListData (99-101)
webapp/src/component/navigation/Navigation.tsx (1)
  • NavigationItem (33-37)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationHistoryList.tsx (1)
  • PlanMigrationHistoryList (21-73)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (3)
webapp/src/constants/links.tsx (1)
  • Link (1-45)
webapp/src/component/common/FullWidthTooltip.tsx (1)
  • FullWidthTooltip (3-11)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (1)
  • PlanMigrationDetail (30-103)
webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx (3)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1)
  • PlanMigrationChip (24-67)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx (3)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1)
  • EditPlanMigrationForm (17-32)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (2)
webapp/src/constants/links.tsx (1)
  • Link (1-45)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1)
  • PlanMigrationPlanPriceDetail (25-61)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationHistoryList.tsx (3)
webapp/src/service/response.types.ts (1)
  • HateoasListData (99-101)
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)
  • PaginatedHateoasTable (26-47)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (2)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx (1)
  • CloudPlanMigratingChip (6-34)
🪛 Biome (2.1.2)
webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx

[error] 16-16: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 17-17: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx

[error] 16-16: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 17-17: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (7)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1)

1-59: LGTM!

The component correctly wires up API mutations for updating and deleting cloud plan migrations, with proper error handling and loading states passed to the child form.

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

148-151: LGTM!

The CloudPlanMigratingChip is properly integrated with the correct props to display migration status for each plan.

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

1-61: LGTM!

The component correctly renders plan price details with appropriate styling and translation support for migration contexts.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx (1)

1-61: LGTM!

The component correctly implements the self-hosted EE plan migration editing flow, properly wiring API mutations and passing the planType to distinguish it from cloud migrations.

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

1-67: LGTM!

The component correctly implements a migration status chip with a detailed tooltip, properly handling loading, error, and success states with good UX.

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

30-102: LGTM!

The component is well-structured with proper separation of concerns, consistent use of translations, and appropriate Material-UI patterns. The conditional rendering for active/pending states and the graceful handling of missing migrations provide good user experience.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)

44-56: **No action needed: onSubmit/onDelete only fire on successful API calls.**
Both form components pass callbacks via mutate(..., { onSuccess: … }), so errors won’t trigger success messages or navigation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (2)

8-8: React Query import path likely incorrect or missing dependency

Use '@tanstack/react-query' (v4+) or ensure 'react-query' (v3) is installed. Update the import to match the installed package to avoid type resolution/build errors.

Run to verify package presence:

#!/bin/bash
jq -r '.dependencies["react-query"], .dependencies["@tanstack/react-query"], .devDependencies["react-query"], .devDependencies["@tanstack/react-query"]' package.json

54-59: Show actual migration date when available

Use finalizedAt when present, falling back to scheduledAt, so “Migrated At” shows completion time for completed items.

Apply:

-            {formatDate(item.scheduledAt, {
+            {formatDate(item.finalizedAt ?? item.scheduledAt, {
               timeZone: 'UTC',
               dateStyle: 'short',
               timeStyle: 'short',
             })}
🧹 Nitpick comments (2)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1)

43-49: Prefer client-side navigation to avoid full page reload

Use RouterLink (react-router) with component={RouterLink} and to=... to keep SPA navigation smooth.

Example:

-import { Link, TableCell, TableRow } from '@mui/material';
+import { Link as MuiLink, TableCell, TableRow } from '@mui/material';
+import { Link as RouterLink } from 'react-router-dom';
...
-            <Link
-              href={LINKS.ORGANIZATION_PROFILE.build({
+            <MuiLink
+              component={RouterLink}
+              to={LINKS.ORGANIZATION_PROFILE.build({
                 [PARAMS.ORGANIZATION_SLUG]: item.organizationSlug,
               })}
             >
               {item.organizationName}
-            </Link>
+            </MuiLink>
backend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.kt (1)

10-11: Make property resolution more robust and iteration clearer.

Use memberProperties to include inherited members and prefer a for‑loop to avoid non‑local returns.

-import kotlin.reflect.full.declaredMemberProperties
+import kotlin.reflect.full.memberProperties
@@
-        val props = instance::class.declaredMemberProperties
+        val props = instance::class.memberProperties

Optionally refactor the loop for clarity:

-    name.split(".").let { namePath ->
-      namePath.forEachIndexed { idx, property ->
-        val isLast = idx == namePath.size - 1
-        val props = instance::class.memberProperties
-        val prop = props.find { it.name == property } ?: throw NotFoundException()
-        if (isLast) {
-          (prop as? KMutableProperty1<Any, Any?>)?.let {
-            if (!it.hasAnnotation<E2eRuntimeMutable>()) {
-              throw BadRequestException(io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE)
-            }
-            it.set(instance, setPropertyDto.value)
-            onSet?.invoke()
-            return
-          } ?: throw BadRequestException(io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE)
-        }
-        instance = (prop as KProperty1<Any, Any?>).get(instance)
-          ?: throw NotFoundException()
-      }
-    }
+    val namePath = name.split(".")
+    for ((idx, property) in namePath.withIndex()) {
+      val isLast = idx == namePath.lastIndex
+      val props = instance::class.memberProperties
+      val prop = props.find { it.name == property } ?: throw NotFoundException()
+      if (isLast) {
+        (prop as? KMutableProperty1<Any, Any?>)?.let {
+          if (!it.hasAnnotation<E2eRuntimeMutable>()) {
+            throw BadRequestException(io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE)
+          }
+          it.set(instance, setPropertyDto.value)
+          onSet?.invoke()
+          return
+        } ?: throw BadRequestException(io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE)
+      }
+      instance = (prop as KProperty1<Any, Any?>).get(instance) ?: throw NotFoundException()
+    }

Note: Consider wrapping it.set(...) to surface a clearer BadRequest on type mismatch.

Also applies to: 25-25, 22-40

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c17988 and eae818a.

📒 Files selected for processing (6)
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.kt (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1 hunks)
  • webapp/src/service/billingApiSchema.generated.ts (16 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx
🔇 Additional comments (3)
webapp/src/service/billingApiSchema.generated.ts (2)

22-32: Migration endpoints look consistent across cloud and self‑hosted

Create/get/update/delete and subscriptions endpoints are coherent and mirror each other. No issues spotted.

Also applies to: 58-68


1040-1044: Confirm UI consumption of embedded list key

The paged list embeds items under _embedded.planMigrationRecordModelList. Ensure consumers (e.g., PaginatedHateoasTable/HateoasListData) handle this key, or add a mapper to normalize it.

You can grep for handling of embedded keys:

Also applies to: 1914-1933, 2550-2566

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

13-15: Clean delegation; please confirm internal-only exposure.

Constructor DI and facade delegation look good. Confirm that @InternalController ensures this endpoint is dev/ops‑only and inaccessible in production-user contexts.

Also applies to: 22-22

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (2)

12-12: Consider removing unused React import.

Modern React (17+) with the new JSX transform doesn't require importing React when using JSX.

Apply this diff:

-import React from 'react';

40-43: Consider adding error handling for the mutation.

While the mutation handles success cases, adding an onError callback would improve user experience by providing feedback when the API call fails.

Example implementation:

const createPlanMigrationLoadable = useBillingApiMutation({
  url: '/v2/administration/billing/self-hosted-ee-plans/migration',
  method: 'post',
  options: {
    onError: (error) => {
      messaging.error(
        <T keyName="administration_plan_migration_created_error" />
      );
    },
  },
});

Note: Verify the API structure supports the options parameter pattern, and ensure the translation key exists.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eae818a and c93e101.

📒 Files selected for processing (3)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (3)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1)
  • PlanMigrationChip (24-67)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (7)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)
  • CreatePlanMigrationFormData (34-35)
  • PlanMigrationFormData (29-32)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)
  • CreatePlanMigrationForm (21-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (5)
webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (2)

13-21: Critical issue resolved—hooks now called unconditionally.

The previous Rules of Hooks violation has been fixed. The hooks are now correctly called before the early return, ensuring consistent call order across renders.

The lazy-loading pattern (query enabled only when opened is true) is efficient. The non-null assertion on line 17 is runtime-safe because the query won't execute when migrationId is falsy.


23-34: LGTM!

The early return guard and component render logic are correct. The component properly wires up the lazy-loading behavior and passes the appropriate props to PlanMigrationChip.

webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (3)

17-20: LGTM!

Component declaration and hook initialization follow best practices.


51-60: LGTM! Breadcrumb navigation correctly references EE plans.

The previously reported issue with cloud plans in the breadcrumb has been fixed. The navigation now correctly references the EE plans page.


67-71: LGTM!

Form configuration is correct with planType="self-hosted" and proper loading state handling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)

83-84: Validation schema constant fixed — thanks.

Validation.PLAN_MIGRATION_FORM looks correct now vs. earlier typo.

🧹 Nitpick comments (9)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1)

19-38: Reorder mutation hook before its usage.

The submit function (line 19) references createPlanMigrationLoadable (line 20) before the hook is defined (line 35). While this works due to JavaScript's function hoisting and React's execution order, it reduces readability and can confuse maintainers.

Apply this diff to improve clarity:

   const { t } = useTranslate();
   const messaging = useMessage();
   const history = useHistory();
+
+  const createPlanMigrationLoadable = useBillingApiMutation({
+    url: '/v2/administration/billing/cloud-plans/migration',
+    method: 'post',
+  });

   const submit = (values: CreatePlanMigrationFormData) => {
     createPlanMigrationLoadable.mutate(
       {
         content: { 'application/json': values },
       },
       {
         onSuccess: () => {
           messaging.success(
             <T keyName="administration_plan_migration_created_success" />
           );
           history.push(LINKS.ADMINISTRATION_BILLING_CLOUD_PLANS.build());
         },
       }
     );
   };
-
-  const createPlanMigrationLoadable = useBillingApiMutation({
-    url: '/v2/administration/billing/cloud-plans/migration',
-    method: 'post',
-  });
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1)

12-12: Validate parsed route parameter.

Number() returns NaN for invalid input, which would cause API calls to fail. Add validation to handle malformed route parameters gracefully.

Apply this diff:

   const match = useRouteMatch();
   const migrationId = Number(match.params[PARAMS.PLAN_MIGRATION_ID]);
+
+  if (Number.isNaN(migrationId)) {
+    return <div>Invalid migration ID</div>; // or redirect to plans list
+  }
+
   const [subscriptionsPage, setSubscriptionsPage] = useState(0);
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1)

19-38: Reorder mutation hook before its usage.

The submit function (line 19) references createPlanMigrationLoadable (line 20) before the hook is defined (line 35). While this works due to JavaScript hoisting, it reduces code clarity.

Apply this diff:

   const { t } = useTranslate();
   const messaging = useMessage();
   const history = useHistory();
+
+  const createPlanMigrationLoadable = useBillingApiMutation({
+    url: '/v2/administration/billing/self-hosted-ee-plans/migration',
+    method: 'post',
+  });

   const submit = (values: CreatePlanMigrationFormData) => {
     createPlanMigrationLoadable.mutate(
       {
         content: { 'application/json': values },
       },
       {
         onSuccess: () => {
           messaging.success(
             <T keyName="administration_plan_migration_created_success" />
           );
           history.push(LINKS.ADMINISTRATION_BILLING_EE_PLANS.build());
         },
       }
     );
   };
-
-  const createPlanMigrationLoadable = useBillingApiMutation({
-    url: '/v2/administration/billing/self-hosted-ee-plans/migration',
-    method: 'post',
-  });
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1)

12-12: Validate parsed route parameter.

Number() returns NaN for invalid input, which would cause API calls to fail silently or with confusing errors. Validate before use.

Apply this diff:

   const match = useRouteMatch();
   const migrationId = Number(match.params[PARAMS.PLAN_MIGRATION_ID]);
+
+  if (Number.isNaN(migrationId)) {
+    return <div>Invalid migration ID</div>; // or redirect to plans list
+  }
+
   const [subscriptionsPage, setSubscriptionsPage] = useState(0);
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (5)

42-51: Align Formik value typing with PlanSelectorField’s context.

PlanMigrationForm is generic T, but PlanSelectorField reads Formik context as PlanMigrationFormData. This weakens type-safety and can mislead tooling.

Options:

  • Narrow this form to PlanMigrationFormData (and make Create type conform), or
  • Make PlanSelectorField generic and use useFormikContext<T>(), passed from parent, or
  • Loosen PlanSelectorField to useFormikContext<any>() if quick fix needed.

Referencing PlanSelectorField.tsx (lines 7–30 in provided snippet).


55-60: Tiny cleanup: remove redundant null-check.

Inside the truthy branch, migration && migration.sourcePlan.id is redundant.

-        id: migration && migration.sourcePlan.id,
+        id: migration.sourcePlan.id,

62-69: Keep local selection state in sync with reinitialized defaults.

With enableReinitialize, local selected* states can desync from props on subsequent renders.

Add effects to sync:

 import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
   const [selectedTargetPlan, setSelectedTargetPlan] = useState<FormPlanType>({
     id: defaultValues.targetPlanId,
   });
+
+  useEffect(() => {
+    setSelectedTargetPlan({ id: defaultValues.targetPlanId });
+  }, [defaultValues.targetPlanId]);
+
+  useEffect(() => {
+    setSelectedSourcePlan(defaultSourcePlan);
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [defaultSourcePlan?.id, defaultSourcePlan?.free]);

146-149: Prefer MUI InputAdornment for end adorners.

Improves semantics and spacing consistency.

-import { Box, Button, Typography } from '@mui/material';
+import { Box, Button, Typography, InputAdornment } from '@mui/material';
-            InputProps={{
-              endAdornment: <Box>{t('global_days')}</Box>,
-            }}
+            InputProps={{
+              endAdornment: (
+                <InputAdornment position="end">
+                  {t('global_days')}
+                </InputAdornment>
+              ),
+            }}
-            InputProps={{
-              endAdornment: <Box>{t('global_days')}</Box>,
-            }}
+            InputProps={{
+              endAdornment: (
+                <InputAdornment position="end">
+                  {t('global_days')}
+                </InputAdornment>
+              ),
+            }}

Also applies to: 154-157, 2-2


29-33: Unused field in type alias.

sourcePlanFree isn’t used in the form. Consider removing to reduce confusion, or wire it where needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c93e101 and 0517db4.

📒 Files selected for processing (6)
  • webapp/src/constants/GlobalValidationSchema.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • webapp/src/constants/GlobalValidationSchema.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (7)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)
  • CreatePlanMigrationForm (21-28)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)
  • AdministrationPlanMigrationEditBase (32-94)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx (1)
  • SelfHostedEePlanEditPlanMigrationForm (16-61)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (7)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)
  • CreatePlanMigrationForm (21-28)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (5)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/constants/GlobalValidationSchema.tsx (1)
  • Validation (52-538)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1)
  • PlanSelectorField (8-31)
webapp/src/component/common/LabelHint.tsx (1)
  • LabelHint (17-26)
webapp/src/hooks/confirmation.tsx (1)
  • confirmation (5-7)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)
  • AdministrationPlanMigrationEditBase (32-94)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1)
  • CloudPlanEditPlanMigrationForm (15-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build backend 🏗️
  • GitHub Check: Frontend static check 🪲

@dkrizan dkrizan force-pushed the danielkrizan/plan-migration branch from 0517db4 to 2e40fa7 Compare October 16, 2025 10:23
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (7)
backend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.kt (1)

28-35: Critical bug: mutability guard is a no‑op (missing throw).

This is the same issue flagged in the previous review. Line 30 only references io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE without throwing an exception, so the property still gets set on line 32 even when it lacks the @E2eRuntimeMutable annotation. This completely bypasses the intended safety check.

Apply this fix:

 (prop as? KMutableProperty1<Any, Any?>)?.let {
   if (!it.hasAnnotation<E2eRuntimeMutable>()) {
-    io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE
+    throw BadRequestException(io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE)
   }
   it.set(instance, setPropertyDto.value)
   onSet?.invoke()
   return
 } ?: throw BadRequestException(io.tolgee.constants.Message.PROPERTY_NOT_MUTABLE)
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (1)

117-130: Use a generic/self-hosted i18n key for the migration button label.

Replace the cloud‑specific key with a generic or EE‑specific one for consistency.

-            {t('administration_cloud_plan_create_migration')}
+            {t('administration_plan_create_migration')}

Also ensure the new key exists in all locales.

Run to verify keys and occurrences:

#!/bin/bash
rg -n -C2 'administration_(ee_)?plan_create_migration|administration_cloud_plan_create_migration'
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1)

54-59: Show completion date when available

Use finalizedAt when present, falling back to scheduledAt.

-            {formatDate(item.scheduledAt, {
+            {formatDate(item.finalizedAt ?? item.scheduledAt, {
               timeZone: 'UTC',
               dateStyle: 'short',
               timeStyle: 'short',
             })}
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1)

9-54: Deduplicate cloud and self-hosted edit components.

This component is nearly identical to the cloud variant, differing only in API endpoints, form component, and navigation labels. Per previous review feedback, this duplication should be addressed by extracting a shared component that accepts planType as a prop to configure these differences.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)

7-7: Fix invalid icon import (compile-time error).

@mui/x-date-pickers doesn't export ArrowRightIcon. This will cause a compilation error.

Import from Material Icons instead:

-import { ArrowRightIcon } from '@mui/x-date-pickers';
+import ArrowForwardIcon from '@mui/icons-material/ArrowForward';

And update usage at line 115:

-          <ArrowRightIcon style={{ marginTop: 25 }} />
+          <ArrowForwardIcon style={{ marginTop: 25 }} />

113-114: Avoid spreading undefined (runtime TypeError).

When migration is falsy, the expression {...(migration && { plans: [migration.sourcePlan] })} evaluates to {...false}, which spreads a boolean and can cause issues. Use a conditional that only spreads an object.

Apply this fix:

-            {...(migration && { plans: [migration.sourcePlan] })}
+            {...(isUpdate ? { plans: [migration.sourcePlan] } : {})}
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)

58-62: Add error handling and null checks before accessing data.

The component only checks migrations.isLoading but doesn't handle error states or the case where data is undefined. The non-null assertion at line 62 will cause a runtime error if the query fails or returns no data.

Apply this fix:

   if (migrations.isLoading) {
     return <SpinnerProgress />;
   }
 
+  if (migrations.isError || !migrations.data) {
+    return (
+      <DashboardPage>
+        <BaseAdministrationView
+          windowTitle={t('administration_plan_migration_configure_existing')}
+          allCentered
+          hideChildrenOnLoading={false}
+          navigation={navigation}
+        >
+          <Typography color="error">
+            <T keyName="administration_plan_migration_not_found" />
+          </Typography>
+        </BaseAdministrationView>
+      </DashboardPage>
+    );
+  }
+
-  const migration = migrations.data!;
+  const migration = migrations.data;
🧹 Nitpick comments (7)
backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt (1)

50-61: Implementation looks good, but consider adding the same null check to scheduleWithFixedDelay for consistency.

The scheduleWithCron method is correctly implemented and follows the same pattern as scheduleWithFixedDelay. The defensive null check (lines 55-57) is good practice since TaskScheduler.schedule() can return null.

However, scheduleWithFixedDelay (line 44) doesn't include this same null check, even though TaskScheduler.scheduleWithFixedDelay() can also return null. For consistency and defensive programming, consider adding the same null validation to the existing method.

Optional: Apply a similar null check to scheduleWithFixedDelay:

fun scheduleWithFixedDelay(
  runnable: Runnable,
  period: Duration,
): String {
  val future = taskScheduler.scheduleWithFixedDelay(runnable, period)
  if (future == null) {
    throw IllegalStateException("Future from scheduler was null")
  }
  val id = UUID.randomUUID().toString()
  scheduledTasks[id] = future
  return id
}
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)

15-24: Avoid sending undefined query params; gate filter flags before serializing

Only include filters when defined to prevent “?filterHasMigration=undefined” hitting the API.

Apply:

-    query: {
-      filterPublic,
-      filterHasMigration,
-    },
+    query: {
+      ...(filterPublic !== undefined ? { filterPublic } : {}),
+      ...(filterHasMigration !== undefined ? { filterHasMigration } : {}),
+    },

Please confirm the backend supports filterHasMigration on /v2/administration/billing/cloud-plans.

webapp/src/constants/GlobalValidationSchema.tsx (1)

533-537: Offsets should be integers (days)

Enforce whole-day values.

-      monthlyOffsetDays: Yup.number().required().min(0),
-      yearlyOffsetDays: Yup.number().required().min(0),
+      monthlyOffsetDays: Yup.number().integer().min(0).required(),
+      yearlyOffsetDays: Yup.number().integer().min(0).required(),
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1)

13-16: Type the status→color map; avoid any

Use a typed map and drop the as any cast.

-const colors = {
-  COMPLETED: 'success',
-};
+import type { ChipProps } from '@mui/material';
+const statusColorMap: Partial<Record<Status, ChipProps['color']>> = {
+  COMPLETED: 'success',
+  // FAILED: 'error',
+  // CANCELED: 'warning',
+};
...
-      color={(colors as any)[status] || 'default'}
+      color={statusColorMap[status] ?? 'default'}

Also applies to: 33-36

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

45-55: Don’t pass empty color prop

Passing highlightColor="" can produce invalid CSS. Omit it.

-              <PricePrimary
+              <PricePrimary
                 prices={plan.prices}
                 period={'YEARLY'}
-                highlightColor={''}
                 sx={{
                   fontSize: 14,
                   fontWeight: 500,
                 }}
                 noPeriodSwitch={true}
               />
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1)

16-18: Type subscriptions as paginated for clarity

The list uses pagination controls; prefer HateoasPaginatedData.

-type Props = {
-  subscriptions: UseQueryResult<HateoasListData<PlanMigrationRecord>>;
+type Props = {
+  subscriptions: UseQueryResult<
+    import('tg.service/response.types').HateoasPaginatedData<PlanMigrationRecord>
+  >;
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (1)

9-14: Apply useMemo to selectItems for performance; all onPlanChange callsites already correctly handle the plan object

The verification confirms that all consumers of onPlanChange throughout the codebase are already correctly receiving and using the plan object (AssignCloudPlanFormFields, PlanMigrationForm, CloudPlanTemplateSelectorField, SelfHostedEe variants, etc.). No callsite updates are needed.

However, the selectItems computation in GenericPlanSelector.tsx (lines 42–54) remains unoptimized and recomputes on every render. The suggested memoization with dependencies [plans, planProps?.hiddenIds, planProps?.free] is a valid performance optimization that should be applied.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0517db4 and 2e40fa7.

📒 Files selected for processing (41)
  • backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1 hunks)
  • backend/data/src/main/resources/I18n_en.properties (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.kt (1 hunks)
  • backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt (1 hunks)
  • e2e/cypress/support/dataCyType.d.ts (5 hunks)
  • webapp/src/component/common/FullWidthTooltip.tsx (1 hunks)
  • webapp/src/component/common/table/PaginatedHateoasTable.tsx (4 hunks)
  • webapp/src/component/layout/HeaderBar.tsx (2 hunks)
  • webapp/src/constants/GlobalValidationSchema.tsx (2 hunks)
  • webapp/src/constants/links.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (4 hunks)
  • webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (1 hunks)
  • webapp/src/eeSetup/eeModule.ee.tsx (3 hunks)
  • webapp/src/service/apiSchema.generated.ts (2 hunks)
  • webapp/src/service/billingApiSchema.generated.ts (16 hunks)
  • webapp/src/translationTools/useErrorTranslation.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • webapp/src/constants/links.tsx
🚧 Files skipped from review as they are similar to previous changes (18)
  • webapp/src/translationTools/useErrorTranslation.ts
  • backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt
  • webapp/src/component/layout/HeaderBar.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx
  • webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx
  • webapp/src/component/common/FullWidthTooltip.tsx
  • e2e/cypress/support/dataCyType.d.ts
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx
  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx
  • webapp/src/service/apiSchema.generated.ts
  • backend/data/src/main/resources/I18n_en.properties
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-15T13:15:16.972Z
Learnt from: dkrizan
PR: tolgee/tolgee-platform#3216
File: webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx:8-8
Timestamp: 2025-10-15T13:15:16.972Z
Learning: In the tolgee-platform repository, when reviewing webapp code, check `webapp/package.json` for dependencies rather than the root `package.json`, as this is a monorepo structure where the webapp has its own package configuration.

Applied to files:

  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx
🧬 Code graph analysis (14)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (5)
webapp/src/service/response.types.ts (1)
  • HateoasListData (99-101)
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)
  • PaginatedHateoasTable (26-47)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1)
  • PlanMigrationStatus (17-52)
webapp/src/component/common/EmptyState.tsx (1)
  • EmptyState (24-42)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1)
webapp/src/ee/billing/component/Price/PricePrimary.tsx (1)
  • PricePrimary (28-66)
webapp/src/eeSetup/eeModule.ee.tsx (5)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1)
  • AdministrationCloudPlanMigrationCreate (14-69)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1)
  • AdministrationCloudPlanMigrationEdit (9-55)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1)
  • AdministrationSelfHostedEePlanMigrationCreate (14-70)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1)
  • AdministrationSelfHostedEePlanMigrationEdit (9-54)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (4)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)
  • CloudPlanSelector (7-33)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1)
  • SelfHostedEePlanSelector (6-29)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (2)
webapp/src/constants/links.tsx (1)
  • Link (1-45)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1)
  • PlanMigrationPlanPriceDetail (25-61)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (3)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1)
  • PlanMigrationChip (24-67)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (2)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (1)
  • SelfHostedEePlanMigratingChip (6-35)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)
  • AdministrationPlanMigrationEditBase (32-94)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx (1)
  • SelfHostedEePlanEditPlanMigrationForm (16-61)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)
  • CreatePlanMigrationFormData (34-35)
  • PlanMigrationForm (42-193)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (6)
webapp/src/service/response.types.ts (1)
  • HateoasListData (99-101)
webapp/src/component/navigation/Navigation.tsx (1)
  • NavigationItem (33-37)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1)
  • PlanMigrationRecordList (20-72)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (1)
webapp/src/component/searchSelect/SearchSelect.tsx (2)
  • SearchSelect (41-131)
  • SelectItem (8-11)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (5)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/constants/GlobalValidationSchema.tsx (1)
  • Validation (52-538)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1)
  • PlanSelectorField (8-31)
webapp/src/component/common/LabelHint.tsx (1)
  • LabelHint (17-26)
webapp/src/hooks/confirmation.tsx (1)
  • confirmation (5-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (14)
backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt (1)

6-6: LGTM!

The import is correctly added for the new scheduleWithCron method.

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

13-14: LGTM: Clean refactoring to use facade pattern.

The extraction of property-setting logic to InternalPropertiesSetterFacade improves separation of concerns. Constructor injection and making tolgeeProperties private are both appropriate changes.

However, ensure the critical bug in InternalPropertiesSetterFacade (lines 28-35) is fixed before merging, as this controller now depends on that facade.


22-22: Verify that the facade's critical bug is fixed.

The delegation to internalPropertiesSetterFacade.setProperty() is correct, but the facade currently has a critical bug where the mutability guard doesn't throw an exception (see review comment on InternalPropertiesSetterFacade.kt lines 28-35). This means properties can be set even without the @E2eRuntimeMutable annotation.

backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt (2)

19-20: LGTM!

The imports are necessary and correct for the new localDate property implementation.


89-92: LGTM!

The localDate property correctly converts the existing date property to LocalDate using the standard conversion pattern. The computed property approach ensures it always reflects the current date state, which is the appropriate behavior for a date provider component.

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

155-158: LGTM: correct migration chip wiring.

Props map as expected: migrationId optional and isEnabled from plan.activeMigration.

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

1-35: LGTM: hooks at top-level and lazy query gating are correct.

Non‑null assertion is acceptable here given enabled gating; editLink points to the EE migration edit route.

webapp/src/constants/GlobalValidationSchema.tsx (1)

42-49: Good: localized number.min messages

Locale override for number.min with translated key looks consistent. No issues.

webapp/src/eeSetup/eeModule.ee.tsx (1)

71-74: Routes added in correct order (avoids :planId catching “migration/…”)

Placement before the “:planId” edit routes prevents conflicts. LGTM.

Also applies to: 146-157, 173-184

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)

1-28: LGTM!

Clean wrapper component that provides sensible default values for the create flow. The implementation correctly forwards props and maintains proper typing.

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

12-19: LGTM!

The addition of filterHasMigration follows the same pattern as the cloud variant, maintaining consistency across plan selectors.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1)

8-31: LGTM!

Clean Formik integration that properly binds the plan selector to form state. The type-based selector choice is straightforward and the implementation follows Formik best practices.

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

1-103: LGTM!

Well-structured component with proper conditional rendering, correct icon imports, and clear layout. The active/inactive state handling and routing integration are implemented correctly.

webapp/src/service/billingApiSchema.generated.ts (1)

1-4938: Auto-generated API schema changes acknowledged.

This file contains the auto-generated OpenAPI schema with new plan migration endpoints, models, and error codes. As an auto-generated file, no code quality review is needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbffdd2 and a679d16.

📒 Files selected for processing (3)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (4)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1)
  • AdministrationPlanMigrationCreateBase (25-68)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (8)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/component/navigation/Navigation.tsx (1)
  • NavigationItem (33-37)
webapp/src/constants/links.tsx (1)
  • Link (1-45)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)
  • CreatePlanMigrationForm (21-28)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (4)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1)
  • AdministrationPlanMigrationCreateBase (25-68)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (5)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1)

8-28: LGTM! Clean refactoring to use the base component.

The component properly delegates to AdministrationPlanMigrationCreateBase, eliminating the duplication flagged in previous reviews. The mutation setup and onSubmit handler are correctly implemented.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (2)

1-23: LGTM! Well-defined props interface.

The component props are clearly typed and properly support both cloud and self-hosted migration flows. The optional planType allows flexibility while maintaining type safety.


25-68: Excellent refactoring to eliminate duplication.

This base component successfully addresses the deduplication concerns flagged in previous reviews. The implementation cleanly abstracts the shared logic between cloud and self-hosted migration flows, reducing maintenance burden and ensuring consistent behavior.

webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (2)

8-28: LGTM! Clean implementation with past issues resolved.

The component properly uses the shared base component and correctly targets the self-hosted EE plans endpoint. Previous issues regarding unnecessary type unions and navigation have been addressed.


30-48: LGTM! Correct usage of the base component.

All props are properly configured, including:

  • Correct EE plans navigation and links
  • Explicit planType={'self-hosted'} passed to the form
  • Appropriate success navigation

This is the correct pattern that both cloud and self-hosted components should follow.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (2)

1-6: Consider moving React import to the top.

By convention, React is typically imported first in React component files, followed by third-party libraries, then local imports.

Apply this diff:

+import React from 'react';
 import { useTranslate } from '@tolgee/react';
 import { LINKS } from 'tg.constants/links';
 import { useBillingApiMutation } from 'tg.service/http/useQueryApi';
-import React from 'react';
 import { AdministrationPlanMigrationCreateBase } from 'tg.ee.module/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase';
 import { CreatePlanMigrationFormData } from 'tg.ee.module/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm';

20-27: Consider adding error handling for the mutation.

The mutation does not specify an onError handler. If the API call fails, users may not receive feedback about the failure.

Add an error handler to improve user experience:

 createMutation.mutate(
   {
     content: { 'application/json': values },
   },
   {
     onSuccess: callbacks?.onSuccess,
+    onError: (error) => {
+      // Handle error appropriately (e.g., show error message)
+    },
   }
 );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a679d16 and ccf8cc9.

📒 Files selected for processing (3)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1)
  • AdministrationPlanMigrationCreateBase (25-68)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build frontend 🏗️
  • GitHub Check: Build backend 🏗️

@dkrizan dkrizan force-pushed the danielkrizan/plan-migration branch from a3a5150 to 6b66d05 Compare October 17, 2025 09:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (2)

8-47: Duplication with self-hosted component remains.

This component continues to duplicate logic from the self-hosted migration create component. While the differences are minimal (API endpoint, navigation links), the duplication increases maintenance burden. Consider the previously suggested refactor to extract a shared component parameterized by plan type.


30-46: Pass the planType prop for consistency.

The base component accepts an optional planType prop that is forwarded to CreatePlanMigrationForm. The self-hosted equivalent explicitly passes planType={'self-hosted'}, but this component omits it. For consistency and to provide proper context to the form, add the prop.

Apply this diff:

     <AdministrationPlanMigrationCreateBase
       onSubmit={onSubmit}
       navigation={[
         [
           t('administration_cloud_plans'),
           LINKS.ADMINISTRATION_BILLING_CLOUD_PLANS.build(),
         ],
         [
           t('administration_plan_migration_configure'),
           LINKS.ADMINISTRATION_BILLING_CLOUD_PLAN_MIGRATION_CREATE.build(),
         ],
       ]}
       successLink={LINKS.ADMINISTRATION_BILLING_CLOUD_PLANS}
       isLoading={createMutation.isLoading}
+      planType={'cloud'}
     />
🧹 Nitpick comments (7)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (7)

25-28: Align prop type with null‑guard (unreachable code otherwise).

Either make migration optional/null in Props or remove the early return.

Option A (keep guard):

-type Props = {
-  migration: CloudPlanModel | SelfHostedEePlanAdministrationModel;
+type Props = {
+  migration?: CloudPlanModel | SelfHostedEePlanAdministrationModel | null;

Option B (assume required and present):

-  if (!migration) {
-    return (
-      <Box p={3}>
-        <Typography variant={'subtitle2'}>
-          {t('administration_plan_migration_not_found')}
-        </Typography>
-      </Box>
-    );
-  }
+  // assume `migration` is always provided

Also applies to: 32-40


61-64: Verify label for target plan (“From” vs “To”).

Per PlanMigrationPlanPriceDetail snippet, the label is “from”. Rendering it for the target plan likely shows “from” twice. Pass a label or add a prop to that component so the second chip says “to”.

If the component supports a custom label:

-          <PlanMigrationPlanPriceDetail plan={migration.sourcePlan} />
+          <PlanMigrationPlanPriceDetail plan={migration.sourcePlan} labelKey="administration_plan_migration_from" />
           <ArrowRight width={18} height={18} />
-          <PlanMigrationPlanPriceDetail plan={migration.targetPlan} />
+          <PlanMigrationPlanPriceDetail plan={migration.targetPlan} labelKey="administration_plan_migration_to" />

If not, update PlanMigrationPlanPriceDetail (outside this file) to accept labelKey?: string and default to the current “from” label.


71-81: Guard optional offsets to avoid “undefined” in UI.

If monthlyOffsetDays/yearlyOffsetDays can be absent, conditionally render lines.

-            <TooltipText>
-              <T
-                keyName="administration_plan_migration_monthly_timing"
-                params={{ days: migration.monthlyOffsetDays, b: <b /> }}
-              />
-            </TooltipText>
+            {migration.monthlyOffsetDays != null && (
+              <TooltipText>
+                <T
+                  keyName="administration_plan_migration_monthly_timing"
+                  params={{ days: migration.monthlyOffsetDays, b: <b /> }}
+                />
+              </TooltipText>
+            )}
...
-            <TooltipText>
-              <T
-                keyName="administration_plan_migration_yearly_timing"
-                params={{ days: migration.yearlyOffsetDays, b: <b /> }}
-              />
-            </TooltipText>
+            {migration.yearlyOffsetDays != null && (
+              <TooltipText>
+                <T
+                  keyName="administration_plan_migration_yearly_timing"
+                  params={{ days: migration.yearlyOffsetDays, b: <b /> }}
+                />
+              </TooltipText>
+            )}

15-17: Avoid forced no‑wrap for translatable text to prevent overflow.

white-space: nowrap; may clip in longer locales. Allow wrapping.

-const TooltipText = styled('div')`
-  white-space: nowrap;
-`;
+const TooltipText = styled('div')`
+  white-space: normal;
+  overflow-wrap: anywhere;
+`;

Also applies to: 70-76, 76-82


59-59: Slightly clearer clsx usage.

-      <MigrationDetailBox className={clsx(!migration.enabled && 'inactive')}>
+      <MigrationDetailBox className={clsx({ inactive: !migration.enabled })}>

87-87: Remove unnecessary key on a non‑list element.

-          key="edit-migration"

62-62: Decorative icon: hide from screen readers.

-          <ArrowRight width={18} height={18} />
+          <ArrowRight width={18} height={18} aria-hidden="true" focusable="false" />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccf8cc9 and 6b66d05.

📒 Files selected for processing (4)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (4 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1)
  • AdministrationPlanMigrationCreateBase (25-68)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (2)
webapp/src/constants/links.tsx (1)
  • Link (1-45)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1)
  • PlanMigrationPlanPriceDetail (25-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (1)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1)

16-28: Fix applied: callbacks parameter now consistent.

The optional chaining on the required callbacks parameter has been corrected. Line 25 now directly accesses callbacks.onSuccess, aligning with the type signature on line 18.

@dkrizan dkrizan force-pushed the danielkrizan/plan-migration branch from 6b66d05 to 42559b8 Compare October 20, 2025 07:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (6)
backend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.kt (1)

28-35: Critical bug: Mutability guard is bypassed (missing throw).

Lines 29-30 check whether the property lacks @E2eRuntimeMutable, but only reference the error message without throwing. Execution continues to line 32, setting the property regardless of the annotation. This defeats the safety check.

This issue was previously flagged. Apply the fix from the earlier review comment.

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

59-59: Unresolved: TableHead expects TableRow children.

This critical issue was flagged in the previous review but has not been addressed. MUI's TableHead component expects TableRow children, but the current implementation accepts any ReactNode, which may include bare TableCell elements that will render incorrectly.

As suggested in the previous review, you should either:

  1. Require callers to pass a <TableRow> element (update the type as suggested in my comment on lines 22-24), or
  2. Add a defensive wrapper to ensure proper structure

The previous review provided a defensive wrapper example that checks for TableRow and wraps content if needed.

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

116-129: customButtons may still be ignored downstream.

Previously flagged: BaseAdministrationView doesn’t forward/render customButtons. Re-verify it’s now plumbed through; otherwise this button won’t appear.

#!/bin/bash
# Check prop typing and rendering of `customButtons` downstream
rg -n -C3 "export (type|interface) .*Props" webapp/src/views/administration/components/BaseAdministrationView.tsx
rg -n "customButtons" webapp/src/views/administration/components/BaseAdministrationView.tsx
rg -n "customButtons" webapp/src/component/layout/BaseSettingsView/**/*.tsx webapp/src/component/layout/BaseView.tsx || true
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1)

31-45: Pass planType={'cloud'} for symmetry and clarity.

Matches the self-hosted variant and avoids relying on downstream defaults.

     <AdministrationPlanMigrationCreateBase
       onSubmit={onSubmit}
       navigation={[
         [
           t('administration_cloud_plans'),
           LINKS.ADMINISTRATION_BILLING_CLOUD_PLANS.build(),
         ],
         [
           t('administration_plan_migration_configure'),
           LINKS.ADMINISTRATION_BILLING_CLOUD_PLAN_MIGRATION_CREATE.build(),
         ],
       ]}
       successLink={LINKS.ADMINISTRATION_BILLING_CLOUD_PLANS}
       isLoading={createMutation.isLoading}
+      planType={'cloud'}
     />
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1)

30-46: Deduplicate with the cloud variant via a shared param.

This mirrors the cloud create with only endpoint/links differing. Extract a shared creator (param: 'cloud' | 'self-hosted') to remove duplication.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)

58-62: Add error handling and null checks before accessing data.

The component only checks migrations.isLoading but doesn't handle error states or the case where data is undefined. The non-null assertion at line 62 will cause a runtime error if the query fails or returns no data.

Apply this diff to add proper guards:

   if (migrations.isLoading) {
     return <SpinnerProgress />;
   }
 
+  if (migrations.isError || !migrations.data) {
+    return (
+      <DashboardPage>
+        <BaseAdministrationView
+          windowTitle={t('administration_plan_migration_configure_existing')}
+          allCentered
+          hideChildrenOnLoading={false}
+          navigation={navigation}
+        >
+          <Typography color="error">
+            <T keyName="administration_plan_migration_not_found" />
+          </Typography>
+        </BaseAdministrationView>
+      </DashboardPage>
+    );
+  }
+
-  const migration = migrations.data!;
+  const migration = migrations.data;
🧹 Nitpick comments (6)
backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt (1)

69-71: LGTM! Useful test helper addition.

The implementation correctly uses Mockito's times(num) matcher to verify exact email send counts, complementing the existing verifyEmailSent() method. The method follows established patterns in the class and will be useful for test scenarios requiring precise count assertions.

Optional: Consider adding KDoc.

For better discoverability, you could add KDoc documentation:

+  /**
+   * Verifies that exactly [num] emails were sent via JavaMailSender.
+   * @param num the expected number of emails sent
+   */
   fun verifyTimesEmailSent(num: Int) {
     verify(javaMailSender, times(num)).send(any<MimeMessage>())
   }
webapp/src/component/common/table/PaginatedHateoasTable.tsx (1)

22-24: Consider stricter typing and documentation for the tableHead prop.

The tableHead?: ReactNode type is very permissive and allows invalid structures. Consider:

  1. Stricter type: Use ReactElement to enforce that callers pass a <TableRow> element:

    tableHead?: ReactElement<typeof TableRow>;

    Or if multiple rows are needed:

    tableHead?: ReactElement | ReactElement[];
  2. JSDoc documentation: Add documentation explaining the expected structure:

    /**
     * Optional table header. Should be a TableRow element containing TableCell children.
     * @example
     * <TableRow><TableCell>Header 1</TableCell><TableCell>Header 2</TableCell></TableRow>
     */
    tableHead?: ReactElement;

This improves type safety and developer experience by catching structural errors at compile time rather than runtime.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (2)

8-15: Consider adding a color for SCHEDULED status and clarifying the date type.

The colors mapping only includes COMPLETED, so SCHEDULED will render with the default chip color. For better visual distinction, consider adding a color for SCHEDULED (e.g., 'info' or 'warning').

Additionally, the date parameter is typed as number but represents a timestamp. Consider adding a comment or type alias for clarity.

Apply this diff to add SCHEDULED color and improve type clarity:

+/** Timestamp in milliseconds */
 type Props = {
   status: Status;
   date?: number;
 };
 
 const colors = {
   COMPLETED: 'success',
+  SCHEDULED: 'info',
-};
+} as const;

39-48: Update date display to use user's local timezone with timezone indicator.

Displaying times in the user's local timezone by default is the most intuitive UX for most users. Currently, the code formats all migration times in hardcoded UTC without context. Always show a timezone indicator when it matters—e.g., "10:00 AM EDT (14:00 UTC)" or "10:00 AM — America/New_York". For migration schedules, consider either:

  • Detecting user timezone (via Intl API) and displaying in local time with a UTC offset indicator, or
  • Showing dual format (local + UTC) with an option for users to switch timezones.
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (1)

132-134: Use a stable key for list items.

Prefer a stable identifier over the array index to prevent unnecessary remounts.

-          {plansLoadable.data?._embedded?.plans?.map((plan, i) => (
-            <ListItem key={i} data-cy="administration-cloud-plans-item">
+          {plansLoadable.data?._embedded?.plans?.map((plan) => (
+            <ListItem key={plan.id} data-cy="administration-cloud-plans-item">
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1)

8-17: Strengthen type safety for name and generic parameters.

Two type safety concerns:

  1. Line 17 uses any in the generic, which defeats type checking for forwarded props from GenericPlanSelector.
  2. Line 14 allows name to be any string, but only specific keys of PlanMigrationFormData are valid. This can cause runtime errors if an invalid field name is passed.

Consider constraining name to valid form field keys:

-  name: string;
+  name: 'sourcePlanId' | 'targetPlanId';

Or use a more flexible keyof constraint if other fields might be added:

+type PlanFieldName = Extract<keyof PlanMigrationFormData, 'sourcePlanId' | 'targetPlanId'>;

 export const PlanSelectorField = ({
   name,
   ...
 }: {
-  name: string;
+  name: PlanFieldName;

For the any generic, if possible, constrain it to the actual plan model types used by the selectors, or at minimum use unknown instead of any.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b66d05 and 42559b8.

📒 Files selected for processing (42)
  • backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt (2 hunks)
  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt (1 hunks)
  • backend/data/src/main/resources/I18n_en.properties (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/controllers/internal/PropertiesController.kt (1 hunks)
  • backend/development/src/main/kotlin/io/tolgee/facade/InternalPropertiesSetterFacade.kt (1 hunks)
  • backend/testing/src/main/kotlin/io/tolgee/fixtures/EmailTestUtil.kt (1 hunks)
  • e2e/cypress/support/dataCyType.d.ts (5 hunks)
  • webapp/src/component/common/FullWidthTooltip.tsx (1 hunks)
  • webapp/src/component/common/table/PaginatedHateoasTable.tsx (4 hunks)
  • webapp/src/component/layout/HeaderBar.tsx (2 hunks)
  • webapp/src/constants/GlobalValidationSchema.tsx (2 hunks)
  • webapp/src/constants/links.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (4 hunks)
  • webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx (1 hunks)
  • webapp/src/eeSetup/eeModule.ee.tsx (3 hunks)
  • webapp/src/service/apiSchema.generated.ts (2 hunks)
  • webapp/src/service/billingApiSchema.generated.ts (17 hunks)
  • webapp/src/translationTools/useErrorTranslation.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/GenericPlanSelector.tsx
🚧 Files skipped from review as they are similar to previous changes (17)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx
  • webapp/src/component/layout/HeaderBar.tsx
  • webapp/src/constants/links.tsx
  • webapp/src/component/common/FullWidthTooltip.tsx
  • backend/data/src/main/kotlin/io/tolgee/constants/Message.kt
  • backend/data/src/main/kotlin/io/tolgee/component/SchedulingManager.kt
  • webapp/src/translationTools/useErrorTranslation.ts
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx
  • webapp/src/ee/billing/administration/subscriptionPlans/components/migration/SelfHostedEePlanEditPlanMigrationForm.tsx
  • webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx
  • backend/data/src/main/kotlin/io/tolgee/component/CurrentDateProvider.kt
  • webapp/src/service/apiSchema.generated.ts
  • webapp/src/ee/billing/component/Plan/migration/SelfHostedEePlanMigratingChip.tsx
🧰 Additional context used
🧬 Code graph analysis (14)
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (2)
webapp/src/constants/links.tsx (2)
  • Link (1-45)
  • LINKS (69-455)
webapp/src/ee/billing/component/Plan/migration/CloudPlanMigratingChip.tsx (1)
  • CloudPlanMigratingChip (6-35)
webapp/src/eeSetup/eeModule.ee.tsx (6)
webapp/src/component/common/PrivateRoute.tsx (1)
  • PrivateRoute (8-14)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (1)
  • AdministrationCloudPlanMigrationCreate (8-47)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (1)
  • AdministrationCloudPlanMigrationEdit (9-55)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (1)
  • AdministrationSelfHostedEePlanMigrationCreate (8-48)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationEdit.tsx (1)
  • AdministrationSelfHostedEePlanMigrationEdit (9-54)
webapp/src/ee/billing/administration/subscriptionPlans/migration/selfhosted/AdministrationSelfHostedEePlanMigrationCreate.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1)
  • AdministrationPlanMigrationCreateBase (25-68)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (3)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/EditPlanMigrationForm.tsx (1)
  • EditPlanMigrationForm (17-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationChip.tsx (3)
webapp/src/constants/links.tsx (1)
  • Link (1-45)
webapp/src/component/common/FullWidthTooltip.tsx (1)
  • FullWidthTooltip (3-11)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (1)
  • PlanMigrationDetail (30-113)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1)
webapp/src/ee/billing/component/Price/PricePrimary.tsx (1)
  • PricePrimary (28-66)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (4)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • PlanMigrationFormData (29-32)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/fields/CloudPlanSelector.tsx (1)
  • CloudPlanSelector (7-33)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/fields/SelfHostedEePlanSelector.tsx (1)
  • SelfHostedEePlanSelector (6-29)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationCreate.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiMutation (295-300)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)
  • CreatePlanMigrationFormData (34-35)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationCreateBase.tsx (1)
  • AdministrationPlanMigrationCreateBase (25-68)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (6)
webapp/src/service/response.types.ts (1)
  • HateoasListData (99-101)
webapp/src/component/navigation/Navigation.tsx (1)
  • NavigationItem (33-37)
webapp/src/hooks/useSuccessMessage.tsx (1)
  • useMessage (6-9)
webapp/src/component/layout/DashboardPage.tsx (1)
  • DashboardPage (43-90)
webapp/src/views/administration/components/BaseAdministrationView.tsx (1)
  • BaseAdministrationView (13-60)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationRecordList.tsx (1)
  • PlanMigrationRecordList (21-74)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (2)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (2)
  • CreatePlanMigrationFormData (34-35)
  • PlanMigrationForm (42-201)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/ee/billing/administration/subscriptionPlans/migration/cloud/AdministrationCloudPlanMigrationEdit.tsx (4)
webapp/src/service/http/useQueryApi.ts (1)
  • useBillingApiQuery (288-293)
webapp/src/ee/billing/administration/subscriptionPlans/migration/general/AdministrationPlanMigrationEditBase.tsx (1)
  • AdministrationPlanMigrationEditBase (32-94)
webapp/src/constants/links.tsx (1)
  • LINKS (69-455)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1)
  • CloudPlanEditPlanMigrationForm (15-59)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationDetail.tsx (2)
webapp/src/constants/links.tsx (1)
  • Link (1-45)
webapp/src/ee/billing/component/Plan/migration/PlanMigrationPlanPriceDetail.tsx (1)
  • PlanMigrationPlanPriceDetail (26-59)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (5)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)
  • PlanType (1-1)
webapp/src/constants/GlobalValidationSchema.tsx (1)
  • Validation (52-538)
webapp/src/ee/billing/administration/subscriptionPlans/components/migration/fields/PlanSelectorField.tsx (1)
  • PlanSelectorField (8-31)
webapp/src/component/common/LabelHint.tsx (1)
  • LabelHint (17-26)
webapp/src/hooks/confirmation.tsx (1)
  • confirmation (5-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build backend 🏗️
🔇 Additional comments (19)
e2e/cypress/support/dataCyType.d.ts (1)

83-85: LGTM! Well-organized plan migration test selectors.

The new data-cy selectors for plan migration features follow the established kebab-case convention, are alphabetically ordered correctly, and have clear, descriptive names that indicate their purpose (create/edit migration actions, migration status indicator, delete button, tooltip detail, and plan source/target selectors).

Also applies to: 269-269, 565-565, 685-685, 717-717

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

5-5: LGTM! Clean refactoring to facade pattern.

The extraction of reflection-based property setting into InternalPropertiesSetterFacade improves separation of concerns and makes the controller simpler and more testable.

Also applies to: 13-14, 22-22

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

1-1: LGTM: Imports are correct.

The new ReactNode and TableHead imports are necessary and correctly added for the tableHead functionality.

Also applies to: 11-11


35-47: LGTM: Clean prop forwarding pattern.

The destructuring and forwarding of tableHead through the listComponent lambda is implemented correctly.


49-52: LGTM: Well-defined internal interface.

The PaginatedHateoasTableListComponentProps interface is clean and correctly defines the component's props.

webapp/src/ee/billing/administration/subscriptionPlans/migration/general/PlanMigrationStatus.tsx (1)

21-30: LGTM!

The status label mapping correctly handles the known COMPLETED and SCHEDULED statuses with translations. The default fallback ensures forward compatibility if new statuses are added.

backend/data/src/main/resources/I18n_en.properties (1)

117-136: Verify if plan-migration email implementation is incomplete or if properties were added prematurely.

The review comment references past feedback that cannot be verified. However, investigation reveals:

Real issues found:

  • UTF-8 encoding corruption: Lines contain mangled characters ("WeM-bM-^@M-^Yre" for "We're", "youM-bM-^@M-^Yd" for "you'd")
  • Wording error: "subscriptions options" should be "subscription options"

Context: The plan-migration i18n properties exist but have no corresponding implementation—no NotificationType.PLAN_MIGRATION enum or PlanMigrationEmailComposer class exists in the codebase. The properties appear added in advance of implementation.

Before addressing the double greeting/sign-off concerns raised in the review: clarify whether this PR is adding complete plan-migration email functionality or just i18n strings. If implementation follows existing email patterns (body injected into the template), the architectural concerns in the review would be valid.

Immediately fixable: The encoding and wording errors should be corrected regardless of implementation status.

webapp/src/constants/GlobalValidationSchema.tsx (2)

42-49: LGTM!

The number min validation with translation support is correctly implemented and follows the existing pattern for string validations.


533-537: LGTM!

The PLAN_MIGRATION_FORM validation schema is correctly defined with appropriate constraints for migration offset days. The typo mentioned in previous reviews has been fixed.

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

26-58: Verify the hardcoded 'YEARLY' period is intentional.

The period prop for PricePrimary is hardcoded to 'YEARLY' on Line 45. If migrations can apply to both monthly and yearly subscriptions, consider accepting the period as a prop or deriving it from the plan/migration context.

If the hardcoded value is intentional (e.g., always showing annual pricing for comparison), please confirm this is the expected behavior.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CloudPlanEditPlanMigrationForm.tsx (1)

15-59: LGTM!

The mutation wiring follows standard patterns and correctly integrates the update and delete operations with proper loading states and success callbacks.

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

30-112: LGTM!

The component is well-structured and correctly uses MUI system props (p={3}, p={1}) instead of the unsupported padding prop. The previous review concern has been addressed.

webapp/src/service/billingApiSchema.generated.ts (1)

1-4943: Auto-generated schema changes look consistent.

This file is auto-generated from the OpenAPI specification. The new plan migration schemas, endpoints, and error codes appear correctly generated and align with the feature requirements.

webapp/src/eeSetup/eeModule.ee.tsx (2)

71-74: LGTM!

The new migration component imports are correctly added and follow the existing import structure.


146-184: LGTM!

The routing configuration for plan migration create and edit pages follows the established patterns and correctly maps to the imported components for both cloud and self-hosted EE plans.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/types.ts (1)

1-1: LGTM!

Clean and simple type definition that provides proper type discrimination for cloud vs self-hosted plans across the migration feature.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/CreatePlanMigrationForm.tsx (1)

7-27: LGTM.

Defaults and typing look correct; wrapper cleanly delegates to PlanMigrationForm.

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

15-24: API schema correctly supports filterHasMigration query param.

Verification confirms the generated schema includes filterHasMigration?: boolean; in the query parameters for the /v2/administration/billing/cloud-plans endpoint (billingApiSchema.generated.ts lines 1729, 2363). The TypeScript types are correctly generated and the code change is properly wired with no type conflicts or runtime param issues.

webapp/src/ee/billing/administration/subscriptionPlans/components/migration/PlanMigrationForm.tsx (1)

66-68: ****

The schema verification shows that targetPlanId: number is a required field in both CreatePlanMigrationRequest and PlanMigrationRequest (no optional marker). Since CreatePlanMigrationFormData and PlanMigrationFormData are derived directly from these schemas, defaultValues.targetPlanId is always of type number, never undefined. The code at lines 66-68 is correct and requires no guard.

Likely an incorrect or invalid review comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants