Add REST endpoint to batch update label definitions
The UI likely wants to display all labels, let the user make modifications and then send a single request to the server to apply all the modifications at once. Bug: Issue 11522 Signed-off-by: Edwin Kempin <ekempin@google.com> Change-Id: I73beb0ab25cd22ac64244ce0c132058cd70f2807
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 | ||||
| @@ -3876,9 +3936,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`. | ||||
| @@ -3961,6 +4026,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,14 +91,42 @@ 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())); | ||||
|       LabelType labelType = createLabel(config, id.get(), input); | ||||
|  | ||||
|       if (input.commitMessage != null) { | ||||
|         md.setMessage(Strings.emptyToNull(input.commitMessage.trim())); | ||||
|       } else { | ||||
|         md.setMessage("Update label"); | ||||
|       } | ||||
|  | ||||
|       config.commit(md); | ||||
|  | ||||
|       projectCache.evict(rsrc.getProjectState().getProject()); | ||||
|  | ||||
|       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(id.get())) { | ||||
|       if (labelName.equalsIgnoreCase(label)) { | ||||
|         throw new ResourceConflictException( | ||||
|               String.format("label %s conflicts with existing label %s", id.get(), labelName)); | ||||
|             String.format("label %s conflicts with existing label %s", label, labelName)); | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -110,9 +138,9 @@ public class CreateLabel | ||||
|  | ||||
|     LabelType labelType; | ||||
|     try { | ||||
|         labelType = new LabelType(id.get(), values); | ||||
|       labelType = new LabelType(label, values); | ||||
|     } catch (IllegalArgumentException e) { | ||||
|         throw new BadRequestException("invalid name: " + id.get(), e); | ||||
|       throw new BadRequestException("invalid name: " + label, e); | ||||
|     } | ||||
|  | ||||
|     if (input.function != null && !input.function.trim().isEmpty()) { | ||||
| @@ -171,18 +199,8 @@ public class CreateLabel | ||||
|       labelType.setIgnoreSelfApproval(input.ignoreSelfApproval); | ||||
|     } | ||||
|  | ||||
|       if (input.commitMessage != null) { | ||||
|         md.setMessage(Strings.emptyToNull(input.commitMessage.trim())); | ||||
|       } else { | ||||
|         md.setMessage("Update label"); | ||||
|       } | ||||
|  | ||||
|     config.getLabelSections().put(labelType.getName(), labelType); | ||||
|       config.commit(md); | ||||
|  | ||||
|       projectCache.evict(rsrc.getProjectState().getProject()); | ||||
|  | ||||
|       return Response.created(LabelDefinitionJson.format(rsrc.getNameKey(), 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,9 +80,36 @@ public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionIn | ||||
|     LabelType labelType = rsrc.getLabelType(); | ||||
|  | ||||
|     try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) { | ||||
|       ProjectConfig config = projectConfigFactory.read(md); | ||||
|  | ||||
|       if (updateLabel(config, labelType, input)) { | ||||
|         if (input.commitMessage != null) { | ||||
|           md.setMessage(Strings.emptyToNull(input.commitMessage.trim())); | ||||
|         } else { | ||||
|           md.setMessage("Update label"); | ||||
|         } | ||||
|  | ||||
|         config.commit(md); | ||||
|         projectCache.evict(rsrc.getProject().getProjectState().getProject()); | ||||
|       } | ||||
|     } | ||||
|     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; | ||||
|  | ||||
|       ProjectConfig config = projectConfigFactory.read(md); | ||||
|     config.getLabelSections().remove(labelType.getName()); | ||||
|  | ||||
|     if (input.name != null) { | ||||
| @@ -188,19 +215,8 @@ public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionIn | ||||
|       dirty = true; | ||||
|     } | ||||
|  | ||||
|       if (dirty) { | ||||
|     config.getLabelSections().put(labelType.getName(), labelType); | ||||
|  | ||||
|         if (input.commitMessage != null) { | ||||
|           md.setMessage(Strings.emptyToNull(input.commitMessage.trim())); | ||||
|         } else { | ||||
|           md.setMessage("Update label"); | ||||
|         } | ||||
|  | ||||
|         config.commit(md); | ||||
|         projectCache.evict(rsrc.getProject().getProjectState().getProject()); | ||||
|       } | ||||
|     } | ||||
|     return Response.ok(LabelDefinitionJson.format(rsrc.getProject().getNameKey(), 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
	 Edwin Kempin
					Edwin Kempin