Merge "Add REST endpoint to batch update label definitions"
This commit is contained in:
@@ -3233,6 +3233,66 @@ If a label was deleted the response is "`204 No Content`".
|
||||
HTTP/1.1 204 No Content
|
||||
----
|
||||
|
||||
[[batch-update-labels]]
|
||||
=== Batch Update Labels
|
||||
--
|
||||
'POST /projects/link:#project-name[\{project-name\}]/labels/'
|
||||
--
|
||||
|
||||
Creates/updates/deletes multiple label definitions in this project at once.
|
||||
|
||||
The calling user must have write access to the `refs/meta/config` branch of the
|
||||
project.
|
||||
|
||||
The updates must be specified in the request body as
|
||||
link:#batch-label-input[BatchLabelInput] entity.
|
||||
|
||||
The updates are processed in the following order:
|
||||
|
||||
1. label deletions
|
||||
2. label creations
|
||||
3. label updates
|
||||
|
||||
.Request
|
||||
----
|
||||
POST /projects/My-Project/labels/ HTTP/1.0
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
{
|
||||
"commit_message": "Update Labels",
|
||||
"delete": [
|
||||
"Old-Review",
|
||||
"Unused-Review"
|
||||
],
|
||||
"create": [
|
||||
{
|
||||
"name": "Foo-Review",
|
||||
"values": {
|
||||
" 0": "No score",
|
||||
"-1": "I would prefer this is not merged as is",
|
||||
"-2": "This shall not be merged",
|
||||
"+1": "Looks good to me, but someone else must approve",
|
||||
"+2": "Looks good to me, approved"
|
||||
}
|
||||
],
|
||||
"update:" {
|
||||
"Bar-Review": {
|
||||
"function": "MaxWithBlock"
|
||||
},
|
||||
"Baz-Review": {
|
||||
"copy_min_score": true
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If the label updates were done successfully the response is "`200 OK`".
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
----
|
||||
|
||||
|
||||
[[ids]]
|
||||
== IDs
|
||||
@@ -3874,9 +3934,14 @@ review label].
|
||||
|Field Name ||Description
|
||||
|`commit_message`|optional|
|
||||
Message that should be used to commit the change of the label in the
|
||||
`project.config` file to the `refs/meta/config` branch.
|
||||
`project.config` file to the `refs/meta/config` branch.+
|
||||
Must not be set if this `LabelDefinitionInput` entity is contained in a
|
||||
link:#batch-label-input[BatchLabelInput] entity.
|
||||
|`name` |optional|
|
||||
The new link:config-labels.html#label_name[name] of the label.
|
||||
The new link:config-labels.html#label_name[name] of the label.+
|
||||
For label creation the name is required if this `LabelDefinitionInput` entity
|
||||
is contained in a link:#batch-label-input[BatchLabelInput]
|
||||
entity.
|
||||
|`function` |optional|
|
||||
The new link:config-labels.html#label_function[function] of the label (can be
|
||||
`MaxWithBlock`, `AnyWithBlock`, `MaxNoBlock`, `NoBlock`, `NoOp` and `PatchSetLock`.
|
||||
@@ -3959,6 +4024,27 @@ the parent project or global config. +
|
||||
Not set if not inherited or overridden.
|
||||
|===============================
|
||||
|
||||
[[batch-label-input]]
|
||||
=== BatchLabelInput
|
||||
The `BatchLabelInput` entity contains information for batch updating label
|
||||
definitions in a project.
|
||||
|
||||
[options="header",cols="1,^2,4"]
|
||||
|=============================
|
||||
|Field Name ||Description
|
||||
|`commit_message`|optional|
|
||||
Message that should be used to commit the label updates in the
|
||||
`project.config` file to the `refs/meta/config` branch.
|
||||
|`delete` |optional|
|
||||
List of labels that should be deleted.
|
||||
|`create` |optional|
|
||||
List of link:#label-definition-input[LabelDefinitionInput] entities that
|
||||
describe labels that should be created.
|
||||
|`update` |optional|
|
||||
Map of label names to link:#label-definition-input[LabelDefinitionInput]
|
||||
entities that describe the updates that should be done for the labels.
|
||||
|=============================
|
||||
|
||||
[[project-access-input]]
|
||||
=== ProjectAccessInput
|
||||
The `ProjectAccessInput` describes changes that should be applied to a project
|
||||
|
@@ -18,6 +18,7 @@ import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
|
||||
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
|
||||
import com.google.gerrit.extensions.api.config.AccessCheckInfo;
|
||||
import com.google.gerrit.extensions.api.config.AccessCheckInput;
|
||||
import com.google.gerrit.extensions.common.BatchLabelInput;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
|
||||
import com.google.gerrit.extensions.common.ProjectInfo;
|
||||
@@ -220,6 +221,13 @@ public interface ProjectApi {
|
||||
|
||||
LabelApi label(String labelName) throws RestApiException;
|
||||
|
||||
/**
|
||||
* Adds, updates and deletes label definitions in a batch.
|
||||
*
|
||||
* @param input input that describes additions, updates and deletions of label definitions
|
||||
*/
|
||||
void labels(BatchLabelInput input) throws RestApiException;
|
||||
|
||||
/**
|
||||
* A default implementation which allows source compatibility when adding new methods to the
|
||||
* interface.
|
||||
@@ -404,5 +412,10 @@ public interface ProjectApi {
|
||||
public LabelApi label(String labelName) throws RestApiException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void labels(BatchLabelInput input) throws RestApiException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.extensions.common;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Input for the REST API that describes additions, updates and deletions of label definitions. */
|
||||
public class BatchLabelInput {
|
||||
public String commitMessage;
|
||||
public List<String> delete;
|
||||
public List<LabelDefinitionInput> create;
|
||||
public Map<String, LabelDefinitionInput> update;
|
||||
}
|
@@ -43,6 +43,7 @@ import com.google.gerrit.extensions.api.projects.ProjectApi;
|
||||
import com.google.gerrit.extensions.api.projects.ProjectInput;
|
||||
import com.google.gerrit.extensions.api.projects.TagApi;
|
||||
import com.google.gerrit.extensions.api.projects.TagInfo;
|
||||
import com.google.gerrit.extensions.common.BatchLabelInput;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.Input;
|
||||
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
|
||||
@@ -77,6 +78,7 @@ import com.google.gerrit.server.restapi.project.ListBranches;
|
||||
import com.google.gerrit.server.restapi.project.ListDashboards;
|
||||
import com.google.gerrit.server.restapi.project.ListLabels;
|
||||
import com.google.gerrit.server.restapi.project.ListTags;
|
||||
import com.google.gerrit.server.restapi.project.PostLabels;
|
||||
import com.google.gerrit.server.restapi.project.ProjectsCollection;
|
||||
import com.google.gerrit.server.restapi.project.PutConfig;
|
||||
import com.google.gerrit.server.restapi.project.PutDescription;
|
||||
@@ -131,6 +133,7 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
private final Index index;
|
||||
private final IndexChanges indexChanges;
|
||||
private final Provider<ListLabels> listLabels;
|
||||
private final PostLabels postLabels;
|
||||
private final LabelApiImpl.Factory labelApi;
|
||||
|
||||
@AssistedInject
|
||||
@@ -168,6 +171,7 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
Index index,
|
||||
IndexChanges indexChanges,
|
||||
Provider<ListLabels> listLabels,
|
||||
PostLabels postLabels,
|
||||
LabelApiImpl.Factory labelApi,
|
||||
@Assisted ProjectResource project) {
|
||||
this(
|
||||
@@ -205,6 +209,7 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
index,
|
||||
indexChanges,
|
||||
listLabels,
|
||||
postLabels,
|
||||
labelApi,
|
||||
null);
|
||||
}
|
||||
@@ -244,6 +249,7 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
Index index,
|
||||
IndexChanges indexChanges,
|
||||
Provider<ListLabels> listLabels,
|
||||
PostLabels postLabels,
|
||||
LabelApiImpl.Factory labelApi,
|
||||
@Assisted String name) {
|
||||
this(
|
||||
@@ -281,6 +287,7 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
index,
|
||||
indexChanges,
|
||||
listLabels,
|
||||
postLabels,
|
||||
labelApi,
|
||||
name);
|
||||
}
|
||||
@@ -320,6 +327,7 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
Index index,
|
||||
IndexChanges indexChanges,
|
||||
Provider<ListLabels> listLabels,
|
||||
PostLabels postLabels,
|
||||
LabelApiImpl.Factory labelApi,
|
||||
String name) {
|
||||
this.permissionBackend = permissionBackend;
|
||||
@@ -357,6 +365,7 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
this.index = index;
|
||||
this.indexChanges = indexChanges;
|
||||
this.listLabels = listLabels;
|
||||
this.postLabels = postLabels;
|
||||
this.labelApi = labelApi;
|
||||
}
|
||||
|
||||
@@ -712,4 +721,13 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
throw asRestApiException("Cannot parse label", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void labels(BatchLabelInput input) throws RestApiException {
|
||||
try {
|
||||
postLabels.apply(checkExists(), input);
|
||||
} catch (Exception e) {
|
||||
throw asRestApiException("Cannot update labels", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -91,85 +91,7 @@ public class CreateLabel
|
||||
try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
|
||||
ProjectConfig config = projectConfigFactory.read(md);
|
||||
|
||||
if (config.getLabelSections().containsKey(id.get())) {
|
||||
throw new ResourceConflictException(String.format("label %s already exists", id.get()));
|
||||
}
|
||||
|
||||
for (String labelName : config.getLabelSections().keySet()) {
|
||||
if (labelName.equalsIgnoreCase(id.get())) {
|
||||
throw new ResourceConflictException(
|
||||
String.format("label %s conflicts with existing label %s", id.get(), labelName));
|
||||
}
|
||||
}
|
||||
|
||||
if (input.values == null || input.values.isEmpty()) {
|
||||
throw new BadRequestException("values are required");
|
||||
}
|
||||
|
||||
List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
|
||||
|
||||
LabelType labelType;
|
||||
try {
|
||||
labelType = new LabelType(id.get(), values);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException("invalid name: " + id.get(), e);
|
||||
}
|
||||
|
||||
if (input.function != null && !input.function.trim().isEmpty()) {
|
||||
labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
|
||||
} else {
|
||||
labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
|
||||
}
|
||||
|
||||
if (input.defaultValue != null) {
|
||||
labelType.setDefaultValue(
|
||||
LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
|
||||
}
|
||||
|
||||
if (input.branches != null) {
|
||||
labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
|
||||
}
|
||||
|
||||
if (input.canOverride != null) {
|
||||
labelType.setCanOverride(input.canOverride);
|
||||
}
|
||||
|
||||
if (input.copyAnyScore != null) {
|
||||
labelType.setCopyAnyScore(input.copyAnyScore);
|
||||
}
|
||||
|
||||
if (input.copyMinScore != null) {
|
||||
labelType.setCopyMinScore(input.copyMinScore);
|
||||
}
|
||||
|
||||
if (input.copyMaxScore != null) {
|
||||
labelType.setCopyMaxScore(input.copyMaxScore);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoChange != null) {
|
||||
labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoCodeChange != null) {
|
||||
labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnTrivialRebase != null) {
|
||||
labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
|
||||
labelType.setCopyAllScoresOnMergeFirstParentUpdate(
|
||||
input.copyAllScoresOnMergeFirstParentUpdate);
|
||||
}
|
||||
|
||||
if (input.allowPostSubmit != null) {
|
||||
labelType.setAllowPostSubmit(input.allowPostSubmit);
|
||||
}
|
||||
|
||||
if (input.ignoreSelfApproval != null) {
|
||||
labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
|
||||
}
|
||||
LabelType labelType = createLabel(config, id.get(), input);
|
||||
|
||||
if (input.commitMessage != null) {
|
||||
md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
|
||||
@@ -177,7 +99,6 @@ public class CreateLabel
|
||||
md.setMessage("Update label");
|
||||
}
|
||||
|
||||
config.getLabelSections().put(labelType.getName(), labelType);
|
||||
config.commit(md);
|
||||
|
||||
projectCache.evict(rsrc.getProjectState().getProject());
|
||||
@@ -185,4 +106,101 @@ public class CreateLabel
|
||||
return Response.created(LabelDefinitionJson.format(rsrc.getNameKey(), labelType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new label.
|
||||
*
|
||||
* @param config the project config
|
||||
* @param label the name of the new label
|
||||
* @param input the input that describes the new label
|
||||
* @return the created label type
|
||||
* @throws BadRequestException if there was invalid data in the input
|
||||
* @throws ResourceConflictException if the label cannot be created due to a conflict
|
||||
*/
|
||||
public LabelType createLabel(ProjectConfig config, String label, LabelDefinitionInput input)
|
||||
throws BadRequestException, ResourceConflictException {
|
||||
if (config.getLabelSections().containsKey(label)) {
|
||||
throw new ResourceConflictException(String.format("label %s already exists", label));
|
||||
}
|
||||
|
||||
for (String labelName : config.getLabelSections().keySet()) {
|
||||
if (labelName.equalsIgnoreCase(label)) {
|
||||
throw new ResourceConflictException(
|
||||
String.format("label %s conflicts with existing label %s", label, labelName));
|
||||
}
|
||||
}
|
||||
|
||||
if (input.values == null || input.values.isEmpty()) {
|
||||
throw new BadRequestException("values are required");
|
||||
}
|
||||
|
||||
List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
|
||||
|
||||
LabelType labelType;
|
||||
try {
|
||||
labelType = new LabelType(label, values);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException("invalid name: " + label, e);
|
||||
}
|
||||
|
||||
if (input.function != null && !input.function.trim().isEmpty()) {
|
||||
labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
|
||||
} else {
|
||||
labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
|
||||
}
|
||||
|
||||
if (input.defaultValue != null) {
|
||||
labelType.setDefaultValue(
|
||||
LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
|
||||
}
|
||||
|
||||
if (input.branches != null) {
|
||||
labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
|
||||
}
|
||||
|
||||
if (input.canOverride != null) {
|
||||
labelType.setCanOverride(input.canOverride);
|
||||
}
|
||||
|
||||
if (input.copyAnyScore != null) {
|
||||
labelType.setCopyAnyScore(input.copyAnyScore);
|
||||
}
|
||||
|
||||
if (input.copyMinScore != null) {
|
||||
labelType.setCopyMinScore(input.copyMinScore);
|
||||
}
|
||||
|
||||
if (input.copyMaxScore != null) {
|
||||
labelType.setCopyMaxScore(input.copyMaxScore);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoChange != null) {
|
||||
labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoCodeChange != null) {
|
||||
labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnTrivialRebase != null) {
|
||||
labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
|
||||
labelType.setCopyAllScoresOnMergeFirstParentUpdate(
|
||||
input.copyAllScoresOnMergeFirstParentUpdate);
|
||||
}
|
||||
|
||||
if (input.allowPostSubmit != null) {
|
||||
labelType.setAllowPostSubmit(input.allowPostSubmit);
|
||||
}
|
||||
|
||||
if (input.ignoreSelfApproval != null) {
|
||||
labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
|
||||
}
|
||||
|
||||
config.getLabelSections().put(labelType.getName(), labelType);
|
||||
|
||||
return labelType;
|
||||
}
|
||||
}
|
||||
|
@@ -77,12 +77,10 @@ public class DeleteLabel implements RestModifyView<LabelResource, InputWithCommi
|
||||
try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
|
||||
ProjectConfig config = projectConfigFactory.read(md);
|
||||
|
||||
if (!config.getLabelSections().containsKey(rsrc.getLabelType().getName())) {
|
||||
if (!deleteLabel(config, rsrc.getLabelType().getName())) {
|
||||
throw new ResourceNotFoundException(IdString.fromDecoded(rsrc.getLabelType().getName()));
|
||||
}
|
||||
|
||||
config.getLabelSections().remove(rsrc.getLabelType().getName());
|
||||
|
||||
if (input.commitMessage != null) {
|
||||
md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
|
||||
} else {
|
||||
@@ -96,4 +94,20 @@ public class DeleteLabel implements RestModifyView<LabelResource, InputWithCommi
|
||||
|
||||
return Response.none();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given label from the given project config.
|
||||
*
|
||||
* @param config the project config from which the label should be deleted
|
||||
* @param labelName the name of the label that should be deleted
|
||||
* @return {@code true} if the label was deleted, {@code false} if the label was not found
|
||||
*/
|
||||
public boolean deleteLabel(ProjectConfig config, String labelName) {
|
||||
if (!config.getLabelSections().containsKey(labelName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config.getLabelSections().remove(labelName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -72,6 +72,7 @@ public class Module extends RestApiModule {
|
||||
get(LABEL_KIND).to(GetLabel.class);
|
||||
put(LABEL_KIND).to(SetLabel.class);
|
||||
delete(LABEL_KIND).to(DeleteLabel.class);
|
||||
postOnCollection(LABEL_KIND).to(PostLabels.class);
|
||||
|
||||
get(PROJECT_KIND, "HEAD").to(GetHead.class);
|
||||
put(PROJECT_KIND, "HEAD").to(SetHead.class);
|
||||
|
148
java/com/google/gerrit/server/restapi/project/PostLabels.java
Normal file
148
java/com/google/gerrit/server/restapi/project/PostLabels.java
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.restapi.project;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gerrit.common.data.LabelType;
|
||||
import com.google.gerrit.extensions.common.BatchLabelInput;
|
||||
import com.google.gerrit.extensions.common.LabelDefinitionInput;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.Response;
|
||||
import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.git.meta.MetaDataUpdate;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.ProjectPermission;
|
||||
import com.google.gerrit.server.project.LabelResource;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectConfig;
|
||||
import com.google.gerrit.server.project.ProjectResource;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.util.Map.Entry;
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
|
||||
/** REST endpoint that allows to add, update and delete label definitions in a batch. */
|
||||
@Singleton
|
||||
public class PostLabels
|
||||
implements RestCollectionModifyView<ProjectResource, LabelResource, BatchLabelInput> {
|
||||
private final Provider<CurrentUser> user;
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final MetaDataUpdate.User updateFactory;
|
||||
private final ProjectConfig.Factory projectConfigFactory;
|
||||
private final DeleteLabel deleteLabel;
|
||||
private final CreateLabel createLabel;
|
||||
private final SetLabel setLabel;
|
||||
private final ProjectCache projectCache;
|
||||
|
||||
@Inject
|
||||
public PostLabels(
|
||||
Provider<CurrentUser> user,
|
||||
PermissionBackend permissionBackend,
|
||||
MetaDataUpdate.User updateFactory,
|
||||
ProjectConfig.Factory projectConfigFactory,
|
||||
DeleteLabel deleteLabel,
|
||||
CreateLabel createLabel,
|
||||
SetLabel setLabel,
|
||||
ProjectCache projectCache) {
|
||||
this.user = user;
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.updateFactory = updateFactory;
|
||||
this.projectConfigFactory = projectConfigFactory;
|
||||
this.deleteLabel = deleteLabel;
|
||||
this.createLabel = createLabel;
|
||||
this.setLabel = setLabel;
|
||||
this.projectCache = projectCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<?> apply(ProjectResource rsrc, BatchLabelInput input)
|
||||
throws AuthException, UnprocessableEntityException, PermissionBackendException, IOException,
|
||||
ConfigInvalidException, BadRequestException, ResourceConflictException {
|
||||
if (!user.get().isIdentifiedUser()) {
|
||||
throw new AuthException("Authentication required");
|
||||
}
|
||||
|
||||
permissionBackend
|
||||
.currentUser()
|
||||
.project(rsrc.getNameKey())
|
||||
.check(ProjectPermission.WRITE_CONFIG);
|
||||
|
||||
if (input == null) {
|
||||
input = new BatchLabelInput();
|
||||
}
|
||||
|
||||
try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
|
||||
boolean dirty = false;
|
||||
|
||||
ProjectConfig config = projectConfigFactory.read(md);
|
||||
|
||||
if (input.delete != null && !input.delete.isEmpty()) {
|
||||
for (String labelName : input.delete) {
|
||||
if (!deleteLabel.deleteLabel(config, labelName.trim())) {
|
||||
throw new UnprocessableEntityException(String.format("label %s not found", labelName));
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.create != null && !input.create.isEmpty()) {
|
||||
for (LabelDefinitionInput labelInput : input.create) {
|
||||
if (labelInput.name == null || labelInput.name.trim().isEmpty()) {
|
||||
throw new BadRequestException("label name is required for new label");
|
||||
}
|
||||
if (labelInput.commitMessage != null) {
|
||||
throw new BadRequestException("commit message on label definition input not supported");
|
||||
}
|
||||
createLabel.createLabel(config, labelInput.name.trim(), labelInput);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.update != null && !input.update.isEmpty()) {
|
||||
for (Entry<String, LabelDefinitionInput> e : input.update.entrySet()) {
|
||||
LabelType labelType = config.getLabelSections().get(e.getKey().trim());
|
||||
if (labelType == null) {
|
||||
throw new UnprocessableEntityException(String.format("label %s not found", e.getKey()));
|
||||
}
|
||||
if (e.getValue().commitMessage != null) {
|
||||
throw new BadRequestException("commit message on label definition input not supported");
|
||||
}
|
||||
setLabel.updateLabel(config, labelType, e.getValue());
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.commitMessage != null) {
|
||||
md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
|
||||
} else {
|
||||
md.setMessage("Update labels");
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
config.commit(md);
|
||||
projectCache.evict(rsrc.getProjectState().getProject());
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok("");
|
||||
}
|
||||
}
|
@@ -80,117 +80,9 @@ public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionIn
|
||||
LabelType labelType = rsrc.getLabelType();
|
||||
|
||||
try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
|
||||
boolean dirty = false;
|
||||
|
||||
ProjectConfig config = projectConfigFactory.read(md);
|
||||
config.getLabelSections().remove(labelType.getName());
|
||||
|
||||
if (input.name != null) {
|
||||
String newName = input.name.trim();
|
||||
if (newName.isEmpty()) {
|
||||
throw new BadRequestException("name cannot be empty");
|
||||
}
|
||||
if (!newName.equals(labelType.getName())) {
|
||||
if (config.getLabelSections().containsKey(newName)) {
|
||||
throw new ResourceConflictException(String.format("name %s already in use", newName));
|
||||
}
|
||||
|
||||
for (String labelName : config.getLabelSections().keySet()) {
|
||||
if (labelName.equalsIgnoreCase(newName)) {
|
||||
throw new ResourceConflictException(
|
||||
String.format("name %s conflicts with existing label %s", newName, labelName));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
labelType.setName(newName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException("invalid name: " + input.name, e);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.function != null) {
|
||||
if (input.function.trim().isEmpty()) {
|
||||
throw new BadRequestException("function cannot be empty");
|
||||
}
|
||||
labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.values != null) {
|
||||
if (input.values.isEmpty()) {
|
||||
throw new BadRequestException("values cannot be empty");
|
||||
}
|
||||
labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.defaultValue != null) {
|
||||
labelType.setDefaultValue(
|
||||
LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.branches != null) {
|
||||
labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.canOverride != null) {
|
||||
labelType.setCanOverride(input.canOverride);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAnyScore != null) {
|
||||
labelType.setCopyAnyScore(input.copyAnyScore);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyMinScore != null) {
|
||||
labelType.setCopyMinScore(input.copyMinScore);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyMaxScore != null) {
|
||||
labelType.setCopyMaxScore(input.copyMaxScore);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoChange != null) {
|
||||
labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoCodeChange != null) {
|
||||
labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnTrivialRebase != null) {
|
||||
labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
|
||||
labelType.setCopyAllScoresOnMergeFirstParentUpdate(
|
||||
input.copyAllScoresOnMergeFirstParentUpdate);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.allowPostSubmit != null) {
|
||||
labelType.setAllowPostSubmit(input.allowPostSubmit);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.ignoreSelfApproval != null) {
|
||||
labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
config.getLabelSections().put(labelType.getName(), labelType);
|
||||
|
||||
if (updateLabel(config, labelType, input)) {
|
||||
if (input.commitMessage != null) {
|
||||
md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
|
||||
} else {
|
||||
@@ -203,4 +95,128 @@ public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionIn
|
||||
}
|
||||
return Response.ok(LabelDefinitionJson.format(rsrc.getProject().getNameKey(), labelType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given label.
|
||||
*
|
||||
* @param config the project config
|
||||
* @param labelType the label type that should be updated
|
||||
* @param input the input that describes the label update
|
||||
* @return whether the label type was modified
|
||||
* @throws BadRequestException if there was invalid data in the input
|
||||
* @throws ResourceConflictException if the update cannot be applied due to a conflict
|
||||
*/
|
||||
public boolean updateLabel(ProjectConfig config, LabelType labelType, LabelDefinitionInput input)
|
||||
throws BadRequestException, ResourceConflictException {
|
||||
boolean dirty = false;
|
||||
|
||||
config.getLabelSections().remove(labelType.getName());
|
||||
|
||||
if (input.name != null) {
|
||||
String newName = input.name.trim();
|
||||
if (newName.isEmpty()) {
|
||||
throw new BadRequestException("name cannot be empty");
|
||||
}
|
||||
if (!newName.equals(labelType.getName())) {
|
||||
if (config.getLabelSections().containsKey(newName)) {
|
||||
throw new ResourceConflictException(String.format("name %s already in use", newName));
|
||||
}
|
||||
|
||||
for (String labelName : config.getLabelSections().keySet()) {
|
||||
if (labelName.equalsIgnoreCase(newName)) {
|
||||
throw new ResourceConflictException(
|
||||
String.format("name %s conflicts with existing label %s", newName, labelName));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
labelType.setName(newName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException("invalid name: " + input.name, e);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.function != null) {
|
||||
if (input.function.trim().isEmpty()) {
|
||||
throw new BadRequestException("function cannot be empty");
|
||||
}
|
||||
labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.values != null) {
|
||||
if (input.values.isEmpty()) {
|
||||
throw new BadRequestException("values cannot be empty");
|
||||
}
|
||||
labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.defaultValue != null) {
|
||||
labelType.setDefaultValue(
|
||||
LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.branches != null) {
|
||||
labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.canOverride != null) {
|
||||
labelType.setCanOverride(input.canOverride);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAnyScore != null) {
|
||||
labelType.setCopyAnyScore(input.copyAnyScore);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyMinScore != null) {
|
||||
labelType.setCopyMinScore(input.copyMinScore);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyMaxScore != null) {
|
||||
labelType.setCopyMaxScore(input.copyMaxScore);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoChange != null) {
|
||||
labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
|
||||
}
|
||||
|
||||
if (input.copyAllScoresIfNoCodeChange != null) {
|
||||
labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnTrivialRebase != null) {
|
||||
labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
|
||||
labelType.setCopyAllScoresOnMergeFirstParentUpdate(
|
||||
input.copyAllScoresOnMergeFirstParentUpdate);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.allowPostSubmit != null) {
|
||||
labelType.setAllowPostSubmit(input.allowPostSubmit);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (input.ignoreSelfApproval != null) {
|
||||
labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
config.getLabelSections().put(labelType.getName(), labelType);
|
||||
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
|
@@ -83,7 +83,8 @@ public class ProjectsRestApiBindingsIT extends AbstractDaemonTest {
|
||||
.expectedResponseCode(SC_NOT_FOUND)
|
||||
.build(),
|
||||
RestCall.get("/projects/%s/dashboards"),
|
||||
RestCall.put("/projects/%s/labels/new-label"));
|
||||
RestCall.put("/projects/%s/labels/new-label"),
|
||||
RestCall.post("/projects/%s/labels/"));
|
||||
|
||||
/**
|
||||
* Child project REST endpoints to be tested, each URL contains placeholders for the parent
|
||||
|
@@ -0,0 +1,456 @@
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.acceptance.rest.project;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
|
||||
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
||||
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
|
||||
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
|
||||
import com.google.gerrit.common.data.LabelFunction;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.entities.RefNames;
|
||||
import com.google.gerrit.extensions.common.BatchLabelInput;
|
||||
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
|
||||
import com.google.gerrit.extensions.common.LabelDefinitionInput;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.server.restapi.project.PostLabels;
|
||||
import com.google.inject.Inject;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.junit.Test;
|
||||
|
||||
/** Tests for the {@link PostLabels} REST endpoint. */
|
||||
public class PostLabelsIT extends AbstractDaemonTest {
|
||||
@Inject private RequestScopeOperations requestScopeOperations;
|
||||
@Inject private ProjectOperations projectOperations;
|
||||
|
||||
@Test
|
||||
public void anonymous() throws Exception {
|
||||
requestScopeOperations.setApiUserAnonymous();
|
||||
AuthException thrown =
|
||||
assertThrows(
|
||||
AuthException.class,
|
||||
() -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
|
||||
assertThat(thrown).hasMessageThat().contains("Authentication required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notAllowed() throws Exception {
|
||||
projectOperations
|
||||
.project(allProjects)
|
||||
.forUpdate()
|
||||
.add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
|
||||
.update();
|
||||
|
||||
requestScopeOperations.setApiUser(user.id());
|
||||
AuthException thrown =
|
||||
assertThrows(
|
||||
AuthException.class,
|
||||
() -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
|
||||
assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteNonExistingLabel() throws Exception {
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of("Foo");
|
||||
|
||||
UnprocessableEntityException thrown =
|
||||
assertThrows(
|
||||
UnprocessableEntityException.class,
|
||||
() -> gApi.projects().name(allProjects.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label Foo not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteLabels() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
configLabel("Bar", LabelFunction.NO_OP);
|
||||
assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of("Foo", "Bar");
|
||||
gApi.projects().name(project.get()).labels(input);
|
||||
assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteLabels_labelNamesAreTrimmed() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
configLabel("Bar", LabelFunction.NO_OP);
|
||||
assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of(" Foo ", " Bar ");
|
||||
gApi.projects().name(project.get()).labels(input);
|
||||
assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotDeleteTheSameLabelTwice() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of("Foo", "Foo");
|
||||
|
||||
UnprocessableEntityException thrown =
|
||||
assertThrows(
|
||||
UnprocessableEntityException.class,
|
||||
() -> gApi.projects().name(project.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label Foo not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCreateLabelWithNameThatIsAlreadyInUse() throws Exception {
|
||||
LabelDefinitionInput labelInput = new LabelDefinitionInput();
|
||||
labelInput.name = "Code-Review";
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(labelInput);
|
||||
|
||||
ResourceConflictException thrown =
|
||||
assertThrows(
|
||||
ResourceConflictException.class,
|
||||
() -> gApi.projects().name(allProjects.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label Code-Review already exists");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCreateTwoLabelsWithTheSameName() throws Exception {
|
||||
LabelDefinitionInput fooInput = new LabelDefinitionInput();
|
||||
fooInput.name = "Foo";
|
||||
fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(fooInput, fooInput);
|
||||
|
||||
ResourceConflictException thrown =
|
||||
assertThrows(
|
||||
ResourceConflictException.class,
|
||||
() -> gApi.projects().name(project.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label Foo already exists");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCreateTwoLabelsWithNamesThatAreTheSameAfterTrim() throws Exception {
|
||||
LabelDefinitionInput foo1Input = new LabelDefinitionInput();
|
||||
foo1Input.name = "Foo";
|
||||
foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
LabelDefinitionInput foo2Input = new LabelDefinitionInput();
|
||||
foo2Input.name = " Foo ";
|
||||
foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(foo1Input, foo2Input);
|
||||
|
||||
ResourceConflictException thrown =
|
||||
assertThrows(
|
||||
ResourceConflictException.class,
|
||||
() -> gApi.projects().name(project.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label Foo already exists");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCreateTwoLabelsWithConflictingNames() throws Exception {
|
||||
LabelDefinitionInput foo1Input = new LabelDefinitionInput();
|
||||
foo1Input.name = "Foo";
|
||||
foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
LabelDefinitionInput foo2Input = new LabelDefinitionInput();
|
||||
foo2Input.name = "foo";
|
||||
foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(foo1Input, foo2Input);
|
||||
|
||||
ResourceConflictException thrown =
|
||||
assertThrows(
|
||||
ResourceConflictException.class,
|
||||
() -> gApi.projects().name(project.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label foo conflicts with existing label Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLabels() throws Exception {
|
||||
LabelDefinitionInput fooInput = new LabelDefinitionInput();
|
||||
fooInput.name = "Foo";
|
||||
fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
LabelDefinitionInput barInput = new LabelDefinitionInput();
|
||||
barInput.name = "Bar";
|
||||
barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(fooInput, barInput);
|
||||
|
||||
gApi.projects().name(allProjects.get()).labels(input);
|
||||
assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
|
||||
assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLabels_labelNamesAreTrimmed() throws Exception {
|
||||
LabelDefinitionInput fooInput = new LabelDefinitionInput();
|
||||
fooInput.name = " Foo ";
|
||||
fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
LabelDefinitionInput barInput = new LabelDefinitionInput();
|
||||
barInput.name = " Bar ";
|
||||
barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(fooInput, barInput);
|
||||
|
||||
gApi.projects().name(allProjects.get()).labels(input);
|
||||
assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
|
||||
assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCreateLabelWithoutName() throws Exception {
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(new LabelDefinitionInput());
|
||||
|
||||
BadRequestException thrown =
|
||||
assertThrows(
|
||||
BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label name is required for new label");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotSetCommitMessageOnLabelDefinitionInputForCreate() throws Exception {
|
||||
LabelDefinitionInput labelInput = new LabelDefinitionInput();
|
||||
labelInput.name = "Foo";
|
||||
labelInput.commitMessage = "Create Label Foo";
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(labelInput);
|
||||
|
||||
BadRequestException thrown =
|
||||
assertThrows(
|
||||
BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("commit message on label definition input not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateNonExistingLabel() throws Exception {
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.update = ImmutableMap.of("Foo", new LabelDefinitionInput());
|
||||
|
||||
UnprocessableEntityException thrown =
|
||||
assertThrows(
|
||||
UnprocessableEntityException.class,
|
||||
() -> gApi.projects().name(allProjects.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label Foo not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateLabels() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
configLabel("Bar", LabelFunction.NO_OP);
|
||||
|
||||
LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
|
||||
fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
|
||||
LabelDefinitionInput barUpdate = new LabelDefinitionInput();
|
||||
barUpdate.name = "Baz";
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.update = ImmutableMap.of("Foo", fooUpdate, "Bar", barUpdate);
|
||||
|
||||
gApi.projects().name(project.get()).labels(input);
|
||||
|
||||
assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
|
||||
.isEqualTo(fooUpdate.function);
|
||||
assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
|
||||
assertThrows(
|
||||
ResourceNotFoundException.class,
|
||||
() -> gApi.projects().name(project.get()).label("Bar").get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateLabels_labelNamesAreTrimmed() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
configLabel("Bar", LabelFunction.NO_OP);
|
||||
|
||||
LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
|
||||
fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
|
||||
LabelDefinitionInput barUpdate = new LabelDefinitionInput();
|
||||
barUpdate.name = "Baz";
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.update = ImmutableMap.of(" Foo ", fooUpdate, " Bar ", barUpdate);
|
||||
|
||||
gApi.projects().name(project.get()).labels(input);
|
||||
|
||||
assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
|
||||
.isEqualTo(fooUpdate.function);
|
||||
assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
|
||||
assertThrows(
|
||||
ResourceNotFoundException.class,
|
||||
() -> gApi.projects().name(project.get()).label("Bar").get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotSetCommitMessageOnLabelDefinitionInputForUpdate() throws Exception {
|
||||
LabelDefinitionInput labelInput = new LabelDefinitionInput();
|
||||
labelInput.commitMessage = "Update label";
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.update = ImmutableMap.of("Code-Review", labelInput);
|
||||
|
||||
BadRequestException thrown =
|
||||
assertThrows(
|
||||
BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains("commit message on label definition input not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteAndRecreateLabel() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
|
||||
LabelDefinitionInput fooInput = new LabelDefinitionInput();
|
||||
fooInput.name = "Foo";
|
||||
fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
|
||||
fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of("Foo");
|
||||
input.create = ImmutableList.of(fooInput);
|
||||
|
||||
gApi.projects().name(project.get()).labels(input);
|
||||
|
||||
LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
|
||||
assertThat(fooLabel.function).isEqualTo(fooInput.function);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteRecreateAndUpdateLabel() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
|
||||
LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
|
||||
fooCreateInput.name = "Foo";
|
||||
fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
|
||||
fooCreateInput.values =
|
||||
ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
|
||||
fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of("Foo");
|
||||
input.create = ImmutableList.of(fooCreateInput);
|
||||
input.update = ImmutableMap.of("Foo", fooUpdateInput);
|
||||
|
||||
gApi.projects().name(project.get()).labels(input);
|
||||
|
||||
LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
|
||||
assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotDeleteAndUpdateLabel() throws Exception {
|
||||
configLabel("Foo", LabelFunction.NO_OP);
|
||||
|
||||
LabelDefinitionInput fooInput = new LabelDefinitionInput();
|
||||
fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of("Foo");
|
||||
input.update = ImmutableMap.of("Foo", fooInput);
|
||||
|
||||
UnprocessableEntityException thrown =
|
||||
assertThrows(
|
||||
UnprocessableEntityException.class,
|
||||
() -> gApi.projects().name(project.get()).labels(input));
|
||||
assertThat(thrown).hasMessageThat().contains("label Foo not found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndUpdateLabel() throws Exception {
|
||||
LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
|
||||
fooCreateInput.name = "Foo";
|
||||
fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
|
||||
fooCreateInput.values =
|
||||
ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
|
||||
|
||||
LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
|
||||
fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();
|
||||
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.create = ImmutableList.of(fooCreateInput);
|
||||
input.update = ImmutableMap.of("Foo", fooUpdateInput);
|
||||
|
||||
gApi.projects().name(project.get()).labels(input);
|
||||
|
||||
LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
|
||||
assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noOpUpdate() throws Exception {
|
||||
RevCommit refsMetaConfigHead =
|
||||
projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG);
|
||||
|
||||
gApi.projects().name(allProjects.get()).labels(new BatchLabelInput());
|
||||
|
||||
assertThat(projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG))
|
||||
.isEqualTo(refsMetaConfigHead);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultCommitMessage() throws Exception {
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.delete = ImmutableList.of("Code-Review");
|
||||
gApi.projects().name(allProjects.get()).labels(input);
|
||||
assertThat(
|
||||
projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
|
||||
.isEqualTo("Update labels");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withCommitMessage() throws Exception {
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.commitMessage = "Batch Update Labels";
|
||||
input.delete = ImmutableList.of("Code-Review");
|
||||
gApi.projects().name(allProjects.get()).labels(input);
|
||||
assertThat(
|
||||
projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
|
||||
.isEqualTo(input.commitMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitMessageIsTrimmed() throws Exception {
|
||||
BatchLabelInput input = new BatchLabelInput();
|
||||
input.commitMessage = " Batch Update Labels ";
|
||||
input.delete = ImmutableList.of("Code-Review");
|
||||
gApi.projects().name(allProjects.get()).labels(input);
|
||||
assertThat(
|
||||
projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
|
||||
.isEqualTo("Batch Update Labels");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user