summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabrice Bellingard <fabrice.bellingard@sonarsource.com>2012-12-05 12:13:48 +0100
committerFabrice Bellingard <fabrice.bellingard@sonarsource.com>2012-12-05 12:43:20 +0100
commit483908be86265d48c16f67999530cec2b013c068 (patch)
tree2a4d02a290a10b55fd73103c0b35f6931875281a
parent1dcb87b93525d1b838a23212626ba1c7ffbaec81 (diff)
downloadsonarqube-483908be86265d48c16f67999530cec2b013c068.tar.gz
sonarqube-483908be86265d48c16f67999530cec2b013c068.zip
SONAR-3862 Provide a link to all "Projects", "Views", "Developers", ...
=> Actually, a link to all "root" resources
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties15
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypeTree.java23
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java11
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java6
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java39
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java16
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/entities_controller.rb36
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/project.rb7
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/entities/index.html.erb107
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_menu_projects.html.erb10
10 files changed, 240 insertions, 30 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
index 663f3c7f2c1..cbabdb74440 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
@@ -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
#------------------------------------------------------------------------------
#
@@ -496,6 +499,18 @@ 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
#
#------------------------------------------------------------------------------
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypeTree.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypeTree.java
index 88fb6f72379..2fc904c4db1 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypeTree.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypeTree.java
@@ -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);
}
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java
index 4834fa61848..e834fd6b40a 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java
@@ -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);
}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java
index c8f73ec0596..5e64e9866ff 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java
@@ -37,7 +37,6 @@ public class ResourceTypeTreeTest {
.addRelations("DIR", "UTS")
.build();
-
@Test
public void getTypes() {
assertThat(tree.getTypes().size(), is(4));
@@ -56,6 +55,11 @@ public class ResourceTypeTreeTest {
}
@Test
+ public void getRoot() {
+ assertThat(tree.getRootType(), is(ResourceType.builder("TRK").build()));
+ }
+
+ @Test
public void getLeaves() {
assertThat(tree.getLeaves().size(), is(2));
assertThat(tree.getLeaves(), hasItems("FIL", "UTS"));
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java
index 673a60a70b3..440bd1fd60b 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java
@@ -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() {
@@ -60,6 +60,11 @@ public class ResourceTypesTest {
}
@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);
assertThat(qualifiers(forFilters)).containsOnly(Qualifiers.PROJECT, Qualifiers.VIEW).doesNotHaveDuplicates();
@@ -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) {
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 681912fd13a..18ee0d05a65 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -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
index 00000000000..21a3cb1923e
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/entities_controller.rb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
index e37e69b3c8a..39580f3327c 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
@@ -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
index 00000000000..5a0782cb89f
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/entities/index.html.erb
@@ -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
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_menu_projects.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_menu_projects.html.erb
index efc3e43f07a..30107900edc 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_menu_projects.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_menu_projects.html.erb
@@ -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>