]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3557 Ability to configure/display some issue widgets based on Issues Filters
authorJulien Lancelot <julien.lancelot@gmail.com>
Thu, 20 Jun 2013 13:17:26 +0000 (15:17 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Thu, 20 Jun 2013 13:17:41 +0000 (15:17 +0200)
16 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/issues/IssueFilterWidget.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issue_filter.html.erb [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/widgets/CoreWidgetsTest.java
sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertyType.java
sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
sonar-server/src/main/java/org/sonar/server/issue/IssueFilterResult.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/issue/IssueFilterService.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/properties_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/models/property_type.rb
sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/project/widgets/issues/_issues_list_widget.html.erb
sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java
sonar-server/src/test/java/org/sonar/server/issue/IssueFilterServiceTest.java

index ee121f9f7060c5a65129219a703fb6022ce9bb0b..97122719ccaa3c321928b60836ca5ba6880e2105 100644 (file)
@@ -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 (file)
index 0000000..fdac731
--- /dev/null
@@ -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");
+  }
+}
index 377c43aba48e7cf4be38fbef61bfe1c42d5b9300..d802c2e225945278fb18d892678d6fe72c076d44 100644 (file)
@@ -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 (file)
index 0000000..9f0dfa0
--- /dev/null
@@ -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? %>
+      <div style="padding-bottom: 5px">
+        <span class="note"><%= h filter.description -%></span>
+      </div>
+    <% 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
+  %>
+  <p><%= image_tag 'warning.png' %> <%= message 'widget.issue_filter.unknown_filter_warning' -%></p>
+<%
+   end
+%>
index c46edc253894aa70e41ce6a9bd91658e46d3f35f..583a4d24ebcc405543eede3eba7afb06b3ce7f31 100644 (file)
@@ -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<CoreWidget> widgets() {
-    return Collections2.transform(widgetClasses(), new Function<Class<? extends CoreWidget>, CoreWidget>() {
+    return newArrayList(Iterables.transform(widgetClasses(), new Function<Class<? extends CoreWidget>, CoreWidget>() {
       public CoreWidget apply(@Nullable Class<? extends CoreWidget> aClass) {
         try {
           return aClass.newInstance();
@@ -104,6 +106,6 @@ public class CoreWidgetsTest {
           throw Throwables.propagate(e);
         }
       }
-    });
+    }));
   }
 }
index e4f170cb02d44e28d280a5d4a7ff8e6b43191252..4e68aeac8683f175a4889ba6d0028c08599cd9bb 100644 (file)
@@ -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
    *
index 42754d424cece36157090cce0a8982564cbf8d63..24b091e3ab2233662c7f61badd7b0fd3a4ce90f3 100644 (file)
@@ -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<String, Object> props) {
-    return PublicRubyIssueService.toQuery(props);
+  public IssueQuery emptyIssueQuery() {
+    return PublicRubyIssueService.toQuery(Maps.<String, Object>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<DefaultIssueFilter> 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<String, Object> 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<String, Object> 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<String, Object> overrideProps) {
+    DefaultIssueFilter issueFilter = issueFilterService.find(issueFilterId, UserSession.get());
+    Map<String, Object> props = issueFilterService.deserializeIssueFilterQuery(issueFilter);
+    overrideProps(props, overrideProps);
+    IssueQuery issueQuery = PublicRubyIssueService.toQuery(props);
+    return issueFilterService.execute(issueQuery);
+  }
+
+  private void overrideProps(Map<String, Object> props, Map<String, Object> overrideProps){
+    overrideProp(props, overrideProps, "pageSize");
+    overrideProp(props, overrideProps, "pageIndex");
+  }
+
+  private void overrideProp(Map<String, Object> props, Map<String, Object> 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 (file)
index 0000000..6f317e8
--- /dev/null
@@ -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;
+  }
+}
index 63b7ae1eb038db4a5dc5e0b224f01b2053eb0335..94458ab3589f5f4d6b9099f4a28703776b0a1a36 100644 (file)
@@ -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<String, Object> 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<String, Object> filterQuery) {
+    return issueFilterSerializer.serialize(filterQuery);
+  }
+
+  public Map<String, Object> 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<String, Object> filterQuery){
-    return issueFilterSerializer.serialize(filterQuery);
-  }
-
-  public Map<String, Object> 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);
+  }
+
 }
index 0ae9f832ddf4378659dedc327634e3726141734e..95bb0d1b177d79adc36e96c7af9fc8dd326b888a 100644 (file)
@@ -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
index e090492a9b398e27cd8230db6ffb41230f724c9b..303eb072fb7500936e0726dc813a058d87ae44f5 100644 (file)
@@ -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 = "<option value=''>#{ default_value }</option>"
index 551fb16b9efde94b7e4684dd906f9abbbed1347f..c07b7f8644058979596fe71b7f16295122189191 100644 (file)
@@ -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
index 3dd2a4bf56f472413d32748e76fad6c331ef1a5e..3acbc7016499538a7dfe3cca0f4eb9d02160ead9 100644 (file)
@@ -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 %>
   <span class="empty_widget"><%= message('widget.my_reviews.no_issue') -%></span>
 
 <% else %>
       </th>
     </tr>
     </thead>
-    <tfoot>
-    <tr>
-      <td colspan="3">
-        <%= 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<page_count %>
-          <%= message('paging_next') unless page_id<page_count %>
-        <%
-           end
-        %>
-      </td>
-    </tr>
-    </tfoot>
     <tbody>
     <%
        issues.each do |issue|
        end
     %>
     </tbody>
+    <% if results.maxResultsReached() %>
+      <p class="notes"><%= message('issue_filter.widget.max_results_reached', :params => paging.total()) -%></p>
+    <% 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}))
+    } -%>
   </table>
+
 <% end %>
\ No newline at end of file
index 8bab20506978c38b1525cdd71a11f51cdedde078..85ba0972c37ac29860be465e2d7aac85bbd7f52b 100644 (file)
@@ -1,8 +1,10 @@
 <%
-     table_limit = widget_properties["numberOfLines"]
+  table_limit = widget_properties["numberOfLines"]
 %>
 
-<h3><%= title -%></h3>
+<% if defined? title %>
+  <h3><%= title -%></h3>
+<% end %>
 
 <div id="issues-widget-<%= widget_id -%>">
   <%= render :partial => 'project/widgets/issues/issues_list',
index b5985bcb552436cab30c9aacc99266a973f96c2c..59ed2b33055a7951ec47abd74c6144fbf87d973e 100644 (file)
@@ -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.<String, Object>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<String, Object> props = newHashMap();
+    props.put("componentRoots", "struts");
+    when(issueFilterService.deserializeIssueFilterQuery(any(DefaultIssueFilter.class))).thenReturn(props);
+
+    Map<String, Object> overrideProps = newHashMap();
+    overrideProps.put("pageSize", 20);
+    overrideProps.put("pageIndex", 2);
+    service.execute(10L, overrideProps);
+    ArgumentCaptor<IssueQuery> 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++) {
index 30ea6d28682b9e9487ed6e71db5d647b8d3a4f3f..d7690d7278bebf9a0e385338db4676ad041a6181 100644 (file)
@@ -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<String, Object> 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<IssueQuery> 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)));