diff options
author | Julien Lancelot <julien.lancelot@gmail.com> | 2013-07-15 08:33:56 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@gmail.com> | 2013-07-15 16:19:14 +0200 |
commit | 184756cd798fa9c5120b3d25c068e1f90767eaf0 (patch) | |
tree | fa448f4a6001c212ea4b80a2fae9934b3f0d2c04 | |
parent | a2eee9d6cd812e3a0a37491bc7124add940d4995 (diff) | |
download | sonarqube-184756cd798fa9c5120b3d25c068e1f90767eaf0.tar.gz sonarqube-184756cd798fa9c5120b3d25c068e1f90767eaf0.zip |
SONAR-4419 Roles page: improve search engine to add ability to search by project key
18 files changed, 957 insertions, 124 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 35e3eb87d0c..405e144c325 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 @@ -140,6 +140,7 @@ title=Title to=To treemap=Treemap true=True +type=Type unfollow=Unfollow unit_test=Unit test unit_tests=Unit tests @@ -343,7 +344,7 @@ manual_metrics.page=Manual Metrics manual_measures.page=Manual Measures manual_rules.page=Manual Rules my_profile.page=My Profile -roles.page=Roles +roles.page=Project Permissions project_settings.page=Settings project_links.page=Links project_history.page=History @@ -2244,6 +2245,8 @@ global_permissions.dryRunScan.desc=Ability to execute local (dry run) analyses w # PROJECTS ROLES # #------------------------------------------------------------------------------ +projects_role.criteria.name=Name contains +projects_role.criteria.key=Key contains projects_role.role=Role Membership For New projects_role.users=Users projects_role.groups=Groups 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 e18f0e4bfb1..9c9b4bd71f3 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 @@ -19,6 +19,8 @@ */ package org.sonar.core.resource; +import com.google.common.base.Function; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.apache.ibatis.session.SqlSession; import org.sonar.api.component.Component; @@ -199,7 +201,7 @@ public class ResourceDao { } } - public ComponentDto toComponent(ResourceDto resourceDto){ + public static ComponentDto toComponent(ResourceDto resourceDto){ return new ComponentDto() .setKey(resourceDto.getKey()) .setLongName(resourceDto.getLongName()) @@ -207,6 +209,15 @@ public class ResourceDao { .setQualifier(resourceDto.getQualifier()); } + public static List<ComponentDto> toComponents(List<ResourceDto> resourceDto){ + return newArrayList(Iterables.transform(resourceDto, new Function<ResourceDto, ComponentDto>() { + @Override + public ComponentDto apply(ResourceDto resourceDto) { + return toComponent(resourceDto); + } + })); + } + public void insertUsingExistingSession(ResourceDto resourceDto, SqlSession session) { ResourceMapper resourceMapper = session.getMapper(ResourceMapper.class); resourceMapper.insert(resourceDto); diff --git a/sonar-server/src/main/java/org/sonar/server/component/ComponentQuery.java b/sonar-server/src/main/java/org/sonar/server/component/ComponentQuery.java new file mode 100644 index 00000000000..32bcb356557 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/component/ComponentQuery.java @@ -0,0 +1,190 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.component; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang.builder.ReflectionToStringBuilder; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +/** + * @since 3.7 + */ +public class ComponentQuery { + + public static final int DEFAULT_PAGE_INDEX = 1; + public static final int DEFAULT_PAGE_SIZE = 100; + + public static final String SORT_BY_NAME = "NAME"; + public static final Set<String> SORTS = ImmutableSet.of(SORT_BY_NAME); + + private final Collection<String> keys; + private final Collection<String> names; + private final Collection<String> qualifiers; + private final String sort; + private final Boolean asc; + + // max results per page + private final int pageSize; + + // index of selected page. Start with 1. + private final int pageIndex; + + private ComponentQuery(Builder builder) { + this.keys = defaultCollection(builder.keys); + this.names = defaultCollection(builder.names); + this.qualifiers = defaultCollection(builder.qualifiers); + + this.sort = builder.sort; + this.asc = builder.asc; + this.pageSize = builder.pageSize; + this.pageIndex = builder.pageIndex; + } + + /** + * Pattern of component keys to search. Can contain a sub part of keys. + * Example : 'org.codehaus' will return 'org.codehaus.sonar', 'org.codehaus.tike', etc. + */ + public Collection<String> keys() { + return keys; + } + + /** + * Pattern of component name to search. Can contain a sub part of names. + * Example : 'Sona' will return 'Sonar', 'SonarJ', etc. + */ + public Collection<String> names() { + return names; + } + + /** + * Qualifiers of components to search. + */ + public Collection<String> qualifiers() { + return qualifiers; + } + + @CheckForNull + public String sort() { + return sort; + } + + @CheckForNull + public Boolean asc() { + return asc; + } + + public int pageSize() { + return pageSize; + } + + public int pageIndex() { + return pageIndex; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Collection<String> keys; + private Collection<String> names; + private Collection<String> qualifiers; + private String sort = SORT_BY_NAME; + private Boolean asc = true; + private Integer pageSize; + private Integer pageIndex; + + private Builder() { + } + + public Builder keys(@Nullable Collection<String> l) { + this.keys = l; + return this; + } + + public Builder names(@Nullable Collection<String> l) { + this.names = l; + return this; + } + + public Builder qualifiers(@Nullable Collection<String> l) { + this.qualifiers = l; + return this; + } + + public Builder sort(@Nullable String s) { + if (s != null && !SORTS.contains(s)) { + throw new IllegalArgumentException("Bad sort field: " + s); + } + this.sort = s; + return this; + } + + public Builder asc(@Nullable Boolean asc) { + this.asc = asc; + return this; + } + + public Builder pageSize(@Nullable Integer i) { + this.pageSize = i; + return this; + } + + public Builder pageIndex(@Nullable Integer i) { + this.pageIndex = i; + return this; + } + + public ComponentQuery build() { + initPageIndex(); + initPageSize(); + return new ComponentQuery(this); + } + + private void initPageSize() { + if (pageSize == null) { + pageSize = DEFAULT_PAGE_SIZE; + } + } + + private void initPageIndex() { + if (pageIndex == null) { + pageIndex = DEFAULT_PAGE_INDEX; + } + Preconditions.checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")"); + } + } + + private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) { + return c == null ? Collections.<T>emptyList() : Collections.unmodifiableCollection(c); + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/component/ComponentsFinderSort.java b/sonar-server/src/main/java/org/sonar/server/component/ComponentsFinderSort.java new file mode 100644 index 00000000000..fa6b16935dd --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/component/ComponentsFinderSort.java @@ -0,0 +1,98 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.component; + +import com.google.common.base.Function; +import com.google.common.collect.Ordering; +import org.sonar.api.component.Component; + +import java.util.Collection; +import java.util.List; + +/** + * @since 3.7 + */ +class ComponentsFinderSort { + + private Collection<? extends Component> components; + private ComponentQuery query; + + public ComponentsFinderSort(Collection<? extends Component> components, ComponentQuery query) { + this.components = components; + this.query = query; + } + + public Collection<? extends Component> sort() { + String sort = query.sort(); + Boolean asc = query.asc(); + if (sort != null && asc != null) { + return getComponentProcessor(sort).sort(components, asc); + } + return components; + } + + private ComponentProcessor getComponentProcessor(String sort) { + if (ComponentQuery.SORT_BY_NAME.equals(sort)) { + return new NameSort(); + } + throw new IllegalArgumentException("Cannot sort on field : " + sort); + } + + abstract static class ComponentProcessor { + abstract Function sortFieldFunction(); + + abstract Ordering sortFieldOrdering(boolean ascending); + + final List<? extends Component> sort(Collection<? extends Component> components, boolean ascending) { + Ordering<Component> ordering = sortFieldOrdering(ascending).onResultOf(sortFieldFunction()); + return ordering.immutableSortedCopy(components); + } + } + + abstract static class TextSort extends ComponentProcessor { + @Override + Function sortFieldFunction() { + return new Function<Component, String>() { + public String apply(Component component) { + return sortField(component); + } + }; + } + + abstract String sortField(Component component); + + @Override + Ordering sortFieldOrdering(boolean ascending) { + Ordering<String> ordering = Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast(); + if (!ascending) { + ordering = ordering.reverse(); + } + return ordering; + } + } + + static class NameSort extends TextSort { + @Override + String sortField(Component component) { + return component.name(); + } + } + +} diff --git a/sonar-server/src/main/java/org/sonar/server/component/DefaultComponentFinder.java b/sonar-server/src/main/java/org/sonar/server/component/DefaultComponentFinder.java new file mode 100644 index 00000000000..ff5164e31cb --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/component/DefaultComponentFinder.java @@ -0,0 +1,128 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.component; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.component.Component; +import org.sonar.api.utils.Paging; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.resource.ResourceDao; +import org.sonar.core.resource.ResourceQuery; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.Lists.newArrayList; + +/** + * @since 3.7 + */ +public class DefaultComponentFinder { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultComponentFinder.class); + private final ResourceDao resourceDao; + + public DefaultComponentFinder(ResourceDao resourceDao) { + this.resourceDao = resourceDao; + } + + public DefaultComponentQueryResult find(ComponentQuery query) { + LOG.debug("ComponentQuery : {}", query); + long start = System.currentTimeMillis(); + try { + // 1. Search components + ResourceQuery resourceQuery = ResourceQuery.create().setQualifiers(query.qualifiers().toArray(new String[]{})); + List<ComponentDto> dtos = ResourceDao.toComponents(resourceDao.getResources(resourceQuery)); + Collection<Component> foundComponents = search(query, dtos); + + // 2. Sort components + Collection<? extends Component> sortedComponents = new ComponentsFinderSort(foundComponents, query).sort(); + + // 3. Apply pagination + Paging paging = Paging.create(query.pageSize(), query.pageIndex(), foundComponents.size()); + Collection<? extends Component> pagedComponents = pagedComponents(sortedComponents, paging); + + return new DefaultComponentQueryResult(pagedComponents).setPaging(paging).setQuery(query); + } finally { + LOG.debug("ComponentQuery execution time : {} ms", System.currentTimeMillis() - start); + } + } + + private Collection<Component> search(final ComponentQuery query, List<? extends Component> allComponents) { + return newArrayList(Iterables.filter(allComponents, new Predicate<Component>() { + @Override + public boolean apply(Component component) { + return new KeyFilter().accept(component, query.keys()) && + new NameFilter().accept(component, query.names()); + } + })); + } + + abstract static class Filter { + + abstract String field(Component component); + + final boolean accept(Component component, Collection<String> collections) { + if (!collections.isEmpty()) { + for (String item : collections) { + if (field(component).toLowerCase().contains(item.toLowerCase())) { + return true; + } + } + return false; + } + return true; + } + } + + static class NameFilter extends Filter { + @Override + String field(Component component) { + return component.name(); + } + } + + static class KeyFilter extends Filter { + @Override + String field(Component component) { + return component.key(); + } + } + + private Collection<? extends Component> pagedComponents(Collection<? extends Component> components, Paging paging) { + Set<Component> pagedComponents = Sets.newLinkedHashSet(); + int index = 0; + for (Component component : components) { + if (index >= paging.offset() && pagedComponents.size() < paging.pageSize()) { + pagedComponents.add(component); + } else if (pagedComponents.size() >= paging.pageSize()) { + break; + } + index++; + } + return pagedComponents; + } + +} diff --git a/sonar-server/src/main/java/org/sonar/server/component/DefaultComponentQueryResult.java b/sonar-server/src/main/java/org/sonar/server/component/DefaultComponentQueryResult.java new file mode 100644 index 00000000000..7cd99f4ffe9 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/component/DefaultComponentQueryResult.java @@ -0,0 +1,60 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component; + +import org.sonar.api.component.Component; +import org.sonar.api.utils.Paging; + +import java.util.Collection; + +public class DefaultComponentQueryResult { + + private Collection<? extends Component> components; + private ComponentQuery query; + private Paging paging; + + public DefaultComponentQueryResult(Collection<? extends Component> components) { + this.components = components; + } + + public DefaultComponentQueryResult setPaging(Paging paging) { + this.paging = paging; + return this; + } + + public ComponentQuery query() { + return query; + } + + public DefaultComponentQueryResult setQuery(ComponentQuery query) { + this.query = query; + return this; + } + + public Collection<? extends Component> components() { + return components; + } + + public Paging paging() { + return paging; + } + +} 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 da913946555..2dce322b90a 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 @@ -19,16 +19,22 @@ */ package org.sonar.server.component; +import com.google.common.base.Strings; import org.sonar.api.component.Component; import org.sonar.api.component.RubyComponentService; import org.sonar.core.resource.ResourceDao; +import org.sonar.server.util.RubyUtils; + +import java.util.Map; public class DefaultRubyComponentService implements RubyComponentService { private final ResourceDao resourceDao; + private final DefaultComponentFinder finder; - public DefaultRubyComponentService(ResourceDao resourceDao) { + public DefaultRubyComponentService(ResourceDao resourceDao, DefaultComponentFinder finder) { this.resourceDao = resourceDao; + this.finder = finder; } @Override @@ -36,4 +42,23 @@ public class DefaultRubyComponentService implements RubyComponentService { return resourceDao.findByKey(key); } + public DefaultComponentQueryResult find(Map<String, Object> params) { + return finder.find(toQuery(params)); + } + + static ComponentQuery toQuery(Map<String, Object> props) { + ComponentQuery.Builder builder = ComponentQuery.builder() + .keys(RubyUtils.toStrings(props.get("keys"))) + .names(RubyUtils.toStrings(props.get("names"))) + .qualifiers(RubyUtils.toStrings(props.get("qualifiers"))) + .pageSize(RubyUtils.toInteger(props.get("pageSize"))) + .pageIndex(RubyUtils.toInteger(props.get("pageIndex"))); + String sort = (String) props.get("sort"); + if (!Strings.isNullOrEmpty(sort)) { + builder.sort(sort); + builder.asc(RubyUtils.toBoolean(props.get("asc"))); + } + return builder.build(); + } + } diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 00218059afb..554de95b521 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -70,6 +70,7 @@ import org.sonar.jpa.session.DatabaseSessionProvider; import org.sonar.jpa.session.DefaultDatabaseConnector; import org.sonar.jpa.session.ThreadLocalDatabaseSessionFactory; import org.sonar.server.charts.ChartFactory; +import org.sonar.server.component.DefaultComponentFinder; import org.sonar.server.component.DefaultRubyComponentService; import org.sonar.server.configuration.Backup; import org.sonar.server.configuration.ProfilesManager; @@ -263,6 +264,7 @@ public final class Platform { servicesContainer.addSingleton(InternalPermissionTemplateService.class); // components + servicesContainer.addSingleton(DefaultComponentFinder.class); servicesContainer.addSingleton(DefaultRubyComponentService.class); // issues diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb index 89398383705..e6c9b3a947c 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb @@ -32,38 +32,19 @@ class RolesController < ApplicationController end def projects - # for backward-compatibility with versions of views plugin that do not depend on sonar 3.0 - if java_facade.hasPlugin('views') - @qualifiers = (['VW'] + java_facade.getQualifiersWithProperty('hasRolePolicy').to_a).compact.uniq - else - @qualifiers = java_facade.getQualifiersWithProperty('hasRolePolicy') - end - @qualifier = params[:qualifier] || 'TRK' - - - conditions_sql = 'projects.enabled=:enabled and projects.qualifier=:qualifier and projects.copy_resource_id is null' - conditions_values = {:enabled => true, :qualifier => @qualifier} - joins = "INNER JOIN resource_index on resource_index.resource_id=projects.id " - joins += "and resource_index.qualifier=#{ActiveRecord::Base::sanitize(@qualifier)} " - if params[:q].present? - query = params[:q].downcase + '%' - joins +="and resource_index.kee like #{ActiveRecord::Base::sanitize(query)}" - else - joins += "and resource_index.position=0" - end - - @pagination = Api::Pagination.new(params) - @projects=Project.all(:select => 'distinct(resource_index.resource_id),projects.id,projects.kee,projects.name,resource_index.kee as resource_index_key', - :include => ['user_roles','group_roles'], - :joins => joins, - :conditions => [conditions_sql, conditions_values], - :order => 'resource_index.kee', - :offset => @pagination.offset, - :limit => @pagination.limit) - @pagination.count=Project.count( - :select => 'distinct(projects.id)', - :joins => joins, - :conditions => [conditions_sql, conditions_values]) + params['pageSize'] = 25 + params['qualifiers'] ||= 'TRK' + @query_result = Internal.component_api.find(params) + + @available_qualifiers = java_facade.getQualifiersWithProperty('hasRolePolicy').collect { |qualifier| [message("qualifiers.#{qualifier}"), qualifier] }.sort + + # For the moment, we return projects from rails models, but it should be replaced to return java components (this will need methods on ComponentQueryResult to return roles from component) + @projects = Project.all( + :include => ['user_roles','group_roles'], + :conditions => ['kee in (?)', @query_result.components().to_a.collect{|component| component.key()}], + # Even if components are already sorted, we must sort them again as this SQL query will not keep order + :order => 'name' + ) end def edit_users diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index a5c7f070de6..c324217b90b 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -130,9 +130,7 @@ <a href="<%= ApplicationController.root_context -%>/groups/index"><%= message('user_groups.page') -%></a></li> <li class="<%= 'active' if request.request_uri.include?('/roles/global') -%>"> <a href="<%= ApplicationController.root_context -%>/roles/global"><%= message('global_permissions.page') -%></a></li> - <li class="<%= 'active' if request.request_uri.include?('/permission_templates') -%>"> - <a href="<%= ApplicationController.root_context -%>/permission_templates"><%= message('permission_templates.page') -%></a></li> - <li class="<%= 'active' if request.request_uri.include?('/roles/projects') -%>"> + <li class="<%= 'active' if request.request_uri.include?('/roles/projects') || request.request_uri.include?('/permission_templates') -%>"> <a href="<%= ApplicationController.root_context -%>/roles/projects"><%= message('roles.page') -%></a></li> <li class="spacer"></li> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb index 7d0360d83f3..2ab96d10867 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb @@ -1,4 +1,4 @@ -<h1><%= message('permission_templates') -%></h1> +<%= render :partial => 'roles/tabs', :locals => {:selected_tab=>'Permission templates'} %> <br/> <h2>Default templates</h2> <div class="box"> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/roles/_tabs.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/_tabs.html.erb new file mode 100644 index 00000000000..0de8a00289d --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/_tabs.html.erb @@ -0,0 +1,13 @@ +<% + selected_tab = nil unless defined?(:selected_tab) +%> +<ul class="tabs"> + <li> + <a href="<%= url_for :controller => 'roles', :action => 'projects' %>" <%= "class='selected'" if selected_tab=='Projects' -%> + id="tab-projects"><%= message('projects') -%></a> + </li> + <li> + <a href="<%= url_for :controller => 'permission_templates', :action => 'index' -%>" <%= "class='selected'" if selected_tab=='Permission templates' -%> + id="tab-perm-templates"><%= message('permission_templates') -%></a> + </li> +</ul>
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb index 4311b9721e7..d2bea8d3bfe 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb @@ -1,17 +1,32 @@ -<h1><%= message('roles.page') -%></h1> +<%= render :partial => 'roles/tabs', :locals => {:selected_tab=>'Projects'} %> -<% if @qualifiers.size>1 %> - <ul class="tabs" id="qualifier-tabs"> - <% @qualifiers.each do |q| - css_class = (q==@qualifier ? 'selected' : '') - %> - <li> - <%= link_to message("qualifiers.#{q}"), {:action => 'projects', :qualifier => q}, {:class => css_class, :id => "tab-#{u q}"} -%> - </li> - <% end %> - </ul> - <div class="tabs-panel"> -<% end %> +<div class="tabs-panel marginbottom10 background-gray"> + <% form_tag({:action => 'projects'}, {:id => 'project-search-form', :method => 'get'}) do %> + <div class="table"> + <div class="project-search top"> + <span class="note"><%= message('projects_role.criteria.name') -%></span><br/> + <% selected_name = @query_result.query.names.to_a.first if @query_result.query.names && @query_result.query.names.size == 1 %> + <%= text_field_tag 'names', selected_name, :id => 'search-text' %> + </div> + <div class="project-search top"> + <span class="note"><%= message('projects_role.criteria.key') -%></span><br/> + <% selected_key = @query_result.query.keys.to_a.first if @query_result.query.keys && @query_result.query.keys.size == 1 %> + <%= text_field_tag 'keys', selected_key, :id => 'search-text' %> + </div> + <div class="project-search top"> + <span class="note"><%= message('type') -%></span><br/> + <% selected_qualifier = @query_result.query.qualifiers.to_a.first if @query_result.query.qualifiers && @query_result.query.qualifiers.size == 1 %> + <%= dropdown_tag 'qualifiers', options_for_select(@available_qualifiers, selected_qualifier), { + :width => '150px' + }, {:id => 'search-qualifier'} -%> + </div> + <div class="project-search"> + <br/> + <%= submit_tag message('search_verb'), :id => 'submit-search', :onclick => 'submitSearch();' %> + </div> + </div> + <% end %> +</div> <div id="project-roles-operations" style="float: right;"> <ul class="operations"> @@ -22,77 +37,75 @@ </ul> </div> - <table class="data width100" id="projects"> - <thead> - <tr> - <th> - <form action="<%= url_for :action => 'projects', :qualifier => @qualifier -%>" method="GET"> - <input type="hidden" name="qualifier" value="<%= @qualifier -%>"/> - <input type="text" name="q" value="<%= params[:q] -%>" id="search_text"/> - <input type="submit" value="<%= message('search_verb') -%>" id="search_submit"/> - </form> - </th> - <th><%= message('projects_role.admin') -%></th> - <th><%= message('projects_role.user') -%></th> - <th><%= message('projects_role.codeviewer') -%></th> - </tr> - </thead> +<table class="data width100" id="projects"> + <thead> + <tr> + <th> </th> + <th><%= message('projects_role.admin') -%></th> + <th><%= message('projects_role.user') -%></th> + <th><%= message('projects_role.codeviewer') -%></th> + </tr> + </thead> - <%= table_pagination(@pagination, :colspan => 4) { |label, page_id| link_to(label, params.merge({:page => page_id}))} -%> + <%= paginate_java(@query_result.paging, :colspan => 4, :id => 'project-roles-foot', :include_loading_icon => true) { |label, page_id| + link_to(label, params.merge({:pageIndex => page_id})) + } + %> - <tbody> - <% if @projects.empty? %> - <tr class="even"> - <td colspan="4" align="left"><%= message('no_results') %></td> - </tr> - <% end + <tbody> + <% if @projects.empty? %> + <tr class="even"> + <td colspan="4" align="left"><%= message('no_results') %></td> + </tr> + <% end - @projects.each do |project| - %> - <tr class="<%= cycle('even', 'odd') -%>"> - <td valign="top"><b><%= h project.name %></b><br/> - <span class="small gray"><%= h project.key -%></span> - </td> - <td valign="top"> - <% - users=Api::Utils.insensitive_sort(project.user_roles.select { |ur| ur.role=='admin' }.map { |ur| ur.user.name }) - groups=Api::Utils.insensitive_sort(project.group_roles.select { |gr| gr.role=='admin' }.map { |gr| group_name(gr.group) }) - %> - <span id="u-admin-<%= u project.kee -%>"><%= users.join(', ') %></span> - (<a href="<%= ApplicationController.root_context -%>/roles/edit_users?redirect=projects&role=admin&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectu-admin-<%= u project.kee -%>">select users</a>)<br/> - <span id="g-admin-<%= u project.kee -%>"><%= groups.join(', ') %></span> - (<a href="<%= ApplicationController.root_context -%>/roles/edit_groups?redirect=projects&role=admin&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectg-admin-<%= u project.kee -%>">select groups</a>) - </td> - <td valign="top"> - <% - users=Api::Utils.insensitive_sort(project.user_roles.select { |ur| ur.role=='user' }.map { |ur| ur.user.name }) - groups=Api::Utils.insensitive_sort(project.group_roles.select { |gr| gr.role=='user' }.map { |gr| group_name(gr.group) }) - %> - <span id="u-user-<%= u project.kee -%>"><%= users.join(', ') %></span> - (<a href="<%= ApplicationController.root_context -%>/roles/edit_users?redirect=projects&role=user&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectu-user-<%= u project.kee -%>">select users</a>)<br/> - <span id="g-user-<%= u project.kee -%>"><%= groups.join(', ') %></span> - (<a href="<%= ApplicationController.root_context -%>/roles/edit_groups?redirect=projects&role=user&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectg-user-<%= u project.kee -%>">select groups</a>) - </td> - <td valign="top"> - <% - users=Api::Utils.insensitive_sort(project.user_roles.select { |ur| ur.role=='codeviewer' }.map { |ur| ur.user.name }) - groups=Api::Utils.insensitive_sort(project.group_roles.select { |gr| gr.role=='codeviewer' }.map { |gr| group_name(gr.group) }) - %> - <span id="u-codeviewer-<%= u project.kee -%>"><%= users.join(', ') %></span> - (<a href="<%= ApplicationController.root_context -%>/roles/edit_users?redirect=projects&role=codeviewer&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectu-codeviewer-<%= u project.kee -%>">select users</a>)<br/> - <span id="g-codeviewer-<%= u project.kee -%>"><%= groups.join(', ') %></span> - (<a href="<%= ApplicationController.root_context -%>/roles/edit_groups?redirect=projects&role=codeviewer&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectg-codeviewer-<%= u project.kee -%>">select groups</a>) - </td> - </tr> - <% - end %> - </tbody> - </table> + @projects.each do |project| + %> + <tr class="<%= cycle('even', 'odd') -%>"> + <td valign="top"><b><%= h project.name %></b><br/> + <span class="small gray"><%= h project.key -%></span> + </td> + <td valign="top"> + <% + users=Api::Utils.insensitive_sort(project.user_roles.select { |ur| ur.role=='admin' }.map { |ur| ur.user.name }) + groups=Api::Utils.insensitive_sort(project.group_roles.select { |gr| gr.role=='admin' }.map { |gr| group_name(gr.group) }) + %> + <span id="u-admin-<%= u project.kee -%>"><%= users.join(', ') %></span> + (<a href="<%= ApplicationController.root_context -%>/roles/edit_users?redirect=projects&role=admin&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectu-admin-<%= u project.kee -%>">select users</a>)<br/> + <span id="g-admin-<%= u project.kee -%>"><%= groups.join(', ') %></span> + (<a href="<%= ApplicationController.root_context -%>/roles/edit_groups?redirect=projects&role=admin&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectg-admin-<%= u project.kee -%>">select groups</a>) + </td> + <td valign="top"> + <% + users=Api::Utils.insensitive_sort(project.user_roles.select { |ur| ur.role=='user' }.map { |ur| ur.user.name }) + groups=Api::Utils.insensitive_sort(project.group_roles.select { |gr| gr.role=='user' }.map { |gr| group_name(gr.group) }) + %> + <span id="u-user-<%= u project.kee -%>"><%= users.join(', ') %></span> + (<a href="<%= ApplicationController.root_context -%>/roles/edit_users?redirect=projects&role=user&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectu-user-<%= u project.kee -%>">select users</a>)<br/> + <span id="g-user-<%= u project.kee -%>"><%= groups.join(', ') %></span> + (<a href="<%= ApplicationController.root_context -%>/roles/edit_groups?redirect=projects&role=user&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectg-user-<%= u project.kee -%>">select groups</a>) + </td> + <td valign="top"> + <% + users=Api::Utils.insensitive_sort(project.user_roles.select { |ur| ur.role=='codeviewer' }.map { |ur| ur.user.name }) + groups=Api::Utils.insensitive_sort(project.group_roles.select { |gr| gr.role=='codeviewer' }.map { |gr| group_name(gr.group) }) + %> + <span id="u-codeviewer-<%= u project.kee -%>"><%= users.join(', ') %></span> + (<a href="<%= ApplicationController.root_context -%>/roles/edit_users?redirect=projects&role=codeviewer&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectu-codeviewer-<%= u project.kee -%>">select users</a>)<br/> + <span id="g-codeviewer-<%= u project.kee -%>"><%= groups.join(', ') %></span> + (<a href="<%= ApplicationController.root_context -%>/roles/edit_groups?redirect=projects&role=codeviewer&resource=<%= project.id -%>&q=<%= u params[:q] -%>&qualifier=<%= @qualifier -%>&page=<%= params[:page] -%>" class="link-action" id="selectg-codeviewer-<%= u project.kee -%>">select groups</a>) + </td> + </tr> + <% + end %> + </tbody> +</table> -<% if @qualifiers.size>1 %> - </div> -<% end %> <script> - $('search_text').focus(); + function submitSearch() { + $j("#project-search-form").submit(); + } + + $j('#search-text').focus(); </script>
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css index 9f2edf7596a..afe528792a2 100644 --- a/sonar-server/src/main/webapp/stylesheets/style.css +++ b/sonar-server/src/main/webapp/stylesheets/style.css @@ -2425,3 +2425,11 @@ textarea.width100 { font-size: 85%; color: #777; } + +/* ------------------- Role search styles ------------------- */ + +.project-search { + display: inline-block; + line-height: 16px; + padding: 4px 2px; +}
\ No newline at end of file diff --git a/sonar-server/src/test/java/org/sonar/server/component/ComponentQueryTest.java b/sonar-server/src/test/java/org/sonar/server/component/ComponentQueryTest.java new file mode 100644 index 00000000000..f3dfdd8164a --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/component/ComponentQueryTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component; + +import org.junit.Test; + +import static com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.Assertions.assertThat; + +public class ComponentQueryTest { + + @Test + public void should_build_query() throws Exception { + ComponentQuery query = ComponentQuery.builder() + .keys(newArrayList("org.codehaus")) + .names(newArrayList("Sona")) + .qualifiers(newArrayList("TRK")) + .pageSize(10) + .pageIndex(2) + .sort(ComponentQuery.SORT_BY_NAME) + .asc(true) + .build(); + assertThat(query.keys()).containsOnly("org.codehaus"); + assertThat(query.names()).containsOnly("Sona"); + assertThat(query.qualifiers()).containsOnly("TRK"); + assertThat(query.sort()).isEqualTo(ComponentQuery.SORT_BY_NAME); + assertThat(query.asc()).isTrue(); + assertThat(query.pageSize()).isEqualTo(10); + assertThat(query.pageIndex()).isEqualTo(2); + } + + @Test + public void should_accept_null_sort() throws Exception { + ComponentQuery query = ComponentQuery.builder().sort(null).build(); + assertThat(query.sort()).isNull(); + } + + @Test + public void should_sort_by_name_asc_by_default() throws Exception { + ComponentQuery query = ComponentQuery.builder().build(); + assertThat(query.sort()).isEqualTo(ComponentQuery.SORT_BY_NAME); + assertThat(query.asc()).isTrue(); + } + + @Test + public void should_throw_exception_if_sort_is_not_valid() throws Exception { + try { + ComponentQuery.builder() + .sort("UNKNOWN") + .build(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Bad sort field: UNKNOWN"); + } + } + + @Test + public void test_default_page_index_and_size() throws Exception { + ComponentQuery query = ComponentQuery.builder().build(); + assertThat(query.pageSize()).isEqualTo(ComponentQuery.DEFAULT_PAGE_SIZE); + assertThat(query.pageIndex()).isEqualTo(ComponentQuery.DEFAULT_PAGE_INDEX); + } + +} diff --git a/sonar-server/src/test/java/org/sonar/server/component/ComponentsFinderSortTest.java b/sonar-server/src/test/java/org/sonar/server/component/ComponentsFinderSortTest.java new file mode 100644 index 00000000000..688009ccbd2 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/component/ComponentsFinderSortTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component; + +import org.junit.Test; +import org.sonar.api.component.Component; +import org.sonar.core.component.ComponentDto; + +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.Assertions.assertThat; + +public class ComponentsFinderSortTest { + + @Test + public void should_sort_by_name() { + List<? extends Component> dtoList = newArrayList( + new ComponentDto().setKey("org.codehaus.sonar").setName("Sonar"), + new ComponentDto().setKey("org.apache.tika:tika").setName("Apache Tika"), + new ComponentDto().setKey("org.picocontainer:picocontainer-parent").setName("PicoContainer Parent"), + new ComponentDto().setKey("org.codehaus.sample") + ); + + ComponentQuery query = ComponentQuery.builder().sort(ComponentQuery.SORT_BY_NAME).asc(true).build(); + ComponentsFinderSort issuesFinderSort = new ComponentsFinderSort(dtoList, query); + + List<Component> result = newArrayList(issuesFinderSort.sort()); + + assertThat(result).hasSize(4); + assertThat(result.get(0).name()).isEqualTo("Apache Tika"); + assertThat(result.get(1).name()).isEqualTo("PicoContainer Parent"); + assertThat(result.get(2).name()).isEqualTo("Sonar"); + assertThat(result.get(3).name()).isNull(); + } + +} diff --git a/sonar-server/src/test/java/org/sonar/server/component/DefaultComponentFinderTest.java b/sonar-server/src/test/java/org/sonar/server/component/DefaultComponentFinderTest.java new file mode 100644 index 00000000000..bba0fb77fa9 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/component/DefaultComponentFinderTest.java @@ -0,0 +1,124 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.component; + +import org.junit.Test; +import org.sonar.api.component.Component; +import org.sonar.core.resource.ResourceDao; +import org.sonar.core.resource.ResourceDto; +import org.sonar.core.resource.ResourceQuery; + +import java.util.Iterator; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultComponentFinderTest { + + ResourceDao dao = mock(ResourceDao.class); + DefaultComponentFinder finder = new DefaultComponentFinder(dao); + + @Test + public void should_return_all_components_when_no_parameter() { + List<ResourceDto> dtoList = newArrayList( + new ResourceDto().setKey("org.codehaus.sonar").setName("Sonar").setQualifier("TRK"), + new ResourceDto().setKey("org.apache.tika:tika").setName("Apache Tika").setQualifier("TRK"), + new ResourceDto().setKey("org.picocontainer:picocontainer-parent").setName("PicoContainer Parent").setQualifier("TRK") + ); + when(dao.getResources(any(ResourceQuery.class))).thenReturn(dtoList); + + ComponentQuery query = ComponentQuery.builder().build(); + DefaultComponentQueryResult results = finder.find(query); + + assertThat(results.components()).hasSize(3); + Component component = results.components().iterator().next(); + assertThat(component.name()).isEqualTo("Apache Tika"); + assertThat(component.key()).isEqualTo("org.apache.tika:tika"); + assertThat(component.qualifier()).isEqualTo("TRK"); + } + + @Test + public void should_find_components_by_key_pattern() { + List<ResourceDto> dtoList = newArrayList( + new ResourceDto().setKey("org.codehaus.sonar").setName("Sonar").setQualifier("TRK"), + new ResourceDto().setKey("org.apache.tika:tika").setName("Apache Tika").setQualifier("TRK"), + new ResourceDto().setKey("org.apache.jackrabbit:jackrabbit").setName("Apache Jackrabbit").setQualifier("TRK") + ); + when(dao.getResources(any(ResourceQuery.class))).thenReturn(dtoList); + + ComponentQuery query = ComponentQuery.builder().keys(newArrayList("org.apache")).build(); + assertThat(finder.find(query).components()).hasSize(2); + } + + @Test + public void should_find_components_by_name_pattern() { + List<ResourceDto> dtoList = newArrayList( + new ResourceDto().setKey("org.codehaus.sonar").setName("Sonar").setQualifier("TRK"), + new ResourceDto().setKey("org.apache.tika:tika").setName("Apache Tika").setQualifier("TRK"), + new ResourceDto().setKey("org.apache.jackrabbit:jackrabbit").setName("Apache Jackrabbit").setQualifier("TRK") + ); + when(dao.getResources(any(ResourceQuery.class))).thenReturn(dtoList); + + ComponentQuery query = ComponentQuery.builder().names(newArrayList("Apache")).build(); + assertThat(finder.find(query).components()).hasSize(2); + } + + @Test + public void should_sort_result_by_name() { + List<ResourceDto> dtoList = newArrayList( + new ResourceDto().setKey("org.codehaus.sonar").setName("Sonar").setQualifier("TRK"), + new ResourceDto().setKey("org.apache.tika:tika").setName("Apache Tika").setQualifier("TRK"), + new ResourceDto().setKey("org.picocontainer:picocontainer-parent").setName("PicoContainer Parent").setQualifier("TRK") + ); + when(dao.getResources(any(ResourceQuery.class))).thenReturn(dtoList); + + ComponentQuery query = ComponentQuery.builder().build(); + DefaultComponentQueryResult results = finder.find(query); + + assertThat(results.components()).hasSize(3); + Iterator<? extends Component> iterator = results.components().iterator(); + assertThat(iterator.next().name()).isEqualTo("Apache Tika"); + assertThat(iterator.next().name()).isEqualTo("PicoContainer Parent"); + assertThat(iterator.next().name()).isEqualTo("Sonar"); + } + + @Test + public void should_find_paginate_result() { + ComponentQuery query = ComponentQuery.builder().pageSize(1).pageIndex(1).build(); + + List<ResourceDto> dtoList = newArrayList( + new ResourceDto().setKey("org.codehaus.sonar").setName("Sonar").setQualifier("TRK"), + new ResourceDto().setKey("org.apache.tika:tika").setName("Apache Tika").setQualifier("TRK"), + new ResourceDto().setKey("org.picocontainer:picocontainer-parent").setName("PicoContainer Parent").setQualifier("TRK") + ); + when(dao.getResources(any(ResourceQuery.class))).thenReturn(dtoList); + + DefaultComponentQueryResult results = finder.find(query); + assertThat(results.paging().offset()).isEqualTo(0); + assertThat(results.paging().pages()).isEqualTo(3); + assertThat(results.paging().total()).isEqualTo(3); + } + +} diff --git a/sonar-server/src/test/java/org/sonar/server/component/DefaultRubyComponentServiceTest.java b/sonar-server/src/test/java/org/sonar/server/component/DefaultRubyComponentServiceTest.java index d9911b33775..2a5cdb3051b 100644 --- a/sonar-server/src/test/java/org/sonar/server/component/DefaultRubyComponentServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/component/DefaultRubyComponentServiceTest.java @@ -21,29 +21,72 @@ package org.sonar.server.component; import org.junit.Before; +import org.junit.Test; import org.sonar.api.component.Component; import org.sonar.core.resource.ResourceDao; +import java.util.Map; + +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; public class DefaultRubyComponentServiceTest { private ResourceDao resourceDao; + private DefaultComponentFinder finder; private DefaultRubyComponentService componentService; @Before - public void before(){ + public void before() { resourceDao = mock(ResourceDao.class); - componentService = new DefaultRubyComponentService(resourceDao); + finder = mock(DefaultComponentFinder.class); + componentService = new DefaultRubyComponentService(resourceDao, finder); } - @Before + @Test public void should_find_by_key() { Component component = mock(Component.class); when(resourceDao.findByKey("struts")).thenReturn(component); assertThat(componentService.findByKey("struts")).isEqualTo(component); } + + @Test + public void should_find() { + Map<String, Object> map = newHashMap(); + map.put("keys", newArrayList("org.codehaus.sonar")); + map.put("names", newArrayList("Sonar")); + map.put("qualifiers", newArrayList("TRK")); + map.put("pageSize", 10l); + map.put("pageIndex", 50); + map.put("sort", "NAME"); + map.put("asc", true); + + componentService.find(map); + verify(finder).find(any(ComponentQuery.class)); + } + + @Test + public void should_create_query_from_parameters() { + Map<String, Object> map = newHashMap(); + map.put("keys", newArrayList("org.codehaus.sonar")); + map.put("names", newArrayList("Sonar")); + map.put("qualifiers", newArrayList("TRK")); + map.put("pageSize", 10l); + map.put("pageIndex", 50); + map.put("sort", "NAME"); + map.put("asc", true); + + ComponentQuery query = DefaultRubyComponentService.toQuery(map); + assertThat(query.keys()).containsOnly("org.codehaus.sonar"); + assertThat(query.names()).containsOnly("Sonar"); + assertThat(query.qualifiers()).containsOnly("TRK"); + assertThat(query.pageSize()).isEqualTo(10); + assertThat(query.pageIndex()).isEqualTo(50); + assertThat(query.sort()).isEqualTo(ComponentQuery.SORT_BY_NAME); + assertThat(query.asc()).isTrue(); + } } |