From e41d709008c5c56e6ea3c61879ec161881ba227a Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Wed, 28 Aug 2013 11:09:58 +0200 Subject: [PATCH] SONAR-4569 The 'Bulk Deletion'>'Ghosts' page should display ALL projects whose definition exist in the DB but which are not displayed in the SonarQube main page --- .../org/sonar/core/resource/ResourceDao.java | 28 +++++- .../sonar/core/resource/ResourceMapper.java | 6 +- .../sonar/core/resource/ResourceMapper.xml | 43 ++++++++- .../sonar/core/resource/ResourceDaoTest.java | 46 ++++++--- ...-ghost-projects-and-technical-project.xml} | 0 .../DefaultRubyComponentService.java | 10 +- .../controllers/bulk_deletion_controller.rb | 36 ++----- .../app/views/bulk_deletion/ghosts.html.erb | 4 +- .../app/views/bulk_deletion/index.html.erb | 96 +++++++++---------- .../DefaultRubyComponentServiceTest.java | 26 ++++- 10 files changed, 184 insertions(+), 111 deletions(-) rename sonar-core/src/test/resources/org/sonar/core/resource/ResourceDaoTest/{fixture-including-technical-project-and-not-finished-projects.xml => fixture-including-ghost-projects-and-technical-project.xml} (100%) diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceDao.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceDao.java index db0ce0dca7b..da292f939b0 100644 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceDao.java +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceDao.java @@ -201,28 +201,46 @@ public class ResourceDao { } } - public List selectComponentsByQualifiers(Collection qualifiers) { + public List selectProjectsByQualifiers(Collection qualifiers) { if (qualifiers.isEmpty()) { return Collections.emptyList(); } SqlSession session = mybatis.openSession(); try { - return toComponents(session.getMapper(ResourceMapper.class).selectComponentsByQualifiers(qualifiers)); + return toComponents(session.getMapper(ResourceMapper.class).selectProjectsByQualifiers(qualifiers)); } finally { MyBatis.closeQuietly(session); } } /** - * Return enabled components including not completed ones, ie without snapshots or without snapshot having islast=true + * Return enabled projects including not completed ones, ie without snapshots or without snapshot having islast=true */ - public List selectComponentsIncludingNotCompletedOnesByQualifiers(Collection qualifiers) { + public List selectProjectsIncludingNotCompletedOnesByQualifiers(Collection qualifiers) { if (qualifiers.isEmpty()) { return Collections.emptyList(); } SqlSession session = mybatis.openSession(); try { - return toComponents(session.getMapper(ResourceMapper.class).selectComponentsIncludingNotCompletedOnesByQualifiers(qualifiers)); + return toComponents(session.getMapper(ResourceMapper.class).selectProjectsIncludingNotCompletedOnesByQualifiers(qualifiers)); + } finally { + MyBatis.closeQuietly(session); + } + } + + /** + * Return ghosts projects : + * - not enabled projects + * - enabled projects without snapshot having islast=true + * - enabled projects without snapshot + */ + public List selectGhostsProjects(Collection qualifiers) { + if (qualifiers.isEmpty()) { + return Collections.emptyList(); + } + SqlSession session = mybatis.openSession(); + try { + return toComponents(session.getMapper(ResourceMapper.class).selectGhostsProjects(qualifiers)); } finally { MyBatis.closeQuietly(session); } diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceMapper.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceMapper.java index 5b947c7bfa8..488dbe38b2b 100644 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceMapper.java @@ -74,9 +74,11 @@ public interface ResourceMapper { List selectAuthorizedChildrenComponentIds(@Param("componentRootKeys") Collection componentRootKeys, @Param("userId") @Nullable Integer userId, @Param("role") String role); - List selectComponentsByQualifiers(@Param("qualifiers") Collection qualifier); + List selectProjectsIncludingNotCompletedOnesByQualifiers(@Param("qualifiers") Collection qualifier); - List selectComponentsIncludingNotCompletedOnesByQualifiers(@Param("qualifiers") Collection qualifier); + List selectProjectsByQualifiers(@Param("qualifiers") Collection qualifier); + + List selectGhostsProjects(@Param("qualifiers") Collection qualifier); void insert(ResourceDto resource); diff --git a/sonar-core/src/main/resources/org/sonar/core/resource/ResourceMapper.xml b/sonar-core/src/main/resources/org/sonar/core/resource/ResourceMapper.xml index 83f8185faea..b74a7eb0d87 100644 --- a/sonar-core/src/main/resources/org/sonar/core/resource/ResourceMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/resource/ResourceMapper.xml @@ -3,6 +3,22 @@ + + p.id, + p.kee as key, + p.name as name, + p.long_name as longName, + p.root_id as rootId, + p.scope as scope, + p.qualifier as qualifier, + p.enabled as enabled, + p.description as description, + p.language as language, + p.copy_resource_id as copyResourceId, + p.person_id as personId, + p.created_at as createdAt + + @@ -120,9 +136,8 @@ - select * from projects p - inner join snapshots s on s.project_id=p.id and p.qualifier=#{qualifier} @@ -130,12 +145,16 @@ and p.enabled=${_true} and p.copy_resource_id is null - and s.islast=${_true} - + + + + + select from projects p + inner join snapshots s on s.project_id=p.id and p.qualifier=#{qualifier} @@ -143,6 +162,20 @@ and p.enabled=${_true} and p.copy_resource_id is null + and s.islast=${_true} + + + + diff --git a/sonar-core/src/test/java/org/sonar/core/resource/ResourceDaoTest.java b/sonar-core/src/test/java/org/sonar/core/resource/ResourceDaoTest.java index cd77a7eed8a..a3b904595aa 100644 --- a/sonar-core/src/test/java/org/sonar/core/resource/ResourceDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/resource/ResourceDaoTest.java @@ -19,6 +19,8 @@ */ package org.sonar.core.resource; +import com.google.common.base.Function; +import com.google.common.collect.Iterables; import org.apache.ibatis.session.SqlSession; import org.junit.Before; import org.junit.Test; @@ -28,6 +30,8 @@ import org.sonar.api.resources.Scopes; import org.sonar.core.component.ComponentDto; import org.sonar.core.persistence.AbstractDaoTestCase; +import javax.annotation.Nullable; + import java.util.Collection; import java.util.Collections; import java.util.List; @@ -311,26 +315,46 @@ public class ResourceDaoTest extends AbstractDaoTestCase { } @Test - public void should_select_components_by_qualifiers(){ - setupData("fixture-including-technical-project-and-not-finished-projects"); + public void should_select_projects_by_qualifiers(){ + setupData("fixture-including-ghost-projects-and-technical-project"); - List components = dao.selectComponentsByQualifiers(newArrayList("TRK")); + List components = dao.selectProjectsByQualifiers(newArrayList("TRK")); assertThat(components).hasSize(1); assertThat(components.get(0).key()).isEqualTo("org.struts:struts"); assertThat(((ComponentDto)components.get(0)).getId()).isEqualTo(1L); - assertThat(dao.selectComponentsIncludingNotCompletedOnesByQualifiers(newArrayList("unknown"))).isEmpty(); - assertThat(dao.selectComponentsIncludingNotCompletedOnesByQualifiers(Collections.emptyList())).isEmpty(); + assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(newArrayList("unknown"))).isEmpty(); + assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(Collections.emptyList())).isEmpty(); + } + + @Test + public void should_select_projects_including_not_finished_by_qualifiers(){ + setupData("fixture-including-ghost-projects-and-technical-project"); + + List components = dao.selectProjectsIncludingNotCompletedOnesByQualifiers(newArrayList("TRK")); + assertThat(getKeys(components)).containsOnly("org.struts:struts", "org.apache.shindig", "org.sample:sample"); + + assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(newArrayList("unknown"))).isEmpty(); + assertThat(dao.selectProjectsIncludingNotCompletedOnesByQualifiers(Collections.emptyList())).isEmpty(); } @Test - public void should_select_components_including_not_finished_by_qualifiers(){ - setupData("fixture-including-technical-project-and-not-finished-projects"); + public void should_select_ghosts_projects_by_qualifiers(){ + setupData("fixture-including-ghost-projects-and-technical-project"); - List components = dao.selectComponentsIncludingNotCompletedOnesByQualifiers(newArrayList("TRK")); - assertThat(components).hasSize(3); + List components = dao.selectGhostsProjects(newArrayList("TRK")); + assertThat(getKeys(components)).containsOnly("org.apache.shindig", "org.sample:sample", "org.apache:tika"); + + assertThat(dao.selectGhostsProjects(newArrayList("unknown"))).isEmpty(); + assertThat(dao.selectGhostsProjects(Collections.emptyList())).isEmpty(); + } - assertThat(dao.selectComponentsIncludingNotCompletedOnesByQualifiers(newArrayList("unknown"))).isEmpty(); - assertThat(dao.selectComponentsIncludingNotCompletedOnesByQualifiers(Collections.emptyList())).isEmpty(); + private List getKeys(final List components){ + return newArrayList(Iterables.transform(components, new Function() { + @Override + public String apply(@Nullable Component input) { + return input.key(); + } + })); } } diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceDaoTest/fixture-including-technical-project-and-not-finished-projects.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceDaoTest/fixture-including-ghost-projects-and-technical-project.xml similarity index 100% rename from sonar-core/src/test/resources/org/sonar/core/resource/ResourceDaoTest/fixture-including-technical-project-and-not-finished-projects.xml rename to sonar-core/src/test/resources/org/sonar/core/resource/ResourceDaoTest/fixture-including-ghost-projects-and-technical-project.xml diff --git a/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java b/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java index cd6aff52739..a7942f193ba 100644 --- a/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java +++ b/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java @@ -45,13 +45,19 @@ public class DefaultRubyComponentService implements RubyComponentService { public DefaultComponentQueryResult find(Map params) { ComponentQuery query = toQuery(params); - List components = resourceDao.selectComponentsByQualifiers(query.qualifiers()); + List components = resourceDao.selectProjectsByQualifiers(query.qualifiers()); return finder.find(query, components); } public DefaultComponentQueryResult findWithUncompleteProjects(Map params) { ComponentQuery query = toQuery(params); - List components = resourceDao.selectComponentsIncludingNotCompletedOnesByQualifiers(query.qualifiers()); + List components = resourceDao.selectProjectsIncludingNotCompletedOnesByQualifiers(query.qualifiers()); + return finder.find(query, components); + } + + public DefaultComponentQueryResult findGhostsProjects(Map params) { + ComponentQuery query = toQuery(params); + List components = resourceDao.selectGhostsProjects(query.qualifiers()); return finder.find(query, components); } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb index b4c2f9c12be..51dbf844be1 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb @@ -31,21 +31,12 @@ class BulkDeletionController < ApplicationController @tabs = deletable_qualifiers - @selected_tab = params[:resource_type] + @selected_tab = params[:qualifiers] @selected_tab = 'TRK' unless @tabs.include?(@selected_tab) - # Search for resources having snapshot with islast column to true - # SONAR-4569 - conditions = "resource_index.qualifier=:qualifier AND projects.qualifier=:qualifier AND projects.enabled=:enabled AND snapshots.islast=:islast" - values = {:qualifier => @selected_tab, :enabled => true, :islast => true} - if params[:name_filter] && !params[:name_filter].blank? - conditions += " AND resource_index.kee LIKE :kee" - values[:kee] = params[:name_filter].strip.downcase + '%' - end - @resources = Project.all(:select => 'distinct(resource_index.resource_id),projects.id,projects.name,projects.kee,projects.long_name', - :conditions => [conditions, values], - :joins => [:resource_index, :snapshots]) - @resources = Api::Utils.insensitive_sort!(@resources){|r| r.name} + params['pageSize'] = 20 + params['qualifiers'] = @selected_tab + @query_result = Internal.component_api.find(params) end def ghosts @@ -55,23 +46,12 @@ class BulkDeletionController < ApplicationController end @tabs = deletable_qualifiers - - conditions = "scope=:scope AND qualifier IN (:qualifiers) AND status=:status" - values = {:scope => 'PRJ', :qualifiers => @tabs} - unprocessed_project_ids = Snapshot.all( - :select => 'project_id', - :conditions => [conditions, values.merge({:status => Snapshot::STATUS_UNPROCESSED})]).map(&:project_id).uniq - already_processed_project_ids = Snapshot.all( - :select => 'project_id', - :conditions => [conditions + " AND project_id IN (:pids)", values.merge({:status => Snapshot::STATUS_PROCESSED, :pids => unprocessed_project_ids})]).map(&:project_id).uniq - # SONAR-4569 - # Detect active projects without any snapshots or with no snapshot having islast column to true - projects_not_having_snapshots_islast_to_true = - Project.find_by_sql ["SELECT p.id FROM projects p WHERE p.enabled=? AND p.scope=? AND p.qualifier IN (?) AND NOT EXISTS (SELECT id FROM snapshots WHERE islast=? and project_id=p.id)", - true, 'PRJ', @tabs, true] + params['pageSize'] = -1 + params['qualifiers'] = @tabs + @query_result = Internal.component_api.findGhostsProjects(params) - @ghosts = Project.all(:conditions => ["id IN (?)", unprocessed_project_ids - already_processed_project_ids + projects_not_having_snapshots_islast_to_true]) + @ghosts = @query_result.components @ghosts_by_qualifier = {} @ghosts.each do |p| diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb index f355ea48a50..e141f0ef2b8 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/ghosts.html.erb @@ -40,9 +40,9 @@
    <% - ghosts.sort {|x,y| x.name <=> y.name}.each_with_index do |resource, index| + ghosts.sort {|x,y| x.name <=> y.name}.each do |resource| %> -
  • <%= h resource.name -%> ( <%= h resource.key -%> )
  • +
  • <%= h resource.name -%> ( <%= h resource.key -%> )
  • <% end %> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb index 29d2118ae52..721bc9be79b 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb @@ -3,7 +3,7 @@
      <% @tabs.each do |tab| %>
    • - id="tab-<%= u tab -%>"><%= message('qualifiers.' + tab) -%> + id="tab-<%= u tab -%>"><%= message('qualifiers.' + tab) -%>
    • <% end %>
    • @@ -14,66 +14,58 @@
      <% - found_resources_count = @resources.size - found_resources_ids = @resources.map {|r| r.id.to_s}.join(',') - page_size = (params[:page_size] && params[:page_size].to_i) || 20 + found_resources_count = @query_result.paging.total + found_resources_ids = @query_result.components.map {|r| r.id.to_s}.join(',') %> <% form_tag( {:action => 'index'}, :method => :get ) do %> - <%= message('bulk_deletion.resource_name_filter_by_name') -%> - + <%= message('bulk_deletion.resource_name_filter_by_name') -%> + <%= submit_tag message('bulk_deletion.filter'), :id => 'filter_resources' %> <% end %> - <% if @resources.empty? %> + <% if @query_result.components.empty? %>
      <%= message('no_results') -%> <% else %> - <% form_remote_tag( :url => {:action => 'delete_resources'}, :loading => "window.location='#{url_for :action => 'pending_deletions', :resource_type => @selected_tab}';") do %> - - - - - - - - - - - - - - - - - - - <% @resources.each_with_index do |resource, index| %> - - - - - - - <% end %> - -
      <%= paginate @resources, {:page_size => page_size} %>
      - -
      - « <%= message('bulk_deletion.select_all') -%> - <% if found_resources_count - @resources.size > 0 %> - - - <% end %> -
      - - - <%= h resource.name -%> - - <%= h resource.key -%>
      - + <% form_remote_tag( :url => {:action => 'delete_resources'}, :loading => "window.location='#{url_for :action => 'pending_deletions', :qualifiers => @selected_tab}';") do %> + + + + + + + + + + <% @query_result.components.each_with_index do |resource, index| %> + + + + + + + <% end %> + + <%= paginate_java(@query_result.paging, :colspan => 3, :id => 'projects-bulk-deletion-foot', :include_loading_icon => true) { |label, page_id| + link_to(label, params.merge({:pageIndex => page_id})) + } + %> + +
      + « <%= message('bulk_deletion.select_all') -%> + <% if found_resources_count - @query_result.components.size > 0 %> + + + <% end %> +
      + + + <%= h resource.name -%> + + <%= h resource.key -%>
      <% end %>