From: Julien Lancelot Date: Thu, 20 Jun 2013 13:17:26 +0000 (+0200) Subject: SONAR-3557 Ability to configure/display some issue widgets based on Issues Filters X-Git-Tag: 3.7~421 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=9e36445a8255b827e4664f135b2e05359b79da42;p=sonarqube.git SONAR-3557 Ability to configure/display some issue widgets based on Issues Filters --- diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index ee121f9f706..97122719cca 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -402,6 +402,7 @@ public final class CorePlugin extends SonarPlugin { ActionPlansWidget.class, UnresolvedIssuesPerAssigneeWidget.class, UnresolvedIssuesStatusesWidget.class, + IssueFilterWidget.class, org.sonar.api.issue.NoSonarFilter.class, // issue notifications diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/issues/IssueFilterWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/issues/IssueFilterWidget.java new file mode 100644 index 00000000000..fdac731ab66 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/issues/IssueFilterWidget.java @@ -0,0 +1,44 @@ +/* + * 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.plugins.core.widgets.issues; + +import org.sonar.api.web.*; +import org.sonar.plugins.core.widgets.CoreWidget; + +import static org.sonar.api.web.WidgetScope.GLOBAL; + +@WidgetCategory({"Filters", "Global", "Issues"}) +@WidgetScope(GLOBAL) +@WidgetProperties({ + @WidgetProperty(key = IssueFilterWidget.FILTER_PROPERTY, type = WidgetPropertyType.ISSUE_FILTER, optional = false), + @WidgetProperty(key = IssueFilterWidget.PAGE_SIZE_PROPERTY, type = WidgetPropertyType.INTEGER, defaultValue = "30"), + @WidgetProperty(key = IssueFilterWidget.DISPLAY_FILTER_DESCRIPTION, type = WidgetPropertyType.BOOLEAN, defaultValue = "false") +}) +public class IssueFilterWidget extends CoreWidget { + + public static final String FILTER_PROPERTY = "filter"; + public static final String PAGE_SIZE_PROPERTY = "numberOfLines"; + public static final String DISPLAY_FILTER_DESCRIPTION = "displayFilterDescription"; + public static final String ID = "issue_filter"; + + public IssueFilterWidget() { + super(ID, "Issue Filter", "/org/sonar/plugins/core/widgets/issues/issue_filter.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 377c43aba48..d802c2e2259 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 @@ -529,6 +529,7 @@ issue_filter.criteria.resolution=Resolution issue_filter.criteria.severity=Severity issue_filter.criteria.status=Status issue_filter.max_results_reached=Only the first {0} issues matching the search criteria have been retrieved. Add some additional criteria to get fewer results to be able to sort this list. +issue_filter.widget.max_results_reached=Only the first {0} issues matching the search criteria have been retrieved. issue_filter.no_result=No matching issues found. issue_filter.save_filter=Save Filter issue_filter.edit_filter=Edit Filter @@ -1026,6 +1027,13 @@ widget.action_plans.title=Open action plans widget.action_plans.no_action_plan=No action plan widget.action_plans.x_unresolved_issues={0} unresolved issues +widget.issue_filter.name=Issue Filter +widget.issue_filter.description=Displays the result of a pre-configured issue filter. +widget.issue_filter.property.filter.name=Filter +widget.issue_filter.property.numberOfLines.name=Page size +widget.issue_filter.property.displayFilterDescription.name=Display filter description +widget.issue_filter.unknown_filter_warning=This widget is configured to display an issue filter that doesn't exist anymore. + widget.treemap-widget.name=Treemap of Components widget.treemap-widget.description=Displays a treemap of all direct components of the selected resource. widget.treemap-widget.property.sizeMetric.name=Size metric diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issue_filter.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issue_filter.html.erb new file mode 100644 index 00000000000..9f0dfa07851 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issue_filter.html.erb @@ -0,0 +1,25 @@ +<% + filter_id = widget_properties['filter'] + filter = Internal.issues.findIssueFilterById(filter_id.to_i) + if filter + if Internal.issues.isUserAuthorized(filter) + search_options = {} + search_options['filter'] = filter_id + @widget_title = link_to h(filter.name), {:controller => 'issues', :action => 'filter', :id => filter.id} +%> + <% if widget_properties['displayFilterDescription'] && !filter.description.blank? %> +
+ <%= h filter.description -%> +
+ <% end %> + + <%= render :partial => 'project/widgets/issues/issues_list_widget', + :locals => {:search_options => search_options, :widget_id => widget.id.to_s, :widget_properties => widget_properties} %> + <% + end + else + %> +

<%= image_tag 'warning.png' %> <%= message 'widget.issue_filter.unknown_filter_warning' -%>

+<% + end +%> diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/widgets/CoreWidgetsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/widgets/CoreWidgetsTest.java index c46edc25389..583a4d24ebc 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/widgets/CoreWidgetsTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/widgets/CoreWidgetsTest.java @@ -22,6 +22,7 @@ package org.sonar.plugins.core.widgets; import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import org.junit.Test; import org.reflections.Reflections; @@ -32,6 +33,7 @@ import javax.annotation.Nullable; import java.util.Collection; import java.util.Set; +import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; public class CoreWidgetsTest { @@ -96,7 +98,7 @@ public class CoreWidgetsTest { } private Collection widgets() { - return Collections2.transform(widgetClasses(), new Function, CoreWidget>() { + return newArrayList(Iterables.transform(widgetClasses(), new Function, CoreWidget>() { public CoreWidget apply(@Nullable Class aClass) { try { return aClass.newInstance(); @@ -104,6 +106,6 @@ public class CoreWidgetsTest { throw Throwables.propagate(e); } } - }); + })); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java index e4f170cb02d..4e68aeac868 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java @@ -48,12 +48,19 @@ public enum WidgetPropertyType { METRIC, /** - * Filter id + * Measure Filter id * * @since 3.1 */ FILTER, + /** + * Issue Filter id + * + * @since 3.7 + */ + ISSUE_FILTER, + /** * Multiple line text-area * diff --git a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index 42754d424ce..24b091e3ab2 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -22,9 +22,13 @@ package org.sonar.server.issue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import com.google.common.base.Strings; +import com.google.common.collect.Maps; import org.apache.commons.lang.StringUtils; import org.sonar.api.ServerComponent; -import org.sonar.api.issue.*; +import org.sonar.api.issue.ActionPlan; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.IssueComment; +import org.sonar.api.issue.IssueQuery; import org.sonar.api.issue.action.Action; import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.rule.RuleKey; @@ -41,6 +45,7 @@ import org.sonar.core.resource.ResourceQuery; import org.sonar.server.user.UserSession; import org.sonar.server.util.RubyUtils; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.util.Collection; @@ -364,38 +369,70 @@ public class InternalRubyIssueService implements ServerComponent { return actionService.listAvailableActions(issue); } - public IssueQuery toQuery(Map props) { - return PublicRubyIssueService.toQuery(props); + public IssueQuery emptyIssueQuery() { + return PublicRubyIssueService.toQuery(Maps.newHashMap()); } - public IssueQuery toQuery(DefaultIssueFilter issueFilter) { - return toQuery(issueFilterService.deserializeIssueFilterQuery(issueFilter)); + @CheckForNull + public DefaultIssueFilter findIssueFilterById(Long id) { + return issueFilterService.findById(id); } + /** + * Return the issue filter if the user has the right to see it + * Never return null + */ public DefaultIssueFilter findIssueFilter(Long id) { - return issueFilterService.findById(id, UserSession.get()); + return issueFilterService.find(id, UserSession.get()); } public List findUserIssueFilters() { return issueFilterService.findByUser(UserSession.get()); } + public boolean isUserAuthorized(DefaultIssueFilter issueFilter) { + try { + UserSession userSession = UserSession.get(); + issueFilterService.verifyLoggedIn(userSession); + issueFilterService.verifyCurrentUserCanReadFilter(issueFilter, userSession); + return true; + } catch (Exception e) { + return false; + } + } + public String serializeFilterQuery(Map filterQuery){ return issueFilterService.serializeFilterQuery(filterQuery); } /** - * Execute issue filter from issue query + * Execute issue filter from parameters */ - public IssueQueryResult execute(IssueQuery issueQuery) { + public IssueFilterResult execute(Map props) { + IssueQuery issueQuery = PublicRubyIssueService.toQuery(props); return issueFilterService.execute(issueQuery); } /** - * Execute issue filter from existing filter + * Execute issue filter from existing filter with optional overridable parameters */ - public IssueQueryResult execute(Long issueFilterId) { - return issueFilterService.execute(issueFilterId, UserSession.get()); + public IssueFilterResult execute(Long issueFilterId, Map overrideProps) { + DefaultIssueFilter issueFilter = issueFilterService.find(issueFilterId, UserSession.get()); + Map props = issueFilterService.deserializeIssueFilterQuery(issueFilter); + overrideProps(props, overrideProps); + IssueQuery issueQuery = PublicRubyIssueService.toQuery(props); + return issueFilterService.execute(issueQuery); + } + + private void overrideProps(Map props, Map overrideProps){ + overrideProp(props, overrideProps, "pageSize"); + overrideProp(props, overrideProps, "pageIndex"); + } + + private void overrideProp(Map props, Map overrideProps, String key){ + if (overrideProps.containsKey(key)) { + props.put(key, overrideProps.get(key)); + } } /** diff --git a/sonar-server/src/main/java/org/sonar/server/issue/IssueFilterResult.java b/sonar-server/src/main/java/org/sonar/server/issue/IssueFilterResult.java new file mode 100644 index 00000000000..6f317e86c69 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/issue/IssueFilterResult.java @@ -0,0 +1,43 @@ +/* + * 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.issue; + +import org.sonar.api.issue.IssueQuery; +import org.sonar.api.issue.IssueQueryResult; + +public class IssueFilterResult { + + private IssueQueryResult issueQueryResult; + private IssueQuery issueQuery; + + public IssueFilterResult(IssueQueryResult issueQueryResult, IssueQuery issueQuery) { + this.issueQueryResult = issueQueryResult; + this.issueQuery = issueQuery; + } + + public IssueQueryResult result() { + return issueQueryResult; + } + + public IssueQuery query() { + return issueQuery; + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/issue/IssueFilterService.java b/sonar-server/src/main/java/org/sonar/server/issue/IssueFilterService.java index 63b7ae1eb03..94458ab3589 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/IssueFilterService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/IssueFilterService.java @@ -61,21 +61,17 @@ public class IssueFilterService implements ServerComponent { this.issueFilterSerializer = issueFilterSerializer; } - public IssueQueryResult execute(IssueQuery issueQuery) { - return issueFinder.find(issueQuery); + public IssueFilterResult execute(IssueQuery issueQuery) { + return createIssueFilterResult(issueFinder.find(issueQuery), issueQuery); } - public IssueQueryResult execute(Long issueFilterId, UserSession userSession) { - IssueFilterDto issueFilterDto = findIssueFilter(issueFilterId, userSession); - - DefaultIssueFilter issueFilter = issueFilterDto.toIssueFilter(); - IssueQuery issueQuery = PublicRubyIssueService.toQuery(deserializeIssueFilterQuery(issueFilter)); - return issueFinder.find(issueQuery); + public DefaultIssueFilter find(Long id, UserSession userSession) { + return findIssueFilterDto(id, userSession).toIssueFilter(); } @CheckForNull - public DefaultIssueFilter findById(Long id, UserSession userSession) { - IssueFilterDto issueFilterDto = findIssueFilter(id, userSession); + public DefaultIssueFilter findById(Long id) { + IssueFilterDto issueFilterDto = issueFilterDao.selectById(id); return issueFilterDto.toIssueFilter(); } @@ -94,8 +90,8 @@ public class IssueFilterService implements ServerComponent { } public DefaultIssueFilter update(DefaultIssueFilter issueFilter, UserSession userSession) { - IssueFilterDto issueFilterDto = findIssueFilter(issueFilter.id(), userSession); - verifyCurrentUserCanModifyFilter(issueFilterDto, userSession); + IssueFilterDto issueFilterDto = findIssueFilterDto(issueFilter.id(), userSession); + verifyCurrentUserCanModifyFilter(issueFilterDto.toIssueFilter(), userSession); validateFilter(issueFilter, userSession); issueFilterDao.update(IssueFilterDto.toIssueFilter(issueFilter)); @@ -103,8 +99,8 @@ public class IssueFilterService implements ServerComponent { } public DefaultIssueFilter updateFilterQuery(Long issueFilterId, Map filterQuery, UserSession userSession) { - IssueFilterDto issueFilterDto = findIssueFilter(issueFilterId, userSession); - verifyCurrentUserCanModifyFilter(issueFilterDto, userSession); + IssueFilterDto issueFilterDto = findIssueFilterDto(issueFilterId, userSession); + verifyCurrentUserCanModifyFilter(issueFilterDto.toIssueFilter(), userSession); DefaultIssueFilter issueFilter = issueFilterDto.toIssueFilter(); issueFilter.setData(serializeFilterQuery(filterQuery)); @@ -113,15 +109,15 @@ public class IssueFilterService implements ServerComponent { } public void delete(Long issueFilterId, UserSession userSession) { - IssueFilterDto issueFilterDto = findIssueFilter(issueFilterId, userSession); - verifyCurrentUserCanModifyFilter(issueFilterDto, userSession); + IssueFilterDto issueFilterDto = findIssueFilterDto(issueFilterId, userSession); + verifyCurrentUserCanModifyFilter(issueFilterDto.toIssueFilter(), userSession); deleteFavouriteIssueFilters(issueFilterDto); issueFilterDao.delete(issueFilterId); } public DefaultIssueFilter copy(Long issueFilterIdToCopy, DefaultIssueFilter issueFilter, UserSession userSession) { - IssueFilterDto issueFilterDtoToCopy = findIssueFilter(issueFilterIdToCopy, userSession); + IssueFilterDto issueFilterDtoToCopy = findIssueFilterDto(issueFilterIdToCopy, userSession); issueFilter.setUser(userSession.login()); issueFilter.setData(issueFilterDtoToCopy.getData()); validateFilter(issueFilter, userSession); @@ -144,7 +140,7 @@ public class IssueFilterService implements ServerComponent { } public void toggleFavouriteIssueFilter(Long issueFilterId, UserSession userSession) { - findIssueFilter(issueFilterId, userSession); + findIssueFilterDto(issueFilterId, userSession); IssueFilterFavouriteDto issueFilterFavouriteDto = findFavouriteIssueFilter(userSession.login(), issueFilterId); if (issueFilterFavouriteDto == null) { addFavouriteIssueFilter(issueFilterId, userSession.login()); @@ -153,40 +149,40 @@ public class IssueFilterService implements ServerComponent { } } - public IssueFilterDto findIssueFilter(Long id, UserSession userSession) { + public String serializeFilterQuery(Map filterQuery) { + return issueFilterSerializer.serialize(filterQuery); + } + + public Map deserializeIssueFilterQuery(DefaultIssueFilter issueFilter) { + return issueFilterSerializer.deserialize(issueFilter.data()); + } + + private IssueFilterDto findIssueFilterDto(Long id, UserSession userSession) { verifyLoggedIn(userSession); IssueFilterDto issueFilterDto = issueFilterDao.selectById(id); if (issueFilterDto == null) { // TODO throw 404 throw new IllegalArgumentException("Filter not found: " + id); } - verifyCurrentUserCanReadFilter(issueFilterDto, userSession); + verifyCurrentUserCanReadFilter(issueFilterDto.toIssueFilter(), userSession); return issueFilterDto; } - public String serializeFilterQuery(Map filterQuery){ - return issueFilterSerializer.serialize(filterQuery); - } - - public Map deserializeIssueFilterQuery(DefaultIssueFilter issueFilter){ - return issueFilterSerializer.deserialize(issueFilter.data()); - } - - private void verifyLoggedIn(UserSession userSession) { + void verifyLoggedIn(UserSession userSession) { if (!userSession.isLoggedIn() && userSession.login() != null) { throw new IllegalStateException("User is not logged in"); } } - private void verifyCurrentUserCanReadFilter(IssueFilterDto issueFilterDto, UserSession userSession) { - if (!issueFilterDto.getUserLogin().equals(userSession.login()) && !issueFilterDto.isShared()) { + void verifyCurrentUserCanReadFilter(DefaultIssueFilter issueFilter, UserSession userSession) { + if (!issueFilter.user().equals(userSession.login()) && !issueFilter.shared()) { // TODO throw unauthorized throw new IllegalStateException("User is not authorized to read this filter"); } } - private void verifyCurrentUserCanModifyFilter(IssueFilterDto issueFilterDto, UserSession userSession) { - if (!issueFilterDto.getUserLogin().equals(userSession.login()) && !isAdmin(userSession.login())) { + private void verifyCurrentUserCanModifyFilter(DefaultIssueFilter issueFilter, UserSession userSession) { + if (!issueFilter.user().equals(userSession.login()) && !isAdmin(userSession.login())) { // TODO throw unauthorized throw new IllegalStateException("User is not authorized to modify this filter"); } @@ -240,4 +236,8 @@ public class IssueFilterService implements ServerComponent { return authorizationDao.selectGlobalPermissions(user).contains(UserRole.ADMIN); } + private IssueFilterResult createIssueFilterResult(IssueQueryResult issueQueryResult, IssueQuery issueQuery) { + return new IssueFilterResult(issueQueryResult, issueQuery); + } + } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb index 0ae9f832ddf..95bb0d1b177 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb @@ -33,9 +33,11 @@ class IssuesController < ApplicationController @filter = find_filter(params[:id].to_i) end - @first_search = criteria_params.empty? - @issue_query = Internal.issues.toQuery(criteria_params) - @issues_result = Internal.issues.execute(@issue_query) + @criteria_params = criteria_params + @first_search = @criteria_params.empty? + issue_filter_result = Internal.issues.execute(@criteria_params) + @issue_query = issue_filter_result.query + @issues_result = issue_filter_result.result end # Load existing filter @@ -43,18 +45,20 @@ class IssuesController < ApplicationController def filter require_parameters :id - @filter = find_filter(params[:id].to_i) @first_search = false - @issue_query = Internal.issues.toQuery(@filter) - @issues_result = Internal.issues.execute(@issue_query) @unchanged = true + @criteria_params = criteria_params + issue_filter_result = Internal.issues.execute(params[:id].to_i, @criteria_params) + @issue_query = issue_filter_result.query + @issues_result = issue_filter_result.result + render :action => 'search' end # GET /issues/manage def manage - @issue_query = Internal.issues.toQuery({}) + @issue_query = Internal.issues.emptyIssueQuery() @filters = Internal.issues.findIssueFiltersForCurrentUser() @shared_filters = Internal.issues.findSharedFiltersForCurrentUser() @favourite_filter_ids = @favourite_filters.map { |filter| filter.id } @@ -91,13 +95,12 @@ class IssuesController < ApplicationController @filter = filter_result.get() redirect_to :action => 'filter', :id => @filter.id.to_s else + @unchanged = true @errors = filter_result.errors - @filter = find_filter(params[:id].to_i) - - @issue_query = Internal.issues.toQuery(@filter.dataAsMap) - @issues_result = Internal.issues.execute(@issue_query) - @unchanged = true + issue_filter_result = Internal.issues.execute(@filter.id, criteria_params) + @issue_query = issue_filter_result.query + @issues_result = issue_filter_result.result render :action => 'search' end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb index e090492a9b3..303eb072fb7 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb @@ -82,6 +82,18 @@ module PropertiesHelper "#{filters_combo} #{filter_link}" + when PropertyType::TYPE_ISSUE_FILTER + user_filters = options_id(value, Internal.issues.findIssueFiltersForCurrentUser()) + shared_filters = options_id(value, Internal.issues.findSharedFiltersForCurrentUser()) + + #user_filters = options_id(value, current_user.measure_filters) + #shared_filters = options_id(value, MeasureFilter.find(:all, :conditions => ['(user_id<>? or user_id is null) and shared=?', current_user.id, true]).sort_by(&:name)) + + filters_combo = select_tag name, option_group('My Filters', user_filters) + option_group('Shared Filters', shared_filters), html_options + filter_link = link_to message('widget.filter.edit'), {:controller => 'issues', :action => 'manage'}, :class => 'link-action' + + "#{filters_combo} #{filter_link}" + when PropertyType::TYPE_SINGLE_SELECT_LIST default_value = options[:default].blank? ? '' : message('default') select_options = "" diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb index 551fb16b9ef..c07b7f86440 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb @@ -30,6 +30,8 @@ class PropertyType TYPE_REGULAR_EXPRESSION = 'REGULAR_EXPRESSION' TYPE_FILTER = 'FILTER' + # Since 3.7 + TYPE_ISSUE_FILTER = 'ISSUE_FILTER' def self.text_to_value(text, type) case type diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb index 3dd2a4bf56f..3acbc701649 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb @@ -1,40 +1,28 @@ <% table_limit = params[:table_limit] unless table_limit widget_id = params[:widget_id] unless widget_id - search_options = params.clone unless search_options - # Hack to delete params coming from pagination - search_options.delete(:controller) - search_options.delete(:action) - search_options.delete(:page_id) - search_options.delete(:table_limit) - search_options.delete(:widget_id) - search_options.delete(:period) + search_options = params.clone unless search_options + search_options['pageIndex'] = params[:pageIndex] || 1 + search_options['pageSize'] = table_limit.to_i - search_options['sort'] = 'UPDATE_DATE' - search_options['asc'] = 'FALSE' - if @dashboard_configuration && @dashboard_configuration.selected_period? - search_options['createdAfter'] = Api::Utils.format_datetime(@dashboard_configuration.from_datetime) - search_options['createdBefore'] = Api::Utils.format_datetime(DateTime.now) + if search_options['filter'] + issue_filter_result = Internal.issues.execute(search_options['filter'].to_i, search_options) + results = issue_filter_result.result + else + search_options['sort'] = 'UPDATE_DATE' + search_options['asc'] = 'FALSE' + if @dashboard_configuration && @dashboard_configuration.selected_period? + search_options['createdAfter'] = Api::Utils.format_datetime(@dashboard_configuration.from_datetime) + search_options['createdBefore'] = Api::Utils.format_datetime(DateTime.now) + end + results = Api.issues.find(search_options) end - results = Api.issues.find(search_options) issues = results.issues - - # table pagination - page_size = table_limit.to_i - total_number = issues.size - if issues.size > page_size - page_id = (params[:page_id] ? params[:page_id].to_i : 1) - page_count = issues.size / page_size - page_count += 1 if (issues.size % page_size > 0) - from = (page_id-1) * page_size - to = (page_id*page_size)-1 - to = issues.size-1 if to >= issues.size - issues = issues[from..to] - end + paging = results.paging %> -<% if issues.size ==0 %> +<% if paging.total() == 0 %> <%= message('widget.my_reviews.no_issue') -%> <% else %> @@ -45,42 +33,6 @@ - - - - <%= link_to(total_number, {:controller => 'issues', :action => 'search' }.merge(search_options)) -%> <%= message('results').downcase -%> - <% - if page_count - page_count = 20 if page_count>20 - link_params = search_options.clone - link_params[:controller] = 'issue' - link_params[:action] = 'widget_issues_list' - link_params[:snapshot_id] = @snapshot.id if @snapshot - link_params[:table_limit] = table_limit - link_params[:widget_id] = widget_id - link_params[:period] = params[:period] - %> - | - <%= link_to_remote(message('paging_previous'), - :update => "issues-widget-#{widget_id}", - :url => {:params => link_params.merge({:page_id => page_id-1})}) if page_id>1 %> - <%= message('paging_previous') unless page_id>1 %> - <% for index in 1..page_count %> - <%= index.to_s if index==page_id %> - <%= link_to_remote(index.to_s, - :update => "issues-widget-#{widget_id}", - :url => {:params => link_params.merge({:page_id => index})}) unless index==page_id %> - <% end %> - <%= link_to_remote(message('paging_next'), - :update => "issues-widget-#{widget_id}", - :url => {:params => link_params.merge({:page_id => page_id+1})}) if page_id - <%= message('paging_next') unless page_id - <% - end - %> - - - <% issues.each do |issue| @@ -107,5 +59,23 @@ end %> + <% if results.maxResultsReached() %> +

<%= message('issue_filter.widget.max_results_reached', :params => paging.total()) -%>

+ <% end %> + <% + link_params = search_options.clone + link_params[:controller] = 'issue' + link_params[:action] = 'widget_issues_list' + link_params[:snapshot_id] = @snapshot.id if @snapshot + link_params[:table_limit] = table_limit + link_params[:widget_id] = widget_id + link_params[:period] = params[:period] + %> + <%= paginate_java(paging, :colspan => 4, :id => "issue-filter-foot-#{widget_id}", :include_loading_icon => true) { |label, page_id| + link_to_remote(label, + :update => "issues-widget-#{widget_id}", + :url => link_params.merge({:pageIndex => page_id})) + } -%> + <% end %> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list_widget.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list_widget.html.erb index 8bab2050697..85ba0972c37 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list_widget.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list_widget.html.erb @@ -1,8 +1,10 @@ <% - table_limit = widget_properties["numberOfLines"] + table_limit = widget_properties["numberOfLines"] %> -

<%= title -%>

+<% if defined? title %> +

<%= title -%>

+<% end %>
<%= render :partial => 'project/widgets/issues/issues_list', diff --git a/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java b/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java index b5985bcb552..59ed2b33055 100644 --- a/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java @@ -20,6 +20,7 @@ package org.sonar.server.issue; +import com.google.common.collect.Maps; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -398,14 +399,28 @@ public class InternalRubyIssueServiceTest { @Test public void should_execute_issue_filter_from_issue_query() { - service.execute(IssueQuery.builder().build()); + service.execute(Maps.newHashMap()); verify(issueFilterService).execute(any(IssueQuery.class)); } @Test public void should_execute_issue_filter_from_existing_filter() { - service.execute(10L); - verify(issueFilterService).execute(eq(10L), any(UserSession.class)); + Map props = newHashMap(); + props.put("componentRoots", "struts"); + when(issueFilterService.deserializeIssueFilterQuery(any(DefaultIssueFilter.class))).thenReturn(props); + + Map overrideProps = newHashMap(); + overrideProps.put("pageSize", 20); + overrideProps.put("pageIndex", 2); + service.execute(10L, overrideProps); + ArgumentCaptor captor = ArgumentCaptor.forClass(IssueQuery.class); + verify(issueFilterService).execute(captor.capture()); + verify(issueFilterService).find(eq(10L), any(UserSession.class)); + + IssueQuery issueQuery = captor.getValue(); + assertThat(issueQuery.componentRoots()).contains("struts"); + assertThat(issueQuery.pageSize()).isEqualTo(20); + assertThat(issueQuery.pageIndex()).isEqualTo(2); } @Test @@ -432,6 +447,14 @@ public class InternalRubyIssueServiceTest { verify(issueFilterService).toggleFavouriteIssueFilter(eq(10L), any(UserSession.class)); } + @Test + public void should_check_is_user_is_authorized_to_see_issue_filter() { + DefaultIssueFilter issueFilter = new DefaultIssueFilter(); + service.isUserAuthorized(issueFilter); + verify(issueFilterService).verifyLoggedIn(any(UserSession.class)); + verify(issueFilterService).verifyCurrentUserCanReadFilter(eq(issueFilter), any(UserSession.class)); + } + private String createLongString(int size) { String result = ""; for (int i = 0; i < size; i++) { diff --git a/sonar-server/src/test/java/org/sonar/server/issue/IssueFilterServiceTest.java b/sonar-server/src/test/java/org/sonar/server/issue/IssueFilterServiceTest.java index 30ea6d28682..d7690d7278b 100644 --- a/sonar-server/src/test/java/org/sonar/server/issue/IssueFilterServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/issue/IssueFilterServiceTest.java @@ -78,7 +78,17 @@ public class IssueFilterServiceTest { IssueFilterDto issueFilterDto = new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("john"); when(issueFilterDao.selectById(1L)).thenReturn(issueFilterDto); - DefaultIssueFilter issueFilter = service.findById(1L, userSession); + DefaultIssueFilter issueFilter = service.findById(1L); + assertThat(issueFilter).isNotNull(); + assertThat(issueFilter.id()).isEqualTo(1L); + } + + @Test + public void should_find_issue_filter() { + IssueFilterDto issueFilterDto = new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("john"); + when(issueFilterDao.selectById(1L)).thenReturn(issueFilterDto); + + DefaultIssueFilter issueFilter = service.find(1L, userSession); assertThat(issueFilter).isNotNull(); assertThat(issueFilter.id()).isEqualTo(1L); } @@ -88,7 +98,7 @@ public class IssueFilterServiceTest { IssueFilterDto issueFilterDto = new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("arthur").setShared(true); when(issueFilterDao.selectById(1L)).thenReturn(issueFilterDto); - DefaultIssueFilter issueFilter = service.findById(1L, userSession); + DefaultIssueFilter issueFilter = service.find(1L, userSession); assertThat(issueFilter).isNotNull(); assertThat(issueFilter.id()).isEqualTo(1L); } @@ -97,7 +107,7 @@ public class IssueFilterServiceTest { public void should_not_find_by_id_on_not_existing_issue() { when(issueFilterDao.selectById(1L)).thenReturn(null); try { - service.findById(1L, userSession); + service.find(1L, userSession); fail(); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Filter not found: 1"); @@ -108,12 +118,11 @@ public class IssueFilterServiceTest { public void should_not_find_by_id_if_not_logged() { when(userSession.isLoggedIn()).thenReturn(false); try { - service.findById(1L, userSession); + service.find(1L, userSession); fail(); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not logged in"); } - verifyZeroInteractions(issueFilterDao); } @@ -123,7 +132,7 @@ public class IssueFilterServiceTest { when(issueFilterDao.selectById(1L)).thenReturn(issueFilterDto); try { // John is not authorized to see eric filters - service.findById(1L, userSession); + service.find(1L, userSession); fail(); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User is not authorized to read this filter"); @@ -396,22 +405,6 @@ public class IssueFilterServiceTest { verify(issueFinder).find(issueQuery); } - @Test - public void should_execute_from_filter_id() { - Map map = newHashMap(); - map.put("componentRoots", "struts"); - when(issueFilterSerializer.deserialize(anyString())).thenReturn(map); - when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Old Filter").setUserLogin("john").setData("componentRoots=struts")); - - ArgumentCaptor issueQueryCaptor = ArgumentCaptor.forClass(IssueQuery.class); - - service.execute(1L, userSession); - verify(issueFinder).find(issueQueryCaptor.capture()); - - IssueQuery issueQuery = issueQueryCaptor.getValue(); - assertThat(issueQuery.componentRoots()).contains("struts"); - } - @Test public void should_find_shared_issue_filter() { when(issueFilterDao.selectSharedWithoutUserFilters("john")).thenReturn(newArrayList(new IssueFilterDto().setId(1L).setName("My Issue").setUserLogin("john").setShared(true)));