]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3825 add more form attributes
authorSimon Brandhof <simon.brandhof@gmail.com>
Thu, 22 Nov 2012 16:19:38 +0000 (17:19 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Mon, 26 Nov 2012 12:54:16 +0000 (13:54 +0100)
15 files changed:
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilter.java
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterCondition.java
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterEngine.java
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterFactory.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java
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/java/org/sonar/core/measure/MeasureFilterTest.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/favourites_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter.rb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_display_list.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/search.html.erb

index 42a4f3e0f0aecd053e2f8ce292cceee6990e7658..be5b8966ad5aed0a850c1025363535079c7c7b91 100644 (file)
  */
 package org.sonar.core.measure;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
 import org.sonar.api.measures.Metric;
-import org.sonar.api.utils.DateUtils;
 
 import javax.annotation.Nullable;
 
-import java.util.Calendar;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
-import java.util.Map;
 
 public class MeasureFilter {
-  private static final String[] EMPTY = {};
 
   // conditions on resources
   private String baseResourceKey;
   private boolean onBaseResourceChildren = false; // only if getBaseResourceKey is set
-  private String[] resourceScopes = EMPTY;
-  private String[] resourceQualifiers = EMPTY;
-  private String[] resourceLanguages = EMPTY;
+  private List<String> resourceScopes = Collections.emptyList();
+  private List<String> resourceQualifiers = Collections.emptyList();
+  private List<String> resourceLanguages = Collections.emptyList();
   private String resourceKeyRegexp;
   private String resourceName;
   private Date fromDate = null, toDate = null;
@@ -69,33 +69,18 @@ public class MeasureFilter {
     return onBaseResourceChildren;
   }
 
-  public MeasureFilter setResourceScopes(String... l) {
-    this.resourceScopes = (l != null ? l : EMPTY);
+  public MeasureFilter setResourceScopes(@Nullable List<String> list) {
+    this.resourceScopes = sanitize(list);
     return this;
   }
 
-  public MeasureFilter setResourceQualifiers(String... l) {
-    this.resourceQualifiers = l;
+  public MeasureFilter setResourceQualifiers(@Nullable List<String> list) {
+    this.resourceQualifiers = sanitize(list);
     return this;
   }
 
-  public MeasureFilter setResourceLanguages(String... l) {
-    this.resourceLanguages = (l != null ? l : EMPTY);
-    return this;
-  }
-
-  public MeasureFilter setResourceScopes(@Nullable List<String> l) {
-    this.resourceScopes = (l != null ? l.toArray(new String[l.size()]) : EMPTY);
-    return this;
-  }
-
-  public MeasureFilter setResourceQualifiers(@Nullable List<String> l) {
-    this.resourceQualifiers = (l != null ? l.toArray(new String[l.size()]) : EMPTY);
-    return this;
-  }
-
-  public MeasureFilter setResourceLanguages(@Nullable List<String> l) {
-    this.resourceLanguages = (l != null ? l.toArray(new String[l.size()]) : EMPTY);
+  public MeasureFilter setResourceLanguages(@Nullable List<String> list) {
+    this.resourceLanguages = sanitize(list);
     return this;
   }
 
@@ -170,15 +155,15 @@ public class MeasureFilter {
     return toDate;
   }
 
-  public String[] getResourceScopes() {
+  public List<String> getResourceScopes() {
     return resourceScopes;
   }
 
-  public String[] getResourceQualifiers() {
+  public List<String> getResourceQualifiers() {
     return resourceQualifiers;
   }
 
-  public String[] getResourceLanguages() {
+  public List<String> getResourceLanguages() {
     return resourceLanguages;
   }
 
@@ -190,56 +175,21 @@ public class MeasureFilter {
     return sort;
   }
 
-  public static MeasureFilter create(Map<String, String> properties) {
-    MeasureFilter filter = new MeasureFilter();
-    filter.setBaseResourceKey(properties.get("base"));
-    filter.setResourceScopes(toArray(properties.get("scopes")));
-    filter.setResourceQualifiers(toArray(properties.get("qualifiers")));
-    filter.setResourceLanguages(toArray(properties.get("languages")));
-    if (properties.containsKey("onBaseChildren")) {
-      filter.setOnBaseResourceChildren(Boolean.valueOf(properties.get("onBaseChildren")));
-    }
-    filter.setResourceName(properties.get("nameRegexp"));
-    filter.setResourceKeyRegexp(properties.get("keyRegexp"));
-    if (properties.containsKey("fromDate")) {
-      filter.setFromDate(toDate(properties.get("fromDate")));
-    } else if (properties.containsKey("afterDays")) {
-      filter.setFromDate(toDays(properties.get("afterDays")));
-    }
-    if (properties.containsKey("toDate")) {
-      filter.setToDate(toDate(properties.get("toDate")));
-    } else if (properties.containsKey("beforeDays")) {
-      filter.setToDate(toDays(properties.get("beforeDays")));
-    }
-
-    if (properties.containsKey("favourites")) {
-      filter.setUserFavourites(Boolean.valueOf(properties.get("favourites")));
-    }
-    return filter;
+  @VisibleForTesting
+  static List<String> sanitize(@Nullable List<String> list) {
+    return isBlank(list) ? Collections.<String>emptyList() : list;
   }
 
-  private static String[] toArray(@Nullable String s) {
-    if (s == null) {
-      return EMPTY;
+  private static boolean isBlank(@Nullable List<String> list) {
+    boolean blank = false;
+    if (list == null || list.isEmpty() || (list.size() == 1 && Strings.isNullOrEmpty(list.get(0)))) {
+      blank = true;
     }
-    return StringUtils.split(s, ",");
+    return blank;
   }
 
-  private static Date toDate(@Nullable String date) {
-    if (date != null) {
-      return DateUtils.parseDate(date);
-    }
-    return null;
+  @Override
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE);
   }
-
-  private static Date toDays(@Nullable String s) {
-    if (s != null) {
-      int days = Integer.valueOf(s);
-      Date date = org.apache.commons.lang.time.DateUtils.truncate(new Date(), Calendar.DATE);
-      date = org.apache.commons.lang.time.DateUtils.addDays(date, -days);
-      return date;
-    }
-    return null;
-  }
-
 }
index b97ec5f6143cb9f9c8f4bcdc1d2327a64571165b..f991b2f270f6f1cc5851aa0da5137748b0235b5a 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.core.measure;
 
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
 import org.sonar.api.measures.Metric;
 
 public class MeasureFilterCondition {
@@ -67,4 +69,9 @@ public class MeasureFilterCondition {
     sql.append(metric.getId());
     sql.append(" AND ").append(valueColumn()).append(operator).append(value);
   }
+
+  @Override
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE);
+  }
 }
index fcebe106ea9b0e4a076ab3ab4e0a712db7e161fa..80c3627e881a3021b6309f1acabc5877e4c46af0 100644 (file)
@@ -20,6 +20,9 @@
 package org.sonar.core.measure;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang.SystemUtils;
 import org.json.simple.parser.ParseException;
 import org.slf4j.Logger;
@@ -35,25 +38,27 @@ public class MeasureFilterEngine implements ServerComponent {
   private static final Logger FILTER_LOG = LoggerFactory.getLogger("org.sonar.MEASURE_FILTER");
 
   private final MeasureFilterDecoder decoder;
+  private final MeasureFilterFactory factory;
   private final MeasureFilterExecutor executor;
 
-  public MeasureFilterEngine(MeasureFilterDecoder decoder, MeasureFilterExecutor executor) {
+  public MeasureFilterEngine(MeasureFilterDecoder decoder, MeasureFilterFactory factory, MeasureFilterExecutor executor) {
     this.decoder = decoder;
     this.executor = executor;
+    this.factory = factory;
   }
 
   public List<MeasureFilterRow> execute(String filterJson, @Nullable Long userId) throws ParseException {
     return execute(filterJson, userId, FILTER_LOG);
   }
 
-  public List<MeasureFilterRow> execute2(Map<String, String> filterMap, @Nullable Long userId) throws ParseException {
+  public List<MeasureFilterRow> execute2(Map<String, Object> filterMap, @Nullable Long userId) throws ParseException {
     Logger logger = FILTER_LOG;
     MeasureFilterContext context = new MeasureFilterContext();
-    context.setJson(filterMap.toString());
+    context.setJson(Joiner.on("|").withKeyValueSeparator("=").join(filterMap));
     context.setUserId(userId);
     try {
       long start = System.currentTimeMillis();
-      MeasureFilter filter = MeasureFilter.create(filterMap);
+      MeasureFilter filter = factory.create(filterMap);
       List<MeasureFilterRow> rows = executor.execute(filter, context);
       log(context, rows, (System.currentTimeMillis() - start), logger);
       return rows;
diff --git a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterFactory.java b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterFactory.java
new file mode 100644 (file)
index 0000000..8908226
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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 org.apache.commons.lang.StringUtils;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.utils.DateUtils;
+
+import javax.annotation.Nullable;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+public class MeasureFilterFactory implements ServerComponent {
+
+  private MetricFinder metricFinder;
+
+  public MeasureFilterFactory(MetricFinder metricFinder) {
+    this.metricFinder = metricFinder;
+  }
+
+  public MeasureFilter create(Map<String, Object> properties) {
+    MeasureFilter filter = new MeasureFilter();
+    filter.setBaseResourceKey((String)properties.get("base"));
+    filter.setResourceScopes((List<String>)properties.get("scopes"));
+    filter.setResourceQualifiers((List<String>)(properties.get("qualifiers")));
+    filter.setResourceLanguages((List<String>)(properties.get("languages")));
+    if (properties.containsKey("onBaseChildren")) {
+      filter.setOnBaseResourceChildren(Boolean.valueOf((String)properties.get("onBaseChildren")));
+    }
+    filter.setResourceName((String)properties.get("nameRegexp"));
+    filter.setResourceKeyRegexp((String)properties.get("keyRegexp"));
+    if (properties.containsKey("fromDate")) {
+      filter.setFromDate(toDate((String)properties.get("fromDate")));
+    } else if (properties.containsKey("afterDays")) {
+      filter.setFromDate(toDays((String)properties.get("afterDays")));
+    }
+    if (properties.containsKey("toDate")) {
+      filter.setToDate(toDate((String)properties.get("toDate")));
+    } else if (properties.containsKey("beforeDays")) {
+      filter.setToDate(toDays((String)properties.get("beforeDays")));
+    }
+
+    if (properties.containsKey("favourites")) {
+      filter.setUserFavourites(Boolean.valueOf((String)properties.get("favourites")));
+    }
+    if (properties.containsKey("asc")) {
+      filter.setSortAsc(Boolean.valueOf((String)properties.get("asc")));
+    }
+    String s = (String)properties.get("sort");
+    if (s != null) {
+      if (StringUtils.startsWith(s, "metric:")) {
+        filter.setSortOnMetric(metricFinder.findByKey(StringUtils.substringAfter(s, "metric:")));
+      } else {
+        filter.setSortOn(MeasureFilterSort.Field.valueOf(s.toUpperCase()));
+      }
+    }
+//    if (map.containsKey("sortPeriod")) {
+//      filter.setSortOnPeriod(((Long) map.get("sortPeriod")).intValue());
+//    }
+    return filter;
+  }
+
+  private static Date toDate(@Nullable String date) {
+    if (date != null) {
+      return DateUtils.parseDate(date);
+    }
+    return null;
+  }
+
+  private static Date toDays(@Nullable String s) {
+    if (s != null) {
+      int days = Integer.valueOf(s);
+      Date date = org.apache.commons.lang.time.DateUtils.truncate(new Date(), Calendar.DATE);
+      date = org.apache.commons.lang.time.DateUtils.addDays(date, -days);
+      return date;
+    }
+    return null;
+  }
+
+}
index bb41a9c77ed324c5439f5a0085ad0d55704f3437..e1ec30a4291fdacedb37d834b65be1ce0cbd6e8f 100644 (file)
@@ -149,15 +149,15 @@ class MeasureFilterSql {
     if (context.getBaseSnapshot() == null) {
       sql.append(" AND p.copy_resource_id IS NULL ");
     }
-    if (filter.getResourceQualifiers().length > 0) {
+    if (!filter.getResourceQualifiers().isEmpty()) {
       sql.append(" AND s.qualifier IN ");
       appendInStatement(filter.getResourceQualifiers(), sql);
     }
-    if (filter.getResourceScopes().length > 0) {
+    if (!filter.getResourceScopes().isEmpty()) {
       sql.append(" AND s.scope IN ");
       appendInStatement(filter.getResourceScopes(), sql);
     }
-    if (filter.getResourceLanguages().length > 0) {
+    if (!filter.getResourceLanguages().isEmpty()) {
       sql.append(" AND p.language IN ");
       appendInStatement(filter.getResourceLanguages(), sql);
     }
@@ -216,7 +216,7 @@ class MeasureFilterSql {
       sql.append(" AND s.project_id IN (SELECT rindex.resource_id FROM resource_index rindex WHERE rindex.kee like '");
       sql.append(StringEscapeUtils.escapeSql(StringUtils.lowerCase(filter.getResourceName())));
       sql.append("%'");
-      if (filter.getResourceQualifiers().length > 0) {
+      if (!filter.getResourceQualifiers().isEmpty()) {
         sql.append(" AND rindex.qualifier IN ");
         appendInStatement(filter.getResourceQualifiers(), sql);
       }
@@ -258,7 +258,7 @@ class MeasureFilterSql {
   }
 
 
-  private static void appendInStatement(String[] values, StringBuilder to) {
+  private static void appendInStatement(List<String> values, StringBuilder to) {
     to.append(" ('");
     to.append(StringUtils.join(values, "','"));
     to.append("') ");
index 2b08db5c968a50d7853edb89670017244aaae24b..01a67868dce9bcb1c552cffa30914b9eac5eb699 100644 (file)
@@ -44,7 +44,7 @@ public class MeasureFilterEngineTest {
     Logger logger = mock(Logger.class);
     when(logger.isDebugEnabled()).thenReturn(true);
 
-    MeasureFilterEngine engine = new MeasureFilterEngine(decoder, executor);
+    MeasureFilterEngine engine = new MeasureFilterEngine(decoder, null, executor);
 
     final long userId = 50L;
     engine.execute("{}", userId, logger);
@@ -69,7 +69,7 @@ public class MeasureFilterEngineTest {
     when(decoder.decode("<xml>")).thenThrow(new ParseException(0));
     MeasureFilterExecutor executor = mock(MeasureFilterExecutor.class);
 
-    MeasureFilterEngine engine = new MeasureFilterEngine(decoder, executor);
+    MeasureFilterEngine engine = new MeasureFilterEngine(decoder, null, executor);
     engine.execute("<xml>", 50L);
   }
 }
index 5d3c01d107cb3981f9f6d47e292b54cd30f9aa8e..d71a364c23867a4cae48f79ccabe5c02073231dc 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.SnapshotDto;
 
 import java.sql.SQLException;
+import java.util.Arrays;
 import java.util.List;
 
 import static org.fest.assertions.Assertions.assertThat;
@@ -64,7 +65,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
   @Test
   public void filter_is_not_valid_if_missing_base_snapshot() {
     MeasureFilterContext context = new MeasureFilterContext();
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setOnBaseResourceChildren(true);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setOnBaseResourceChildren(true);
     assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();
 
     context.setBaseSnapshot(new SnapshotDto().setId(123L));
@@ -88,7 +89,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
   @Test
   public void filter_is_not_valid_if_anonymous_favourites() {
     MeasureFilterContext context = new MeasureFilterContext();
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setUserFavourites(true);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setUserFavourites(true);
     assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();
 
     context.setUserId(123L);
@@ -97,7 +98,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void projects_without_measure_conditions() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.LANGUAGE);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setSortOn(MeasureFilterSort.Field.LANGUAGE);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(2);
@@ -107,7 +108,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void test_default_sort() {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA");
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA"));
 
     assertThat(filter.sort().isAsc()).isTrue();
     assertThat(filter.sort().field()).isEqualTo(MeasureFilterSort.Field.NAME);
@@ -116,7 +117,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_ascending_resource_name() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortAsc(true);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortAsc(true);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // Big -> Tiny
@@ -127,7 +128,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_ascending_resource_key() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortAsc(true).setSortOn(MeasureFilterSort.Field.KEY);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortAsc(true).setSortOn(MeasureFilterSort.Field.KEY);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // Big -> Tiny
@@ -138,7 +139,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_ascending_resource_version() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortAsc(true).setSortOn(MeasureFilterSort.Field.VERSION);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setSortAsc(true).setSortOn(MeasureFilterSort.Field.VERSION);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // Java Project 1.0 then Php Project 3.0
@@ -149,7 +150,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_descending_resource_name() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortAsc(false);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortAsc(false);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // Tiny -> Big
@@ -160,7 +161,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_ascending_text_measure() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_PROFILE);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setSortOnMetric(METRIC_PROFILE);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(2);
@@ -170,7 +171,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_descending_text_measure() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_PROFILE).setSortAsc(false);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setSortOnMetric(METRIC_PROFILE).setSortAsc(false);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(2);
@@ -181,7 +182,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
   @Test
   public void sort_by_missing_text_measure() throws SQLException {
     // the metric 'profile' is not set on files
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_PROFILE);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortOnMetric(METRIC_PROFILE);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(2);//2 files randomly sorted
@@ -189,7 +190,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_ascending_numeric_measure() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_LINES);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortOnMetric(METRIC_LINES);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // Tiny -> Big
@@ -200,7 +201,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_descending_numeric_measure() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_LINES).setSortAsc(false);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortOnMetric(METRIC_LINES).setSortAsc(false);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // Big -> Tiny
@@ -212,7 +213,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("CLA").setSortOnMetric(METRIC_COVERAGE);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setSortOnMetric(METRIC_COVERAGE);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // 2 files, random order
@@ -221,7 +222,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_ascending_variation() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_LINES).setSortOnPeriod(5);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setSortOnMetric(METRIC_LINES).setSortOnPeriod(5);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(2);
@@ -231,7 +232,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_descending_variation() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK")
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK"))
       .setSortOnMetric(METRIC_LINES).setSortOnPeriod(5).setSortAsc(false);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
@@ -242,7 +243,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_ascending_date() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.DATE);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setSortOn(MeasureFilterSort.Field.DATE);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     verifyJavaProject(rows.get(0));// 2008
@@ -251,7 +252,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void sort_by_descending_date() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.DATE).setSortAsc(false);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setSortOn(MeasureFilterSort.Field.DATE).setSortAsc(false);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     verifyPhpProject(rows.get(0));// 2012
@@ -260,7 +261,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void condition_on_numeric_measure() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA")
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA"))
       .setSortOnMetric(METRIC_LINES)
       .addCondition(new MeasureFilterCondition(METRIC_LINES, ">", 200));
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
@@ -271,7 +272,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void condition_on_measure_variation() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK")
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK"))
       .setSortOnMetric(METRIC_LINES)
       .addCondition(new MeasureFilterCondition(METRIC_LINES, ">", 1000).setPeriod(5));
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
@@ -282,7 +283,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void multiple_conditions_on_numeric_measures() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA")
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA"))
       .setSortOnMetric(METRIC_LINES)
       .addCondition(new MeasureFilterCondition(METRIC_LINES, ">", 2))
       .addCondition(new MeasureFilterCondition(METRIC_LINES, "<=", 50));
@@ -294,7 +295,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_language() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceLanguages("java", "cobol");
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setResourceLanguages(Arrays.asList("java", "cobol"));
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(1);
@@ -303,7 +304,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_min_date() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setFromDate(DateUtils.parseDate("2012-12-13"));
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setFromDate(DateUtils.parseDate("2012-12-13"));
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     // php has been analyzed in 2012-12-13, whereas java project has been analyzed in 2008
@@ -313,7 +314,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_range_of_dates() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK")
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK"))
       .setFromDate(DateUtils.parseDate("2007-01-01"))
       .setToDate(DateUtils.parseDate("2010-01-01"));
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
@@ -325,7 +326,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_resource_name() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceName("PHP Proj");
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setResourceName("PHP Proj");
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(1);
@@ -334,7 +335,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_resource_key_star_regexp() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceKeyRegexp("java*");
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setResourceKeyRegexp("java*");
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(1);
@@ -343,7 +344,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_resource_key_exclamation_mark() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceKeyRegexp("JaV?_proje*");
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK")).setResourceKeyRegexp("JaV?_proje*");
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
     assertThat(rows).hasSize(1);
     verifyJavaProject(rows.get(0));
@@ -351,7 +352,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_base_resource() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setBaseResourceKey("java_project");
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("CLA")).setBaseResourceKey("java_project");
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).hasSize(2);
@@ -371,7 +372,7 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
 
   @Test
   public void filter_by_parent_without_children() throws SQLException {
-    MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK", "PAC", "CLA").setBaseResourceKey("java_project:org.sonar.foo.Big").setOnBaseResourceChildren(true);
+    MeasureFilter filter = new MeasureFilter().setResourceQualifiers(Arrays.asList("TRK", "PAC", "CLA")).setBaseResourceKey("java_project:org.sonar.foo.Big").setOnBaseResourceChildren(true);
     List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());
 
     assertThat(rows).isEmpty();
diff --git a/sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterTest.java b/sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterTest.java
new file mode 100644 (file)
index 0000000..74f30fa
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 com.google.common.collect.Lists;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class MeasureFilterTest {
+  @Test
+  public void should_sanitize_list() {
+    assertThat(MeasureFilter.sanitize(null)).isEmpty();
+    assertThat(MeasureFilter.sanitize(Lists.<String>newArrayList())).isEmpty();
+    assertThat(MeasureFilter.sanitize(Arrays.asList(""))).isEmpty();
+    assertThat(MeasureFilter.sanitize(Lists.newArrayList("TRK"))).containsExactly("TRK");
+    assertThat(MeasureFilter.sanitize(Lists.newArrayList("TRK", "BRC"))).containsExactly("TRK", "BRC");
+  }
+}
index 956d72cfb9507bffec1781e36a89fbd929b0a0a4..79d5345884fc53bdbc3ba0ff9bf5d0f030528ea0 100644 (file)
@@ -43,6 +43,7 @@ import org.sonar.core.i18n.RuleI18nManager;
 import org.sonar.core.measure.MeasureFilterDecoder;
 import org.sonar.core.measure.MeasureFilterEngine;
 import org.sonar.core.measure.MeasureFilterExecutor;
+import org.sonar.core.measure.MeasureFilterFactory;
 import org.sonar.core.metric.DefaultMetricFinder;
 import org.sonar.core.notification.DefaultNotificationManager;
 import org.sonar.core.persistence.DaoUtils;
@@ -241,6 +242,7 @@ public final class Platform {
     servicesContainer.addSingleton(SettingsChangeNotifier.class);
     servicesContainer.addSingleton(PageDecorations.class);
     servicesContainer.addSingleton(MeasureFilterDecoder.class);
+    servicesContainer.addSingleton(MeasureFilterFactory.class);
     servicesContainer.addSingleton(MeasureFilterExecutor.class);
     servicesContainer.addSingleton(MeasureFilterEngine.class);
     servicesContainer.addSingleton(DryRunDatabaseFactory.class);
index cbedd251521afc4a68910036548a3061b376da6c..e362cbb6bfb6f918d7240095f8112572c6f5f480 100644 (file)
@@ -102,7 +102,7 @@ public final class JRubyFacade {
     return get(MeasureFilterEngine.class).execute(json, userId);
   }
 
-  public List<MeasureFilterRow> executeMeasureFilter2(Map<String,String> map, @Nullable Long userId) throws ParseException {
+  public List<MeasureFilterRow> executeMeasureFilter2(Map<String,Object> map, @Nullable Long userId) throws ParseException {
     return get(MeasureFilterEngine.class).execute2(map, userId);
   }
 
index ea8ebd54e8e6c0bdf897eff30359baadbd5fad14..a529ac6d2737b0c04eebd0926bde6923fc1b5d82 100644 (file)
@@ -30,7 +30,7 @@ class FavouritesController < ApplicationController
     else
       current_user.add_favourite(favourite_id)
       @style='fav'
-      @tooltip='Click to delete from favourites'
+      @tooltip='Click to remove from favourites'
     end
 
     star_id=params[:elt]
@@ -40,7 +40,7 @@ class FavouritesController < ApplicationController
       page.element.addClassName(star_id, @style)
       page.element.writeAttribute(star_id, 'alt', @tooltip)
       page.element.writeAttribute(star_id, 'title', @tooltip)
-    end 
+    end
   end
 
 end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/measures_helper.rb
new file mode 100644 (file)
index 0000000..6d4beff
--- /dev/null
@@ -0,0 +1,38 @@
+#
+# Sonar, entreprise quality control 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
+#
+module MeasuresHelper
+
+  def list_column_title(filter, column, url_params)
+    if column.sort?
+      html = link_to(h(column.display_name), url_params.merge({:controller => 'measures', :action => 'search', :asc => (!filter.sort_asc?).to_s, :sort => column.key}))
+    else
+      html=h(column.display_name)
+    end
+    #if column.variation
+    #  html="<img src='#{ApplicationController.root_context}/images/trend-up.png'></img> #{html}"
+    #end
+
+    if filter.sort_key==column.key
+      html << (filter.sort_asc? ? image_tag("asc12.png") : image_tag("desc12.png"))
+    end
+    "<th class='#{column.align}'>#{html}</th>"
+  end
+
+end
index f8c4c8cc55d70d88d7c3edd4dacf1ce8dbf29a02..fd71d2aa75291bbe263d0ec3ab8ff116bc81c321 100644 (file)
@@ -44,23 +44,17 @@ class MeasureFilter
 
   # Column to be displayed
   class Column
-    attr_reader :key
+    attr_reader :key, :metric
 
     def initialize(key)
       @key = key
-    end
-
-    def metric
-      @metric ||=
-        begin
-          metric_key = @key.split(':')[1]
-          Metric.by_key(metric_key) if metric_key
-        end
+      metric_key = @key.split(':')[1]
+      @metric = Metric.by_key(metric_key) if metric_key
     end
 
     def display_name
-      if metric
-        Api::Utils.message("metric.#{metric.key}.name", :default => metric.short_name)
+      if @metric
+        Api::Utils.message("metric.#{@metric.key}.name", :default => @metric.short_name)
       else
         Api::Utils.message("filters.col.#{@key}", :default => @key)
       end
@@ -146,7 +140,6 @@ class MeasureFilter
     user = options[:user]
     rows=Api::Utils.java_facade.executeMeasureFilter2(@criteria, (user ? user.id : nil))
     snapshot_ids = filter_authorized_snapshot_ids(rows, controller)
-    snapshot_ids = paginate_snapshot_ids(snapshot_ids)
     init_data(snapshot_ids)
 
     self
@@ -161,9 +154,7 @@ class MeasureFilter
   end
 
   def set_criteria_default_value(key, value)
-    if !@criteria.has_key?(key.to_sym)
-      set_criteria_value(key, value)
-    end
+    set_criteria_value(key, value) unless @criteria.has_key?(key.to_sym)
   end
 
   private
@@ -193,12 +184,8 @@ class MeasureFilter
     authorized_project_ids = controller.select_authorized(:user, project_ids)
     snapshot_ids = rows.map { |row| row.getSnapshotId() if authorized_project_ids.include?(row.getResourceRootId()) }.compact
     @security_exclusions = (snapshot_ids.size<rows.size)
-    snapshot_ids
-  end
-
-  def paginate_snapshot_ids(snapshot_ids)
     @pagination.count = snapshot_ids.size
-    snapshot_ids[@pagination.offset .. (@pagination.offset+@pagination.limit)]
+        snapshot_ids[@pagination.offset .. (@pagination.offset+@pagination.limit)]
   end
 
   def init_data(snapshot_ids)
@@ -209,7 +196,11 @@ class MeasureFilter
       snapshots.each do |snapshot|
         data = Data.new(snapshot)
         data_by_snapshot_id[snapshot.id] = data
-        @data << data
+      end
+
+      # @data must be in the same order than the snapshot ids
+      snapshot_ids.each do |sid|
+        @data << data_by_snapshot_id[sid]
       end
 
       metric_ids = @columns.map { |column| column.metric }.compact.uniq.map { |metric| metric.id }
index fd24b72fa6032b1763e1cbb3960be134fc848a32..15648dbf9e8c6599cfd21fc57aefc2e30844ebd2 100644 (file)
@@ -1,20 +1,21 @@
+<%
+   display_favourites = logged_in?
+   colspan = @filter.columns.size
+   colspan += 1 if display_favourites
+%>
 <table class="data">
   <thead>
   <tr>
+    <% if display_favourites %><th class="thin"></th><% end %>
     <% @filter.columns.each do |column| %>
-      <% if column.metric %>
-        <th class="right"><%= Api::Utils.message("metric.#{column.metric.key}.name", :default => column.metric.short_name) -%></th>
-      <% elsif column.key=='name' %>
-        <th class="left"><%= Api::Utils.message("filters.col.name") -%></th>
-      <% else %>
-        <th class="right"><%= Api::Utils.message("filters.col.#{column.key}", :default => column.key) -%></th>
-      <% end %>
+      <%= list_column_title(@filter, column, url_params) -%>
     <% end %>
   </tr>
   </thead>
   <tbody>
   <% @filter.data.each do |data| %>
     <tr class="<%= cycle 'even', 'odd' -%>">
+      <% if display_favourites %><td class="thin"><%= link_to_favourite(data.snapshot.resource) -%></td><% end %>
       <% @filter.columns.each do |column| %>
         <% if column.metric %>
           <td class="right">
       <% end %>
     </tr>
   <% end %>
+  <% if @filter.data.empty? %>
+    <tr class="even">
+      <td colspan="<%= colspan -%>"><%= message 'no_data' -%></td>
+    </tr>
+  <% end %>
   </tbody>
-  <%= render :partial => 'utils/tfoot_pagination', :locals => {:pagination => @filter.pagination, :colspan => @filter.columns.size} %>
+  <%= render :partial => 'utils/tfoot_pagination', :locals => {:pagination => @filter.pagination, :colspan => colspan} %>
 </table>
\ No newline at end of file
index fd1fc74d54c1a2ec1d65e2384401fdfe80fc77be..09750fc6be3ccde1c13d688e929ff3578f71a51c 100644 (file)
@@ -5,13 +5,22 @@
       width: 240px;
     }
 
+    #filter-form select {
+      width: 200px;
+      padding: 1px;
+    }
+
+    #filter-form select option {
+      padding: 1px 10px 1px 4px;
+    }
+
     #filter-result {
       padding-left: 250px;
     }
   </style>
 <% end %>
 
-<% url_options = params.reject { |k, v| v.empty? || k=='search' }.merge({:controller => 'measures', :action => 'search'}) %>
+<% url_params = params.reject { |k, v| v.nil? || v=='' || k.starts_with?('_') }.merge({:controller => 'measures', :action => 'search'}) %>
 
 <div id="measure-filter">
   <div id="filter-form">
       <table>
         <tbody>
         <tr>
-          <td>Base:</td>
-          <td><input type="text" name="base"></td>
+          <td>
+            Base:<br>
+            <input type="text" name="base">
+          </td>
+        </tr>
+        <tr>
+          <td>
+            Direct children:<br>
+            <%= check_box_tag 'onBaseChildren', 'true', params['onBaseChildren']=='true' -%>
+          </td>
         </tr>
         <tr>
-          <td>Qualifiers:</td>
           <td>
-            <select name="qualifiers">
-              <option value="">Any</option>
-              <option value="TRK">Project</option>
-              <option value="TRK,BRC">Project</option>
-              <option value="DIR,PAC">Directory/Package</option>
-              <option value="FIL,CLA">File</option>
-              <option value="UTS">Unit Test File</option>
+            Qualifiers:<br>
+            <%= select_tag 'qualifiers[]', options_for_select([['Any', ''], ['Project', 'TRK'], ['Sub-project', 'BRC'], ['Directory/Package', 'DIR']], params['qualifiers']||''), :size => 5, :multiple => true -%>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            Language:<br>
+            <select name="languages[]" multiple size="5">
+              <option value="" <%= 'selected' if params['languages[]'].blank? -%>>Any</option>
+              <% Api::Utils.languages.each do |language| %>
+                <option value="<%= language.key.parameterize -%>"><%= h language.name -%></option>
+              <% end %>
             </select>
           </td>
         </tr>
         <tr>
-          <td>Language:</td>
-          <td><input type="text" name="language"></td>
+          <td>
+            Name:<br>
+            <input type="text" name="nameRegexp"></td>
         </tr>
         <tr>
-          <td>Name:</td>
-          <td><input type="text" name="nameRegexp"></td>
+          <td>
+            Key:<br>
+            <input type="text" name="keyRegexp"></td>
         </tr>
         <tr>
-          <td>Key:</td>
-          <td><input type="text" name="keyRegexp"></td>
+          <td>
+            Favourites only:<br>
+            <%= check_box_tag 'favourites', 'true', params['favourites']=='true' %>
+          </td>
         </tr>
         <tr>
-          <td><input type="submit" name="search" value="Search"></td>
-          <td></td>
+          <td>
+            From date:<br>
+            <input type="text" name="fromDate" value="<%= params['fromDate'] -%>">
+          </td>
+        </tr>
+        <tr>
+          <td>
+            To date:<br>
+            <input type="text" name="toDate" value="<%= params['toDate'] -%>">
+          </td>
+        </tr>
+        <tr>
+          <td>
+            Before:<br>
+            <input type="text" name="beforeDays" value="<%= params['beforeDays'] -%>" size="4"> days
+          </td>
+        </tr>
+        <tr>
+          <td>After:<br>
+            <input type="text" name="afterDays" value="<%= params['afterDays'] -%>" size="4"> days
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <input type="submit" name="_search" value="Search">
+            <a href="<%= ApplicationController.root_context -%>/measures">Reset</a>
+          </td>
         </tr>
         </tbody>
       </table>
   </div>
   <% if @filter %>
     <div id="filter-result">
+      Display as:
       <% MeasureFilter::DISPLAYS.each do |display| %>
-        <%= link_to display.key, url_options.merge(:display => display.key) -%>
+        <%= link_to_if display.key!=@filter.display.key, display.key, url_params.merge(:display => display.key) -%>
       <% end %>
 
-      <%= render :partial => "measures/display_#{@filter.display.key}.html.erb" -%>
+      <%= render :partial => "measures/display_#{@filter.display.key}", :locals => {:url_params => url_params} -%>
       <p>
-        <% permalink = url_for(url_options) %>
+        <% permalink = url_for(url_params) %>
         <a href="<%= permalink -%>">Permalink</a>: <input type="text" value="<%= permalink -%>" size="100">
       </p>
+      <% if @filter.security_exclusions %>
+        <p class="notes"><%= message('results_not_display_due_to_security') -%></p>
+      <% end %>
+
     </div>
   <% end %>