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 and order-translations improvements #2822

Merged
merged 9 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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
Loading