Skip to content

Conversation

@zomp
Copy link
Contributor

@zomp zomp commented Oct 22, 2025

last activity screenshot

Requested in #2973.

Based on #3238.

Summary by CodeRabbit

  • New Features

    • Users list now shows formatted "last activity" with a compact two-column user item and action area.
  • Documentation

    • Development guide refined for clarity, formatting, examples and authentication snippets.
  • Chores

    • Public user schemas and API responses migrated to an administration-focused user model.
    • Administration endpoints now require stricter super-admin access.
    • Public LLM prompt endpoint and related schemas removed.
  • Tests

    • E2E helpers updated and a test added to verify user last-activity display and date forcing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

Renames admin user models/assemblers to administration-specific types, adds lastActivity through repository/service/controller/DTOs and frontend schemas, removes a public LLM prompt endpoint and related schemas, refactors admin UI list rendering, and updates E2E tests to assert lastActivity.

Changes

Cohort / File(s) Summary
Model & HATEOAS
backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountAdministrationModel.kt, backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountAdministrationModelAssembler.kt, backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountModelAssembler.kt
Introduced UserAccountAdministrationModel (adds lastActivity) and UserAccountAdministrationModelAssembler (maps UserAccountAdministrationView → model using AvatarService); removed legacy UserAccountModelAssembler.
DTO & Repository
backend/data/src/main/kotlin/io/tolgee/dtos/queryResults/UserAccountAdministrationView.kt, backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt
Added UserAccountAdministrationView (includes lastActivity); findAllWithDisabledPaged now returns Page<UserAccountAdministrationView> and SQL/query produces lastActivity.
Service & Controller
backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt, backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationController.kt
Service signature updated to use administration view; controller uses administration assemblers/types, returns PagedModel<UserAccountAdministrationModel>, and endpoints gained @RequiresSuperAuthentication where applicable; minor textual fixes.
Frontend schemas & UI
webapp/src/service/apiSchema.generated.ts, webapp/src/websocket-client/WebsocketClient.ts, webapp/src/component/security/UserMenu/MenuHeader.tsx, webapp/src/views/administration/components/*, webapp/src/views/administration/AdministrationUsers.tsx
Replaced UserAccountModel usages with UserAccountAdministrationModel (schema now includes lastActivity); removed LLM prompt endpoint and schemas (LlmMessage, LlmParams, PromptResult); websocket actor.data switched to SimpleUserAccountModel; admin UI refactored to use AdministrationUserItem.
E2E tests & helpers
e2e/cypress/common/administration.ts, e2e/cypress/e2e/administration/*.ts, e2e/cypress/support/dataCyType.d.ts
Split/renamed navigation helpers (visitAdministrationvisitAdministrationOrganizations, added visitAdministrationUsers), added administration-user-activity data-cy literal, updated selectors, and added test asserting user's lastActivity after forced-date activity.
Tests
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt
Added cleanup for forced date and a test verifying lastActivity is returned and updated after creating project activity.
Docs
DEVELOPMENT.md
Documentation-only editorial and formatting updates; added examples and minor clarifications.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Controller as AdministrationController
  participant Service as UserAccountService
  participant Repo as UserAccountRepository
  participant Assembler as UserAccountAdministrationModelAssembler

  Client->>Controller: GET /v2/administration/users
  Controller->>Service: findAllWithDisabledPaged(pageable, search)
  Service->>Repo: findAllWithDisabledPaged(pageable, search)
  Repo-->>Service: Page<UserAccountAdministrationView> (includes lastActivity)
  Service-->>Controller: Page<UserAccountAdministrationView>
  Controller->>Assembler: toModel(view) for each item
  Assembler-->>Controller: UserAccountAdministrationModel (avatar links, lastActivity)
  Controller-->>Client: PagedModel<UserAccountAdministrationModel>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • Repository query CTE/join producing lastActivity (correctness, indices, performance).
    • Assembler mapping (avatar links, nullable role defaulting, lastActivity propagation).
    • Frontend generated types and websocket payload typing changes.
    • E2E date-forcing and timing around lastActivity assertions.

Possibly related PRs

Suggested reviewers

  • JanCizmar
  • ZuzanaOdstrcilova

Poem

🐰 I hopped through lines of code tonight,
I stamped the clock to mark who’s bright.
Admin lists now show the last small race,
Old prompts gone, new timestamps in place.
Hop, review, and nibble on the trace!

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: implementing user last activity tracking and display throughout the administration interface.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 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 bcc8011 and 56ee88f.

📒 Files selected for processing (1)
  • backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-07T14:36:39.331Z
Learnt from: Anty0
Repo: tolgee/tolgee-platform PR: 3263
File: backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.kt:279-290
Timestamp: 2025-10-07T14:36:39.331Z
Learning: In OrganizationRoleService in backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.kt, the `removeOrDeactivateUser` method intentionally has two distinct paths: if a user can be removed, it calls `removeUserForReal` (which removes organization roles, project permissions, and evicts caches); if a user is managed by the organization and cannot be removed, it calls `userAccountService.disable(userId)` without any cleanup. This design is intentional—disabling should be reversible so the user stays unmodified (keeping their organization roles and project permissions intact), allowing them to be re-enabled if they return to the organization.

Applied to files:

  • backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
DEVELOPMENT.md (3)

80-86: New "API schema changes" section is helpful but consider clarifying the workflow.

The new documentation is clear and useful for developers who modify API schemas. Consider whether you want to add a note about regenerating the schema after significant API model changes (e.g., adding new fields like lastActivity to administration models). This would help developers avoid missing the schema sync step when adding new features.


82-82: Minor wording suggestion for clarity.

The phrase "invoke the webapp schema script" reads slightly awkwardly. Consider rephrasing to "run the webapp schema script" for consistency with other command instructions in the document.


115-115: Use more formal wording in the instructions.

Replace "To fix Prettier issues" with "To resolve Prettier issues" for more formal and consistent documentation tone.

webapp/src/views/administration/AdministrationUsers.tsx (1)

82-83: MUI color prop: use text.secondary (v5), not textSecondary.

Update Typography color to the v5 token to avoid deprecation warnings.

-<Typography variant="body2" color="textSecondary">
+<Typography variant="body2" color="text.secondary">
backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountAdministrationModel.kt (1)

7-7: Prefer Instant/OffsetDateTime over Date in public API.

Using java.time types is clearer and timezone‑safe; Jackson maps them well. Consider switching lastActivity to Instant (or OffsetDateTime).

-import java.util.Date
+import java.time.Instant
@@
-  val lastActivity: Date?,
+  val lastActivity: Instant?,

Changing the type will cascade to the assembler and frontend schema; confirm compatibility before applying.

Also applies to: 20-21

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

7-18: Be aware of ByteArray in data class.

The totpKey property uses ByteArray? (line 18), which relies on reference equality rather than structural equality in Kotlin data classes. This can lead to unexpected behavior with equals(), hashCode(), and copy() if instances are compared or used in collections. Since this is a view/DTO, it's likely acceptable, but consider using a different type (e.g., List<Byte> or a wrapper) if equality semantics become important.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99cbd12 and 9881594.

📒 Files selected for processing (10)
  • DEVELOPMENT.md (12 hunks)
  • backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationController.kt (4 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountAdministrationModel.kt (2 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountAdministrationModelAssembler.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountModelAssembler.kt (0 hunks)
  • backend/data/src/main/kotlin/io/tolgee/dtos/queryResults/UserAccountAdministrationView.kt (1 hunks)
  • backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt (3 hunks)
  • backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt (2 hunks)
  • webapp/src/service/apiSchema.generated.ts (3 hunks)
  • webapp/src/views/administration/AdministrationUsers.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountModelAssembler.kt
🧰 Additional context used
🧬 Code graph analysis (1)
webapp/src/service/apiSchema.generated.ts (1)
webapp/src/service/billingApiSchema.generated.ts (1)
  • components (218-1508)
🪛 LanguageTool
DEVELOPMENT.md

[grammar] ~82-~82: Ensure spelling is correct
Context: ... to have backend running and invoke the webapp schema script to update frontend: ```s...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~115-~115: Consider using a different verb for a more formal wording.
Context: ...ins for VS Code, Idea, or WebStorm. To fix Prettier issues and check everything is...

(FIX_RESOLVE)

🔇 Additional comments (10)
webapp/src/service/apiSchema.generated.ts (3)

6243-6257: No issues found — lastActivity is already correctly handled.

The codebase already implements the safety pattern requested: it checks for undefined values (line 83 in AdministrationUsers.tsx), falls back to a localized translation for missing data, and correctly parses the ISO date string via new Date(). No numeric assumptions exist. The type is correctly defined as optional string with date-time format.


6948-6949: No action required - changes are already correctly implemented.

The single consumer of the getUsers endpoint (AdministrationUsers.tsx line 36) already correctly handles the PagedModelUserAccountAdministrationModel response type and properly reads the lastActivity field from UserAccountAdministrationModel (lines 83, 86). The typing is automatically inferred through the generic useApiQuery hook, and the schema is correctly defined. No updates are needed.

Likely an incorrect or invalid review comment.


4068-4073: Embedded users type alignment looks correct.

PagedModelUserAccountInProjectModel.embedded.users now points to UserAccountInProjectModel[]. Sanity-check affected UI/DTO mappers.

backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationController.kt (2)

51-59: Wiring to new admin view/model is correct; auth tightened.

Assembler/typed PagedResourcesAssembler updates align with the service/repository return type; adding @RequiresSuperAuthentication to list endpoints is appropriate.

Also applies to: 86-89


144-148: Minor doc polish looks good.

Updated summary text improves clarity.

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

570-575: Return type switch verified as complete and consistent.

Verification confirms the Page return type is properly implemented across the entire call chain:

  • Repository method returns Page
  • Service method returns Page
  • Controller correctly handles the new type

No lingering Page expectations found for this method; all call sites are properly aligned with the type change.

webapp/src/views/administration/AdministrationUsers.tsx (1)

86-90: Translation keys are missing from i18n files; refactor suggestion doesn't address root issue.

The keys administration_user_no_activity and administration_user_last_activity do not exist in webapp/src/i18n/en.json (or other locale files). The current code at lines 83–88 already references these exact keys. The suggested refactor uses the same non-existent keys, merely wrapping them in an IIFE without solving the underlying problem.

Additionally, the code already guards against missing lastActivity (!u.lastActivity check), and the API schema confirms lastActivity is optional and provided as ISO 8601 when present. The suggested defensive parsing adds complexity without addressing the missing translations.

Action: First, add the missing translation keys to all locale files in webapp/src/i18n/. Then reassess whether additional defensive parsing is necessary (it likely is not, given the existing guard clause and optional API field).

Likely an incorrect or invalid review comment.

backend/api/src/main/kotlin/io/tolgee/hateoas/userAccount/UserAccountAdministrationModelAssembler.kt (2)

11-17: LGTM! Clean HATEOAS assembler structure.

The component follows Spring HATEOAS best practices with proper type parameters and dependency injection.


18-33: No issues found. The role defaulting behavior is correct and intentional.

The implementation defaults null role to UserAccount.Role.USER consistently. The UserAccount.role field is defined as nullable (Role? = Role.USER) without database constraints, making null values theoretically possible. The defensive defaulting in the assembler aligns with the entity's own default value and is replicated across multiple assemblers (UserAccountAdministrationModelAssembler and PrivateUserAccountModelAssembler), indicating this is an established pattern in the codebase. This approach is safe and appropriate.

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

19-22: LGTM! Last activity field properly added.

The new lastActivity field is correctly typed as nullable Date?, which aligns with the PR objective to track user last activity.

@JanCizmar JanCizmar linked an issue Oct 22, 2025 that may be closed by this pull request
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 for working on this! 🙏

I have checked it fast and I have few points.

  • I don't think that the feature request specified the need for frontend. However, it's nice to have.
  • We need to add backend test
  • we need to add e2e tests

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
webapp/src/websocket-client/WebsocketClient.ts (1)

85-93: Don’t drop subscriptions on disconnect — breaks resubscribe after reconnect.

Clearing the array via removeSubscription makes resubscribe() a no-op on the next connect. Keep entries and only null out runtime handles.

Apply:

     const onDisconnect = function () {
       connected = false;
-      subscriptions.forEach((s) => {
-        s.unsubscribe = undefined;
-        s.id = undefined;
-        removeSubscription(s);
-      });
+      // Keep subscription records so onConnected() can resubscribe them.
+      subscriptions.forEach((s) => {
+        s.unsubscribe = undefined;
+        s.id = undefined;
+      });
       options.onConnectionClose?.();
     };
webapp/src/views/administration/components/OptionsButton.tsx (1)

93-99: Use currentTarget for menu anchor; target can be the SVG path.

Using event.target may anchor to a child element and break positioning.

-<IconButton
-  onClick={(e) => setAnchor(e.target as HTMLElement)}
+<IconButton
+  onClick={(e) => setAnchor(e.currentTarget as HTMLElement)}
   data-cy="administration-user-menu"
 >
   <DotsVertical />
 </IconButton>

Optional a11y polish:

-<IconButton
+<IconButton
+  aria-haspopup="menu"
+  aria-expanded={Boolean(anchor)}
+  aria-controls="administration-user-menu"

And give Menu an id:

-<Menu
+<Menu id="administration-user-menu"
🧹 Nitpick comments (1)
webapp/src/websocket-client/WebsocketClient.ts (1)

50-57: Typed parsing is effectively “any” — consider a minimal guard.

We parse JSON and cast to Message, then downcast to any. A tiny improvement: type it as WebsocketEvent and narrow at call site to retain some structure without big changes.

-const parsed = JSON.parse(message.body) as Message;
-subscription.callback(parsed as any);
+const parsed = JSON.parse(message.body) as WebsocketEvent<unknown>;
+subscription.callback(parsed as any); // retain current behavior while keeping event shape
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9881594 and bc4f01f.

📒 Files selected for processing (5)
  • webapp/src/component/security/UserMenu/MenuHeader.tsx (1 hunks)
  • webapp/src/views/administration/AdministrationUsers.tsx (3 hunks)
  • webapp/src/views/administration/components/OptionsButton.tsx (1 hunks)
  • webapp/src/views/administration/components/RoleSelector.tsx (1 hunks)
  • webapp/src/websocket-client/WebsocketClient.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • webapp/src/views/administration/AdministrationUsers.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
webapp/src/views/administration/components/RoleSelector.tsx (2)
webapp/src/service/apiSchema.generated.ts (1)
  • components (1072-6349)
webapp/src/component/UserAccount.tsx (1)
  • User (10-15)
🔇 Additional comments (4)
webapp/src/component/security/UserMenu/MenuHeader.tsx (1)

6-7: LGTM — type swap aligns with admin model.

Union still satisfies usages (id, name, avatar) in this component.

webapp/src/views/administration/components/OptionsButton.tsx (1)

12-12: LGTM — local alias updated to administration model.

Matches administration endpoints used below.

webapp/src/views/administration/components/RoleSelector.tsx (1)

10-12: LGTM — roles and user types now sourced from administration model.

The satisfies Role usage keeps literals aligned with the union.

If server roles ever change, update both the OpenAPI schema and these literals together.

webapp/src/websocket-client/WebsocketClient.ts (1)

193-200: Narrow actor type to prevent admin field exposure — no internal impact but improves API safety.

Current code has no consumers accessing actor or actor.data fields. However, the exported WebsocketEvent type exposes UserAccountAdministrationModel including admin-only fields (disabled, deleted, mfaEnabled, etc.). This is a design-level API exposure risk.

The proposed refactor is safe and recommended:

+type ActorUser = Pick<
+  components['schemas']['UserAccountAdministrationModel'],
+  'id' | 'username' | 'name' | 'avatar'
+>;
 export type WebsocketEvent<Data> = {
   activityId: number;
   actor: {
     type: 'user';
-    data: components['schemas']['UserAccountAdministrationModel'];
+    data: ActorUser;
   };
   data: Data;
 };

This change has zero runtime or internal compilation impact (verified: no code accesses actor.data beyond these fields).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
webapp/src/views/administration/AdministrationUsers.tsx (1)

83-105: Consider adding explicit spacing between primary and secondary text rows.

The nested Box structure creates a two-row layout, but relies on default Typography margins for spacing. While this likely works, adding explicit spacing (e.g., gap on the outer Box or marginTop on the secondary Box) would provide more predictable and maintainable layout control.

Example refinement:

-                <Box>
+                <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
                   <Box>
                     <Typography variant="body1">

Alternatively, add margin to the secondary text box:

                   </Box>
-                  <Box>
+                  <Box sx={{ mt: 0.5 }}>
                     <Typography
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc4f01f and f21a136.

📒 Files selected for processing (5)
  • e2e/cypress/common/administration.ts (1 hunks)
  • e2e/cypress/e2e/administration/base.cy.ts (6 hunks)
  • e2e/cypress/e2e/administration/customerDebug.cy.ts (2 hunks)
  • e2e/cypress/support/dataCyType.d.ts (1 hunks)
  • webapp/src/views/administration/AdministrationUsers.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
e2e/cypress/common/administration.ts (1)
e2e/cypress/common/shared.ts (1)
  • gcy (28-29)
e2e/cypress/e2e/administration/customerDebug.cy.ts (2)
e2e/cypress/common/administration.ts (2)
  • visitAdministrationUsers (8-10)
  • getUserListItem (12-16)
e2e/cypress/common/shared.ts (1)
  • gcy (28-29)
e2e/cypress/e2e/administration/base.cy.ts (2)
e2e/cypress/common/administration.ts (3)
  • visitAdministrationOrganizations (4-6)
  • visitAdministrationUsers (8-10)
  • getUserListItem (12-16)
e2e/cypress/common/apiCalls/common.ts (2)
  • forceDate (529-531)
  • releaseForcedDate (533-535)
🔇 Additional comments (11)
e2e/cypress/support/dataCyType.d.ts (1)

90-90: LGTM!

The new data-cy attribute for the user activity feature is properly placed among other administration entries and follows the existing naming convention.

e2e/cypress/e2e/administration/customerDebug.cy.ts (3)

9-9: LGTM!

The refactor from visitAdministration to visitAdministrationUsers provides more specific navigation and aligns with this test's focus on user-related administration actions.

Also applies to: 18-18


26-26: LGTM!

The debug account button click is correctly implemented using the updated helper functions.

Also applies to: 34-34


38-38: Good addition to verify project creation.

The assertion confirms the action succeeded, improving test reliability.

e2e/cypress/common/administration.ts (2)

2-2: LGTM!

The refactor to provide separate visitAdministrationOrganizations and visitAdministrationUsers functions improves clarity and makes test navigation more explicit.

Also applies to: 4-10


12-16: Improved selector specificity.

The updated implementation first targets elements with data-cy="administration-users-list-item" before searching for the userName, reducing the risk of false positives if the username appears elsewhere on the page.

e2e/cypress/e2e/administration/base.cy.ts (3)

3-9: LGTM!

The new imports support the user last activity test and are all properly utilized.


21-22: LGTM!

The navigation updates correctly map tests to their appropriate administration sections (organizations vs. users), improving test clarity.

Also applies to: 52-52, 61-61, 70-70, 98-98


80-95: Consider explicit page reload and verify timezone assumptions.

The test logic is sound, but there are a few considerations:

  1. Indirect reload (Line 90): The comment indicates a reload, but clicking the menu item is an indirect navigation. Consider using cy.reload() or re-calling visitAdministrationUsers() for more explicit and robust page reloading.

  2. Timezone/locale sensitivity (Line 93): The assertion expects "Last activity on August 28, 2023 at 2:00 AM" which assumes a specific timezone and time format. Verify that the test environment has consistent timezone settings, or consider using a more flexible assertion pattern.

  3. Activity recording timing: There's no explicit wait between createProject (lines 86-89) and reloading (line 90). If activity recording is asynchronous, consider adding cy.wait() or cy.waitForDom() to ensure the activity is persisted before reloading.

webapp/src/views/administration/AdministrationUsers.tsx (2)

2-2: LGTM: Imports and hooks are correctly added for the feature.

The new imports (useDateFormatter, Material-UI components) and hooks are necessary for displaying the last activity date in a formatted, localized manner.

Also applies to: 4-11, 56-57


95-102: Add validation for empty or invalid date strings.

The current check !u.lastActivity handles null/undefined but doesn't guard against empty strings or malformed date strings. If u.lastActivity is "", it would pass the truthy check but new Date("") creates an Invalid Date, potentially causing formatDate to fail or display unexpected output.

Apply this diff to add robust date validation:

-                      {!u.lastActivity
+                      {!u.lastActivity || isNaN(new Date(u.lastActivity).getTime())
                         ? t('administration_user_no_activity')
                         : t('administration_user_last_activity', {
                             date: formatDate(new Date(u.lastActivity), {

Likely an incorrect or invalid review comment.

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

🧹 Nitpick comments (2)
e2e/cypress/e2e/administration/base.cy.ts (1)

86-90: Consider adding explicit waits for async operations.

After creating the project (line 86-89) and reloading the view (line 90), there are no explicit waits to ensure the operations complete. While Cypress has built-in retry logic, adding explicit waits can make the test more robust and clear about its dependencies.

Consider adding waits to ensure operations complete:

  createProject({
    name: 'just.to.record.activity',
    languages: [{ name: 'cs', originalName: 'čj', tag: 'cs' }],
- });
+ }).then(() => {
+   cy.wait(500); // Ensure activity is recorded
+ });
  gcy('settings-menu-item').contains('Users').click();
+ gcy('administration-users-list-item').should('be.visible'); // Ensure page loaded
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt (1)

90-90: Redundant login call.

The explicit loginAsUser(userAccount!!.username) is unnecessary because:

  1. userAccount is already set to testData.admin from the @BeforeEach setup
  2. The previous performAuthGet at line 81 already logged in as admin via loginAsAdminIfNotLogged()
  3. performAuthPost at line 97 won't re-login since a user is already authenticated

Remove line 90 to simplify the test:

     val organization = dbPopulator.createOrganization("just.to.record.activity", userAccount!!)
-    loginAsUser(userAccount!!.username)
     val projectRequest = CreateProjectRequest(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f21a136 and af6393f.

📒 Files selected for processing (2)
  • backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt (3 hunks)
  • e2e/cypress/e2e/administration/base.cy.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
e2e/cypress/e2e/administration/base.cy.ts (3)
e2e/cypress/common/administration.ts (3)
  • visitAdministrationOrganizations (4-6)
  • visitAdministrationUsers (8-10)
  • getUserListItem (12-16)
e2e/cypress/common/apiCalls/common.ts (2)
  • forceDate (529-531)
  • releaseForcedDate (533-535)
e2e/cypress/common/shared.ts (1)
  • gcy (28-29)
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt (1)
backend/testing/src/main/kotlin/io/tolgee/testing/AuthorizedControllerTest.kt (4)
  • performAuthGet (130-133)
  • loginAsUser (59-62)
  • loginAsUser (64-67)
  • performAuthPost (122-128)
🔇 Additional comments (2)
e2e/cypress/e2e/administration/base.cy.ts (2)

3-9: LGTM! Import updates align with the new test and refactored navigation helpers.

The new imports support the last activity test (date forcing, project creation) and the more granular navigation helpers improve test organization.

Also applies to: 21-22


52-52: LGTM! Correct use of specialized navigation helpers.

The migration from visitAdministration() to context-specific helpers (visitAdministrationOrganizations() and visitAdministrationUsers()) improves test clarity and maintainability.

Also applies to: 61-61, 70-70, 98-98

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
e2e/cypress/e2e/administration/base.cy.ts (1)

25-25: Consider using a nested describe block for scoped setup/teardown.

The current pattern of matching test titles with Cypress.currentTest.title is fragile—renaming the test will silently break the date forcing setup.

Refactor to use a nested describe block with isolated hooks:

-const activityTest = "can display user's last activity";
-
 describe('Administration', () => {
   beforeEach(() => {
-    if (Cypress.currentTest.title === activityTest) {
-      forceDate(new Date('2023-08-28T02:30').getTime()); // app displays local time, set this one in local timezone
-    }
     administrationTestData.clean();
     administrationTestData.generate().then((res) => {});
     login('admin@admin.com');
   });

   afterEach(() => {
-    if (Cypress.currentTest.title === activityTest) {
-      releaseForcedDate();
-    }
     administrationTestData.clean();
     setProperty('authentication.userCanCreateOrganizations', true);
   });

   // ... existing tests ...

+  describe('User Activity', () => {
+    beforeEach(() => {
+      forceDate(new Date('2023-08-28T02:30').getTime()); // app displays local time, set this one in local timezone
+    });
+
+    afterEach(() => {
+      releaseForcedDate();
+    });
+
-    it(activityTest, () => {
+    it("can display user's last activity", () => {
       visitAdministrationUsers();
       // ... rest of test
     });
+  });
 });

Also applies to: 29-31, 38-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 af6393f and 9670118.

📒 Files selected for processing (1)
  • e2e/cypress/e2e/administration/base.cy.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
e2e/cypress/e2e/administration/base.cy.ts (3)
e2e/cypress/common/apiCalls/common.ts (2)
  • forceDate (529-531)
  • releaseForcedDate (533-535)
e2e/cypress/common/administration.ts (3)
  • visitAdministrationOrganizations (4-6)
  • visitAdministrationUsers (8-10)
  • getUserListItem (12-16)
e2e/cypress/common/shared.ts (1)
  • gcy (28-29)
🔇 Additional comments (3)
e2e/cypress/e2e/administration/base.cy.ts (3)

3-9: LGTM! Imports support the new activity test.

All new imports (createProject, forceDate, releaseForcedDate, setProperty) are properly utilized in the test setup and the new activity test case.


60-60: LGTM! Good refactoring to specialized helpers.

Replacing the generic navigation helper with visitAdministrationOrganizations() and visitAdministrationUsers() makes the test intent clearer and improves maintainability.

Also applies to: 69-69, 78-78, 106-106


88-103: LGTM! Test logic correctly verifies activity tracking.

The test properly validates the user activity feature by:

  1. Confirming initial "No activity yet" state
  2. Triggering activity via project creation
  3. Reloading the view and verifying the timestamp appears

The navigation-based reload (line 99) appropriately tests the real user flow.

Copy link
Collaborator

@Anty0 Anty0 left a comment

Choose a reason for hiding this comment

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

Good job, looking good. I have some notes, but they should all be easy to fix ^^

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 055045a and cab9feb.

📒 Files selected for processing (1)
  • backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-07T14:36:39.331Z
Learnt from: Anty0
Repo: tolgee/tolgee-platform PR: 3263
File: backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.kt:279-290
Timestamp: 2025-10-07T14:36:39.331Z
Learning: In OrganizationRoleService in backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationRoleService.kt, the `removeOrDeactivateUser` method intentionally has two distinct paths: if a user can be removed, it calls `removeUserForReal` (which removes organization roles, project permissions, and evicts caches); if a user is managed by the organization and cannot be removed, it calls `userAccountService.disable(userId)` without any cleanup. This design is intentional—disabling should be reversible so the user stays unmodified (keeping their organization roles and project permissions intact), allowing them to be re-enabled if they return to the organization.

Applied to files:

  • backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt
🧬 Code graph analysis (1)
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt (1)
backend/testing/src/main/kotlin/io/tolgee/testing/AuthorizedControllerTest.kt (4)
  • performAuthGet (130-133)
  • loginAsUser (59-62)
  • loginAsUser (64-67)
  • performAuthPost (122-128)
🔇 Additional comments (2)
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt (2)

4-4: LGTM!

The new imports are necessary for the test implementation and are all properly used.

Also applies to: 6-6, 17-17, 23-23


37-40: LGTM!

The @AfterEach cleanup ensures forcedDate is properly reset after each test, preventing state leakage between tests. This addresses the concerns from the previous review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
webapp/src/views/administration/components/AdministrationUserItem.tsx (1)

33-35: Add defensive coding for consistency with adjacent admin components.

The codebase uses user.name || user.username fallback patterns in adjacent administration components (OptionsButton.tsx, MemberItem.tsx). Line 34 lacks this fallback, making it inconsistent. While TypeScript types declare these fields as required, the team applies defensive coding elsewhere in the administration module—follow the same pattern here.

Suggested fix:

-            {user.name} | {user.username} <Chip size="small" label={user.id} />
+            {user.name || user.username} {user.name && user.username && '|'} {user.username} <Chip size="small" label={user.id} />

Or simpler, if name is display-preferred:

-            {user.name} | {user.username} <Chip size="small" label={user.id} />
+            {user.name ? `${user.name} | ${user.username}` : user.username} <Chip size="small" label={user.id} />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd1a88 and bcc8011.

📒 Files selected for processing (3)
  • e2e/cypress/e2e/administration/base.cy.ts (6 hunks)
  • webapp/src/views/administration/AdministrationUsers.tsx (2 hunks)
  • webapp/src/views/administration/components/AdministrationUserItem.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-03T08:38:27.934Z
Learnt from: Anty0
Repo: tolgee/tolgee-platform PR: 3248
File: backend/data/src/main/kotlin/io/tolgee/service/organization/OrganizationService.kt:157-157
Timestamp: 2025-10-03T08:38:27.934Z
Learning: The SUPPORTER role is read-only and should not be able to create organizations in the name of other users. When checking if a user can create organizations, use `isAdmin()` rather than `isSupporterOrAdmin()` to exclude SUPPORTER from this capability.

Applied to files:

  • e2e/cypress/e2e/administration/base.cy.ts
🧬 Code graph analysis (3)
webapp/src/views/administration/AdministrationUsers.tsx (1)
webapp/src/views/administration/components/AdministrationUserItem.tsx (1)
  • AdministrationUserItem (18-62)
e2e/cypress/e2e/administration/base.cy.ts (3)
e2e/cypress/common/administration.ts (3)
  • visitAdministrationOrganizations (4-6)
  • visitAdministrationUsers (8-10)
  • getUserListItem (12-16)
e2e/cypress/common/apiCalls/common.ts (2)
  • releaseForcedDate (533-535)
  • forceDate (529-531)
e2e/cypress/common/shared.ts (1)
  • gcy (28-29)
webapp/src/views/administration/components/AdministrationUserItem.tsx (4)
webapp/src/component/MfaBadge.tsx (1)
  • MfaBadge (34-53)
webapp/src/views/administration/components/DebugCustomerAccountButton.tsx (1)
  • DebugCustomerAccountButton (8-39)
webapp/src/views/administration/components/RoleSelector.tsx (1)
  • RoleSelector (14-68)
webapp/src/views/administration/components/OptionsButton.tsx (1)
  • OptionsButton (18-133)
🔇 Additional comments (4)
webapp/src/views/administration/AdministrationUsers.tsx (1)

3-3: Excellent refactoring that addresses previous feedback.

The extraction of user item rendering into the AdministrationUserItem component successfully addresses the suggestion from the previous review to split this into a separate component. This improves code organization and maintainability.

Also applies to: 9-9, 66-69

webapp/src/views/administration/components/AdministrationUserItem.tsx (3)

1-16: LGTM - Clean component structure and type definitions.

The imports, type alias, and props interface are well-organized and appropriate for the component's needs.


54-59: LGTM - Actions layout and props are correct.

The actions section properly renders all user-related controls with appropriate props and spacing.


43-50: Remove the defensive date parsing suggestion—the code is correct as-is.

The API schema explicitly defines lastActivity as string with format date-time, which means the backend guarantees valid ISO 8601 date strings. The useDateFormatter() hook safely uses Intl.DateTimeFormat to format dates, and new Date(string) correctly parses ISO 8601 strings. The existing null check (!user.lastActivity) already covers the only legitimate edge case. Adding defensive parsing would mask a backend contract violation rather than handle a real frontend issue.

Likely an incorrect or invalid review comment.

@zomp zomp requested a review from Anty0 November 4, 2025 16:27
Copy link
Collaborator

@Anty0 Anty0 left a comment

Choose a reason for hiding this comment

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

LGTM (except for the note on the deprecated property). The PR itself needs to wait until we extend the activity; otherwise, the lastActivity would lag behind when the user is not performing any of the tracked activities.

Comment on lines +13 to +15
@Deprecated("See `UserAccount` for details.")
/** @see [io.tolgee.model.UserAccount.thirdPartyAuthType] */
override val accountType: UserAccount.AccountType?,
Copy link
Collaborator

Choose a reason for hiding this comment

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

@JanCizmar What do you think about this? I wouldn't mark it as deprecated. If anything, the accountType is duplicated data in the database, but from the API perspective, I wouldn't stop providing the accountType – I wouldn't deprecate it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It comes from my possible misunderstanding of:

  /**
   * This property is redundant - it's value can be derived from other existing properties.
   * Kept for legacy reasons.

in backend/data/src/main/kotlin/io/tolgee/model/UserAccount.kt.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yup, from my perspective, it isn't really deprecated. It can be calculated from other columns rather than stored in its own database column—that's what the comment is trying to say.

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.

[API] Get user's last connection/interaction date/time

3 participants