From 55d3f92771632c33afbe551a19fa6bb9d5b1ba5e Mon Sep 17 00:00:00 2001 From: KoCoder <83128842+KoCoder@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:29:17 +0200 Subject: [PATCH] Add Field Iteration to Issue (#1027) --------- Co-authored-by: Konstantin Hintermayer Co-authored-by: Jeremie Bresson --- src/main/java/org/gitlab4j/api/GroupApi.java | 19 ++ .../java/org/gitlab4j/api/ProjectApi.java | 17 ++ .../gitlab4j/api/models/AbstractIssue.java | 11 +- .../org/gitlab4j/api/models/Iteration.java | 129 ++++++++++++ .../gitlab4j/api/models/IterationFilter.java | 183 ++++++++++++++++++ .../org/gitlab4j/api/TestGitLabApiBeans.java | 19 +- .../org/gitlab4j/api/epic-issue.json | 16 +- .../resources/org/gitlab4j/api/issue.json | 16 +- .../resources/org/gitlab4j/api/iteration.json | 14 ++ 9 files changed, 415 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/gitlab4j/api/models/Iteration.java create mode 100644 src/main/java/org/gitlab4j/api/models/IterationFilter.java create mode 100644 src/test/resources/org/gitlab4j/api/iteration.json diff --git a/src/main/java/org/gitlab4j/api/GroupApi.java b/src/main/java/org/gitlab4j/api/GroupApi.java index 7a8d433c..559894a8 100644 --- a/src/main/java/org/gitlab4j/api/GroupApi.java +++ b/src/main/java/org/gitlab4j/api/GroupApi.java @@ -10,6 +10,7 @@ import java.util.stream.Stream; import javax.ws.rs.core.Form; import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import org.gitlab4j.api.GitLabApi.ApiVersion; @@ -22,6 +23,8 @@ import org.gitlab4j.api.models.Group; import org.gitlab4j.api.models.GroupFilter; import org.gitlab4j.api.models.GroupParams; import org.gitlab4j.api.models.GroupProjectsFilter; +import org.gitlab4j.api.models.Iteration; +import org.gitlab4j.api.models.IterationFilter; import org.gitlab4j.api.models.LdapGroupLink; import org.gitlab4j.api.models.Member; import org.gitlab4j.api.models.Project; @@ -1958,4 +1961,20 @@ public class GroupApi extends AbstractApi { delete(Response.Status.OK, null, "groups", getGroupIdOrPath(groupIdOrPath), "custom_attributes", key); } + + /** + * Lists group iterations. + * + *
GitLab Endpoint: GET /groups/:id/iterations
+ * + * @param groupIdOrPath the group in the form of an Long(ID), String(path), or Group instance + * @param filter the iteration filter + * @return the list of group iterations + * @throws GitLabApiException if any exception occurs + */ + public List listGroupIterations(Object groupIdOrPath, IterationFilter filter) throws GitLabApiException { + MultivaluedMap queryParams = (filter == null) ? null : filter.getQueryParams().asMap(); + Response response = get(Response.Status.OK, queryParams, "groups", getGroupIdOrPath(groupIdOrPath), "iterations"); + return (response.readEntity(new GenericType>() { })); + } } diff --git a/src/main/java/org/gitlab4j/api/ProjectApi.java b/src/main/java/org/gitlab4j/api/ProjectApi.java index 8356a9fc..a1e127da 100644 --- a/src/main/java/org/gitlab4j/api/ProjectApi.java +++ b/src/main/java/org/gitlab4j/api/ProjectApi.java @@ -48,6 +48,8 @@ import org.gitlab4j.api.models.CustomAttribute; import org.gitlab4j.api.models.Event; import org.gitlab4j.api.models.FileUpload; import org.gitlab4j.api.models.Issue; +import org.gitlab4j.api.models.Iteration; +import org.gitlab4j.api.models.IterationFilter; import org.gitlab4j.api.models.Member; import org.gitlab4j.api.models.Namespace; import org.gitlab4j.api.models.Project; @@ -4015,4 +4017,19 @@ public class ProjectApi extends AbstractApi implements Constants { delete(Response.Status.NO_CONTENT, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId); } + /** + * Lists project iterations. + * + *
GitLab Endpoint: GET /projects/:id/iterations
+ * + * @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance + * @param filter the iteration filter + * @return the list of project iterations + * @throws GitLabApiException if any exception occurs + */ + public List listProjectIterations(Object projectIdOrPath, IterationFilter filter) throws GitLabApiException { + MultivaluedMap queryParams = (filter == null) ? null : filter.getQueryParams().asMap(); + Response response = get(Response.Status.OK, queryParams, "projects", getProjectIdOrPath(projectIdOrPath), "iterations"); + return (response.readEntity(new GenericType>() { })); + } } diff --git a/src/main/java/org/gitlab4j/api/models/AbstractIssue.java b/src/main/java/org/gitlab4j/api/models/AbstractIssue.java index c7a6e2e4..d618b21d 100644 --- a/src/main/java/org/gitlab4j/api/models/AbstractIssue.java +++ b/src/main/java/org/gitlab4j/api/models/AbstractIssue.java @@ -78,6 +78,7 @@ public abstract class AbstractIssue { private Integer mergeRequestsCount; private Boolean hasTasks; private String taskStatus; + private Iteration iteration; private TaskCompletionStatus taskCompletionStatus; public Assignee getAssignee() { @@ -325,7 +326,15 @@ public abstract class AbstractIssue { this.taskStatus = taskStatus; } - public TaskCompletionStatus getTaskCompletionStatus() { + public Iteration getIteration() { + return iteration; + } + + public void setIteration(Iteration iteration) { + this.iteration = iteration; + } + + public TaskCompletionStatus getTaskCompletionStatus() { return taskCompletionStatus; } diff --git a/src/main/java/org/gitlab4j/api/models/Iteration.java b/src/main/java/org/gitlab4j/api/models/Iteration.java new file mode 100644 index 00000000..b3369d64 --- /dev/null +++ b/src/main/java/org/gitlab4j/api/models/Iteration.java @@ -0,0 +1,129 @@ +package org.gitlab4j.api.models; + +import java.util.Date; + +import org.gitlab4j.api.utils.JacksonJson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public class Iteration { + public enum IterationState { + UPCOMMING(1), CURRENT(2), CLOSED(3); + + private int value; + + IterationState(int value) { + this.value = value; + } + + @JsonCreator + public static IterationState fromIntValue(int value) { + for (IterationState it : values()) { + if(it.value == value) { + return it; + } + } + throw new IllegalArgumentException("No enum found for value: " + value); + } + + @JsonValue + public int toIntValue() { + return this.value; + } + + @Override + public String toString() { + return name(); + } + } + + private Long id; + private Long iid; + private Long sequence; + private Long groupId; + private String title; + private String description; + private IterationState state; + private Date createdAt; + private Date updatedAt; + private Date startDate; + private Date dueDate; + private String webUrl; + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public Long getIid() { + return iid; + } + public void setIid(Long iid) { + this.iid = iid; + } + public Long getSequence() { + return sequence; + } + public void setSequence(Long sequence) { + this.sequence = sequence; + } + public Long getGroupId() { + return groupId; + } + public void setGroupId(Long groupId) { + this.groupId = groupId; + } + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public IterationState getState() { + return state; + } + public void setState(IterationState state) { + this.state = state; + } + public Date getCreatedAt() { + return createdAt; + } + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + public Date getUpdatedAt() { + return updatedAt; + } + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + public Date getStartDate() { + return startDate; + } + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + public Date getDueDate() { + return dueDate; + } + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + public String getWebUrl() { + return webUrl; + } + public void setWebUrl(String webUrl) { + this.webUrl = webUrl; + } + @Override + public String toString() { + return (JacksonJson.toJsonString(this)); + } +} diff --git a/src/main/java/org/gitlab4j/api/models/IterationFilter.java b/src/main/java/org/gitlab4j/api/models/IterationFilter.java new file mode 100644 index 00000000..58c99cd6 --- /dev/null +++ b/src/main/java/org/gitlab4j/api/models/IterationFilter.java @@ -0,0 +1,183 @@ +package org.gitlab4j.api.models; + +import java.util.Date; + +import org.gitlab4j.api.Constants; +import org.gitlab4j.api.GitLabApiForm; +import org.gitlab4j.api.utils.ISO8601; +import org.gitlab4j.api.utils.JacksonJsonEnumHelper; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonValue; + +public class IterationFilter { + + public enum IterationFilterState { + OPENED, UPCOMING, CURRENT, CLOSED, ALL; + + private static JacksonJsonEnumHelper enumHelper = new JacksonJsonEnumHelper<>(IterationFilterState.class, false, true); + + @JsonCreator + public static IterationFilterState forValue(String value) { + return enumHelper.forValue(value); + } + + @JsonValue + public String toValue() { + return (enumHelper.toString(this)); + } + + @Override + public String toString() { + return (enumHelper.toString(this)); + } + } + + public enum IterationFilterIn { + TITLE, CADENCE_TITLE; + + private static JacksonJsonEnumHelper enumHelper = new JacksonJsonEnumHelper<>(IterationFilterIn.class, false, false, true); + + @JsonCreator + public static IterationFilterIn forValue(String value) { + return enumHelper.forValue(value); + } + + @JsonValue + public String toValue() { + return (enumHelper.toString(this)); + } + + @Override + public String toString() { + return (enumHelper.toString(this)); + } + } + + /** + * Return opened, upcoming, current, closed, or all iterations. + */ + private IterationFilterState state; + + /** + * Return only iterations with a title matching the provided string. + */ + private String search; + + /** + * Fields in which fuzzy search should be performed with the query given in the argument search. + */ + private IterationFilterIn in; + + /** + * Include iterations from parent group and its ancestors. Defaults to true. + */ + private Boolean includeAncestors; + + /** + * Return iterations updated after the specified date. Expected in ISO 8601 format (2019-03-15T08:00:00Z). + */ + private Date updatedAfter; + + /** + * Return iterations updated before the specified date. Expected in ISO 8601 format (2019-03-15T08:00:00Z). + */ + private Date updatedBefore; + + public IterationFilterState getState() { + return state; + } + + public void setState(IterationFilterState state) { + this.state = state; + } + + public String getSearch() { + return search; + } + + public void setSearch(String search) { + this.search = search; + } + + public IterationFilterIn getIn() { + return in; + } + + public void setIn(IterationFilterIn in) { + this.in = in; + } + + public Boolean getIncludeAncestors() { + return includeAncestors; + } + + public void setIncludeAncestors(Boolean includeAncestors) { + this.includeAncestors = includeAncestors; + } + + public Date getUpdatedAfter() { + return updatedAfter; + } + + public void setUpdatedAfter(Date updatedAfter) { + this.updatedAfter = updatedAfter; + } + + public Date getUpdatedBefore() { + return updatedBefore; + } + + public void setUpdatedBefore(Date updatedBefore) { + this.updatedBefore = updatedBefore; + } + + public IterationFilter withState(IterationFilterState state) { + this.state = state; + return (this); + } + + public IterationFilter withSearch(String search) { + this.search = search; + return (this); + } + + public IterationFilter withIn(IterationFilterIn in) { + this.in = in; + return (this); + } + + public IterationFilter withIncludeAncestors(Boolean includeAncestors) { + this.includeAncestors = includeAncestors; + return (this); + } + + public IterationFilter withUpdatedAfter(Date updatedAfter) { + this.updatedAfter = updatedAfter; + return (this); + } + + public IterationFilter withUpdatedBefore(Date updatedBefore) { + this.updatedBefore = updatedBefore; + return (this); + } + + @JsonIgnore + public GitLabApiForm getQueryParams(int page, int perPage) { + return (getQueryParams() + .withParam(Constants.PAGE_PARAM, page) + .withParam(Constants.PER_PAGE_PARAM, perPage)); + } + + @JsonIgnore + public GitLabApiForm getQueryParams() { + return new GitLabApiForm() + .withParam("state", state) + .withParam("search", search) + .withParam("in", in) + .withParam("include_ancestors", includeAncestors) + .withParam("updated_after", ISO8601.toString(updatedAfter, false)) + .withParam("updated_before", ISO8601.toString(updatedBefore, false)); + } +} diff --git a/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java b/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java index 1ccb062a..fcffc9de 100644 --- a/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java +++ b/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java @@ -79,6 +79,7 @@ import org.gitlab4j.api.models.ImportStatus; import org.gitlab4j.api.models.Issue; import org.gitlab4j.api.models.IssueLink; import org.gitlab4j.api.models.IssuesStatistics; +import org.gitlab4j.api.models.Iteration; import org.gitlab4j.api.models.Job; import org.gitlab4j.api.models.Key; import org.gitlab4j.api.models.Label; @@ -244,7 +245,7 @@ public class TestGitLabApiBeans { @Test public void testDeployment() throws Exception { - Deployment deployment = unmarshalResource(Deployment.class, "deployment.json"); + Deployment deployment = unmarshalResource(Deployment.class, "deployment.json"); assertTrue(compareJson(deployment, "deployment.json")); } @@ -346,7 +347,7 @@ public class TestGitLabApiBeans { @Test public void testGpgSignature() throws Exception { - GpgSignature gpgSignature = unmarshalResource(GpgSignature.class, "gpg-signature.json"); + GpgSignature gpgSignature = unmarshalResource(GpgSignature.class, "gpg-signature.json"); assertTrue(compareJson(gpgSignature, "gpg-signature.json")); } @@ -544,7 +545,7 @@ public class TestGitLabApiBeans { @Test public void testProjectApprovalsCofig() throws Exception { - ProjectApprovalsConfig approvalsConfig = unmarshalResource(ProjectApprovalsConfig.class, "project-approvals-config.json"); + ProjectApprovalsConfig approvalsConfig = unmarshalResource(ProjectApprovalsConfig.class, "project-approvals-config.json"); assertTrue(compareJson(approvalsConfig, "project-approvals-config.json")); } @@ -592,7 +593,7 @@ public class TestGitLabApiBeans { @Test public void testRemoteMirror() throws Exception { - RemoteMirror remoteMirror = unmarshalResource(RemoteMirror.class, "remote-mirror.json"); + RemoteMirror remoteMirror = unmarshalResource(RemoteMirror.class, "remote-mirror.json"); assertTrue(compareJson(remoteMirror, "remote-mirror.json")); } @@ -610,7 +611,7 @@ public class TestGitLabApiBeans { @Test public void testSettings() throws Exception { - JsonNode json = readTreeFromResource("application-settings.json"); + JsonNode json = readTreeFromResource("application-settings.json"); ApplicationSettings applicationSettings = ApplicationSettingsApi.parseApplicationSettings(json); assertTrue(compareJson(applicationSettings.getSettings(), "application-settings.json")); } @@ -653,7 +654,7 @@ public class TestGitLabApiBeans { @Test public void testMergeRequestApprovalRule() throws Exception { - ApprovalRule approvalRule = unmarshalResource(ApprovalRule.class, "approval-rule.json"); + ApprovalRule approvalRule = unmarshalResource(ApprovalRule.class, "approval-rule.json"); assertTrue(compareJson(approvalRule, "approval-rule.json")); } @@ -783,6 +784,12 @@ public class TestGitLabApiBeans { assertTrue(compareJson(token, "impersonation-token.json")); } + @Test + public void testIteration() throws Exception { + Iteration token = unmarshalResource(Iteration.class, "iteration.json"); + assertTrue(compareJson(token, "iteration.json")); + } + @Test public void testOauthToken() throws Exception { OauthTokenResponse token = unmarshalResource(OauthTokenResponse.class, "oauth-token.json"); diff --git a/src/test/resources/org/gitlab4j/api/epic-issue.json b/src/test/resources/org/gitlab4j/api/epic-issue.json index 10561292..feb959e3 100644 --- a/src/test/resources/org/gitlab4j/api/epic-issue.json +++ b/src/test/resources/org/gitlab4j/api/epic-issue.json @@ -61,5 +61,19 @@ }, "subscribed": true, "epic_issue_id": 2, - "relative_position": 55 + "relative_position": 55, + "iteration": { + "id": 158, + "iid": 34, + "sequence": 9, + "group_id": 114, + "title": "title", + "description": "description", + "state": 3, + "created_at": "2023-08-07T00:05:06.739Z", + "updated_at": "2023-09-04T00:05:06.612Z", + "start_date": "2023-09-04T00:00:00Z", + "due_date": "2023-09-10T00:00:00Z", + "web_url": "http://example.com/example/-/iterations/158" + } } \ No newline at end of file diff --git a/src/test/resources/org/gitlab4j/api/issue.json b/src/test/resources/org/gitlab4j/api/issue.json index b1757ce0..d3aaa0ca 100644 --- a/src/test/resources/org/gitlab4j/api/issue.json +++ b/src/test/resources/org/gitlab4j/api/issue.json @@ -58,5 +58,19 @@ "task_completion_status":{ "count": 0, "completed_count": 0 - } + }, + "iteration": { + "id": 158, + "iid": 34, + "sequence": 9, + "group_id": 114, + "title": "title", + "description": "description", + "state": 1, + "created_at": "2023-08-07T00:05:06.739Z", + "updated_at": "2023-09-04T00:05:06.612Z", + "start_date": "2023-09-04T00:00:00Z", + "due_date": "2023-09-10T00:00:00Z", + "web_url": "http://example.com/example/-/iterations/158" + } } \ No newline at end of file diff --git a/src/test/resources/org/gitlab4j/api/iteration.json b/src/test/resources/org/gitlab4j/api/iteration.json new file mode 100644 index 00000000..30cd7678 --- /dev/null +++ b/src/test/resources/org/gitlab4j/api/iteration.json @@ -0,0 +1,14 @@ +{ + "id": 90, + "iid": 4, + "sequence": 2, + "group_id": 162, + "title": "title", + "description": "description", + "state": 2, + "created_at": "2022-03-14T05:21:11.929Z", + "updated_at": "2022-03-14T05:21:11.929Z", + "start_date": "2022-03-08T00:00:00Z", + "due_date": "2022-03-14T00:00:00Z", + "web_url": "https://gitlab.com/groups/my-group/-/iterations/90" +} -- GitLab