Skip to content

Commit

Permalink
feat: tasks and order-translations improvements (#2822)
Browse files Browse the repository at this point in the history
  • Loading branch information
stepan662 authored Jan 14, 2025
1 parent 9d139d7 commit c4505f4
Show file tree
Hide file tree
Showing 57 changed files with 840 additions and 257 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.tolgee.api.v2.controllers.translations.v2TranslationsController

import io.tolgee.ProjectAuthControllerTest
import io.tolgee.development.testDataBuilder.data.NamespacesTestData
import io.tolgee.development.testDataBuilder.data.TaskTestData
import io.tolgee.development.testDataBuilder.data.TranslationSourceChangeStateTestData
import io.tolgee.development.testDataBuilder.data.TranslationsTestData
import io.tolgee.fixtures.andAssertError
Expand Down Expand Up @@ -357,4 +358,39 @@ class TranslationsControllerFilterTest : ProjectAuthControllerTest("/v2/projects
node("_embedded.keys").isArray.hasSize(2)
}
}

@ProjectJWTAuthTestMethod
@Test
fun `filters by task`() {
val testData = TaskTestData()
testData.processFirstKeyOfTranslateTask()
testDataService.saveTestData(testData.root)
userAccount = testData.user
projectSupplier = { testData.projectBuilder.self }
performProjectAuthGet(
"/translations?filterTaskNumber=${testData.translateTask.self.number}",
).andIsOk.andAssertThatJson {
node("_embedded.keys") {
isArray.hasSize(2)
node("[0].keyName").isEqualTo("key 0")
node("[1].keyName").isEqualTo("key 1")
}
}
performProjectAuthGet(
"/translations?filterTaskNumber=${testData.translateTask.self.number}&filterTaskKeysNotDone=true",
).andIsOk.andAssertThatJson {
node("_embedded.keys") {
isArray.hasSize(1)
node("[0].keyName").isEqualTo("key 1")
}
}
performProjectAuthGet(
"/translations?filterTaskNumber=${testData.translateTask.self.number}&filterTaskKeysDone=true",
).andIsOk.andAssertThatJson {
node("_embedded.keys") {
isArray.hasSize(1)
node("[0].keyName").isEqualTo("key 0")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.tolgee.model.enums.OrganizationRoleType
import io.tolgee.model.enums.ProjectPermissionType
import io.tolgee.model.enums.Scope
import io.tolgee.model.enums.TaskType
import io.tolgee.model.task.TaskKey

class TaskTestData : BaseTestData("tasksTestUser", "Project with tasks") {
var projectUser: UserAccountBuilder
Expand All @@ -20,6 +21,7 @@ class TaskTestData : BaseTestData("tasksTestUser", "Project with tasks") {
var projectViewRoleUser: UserAccountBuilder
var projectManageRoleUser: UserAccountBuilder
var translateTask: TaskBuilder
var translateTaskKeys: MutableSet<TaskKey> = mutableSetOf()
var reviewTask: TaskBuilder
var relatedProject: ProjectBuilder
var keysInTask: MutableSet<KeyBuilder> = mutableSetOf()
Expand Down Expand Up @@ -151,6 +153,7 @@ class TaskTestData : BaseTestData("tasksTestUser", "Project with tasks") {
addTaskKey {
task = translateTask.self
key = it.self
translateTaskKeys.add(this)
}
}

Expand Down Expand Up @@ -266,4 +269,10 @@ class TaskTestData : BaseTestData("tasksTestUser", "Project with tasks") {
}
return keys
}

fun processFirstKeyOfTranslateTask(): TaskKey {
val firstKey = translateTaskKeys.first()
firstKey.done = true
return firstKey
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,14 @@ To filter default namespace, set to empty string.
description = "Select only keys which are in specified task",
)
var filterTaskNumber: List<Long>? = null

@field:Parameter(
description = "Filter task keys which are `not done`",
)
var filterTaskKeysNotDone: Boolean? = null

@field:Parameter(
description = "Filter task keys which are `done`",
)
var filterTaskKeysDone: Boolean? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,22 @@ class QueryGlobalFiltering(

private fun filterTask() {
if (params.filterTaskNumber != null) {
val translationTaskJoin =
val translationTaskKeyJoin =
queryBase.root
.join(Key_.tasks, JoinType.LEFT)
val translationTaskJoin =
translationTaskKeyJoin
.join(TaskKey_.task, JoinType.LEFT)

queryBase.whereConditions.add(translationTaskJoin.get(Task_.number).`in`(params.filterTaskNumber))

if (params.filterTaskKeysNotDone == true) {
queryBase.whereConditions.add(translationTaskKeyJoin.get(TaskKey_.done).`in`(false))
}

if (params.filterTaskKeysDone == true) {
queryBase.whereConditions.add(translationTaskKeyJoin.get(TaskKey_.done).`in`(true))
}
}
}

Expand Down
42 changes: 38 additions & 4 deletions e2e/cypress/e2e/tasks/projectTasks.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe('project tasks', () => {
cy.gcy('create-task-field-description').type(
'This is task description ...'
);
cy.gcy('translations-state-filter-clear').click();
getTaskPreview('Czech').findDcy('assignee-select').click();
cy.gcy('assignee-search-select-popover')
.contains('Organization member')
Expand Down Expand Up @@ -116,6 +117,7 @@ describe('project tasks', () => {
cy.gcy('create-task-field-languages-item').contains('Czech').click();
dismissMenu();
cy.waitForDom();
cy.gcy('translations-state-filter-clear').click();

checkTaskPreview({
language: 'Czech',
Expand All @@ -135,6 +137,7 @@ describe('project tasks', () => {
cy.gcy('create-task-field-languages-item').contains('English').click();
dismissMenu();
cy.waitForDom();
cy.gcy('translations-state-filter-clear').click();

checkTaskPreview({
language: 'Czech',
Expand All @@ -157,18 +160,49 @@ describe('project tasks', () => {
cy.gcy('create-task-field-languages').click();
cy.gcy('create-task-field-languages-item').contains('Czech').click();
dismissMenu();
cy.gcy('translations-state-filter-clear').click();

checkTaskPreview({
language: 'Czech',
keys: 4,
alert: false,
words: 8,
characters: 52,
});
});

it('wont allow creation of empty task', () => {
cy.gcy('tasks-header-add-task').click();
cy.gcy('create-task-field-name').click().type('new task');
cy.gcy('create-task-field-languages').click();
cy.gcy('create-task-field-languages-item').contains('Czech').click();
dismissMenu();

cy.gcy('translations-state-filter-clear').click();
cy.gcy('translations-state-filter').click();
cy.gcy('translations-state-filter-option').contains('Untranslated').click();
cy.gcy('translations-state-filter-option').contains('Outdated').click();

dismissMenu();
cy.waitForDom();

checkTaskPreview({
language: 'Czech',
keys: 2,
keys: 0,
alert: false,
words: 4,
characters: 26,
words: 0,
characters: 0,
});

cy.gcy('create-task-submit').click();
cy.gcy('empty-scope-dialog').should('be.visible');
});

it('uses default state filters', () => {
cy.gcy('tasks-header-add-task').click();
cy.gcy('translations-state-filter').contains('Untranslated');
cy.gcy('create-task-field-type').click();
cy.gcy('create-task-field-type-item').contains('Review').click();

cy.gcy('translations-state-filter').contains('Translated');
});
});
4 changes: 3 additions & 1 deletion e2e/cypress/support/dataCyType.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ declare namespace DataCy {
"edit-pat-dialog-content" |
"edit-pat-dialog-description-input" |
"edit-pat-dialog-title" |
"empty-scope-dialog" |
"expiration-date-field" |
"expiration-date-picker" |
"expiration-select" |
Expand Down Expand Up @@ -549,7 +550,7 @@ declare namespace DataCy {
"tasks-header-add-task" |
"tasks-header-filter-select" |
"tasks-header-order-translation" |
"tasks-header-show-closed" |
"tasks-header-show-all" |
"tasks-view-board-button" |
"tasks-view-list-button" |
"this-is-the-element" |
Expand Down Expand Up @@ -609,6 +610,7 @@ declare namespace DataCy {
"translations-select-all-button" |
"translations-shortcuts-command" |
"translations-state-filter" |
"translations-state-filter-clear" |
"translations-state-filter-option" |
"translations-state-indicator" |
"translations-table-cell" |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,14 @@ open class TaskFilters {
)
var filterAgency: List<Long>? = null

@Deprecated("Confusing logic and naming", ReplaceWith("filterNotClosedBefore"))
@field:Parameter(
description = """Exclude "done" tasks which are older than specified timestamp""",
)
var filterDoneMinClosedAt: Long? = null

@field:Parameter(
description = """Exclude tasks which were closed before specified timestamp""",
)
var filterNotClosedBefore: Long? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ private const val TASK_FILTERS = """
or :#{#filters.filterDoneMinClosedAt} is null
or tk.closedAt > :#{#filters.filterDoneMinClosedAt}
)
and (
:#{#filters.filterNotClosedBefore} is null
or tk.closedAt is null
or tk.closedAt > :#{#filters.filterNotClosedBefore}
)
"""

@Repository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class TaskService(
}
if (state == TaskState.NEW || state == TaskState.IN_PROGRESS) {
task.state = if (taskWithScope.doneItems == 0L) TaskState.NEW else TaskState.IN_PROGRESS
task.closedAt = null
} else {
task.closedAt = currentDateProvider.date
task.state = state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,32 @@ class TaskControllerTest : ProjectAuthControllerTest("/v2/projects/") {
node("state").isEqualTo("NEW")
}
}

@Test
@ProjectJWTAuthTestMethod
fun `closed tasks can be filtered out by timestamp`() {
val timeBeforeCreation = System.currentTimeMillis()
performProjectAuthPut(
"tasks/${testData.translateTask.self.number}/close",
).andIsOk.andAssertThatJson {
node("state").isEqualTo("CLOSED")
}
val timeAfterCreation = System.currentTimeMillis()

// should be included
performProjectAuthGet(
"tasks?filterNotClosedBefore=$timeBeforeCreation",
).andIsOk.andAssertThatJson {
node("page").node("totalElements").isEqualTo(2)
node("_embedded.tasks[0].name").isEqualTo("Translate task")
}

// should be excluded
performProjectAuthGet(
"tasks?filterNotClosedBefore=$timeAfterCreation",
).andIsOk.andAssertThatJson {
node("page").node("totalElements").isEqualTo(1)
node("_embedded.tasks[0].name").isEqualTo("Review task")
}
}
}
7 changes: 4 additions & 3 deletions webapp/src/component/common/LabelHint.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HelpCircle } from '@untitled-ui/icons-react';
import { Tooltip, styled } from '@mui/material';
import { SxProps, Tooltip, styled } from '@mui/material';

const StyledLabelBody = styled('div')`
display: inline-flex;
Expand All @@ -11,12 +11,13 @@ type Props = {
size?: number;
title: React.ReactNode;
children: React.ReactNode;
sx?: SxProps;
};

export const LabelHint = ({ children, title, size = 15 }: Props) => {
export const LabelHint = ({ children, title, size = 15, sx }: Props) => {
return (
<Tooltip title={title} disableInteractive>
<StyledLabelBody>
<StyledLabelBody {...{ sx }}>
{children}
<HelpCircle style={{ width: size, height: size }} />
</StyledLabelBody>
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/component/task/TaskTypeChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function getBackgroundColor(type: TaskType, theme: Theme) {
case 'TRANSLATE':
return theme.palette.tokens.text._states.focus;
case 'REVIEW':
return theme.palette.tokens.secondary._states.focus;
return theme.palette.tokens.success._states.focusVisible;
}
}

Expand Down
9 changes: 9 additions & 0 deletions webapp/src/constants/links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,12 @@ export class LINKS {
static SLACK_CONNECT = Link.ofParent(LINKS.SLACK, 'connect');
static SLACK_CONNECTED = Link.ofParent(LINKS.SLACK, 'connected');
}

export enum QUERY {
TRANSLATIONS_PREFILTERS_ACTIVITY = 'activity',
TRANSLATIONS_PREFILTERS_FAILED_JOB = 'failedJob',
TRANSLATIONS_PREFILTERS_TASK = 'task',
TRANSLATIONS_PREFILTERS_TASK_HIDE_DONE = 'taskHideDone',
TRANSLATIONS_TASK_DETAIL = 'taskDetail',
TASKS_FILTERS_SHOW_ALL = 'showAll',
}
Loading

0 comments on commit c4505f4

Please sign in to comment.