Commit 88f29e83 authored by Greg Messner's avatar Greg Messner
Browse files

Added suppot for project Custom Attributes API (#579)

parent 9a32d31f
......@@ -29,6 +29,7 @@ import java.net.URLEncoder;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
......@@ -44,6 +45,7 @@ import org.gitlab4j.api.models.ApprovalRule;
import org.gitlab4j.api.models.ApprovalRuleParams;
import org.gitlab4j.api.models.AuditEvent;
import org.gitlab4j.api.models.Badge;
import org.gitlab4j.api.models.CustomAttribute;
import org.gitlab4j.api.models.Event;
import org.gitlab4j.api.models.FileUpload;
import org.gitlab4j.api.models.Issue;
......@@ -69,9 +71,9 @@ import org.gitlab4j.api.utils.ISO8601;
* @see <a href="https://docs.gitlab.com/ce/api/members.html">Group and project members API at GitLab</a>
* @see <a href="https://docs.gitlab.com/ce/api/access_requests.html#group-and-project-access-requests-api">Group and project access requests API</a>
* @see <a href="https://docs.gitlab.com/ee/api/project_badges.html">Project badges API</a>
* @see <a href="https://docs.gitlab.com/ce/api/merge_request_approvals.html">
* @see <a href="https://docs.gitlab.com/ee/api/audit_events.html#retrieve-all-project-audit-events">Project audit events API</a>
* Merge request approvals API (Project-level) at GitLab</a>
* @see <a href="https://docs.gitlab.com/ce/api/merge_request_approvals.html"> * Merge request approvals API (Project-level) at GitLab</a>
* @see <a href="https://docs.gitlab.com/ce/api/audit_events.html#retrieve-all-project-audit-events">Project audit events API</a>
* @see <a href="https://docs.gitlab.com/ce/api/custom_attributes.html">Custom Attributes API</a>
*/
public class ProjectApi extends AbstractApi implements Constants {
......@@ -103,9 +105,8 @@ public class ProjectApi extends AbstractApi implements Constants {
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required
* @return an Optional instance with the value for the project fetch statistics for the last 30 day
* @throws GitLabApiException if any exception occurs during execution
*/
public Optional<ProjectFetches> getOptionalProjectStatistics(Object projectIdOrPath) throws GitLabApiException {
public Optional<ProjectFetches> getOptionalProjectStatistics(Object projectIdOrPath) {
try {
return (Optional.ofNullable(getProjectStatistics(projectIdOrPath)));
} catch (GitLabApiException glae) {
......@@ -3446,4 +3447,123 @@ public class ProjectApi extends AbstractApi implements Constants {
delete(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "approval_rules", approvalRuleId);
}
/**
* Get all custom attributes for the specified project.
*
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes</code></pre>
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
* @return a list of project's CustomAttributes
* @throws GitLabApiException if any exception occurs
*/
public List<CustomAttribute> getCustomAttributes(final Object projectIdOrPath) throws GitLabApiException {
return (getCustomAttributes(projectIdOrPath, getDefaultPerPage()).all());
}
/**
* Get a Pager of custom attributes for the specified project.
*
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes</code></pre>
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
* @param itemsPerPage the number of items per page
* @return a Pager of project's custom attributes
* @throws GitLabApiException if any exception occurs
*/
public Pager<CustomAttribute> getCustomAttributes(final Object projectIdOrPath, int itemsPerPage) throws GitLabApiException {
return (new Pager<CustomAttribute>(this, CustomAttribute.class, itemsPerPage, null,
"projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes"));
}
/**
* Get a Stream of all custom attributes for the specified project.
*
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes</code></pre>
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
* @return a Stream of project's custom attributes
* @throws GitLabApiException if any exception occurs
*/
public Stream<CustomAttribute> getCustomAttributesStream(final Object projectIdOrPath) throws GitLabApiException {
return (getCustomAttributes(projectIdOrPath, getDefaultPerPage()).stream());
}
/**
* Get a single custom attribute for the specified project.
*
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes/:key</code></pre>
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
* @param key the key for the custom attribute
* @return a CustomAttribute instance for the specified key
* @throws GitLabApiException if any exception occurs
*/
public CustomAttribute getCustomAttribute(final Object projectIdOrPath, final String key) throws GitLabApiException {
Response response = get(Response.Status.OK, null,
"projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes", key);
return (response.readEntity(CustomAttribute.class));
}
/**
* Get an Optional instance with the value for a single custom attribute for the specified project.
*
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes/:key</code></pre>
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required
* @param key the key for the custom attribute, required
* @return an Optional instance with the value for a single custom attribute for the specified project
*/
public Optional<CustomAttribute> geOptionalCustomAttribute(final Object projectIdOrPath, final String key) {
try {
return (Optional.ofNullable(getCustomAttribute(projectIdOrPath, key)));
} catch (GitLabApiException glae) {
return (GitLabApi.createOptionalFromException(glae));
}
}
/**
* Set a custom attribute for the specified project. The attribute will be updated if it already exists,
* or newly created otherwise.
*
* <pre><code>GitLab Endpoint: PUT /projects/:id/custom_attributes/:key</code></pre>
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
* @param key the key for the custom attribute
* @param value the value for the customAttribute
* @return a CustomAttribute instance for the updated or created custom attribute
* @throws GitLabApiException if any exception occurs
*/
public CustomAttribute setCustomAttribute(final Object projectIdOrPath, final String key, final String value) throws GitLabApiException {
if (Objects.isNull(key) || key.trim().isEmpty()) {
throw new IllegalArgumentException("Key cannot be null or empty");
}
if (Objects.isNull(value) || value.trim().isEmpty()) {
throw new IllegalArgumentException("Value cannot be null or empty");
}
GitLabApiForm formData = new GitLabApiForm().withParam("value", value);
Response response = putWithFormData(Response.Status.OK, formData,
"projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes", key);
return (response.readEntity(CustomAttribute.class));
}
/**
* Delete a custom attribute for the specified project.
*
* <pre><code>GitLab Endpoint: DELETE /projects/:id/custom_attributes/:key</code></pre>
*
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
* @param key the key of the custom attribute to delete
* @throws GitLabApiException if any exception occurs
*/
public void deleteCustomAttribute(final Object projectIdOrPath, final String key) throws GitLabApiException {
if (Objects.isNull(key) || key.trim().isEmpty()) {
throw new IllegalArgumentException("Key can't be null or empty");
}
delete(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes", key);
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 Greg Messner <greg@messners.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.gitlab4j.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.gitlab4j.api.models.CustomAttribute;
import org.gitlab4j.api.models.Project;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* In order for these tests to run you must set the following properties in ~/test-gitlab4j.properties
*
* TEST_NAMESPACE
* TEST_PROJECT_NAME
* TEST_HOST_URL
* TEST_PRIVATE_TOKEN
*
* If any of the above are NULL, all tests in this class will be skipped.
*/
@Category(IntegrationTest.class)
public class TestProjectCustomAttributes extends AbstractIntegrationTest {
private static final String TEST_CUSTOM_ATTRIBUTE_KEY = "GitLab4JCustomAttributeTestKey";
private static final String TEST_CUSTOM_ATTRIBUTE_VALUE = "CustomAttributeValue";
private static GitLabApi gitLabApi;
private static Project testProject;
public TestProjectCustomAttributes() {
super();
}
@BeforeClass
public static void setup() {
// Must setup the connection to the GitLab test server and get the test Project instance
gitLabApi = baseTestSetup();
testProject = getTestProject();
deleteAllTestCustomAttributes();
}
@AfterClass
public static void teardown() throws GitLabApiException {
deleteAllTestCustomAttributes();
}
private static void deleteAllTestCustomAttributes() {
if (gitLabApi != null) {
try {
List<CustomAttribute> customAttributes = gitLabApi.getProjectApi().getCustomAttributes(testProject);
if (customAttributes != null) {
for (CustomAttribute customAttribute : customAttributes) {
if (customAttribute.getKey().startsWith(TEST_CUSTOM_ATTRIBUTE_KEY)) {
gitLabApi.getProjectApi().deleteCustomAttribute(testProject, customAttribute.getKey());
}
}
}
} catch (GitLabApiException ignore) {
}
}
}
@Before
public void beforeMethod() {
assumeNotNull(gitLabApi);
}
private CustomAttribute createCustomAttribute(String key, String value) throws GitLabApiException {
return (gitLabApi.getProjectApi().setCustomAttribute(testProject, key, value));
}
@Test
public void testCreate() throws GitLabApiException {
CustomAttribute customAttribute = createCustomAttribute(TEST_CUSTOM_ATTRIBUTE_KEY, TEST_CUSTOM_ATTRIBUTE_VALUE);
assertNotNull(customAttribute);
assertEquals(TEST_CUSTOM_ATTRIBUTE_KEY, customAttribute.getKey());
assertEquals(TEST_CUSTOM_ATTRIBUTE_VALUE, customAttribute.getValue());
}
@Test
public void testUpdate() throws GitLabApiException {
assumeNotNull(testProject);
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestUpdate";
String value = TEST_CUSTOM_ATTRIBUTE_VALUE;
CustomAttribute customAttribute = createCustomAttribute(key, value);
assertNotNull(customAttribute);
assertEquals(key, customAttribute.getKey());
assertEquals(value, customAttribute.getValue());
value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (updated)";
customAttribute = gitLabApi.getProjectApi().setCustomAttribute(testProject, key, value);
assertEquals(key, customAttribute.getKey());
assertEquals(value, customAttribute.getValue());
}
@Test
public void testGetCustomAttribute() throws GitLabApiException {
assumeNotNull(testProject);
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestGet";
String value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (test get)";
CustomAttribute newCustomAttribute = createCustomAttribute(key, value);
assertNotNull(newCustomAttribute);
Optional<CustomAttribute> customAttribute =
gitLabApi.getProjectApi().geOptionalCustomAttribute(testProject, key);
assertTrue(customAttribute.isPresent());
assertEquals(key, customAttribute.get().getKey());
assertEquals(value, customAttribute.get().getValue());
}
@Test
public void testListCustomAttributes() throws GitLabApiException {
assumeNotNull(testProject);
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestList";
String value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (test list)";
CustomAttribute newCustomAttribute = createCustomAttribute(key, value);
assertNotNull(newCustomAttribute);
List<CustomAttribute> customAttributes = gitLabApi.getProjectApi().getCustomAttributes(testProject);
assertNotNull(customAttributes);
for (CustomAttribute customAttribute : customAttributes) {
if (key.equals(customAttribute.getKey())) {
assertEquals(value, customAttribute.getValue());
break;
}
}
}
@Test
public void testDeleteCustomAttribute() throws GitLabApiException {
assumeNotNull(testProject);
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestDelete";
String value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (test delete)";
createCustomAttribute(key, value);
Stream<CustomAttribute> stream = gitLabApi.getProjectApi().getCustomAttributesStream(testProject);
Optional<CustomAttribute> match = stream.filter(c -> c.getKey().equals(key)).findFirst();
assertTrue(match.isPresent());
gitLabApi.getProjectApi().deleteCustomAttribute(testProject, key);
stream = gitLabApi.getProjectApi().getCustomAttributesStream(testProject);
match = stream.filter(c -> c.getKey().equals(key)).findFirst();
assertFalse(match.isPresent());
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment