Skip to content

Commit c7c3e8c

Browse files
committed
wip: Initial support for multi-sort
1 parent faefb3d commit c7c3e8c

8 files changed

Lines changed: 479 additions & 13 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.crowdin.client.core.http.impl.util;
2+
3+
import java.io.UnsupportedEncodingException;
4+
import java.net.URLEncoder;
5+
6+
public class RequestEncoder {
7+
public static String encodeSpaces(String url) {
8+
try {
9+
return URLEncoder.encode(url, "UTF-8").replaceAll("\\+", "%20");
10+
} catch (UnsupportedEncodingException e) {
11+
return null;
12+
}
13+
}
14+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.crowdin.client.core.http.impl.util;
2+
3+
import java.util.Map;
4+
import java.util.stream.Collectors;
5+
6+
import com.crowdin.client.core.model.SortOrder;
7+
8+
public class SortOrderGenerator {
9+
public static String generateSortParam(Map<String, SortOrder> orderByMap) {
10+
if (orderByMap == null || orderByMap.isEmpty()) {
11+
return null;
12+
}
13+
14+
String sortParam = orderByMap.entrySet().stream()
15+
.map(entry -> {
16+
if (entry.getValue() == null) {
17+
entry.setValue(SortOrder.ASC);
18+
}
19+
20+
return entry.getKey() + " " + entry.getValue().to(entry.getValue());
21+
})
22+
.collect(Collectors.joining(","));
23+
24+
return RequestEncoder.encodeSpaces(sortParam);
25+
26+
27+
}
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.crowdin.client.core.model;
2+
3+
public enum SortOrder implements EnumConverter<SortOrder> {
4+
ASC, DESC;
5+
6+
public static SortOrder from(String value) {
7+
if (value == null) return ASC;
8+
9+
return SortOrder.valueOf(value.toUpperCase());
10+
}
11+
12+
@Override
13+
public String to(SortOrder v) {
14+
return (v != null ? v : ASC).name().toLowerCase();
15+
}
16+
}

src/main/java/com/crowdin/client/tasks/TasksApi.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,8 @@
44
import com.crowdin.client.core.http.HttpRequestConfig;
55
import com.crowdin.client.core.http.exceptions.HttpBadRequestException;
66
import com.crowdin.client.core.http.exceptions.HttpException;
7-
import com.crowdin.client.core.model.BooleanInt;
8-
import com.crowdin.client.core.model.ClientConfig;
9-
import com.crowdin.client.core.model.Credentials;
10-
import com.crowdin.client.core.model.DownloadLink;
11-
import com.crowdin.client.core.model.DownloadLinkResponseObject;
12-
import com.crowdin.client.core.model.PatchRequest;
13-
import com.crowdin.client.core.model.ResponseList;
14-
import com.crowdin.client.core.model.ResponseObject;
7+
import com.crowdin.client.core.http.impl.util.SortOrderGenerator;
8+
import com.crowdin.client.core.model.*;
159
import com.crowdin.client.tasks.model.*;
1610

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

49+
/**
50+
* @param projectId project identifier
51+
* @param limit maximum number of items to retrieve (default 25)
52+
* @param offset starting offset in the collection (default 0)
53+
* @param status filter by status
54+
* @param assigneeId filter by assignee id
55+
* @param orderBy sort keys and strategy
56+
* @return list of tasks
57+
* @see <ul>
58+
* <li><a href="https://developer.crowdin.com/api/v2/#operation/api.projects.tasks.getMany" target="_blank"><b>API Documentation</b></a></li>
59+
* <li><a href="https://developer.crowdin.com/enterprise/api/v2/#operation/api.projects.tasks.getMany" target="_blank"><b>Enterprise API Documentation</b></a></li>
60+
* </ul>
61+
*/
62+
public ResponseList<Task> listTasks(Long projectId, Integer limit, Integer offset, Status status, Integer assigneeId, Map<String, SortOrder> orderBy) throws HttpException, HttpBadRequestException {
63+
Map<String, Optional<Object>> queryParams = HttpRequestConfig.buildUrlParams(
64+
"status", Optional.ofNullable(status),
65+
"assigneeId", Optional.ofNullable(assigneeId),
66+
"limit", Optional.ofNullable(limit),
67+
"offset", Optional.ofNullable(offset),
68+
"orderBy", Optional.ofNullable(SortOrderGenerator.generateSortParam(orderBy))
69+
);
70+
TaskResponseList taskResponseList = this.httpClient.get(this.url + "/projects/" + projectId + "/tasks", new HttpRequestConfig(queryParams), TaskResponseList.class);
71+
return TaskResponseList.to(taskResponseList);
72+
}
73+
74+
5575
/**
5676
* Lists tasks for a given project, filtered by multiple statuses.
5777
*

src/test/java/com/crowdin/client/tasks/TasksApiTest.java

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package com.crowdin.client.tasks;
22

3-
import com.crowdin.client.core.model.DownloadLink;
4-
import com.crowdin.client.core.model.PatchOperation;
5-
import com.crowdin.client.core.model.PatchRequest;
6-
import com.crowdin.client.core.model.ResponseList;
7-
import com.crowdin.client.core.model.ResponseObject;
3+
import com.crowdin.client.core.model.*;
84
import com.crowdin.client.framework.RequestMock;
95
import com.crowdin.client.framework.TestClient;
106
import com.crowdin.client.tasks.model.*;
@@ -29,6 +25,9 @@ public class TasksApiTest extends TestClient {
2925
private final Long enterpriseProjectId = 13L;
3026
private final Long multiStatusProjectId = 14L;
3127
private final Long singleStatusProjectId = 15L;
28+
private final Long tasksProjectIdSortByIdAsc = 16L;
29+
private final Long tasksProjectIdSortByIdDesc = 17L;
30+
private final Long tasksProjectIdSortByIdDescTitleAsc = 18L;
3231
private final Long taskId = 2L;
3332
private final Long prevTaskId = 1L;
3433
private final Status status = Status.TODO;
@@ -55,6 +54,15 @@ public List<RequestMock> getMocks() {
5554
}}),
5655
RequestMock.build(this.url + "/projects/" + singleStatusProjectId + "/tasks", HttpGet.METHOD_NAME, "api/tasks/singleStatusListTasks.json", new HashMap<String, String>() {{
5756
put("status", "in_progress");
57+
}}),
58+
RequestMock.build(this.url + "/projects/" + tasksProjectIdSortByIdAsc + "/tasks", HttpGet.METHOD_NAME, "api/tasks/listTasksSortByIdAsc.json", new HashMap<String, String>() {{
59+
put("orderBy", "id%20asc");
60+
}}),
61+
RequestMock.build(this.url + "/projects/" + tasksProjectIdSortByIdDesc + "/tasks", HttpGet.METHOD_NAME, "api/tasks/listTasksSortByIdDesc.json", new HashMap<String, String>() {{
62+
put("orderBy", "id%20desc");
63+
}}),
64+
RequestMock.build(this.url + "/projects/" + tasksProjectIdSortByIdDescTitleAsc + "/tasks", HttpGet.METHOD_NAME, "api/tasks/listTasksSortByIdDescTitleAsc.json", new HashMap<String, String>() {{
65+
put("orderBy", "id%20desc%2Ctitle%20asc");
5866
}})
5967
);
6068
}
@@ -72,6 +80,80 @@ public void listTasksTest() {
7280
assertListTasks(taskResponseList);
7381
}
7482

83+
@Test
84+
public void listTasksTest_noSortDefined() {
85+
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
86+
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(projectId, null, null, null, null, null);
87+
88+
assertNotNull(taskResponseList.getData().get(0).getData());
89+
assertEquals(1, taskResponseList.getData().size());
90+
91+
assertEquals(projectId, taskResponseList.getData().get(0).getData().getProjectId());
92+
93+
assertListTasks(taskResponseList);
94+
}
95+
96+
@Test
97+
public void listTasksTest_testSortByIdDefault() {
98+
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
99+
orderBy.put("id", null);
100+
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdAsc, null, null, null, null, orderBy);
101+
102+
assertNotNull(taskResponseList.getData().get(0).getData());
103+
assertEquals(2, taskResponseList.getData().size());
104+
105+
assertEquals(1, taskResponseList.getData().get(0).getData().getId());
106+
assertEquals(2, taskResponseList.getData().get(1).getData().getId());
107+
}
108+
109+
@Test
110+
public void listTasksTest_testSortByIdAsc() {
111+
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
112+
orderBy.put("id", SortOrder.ASC);
113+
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdAsc, null, null, null, null, orderBy);
114+
115+
assertNotNull(taskResponseList.getData().get(0).getData());
116+
assertEquals(2, taskResponseList.getData().size());
117+
118+
assertEquals(1, taskResponseList.getData().get(0).getData().getId());
119+
assertEquals(2, taskResponseList.getData().get(1).getData().getId());
120+
}
121+
122+
@Test
123+
public void listTasksTest_testSortByIdDesc() {
124+
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
125+
orderBy.put("id", SortOrder.DESC);
126+
127+
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdDesc, null, null, null, null, orderBy);
128+
129+
assertNotNull(taskResponseList.getData().get(0).getData());
130+
assertEquals(2, taskResponseList.getData().size());
131+
132+
assertEquals(2, taskResponseList.getData().get(0).getData().getId());
133+
assertEquals(1, taskResponseList.getData().get(1).getData().getId());
134+
}
135+
136+
@Test
137+
public void listTasksTest_testSortByIdDescTitleAsc() {
138+
Map<String, SortOrder> orderBy = new LinkedHashMap<>();
139+
orderBy.put("id", SortOrder.DESC);
140+
orderBy.put("title", null);
141+
142+
ResponseList<Task> taskResponseList = this.getTasksApi().listTasks(tasksProjectIdSortByIdDescTitleAsc, null, null, null, null, orderBy);
143+
144+
assertNotNull(taskResponseList.getData().get(0).getData());
145+
assertEquals(2, taskResponseList.getData().size());
146+
147+
assertEquals(2, taskResponseList.getData().get(0).getData().getId());
148+
assertNotNull(taskResponseList.getData().get(0).getData().getTitle());
149+
assertEquals("French#1", taskResponseList.getData().get(0).getData().getTitle());
150+
151+
152+
assertEquals(1, taskResponseList.getData().get(1).getData().getId());
153+
assertNotNull(taskResponseList.getData().get(1).getData().getTitle());
154+
assertEquals("French#2", taskResponseList.getData().get(1).getData().getTitle());
155+
}
156+
75157
@Test
76158
public void listTasksTest_testSingleStatus() {
77159
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{
2+
"data": [
3+
{
4+
"data": {
5+
"id": 1,
6+
"projectId": 12,
7+
"creatorId": 6,
8+
"type": 1,
9+
"status": "todo",
10+
"title": "French#1",
11+
"assignees": [
12+
{
13+
"id": 1,
14+
"username": "john_smith",
15+
"fullName": "john_smith",
16+
"avatarUrl": "",
17+
"wordsCount": 5,
18+
"wordsLeft": 3
19+
}
20+
],
21+
"assignedTeams": [
22+
{
23+
"id": 1,
24+
"wordsCount": 5
25+
}
26+
],
27+
"fileIds": [
28+
1
29+
],
30+
"progress": {
31+
"total": 24,
32+
"done": 15,
33+
"percent": 62
34+
},
35+
"sourceLanguageId": "en",
36+
"targetLanguageId": "fr",
37+
"description": "Proofread all French strings",
38+
"hash": "dac37aff364d83899128e68afe0de4994",
39+
"translationUrl": "/proofread/9092638ac9f2a2d1b5571d08edc53763/all/en-fr/10?task=dac37aff364d83899128e68afe0de4994",
40+
"wordsCount": 24,
41+
"filesCount": 2,
42+
"commentsCount": 0,
43+
"deadline": "2019-09-27T07:00:14+00:00",
44+
"timeRange": "string",
45+
"workflowStepId": 10,
46+
"createdAt": "2019-09-23T09:04:29+00:00",
47+
"updatedAt": "2019-09-23T09:04:29+00:00"
48+
}
49+
},
50+
{
51+
"data": {
52+
"id": 2,
53+
"projectId": 12,
54+
"creatorId": 6,
55+
"type": 1,
56+
"status": "todo",
57+
"title": "French#2",
58+
"assignees": [
59+
{
60+
"id": 1,
61+
"username": "john_smith",
62+
"fullName": "john_smith",
63+
"avatarUrl": "",
64+
"wordsCount": 5,
65+
"wordsLeft": 3
66+
}
67+
],
68+
"assignedTeams": [
69+
{
70+
"id": 1,
71+
"wordsCount": 5
72+
}
73+
],
74+
"fileIds": [
75+
1
76+
],
77+
"progress": {
78+
"total": 24,
79+
"done": 15,
80+
"percent": 62
81+
},
82+
"sourceLanguageId": "en",
83+
"targetLanguageId": "fr",
84+
"description": "Proofread all French strings",
85+
"hash": "dac37aff364d83899128e68afe0de4994",
86+
"translationUrl": "/proofread/9092638ac9f2a2d1b5571d08edc53763/all/en-fr/10?task=dac37aff364d83899128e68afe0de4994",
87+
"wordsCount": 24,
88+
"filesCount": 2,
89+
"commentsCount": 0,
90+
"deadline": "2019-09-27T07:00:14+00:00",
91+
"timeRange": "string",
92+
"workflowStepId": 10,
93+
"createdAt": "2019-09-23T09:04:29+00:00",
94+
"updatedAt": "2019-09-23T09:04:29+00:00"
95+
}
96+
}
97+
],
98+
"pagination": {
99+
"offset": 0,
100+
"limit": 25
101+
}
102+
}

0 commit comments

Comments
 (0)