From 483908be86265d48c16f67999530cec2b013c068 Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Wed, 5 Dec 2012 12:13:48 +0100 Subject: [PATCH] SONAR-3862 Provide a link to all "Projects", "Views", "Developers", ... => Actually, a link to all "root" resources --- .../resources/org/sonar/l10n/core.properties | 15 +++ .../sonar/api/resources/ResourceTypeTree.java | 23 +++- .../sonar/api/resources/ResourceTypes.java | 11 ++ .../api/resources/ResourceTypeTreeTest.java | 6 +- .../api/resources/ResourceTypesTest.java | 39 ++++--- .../java/org/sonar/server/ui/JRubyFacade.java | 16 ++- .../app/controllers/entities_controller.rb | 36 ++++++ .../main/webapp/WEB-INF/app/models/project.rb | 7 ++ .../WEB-INF/app/views/entities/index.html.erb | 107 ++++++++++++++++++ .../app/views/layouts/_menu_projects.html.erb | 10 +- 10 files changed, 240 insertions(+), 30 deletions(-) create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/controllers/entities_controller.rb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/entities/index.html.erb 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 #------------------------------------------------------------------------------ # @@ -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 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 types; private ListMultimap 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 getTypes() { @@ -55,6 +64,10 @@ public final class ResourceTypeTree implements BatchExtension, ServerExtension { return relations.get(qualifier); } + public ResourceType getRootType() { + return root; + } + public List getLeaves() { return ImmutableList.copyOf(Collections2.filter(relations.values(), new Predicate() { public boolean apply(String qualifier) { @@ -70,6 +83,7 @@ public final class ResourceTypeTree implements BatchExtension, ServerExtension { public static final class Builder { private List types = Lists.newArrayList(); private ListMultimap relations = ArrayListMultimap.create(); + private ResourceType root; private Builder() { } @@ -90,6 +104,13 @@ public final class ResourceTypeTree implements BatchExtension, ServerExtension { } public ResourceTypeTree build() { + Collection 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 treeByQualifier; private final Map typeByQualifier; + private final Collection rootTypes; public ResourceTypes(ResourceTypeTree[] trees) { Preconditions.checkNotNull(trees); Map treeMap = Maps.newHashMap(); Map typeMap = Maps.newLinkedHashMap(); + Collection 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 getRoots() { + return rootTypes; + } + public Collection getAll(Predicate 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)); @@ -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)); 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() { @@ -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 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 qualifiers(Collection 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 executeMeasureFilter(Map map, @Nullable Long userId) throws ParseException { + public List executeMeasureFilter(Map 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 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 %> + +<% end %> + +<% if @filter.results %> + +

<%= message('qualifiers.all.' + @qualifier) -%>

+
+ +
+ + <% if @filter.security_exclusions %> +

<%= message('results_not_display_due_to_security') -%>

+ <% end %> + + <% + display_favourites = logged_in? + colspan = 4 + colspan += 1 if display_favourites + %> + + + + + <% if display_favourites %> + + <% end %> + + + + + + + + + <% @filter.results.each do |result| %> + + <% if display_favourites %> + + <% end %> + + + + + + <% end %> + + <% if @filter.results.empty? %> + + + + <% end %> + + + <%= render :partial => 'utils/tfoot_pagination', :locals => {:pagination => @filter.pagination, :colspan => colspan} %> + +
+ <%= 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 %> + + <%= 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 %> + + <%= 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 %> + + <%= message('entities.cols.links') -%> +
<%= link_to_favourite(result.snapshot.resource) -%> + <%= qualifier_icon(result.snapshot)-%> <%= link_to(result.snapshot.resource.name(true), {:controller => 'dashboard', :id => result.snapshot.resource_id}, :title => result.snapshot.resource.key) -%> + + <%= h result.snapshot.resource.description -%> + + <%= result.snapshot.resource.kee -%> + + <% + 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 + %> +
<%= message 'no_data' -%>
+ +
+ +<% 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 @@ <%= message('layout.projects') -%>