]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3825 improve error handling + compatibility of sorting with oracle
authorSimon Brandhof <simon.brandhof@gmail.com>
Tue, 11 Dec 2012 15:58:57 +0000 (16:58 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Tue, 11 Dec 2012 15:58:57 +0000 (16:58 +0100)
19 files changed:
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterEngine.java
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterResult.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java
sonar-core/src/main/java/org/sonar/core/measure/package-info.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterEngineTest.java
sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterExecutorTest.java
sonar-core/src/test/resources/org/sonar/core/measure/MeasureFilterExecutorTest/shared.xml
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/all_projects_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter.rb
sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter_display_treemap.rb
sonar-server/src/main/webapp/WEB-INF/app/views/all_projects/index.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_display_cloud.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_display_list.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_display_treemap.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_sidebar.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/search.html.erb

index 05b417f63469c209f22920e9bba0412732c142be..11d96ce8ab6adf9ed63316d027a77eedb57349e2 100644 (file)
@@ -444,10 +444,10 @@ measure_filter.display.list=List
 measure_filter.display.treemap=Treemap
 measure_filter.list.change=Change Columns
 measure_filter.treemap.change=Change Treemap
-measure_filter.too_many_results=Too many results. Please refine your search.
 measure_filter.add_column_button=Add Column
 measure_filter.widget.unknown_filter_warning=This widget is configured to display a measure filter that doesn't exist anymore.
-
+measure_filter.error.UNKNOWN=Unexpected error. Please contact the administrator.
+measure_filter.error.TOO_MANY_RESULTS=Too many results. Please refine your search.
 
 #------------------------------------------------------------------------------
 #
index 69bc849667c3d64420a95492c62409c292114cea..84d3211ecf2ee22e442bb07ea1c81ea53076c1b6 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.core.measure;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
 import org.apache.commons.lang.SystemUtils;
-import org.json.simple.parser.ParseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.ServerComponent;
@@ -37,38 +36,49 @@ public class MeasureFilterEngine implements ServerComponent {
   private final MeasureFilterFactory factory;
   private final MeasureFilterExecutor executor;
 
+  private static final int MAX_ROWS = 5000;
+
   public MeasureFilterEngine(MeasureFilterFactory factory, MeasureFilterExecutor executor) {
     this.executor = executor;
     this.factory = factory;
   }
 
-  public List<MeasureFilterRow> execute(Map<String, Object> filterMap, @Nullable Long userId) throws ParseException {
+  public MeasureFilterResult execute(Map<String, Object> filterMap, @Nullable Long userId) {
     return execute(filterMap, userId, LoggerFactory.getLogger("org.sonar.MEASURE_FILTER"));
   }
 
   @VisibleForTesting
-  List<MeasureFilterRow> execute(Map<String, Object> filterMap, @Nullable Long userId, Logger logger) throws ParseException {
+  MeasureFilterResult execute(Map<String, Object> filterMap, @Nullable Long userId, Logger logger) {
+    long start = System.currentTimeMillis();
+    MeasureFilterResult result = new MeasureFilterResult();
     MeasureFilterContext context = new MeasureFilterContext();
     context.setUserId(userId);
     context.setData(String.format("{%s}", Joiner.on('|').withKeyValueSeparator("=").join(filterMap)));
     try {
-      long start = System.currentTimeMillis();
       MeasureFilter filter = factory.create(filterMap);
       List<MeasureFilterRow> rows = executor.execute(filter, context);
-      log(context, rows, (System.currentTimeMillis() - start), logger);
-      return rows;
+      if (rows.size() <= MAX_ROWS) {
+        result.setRows(rows);
+      } else {
+        result.setError(MeasureFilterResult.Error.TOO_MANY_RESULTS);
+      }
+      result.setDurationInMs(System.currentTimeMillis() - start);
+      log(context, result, logger);
+
     } catch (Exception e) {
-      throw new IllegalStateException("Fail to execute filter: " + context, e);
+      result.setError(MeasureFilterResult.Error.UNKNOWN);
+      logger.error("Fail to execute measure filter: " + context, e);
     }
+    return result;
   }
 
-  private void log(MeasureFilterContext context, List<MeasureFilterRow> rows, long durationMs, Logger logger) {
+  private void log(MeasureFilterContext context, MeasureFilterResult result, Logger logger) {
     if (logger.isDebugEnabled()) {
       StringBuilder log = new StringBuilder();
       log.append(SystemUtils.LINE_SEPARATOR);
-      log.append(" filter: ").append(context.getData()).append(SystemUtils.LINE_SEPARATOR);
+      log.append("request: ").append(context.getData()).append(SystemUtils.LINE_SEPARATOR);
+      log.append(" result: ").append(result.toString()).append(SystemUtils.LINE_SEPARATOR);
       log.append("    sql: ").append(context.getSql()).append(SystemUtils.LINE_SEPARATOR);
-      log.append("results: ").append(rows.size()).append(" rows in ").append(durationMs).append("ms").append(SystemUtils.LINE_SEPARATOR);
       logger.debug(log.toString());
     }
   }
diff --git a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterResult.java b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterResult.java
new file mode 100644 (file)
index 0000000..118733e
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.core.measure;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+
+public class MeasureFilterResult {
+
+  public static enum Error {
+    TOO_MANY_RESULTS, UNKNOWN
+  }
+
+  private List<MeasureFilterRow> rows = null;
+  private Error error = null;
+  private long durationInMs;
+
+  MeasureFilterResult() {
+  }
+
+  public List<MeasureFilterRow> getRows() {
+    return rows;
+  }
+
+  public Error getError() {
+    return error;
+  }
+
+  public long getDurationInMs() {
+    return durationInMs;
+  }
+
+  MeasureFilterResult setDurationInMs(long l) {
+    this.durationInMs = l;
+    return this;
+  }
+
+  MeasureFilterResult setRows(@Nullable List<MeasureFilterRow> rows) {
+    this.rows = rows;
+    return this;
+  }
+
+  MeasureFilterResult setError(@Nullable Error err) {
+    this.error = err;
+    return this;
+  }
+
+  public boolean isSuccess() {
+    return error == null;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    if (rows != null) {
+      sb.append(rows.size()).append(" rows, ");
+    }
+    if (error != null) {
+      sb.append("error=").append(error).append(", ");
+    }
+    sb.append(durationInMs).append("ms");
+    return sb.toString();
+  }
+}
index 21a99ef519798b50ab9561a06fecb51d43aba9fb..0da49a1f0f2152ba1c4832ab59449a4e052b59a8 100644 (file)
@@ -75,7 +75,7 @@ class MeasureFilterSql {
   }
 
   private void init() {
-    sql.append("SELECT block.id, max(block.rid) as rid, max(block.rootid) as rootid, max(sortval) as sortval");
+    sql.append("SELECT block.id, max(block.rid) AS rid, max(block.rootid) AS rootid, max(sortval) AS sortval1, CASE WHEN sortval IS NULL THEN 1 ELSE 0 END AS sortval2 ");
     for (int index = 0; index < filter.getMeasureConditions().size(); index++) {
       sql.append(", max(crit_").append(index).append(")");
     }
@@ -88,7 +88,7 @@ class MeasureFilterSql {
       appendConditionBlock(index, condition);
     }
 
-    sql.append(") block GROUP BY block.id");
+    sql.append(") block GROUP BY block.id, sortval2");
     if (!filter.getMeasureConditions().isEmpty()) {
       sql.append(" HAVING ");
       for (int index = 0; index < filter.getMeasureConditions().size(); index++) {
@@ -99,14 +99,13 @@ class MeasureFilterSql {
       }
     }
     if (filter.sort().isSortedByDatabase()) {
-      sql.append(" ORDER BY sortval ");
-      sql.append(filter.sort().isAsc() ? "ASC" : "DESC");
-      sql.append(" NULLS LAST");
+      sql.append(" ORDER BY sortval2 ASC, sortval1 ");
+      sql.append(filter.sort().isAsc() ? "ASC " : "DESC ");
     }
   }
 
   private void appendSortBlock() {
-    sql.append(" SELECT s.id, s.project_id AS rid, s.root_project_id AS rootid, ").append(filter.sort().column()).append(" AS sortval");
+    sql.append(" SELECT s.id, s.project_id AS rid, s.root_project_id AS rootid, ").append(filter.sort().column()).append(" AS sortval ");
     for (int index = 0; index < filter.getMeasureConditions().size(); index++) {
       MeasureFilterCondition condition = filter.getMeasureConditions().get(index);
       sql.append(", ").append(nullSelect(condition.metric())).append(" AS crit_").append(index);
@@ -125,7 +124,7 @@ class MeasureFilterSql {
   }
 
   private void appendConditionBlock(int conditionIndex, MeasureFilterCondition condition) {
-    sql.append(" SELECT s.id, s.project_id AS rid, s.root_project_id AS rootid, null AS sortval");
+    sql.append(" SELECT s.id, s.project_id AS rid, s.root_project_id AS rootid, null AS sortval ");
     for (int j = 0; j < filter.getMeasureConditions().size(); j++) {
       sql.append(", ");
       if (j == conditionIndex) {
diff --git a/sonar-core/src/main/java/org/sonar/core/measure/package-info.java b/sonar-core/src/main/java/org/sonar/core/measure/package-info.java
new file mode 100644 (file)
index 0000000..6b2f8b9
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.core.measure;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index 06051a65d5de90ee308ce0c5598528f5456fe04a..95a4e8fc9929c33825d29571924b6dae8e4a7474 100644 (file)
 package org.sonar.core.measure;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
-import org.json.simple.parser.ParseException;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.slf4j.Logger;
 
-import java.util.HashMap;
 import java.util.Map;
 
+import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.refEq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class MeasureFilterEngineTest {
 
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
   @Test
   public void should_create_and_execute_filter() throws Exception {
-    Map<String,Object> filterMap = ImmutableMap.of("qualifiers", (Object) "TRK");
+    Map<String, Object> filterMap = ImmutableMap.of("qualifiers", (Object) "TRK");
     MeasureFilterFactory factory = mock(MeasureFilterFactory.class);
     MeasureFilter filter = new MeasureFilter();
     when(factory.create(filterMap)).thenReturn(filter);
@@ -67,16 +64,16 @@ public class MeasureFilterEngineTest {
   }
 
   @Test
-  public void throw_definition_of_filter_on_error() throws Exception {
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("filter={qualifiers=TRK}");
-
-    Map<String,Object> filterMap = ImmutableMap.of("qualifiers", (Object)"TRK");
+  public void keep_error_but_do_not_fail() throws Exception {
+    Map<String, Object> filterMap = ImmutableMap.of("qualifiers", (Object) "TRK");
     MeasureFilterFactory factory = mock(MeasureFilterFactory.class);
     when(factory.create(filterMap)).thenThrow(new IllegalArgumentException());
     MeasureFilterExecutor executor = mock(MeasureFilterExecutor.class);
 
     MeasureFilterEngine engine = new MeasureFilterEngine(factory, executor);
-    engine.execute(filterMap, 50L);
+    MeasureFilterResult result = engine.execute(filterMap, 50L);
+
+    assertThat(result.isSuccess()).isFalse();
+    assertThat(result.getError()).isEqualTo(MeasureFilterResult.Error.UNKNOWN);
   }
 }
index 3416b62fa8ec1f1b19e21e590eb27e48226ddb53..4eeab0003ec85b436d6062db85baa42c5c37399c 100644 (file)
@@ -46,6 +46,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
   private static final Metric METRIC_LINES = new Metric.Builder("lines", "Lines", Metric.ValueType.INT).create().setId(1);
   private static final Metric METRIC_PROFILE = new Metric.Builder("profile", "Profile", Metric.ValueType.STRING).create().setId(2);
   private static final Metric METRIC_COVERAGE = new Metric.Builder("coverage", "Coverage", Metric.ValueType.FLOAT).create().setId(3);
+  private static final Metric METRIC_UNKNOWN = new Metric.Builder("unknown", "Unknown", Metric.ValueType.FLOAT).create().setId(4);
 
   private MeasureFilterExecutor executor;
 
@@ -224,7 +225,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
       .setSortOnMetric(METRIC_COVERAGE).setSortAsc(false);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
-    // Java has coverage but not PHP
+    // Java project has coverage but not PHP
     assertThat(rows).hasSize(2);
     verifyJavaProject(rows.get(0));
     verifyPhpProject(rows.get(1));
@@ -236,7 +237,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
         .setSortOnMetric(METRIC_COVERAGE).setSortAsc(true);
       List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
-      // Java has coverage but not PHP
+      // Java project has coverage but not PHP
       assertThat(rows).hasSize(2);
       verifyJavaProject(rows.get(0));
       verifyPhpProject(rows.get(1));
@@ -245,7 +246,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
   @Test
   public void sort_by_missing_numeric_measure() throws SQLException {
     // coverage measures are not computed
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortOnMetric(METRIC_COVERAGE);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortOnMetric(METRIC_UNKNOWN);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // 2 files, random order
index bd5a18fea6bddba3755bd6472e55ae69ece430cf..8b4a6bbc26ee1c0f2cb354d1f04e490b52783a7d 100644 (file)
            optimized_best_value="[true]" best_value="100" direction="1" hidden="[false]"
            delete_historical_data="[null]"/>
 
+  <metrics id="4" name="unknown" val_type="FLOAT" description="Coverage" domain="Test"
+             short_name="Unknown" qualitative="[true]" user_managed="[false]" enabled="[true]" origin="JAV"
+             worst_value="[null]"
+             optimized_best_value="[true]" best_value="100" direction="1" hidden="[false]"
+             delete_historical_data="[null]"/>
+
   <!-- java project -->
   <projects kee="java_project" long_name="Java project" scope="PRJ" qualifier="TRK" name="Java project"
             id="1" root_id="[null]"
                     RULE_ID="[null]" text_value="Sonar way" tendency="[null]" measure_date="[null]" project_id="[null]"
                     alert_status="[null]" description="[null]" characteristic_id="[null]"/>
 
+  <!-- coverage of java project -->
+  <project_measures id="1006" metric_id="3" value="12.3" snapshot_id="101"
+                      url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+                      variation_value_4="[null]" variation_value_5="[null]"
+                      rule_priority="[null]" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+                      RULE_ID="[null]" text_value="Sonar way" tendency="[null]" measure_date="[null]" project_id="[null]"
+                      alert_status="[null]" description="[null]" characteristic_id="[null]"/>
+
   <!-- php project -->
   <projects kee="php_project" long_name="PHP project" scope="PRJ" qualifier="TRK" name="PHP project"
             id="10" root_id="[null]"
index ef7c50933ca88b59fb437510870702c4edc5c076..154f832d36ba2e5a3198d2eed78ae0852e7a3f9f 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.ui;
 
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import org.json.simple.parser.ParseException;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.config.License;
@@ -50,7 +49,7 @@ import org.sonar.api.workflow.internal.DefaultWorkflowContext;
 import org.sonar.api.workflow.screen.Screen;
 import org.sonar.core.i18n.RuleI18nManager;
 import org.sonar.core.measure.MeasureFilterEngine;
-import org.sonar.core.measure.MeasureFilterRow;
+import org.sonar.core.measure.MeasureFilterResult;
 import org.sonar.core.persistence.Database;
 import org.sonar.core.persistence.DatabaseMigrator;
 import org.sonar.core.persistence.DryRunDatabaseFactory;
@@ -100,7 +99,7 @@ public final class JRubyFacade {
     return getContainer().getComponentByType(componentType);
   }
 
-  public List<MeasureFilterRow> executeMeasureFilter(Map<String, Object> map, @Nullable Long userId) throws ParseException {
+  public MeasureFilterResult executeMeasureFilter(Map<String, Object> map, @Nullable Long userId) {
     return get(MeasureFilterEngine.class).execute(map, userId);
   }
 
index 60c62124ae7e513ecfcf5486e0b2945d3da647d9..fa74c75dc58b402530cba0ae475c819bb8aae6a5 100644 (file)
@@ -22,14 +22,11 @@ class AllProjectsController < ApplicationController
   SECTION=Navigation::SECTION_HOME
   
   def index
-    require_parameters :qualifier
-    @qualifier = params[:qualifier]
+    @qualifier = params[:qualifier]||'TRK'
     bad_request("The 'qualifier' parameter is not valid. It must reference a root type.") unless Project.root_qualifiers.include?(@qualifier)
-    
-    
+
     @filter = MeasureFilter.new
-    @filter.criteria = params.merge({'qualifiers' => [@qualifier], :cols => ['name', 'description', 'links'], :sort => 'name'})
-    @filter.enable_default_display
+    @filter.criteria={:qualifiers => @qualifier, :sort => 'name', :asc => (params[:asc]!='false')}
     @filter.execute(self, :user => current_user)
   end
 
index 7e79a383ce3f5814ff6c5b38cdb1cf15fdd9f4d4..05b3e471d393b8b342f5c4cee2d617ba4c9f1b80 100644 (file)
@@ -34,9 +34,9 @@ module MeasuresHelper
     "<th class='#{column.align} #{column.title_css}'>#{html}</th>"
   end
 
-  def list_cell_html(column, result)
+  def list_cell_html(column, row)
     if column.metric
-      measure = result.measure(column.metric)
+      measure = row.measure(column.metric)
       if column.period
         format_variation(measure, :index => column.period, :style => 'light')
       elsif column.metric.numeric?
@@ -46,22 +46,22 @@ module MeasuresHelper
       end
 
     elsif column.key=='name'
-      "#{qualifier_icon(result.snapshot)} #{link_to(result.snapshot.resource.name(true), {:controller => 'dashboard', :id => result.snapshot.resource_id}, :title => result.snapshot.resource.key)}"
+      "#{qualifier_icon(row.snapshot)} #{link_to(row.snapshot.resource.name(true), {:controller => 'dashboard', :id => row.snapshot.resource_id}, :title => row.snapshot.resource.key)}"
     elsif column.key=='short_name'
-      "#{qualifier_icon(result.snapshot)} #{link_to(result.snapshot.resource.name(false), {:controller => 'dashboard', :id => result.snapshot.resource_id}, :title => result.snapshot.resource.key)}"
+      "#{qualifier_icon(row.snapshot)} #{link_to(row.snapshot.resource.name(false), {:controller => 'dashboard', :id => row.snapshot.resource_id}, :title => row.snapshot.resource.key)}"
     elsif column.key=='date'
-      human_short_date(result.snapshot.created_at)
+      human_short_date(row.snapshot.created_at)
     elsif column.key=='key'
-      "<span class='small'>#{result.snapshot.resource.kee}</span>"
+      "<span class='small'>#{row.snapshot.resource.kee}</span>"
     elsif column.key=='description'
-      h result.snapshot.resource.description
+      h row.snapshot.resource.description
     elsif column.key=='version'
-      h result.snapshot.version
+      h row.snapshot.version
     elsif column.key=='language'
-      Api::Utils.language_name(result.snapshot.resource.language)
-    elsif column.key=='links' && result.links
+      Api::Utils.language_name(row.snapshot.resource.language)
+    elsif column.key=='links' && row.links
       html = ''
-      result.links.select { |link| link.href.start_with?('http') }.each do |link|
+      row.links.select { |link| link.href.start_with?('http') }.each do |link|
         html += link_to(image_tag(link.icon, :alt => link.name), link.href, :class => 'nolink', :popup => true) unless link.custom?
       end
       html
index d24cfa46ed759b554108c6bce283110b188b19f9..b7ab2cdd13cccfd80d9c24e69749673f6dca05bf 100644 (file)
@@ -21,7 +21,7 @@ require 'set'
 class MeasureFilter < ActiveRecord::Base
 
   # Row in the table of results
-  class Result
+  class Row
     attr_reader :snapshot, :measures_by_metric, :links
 
     def initialize(snapshot)
@@ -64,7 +64,7 @@ class MeasureFilter < ActiveRecord::Base
   validates_length_of :name, :maximum => 100, :message => Api::Utils.message('measure_filter.name_too_long')
   validates_length_of :description, :allow_nil => true, :maximum => 4000
 
-  attr_reader :pagination, :security_exclusions, :base_result, :results, :display
+  attr_reader :pagination, :security_exclusions, :base_row, :rows, :display
 
 
   def sort_key
@@ -72,7 +72,7 @@ class MeasureFilter < ActiveRecord::Base
   end
 
   def sort_asc?
-    criteria['asc']=='true'
+    criteria['asc']!='false'
   end
 
   # array of the metrics to use when loading measures
@@ -126,9 +126,8 @@ class MeasureFilter < ActiveRecord::Base
   def set_criteria_value(key, value)
     @criteria ||= HashWithIndifferentAccess.new
     if key
-      if value && value!='' && value!=['']
-        value = value.to_s if value.is_a?(Fixnum)
-        @criteria[key]=value
+      if value!=nil && value!='' && value!=['']
+        @criteria[key]=(value.kind_of?(Array) ? value : value.to_s)
       else
         @criteria.delete(key)
       end
@@ -188,9 +187,14 @@ class MeasureFilter < ActiveRecord::Base
     init_results
     init_display(options)
     user = options[:user]
-    rows=Api::Utils.java_facade.executeMeasureFilter(criteria, (user ? user.id : nil))
-    snapshot_ids = filter_authorized_snapshot_ids(rows, controller)
-    load_results(snapshot_ids)
+    result = Api::Utils.java_facade.executeMeasureFilter(criteria, (user ? user.id : nil))
+    if result.error
+      errors.add_to_base(Api::Utils.message("measure_filter.error.#{result.error}"))
+    else
+      rows = result.getRows()
+      snapshot_ids = filter_authorized_snapshot_ids(rows, controller)
+      load_results(snapshot_ids)
+    end
     self
   end
 
@@ -204,8 +208,8 @@ class MeasureFilter < ActiveRecord::Base
   def init_results
     @pagination = Api::Pagination.new
     @security_exclusions = nil
-    @results = nil
-    @base_result = nil
+    @rows = nil
+    @base_row = nil
     self
   end
 
@@ -223,20 +227,20 @@ class MeasureFilter < ActiveRecord::Base
   end
 
   def load_results(snapshot_ids)
-    @results = []
+    @rows = []
     metric_ids = metrics.map(&:id)
 
     if !snapshot_ids.empty?
-      results_by_snapshot_id = {}
+      rows_by_snapshot_id = {}
       snapshots = Snapshot.find(:all, :include => ['project'], :conditions => ['id in (?)', snapshot_ids])
       snapshots.each do |snapshot|
-        result = Result.new(snapshot)
-        results_by_snapshot_id[snapshot.id] = result
+        row = Row.new(snapshot)
+        rows_by_snapshot_id[snapshot.id] = row
       end
 
-      # @results must be in the same order than the snapshot ids
+      # @rows must be in the same order than the snapshot ids
       snapshot_ids.each do |sid|
-        @results << results_by_snapshot_id[sid]
+        @rows << rows_by_snapshot_id[sid]
       end
 
       unless metric_ids.empty?
@@ -244,34 +248,34 @@ class MeasureFilter < ActiveRecord::Base
           ['rule_priority is null and rule_id is null and characteristic_id is null and person_id is null and snapshot_id in (?) and metric_id in (?)', snapshot_ids, metric_ids]
         )
         measures.each do |measure|
-          result = results_by_snapshot_id[measure.snapshot_id]
-          result.add_measure(measure)
+          row = rows_by_snapshot_id[measure.snapshot_id]
+          row.add_measure(measure)
         end
       end
 
       if require_links?
         project_ids = []
-        results_by_project_id = {}
+        rows_by_project_id = {}
         snapshots.each do |snapshot|
           project_ids << snapshot.project_id
-          results_by_project_id[snapshot.project_id] = results_by_snapshot_id[snapshot.id]
+          rows_by_project_id[snapshot.project_id] = rows_by_snapshot_id[snapshot.id]
         end
         links = ProjectLink.find(:all, :conditions => {:project_id => project_ids}, :order => 'link_type')
         links.each do |link|
-          results_by_project_id[link.project_id].add_link(link)
+          rows_by_project_id[link.project_id].add_link(link)
         end
       end
     end
     if base_resource
       base_snapshot = base_resource.last_snapshot
       if base_snapshot
-        @base_result = Result.new(base_snapshot)
+        @base_row = Row.new(base_snapshot)
         unless metric_ids.empty?
           base_measures = ProjectMeasure.find(:all, :conditions =>
             ['rule_priority is null and rule_id is null and characteristic_id is null and person_id is null and snapshot_id=? and metric_id in (?)', base_snapshot.id, metric_ids]
           )
           base_measures.each do |base_measure|
-            @base_result.add_measure(base_measure)
+            @base_row.add_measure(base_measure)
           end
         end
       end
index 48323872b3769ac05f793355bda441d2c73ab6cf..6fed8141b91b719076c6a1bc539a485a17be4621 100644 (file)
@@ -41,7 +41,7 @@ class MeasureFilterDisplayTreemap < MeasureFilterDisplay
   end
 
   def html
-    if filter.results
+    if filter.rows
       root = Treemap::Node.new(:id => -1, :label => '')
       build_tree(root)
 
@@ -57,7 +57,7 @@ class MeasureFilterDisplayTreemap < MeasureFilterDisplay
   end
 
   def empty?
-    @filter.results.nil? || @filter.results.empty?
+    @filter.rows.nil? || @filter.rows.empty?
   end
 
   def url_params
@@ -67,12 +67,12 @@ class MeasureFilterDisplayTreemap < MeasureFilterDisplay
   private
 
   def build_tree(node)
-    if @filter.results && @size_metric
-      @filter.results.each do |result|
-        size_measure=result.measure(@size_metric)
+    if @filter.rows && @size_metric
+      @filter.rows.each do |row|
+        size_measure=row.measure(@size_metric)
         if size_measure
-          color_measure=(@color_metric ? result.measure(@color_metric) : nil)
-          resource = result.snapshot.resource
+          color_measure=(@color_metric ? row.measure(@color_metric) : nil)
+          resource = row.snapshot.resource
           child = Treemap::Node.new(:size => size_value(size_measure),
                                     :label => resource.name(false),
                                     :title => escape_javascript(resource.name(true)),
index 892def6032b0eaae4b26fa383970ef4b8184fc58..9f174c9e69255d206aa0bc5ec838afc8877d7ab4 100644 (file)
@@ -1,29 +1,11 @@
-<% content_for :script do %>
-  <script>
-    function removeUrlAttr(url, attribute_key) {
-      var regexp = new RegExp("&?" + attribute_key + "=([^&]$|[^&]*)", "g");
-      return url.replace(regexp, '');
-    }
-    function reloadParameters(params) {
-      var url = decodeURI(window.location.href);
-      $j.each(params, function (key, value) {
-        url = removeUrlAttr(url, key);
-        url += '&' + key + '=' + value;
-      });
-      window.location = url;
-    }
-  </script>
-<% end %>
+<% if @filter.rows %>
 
-<% if @filter.results %>
-
-  <h1><%= message('qualifiers.all.' + @qualifier) -%></h1>
-  <br/>
+  <h1 class="marginbottom10"><%= message('qualifiers.all.' + @qualifier) -%></h1>
 
   <div id="all-projects">
 
     <% if @filter.security_exclusions %>
-      <p class="notes"><%= message('results_not_display_due_to_security') -%></p>
+      <p class="notes"><%= message('rows_not_display_due_to_security') -%></p>
     <% end %>
 
     <%
             <th class="thin" style></th>
           <% end %>
           <th class="thin nowrap">
-            <%= link_to_function( message('all-projects.cols.name'), "reloadParameters({asc:'#{(!@filter.sort_asc?).to_s}', sort:'name'})") -%>
-            <% if @filter.sort_key=='name' %>
-              <%= @filter.sort_asc? ? image_tag("asc12.png") : image_tag("desc12.png") -%>
-            <% end %>
+            <%= link_to message('all-projects.cols.name'), {:action => 'index', :qualifier => params[:qualifier], :asc => (!@filter.sort_asc?).to_s} -%>
+            <%= @filter.sort_asc? ? image_tag("asc12.png") : image_tag("desc12.png") -%>
           </th>
           <th></th>
           <th></th>
       </thead>
   
       <tbody>
-        <% @filter.results.each do |result| %>
+        <% @filter.rows.each do |row| %>
           <tr class="<%= cycle 'even', 'odd' -%>">
             <% if display_favourites %>
-              <td class="thin"><%= link_to_favourite(result.snapshot.resource) -%></td>
+              <td class="thin"><%= link_to_favourite(row.snapshot.resource) -%></td>
             <% end %>
             <td class="nowrap">
-              <%= qualifier_icon(result.snapshot)-%> <%= link_to(result.snapshot.resource.name(true), {:controller => 'dashboard', :id => result.snapshot.resource_id}, :title => result.snapshot.resource.key) -%>
+              <%= qualifier_icon(row.snapshot)-%> <%= link_to(row.snapshot.resource.name(true), {:controller => 'dashboard', :id => row.snapshot.resource_id}, :title => row.snapshot.resource.key) -%>
             </td>
             <td class="sep"></td>
             <td>
-              <%= h result.snapshot.resource.description -%>
+              <%= h row.snapshot.resource.description -%>
             </td>
             <td class="sep"></td>
             <td class="nowrap right">
               <% 
-                 if result.links
-                   result.links.select { |link| link.href.start_with?('http') }.each do |link|
+                 if row.links
+                   row.links.select { |link| link.href.start_with?('http') }.each do |link|
               %>
                 <%= link_to(image_tag(link.icon, :alt => link.name), link.href, :class => 'nolink', :popup => true) unless link.custom? -%>
               <%
@@ -79,7 +59,7 @@
           </tr>
         <% end %>
         
-        <% if @filter.results.empty? %>
+        <% if @filter.rows.empty? %>
           <tr class="even">
             <td colspan="<%= colspan -%>"><%= message 'no_data' -%></td>
           </tr>
@@ -89,7 +69,6 @@
     <%= render :partial => 'utils/tfoot_pagination', :locals => {:pagination => @filter.pagination, :colspan => colspan} %>
     
   </table>
-
   </div>
   
 <% end %>
\ No newline at end of file
index d853186cd1d070b94862437112c4ac51b4499681..5838afe905aede8c8eb31190d2050d013cf28669 100644 (file)
@@ -6,25 +6,25 @@
        :mean_color => Color::RGB.from_html("4D05B1"), # purple
        :max_color => Color::RGB.from_html("2360BF") # blue
      }
-     size_measure_values = filter.results.map do |result|
-       size_measure = result.measure(filter.display.size_metric)
+     size_measure_values = filter.rows.map do |row|
+       size_measure = row.measure(filter.display.size_metric)
        size_measure.value if size_measure
      end.compact
 
      min_size_value=(size_measure_values.empty? ? 0.0 : size_measure_values.min)
      max_size_value=(size_measure_values.empty? ? 0.0 : size_measure_values.max)
 
-     filter.results.each do |result|
-       size_measure=result.measure(filter.display.size_metric)
+     filter.rows.each do |row|
+       size_measure=row.measure(filter.display.size_metric)
        if size_measure && size_measure.value
-         color_measure=result.measure(filter.display.color_metric)
+         color_measure=row.measure(filter.display.color_metric)
 
-         title="#{result.snapshot.resource.long_name} | #{filter.display.size_metric.short_name}: #{size_measure.formatted_value}"
+         title="#{row.snapshot.resource.long_name} | #{filter.display.size_metric.short_name}: #{size_measure.formatted_value}"
          if color_measure && color_measure.value
            title += " | #{filter.display.color_metric.short_name}: #{color_measure.formatted_value}"
          end
   %>
-      <a href="<%= ApplicationController.root_context -%>/dashboard/index/<%= result.snapshot.resource_id -%>" title="<%= title -%>"><span style="font-size: <%= cloud_font_size(size_measure.value, min_size_value, max_size_value) -%>%;color: <%= MeasureColor.color(color_measure, color_options).html -%>"><%= result.snapshot.resource.name %></span></a>
+      <a href="<%= ApplicationController.root_context -%>/dashboard/index/<%= row.snapshot.resource_id -%>" title="<%= title -%>"><span style="font-size: <%= cloud_font_size(size_measure.value, min_size_value, max_size_value) -%>%;color: <%= MeasureColor.color(color_measure, color_options).html -%>"><%= row.snapshot.resource.name %></span></a>
     <% end
        end %>
 </div>
\ No newline at end of file
index e1e28ecd02f06bf3badc19acef8ff43d65d54df2..df8ad570af067f2b9ebd35c5dab087609a340d2c 100644 (file)
       <% end %>
   <% end %>
 
-  <% if filter.base_result %>
+  <% if filter.base_row %>
     <tr class="highlight">
       <% if display_favourites %>
-        <td class="thin"><%= link_to_favourite(filter.base_result.snapshot.resource) -%></td>
+        <td class="thin"><%= link_to_favourite(filter.base_row.snapshot.resource) -%></td>
       <% end %>
       <% filter.display.columns.each do |column| %>
         <td class="<%= column.align -%> <%= column.row_css -%>">
-          <%= list_cell_html(column, filter.base_result) -%>
+          <%= list_cell_html(column, filter.base_row) -%>
         </td>
       <% end %>
     </tr>
   <% end %>
 
-  <% filter.results.each do |result| %>
+  <% filter.rows.each do |row| %>
     <tr class="<%= cycle 'even', 'odd' -%>">
       <% if display_favourites %>
-        <td class="thin"><%= link_to_favourite(result.snapshot.resource) -%></td>
+        <td class="thin"><%= link_to_favourite(row.snapshot.resource) -%></td>
       <% end %>
       <% filter.display.columns.each do |column| %>
         <td class="<%= column.align -%> <%= column.row_css -%>">
-          <%= list_cell_html(column, result) -%>
+          <%= list_cell_html(column, row) -%>
         </td>
       <% end %>
     </tr>
   <% end %>
 
-  <% if filter.results.empty? %>
+  <% if filter.rows.empty? %>
     <tr class="even">
       <td colspan="<%= colspan -%>"><%= message 'no_data' -%></td>
     </tr>
index 8b20b0ab9904b3ccc5889cabdfeebfd767a33450..9e084aceccbeac1305d0bff78dfdf5ea7384083b 100644 (file)
   <% end
      end %>
 
-<% if filter.results.empty? %>
+<% if filter.rows.empty? %>
   <p><%= message('no_data') -%></p>
 <% elsif filter.pagination.pages>1 %>
-  <p><%= message('measure_filter.too_many_results') -%></p>
+  <p><%= message('measure_filter.errors.TOO_MANY_RESULTS') -%></p>
 <% else %>
   <div id="tm-<%= treemap_id -%>" class="treemap width100">
     <%= filter.display.html -%>
index 4a732647086910e1419c8f7ffa417917c35b8d8a..b0f8aabdf680b5c0ba0ad29ac96f6c93dd65137c 100644 (file)
     </li>
     <li>
       <input type="submit" name="search" value="<%= message('search_verb') -%>" id="search-button">
-      <% if @filter.results %>
+      <% if @filter.rows %>
         <a href="<%= ApplicationController.root_context -%>/measures" class="link-action"><%= message 'measure_filter.new_search' -%></a>
       <% end %>
     </li>
index a227532530e22dab670cd5d7ed34cdb6a7844d76..7129a6b37486d9bbd77754acfee242cdb18d7543 100644 (file)
@@ -3,14 +3,16 @@
     <%= render :partial => 'measures/sidebar' -%>
   </div>
 
-  <% if @filter.results && @filter.display %>
+  <% if @filter %>
     <div class="page-split-right">
       <div id="content">
         <div class="line-block marginbottom10">
           <ul class="operations">
+
             <%
-               edit_mode = (params[:edit]=='true')
-               unless edit_mode
+               if @filter.display
+                edit_mode = (params[:edit]=='true')
+                unless edit_mode
             %>
               <li>
                 <a id="change-display" href="<%= url_for params.merge({:edit => true, :id => @filter.id}) -%>"><%= message("measure_filter.#{@filter.display.key}.change") -%></a>
@@ -22,6 +24,8 @@
                 <%= link_to_if display_key!=@filter.display.key, message("measure_filter.display.#{display_key}"), params.merge(:action => 'search', :display => display_key, :id => @filter.id), :id => "display_as_#{display_key}" -%>
               <% end %>
             </li>
+            <% end %>
+
             <% if logged_in? %>
               <% if @filter.id %>
                 <li><a id="copy" href="<%= url_for params.merge({:action => 'copy_form', :id => @filter.id}) -%>" class="link-action open-modal"><%= message('copy') -%></a></li>
           <p class="notes"><%= message('results_not_display_due_to_security') -%></p>
         <% end %>
 
-        <%= render :partial => "measures/display_#{@filter.display.class::KEY}", :locals => {:filter => @filter, :edit_mode => edit_mode} -%>
+        <% if !@filter.errors.empty? %>
+          <% @filter.errors.full_messages.each do |message| %>
+            <div class="warning"><%= h message %></div>
+          <% end %>
+        <% elsif @filter.rows && @filter.display %>
+          <%= render :partial => "measures/display_#{@filter.display.class::KEY}", :locals => {:filter => @filter, :edit_mode => edit_mode} -%>
+        <% end %>
       </div>
     </div>
   <% end %>