]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9077 api/components/suggestions emphasises matching
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>
Mon, 10 Apr 2017 12:48:22 +0000 (14:48 +0200)
committerDaniel Schwarz <bartfastiel@users.noreply.github.com>
Thu, 20 Apr 2017 07:48:52 +0000 (09:48 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentHit.java
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentHitsPerQualifier.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java
server/sonar-server/src/main/resources/org/sonar/server/component/ws/components-example-suggestions.json
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java

index 1968f6693e471336bcb4587c2f6f53831d552a02..891edc797919a7aacecbcf98752701869b396fa9 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
+ * Copyright (C) 2009-2017 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
index be3ad4ee3be22483e19f5594ba9aef3bf78a2bd2..6576b7fdac9ffd8c01dd19ccadf1e1f6a555b329 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.server.component.index;
 
 import java.util.List;
-import org.sonar.core.util.stream.MoreCollectors;
 
 public class ComponentHitsPerQualifier {
 
@@ -38,10 +37,6 @@ public class ComponentHitsPerQualifier {
     return qualifier;
   }
 
-  public List<String> getComponentUuids() {
-    return hits.stream().map(ComponentHit::getUuid).collect(MoreCollectors.toList(hits.size()));
-  }
-
   public List<ComponentHit> getHits() {
     return hits;
   }
index b88c983d00a9b60e676b6224d1e932a5a33a1921..39dde1a4e10f1b556dd800d185149d5fdf74df95 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.component.ws;
 
+import com.google.common.html.HtmlEscapers;
 import com.google.common.io.Resources;
 import java.util.Collection;
 import java.util.List;
@@ -35,6 +36,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.component.index.ComponentHit;
 import org.sonar.server.component.index.ComponentHitsPerQualifier;
 import org.sonar.server.component.index.ComponentIndex;
 import org.sonar.server.component.index.ComponentIndexQuery;
@@ -77,7 +79,15 @@ public class SuggestionsAction implements ComponentsWsAction {
   @Override
   public void define(WebService.NewController context) {
     NewAction action = context.createAction(ACTION_SUGGESTIONS)
-      .setDescription("Internal WS for the top-right search engine")
+      .setDescription(
+        "Internal WS for the top-right search engine. The result will contain component search results, grouped by their qualifiers.<p>"
+          + "Each result contains:"
+          + "<ul>"
+          + "<li>the organization key</li>"
+          + "<li>the component key</li>"
+          + "<li>the component's name (unescaped)</li>"
+          + "<li>optionally a display name, which puts emphasis to matching characters (this text contains html tags and parts of the html-escaped name)</li>"
+          + "</ul>")
       .setSince("4.2")
       .setInternal(true)
       .setHandler(this)
@@ -142,8 +152,9 @@ public class SuggestionsAction implements ComponentsWsAction {
       Map<String, ComponentDto> projectsByUuids;
       try (DbSession dbSession = dbClient.openSession(false)) {
         Set<String> componentUuids = componentsPerQualifiers.stream()
-          .map(ComponentHitsPerQualifier::getComponentUuids)
+          .map(ComponentHitsPerQualifier::getHits)
           .flatMap(Collection::stream)
+          .map(ComponentHit::getUuid)
           .collect(toSet());
         componentsByUuids = dbClient.componentDao().selectByUuids(dbSession, componentUuids).stream()
           .collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
@@ -172,9 +183,8 @@ public class SuggestionsAction implements ComponentsWsAction {
     Map<String, OrganizationDto> organizationByUuids, Map<String, ComponentDto> projectsByUuids) {
     return componentsPerQualifiers.stream().map(qualifier -> {
 
-      List<Suggestion> suggestions = qualifier.getComponentUuids().stream()
-        .map(componentsByUuids::get)
-        .map(dto -> toSuggestion(dto, organizationByUuids, projectsByUuids))
+      List<Suggestion> suggestions = qualifier.getHits().stream()
+        .map(hit -> toSuggestion(hit, componentsByUuids, organizationByUuids, projectsByUuids))
         .collect(toList());
 
       return Category.newBuilder()
@@ -185,8 +195,10 @@ public class SuggestionsAction implements ComponentsWsAction {
     }).collect(toList());
   }
 
-  private static Suggestion toSuggestion(ComponentDto result, Map<String, OrganizationDto> organizationByUuid, Map<String, ComponentDto> projectsByUuids) {
-    String organizationKey = organizationByUuid.get(result.getOrganizationUuid()).getKey();
+  private static Suggestion toSuggestion(ComponentHit hit, Map<String, ComponentDto> componentsByUuids, Map<String, OrganizationDto> organizationByUuids,
+    Map<String, ComponentDto> projectsByUuids) {
+    ComponentDto result = componentsByUuids.get(hit.getUuid());
+    String organizationKey = organizationByUuids.get(result.getOrganizationUuid()).getKey();
     checkState(organizationKey != null, "Organization with uuid '%s' not found", result.getOrganizationUuid());
     String projectKey = ofNullable(result.projectUuid()).map(projectsByUuids::get).map(ComponentDto::getKey).orElse("");
     return Suggestion.newBuilder()
@@ -194,6 +206,7 @@ public class SuggestionsAction implements ComponentsWsAction {
       .setProject(projectKey)
       .setKey(result.getKey())
       .setName(result.longName())
+      .setMatch(hit.getHighlightedText().orElse(HtmlEscapers.htmlEscaper().escape(result.longName())))
       .build();
   }
 
index cc53d99b9e3f01db4eaeb5215971fb1af2f0af82..7470618842c6388ce0dafa5b8cd280c38a26ed28 100644 (file)
@@ -6,12 +6,14 @@
         {
           "organization": "default-organization",
           "key": "org.sonarsource:sonarqube",
-          "name": "SonarSource :: SonarQube"
+          "name": "SonarSource :: SonarQube",
+          "match": "<mark>Sonar</mark>Source :: SonarQube"
         },
         {
           "organization": "default-organization",
           "key": "org.sonarsource:sonarlint",
-          "name": "SonarSource :: SonarLint"
+          "name": "SonarSource :: SonarLint",
+          "match": "<mark>Sonar</mark>Source :: SonarLint"
         }
       ],
       "more": 74
index cef70eba4def811ad0d9a4e5ff34a969f0e12a0d..1f0c14fa483b2515eea14a72e596fde09d346df9 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
+ * Copyright (C) 2009-2017 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
@@ -29,14 +29,34 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class ComponentIndexHighlightTest extends ComponentIndexTest {
 
+  @Test
+  public void should_escape_html() {
+    assertHighlighting("quick< brown fox", "brown", "quick&lt; <mark>brown</mark> fox");
+  }
+
+  @Test
+  public void should_highlight_partial_name() {
+    assertHighlighting("quickbrownfox", "brown", "quick<mark>brown</mark>fox");
+  }
+
   @Test
   public void should_highlight_prefix() {
-    assertHighlighting("quick brown fox", "brown", "quick <mark>brown</mark> fox");
+    assertHighlighting("quickbrownfox", "quick", "<mark>quick</mark>brownfox");
   }
 
   @Test
-  public void should_escape_html() {
-    assertHighlighting("quick< brown fox", "brown", "quick&lt; <mark>brown</mark> fox");
+  public void should_highlight_suffix() {
+    assertHighlighting("quickbrownfox", "fox", "quickbrown<mark>fox</mark>");
+  }
+
+  @Test
+  public void should_highlight_multiple_words() {
+    assertHighlighting("quickbrownfox", "fox bro", "quick<mark>bro</mark>wn<mark>fox</mark>");
+  }
+
+  @Test
+  public void should_highlight_multiple_connected_words() {
+    assertHighlighting("quickbrownfox", "fox brown", "quick<mark>brownfox</mark>");
   }
 
   private void assertHighlighting(String fileName, String search, String expectedHighlighting) {
index f2bce6bb2483f8d89d3df7f3e2fba3553a8e6e9b..b483f6c7752322643bf45fa8b698efc513fb6850 100644 (file)
@@ -104,7 +104,8 @@ public abstract class ComponentIndexTest {
 
   protected AbstractListAssert<?, ? extends List<? extends String>, String> assertSearch(ComponentIndexQuery query) {
     return assertThat(index.search(query, features.get()))
-      .flatExtracting(ComponentHitsPerQualifier::getComponentUuids);
+      .flatExtracting(ComponentHitsPerQualifier::getHits)
+      .extracting(ComponentHit::getUuid);
   }
 
   protected void assertSearchResults(String query, ComponentDto... expectedComponents) {