]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3862 Provide a link to all "Projects", "Views", "Developers", ...
authorFabrice Bellingard <fabrice.bellingard@sonarsource.com>
Wed, 5 Dec 2012 11:13:48 +0000 (12:13 +0100)
committerFabrice Bellingard <fabrice.bellingard@sonarsource.com>
Wed, 5 Dec 2012 11:43:20 +0000 (12:43 +0100)
=> Actually, a link to all "root" resources

plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypeTree.java
sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java
sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java
sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/entities_controller.rb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
sonar-server/src/main/webapp/WEB-INF/app/views/entities/index.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_menu_projects.html.erb

index 663f3c7f2c15b1a3d4a8f12b6f8832e2d71c2803..cbabdb74440804bda01499dfcdee364fef1ed753 100644 (file)
@@ -249,6 +249,9 @@ qualifiers.FIL=Files
 qualifiers.CLA=Files
 qualifiers.UTS=Unit Test Files
 
+qualifiers.all.TRK=All Projects
+qualifiers.all.VW=All Views
+qualifiers.all.DEV=All Developers
 
 #------------------------------------------------------------------------------
 #
@@ -494,6 +497,18 @@ reviews.filtered_by.from=From date
 reviews.filtered_by.to=To date
 
 
+#------------------------------------------------------------------------------
+#
+# ENTITIES PAGE
+#
+#------------------------------------------------------------------------------
+
+entities.cols.name=Name
+entities.cols.description=Description
+entities.cols.key=Key
+entities.cols.links=Links
+
+
 #------------------------------------------------------------------------------
 #
 # COMPARISON
index 88fb6f72379f5dd7f94aac566cca6ee22de2f8c7..2fc904c4db17fe4ed7a5a746bd41de37daf02e30 100644 (file)
@@ -22,13 +22,20 @@ package org.sonar.api.resources;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
-import com.google.common.collect.*;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
 import org.sonar.api.BatchExtension;
 import org.sonar.api.ServerExtension;
 import org.sonar.api.batch.InstantiationStrategy;
 
 import javax.annotation.concurrent.Immutable;
+
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -41,10 +48,12 @@ public final class ResourceTypeTree implements BatchExtension, ServerExtension {
 
   private List<ResourceType> types;
   private ListMultimap<String, String> relations;
+  private ResourceType root;
 
   private ResourceTypeTree(Builder builder) {
     this.types = ImmutableList.copyOf(builder.types);
     this.relations = ImmutableListMultimap.copyOf(builder.relations);
+    this.root = builder.root;
   }
 
   public List<ResourceType> getTypes() {
@@ -55,6 +64,10 @@ public final class ResourceTypeTree implements BatchExtension, ServerExtension {
     return relations.get(qualifier);
   }
 
+  public ResourceType getRootType() {
+    return root;
+  }
+
   public List<String> getLeaves() {
     return ImmutableList.copyOf(Collections2.filter(relations.values(), new Predicate<String>() {
       public boolean apply(String qualifier) {
@@ -70,6 +83,7 @@ public final class ResourceTypeTree implements BatchExtension, ServerExtension {
   public static final class Builder {
     private List<ResourceType> types = Lists.newArrayList();
     private ListMultimap<String, String> relations = ArrayListMultimap.create();
+    private ResourceType root;
 
     private Builder() {
     }
@@ -90,6 +104,13 @@ public final class ResourceTypeTree implements BatchExtension, ServerExtension {
     }
 
     public ResourceTypeTree build() {
+      Collection<String> children = relations.values();
+      for (ResourceType type : types) {
+        if (!children.contains(type)) {
+          root = type;
+          break;
+        }
+      }
       return new ResourceTypeTree(this);
     }
   }
index 4834fa61848008d2858964ca7c9c6433cc09eaae..e834fd6b40a56273120068efd06270381660042a 100644 (file)
@@ -25,13 +25,16 @@ import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.sonar.api.BatchComponent;
 import org.sonar.api.ServerComponent;
 
 import javax.annotation.Nullable;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -51,14 +54,17 @@ public final class ResourceTypes implements BatchComponent, ServerComponent {
 
   private final Map<String, ResourceTypeTree> treeByQualifier;
   private final Map<String, ResourceType> typeByQualifier;
+  private final Collection<ResourceType> rootTypes;
 
   public ResourceTypes(ResourceTypeTree[] trees) {
     Preconditions.checkNotNull(trees);
 
     Map<String, ResourceTypeTree> treeMap = Maps.newHashMap();
     Map<String, ResourceType> typeMap = Maps.newLinkedHashMap();
+    Collection<ResourceType> rootsSet = Sets.newHashSet();
 
     for (ResourceTypeTree tree : trees) {
+      rootsSet.add(tree.getRootType());
       for (ResourceType type : tree.getTypes()) {
         if (treeMap.containsKey(type.getQualifier())) {
           throw new IllegalStateException("Qualifier " + type.getQualifier() + " is defined in several trees");
@@ -69,6 +75,7 @@ public final class ResourceTypes implements BatchComponent, ServerComponent {
     }
     treeByQualifier = ImmutableMap.copyOf(treeMap);
     typeByQualifier = ImmutableMap.copyOf(typeMap);
+    rootTypes = ImmutableList.copyOf(rootsSet);
   }
 
   public ResourceType get(String qualifier) {
@@ -80,6 +87,10 @@ public final class ResourceTypes implements BatchComponent, ServerComponent {
     return typeByQualifier.values();
   }
 
+  public Collection<ResourceType> getRoots() {
+    return rootTypes;
+  }
+
   public Collection<ResourceType> getAll(Predicate<ResourceType> predicate) {
     return Collections2.filter(typeByQualifier.values(), predicate);
   }
index c8f73ec0596ee02e9b397f980e48e2a612fbd94c..5e64e9866ff99d7c99da42e8e783a55057468c05 100644 (file)
@@ -37,7 +37,6 @@ public class ResourceTypeTreeTest {
       .addRelations("DIR", "UTS")
       .build();
 
-
   @Test
   public void getTypes() {
     assertThat(tree.getTypes().size(), is(4));
@@ -55,6 +54,11 @@ public class ResourceTypeTreeTest {
     assertThat(tree.getChildren("FIL").size(), is(0));
   }
 
+  @Test
+  public void getRoot() {
+    assertThat(tree.getRootType(), is(ResourceType.builder("TRK").build()));
+  }
+
   @Test
   public void getLeaves() {
     assertThat(tree.getLeaves().size(), is(2));
index 673a60a70b3d909050748dda0eb8718718080c44..440bd1fd60bc45d75bccacda7bd50a2224bfd799 100644 (file)
@@ -30,21 +30,21 @@ import static org.fest.assertions.Assertions.assertThat;
 public class ResourceTypesTest {
 
   private ResourceTypeTree viewsTree = ResourceTypeTree.builder()
-    .addType(ResourceType.builder(Qualifiers.VIEW).setProperty("supportsMeasureFilters", "true").build())
-    .addType(ResourceType.builder(Qualifiers.SUBVIEW).build())
-    .addRelations(Qualifiers.VIEW, Qualifiers.SUBVIEW)
-    .addRelations(Qualifiers.SUBVIEW, Qualifiers.PROJECT)
-    .build();
+      .addType(ResourceType.builder(Qualifiers.VIEW).setProperty("supportsMeasureFilters", "true").build())
+      .addType(ResourceType.builder(Qualifiers.SUBVIEW).build())
+      .addRelations(Qualifiers.VIEW, Qualifiers.SUBVIEW)
+      .addRelations(Qualifiers.SUBVIEW, Qualifiers.PROJECT)
+      .build();
 
   private ResourceTypeTree defaultTree = ResourceTypeTree.builder()
-    .addType(ResourceType.builder(Qualifiers.PROJECT).setProperty("supportsMeasureFilters", "true").build())
-    .addType(ResourceType.builder(Qualifiers.DIRECTORY).build())
-    .addType(ResourceType.builder(Qualifiers.FILE).build())
-    .addRelations(Qualifiers.PROJECT, Qualifiers.DIRECTORY)
-    .addRelations(Qualifiers.DIRECTORY, Qualifiers.FILE)
-    .build();
+      .addType(ResourceType.builder(Qualifiers.PROJECT).setProperty("supportsMeasureFilters", "true").build())
+      .addType(ResourceType.builder(Qualifiers.DIRECTORY).build())
+      .addType(ResourceType.builder(Qualifiers.FILE).build())
+      .addRelations(Qualifiers.PROJECT, Qualifiers.DIRECTORY)
+      .addRelations(Qualifiers.DIRECTORY, Qualifiers.FILE)
+      .build();
 
-  private ResourceTypes types = new ResourceTypes(new ResourceTypeTree[]{viewsTree, defaultTree});
+  private ResourceTypes types = new ResourceTypes(new ResourceTypeTree[] {viewsTree, defaultTree});
 
   @Test
   public void get() {
@@ -59,6 +59,11 @@ public class ResourceTypesTest {
     assertThat(qualifiers(types.getAll())).containsOnly(Qualifiers.PROJECT, Qualifiers.DIRECTORY, Qualifiers.FILE, Qualifiers.VIEW, Qualifiers.SUBVIEW);
   }
 
+  @Test
+  public void getRoots() {
+    assertThat(qualifiers(types.getRoots())).containsOnly(Qualifiers.PROJECT, Qualifiers.VIEW);
+  }
+
   @Test
   public void getAll_predicate() {
     Collection<ResourceType> forFilters = types.getAll(ResourceTypes.AVAILABLE_FOR_FILTERS);
@@ -111,13 +116,13 @@ public class ResourceTypesTest {
   @Test(expected = IllegalStateException.class)
   public void failOnDuplicatedQualifier() {
     ResourceTypeTree tree1 = ResourceTypeTree.builder()
-      .addType(ResourceType.builder("foo").build())
-      .build();
+        .addType(ResourceType.builder("foo").build())
+        .build();
     ResourceTypeTree tree2 = ResourceTypeTree.builder()
-      .addType(ResourceType.builder("foo").build())
-      .build();
+        .addType(ResourceType.builder("foo").build())
+        .build();
 
-    new ResourceTypes(new ResourceTypeTree[]{tree1, tree2});
+    new ResourceTypes(new ResourceTypeTree[] {tree1, tree2});
   }
 
   static Collection<String> qualifiers(Collection<ResourceType> types) {
index 681912fd13a7d783ee03a688711d479f85111879..18ee0d05a651b6f3dbf8666847cb91d6f248139e 100644 (file)
@@ -98,7 +98,7 @@ public final class JRubyFacade {
     return getContainer().getComponentByType(componentType);
   }
 
-  public List<MeasureFilterRow> executeMeasureFilter(Map<String,Object> map, @Nullable Long userId) throws ParseException {
+  public List<MeasureFilterRow> executeMeasureFilter(Map<String, Object> map, @Nullable Long userId) throws ParseException {
     return get(MeasureFilterEngine.class).execute(map, userId);
   }
 
@@ -110,6 +110,10 @@ public final class JRubyFacade {
     return get(ResourceTypes.class).getAll();
   }
 
+  public Collection<ResourceType> getResourceRootTypes() {
+    return get(ResourceTypes.class).getRoots();
+  }
+
   public ResourceType getResourceType(String qualifier) {
     return get(ResourceTypes.class).get(qualifier);
   }
@@ -314,7 +318,7 @@ public final class JRubyFacade {
 
   public void ruleSeverityChanged(int parentProfileId, int activeRuleId, int oldSeverityId, int newSeverityId, String userName) {
     getProfilesManager().ruleSeverityChanged(parentProfileId, activeRuleId, RulePriority.values()[oldSeverityId],
-      RulePriority.values()[newSeverityId], userName);
+        RulePriority.values()[newSeverityId], userName);
   }
 
   public void ruleDeactivated(int parentProfileId, int deactivatedRuleId, String userName) {
@@ -510,10 +514,10 @@ public final class JRubyFacade {
     // notifier is null when creating the administrator in the migration script 011.
     if (notifier != null) {
       notifier.onNewUser(NewUserHandler.Context.builder()
-        .setLogin(fields.get("login"))
-        .setName(fields.get("name"))
-        .setEmail(fields.get("email"))
-        .build());
+          .setLogin(fields.get("login"))
+          .setName(fields.get("name"))
+          .setEmail(fields.get("email"))
+          .build());
     }
   }
 
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/entities_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/entities_controller.rb
new file mode 100644 (file)
index 0000000..21a3cb1
--- /dev/null
@@ -0,0 +1,36 @@
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2012 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar 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.
+#
+# Sonar 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 Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+#
+class EntitiesController < ApplicationController
+
+  SECTION=Navigation::SECTION_HOME
+  
+  def index
+    require_parameters :qualifier
+    @qualifier = params[:qualifier]
+    bad_request("The 'qualifier' parameter is not valid. It must reference a root type.") unless Project.root_qualifiers.include?(@qualifier)
+    
+    
+    @filter = MeasureFilter.new
+    @filter.criteria = params.merge({'qualifiers' => [@qualifier], 'cols' => ['name', 'description', 'key', 'links']})
+    @filter.enable_default_display
+    @filter.execute(self, :user => current_user)
+  end
+
+end
\ No newline at end of file
index e37e69b3c8ad06b52d492bfae1dc222c7285eb08..39580f3327c33ebe2df7502db233845570519b93 100644 (file)
@@ -50,6 +50,13 @@ class Project < ActiveRecord::Base
       java_facade.deleteResourceTree(project.id)
     end
   end
+  
+  def self.root_qualifiers()
+    @root_types ||= 
+      begin
+        Java::OrgSonarServerUi::JRubyFacade.getInstance().getResourceRootTypes().map {|type| type.getQualifier()}
+      end
+  end
 
   def project
     root||self
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/entities/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/entities/index.html.erb
new file mode 100644 (file)
index 0000000..5a0782c
--- /dev/null
@@ -0,0 +1,107 @@
+<% content_for :script do %>
+  <script>
+    function removeUrlAttr(url, attribute_key) {
+      var regexp = new RegExp("&?" + attribute_key + "=([^&]$|[^&]*)", "g");
+      return url.replace(regexp, '');
+    }
+    function reloadParameters(params) {
+      var url = decodeURI(window.location.href);
+      $j.each(params, function (key, value) {
+        url = removeUrlAttr(url, key);
+        url += '&' + key + '=' + value;
+      });
+      window.location = url;
+    }
+  </script>
+<% end %>
+
+<% if @filter.results %>
+
+  <h1><%= message('qualifiers.all.' + @qualifier) -%></h1>
+  <br/>
+
+  <div id="entities">
+
+    <% if @filter.security_exclusions %>
+      <p class="notes"><%= message('results_not_display_due_to_security') -%></p>
+    <% end %>
+
+    <%
+      display_favourites = logged_in?
+      colspan = 4
+      colspan += 1 if display_favourites
+    %>
+
+    <table class="data" id="entities-table">
+      <thead>
+        <tr>
+          <% if display_favourites %>
+            <th class="thin"></th>
+          <% end %>
+          <th>
+            <%= link_to_function( message('entities.cols.name'), "reloadParameters({asc:'#{(!@filter.sort_asc?).to_s}', sort:'name'})") -%>
+            <% if @filter.sort_key=='name' %>
+              <%= @filter.sort_asc? ? image_tag("asc12.png") : image_tag("desc12.png") -%>
+            <% end %>
+          </th>
+          <th>
+            <%= link_to_function( message('entities.cols.description'), "reloadParameters({asc:'#{(!@filter.sort_asc?).to_s}', sort:'description'})") -%>
+            <% if @filter.sort_key=='description' %>
+              <%= @filter.sort_asc? ? image_tag("asc12.png") : image_tag("desc12.png") -%>
+            <% end %>
+          </th>
+          <th class="right">
+            <%= link_to_function( message('entities.cols.key'), "reloadParameters({asc:'#{(!@filter.sort_asc?).to_s}', sort:'key'})") -%>
+            <% if @filter.sort_key=='key' %>
+              <%= @filter.sort_asc? ? image_tag("asc12.png") : image_tag("desc12.png") -%>
+            <% end %>
+          </th>
+          <th class="right">
+            <%= message('entities.cols.links') -%>
+          </th>
+        </tr>
+      </thead>
+  
+      <tbody>
+        <% @filter.results.each do |result| %>
+          <tr class="<%= cycle 'even', 'odd' -%>">
+            <% if display_favourites %>
+              <td class="thin"><%= link_to_favourite(result.snapshot.resource) -%></td>
+            <% end %>
+            <td class="nowrap">
+              <%= qualifier_icon(result.snapshot)-%> <%= link_to(result.snapshot.resource.name(true), {:controller => 'dashboard', :id => result.snapshot.resource_id}, :title => result.snapshot.resource.key) -%>
+            </td>
+            <td>
+              <%= h result.snapshot.resource.description -%>
+            </td>
+            <td class="nowrap right">
+              <span class='small'><%= result.snapshot.resource.kee -%></span>
+            </td>
+            <td class="nowrap right">
+              <% 
+                 if result.links
+                   result.links.select { |link| link.href.start_with?('http') }.each do |link|
+              %>
+                <%= link_to(image_tag(link.icon, :alt => link.name), link.href, :class => 'nolink', :popup => true) unless link.custom? -%>
+              <%
+                   end
+                 end
+              %>
+            </td>
+          </tr>
+        <% end %>
+        
+        <% if @filter.results.empty? %>
+          <tr class="even">
+            <td colspan="<%= colspan -%>"><%= message 'no_data' -%></td>
+          </tr>
+        <% end %>
+    </tbody>
+    
+    <%= render :partial => 'utils/tfoot_pagination', :locals => {:pagination => @filter.pagination, :colspan => colspan} %>
+    
+  </table>
+
+  </div>
+  
+<% end %>
\ No newline at end of file
index efc3e43f07a63e0bc3cde6b3c174cff0e2e27722..30107900edcd1264bd9b6b5bf2bc0ee02f095fd2 100644 (file)
@@ -8,17 +8,17 @@
   <a href="#" onclick="if (sonarRecentHistory) { sonarRecentHistory.populateRecentHistoryPanel(); }; $j('#projects-menu').toggle(); return false;" class="link-more"><%= message('layout.projects') -%></a>
   
   <div id="projects-menu" class="dropdown-menu" style="max-width: none; display: none;" onmouseout="$j(this).hide();" onmouseover="$j(this).show();">
-    <div id="recent-history">
+    <div id="recent-history" style="border-bottom: 1px solid #ccc; padding-bottom: 10px;">
       <h2><%= message('layout.recent_activity') -%></h2>
       <ul id="recent-history-list">
       </ul>
     </div>
     
-    <div style="border-top: 1px solid #ccc; margin-top: 10px;">
+    <div>
       <ul>
-        <li><a href="<%= ApplicationController.root_context -%>/">Projects</a></li>
-        <li><a href="<%= ApplicationController.root_context -%>/">Views</a></li>
-        <li><a href="<%= ApplicationController.root_context -%>/">Developers</a></li>
+        <% Project.root_qualifiers.sort.each do |qualifier| %>      
+          <li><a href="<%= ApplicationController.root_context -%>/entities?qualifier=<%= qualifier -%>"><%= message('qualifiers.all.' + qualifier) -%></a></li>
+        <% end %>
       </ul>
     </div>
   </div>