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 shows formatted "last activity" with a new two-line user item layout.
  • Documentation

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

    • Public API schemas migrated to an administration-focused user model.
    • Administration endpoints tightened with stricter access control.
    • Public LLM prompt endpoint and related schemas removed.
  • Tests

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

Renames admin user models to UserAccountAdministrationModel (adds lastActivity), introduces UserAccountAdministrationView and assembler, updates repository/service/controller flows and access annotations to surface lastActivity, removes a public LLM prompt endpoint/schemas, updates frontend types/UI and websockets, and adjusts E2E tests and docs.

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
Added UserAccountAdministrationModel (new lastActivity: Date?) and UserAccountAdministrationModelAssembler (maps UserAccountAdministrationView→model, uses AvatarService); removed legacy UserAccountModelAssembler.
Data DTO & Repository
backend/data/src/main/kotlin/io/tolgee/dtos/queryResults/UserAccountAdministrationView.kt, backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt
New UserAccountAdministrationView DTO (includes lastActivity); findAllWithDisabledPaged now returns Page<UserAccountAdministrationView> and query computes/joins lastActivity and email verification.
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 changed to return Page<UserAccountAdministrationView>; controller updated to use administration assemblers/types and return PagedModel<UserAccountAdministrationModel>; added @RequiresSuperAuthentication to admin endpoints and minor text 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 in generated types, added lastActivity to schema, removed LLM prompt endpoint and related schemas (LlmMessage, LlmParams, PromptResult), updated websocket actor data type, and updated admin UI to format/display last activity.
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 test asserting user's lastActivity, adjusted DOM selectors, and added administration-user-activity data-cy literal.
Docs
DEVELOPMENT.md
Editorial and formatting updates, added examples and minor clarifications (documentation-only changes).
Tests
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt
Added test verifying lastActivity is returned and updated after creating project activity.

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

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • Anty0

Poem

🐰 I hopped through lines of code tonight,
I added timestamps, small and bright.
Old prompt doors closed, admin views now show —
Who wandered last where carrots grow.
Hop, review, and give a bite!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "User last activity" directly and accurately summarizes the primary change throughout the changeset. The entire pull request implements a feature to track and display user last activity timestamps, which is evident from the backend changes (adding lastActivity field to UserAccountAdministrationView, updating repositories and services), frontend updates (displaying formatted last activity in AdministrationUsers.tsx), schema generation, and comprehensive tests. The title is concise, clear, specific, and contains no vague terms or noise—a developer scanning the commit history would immediately understand that this PR adds user last activity tracking.
✨ 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 9670118 and 731eb8a.

📒 Files selected for processing (1)
  • backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.kt (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/administration/AdministrationControllerTest.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.

Comment on lines +264 to 283
with lastActivityCTE as (
select ar.authorId as authorId, max(ar.timestamp) as lastActivity
from ActivityRevision ar
group by ar.authorId
)
select new io.tolgee.dtos.queryResults.UserAccountAdministrationView(
userAccount.id,
userAccount.username,
userAccount.name,
case when ev is not null then coalesce(ev.newEmail, userAccount.username) else null end,
userAccount.avatarHash,
userAccount.accountType,
userAccount.role,
userAccount.isInitialUser,
userAccount.totpKey,
userAccount.deletedAt,
userAccount.disabledAt,
la.lastActivity
)
from UserAccount userAccount
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

CTE in HQL + paging: add explicit countQuery and verify Hibernate version.

The WITH lastActivityCTE pattern is concise, but:

  • Derived count queries can fail with CTEs; add an explicit countQuery.
  • HQL CTEs require Hibernate 6+; verify runtime version.
  • Ensure ActivityRevision has fields authorId and timestamp as used here.
-  @Query(
-    """
+  @Query(
+    value = """
       with lastActivityCTE as (
         select ar.authorId as authorId, max(ar.timestamp) as lastActivity 
         from ActivityRevision ar 
         group by ar.authorId
       )
       select new io.tolgee.dtos.queryResults.UserAccountAdministrationView(
         userAccount.id,
         userAccount.username,
         userAccount.name,
         case when ev is not null then coalesce(ev.newEmail, userAccount.username) else null end,
         userAccount.avatarHash,
         userAccount.accountType,
         userAccount.role,
         userAccount.isInitialUser,
         userAccount.totpKey,
         userAccount.deletedAt,
         userAccount.disabledAt,
         la.lastActivity
       )
       from UserAccount userAccount
       left join userAccount.emailVerification ev
       left join lastActivityCTE la on la.authorId = userAccount.id
       where ((lower(userAccount.name)
         like lower(concat('%', cast(:search as text),'%')) 
         or lower(userAccount.username) like lower(concat('%', cast(:search as text),'%'))) or cast(:search as text) is null)
         and userAccount.deletedAt is null
-  """,
-  )
+    """,
+    countQuery = """
+      with lastActivityCTE as (
+        select ar.authorId as authorId, max(ar.timestamp) as lastActivity 
+        from ActivityRevision ar 
+        group by ar.authorId
+      )
+      select count(userAccount)
+      from UserAccount userAccount
+      left join userAccount.emailVerification ev
+      left join lastActivityCTE la on la.authorId = userAccount.id
+      where ((lower(userAccount.name)
+        like lower(concat('%', cast(:search as text),'%')) 
+        or lower(userAccount.username) like lower(concat('%', cast(:search as text),'%'))) or cast(:search as text) is null)
+        and userAccount.deletedAt is null
+    """
+  )
   fun findAllWithDisabledPaged(
     search: String?,
     pageable: Pageable,
   ): Page<UserAccountAdministrationView>

For large ActivityRevision tables, add a composite index on (author_id, timestamp desc) to accelerate max(timestamp) per author.

Also applies to: 284-286, 292-296


🏁 Script executed:

#!/bin/bash
# Verify Hibernate version (CTE needs 6.x)
fd -a 'libs.versions.toml' -x cat {} | rg -n 'hibernate'
rg -n --glob '**/build.gradle*' 'hibernate-core|org.hibernate:hibernate-core'
# Confirm ActivityRevision fields used by the CTE
rg -n --type=kt -C3 'class\s+ActivityRevision\b'
rg -n --type=kt -C2 '\bauthorId\b|\btimestamp\b'

Length of output: 53


I'll generate a corrected script to properly search for Kotlin files and verify the necessary details:


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and inspect Hibernate version in build files
echo "=== Checking Hibernate version ==="
fd 'build.gradle' -x cat {} | grep -i hibernate
fd 'libs.versions.toml' -x cat {} | grep -i hibernate

echo "=== Finding ActivityRevision class ==="
fd '\.kt$' -x grep -l 'class ActivityRevision' {}

echo "=== Inspecting ActivityRevision class ==="
fd '\.kt$' -x grep -A 20 'class ActivityRevision' {}

echo "=== Checking for authorId and timestamp fields ==="
fd 'ActivityRevision.kt' -x cat {}

echo "=== Finding UserAccountRepository ==="
fd 'UserAccountRepository.kt' | head -5

Length of output: 6995



---

🏁 Script executed:

```shell
#!/bin/bash
# Find Hibernate version number
echo "=== Finding hibernateVersion value ==="
fd '(build.gradle|gradle.properties|libs.versions.toml)' -x grep -i 'hibernateVersion\s*=' {} | head -10

echo "=== Checking libs.versions.toml for hibernate ==="
fd 'libs.versions.toml' -x cat {} | grep -A2 -B2 'hibernate'

echo "=== Inspect UserAccountRepository.kt around the @Query ==="
fd 'UserAccountRepository.kt' -x sed -n '250,310p' {}

echo "=== Find countQuery usage pattern in repository ==="
fd 'Repository.kt' -x grep -l 'countQuery' {}

Length of output: 2533



---

🏁 Script executed:

```shell
#!/bin/bash
# Check countQuery examples in other repositories
echo "=== CountQuery usage examples ==="
fd 'Repository.kt' -x grep -B5 -A10 'countQuery' {} | head -60

echo "=== Check if UserAccountRepository has other countQuery examples ==="
fd 'UserAccountRepository.kt' -x grep -B3 -A8 'countQuery' {}

Length of output: 2478


Add mandatory countQuery to findAllWithDisabledPaged for CTE pagination support.

The review is correct. Verification confirms:

  • Hibernate 6.5.3.Final supports HQL CTEs (no version issue)
  • ActivityRevision has both required fields: authorId and timestamp
  • The method returns Page<UserAccountAdministrationView> with Pageable, requiring an explicit count query
  • Without countQuery, Hibernate auto-derivation fails with CTEs—confirmed by the established pattern in findUsersWithMinimalPermissions (same file, also uses CTE + explicit countQuery)

The proposed diff is necessary and should be applied. The composite index suggestion on (authorId, timestamp desc) remains optional for performance optimization.

🤖 Prompt for AI Agents
In backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt
around lines 264 to 283, the HQL query using a CTE lacks the required explicit
countQuery for the Pageable return type; add a corresponding countQuery that
mirrors the CTE and counts the userAccount rows (e.g., SELECT count(userAccount)
FROM ... using the same with lastActivityCTE) so Spring/Hibernate can compute
total pages correctly when using CTEs.

@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('[email protected]');
   });

   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.

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

2 participants