Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tasks #2401

Merged
merged 38 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
746b71d
feat: tasks
stepan662 Jul 3, 2024
5666884
feat: improve prefilter and task detail
stepan662 Oct 3, 2024
17bb75e
fix: due date clone
stepan662 Oct 3, 2024
d419501
feat: reorganize project menu
stepan662 Oct 3, 2024
5ee7361
fix: board view fetch more
stepan662 Oct 3, 2024
ac5a376
fix: safari performance issues
stepan662 Oct 3, 2024
8bdc465
fix: safari performance issues
stepan662 Oct 3, 2024
7ebcfd8
fix: task detail layout
stepan662 Oct 3, 2024
ade2b76
fix: task detail dialog
stepan662 Oct 3, 2024
e44c131
feat: task creation warning only when task is truely blocked
stepan662 Oct 3, 2024
35880cb
feat: task creation warning only when task is truely blocked
stepan662 Oct 3, 2024
c8b51be
feat: improve task notification email
stepan662 Oct 3, 2024
f86f4eb
feat: improve task notification email
stepan662 Oct 3, 2024
7b45c2e
fix: improve task detail
stepan662 Oct 3, 2024
445eb0e
chore: fix e2e
stepan662 Oct 3, 2024
006b5c5
feat: improve task detail loading
stepan662 Oct 3, 2024
4aae48c
fix: vulnerable dependency
stepan662 Oct 3, 2024
8e392f0
fix: warning wrapping
stepan662 Oct 3, 2024
fb4457a
fix: increase spacing in report
stepan662 Oct 3, 2024
88a12e3
chore: remove unused gradle dependecy
stepan662 Oct 4, 2024
746ab70
fix: improve code style
stepan662 Oct 7, 2024
8f32083
fix: improve code style
stepan662 Oct 7, 2024
b919aa5
fix: improve code style
stepan662 Oct 7, 2024
490d0f2
fix: improve code style
stepan662 Oct 7, 2024
843b103
fix: convert diff types to react component
stepan662 Oct 7, 2024
0bcb5e1
chore: ktlint
stepan662 Oct 8, 2024
3b56be9
fix: user filters problem
stepan662 Oct 8, 2024
7e9b9f9
fix: BE tests
stepan662 Oct 8, 2024
d3b3491
chore: add BE tests
stepan662 Oct 8, 2024
54d9513
fix: refactor edit service
stepan662 Oct 8, 2024
20e1e39
fix: Make activity stored correctly (#2531)
JanCizmar Oct 8, 2024
f459ae4
feat: standardize edit task endpoint
stepan662 Oct 8, 2024
f66c507
feat: announcement
stepan662 Oct 8, 2024
efed705
feat: better task numbering
stepan662 Oct 8, 2024
db7074b
feat: better task numbering
stepan662 Oct 8, 2024
8573f11
fix: task creation error
stepan662 Oct 8, 2024
49723f4
chore: fix BE test
stepan662 Oct 8, 2024
a868156
fix: update schema
stepan662 Oct 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ When no languages provided, it translates only untranslated languages.""",
languagesToTranslate: Set<String>,
) {
keyService.checkInProject(key, projectHolder.project.id)
securityService.checkLanguageTranslatePermissionsByTag(languagesToTranslate, projectHolder.project.id)
securityService.checkLanguageTranslatePermissionsByTag(languagesToTranslate, projectHolder.project.id, key.id)
}

private fun getAllLanguagesToTranslate(): Set<String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class ProjectStatsController(
projectId = projectStats.id,
languageCount = languageStats.size,
keyCount = projectStats.keyCount,
taskCount = projectStats.taskCount,
baseWordsCount = totals.baseWordsCount,
translatedPercentage = totals.translatedPercent,
reviewedPercentage = totals.reviewedPercent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.tolgee.component.LanguageValidator
import io.tolgee.constants.Message
import io.tolgee.dtos.cacheable.LanguageDto
import io.tolgee.dtos.request.LanguageRequest
import io.tolgee.dtos.request.language.LanguageFilters
import io.tolgee.exceptions.BadRequestException
import io.tolgee.hateoas.language.LanguageModel
import io.tolgee.hateoas.language.LanguageModelAssembler
Expand Down Expand Up @@ -95,8 +96,10 @@ class V2LanguagesController(
fun getAll(
@PathVariable("projectId") pathProjectId: Long?,
@ParameterObject @SortDefault("tag") pageable: Pageable,
@ParameterObject
filters: LanguageFilters,
): PagedModel<LanguageModel> {
val data = languageService.getPaged(projectHolder.project.id, pageable)
val data = languageService.getPaged(projectHolder.project.id, pageable, filters)
return pagedAssembler.toModel(data, languageModelAssembler)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.tolgee.activity.data.ActivityType
import io.tolgee.constants.Message
import io.tolgee.dtos.request.project.CreateProjectRequest
import io.tolgee.dtos.request.project.EditProjectRequest
import io.tolgee.dtos.request.project.ProjectFilters
import io.tolgee.dtos.request.project.SetPermissionLanguageParams
import io.tolgee.exceptions.BadRequestException
import io.tolgee.facade.ProjectPermissionFacade
Expand Down Expand Up @@ -114,10 +115,13 @@ class ProjectsController(
@AllowApiAccess(tokenType = AuthTokenType.ONLY_PAT)
@OpenApiOrderExtension(3)
fun getAll(
@ParameterObject pageable: Pageable,
@ParameterObject
filters: ProjectFilters,
@ParameterObject
pageable: Pageable,
@RequestParam("search") search: String?,
): PagedModel<ProjectModel> {
val projects = projectService.findPermittedInOrganizationPaged(pageable, search)
val projects = projectService.findPermittedInOrganizationPaged(pageable, search, filters = filters)
return arrayResourcesAssembler.toModel(projects, projectModelAssembler)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.tolgee.model.views.TranslationMemoryItemView
import io.tolgee.security.ProjectHolder
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authorization.RequiresProjectPermissions
import io.tolgee.security.authorization.UseDefaultPermissions
import io.tolgee.service.key.KeyService
import io.tolgee.service.language.LanguageService
import io.tolgee.service.security.SecurityService
Expand Down Expand Up @@ -72,12 +73,17 @@ class TranslationSuggestionController(
"If an error occurs when for any service provider used," +
" the error information is returned as a part of the result item, while the response has 200 status code.",
)
@RequiresProjectPermissions([Scope.TRANSLATIONS_EDIT])
@UseDefaultPermissions
@AllowApiAccess
fun suggestMachineTranslationsStreaming(
@RequestBody @Valid
dto: SuggestRequestDto,
): ResponseEntity<StreamingResponseBody> {
securityService.checkScopeOrAssignedToTask(
Scope.TRANSLATIONS_EDIT,
dto.targetLanguageId,
dto.keyId ?: -1,
)
return ResponseEntity.ok().disableAccelBuffering().body(
machineTranslationSuggestionFacade.suggestStreaming(dto),
)
Expand All @@ -90,17 +96,20 @@ class TranslationSuggestionController(
"Suggests machine translations from translation memory. " +
"The result is always sorted by similarity, so sorting is not supported.",
)
@RequiresProjectPermissions([Scope.TRANSLATIONS_EDIT])
@UseDefaultPermissions
@AllowApiAccess
fun suggestTranslationMemory(
@RequestBody @Valid
dto: SuggestRequestDto,
@ParameterObject pageable: Pageable,
): PagedModel<TranslationMemoryItemModel> {
securityService.checkScopeOrAssignedToTask(
Scope.TRANSLATIONS_EDIT,
dto.targetLanguageId,
dto.keyId ?: -1,
)
val targetLanguage = languageService.get(dto.targetLanguageId, projectHolder.project.id)

securityService.checkLanguageTranslatePermission(projectHolder.project.id, listOf(targetLanguage.id))

val data =
dto.baseText?.let { baseText ->
translationMemoryService.getSuggestions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ class CreateOrUpdateTranslationsFacade(
key: Key? = null,
): SetTranslationsResponseModel {
val keyNotNull = key ?: keyService.get(projectHolder.project.id, dto.key, dto.namespace)
securityService.checkLanguageTranslatePermissionsByTag(dto.translations.keys, projectHolder.project.id)
securityService.checkLanguageTranslatePermissionsByTag(
dto.translations.keys,
projectHolder.project.id,
keyNotNull.id,
)

val modifiedTranslations = translationService.setForKey(keyNotNull, dto.translations)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,19 @@ class TranslationCommentController(
@PutMapping(value = ["{translationId}/comments/{commentId}/set-state/{state}"])
@Operation(summary = "Set state of translation comment")
@RequestActivity(ActivityType.TRANSLATION_COMMENT_SET_STATE)
@RequiresProjectPermissions([Scope.TRANSLATIONS_COMMENTS_SET_STATE])
@UseDefaultPermissions
@AllowApiAccess
fun setState(
@PathVariable translationId: Long,
@PathVariable commentId: Long,
@PathVariable state: TranslationCommentState,
): TranslationCommentModel {
val translation = translationService.get(translationId)
securityService.checkScopeOrAssignedToTask(
Scope.TRANSLATIONS_COMMENTS_SET_STATE,
translation.language.id,
translation.key.id,
)
val comment = translationCommentService.getWithAuthorFetched(projectHolder.project.id, translationId, commentId)
translationCommentService.setState(comment, state)
return translationCommentModelAssembler.toModel(comment)
Expand Down Expand Up @@ -174,12 +180,17 @@ class TranslationCommentController(
description = "Creates a translation comment. Empty translation is stored, when not exists.",
)
@RequestActivity(ActivityType.TRANSLATION_COMMENT_ADD)
@RequiresProjectPermissions([Scope.TRANSLATIONS_COMMENTS_ADD])
@UseDefaultPermissions
@AllowApiAccess
fun create(
@RequestBody @Valid
dto: TranslationCommentWithLangKeyDto,
): ResponseEntity<TranslationWithCommentModel> {
securityService.checkScopeOrAssignedToTask(
Scope.TRANSLATIONS_COMMENTS_ADD,
dto.languageId,
dto.keyId,
)
val translation = translationService.getOrCreate(dto.keyId, dto.languageId)
if (translation.key.project.id != projectHolder.project.id) {
throw BadRequestException(io.tolgee.constants.Message.KEY_NOT_FROM_PROJECT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ import io.tolgee.hateoas.translations.TranslationModelAssembler
import io.tolgee.model.enums.AssignableTranslationState
import io.tolgee.model.enums.Scope
import io.tolgee.model.translation.Translation
import io.tolgee.model.views.KeyTaskView
import io.tolgee.model.views.KeyWithTranslationsView
import io.tolgee.openApiDocs.OpenApiOrderExtension
import io.tolgee.security.ProjectHolder
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authorization.RequiresProjectPermissions
import io.tolgee.security.authorization.UseDefaultPermissions
import io.tolgee.service.ITaskService
import io.tolgee.service.key.ScreenshotService
import io.tolgee.service.language.LanguageService
import io.tolgee.service.queryBuilders.CursorUtil
Expand Down Expand Up @@ -100,6 +102,7 @@ class TranslationsController(
private val activityService: ActivityService,
private val projectTranslationLastModifiedManager: ProjectTranslationLastModifiedManager,
private val createOrUpdateTranslationsFacade: CreateOrUpdateTranslationsFacade,
private val taskService: ITaskService,
) : IController {
@GetMapping(value = ["/{languages}"])
@Operation(
Expand Down Expand Up @@ -176,7 +179,7 @@ When null, resulting file will be a flat key-value object.
@PutMapping("")
@Operation(summary = "Update translations for existing key", description = "Sets translations for existing key")
@RequestActivity(ActivityType.SET_TRANSLATIONS)
@RequiresProjectPermissions([Scope.TRANSLATIONS_EDIT])
@UseDefaultPermissions
@AllowApiAccess
@OpenApiOrderExtension(2)
fun setTranslations(
Expand Down Expand Up @@ -206,7 +209,7 @@ When null, resulting file will be a flat key-value object.
@PutMapping("/{translationId}/set-state/{state}")
@Operation(summary = "Set translation state")
@RequestActivity(ActivityType.SET_TRANSLATION_STATE)
@RequiresProjectPermissions([Scope.TRANSLATIONS_STATE_EDIT])
@UseDefaultPermissions
@AllowApiAccess
fun setTranslationState(
@PathVariable translationId: Long,
Expand Down Expand Up @@ -253,6 +256,7 @@ When null, resulting file will be a flat key-value object.
.getViewData(projectHolder.project.id, pageableWithSort, params, languages)

addScreenshotsToResponse(data)
addTasksToResponse(data)

val cursor = if (data.content.isNotEmpty()) CursorUtil.getCursor(data.content.last(), data.sort) else null
return pagedAssembler.toTranslationModel(data, languages, cursor)
Expand All @@ -270,6 +274,27 @@ When null, resulting file will be a flat key-value object.
data.content.forEach { it.screenshots = keysWithScreenshots[it.keyId] ?: listOf() }
}

private fun addTasksToResponse(data: Page<KeyWithTranslationsView>) {
val user = authenticationFacade.authenticatedUser
val keyIds = data.content.map { key -> key.keyId }

val translationsWithTasks = taskService.getKeysWithTasks(user.id, keyIds)

data.content.forEach { key ->
key.tasks =
translationsWithTasks[key.keyId]?.map {
KeyTaskView(
it.taskNumber,
it.languageId,
it.languageTag,
it.taskDone,
it.taskAssigned,
it.taskType,
)
}
}
}

@PutMapping(value = ["/{translationId:[0-9]+}/dismiss-auto-translated-state"])
@Operation(summary = "Dismiss auto-translated", description = """Removes "auto translated" indication""")
@RequestActivity(ActivityType.DISMISS_AUTO_TRANSLATED_STATE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ class KeyComplexEditHelper(

private fun doStateUpdate() {
if (areStatesModified) {
securityService.checkLanguageChangeStatePermissionsByLanguageId(modifiedStates!!.keys, projectHolder.project.id)
securityService.checkLanguageChangeStatePermissionsByLanguageId(
modifiedStates!!.keys,
projectHolder.project.id,
key.id,
)
translationService.setStateBatch(
states =
modifiedStates!!.map {
Expand All @@ -187,10 +191,10 @@ class KeyComplexEditHelper(

private fun doTranslationsUpdate() {
if (modifiedTranslations != null && areTranslationsModified) {
projectHolder.projectEntity.checkTranslationsEditPermission()
securityService.checkLanguageTranslatePermissionsByLanguageId(
modifiedTranslations!!.keys,
projectHolder.project.id,
keyId,
)

val modifiedTranslations = getModifiedTranslationsByTag()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ open class ProjectStatsModel(
val projectId: Long,
val languageCount: Int,
val keyCount: Long,
val taskCount: Long,
val baseWordsCount: Long,
val translatedPercentage: Double,
val reviewedPercentage: Double,
Expand Down
27 changes: 27 additions & 0 deletions backend/api/src/main/kotlin/io/tolgee/hateoas/task/TaskModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.tolgee.hateoas.task

import io.tolgee.hateoas.language.LanguageModel
import io.tolgee.hateoas.userAccount.SimpleUserAccountModel
import io.tolgee.model.enums.TaskState
import io.tolgee.model.enums.TaskType
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation

@Relation(collectionRelation = "tasks", itemRelation = "task")
class TaskModel(
var number: Long = 0L,
var name: String = "",
var description: String = "",
var type: TaskType = TaskType.TRANSLATE,
var language: LanguageModel,
var dueDate: Long? = null,
var assignees: MutableSet<SimpleUserAccountModel> = mutableSetOf(),
var totalItems: Long = 0,
var doneItems: Long = 0,
var baseWordCount: Long = 0,
var baseCharacterCount: Long = 0,
var author: SimpleUserAccountModel? = null,
var createdAt: Long? = 0,
var closedAt: Long? = null,
var state: TaskState = TaskState.IN_PROGRESS,
) : RepresentationModel<TaskModel>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.tolgee.hateoas.task

import io.tolgee.hateoas.userAccount.SimpleUserAccountModel
import org.springframework.hateoas.RepresentationModel

class TaskPerUserReportModel(
var user: SimpleUserAccountModel,
var doneItems: Long,
var baseCharacterCount: Long,
var baseWordCount: Long,
) : RepresentationModel<TaskPerUserReportModel>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.tolgee.hateoas.task

import io.tolgee.hateoas.language.LanguageModel
import io.tolgee.hateoas.project.SimpleProjectModel
import io.tolgee.hateoas.userAccount.SimpleUserAccountModel
import io.tolgee.model.enums.TaskState
import io.tolgee.model.enums.TaskType
import org.springframework.hateoas.RepresentationModel
import org.springframework.hateoas.server.core.Relation

@Relation(collectionRelation = "tasks", itemRelation = "task")
data class TaskWithProjectModel(
var number: Long = 0L,
var name: String = "",
var description: String = "",
var type: TaskType = TaskType.TRANSLATE,
var language: LanguageModel,
var dueDate: Long? = null,
var assignees: MutableSet<SimpleUserAccountModel> = mutableSetOf(),
var totalItems: Long = 0,
var doneItems: Long = 0,
var baseWordCount: Long = 0,
var baseCharacterCount: Long = 0,
var author: SimpleUserAccountModel? = null,
var createdAt: Long? = 0,
var closedAt: Long? = null,
var state: TaskState = TaskState.IN_PROGRESS,
var project: SimpleProjectModel,
) : RepresentationModel<TaskWithProjectModel>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.tolgee.hateoas.translations

import io.tolgee.model.enums.TaskType
import org.springframework.hateoas.RepresentationModel

open class KeyTaskViewModel(
val number: Long,
val languageId: Long,
val languageTag: String,
val done: Boolean,
val userAssigned: Boolean,
val type: TaskType,
) : RepresentationModel<KeyTaskViewModel>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.tolgee.hateoas.translations

import io.tolgee.api.v2.controllers.translation.TranslationsController
import io.tolgee.model.views.KeyTaskView
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
import org.springframework.stereotype.Component

@Component
class KeyTaskViewModelAssembler :
RepresentationModelAssemblerSupport<KeyTaskView, KeyTaskViewModel>(
TranslationsController::class.java,
KeyTaskViewModel::class.java,
) {
override fun toModel(view: KeyTaskView): KeyTaskViewModel {
return KeyTaskViewModel(
number = view.number,
languageId = view.languageId,
languageTag = view.languageTag,
done = view.done,
userAssigned = view.userAssigned,
type = view.type,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ open class KeyWithTranslationsModel(
""",
)
val translations: Map<String, TranslationViewModel>,
@Schema(description = "Tasks related to this key")
val tasks: List<KeyTaskViewModel>?,
) : RepresentationModel<KeyWithTranslationsModel>()
Loading
Loading