Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
bc0f83c
feat: notifications core service draft
cyyynthia Nov 14, 2023
1ec0527
chore: cleanup batch job internal events
cyyynthia Nov 19, 2023
eefcec0
feat: core user notification dispatch
cyyynthia Nov 19, 2023
c4a183c
chore: lint
cyyynthia Nov 19, 2023
fcecce7
feat: finish user notification dispatch logic
cyyynthia Nov 20, 2023
bd8bfab
fix: make fetching translation to language maps work again
cyyynthia Nov 21, 2023
a850303
tests: test notification dispatching
cyyynthia Nov 24, 2023
3f10a64
Merge remote-tracking branch 'origin/main' into cynthia/notifications…
cyyynthia Nov 24, 2023
3b31359
feat: redo notifications the right way this time
cyyynthia Dec 3, 2023
397dd08
chore(deps): solve ij classpath issues with json-unit & assertj
cyyynthia Dec 3, 2023
9f1c7cd
feat: debounce key creation and initial string creations
cyyynthia Dec 3, 2023
c97c9e9
feat: notification preference configuration
cyyynthia Dec 3, 2023
85149be
feat: respect notification subscription settings
cyyynthia Dec 3, 2023
f235131
tests: fix json number matching
cyyynthia Dec 4, 2023
caa4ce4
fix: handle modified entity relations using rdbms-level cascade
cyyynthia Dec 4, 2023
2384df9
chore: lint
cyyynthia Dec 4, 2023
be30fc9
fix: hibernate proxy behavior
cyyynthia Dec 4, 2023
16de53b
tests: remove unused mockbean
cyyynthia Dec 4, 2023
19d7c6e
fix: hibernate lazy initialization problems
cyyynthia Dec 4, 2023
e612c0c
feat: implement notification http endpoints
cyyynthia Dec 13, 2023
4aebb94
fix: notification updates
cyyynthia Dec 13, 2023
621113a
Merge remote-tracking branch 'origin/main' into cynthia/notifications…
cyyynthia Dec 13, 2023
38e938a
fix: key creation test
cyyynthia Dec 13, 2023
9569892
Merge remote-tracking branch 'origin/main' into cynthia/notifications…
cyyynthia Jan 22, 2024
8823857
Merge remote-tracking branch 'origin/main' into cynthia/notifications…
cyyynthia Jan 24, 2024
1d54b2e
fix: migrate notifications to Spring Boot 3
cyyynthia Jan 26, 2024
49b8710
Merge remote-tracking branch 'origin/main' into cynthia/notifications…
cyyynthia Jan 26, 2024
1698670
chore: format
cyyynthia Jan 26, 2024
cda1335
fix(tests): handle bad transactions
cyyynthia Jan 26, 2024
b838b7f
fix: make language permission fetching more efficient
cyyynthia Jan 27, 2024
c845dda
Merge remote-tracking branch 'origin/cynthia/notifications-core' into…
cyyynthia Jan 27, 2024
b841451
chore: ktlint
cyyynthia Jan 27, 2024
c208be0
fix: bad aggregation result
cyyynthia Jan 27, 2024
0601b71
fix: bad permission mock
cyyynthia Jan 27, 2024
9cb51f4
chore(webapp): cleanup & update some deps
cyyynthia Feb 5, 2024
ac25e49
feat: add notification bell and root view
cyyynthia Feb 5, 2024
ca62a12
fix: workaround the fact npm is a bad pkg manager
cyyynthia Feb 5, 2024
a6e0362
feat: skeleton of the notifications view
cyyynthia Feb 14, 2024
f018b9b
Merge remote-tracking branch 'origin/cynthia/notifications-core' into…
cyyynthia Feb 14, 2024
cbb7633
Merge branch 'main' into cynthia/notifications-core
cyyynthia Apr 29, 2024
f1e0ee9
fix: use listagg instead of array_agg
cyyynthia Apr 29, 2024
0f2dca5
fix: Minor updates
JanCizmar May 15, 2024
2c3c8f7
feat: Provide swagger schemas of activity modifications
JanCizmar May 17, 2024
c6ed341
feat: Activity type definitions, activity grouping
JanCizmar May 30, 2024
dab8b16
feat: Grouper
JanCizmar May 30, 2024
5fb7b38
fix: Add JOOQ, activity grouping stuff
JanCizmar Jun 6, 2024
4e7166a
Merge branch 'main' into jancizmar/notifications-activity-grouping
JanCizmar Jul 29, 2024
3a8d10c
feat: Project creation group backend
JanCizmar Jul 31, 2024
80b59d4
feat: "Create Key" group with items
JanCizmar Aug 22, 2024
0431b7a
feat: "Create Key" group with items
JanCizmar Aug 22, 2024
59cf99c
feat: "Create Key" group items and their relations
JanCizmar Sep 4, 2024
59bc144
feat: Store base translation value for key
JanCizmar Sep 14, 2024
8a77db4
feat: Activity grouping
JanCizmar Nov 26, 2024
11cde42
Merge branch 'main' into jancizmar/notifications-activity-grouping
JanCizmar Jul 11, 2026
5d7a733
fix: adapt branch code to current main after merge
JanCizmar Jul 11, 2026
348710d
refactor: drop branch user-notification system in favor of main's not…
JanCizmar Jul 11, 2026
16ebcfa
feat: cover all groupable activity types with activity group definitions
JanCizmar Jul 11, 2026
a021343
fix: address CI failures after activity grouping merge
JanCizmar Jul 11, 2026
5706daf
feat: branch-aware activity groups, expanded views for all group type…
JanCizmar Jul 11, 2026
0587adc
fix: stable identity keys for pre-allocation modified entities
JanCizmar Jul 11, 2026
5c00953
feat: show grouped activity on the project dashboard
JanCizmar Jul 11, 2026
234314d
fix: drop explicit buffer import in avatar generation
JanCizmar Jul 11, 2026
10fff7a
fix: revert branch-era lazy permission languages to main's eager fetch
JanCizmar Jul 13, 2026
82453b7
fix: correct SQL generated by activity group value matchers
JanCizmar Jul 13, 2026
1b6b284
feat: show trigger origin and compound source actions in activity groups
JanCizmar Jul 13, 2026
8476dc8
feat: make expanded group items readable
JanCizmar Jul 13, 2026
ce4b2e6
test: migrate activity e2e specs to the grouped dashboard
JanCizmar Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.activity.ActivityService
import io.tolgee.activity.groups.ActivityGroupService
import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.constants.Feature
import io.tolgee.dtos.queryResults.ActivityGroupView
import io.tolgee.dtos.request.ActivityGroupFilters
import io.tolgee.exceptions.NotFoundException
import io.tolgee.hateoas.activity.ActivityGroupModel
import io.tolgee.hateoas.activity.ActivityGroupModelAssembler
import io.tolgee.hateoas.activity.ModifiedEntityModel
import io.tolgee.hateoas.activity.ModifiedEntityModelAssembler
import io.tolgee.hateoas.activity.ProjectActivityModel
Expand All @@ -28,6 +33,7 @@ import io.tolgee.service.project.ProjectFeatureGuard
import org.springdoc.core.annotations.ParameterObject
import org.springframework.data.domain.Pageable
import org.springframework.data.web.PagedResourcesAssembler
import org.springframework.hateoas.MediaTypes
import org.springframework.hateoas.PagedModel
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
Expand All @@ -49,6 +55,9 @@ class ProjectActivityController(
private val modificationResourcesAssembler: PagedResourcesAssembler<ModifiedEntityView>,
private val projectActivityModelAssembler: ProjectActivityModelAssembler,
private val modifiedEntityModelAssembler: ModifiedEntityModelAssembler,
private val activityGroupService: ActivityGroupService,
private val groupPagedResourcesAssembler: PagedResourcesAssembler<ActivityGroupView>,
private val groupModelAssembler: ActivityGroupModelAssembler,
private val branchService: BranchService,
private val projectFeatureGuard: ProjectFeatureGuard,
private val rateLimitService: RateLimitService,
Expand Down Expand Up @@ -123,4 +132,31 @@ class ProjectActivityController(
)
return modificationResourcesAssembler.toModel(page, modifiedEntityModelAssembler)
}

@Operation(
summary = "Get project activity groups",
description = "This endpoints returns the activity grouped by time windows so it's easier to read on the frontend.",
)
@GetMapping("/groups", produces = [MediaTypes.HAL_JSON_VALUE])
@RequiresProjectPermissions([Scope.ACTIVITY_VIEW])
@AllowApiAccess
fun getActivityGroups(
@ParameterObject pageable: Pageable,
@ParameterObject activityGroupFilters: ActivityGroupFilters,
@RequestParam(required = false) branch: String? = null,
): PagedModel<ActivityGroupModel> {
projectFeatureGuard.checkIfUsed(Feature.BRANCHING, branch)
val activeBranch = branchService.getActiveNonDefaultBranch(projectHolder.project.id, branch)
val defaultBranchId =
if (activeBranch == null) branchService.getDefaultBranch(projectHolder.project.id)?.id else null
val views =
activityGroupService.getProjectActivityGroups(
projectId = projectHolder.project.id,
pageable,
activityGroupFilters,
branchId = activeBranch?.id,
defaultBranchId = defaultBranchId,
)
return groupPagedResourcesAssembler.toModel(views, groupModelAssembler)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2020. Tolgee
*/

package io.tolgee.api.v2.controllers

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.activity.groups.ActivityGroupService
import io.tolgee.activity.groups.viewProviders.createKey.CreateKeyGroupItemModel
import io.tolgee.activity.groups.viewProviders.createKey.CreateKeyGroupModelProvider
import io.tolgee.activity.groups.viewProviders.generic.GenericGroupItemModel
import io.tolgee.activity.groups.viewProviders.generic.GenericGroupModelProvider
import io.tolgee.model.enums.Scope
import io.tolgee.security.ProjectHolder
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authorization.RequiresProjectPermissions
import org.springdoc.core.annotations.ParameterObject
import org.springframework.data.domain.Pageable
import org.springframework.hateoas.MediaTypes
import org.springframework.hateoas.PagedModel
import org.springframework.hateoas.PagedModel.PageMetadata
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@Suppress("MVCPathVariableInspection")
@RestController
@CrossOrigin(origins = ["*"])
@RequestMapping(value = ["/v2/projects/{projectId}/activity/group-items", "/v2/projects/activity/group-items"])
@Tag(name = "Activity Groups")
class ProjectActivityGroupItemsController(
private val createKeyGroupModelProvider: CreateKeyGroupModelProvider,
private val genericGroupModelProvider: GenericGroupModelProvider,
private val activityGroupService: ActivityGroupService,
private val projectHolder: ProjectHolder,
) {
@Operation(
summary = "Get CREATE_KEY group items",
)
@GetMapping("/create-key/{groupId}", produces = [MediaTypes.HAL_JSON_VALUE])
@RequiresProjectPermissions([Scope.ACTIVITY_VIEW])
@AllowApiAccess
fun getCreateKeyItems(
@ParameterObject pageable: Pageable,
@PathVariable groupId: Long,
): PagedModel<CreateKeyGroupItemModel> {
val data = createKeyGroupModelProvider.provideItems(groupId, pageable)
return PagedModel.of(
data.content,
PageMetadata(data.pageable.pageSize.toLong(), data.pageable.pageNumber.toLong(), data.totalElements),
)
}

@Operation(
summary = "Get group items",
description = "Returns the modified entities of the group as generic items (any group type).",
)
@GetMapping("/generic/{groupId}", produces = [MediaTypes.HAL_JSON_VALUE])
@RequiresProjectPermissions([Scope.ACTIVITY_VIEW])
@AllowApiAccess
fun getGenericItems(
@ParameterObject pageable: Pageable,
@PathVariable groupId: Long,
): PagedModel<GenericGroupItemModel> {
val group = activityGroupService.getGroupInProject(groupId, projectHolder.project.id)
val data = genericGroupModelProvider.provideItems(group.type, groupId, pageable)
return PagedModel.of(
data.content,
PageMetadata(data.pageable.pageSize.toLong(), data.pageable.pageNumber.toLong(), data.totalElements),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.tolgee.hateoas.activity

import io.tolgee.activity.data.ActivityOrigin
import io.tolgee.activity.data.ActivityType
import io.tolgee.activity.groups.ActivityGroupType
import io.tolgee.hateoas.userAccount.SimpleUserAccountModel
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation
import java.io.Serializable

@Suppress("unused")
@Relation(collectionRelation = "groups", itemRelation = "group")
class ActivityGroupModel(
val id: Long,
val timestamp: Long,
val type: ActivityGroupType,
val author: SimpleUserAccountModel?,
val data: Any?,
var mentionedLanguageIds: List<Long>,
val sourceActivityTypes: List<ActivityType>,
val origins: List<ActivityOrigin>,
) : RepresentationModel<ActivityGroupModel>(),
Serializable
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.tolgee.hateoas.activity

import io.tolgee.activity.data.ActivityOrigin
import io.tolgee.activity.data.ActivityType
import io.tolgee.dtos.queryResults.ActivityGroupView
import io.tolgee.hateoas.userAccount.SimpleUserAccountModelAssembler
import org.springframework.hateoas.server.RepresentationModelAssembler
import org.springframework.stereotype.Component

@Component
class ActivityGroupModelAssembler(
private val simpleUserAccountModelAssembler: SimpleUserAccountModelAssembler,
) : RepresentationModelAssembler<ActivityGroupView, ActivityGroupModel> {
override fun toModel(view: ActivityGroupView): ActivityGroupModel {
return ActivityGroupModel(
id = view.id,
timestamp = view.timestamp.time,
type = view.type,
author =
simpleUserAccountModelAssembler.toModel(
view.author,
),
data = view.data,
mentionedLanguageIds = view.mentionedLanguageIds,
sourceActivityTypes =
view.sourceActivityTypes.mapNotNull { type ->
ActivityType.entries.find { it.name == type }
},
origins =
view.origins.mapNotNull { origin ->
ActivityOrigin.entries.find { it.name == origin }
},
)
}
}
14 changes: 8 additions & 6 deletions backend/api/src/main/kotlin/io/tolgee/hateoas/key/KeyModel.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.tolgee.hateoas.key

import io.swagger.v3.oas.annotations.media.Schema
import io.tolgee.api.IKeyModel
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation
import java.io.Serializable
Expand All @@ -9,21 +10,22 @@ import java.io.Serializable
@Relation(collectionRelation = "keys", itemRelation = "key")
open class KeyModel(
@Schema(description = "Id of key record")
val id: Long,
override val id: Long,
@Schema(description = "Name of key", example = "this_is_super_key")
val name: String,
override val name: String,
@Schema(description = "Namespace of key", example = "homepage")
val namespace: String?,
override val namespace: String?,
@Schema(
description = "Description of key",
example = "This key is used on homepage. It's a label of sign up button.",
)
val description: String?,
override val description: String?,
@Schema(description = "Custom values of the key")
val custom: Map<String, Any?>?,
override val custom: Map<String, Any?>?,
@Schema(description = "Maximum character limit for translations of this key")
val maxCharLimit: Int?,
@Schema(description = "Branch of key", example = "dev")
val branch: String?,
) : RepresentationModel<KeyModel>(),
Serializable
Serializable,
IKeyModel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema
import io.tolgee.api.v2.hateoas.invitation.TagModel
import io.tolgee.hateoas.screenshot.ScreenshotModel
import io.tolgee.hateoas.translations.TranslationModel
import io.tolgee.sharedDocs.Key
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation
import java.io.Serializable
Expand Down Expand Up @@ -31,9 +32,9 @@ open class KeyWithDataModel(
val tags: Set<TagModel>,
@Schema(description = "Screenshots of the key")
val screenshots: List<ScreenshotModel>,
@Schema(description = "If key is pluralized. If it will be reflected in the editor")
@Schema(description = Key.IS_PLURAL_FIELD)
val isPlural: Boolean,
@Schema(description = "The argument name for the plural")
@Schema(description = Key.PLURAL_ARG_NAME_FIELD)
val pluralArgName: String?,
@Schema(description = "Custom values of the key")
val custom: Map<String, Any?>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package io.tolgee.hateoas.language

import io.swagger.v3.oas.annotations.media.Schema
import io.tolgee.api.ILanguageModel
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation

@Suppress("unused")
@Relation(collectionRelation = "languages", itemRelation = "language")
open class LanguageModel(
val id: Long,
override val id: Long,
@Schema(example = "Czech", description = "Language name in english")
val name: String,
override val name: String,
@Schema(example = "cs-CZ", description = "Language tag according to BCP 47 definition")
var tag: String,
override var tag: String,
@Schema(example = "čeština", description = "Language name in this language")
var originalName: String? = null,
override var originalName: String?,
@Schema(example = "\uD83C\uDDE8\uD83C\uDDFF", description = "Language flag emoji as UTF-8 emoji")
var flagEmoji: String? = null,
override var flagEmoji: String?,
@Schema(example = "false", description = "Whether is base language of project")
var base: Boolean,
) : RepresentationModel<LanguageModel>()
) : RepresentationModel<LanguageModel>(),
ILanguageModel
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.tolgee.hateoas.userAccount

import io.tolgee.api.SimpleUserAccount
import io.tolgee.api.v2.controllers.V2UserController
import io.tolgee.dtos.cacheable.UserAccountDto
import io.tolgee.model.UserAccount
Expand All @@ -26,6 +27,18 @@ class SimpleUserAccountModelAssembler(
)
}

fun toModel(entity: SimpleUserAccount): SimpleUserAccountModel {
val avatar = avatarService.getAvatarLinks(entity.avatarHash)

return SimpleUserAccountModel(
id = entity.id,
username = entity.username,
name = entity.name,
avatar = avatar,
deleted = entity.deleted,
)
}

fun toModel(dto: UserAccountDto): SimpleUserAccountModel {
val avatar = avatarService.getAvatarLinks(dto.avatarHash)

Expand Down
2 changes: 2 additions & 0 deletions backend/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,15 @@ dependencies {
testImplementation('io.netty:netty-codec-http:4.2.5.Final')
testImplementation('io.netty:netty-codec-http2:4.2.5.Final')
testImplementation('io.netty:netty-handler:4.2.5.Final')
testImplementation libs.springJooq
ktlint(project(":ktlint"))


/**
* MISC
*/
implementation libs.commonsCodec
implementation libs.commonsText
implementation libs.commonsLang
implementation libs.amazonS3
implementation libs.amazonSTS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package io.tolgee.configuration.openApi
import io.swagger.v3.oas.models.Operation
import io.swagger.v3.oas.models.PathItem
import io.swagger.v3.oas.models.Paths
import io.tolgee.configuration.openApi.activity.ActivityGroupModelEnhancer
import io.tolgee.configuration.openApi.activity.ProjectActivityModelEnhancer
import io.tolgee.openApiDocs.OpenApiCloudExtension
import io.tolgee.openApiDocs.OpenApiEeExtension
import io.tolgee.openApiDocs.OpenApiOrderExtension
Expand Down Expand Up @@ -39,13 +41,22 @@ class OpenApiGroupBuilder(

addExtensions()

updateActivitySchema()

cleanUnusedModels()

sortOutput()

return@lazy builder.build()
}

private fun updateActivitySchema() {
builder.addOpenApiCustomizer {
ProjectActivityModelEnhancer(it).enhance()
ActivityGroupModelEnhancer(it).enhance()
}
}

private fun sortOutput() {
builder.addOpenApiCustomizer { openAPI ->
openAPI.paths =
Expand Down
Loading
Loading