diff options
author | Fabrice Bellingard <fabrice.bellingard@sonarsource.com> | 2012-12-05 12:13:48 +0100 |
---|---|---|
committer | Fabrice Bellingard <fabrice.bellingard@sonarsource.com> | 2012-12-05 12:43:20 +0100 |
commit | 483908be86265d48c16f67999530cec2b013c068 (patch) | |
tree | 2a4d02a290a10b55fd73103c0b35f6931875281a | |
parent | 1dcb87b93525d1b838a23212626ba1c7ffbaec81 (diff) | |
download | sonarqube-483908be86265d48c16f67999530cec2b013c068.tar.gz sonarqube-483908be86265d48c16f67999530cec2b013c068.zip |
SONAR-3862 Provide a link to all "Projects", "Views", "Developers", ...
=> Actually, a link to all "root" resources
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> |