Skip to content

feat: Activity grouping - #2330

Draft
JanCizmar wants to merge 67 commits into
mainfrom
jancizmar/notifications-activity-grouping
Draft

feat: Activity grouping#2330
JanCizmar wants to merge 67 commits into
mainfrom
jancizmar/notifications-activity-grouping

Conversation

@JanCizmar

@JanCizmar JanCizmar commented May 30, 2024

Copy link
Copy Markdown
Member

What this PR does

Groups related consecutive activity revisions into activity groups, so the project activity feed can show "created 50 keys" instead of 50 separate rows, and adds a grouped activity view to the webapp at /projects/{id}/activity.

Scope change: this PR originally also contained a user-notification system (UserNotification, preferences, debouncing, bell). That was dropped — main has since shipped its own notification system — and this PR is now activity grouping only.

How grouping works

  • Each ActivityGroupType declares which ActivityTypes feed it and a matcher over the modified entities (entity class, revision type, changed props, allowed/denied values). One revision can land in several groups — e.g. COMPLEX_EDIT splits into EDIT_KEY_NAME + SET_BASE_TRANSLATION + REVIEW.
  • When a revision is stored, ActivityGrouper matches it against all group types and links it into the currently open group of that type (same project, author, matching string and branch). A group closes after 24 h of age or 2 h of inactivity.
  • Batch jobs: when chunk revisions get merged after job completion, their group links are merged into the surviving revision.
  • All 77 group types cover every groupable ActivityType. Not grouped (intentionally): hidden types (AUTOMATION, TASK_KEY_UPDATE, HARD_DELETE_LANGUAGE), enum values never set in code (ORDER_TRANSLATION, TRANSLATION_MEMORY_COPY_FROM_PROJECT), and activities that record no activity-logged entities (TRANSLATION_MEMORY_ENTRY_*, TRANSLATION_MEMORY_*_PROJECT*, BRANCH_DELETE).

API

  • GET /v2/projects/{id}/activity/groups — paged groups with author, timestamp, type, per-type data (counts or a specialized payload), filterable by type/author/language and branch (same semantics as the regular activity feed).
  • GET /v2/projects/{id}/activity/group-items/generic/{groupId} — paged raw modified entities of any group.
  • GET /v2/projects/{id}/activity/group-items/create-key/{groupId} — specialized items (key + base translation).
  • OpenAPI: per-activity-type schemas (ProjectActivity<Type>Model, ActivityGroup<Type>Model) are registered as standalone models; the base models stay generic.

Frontend

Grouped activity view with group cards (author, translated group-type label, count chip, timestamp) expandable into per-type content (created keys with base translations, created project languages) or a generic modified-entities table.

Notable internal changes

  • ModifiedEntitiesType is keyed by entity id once allocated (instance only pre-allocation). Pure instance keying recorded the same row twice when Hibernate yielded two instances in one transaction (e.g. branch merge after entityManager.clear()), causing duplicate-PK inserts.
  • ActivityRevision.baseLanguageId records the base language at change time (used by base/non-base translation matchers and the base-translation describer).
  • activity_modified_entity.additional_description + ActivityAdditionalDescriber extension point (used to store base translation text with key activity).
  • JOOQ added (grouping queries + SQL-side matchers); also added to the liquibase runtime classpath.

Testing

  • ActivityGroupsCreationTest — time-window grouping, complex-edit splitting
  • ProjectCreationGroupViewTest — group listing payloads
  • Existing activity/batch/branch-merge suites pass (batch finalizer + branch merges exercise the grouping path)

Follow-ups

  • Upload the new translation keys (activity_groups_type_*, project_activity_title, …) to the Tolgee platform
  • Branch selector in the grouped view UI (endpoint already supports branch)
  • Cypress e2e spec for the grouped view
  • Specialized expanded views for more group types where the generic table isn't enough

…-core

# Conflicts:
#	backend/data/src/main/resources/db/changelog/schema.xml
The amount of time spent solving made up problems instead of working on real problems...
…-core

# Conflicts:
#	backend/api/src/main/kotlin/io/tolgee/hateoas/InitialDataModel.kt
#	backend/app/src/main/resources/application.yaml
#	backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImageUploadController/V2ImageUploadControllerTest.kt
#	backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ProjectsController/V2ProjectsControllerTest.kt
#	backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ScreenshotController/KeyScreenshotControllerTest.kt
#	backend/app/src/test/kotlin/io/tolgee/initialUserCreation/LegacyMigrationTest.kt
#	backend/data/src/main/kotlin/io/tolgee/activity/ActivityService.kt
#	backend/data/src/main/kotlin/io/tolgee/activity/views/ActivityViewByRevisionsProvider.kt
#	backend/data/src/main/kotlin/io/tolgee/activity/views/ProjectActivityViewByPageableProvider.kt
#	backend/data/src/main/kotlin/io/tolgee/batch/BatchJobActionService.kt
#	backend/data/src/main/kotlin/io/tolgee/batch/BatchJobConcurrentLauncher.kt
#	backend/data/src/main/kotlin/io/tolgee/batch/ProgressManager.kt
#	backend/data/src/main/kotlin/io/tolgee/batch/events/OnBatchJobStatusUpdated.kt
#	backend/data/src/main/kotlin/io/tolgee/dialects/postgres/CustomPostgreSQLDialect.kt
#	backend/data/src/main/kotlin/io/tolgee/dtos/ComputedPermissionDto.kt
#	backend/data/src/main/kotlin/io/tolgee/dtos/request/key/KeyScreenshotDto.kt
#	backend/data/src/main/kotlin/io/tolgee/model/DismissedAnnouncement.kt
#	backend/data/src/main/kotlin/io/tolgee/model/UserAccount.kt
#	backend/data/src/main/kotlin/io/tolgee/repository/LanguageRepository.kt
#	backend/data/src/main/kotlin/io/tolgee/repository/UserAccountRepository.kt
#	backend/data/src/main/kotlin/io/tolgee/service/LanguageService.kt
#	backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt
#	backend/data/src/main/kotlin/io/tolgee/service/security/UserAccountService.kt
#	backend/data/src/main/resources/db/changelog/schema.xml
#	backend/development/src/main/kotlin/io/tolgee/controllers/internal/e2eData/NotificationsE2eDataController.kt
#	backend/testing/src/main/kotlin/io/tolgee/fixtures/scopeAssert.kt
#	backend/testing/src/main/kotlin/io/tolgee/fixtures/statusExpectations.kt
…-core

# Conflicts:
#	backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/InitialDataController.kt
#	backend/data/src/main/kotlin/io/tolgee/repository/LanguageRepository.kt
#	backend/data/src/main/resources/db/changelog/schema.xml
…-core

# Conflicts:
#	backend/data/src/main/kotlin/io/tolgee/model/UserAccount.kt
@github-actions github-actions Bot added the stale label Jul 9, 2024
@tolgee tolgee deleted a comment from github-actions Bot Jul 22, 2024
@JanCizmar JanCizmar added enhancement New feature or request roadmap item Items that are automatically added to the Roadmap and removed stale labels Jul 22, 2024
JanCizmar added 10 commits July 29, 2024 13:22
- revert PropertyModification to non-generic (main's PropertyModifications
  map is built around it; generic form crashed kapt)
- keep instance-keyed ModifiedEntitiesType; adapt LanguageStatsListener and
  QaActivityListener to entity-instance keys
- move user-notification cleanup into ProjectHardDeletingService
- align permission service/dto signatures with main (suggestLanguageIds,
  non-null base permission handling, wrapper-returning repository)
- rename branch NotificationsTestData to UserNotificationsTestData (main
  introduced its own NotificationsTestData)
- triage new ActivityTypes into the notification listener ignore list
- ktlint format
…ifications

Main shipped its own in-app notification system (Notification entity,
settings, email channel, top-bar popup) while this branch was dormant.
Remove the branch's parallel UserNotification/NotificationPreferences
stack — entities, services, dispatch/debounce, REST controllers, test
data, webapp views and DB changesets. Activity grouping stays.
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 354d801a-eca4-4b89-8f08-73d8df7d05c5

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jancizmar/notifications-activity-grouping

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

- add group types for key soft-delete/restore/hard-delete, character limit,
  labels, tasks, glossaries, translation memories, suggestions, AI prompts,
  branches and QA issues
- fix copy-pasted source activity types on webhook/content-delivery groups
- merge activity-group links into the surviving revision when batch chunk
  revisions are merged (FK violation broke batch job finalization)
- OpenAPI: keep base ProjectActivityModel generic; register per-activity-type
  schemas as standalone models and type modified entities as arrays
- regenerate frontend API schema; fix React 18 children typings in group
  components

Not grouped (intentionally): hidden types (AUTOMATION, TASK_KEY_UPDATE,
HARD_DELETE_LANGUAGE), enum values never set in code (ORDER_TRANSLATION,
TRANSLATION_MEMORY_COPY_FROM_PROJECT), activities recording no logged
entities (TRANSLATION_MEMORY_ENTRY_*/\*_PROJECT*, BRANCH_DELETE)
- key modified-entity map by entity id once allocated (instance only as a
  fallback) — pure instance keying recorded the same row twice when Hibernate
  yielded multiple instances in one transaction (e.g. branch merge after
  entityManager.clear()), breaking inserts with duplicate PKs; set entityId
  on records created before id allocation so listeners see the real id
- revert json-unit to 2.28.0 (main's version) and drop the custom number
  comparator — 2.38 compares BigDecimal scale strictly, breaking stats tests
- add JOOQ to the liquibase runtime classpath (migration check loads entity
  classes referencing ActivityGroupType matchers)
- add project_activity_title translation key used by the ActivityView
- ktlint: line lengths, wildcard imports, jakarta @transient in Permission
…s, grouped activity UI

- record branch_id on activity groups (split per branch at grouping time)
  and filter the /groups endpoint by branch like the regular activity feed
- record baseLanguageId on activity revisions again (lost when main's
  ActivityRevisionInitializer replaced setProject) — fixes base/non-base
  translation matching and base-translation describing
- generic group model provider: per-entity-class counts for every group
  type without a bespoke provider, plus a generic paged group-items
  endpoint returning raw modified entities
- rebuild the grouped activity view: group cards with author, translated
  group-type labels, count chips and expandable per-type or generic item
  tables; translated labels for all 77 group types
@JanCizmar JanCizmar changed the title feat: Enhanced activity & notifications feat: Activity grouping Jul 11, 2026
Entity hashCode is id-based and mutates when the id gets assigned, stranding
instance-keyed map entries in stale hash buckets — the same row was then
recorded again under its id key, breaking activity inserts with duplicate
PKs (project stats, history, language deletion, usage reporting tests).
Key such entries by an EntityIdentity wrapper with identity hashCode and
migrate them to the id key once the id is known.

Also restore main's expected decimal formats in ProjectsControllerTest.
Replace the per-revision ActivityCompact list with the grouped activity
cards (infinite scroll over /activity/groups, date separators kept).
The date separator now spans all implicit columns instead of the removed
3-column grid. Dashboard e2e spec asserts group cards and expanded
create-key items instead of compact rows and the detail dialog.
Buffer comes from the global polyfill on main; importing the 'buffer'
package (not a direct dependency) broke avatar rendering in production
builds — every avatar crashed into the global error dialog, which covered
the page and failed the project-list E2E specs.
The branch made Permission.viewLanguages/translateLanguages/stateChangeLanguages
lazy with a query-side id prefetch. Assemblers outside the fetching query hit
LazyInitializationException, so listing projects with stats 500'd — the global
error dialog then covered the page and broke the project-list E2E specs.
Value matchers built conditions against the whole modifications json instead
of the matched property, and compared json = json which postgres has no
operator for. Any group type with allowed/denied values (REVIEW,
SET_TRANSLATION_STATE, batch clear...) made the whole groups listing fail
with a bad-grammar error. Match on the property attribute, compare as jsonb,
and treat JSON null as null. Also match soft-deleted languages in
DELETE_LANGUAGE — the hard delete happens asynchronously later.
Record how the request was authenticated (UI / project API key / PAT) on each
activity revision and aggregate the distinct origins and source activity types
per group. The feed now shows an origin chip on groups touched over the API and
attributes groups that emerged from a compound action — e.g. translations set
while creating a key render as 'Set translations ... as part of: Created key'.

Redesign the group cards along the way: semantic type icon (green for creating,
red for deleting), avatar, translated label, count chip, relative time with an
exact-datetime tooltip.
- IMPORT had no matcher, so its expanded list was always empty — match the
  keys and translations the import created
- resolve the describing relations of generic group items, so a translation
  row shows its key name and language instead of a bare entity id
- render modifications as old → new, formatting entity references by name,
  timestamps as dates and arrays as lists, instead of dumping field names
- fall back to the deleted entity's previous name when it no longer has
  describing data
The dashboard no longer renders per-revision compact rows, so the specs that
asserted them (labels, translation labels, suggestions, outdated) now assert
group cards by type and expand them to check the affected items. Drop the
unused E2ActivityChecker compound.

Also show the entity's own text under the item title, so a suggestion row
shows the suggested translation next to the key it belongs to.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request roadmap item Items that are automatically added to the Roadmap

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants