Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.crowdin.client.core.http.impl.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class RequestEncoder {
public static String encodeSpaces(String url) {
try {
return URLEncoder.encode(url, "UTF-8").replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.crowdin.client.core.http.impl.util;

import java.util.Map;
import java.util.stream.Collectors;

import com.crowdin.client.core.model.SortOrder;

public class SortOrderGenerator {
public static String generateSortParam(Map<String, SortOrder> orderByMap) {
if (orderByMap == null || orderByMap.isEmpty()) {
return null;
}

String sortParam = orderByMap.entrySet().stream()
.map(entry -> {
if (entry.getValue() == null) {
entry.setValue(SortOrder.ASC);
}

return entry.getKey() + " " + entry.getValue().to(entry.getValue());
})
.collect(Collectors.joining(","));

return RequestEncoder.encodeSpaces(sortParam);


}
}
16 changes: 16 additions & 0 deletions src/main/java/com/crowdin/client/core/model/SortOrder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.crowdin.client.core.model;

public enum SortOrder implements EnumConverter<SortOrder> {
ASC, DESC;

public static SortOrder from(String value) {
if (value == null) return ASC;

return SortOrder.valueOf(value.toUpperCase());
}

@Override
public String to(SortOrder v) {
return (v != null ? v : ASC).name().toLowerCase();
}
}
36 changes: 28 additions & 8 deletions src/main/java/com/crowdin/client/tasks/TasksApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@
import com.crowdin.client.core.http.HttpRequestConfig;
import com.crowdin.client.core.http.exceptions.HttpBadRequestException;
import com.crowdin.client.core.http.exceptions.HttpException;
import com.crowdin.client.core.model.BooleanInt;
import com.crowdin.client.core.model.ClientConfig;
import com.crowdin.client.core.model.Credentials;
import com.crowdin.client.core.model.DownloadLink;
import com.crowdin.client.core.model.DownloadLinkResponseObject;
import com.crowdin.client.core.model.PatchRequest;
import com.crowdin.client.core.model.ResponseList;
import com.crowdin.client.core.model.ResponseObject;
import com.crowdin.client.core.http.impl.util.SortOrderGenerator;
import com.crowdin.client.core.model.*;
import com.crowdin.client.tasks.model.*;

import java.util.EnumSet;
Expand Down Expand Up @@ -52,6 +46,32 @@ public ResponseList<Task> listTasks(Long projectId, Integer limit, Integer offse
return TaskResponseList.to(taskResponseList);
}

/**
* @param projectId project identifier
* @param limit maximum number of items to retrieve (default 25)
* @param offset starting offset in the collection (default 0)
* @param status filter by status
* @param assigneeId filter by assignee id
* @param orderBy sort keys and strategy
* @return list of tasks
* @see <ul>
* <li><a href="https://developer.crowdin.com/api/v2/#operation/api.projects.tasks.getMany" target="_blank"><b>API Documentation</b></a></li>
* <li><a href="https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.tasks.getMany" target="_blank"><b>Enterprise API Documentation</b></a></li>
* </ul>
*/
public ResponseList<Task> listTasks(Long projectId, Integer limit, Integer offset, Status status, Integer assigneeId, Map<String, SortOrder> orderBy) throws HttpException, HttpBadRequestException {
Comment thread
korzol marked this conversation as resolved.
Outdated
Map<String, Optional<Object>> queryParams = HttpRequestConfig.buildUrlParams(
"status", Optional.ofNullable(status),
"assigneeId", Optional.ofNullable(assigneeId),
"limit", Optional.ofNullable(limit),
"offset", Optional.ofNullable(offset),
"orderBy", Optional.ofNullable(SortOrderGenerator.generateSortParam(orderBy))
);
TaskResponseList taskResponseList = this.httpClient.get(this.url + "/projects/" + projectId + "/tasks", new HttpRequestConfig(queryParams), TaskResponseList.class);
return TaskResponseList.to(taskResponseList);
}


/**
* Lists tasks for a given project, filtered by multiple statuses.
*
Expand Down
92 changes: 87 additions & 5 deletions src/test/java/com/crowdin/client/tasks/TasksApiTest.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package com.crowdin.client.tasks;

import com.crowdin.client.core.model.DownloadLink;
import com.crowdin.client.core.model.PatchOperation;
import com.crowdin.client.core.model.PatchRequest;
import com.crowdin.client.core.model.ResponseList;
import com.crowdin.client.core.model.ResponseObject;
import com.crowdin.client.core.model.*;
import com.crowdin.client.framework.RequestMock;
import com.crowdin.client.framework.TestClient;
import com.crowdin.client.tasks.model.*;
Expand All @@ -29,6 +25,9 @@ public class TasksApiTest extends TestClient {
private final Long enterpriseProjectId = 13L;
private final Long multiStatusProjectId = 14L;
private final Long singleStatusProjectId = 15L;
private final Long tasksProjectIdSortByIdAsc = 16L;
private final Long tasksProjectIdSortByIdDesc = 17L;
private final Long tasksProjectIdSortByIdDescTitleAsc = 18L;
private final Long taskId = 2L;
private final Long prevTaskId = 1L;
private final Status status = Status.TODO;
Expand All @@ -55,6 +54,15 @@ public List<RequestMock> getMocks() {
}}),
RequestMock.build(this.url + "/projects/" + singleStatusProjectId + "/tasks", HttpGet.METHOD_NAME, "api/tasks/singleStatusListTasks.json", new HashMap<String, String>() {{
put("status", "in_progress");
}}),
RequestMock.build(this.url + "/projects/" + tasksProjectIdSortByIdAsc + "/tasks", HttpGet.METHOD_NAME, "api/tasks/listTasksSortByIdAsc.json", new HashMap<String, String>() {{
put("orderBy", "id%20asc");
}}),
RequestMock.build(this.url + "/projects/" + tasksProjectIdSortByIdDesc + "/tasks", HttpGet.METHOD_NAME, "api/tasks/listTasksSortByIdDesc.json", new HashMap<String, String>() {{
put("orderBy", "id%20desc");
}}),
RequestMock.build(this.url + "/projects/" + tasksProjectIdSortByIdDescTitleAsc + "/tasks", HttpGet.METHOD_NAME, "api/tasks/listTasksSortByIdDescTitleAsc.json", new HashMap<String, String>() {{
put("orderBy", "id%20desc%2Ctitle%20asc");
}})
);
}
Expand All @@ -72,6 +80,80 @@ public void listTasksTest() {
assertListTasks(taskResponseList);
}

@Test
public void listTasksTest_noSortDefined() {
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(projectId, null, null, null, null, null);

assertNotNull(taskResponseList.getData().get(0).getData());
assertEquals(1, taskResponseList.getData().size());

assertEquals(projectId, taskResponseList.getData().get(0).getData().getProjectId());

assertListTasks(taskResponseList);
}

@Test
public void listTasksTest_testSortByIdDefault() {
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
orderBy.put("id", null);
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdAsc, null, null, null, null, orderBy);

assertNotNull(taskResponseList.getData().get(0).getData());
assertEquals(2, taskResponseList.getData().size());

assertEquals(1, taskResponseList.getData().get(0).getData().getId());
assertEquals(2, taskResponseList.getData().get(1).getData().getId());
}

@Test
public void listTasksTest_testSortByIdAsc() {
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
orderBy.put("id", SortOrder.ASC);
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdAsc, null, null, null, null, orderBy);

assertNotNull(taskResponseList.getData().get(0).getData());
assertEquals(2, taskResponseList.getData().size());

assertEquals(1, taskResponseList.getData().get(0).getData().getId());
assertEquals(2, taskResponseList.getData().get(1).getData().getId());
}

@Test
public void listTasksTest_testSortByIdDesc() {
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
orderBy.put("id", SortOrder.DESC);

ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdDesc, null, null, null, null, orderBy);

assertNotNull(taskResponseList.getData().get(0).getData());
assertEquals(2, taskResponseList.getData().size());

assertEquals(2, taskResponseList.getData().get(0).getData().getId());
assertEquals(1, taskResponseList.getData().get(1).getData().getId());
}

@Test
public void listTasksTest_testSortByIdDescTitleAsc() {
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
orderBy.put("id", SortOrder.DESC);
orderBy.put("title", null);

ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdDescTitleAsc, null, null, null, null, orderBy);

assertNotNull(taskResponseList.getData().get(0).getData());
assertEquals(2, taskResponseList.getData().size());

assertEquals(2, taskResponseList.getData().get(0).getData().getId());
assertNotNull(taskResponseList.getData().get(0).getData().getTitle());
assertEquals("French#1", taskResponseList.getData().get(0).getData().getTitle());


assertEquals(1, taskResponseList.getData().get(1).getData().getId());
assertNotNull(taskResponseList.getData().get(1).getData().getTitle());
assertEquals("French#2", taskResponseList.getData().get(1).getData().getTitle());
}

@Test
public void listTasksTest_testSingleStatus() {
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
Expand Down
102 changes: 102 additions & 0 deletions src/test/resources/api/tasks/listTasksSortByIdAsc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"data": [
{
"data": {
"id": 1,
"projectId": 12,
"creatorId": 6,
"type": 1,
"status": "todo",
"title": "French#1",
"assignees": [
{
"id": 1,
"username": "john_smith",
"fullName": "john_smith",
"avatarUrl": "",
"wordsCount": 5,
"wordsLeft": 3
}
],
"assignedTeams": [
{
"id": 1,
"wordsCount": 5
}
],
"fileIds": [
1
],
"progress": {
"total": 24,
"done": 15,
"percent": 62
},
"sourceLanguageId": "en",
"targetLanguageId": "fr",
"description": "Proofread all French strings",
"hash": "dac37aff364d83899128e68afe0de4994",
"translationUrl": "/proofread/9092638ac9f2a2d1b5571d08edc53763/all/en-fr/10?task=dac37aff364d83899128e68afe0de4994",
"wordsCount": 24,
"filesCount": 2,
"commentsCount": 0,
"deadline": "2019-09-27T07:00:14+00:00",
"timeRange": "string",
"workflowStepId": 10,
"createdAt": "2019-09-23T09:04:29+00:00",
"updatedAt": "2019-09-23T09:04:29+00:00"
}
},
{
"data": {
"id": 2,
"projectId": 12,
"creatorId": 6,
"type": 1,
"status": "todo",
"title": "French#2",
"assignees": [
{
"id": 1,
"username": "john_smith",
"fullName": "john_smith",
"avatarUrl": "",
"wordsCount": 5,
"wordsLeft": 3
}
],
"assignedTeams": [
{
"id": 1,
"wordsCount": 5
}
],
"fileIds": [
1
],
"progress": {
"total": 24,
"done": 15,
"percent": 62
},
"sourceLanguageId": "en",
"targetLanguageId": "fr",
"description": "Proofread all French strings",
"hash": "dac37aff364d83899128e68afe0de4994",
"translationUrl": "/proofread/9092638ac9f2a2d1b5571d08edc53763/all/en-fr/10?task=dac37aff364d83899128e68afe0de4994",
"wordsCount": 24,
"filesCount": 2,
"commentsCount": 0,
"deadline": "2019-09-27T07:00:14+00:00",
"timeRange": "string",
"workflowStepId": 10,
"createdAt": "2019-09-23T09:04:29+00:00",
"updatedAt": "2019-09-23T09:04:29+00:00"
}
}
],
"pagination": {
"offset": 0,
"limit": 25
}
}
Loading
Loading