Skip to content

Commit 88f29e8

Browse files
committed
Added suppot for project Custom Attributes API (#579)
1 parent 9a32d31 commit 88f29e8

File tree

2 files changed

+315
-5
lines changed

2 files changed

+315
-5
lines changed

src/main/java/org/gitlab4j/api/ProjectApi.java

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Date;
3030
import java.util.List;
3131
import java.util.Map;
32+
import java.util.Objects;
3233
import java.util.Optional;
3334
import java.util.stream.Stream;
3435

@@ -44,6 +45,7 @@
4445
import org.gitlab4j.api.models.ApprovalRuleParams;
4546
import org.gitlab4j.api.models.AuditEvent;
4647
import org.gitlab4j.api.models.Badge;
48+
import org.gitlab4j.api.models.CustomAttribute;
4749
import org.gitlab4j.api.models.Event;
4850
import org.gitlab4j.api.models.FileUpload;
4951
import org.gitlab4j.api.models.Issue;
@@ -69,9 +71,9 @@
6971
* @see <a href="https://docs.gitlab.com/ce/api/members.html">Group and project members API at GitLab</a>
7072
* @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>
7173
* @see <a href="https://docs.gitlab.com/ee/api/project_badges.html">Project badges API</a>
72-
* @see <a href="https://docs.gitlab.com/ce/api/merge_request_approvals.html">
73-
* @see <a href="https://docs.gitlab.com/ee/api/audit_events.html#retrieve-all-project-audit-events">Project audit events API</a>
74-
* Merge request approvals API (Project-level) at GitLab</a>
74+
* @see <a href="https://docs.gitlab.com/ce/api/merge_request_approvals.html"> * Merge request approvals API (Project-level) at GitLab</a>
75+
* @see <a href="https://docs.gitlab.com/ce/api/audit_events.html#retrieve-all-project-audit-events">Project audit events API</a>
76+
* @see <a href="https://docs.gitlab.com/ce/api/custom_attributes.html">Custom Attributes API</a>
7577
*/
7678
public class ProjectApi extends AbstractApi implements Constants {
7779

@@ -103,9 +105,8 @@ public ProjectFetches getProjectStatistics(Object projectIdOrPath) throws GitLab
103105
*
104106
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required
105107
* @return an Optional instance with the value for the project fetch statistics for the last 30 day
106-
* @throws GitLabApiException if any exception occurs during execution
107108
*/
108-
public Optional<ProjectFetches> getOptionalProjectStatistics(Object projectIdOrPath) throws GitLabApiException {
109+
public Optional<ProjectFetches> getOptionalProjectStatistics(Object projectIdOrPath) {
109110
try {
110111
return (Optional.ofNullable(getProjectStatistics(projectIdOrPath)));
111112
} catch (GitLabApiException glae) {
@@ -3446,4 +3447,123 @@ public void deleteApprovalRule(Object projectIdOrPath, Integer approvalRuleId) t
34463447

34473448
delete(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "approval_rules", approvalRuleId);
34483449
}
3450+
3451+
/**
3452+
* Get all custom attributes for the specified project.
3453+
*
3454+
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes</code></pre>
3455+
*
3456+
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
3457+
* @return a list of project's CustomAttributes
3458+
* @throws GitLabApiException if any exception occurs
3459+
*/
3460+
public List<CustomAttribute> getCustomAttributes(final Object projectIdOrPath) throws GitLabApiException {
3461+
return (getCustomAttributes(projectIdOrPath, getDefaultPerPage()).all());
3462+
}
3463+
3464+
/**
3465+
* Get a Pager of custom attributes for the specified project.
3466+
*
3467+
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes</code></pre>
3468+
*
3469+
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
3470+
* @param itemsPerPage the number of items per page
3471+
* @return a Pager of project's custom attributes
3472+
* @throws GitLabApiException if any exception occurs
3473+
*/
3474+
public Pager<CustomAttribute> getCustomAttributes(final Object projectIdOrPath, int itemsPerPage) throws GitLabApiException {
3475+
return (new Pager<CustomAttribute>(this, CustomAttribute.class, itemsPerPage, null,
3476+
"projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes"));
3477+
}
3478+
3479+
/**
3480+
* Get a Stream of all custom attributes for the specified project.
3481+
*
3482+
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes</code></pre>
3483+
*
3484+
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
3485+
* @return a Stream of project's custom attributes
3486+
* @throws GitLabApiException if any exception occurs
3487+
*/
3488+
public Stream<CustomAttribute> getCustomAttributesStream(final Object projectIdOrPath) throws GitLabApiException {
3489+
return (getCustomAttributes(projectIdOrPath, getDefaultPerPage()).stream());
3490+
}
3491+
3492+
/**
3493+
* Get a single custom attribute for the specified project.
3494+
*
3495+
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes/:key</code></pre>
3496+
*
3497+
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
3498+
* @param key the key for the custom attribute
3499+
* @return a CustomAttribute instance for the specified key
3500+
* @throws GitLabApiException if any exception occurs
3501+
*/
3502+
public CustomAttribute getCustomAttribute(final Object projectIdOrPath, final String key) throws GitLabApiException {
3503+
Response response = get(Response.Status.OK, null,
3504+
"projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes", key);
3505+
return (response.readEntity(CustomAttribute.class));
3506+
}
3507+
3508+
/**
3509+
* Get an Optional instance with the value for a single custom attribute for the specified project.
3510+
*
3511+
* <pre><code>GitLab Endpoint: GET /projects/:id/custom_attributes/:key</code></pre>
3512+
*
3513+
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required
3514+
* @param key the key for the custom attribute, required
3515+
* @return an Optional instance with the value for a single custom attribute for the specified project
3516+
*/
3517+
public Optional<CustomAttribute> geOptionalCustomAttribute(final Object projectIdOrPath, final String key) {
3518+
try {
3519+
return (Optional.ofNullable(getCustomAttribute(projectIdOrPath, key)));
3520+
} catch (GitLabApiException glae) {
3521+
return (GitLabApi.createOptionalFromException(glae));
3522+
}
3523+
}
3524+
3525+
/**
3526+
* Set a custom attribute for the specified project. The attribute will be updated if it already exists,
3527+
* or newly created otherwise.
3528+
*
3529+
* <pre><code>GitLab Endpoint: PUT /projects/:id/custom_attributes/:key</code></pre>
3530+
*
3531+
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
3532+
* @param key the key for the custom attribute
3533+
* @param value the value for the customAttribute
3534+
* @return a CustomAttribute instance for the updated or created custom attribute
3535+
* @throws GitLabApiException if any exception occurs
3536+
*/
3537+
public CustomAttribute setCustomAttribute(final Object projectIdOrPath, final String key, final String value) throws GitLabApiException {
3538+
3539+
if (Objects.isNull(key) || key.trim().isEmpty()) {
3540+
throw new IllegalArgumentException("Key cannot be null or empty");
3541+
}
3542+
if (Objects.isNull(value) || value.trim().isEmpty()) {
3543+
throw new IllegalArgumentException("Value cannot be null or empty");
3544+
}
3545+
3546+
GitLabApiForm formData = new GitLabApiForm().withParam("value", value);
3547+
Response response = putWithFormData(Response.Status.OK, formData,
3548+
"projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes", key);
3549+
return (response.readEntity(CustomAttribute.class));
3550+
}
3551+
3552+
/**
3553+
* Delete a custom attribute for the specified project.
3554+
*
3555+
* <pre><code>GitLab Endpoint: DELETE /projects/:id/custom_attributes/:key</code></pre>
3556+
*
3557+
* @param projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
3558+
* @param key the key of the custom attribute to delete
3559+
* @throws GitLabApiException if any exception occurs
3560+
*/
3561+
public void deleteCustomAttribute(final Object projectIdOrPath, final String key) throws GitLabApiException {
3562+
3563+
if (Objects.isNull(key) || key.trim().isEmpty()) {
3564+
throw new IllegalArgumentException("Key can't be null or empty");
3565+
}
3566+
3567+
delete(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "custom_attributes", key);
3568+
}
34493569
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2020 Greg Messner <[email protected]>
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
7+
* this software and associated documentation files (the "Software"), to deal in
8+
* the Software without restriction, including without limitation the rights to
9+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10+
* the Software, and to permit persons to whom the Software is furnished to do so,
11+
* subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*/
23+
24+
package org.gitlab4j.api;
25+
26+
import static org.junit.Assert.assertEquals;
27+
import static org.junit.Assert.assertFalse;
28+
import static org.junit.Assert.assertNotNull;
29+
import static org.junit.Assert.assertTrue;
30+
import static org.junit.Assume.assumeNotNull;
31+
32+
import java.util.List;
33+
import java.util.Optional;
34+
import java.util.stream.Stream;
35+
36+
import org.gitlab4j.api.models.CustomAttribute;
37+
import org.gitlab4j.api.models.Project;
38+
import org.junit.AfterClass;
39+
import org.junit.Before;
40+
import org.junit.BeforeClass;
41+
import org.junit.Test;
42+
import org.junit.experimental.categories.Category;
43+
44+
/**
45+
* In order for these tests to run you must set the following properties in ~/test-gitlab4j.properties
46+
*
47+
* TEST_NAMESPACE
48+
* TEST_PROJECT_NAME
49+
* TEST_HOST_URL
50+
* TEST_PRIVATE_TOKEN
51+
*
52+
* If any of the above are NULL, all tests in this class will be skipped.
53+
*/
54+
@Category(IntegrationTest.class)
55+
public class TestProjectCustomAttributes extends AbstractIntegrationTest {
56+
57+
private static final String TEST_CUSTOM_ATTRIBUTE_KEY = "GitLab4JCustomAttributeTestKey";
58+
private static final String TEST_CUSTOM_ATTRIBUTE_VALUE = "CustomAttributeValue";
59+
60+
private static GitLabApi gitLabApi;
61+
private static Project testProject;
62+
63+
public TestProjectCustomAttributes() {
64+
super();
65+
}
66+
67+
@BeforeClass
68+
public static void setup() {
69+
70+
// Must setup the connection to the GitLab test server and get the test Project instance
71+
gitLabApi = baseTestSetup();
72+
testProject = getTestProject();
73+
74+
deleteAllTestCustomAttributes();
75+
}
76+
77+
@AfterClass
78+
public static void teardown() throws GitLabApiException {
79+
deleteAllTestCustomAttributes();
80+
}
81+
82+
private static void deleteAllTestCustomAttributes() {
83+
if (gitLabApi != null) {
84+
try {
85+
List<CustomAttribute> customAttributes = gitLabApi.getProjectApi().getCustomAttributes(testProject);
86+
if (customAttributes != null) {
87+
for (CustomAttribute customAttribute : customAttributes) {
88+
if (customAttribute.getKey().startsWith(TEST_CUSTOM_ATTRIBUTE_KEY)) {
89+
gitLabApi.getProjectApi().deleteCustomAttribute(testProject, customAttribute.getKey());
90+
}
91+
}
92+
}
93+
} catch (GitLabApiException ignore) {
94+
}
95+
}
96+
}
97+
98+
@Before
99+
public void beforeMethod() {
100+
assumeNotNull(gitLabApi);
101+
}
102+
103+
private CustomAttribute createCustomAttribute(String key, String value) throws GitLabApiException {
104+
return (gitLabApi.getProjectApi().setCustomAttribute(testProject, key, value));
105+
}
106+
107+
@Test
108+
public void testCreate() throws GitLabApiException {
109+
110+
CustomAttribute customAttribute = createCustomAttribute(TEST_CUSTOM_ATTRIBUTE_KEY, TEST_CUSTOM_ATTRIBUTE_VALUE);
111+
assertNotNull(customAttribute);
112+
assertEquals(TEST_CUSTOM_ATTRIBUTE_KEY, customAttribute.getKey());
113+
assertEquals(TEST_CUSTOM_ATTRIBUTE_VALUE, customAttribute.getValue());
114+
}
115+
116+
@Test
117+
public void testUpdate() throws GitLabApiException {
118+
119+
assumeNotNull(testProject);
120+
121+
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestUpdate";
122+
String value = TEST_CUSTOM_ATTRIBUTE_VALUE;
123+
CustomAttribute customAttribute = createCustomAttribute(key, value);
124+
assertNotNull(customAttribute);
125+
assertEquals(key, customAttribute.getKey());
126+
assertEquals(value, customAttribute.getValue());
127+
128+
value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (updated)";
129+
customAttribute = gitLabApi.getProjectApi().setCustomAttribute(testProject, key, value);
130+
assertEquals(key, customAttribute.getKey());
131+
assertEquals(value, customAttribute.getValue());
132+
}
133+
134+
@Test
135+
public void testGetCustomAttribute() throws GitLabApiException {
136+
137+
assumeNotNull(testProject);
138+
139+
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestGet";
140+
String value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (test get)";
141+
CustomAttribute newCustomAttribute = createCustomAttribute(key, value);
142+
assertNotNull(newCustomAttribute);
143+
144+
Optional<CustomAttribute> customAttribute =
145+
gitLabApi.getProjectApi().geOptionalCustomAttribute(testProject, key);
146+
assertTrue(customAttribute.isPresent());
147+
assertEquals(key, customAttribute.get().getKey());
148+
assertEquals(value, customAttribute.get().getValue());
149+
}
150+
151+
@Test
152+
public void testListCustomAttributes() throws GitLabApiException {
153+
154+
assumeNotNull(testProject);
155+
156+
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestList";
157+
String value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (test list)";
158+
CustomAttribute newCustomAttribute = createCustomAttribute(key, value);
159+
assertNotNull(newCustomAttribute);
160+
161+
List<CustomAttribute> customAttributes = gitLabApi.getProjectApi().getCustomAttributes(testProject);
162+
assertNotNull(customAttributes);
163+
for (CustomAttribute customAttribute : customAttributes) {
164+
if (key.equals(customAttribute.getKey())) {
165+
assertEquals(value, customAttribute.getValue());
166+
break;
167+
}
168+
}
169+
}
170+
171+
@Test
172+
public void testDeleteCustomAttribute() throws GitLabApiException {
173+
174+
assumeNotNull(testProject);
175+
176+
String key = TEST_CUSTOM_ATTRIBUTE_KEY + "TestDelete";
177+
String value = TEST_CUSTOM_ATTRIBUTE_VALUE + " (test delete)";
178+
createCustomAttribute(key, value);
179+
180+
Stream<CustomAttribute> stream = gitLabApi.getProjectApi().getCustomAttributesStream(testProject);
181+
Optional<CustomAttribute> match = stream.filter(c -> c.getKey().equals(key)).findFirst();
182+
assertTrue(match.isPresent());
183+
184+
gitLabApi.getProjectApi().deleteCustomAttribute(testProject, key);
185+
186+
stream = gitLabApi.getProjectApi().getCustomAttributesStream(testProject);
187+
match = stream.filter(c -> c.getKey().equals(key)).findFirst();
188+
assertFalse(match.isPresent());
189+
}
190+
}

0 commit comments

Comments
 (0)