]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13190 Support tags to application
authorJacek <jacek.poreda@sonarsource.com>
Thu, 2 Apr 2020 09:52:09 +0000 (11:52 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 15 Apr 2020 20:03:38 +0000 (20:03 +0000)
server/sonar-server-common/src/main/java/org/sonar/server/es/ProjectIndexers.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/projecttag/TagsWsSupport.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/projecttag/ws/ProjectTagsWsModule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/projecttag/ws/SetAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/projecttag/ws/ProjectTagsWsModuleTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/projecttag/ws/SetActionTest.java

index 979c3be0a050b0e069bb524a1bf36f62d313b693..7e570725f74b25813200c26c80f2c26b4001060b 100644 (file)
@@ -32,7 +32,7 @@ public interface ProjectIndexers {
   /**
    * Commits the DB transaction and indexes the specified projects, if needed (according to
    * "cause" parameter).
-   * IMPORTANT - UUIDs must relate to projects only. Modules, directories and files are forbidden
+   * IMPORTANT - UUIDs must relate to applications and projects only. Modules, directories and files are forbidden
    * and will lead to lack of indexing.
    */
   void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause);
index a41c38571076d659fe3638a96b5f9a698584b7f8..aeaf1cbe1cb537e7b31f029ca843d1f453286149 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.component.ws;
 
 import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.Set;
 import javax.annotation.Nullable;
@@ -69,9 +70,7 @@ class ComponentDtoToWsComponent {
       });
     if (QUALIFIERS_WITH_VISIBILITY.contains(project.getQualifier())) {
       wsComponent.setVisibility(Visibility.getLabel(project.isPrivate()));
-      if (Qualifiers.PROJECT.equals(project.getQualifier())) {
-        wsComponent.getTagsBuilder().addAllTags(project.getTags());
-      }
+      wsComponent.getTagsBuilder().addAllTags(project.getTags());
     }
 
     return wsComponent;
@@ -106,7 +105,7 @@ class ComponentDtoToWsComponent {
       });
     if (QUALIFIERS_WITH_VISIBILITY.contains(dto.qualifier())) {
       wsComponent.setVisibility(Visibility.getLabel(dto.isPrivate()));
-      if (Qualifiers.PROJECT.equals(dto.qualifier()) && dto.getBranch() != null && parentProjectDto != null) {
+      if (Arrays.asList(Qualifiers.PROJECT, Qualifiers.APP).contains(dto.qualifier()) && dto.getBranch() != null && parentProjectDto != null) {
         wsComponent.getTagsBuilder().addAllTags(parentProjectDto.getTags());
       }
     }
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projecttag/TagsWsSupport.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projecttag/TagsWsSupport.java
new file mode 100644 (file)
index 0000000..7177286
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.projecttag;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.es.ProjectIndexers;
+import org.sonar.server.user.UserSession;
+
+import static java.util.Collections.singletonList;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_TAGS_UPDATE;
+import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+
+public class TagsWsSupport {
+
+  private final DbClient dbClient;
+  private final ComponentFinder componentFinder;
+  private final UserSession userSession;
+  private final ProjectIndexers projectIndexers;
+  private final System2 system2;
+
+  /**
+   * The characters allowed in project tags are lower-case
+   * letters, digits, plus (+), sharp (#), dash (-) and dot (.)
+   */
+  private static final Pattern VALID_TAG_REGEXP = Pattern.compile("[a-z0-9+#\\-.]+$");
+
+  public TagsWsSupport(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, ProjectIndexers projectIndexers, System2 system2) {
+    this.dbClient = dbClient;
+    this.componentFinder = componentFinder;
+    this.userSession = userSession;
+    this.projectIndexers = projectIndexers;
+    this.system2 = system2;
+  }
+
+  public void updateProjectTags(DbSession dbSession, String projectKey, List<String> providedTags) {
+    List<String> validatedTags = checkAndUnifyTags(providedTags);
+    ProjectDto project = componentFinder.getProjectByKey(dbSession, projectKey);
+    updateTagsForProjectsOrApplication(dbSession, validatedTags, project);
+  }
+
+  public void updateApplicationTags(DbSession dbSession, String applicationKey, List<String> providedTags) {
+    List<String> validatedTags = checkAndUnifyTags(providedTags);
+    ProjectDto application = componentFinder.getApplicationByKey(dbSession, applicationKey);
+    updateTagsForProjectsOrApplication(dbSession, validatedTags, application);
+  }
+
+  private void updateTagsForProjectsOrApplication(DbSession dbSession, List<String> tags, ProjectDto projectOrApplication) {
+    userSession.checkProjectPermission(UserRole.ADMIN, projectOrApplication);
+    projectOrApplication.setTags(tags);
+    projectOrApplication.setUpdatedAt(system2.now());
+    dbClient.projectDao().updateTags(dbSession, projectOrApplication);
+
+    projectIndexers.commitAndIndexProjects(dbSession, singletonList(projectOrApplication), PROJECT_TAGS_UPDATE);
+  }
+
+  public static List<String> checkAndUnifyTags(List<String> tags) {
+    return tags.stream()
+      .filter(StringUtils::isNotBlank)
+      .map(t -> t.toLowerCase(Locale.ENGLISH))
+      .map(TagsWsSupport::checkTag)
+      .distinct()
+      .collect(MoreCollectors.toList());
+  }
+
+  private static String checkTag(String tag) {
+    checkRequest(VALID_TAG_REGEXP.matcher(tag).matches(), "Tag '%s' is invalid. Tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'", tag);
+    return tag;
+  }
+}
index 7e8a9b7409397e1fa317b75d1cfe293d950783ab..4e3039f20b576f120c0b1c597367508ed8ddec88 100644 (file)
 package org.sonar.server.projecttag.ws;
 
 import org.sonar.core.platform.Module;
+import org.sonar.server.projecttag.TagsWsSupport;
 
 public class ProjectTagsWsModule extends Module {
   @Override
   protected void configureModule() {
     add(
+      TagsWsSupport.class,
       ProjectTagsWs.class,
       SetAction.class,
       SearchAction.class
index e1f71885ac2851716c3b093036a39dcc75d7bb25..7ab3ff39ff5d1d340d5bcd03087ce2c134b3026e 100644 (file)
 package org.sonar.server.projecttag.ws;
 
 import java.util.List;
-import java.util.Locale;
-import org.apache.commons.lang.StringUtils;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.api.utils.System2;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.es.ProjectIndexers;
-import org.sonar.server.user.UserSession;
+import org.sonar.server.projecttag.TagsWsSupport;
 
-import static java.util.Collections.singletonList;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_TAGS_UPDATE;
-import static org.sonar.server.exceptions.BadRequestException.checkRequest;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 
 public class SetAction implements ProjectTagsWsAction {
-  /**
-   * The characters allowed in project tags are lower-case
-   * letters, digits, plus (+), sharp (#), dash (-) and dot (.)
-   */
-  private static final String VALID_TAG_REGEXP = "[a-z0-9+#\\-.]+$";
+
   private static final String PARAM_PROJECT = "project";
   private static final String PARAM_TAGS = "tags";
 
   private final DbClient dbClient;
-  private final ComponentFinder componentFinder;
-  private final UserSession userSession;
-  private final ProjectIndexers projectIndexers;
-  private final System2 system2;
+  private final TagsWsSupport tagsWsSupport;
 
-  public SetAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, ProjectIndexers projectIndexers, System2 system2) {
+  public SetAction(DbClient dbClient, TagsWsSupport tagsWsSupport) {
     this.dbClient = dbClient;
-    this.componentFinder = componentFinder;
-    this.userSession = userSession;
-    this.projectIndexers = projectIndexers;
-    this.system2 = system2;
+    this.tagsWsSupport = tagsWsSupport;
   }
 
   @Override
@@ -87,29 +65,12 @@ public class SetAction implements ProjectTagsWsAction {
   @Override
   public void handle(Request request, Response response) throws Exception {
     String projectKey = request.mandatoryParam(PARAM_PROJECT);
-    List<String> tags = request.mandatoryParamAsStrings(PARAM_TAGS).stream()
-      .filter(StringUtils::isNotBlank)
-      .map(t -> t.toLowerCase(Locale.ENGLISH))
-      .map(SetAction::checkTag)
-      .distinct()
-      .collect(MoreCollectors.toList());
+    List<String> tags = request.mandatoryParamAsStrings(PARAM_TAGS);
 
     try (DbSession dbSession = dbClient.openSession(false)) {
-      ProjectDto project = componentFinder.getProjectByKey(dbSession, projectKey);
-      userSession.checkProjectPermission(UserRole.ADMIN, project);
-
-      project.setTags(tags);
-      project.setUpdatedAt(system2.now());
-      dbClient.projectDao().updateTags(dbSession, project);
-
-      projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), PROJECT_TAGS_UPDATE);
+      tagsWsSupport.updateProjectTags(dbSession, projectKey, tags);
     }
 
     response.noContent();
   }
-
-  private static String checkTag(String tag) {
-    checkRequest(tag.matches(VALID_TAG_REGEXP), "Tag '%s' is invalid. Project tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'", tag);
-    return tag;
-  }
 }
index bba0d6dd7a64a36333f0110b3d4f74b0f0463384..b699135f0a5a8007b085930e26b327649e857f11 100644 (file)
@@ -30,6 +30,6 @@ public class ProjectTagsWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new ProjectTagsWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 4);
   }
 }
index 0c1df5d6d79cfa4b469b8c868fefdd315e8df4c0..3699e6ea46c325589af30d40115ab25e25328d6e 100644 (file)
@@ -23,7 +23,6 @@ import javax.annotation.Nullable;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.api.web.UserRole;
@@ -38,6 +37,7 @@ import org.sonar.server.es.TestProjectIndexers;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.projecttag.TagsWsSupport;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.TestResponse;
@@ -47,12 +47,11 @@ import static java.lang.String.format;
 import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
 import static java.util.Optional.ofNullable;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
 
 public class SetActionTest {
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
   @Rule
   public UserSessionRule userSession = UserSessionRule.standalone().logIn().setRoot();
   @Rule
@@ -64,7 +63,9 @@ public class SetActionTest {
 
   private TestProjectIndexers projectIndexers = new TestProjectIndexers();
 
-  private WsActionTester ws = new WsActionTester(new SetAction(dbClient, TestComponentFinder.from(db), userSession, projectIndexers, System2.INSTANCE));
+  private TagsWsSupport tagsWsSupport = new TagsWsSupport(dbClient, TestComponentFinder.from(db), userSession, projectIndexers, System2.INSTANCE);
+
+  private WsActionTester ws = new WsActionTester(new SetAction(dbClient, tagsWsSupport));
 
   @Before
   public void setUp() {
@@ -93,7 +94,8 @@ public class SetActionTest {
 
   @Test
   public void override_existing_tags() {
-    project = db.components().insertPrivateProjectDto(c -> {}, p -> p.setTagsString("marketing,languages"));
+    project = db.components().insertPrivateProjectDto(c -> {
+    }, p -> p.setTagsString("marketing,languages"));
 
     call(project.getKey(), "finance,offshore,platform");
 
@@ -118,43 +120,38 @@ public class SetActionTest {
 
   @Test
   public void fail_if_tag_does_not_respect_format() {
-    expectedException.expect(BadRequestException.class);
-    expectedException.expectMessage("_finance_' is invalid. Project tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'");
-
-    call(project.getKey(), "_finance_");
+    assertThatThrownBy(() -> call(project.getKey(), "_finance_"))
+      .isInstanceOf(BadRequestException.class)
+      .hasMessage("Tag '_finance_' is invalid. Tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'");
   }
 
   @Test
   public void fail_if_not_project_admin() {
     userSession.logIn().addProjectPermission(UserRole.USER, project);
 
-    expectedException.expect(ForbiddenException.class);
-
-    call(project.getKey(), "platform");
+    assertThatThrownBy(() -> call(project.getKey(), "platform"))
+      .isInstanceOf(ForbiddenException.class);
   }
 
   @Test
   public void fail_if_no_project() {
-    expectedException.expect(IllegalArgumentException.class);
-
-    call(null, "platform");
+    assertThatThrownBy(() -> call(null, "platform"))
+      .isInstanceOf(IllegalArgumentException.class);
   }
 
   @Test
   public void fail_if_no_tags() {
-    expectedException.expect(IllegalArgumentException.class);
-
-    call(project.getKey(), null);
+    assertThatThrownBy(() -> call(project.getKey(), null))
+      .isInstanceOf(IllegalArgumentException.class);
   }
 
   @Test
   public void fail_if_component_is_a_view() {
     ComponentDto view = db.components().insertView(v -> v.setDbKey("VIEW_KEY"));
 
-    expectedException.expect(NotFoundException.class);
-    expectedException.expectMessage("Project 'VIEW_KEY' not found");
-
-    call(view.getKey(), "point-of-view");
+    assertThatThrownBy(() -> call(view.getKey(), "point-of-view"))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Project 'VIEW_KEY' not found");
   }
 
   @Test
@@ -162,10 +159,9 @@ public class SetActionTest {
     ComponentDto projectComponent = dbClient.componentDao().selectByUuid(dbSession, project.getUuid()).get();
     ComponentDto module = db.components().insertComponent(newModuleDto(projectComponent).setDbKey("MODULE_KEY"));
 
-    expectedException.expect(NotFoundException.class);
-    expectedException.expectMessage("Project 'MODULE_KEY' not found");
-
-    call(module.getKey(), "modz");
+    assertThatThrownBy(() -> call(module.getKey(), "modz"))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Project 'MODULE_KEY' not found");
   }
 
   @Test
@@ -173,10 +169,9 @@ public class SetActionTest {
     ComponentDto projectComponent = dbClient.componentDao().selectByUuid(dbSession, project.getUuid()).get();
     ComponentDto file = db.components().insertComponent(newFileDto(projectComponent).setDbKey("FILE_KEY"));
 
-    expectedException.expect(NotFoundException.class);
-    expectedException.expectMessage("Project 'FILE_KEY' not found");
-
-    call(file.getKey(), "secret");
+    assertThatThrownBy(() -> call(file.getKey(), "secret"))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Project 'FILE_KEY' not found");
   }
 
   @Test
@@ -186,10 +181,9 @@ public class SetActionTest {
     userSession.logIn().addProjectPermission(UserRole.USER, project);
     ComponentDto branch = db.components().insertProjectBranch(project);
 
-    expectedException.expect(NotFoundException.class);
-    expectedException.expectMessage(format("Project '%s' not found", branch.getDbKey()));
-
-    call(branch.getDbKey(), "secret");
+    assertThatThrownBy(() -> call(branch.getDbKey(), "secret"))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage(format("Project '%s' not found", branch.getDbKey()));
   }
 
   @Test