]> source.dussan.org Git - sonarqube.git/commitdiff
Improve UX of measure filters
authorSimon Brandhof <simon.brandhof@gmail.com>
Tue, 18 Dec 2012 21:40:16 +0000 (22:40 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Tue, 18 Dec 2012 21:40:16 +0000 (22:40 +0100)
* two different actions for "Save" (an existing filter) and "Save as" (a new filter)
* replace the link "Edit" by a pencil icon on the left

12 files changed:
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-server/src/main/webapp/WEB-INF/app/controllers/measures_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/treemap_controller.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_list.rb
sonar-server/src/main/webapp/WEB-INF/app/models/measure_filter_display_treemap.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/_display_treemap.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_edit_form.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_save_as_form.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_save_form.html.erb [deleted file]
sonar-server/src/main/webapp/WEB-INF/app/views/measures/search.html.erb

index c980f85eb5d6a9c7d897d228f94611c89fd98fbb..787986ec9fd70a47c5c147a4fc17c1a87b94db7f 100644 (file)
@@ -110,6 +110,7 @@ review_verb=Review
 rule=Rule
 rules=Rules
 save=Save
+save_as=Save as
 search_verb=Search
 select_all=Select all
 select_verb=Select
@@ -386,7 +387,6 @@ session.flash_notice.logged_out=You have been logged out.
 # MEASURE FILTERS
 #
 #------------------------------------------------------------------------------
-measure_filter.close_and_save=Close & Save
 measure_filter.delete_column=Delete column
 measure_filter.no_filters=No filters
 measure_filter.display_as=Display as
@@ -449,6 +449,7 @@ measure_filter.treemap.change=Change Treemap
 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 391c759f859d681f49ada75ac58591bf9ff26bb2..79417be5417c6cce643b0226b8372f062640e501 100644 (file)
@@ -31,7 +31,7 @@ class MeasuresController < ApplicationController
     else
       @filter = MeasureFilter.new
     end
-    @filter.criteria=(params.merge({:controller => nil, :action => nil, :search => nil, :widget_id => nil}))
+    @filter.criteria=criteria_params
     @filter.enable_default_display
     @filter.execute(self, :user => current_user)
 
@@ -47,25 +47,31 @@ class MeasuresController < ApplicationController
 
     @filter = find_filter(params[:id])
     @filter.load_criteria_from_data
+    @filter.enable_default_display
 
     # criteria can be overridden
-    @filter.override_criteria(params.reject{|k,v| k=='controller' || k=='action' || k=='id'})
+    @filter.override_criteria(criteria_params)
+
+    @filter.execute(self, :user => current_user)
+    @unchanged = true
 
-    redirect_to @filter.criteria.merge({:controller => 'measures', :action => 'search', :id => params[:id]})
+    render :action => 'search'
   end
 
-  def save_form
+  # GET /measures/save_as_form?[id=<id>][&criteria]
+  def save_as_form
     if params[:id].present?
       @filter = find_filter(params[:id])
     else
       @filter = MeasureFilter.new
     end
-    @filter.criteria=(params)
+    @filter.criteria=(criteria_params)
     @filter.convert_criteria_to_data
-    render :partial => 'measures/save_form'
+    render :partial => 'measures/save_as_form'
   end
 
-  def save
+  # POST /measures/save_as?[id=<id>]&name=<name>[&parameters]
+  def save_as
     verify_post_request
     access_denied unless logged_in?
 
@@ -85,10 +91,25 @@ class MeasuresController < ApplicationController
       current_user.favourited_measure_filters<<@filter if add_to_favourites
       render :text => @filter.id.to_s, :status => 200
     else
-      render :partial => 'measures/save_form', :status => 400
+      render :partial => 'measures/save_as_form', :status => 400
     end
   end
 
+  # POST /measures/save?id=<id>&[criteria]
+  def save
+    verify_post_request
+    require_parameters :id
+    access_denied unless logged_in?
+
+    @filter = find_filter(params[:id])
+    @filter.criteria=criteria_params
+    @filter.convert_criteria_to_data
+    unless @filter.save
+      flash[:error]='Error'
+    end
+    redirect_to :action => 'filter', :id => @filter.id
+  end
+
   # GET /measures/manage
   def manage
     access_denied unless logged_in?
@@ -199,4 +220,8 @@ class MeasuresController < ApplicationController
     access_denied unless filter.shared || filter.owner?(current_user)
     filter
   end
+
+  def criteria_params
+    params.merge({:controller => nil, :action => nil, :search => nil, :widget_id => nil, :edit => nil})
+  end
 end
index a2db5e892e80f755ce6a424a40010e365ebbacec..5213d58262912227a56895ea38880ef272cee68d 100644 (file)
@@ -41,11 +41,11 @@ class TreemapController < ApplicationController
     resource = resource.permanent_resource
 
     filter = MeasureFilter.new
-    filter.set_criteria_value('baseId', resource.id)
-    filter.set_criteria_value('onBaseComponents', 'true')
-    filter.set_criteria_value('display', 'treemap')
-    filter.set_criteria_value('tmSize', size_metric.key) if size_metric
-    filter.set_criteria_value('tmColor', color_metric.key) if color_metric
+    filter.set_criteria_value(:baseId, resource.id)
+    filter.set_criteria_value(:onBaseComponents, 'true')
+    filter.set_criteria_value(:display, 'treemap')
+    filter.set_criteria_value(:tmSize, size_metric.key) if size_metric
+    filter.set_criteria_value(:tmColor, color_metric.key) if color_metric
     filter.execute(self, :user => current_user)
 
     render :text => filter.display.html
index 587b4eea749f80e99db83488f25e8b14d11ff8d4..ca208d907bcb555b9ef48752fee10a2336a1e118 100644 (file)
@@ -66,7 +66,6 @@ class MeasureFilter < ActiveRecord::Base
 
   attr_reader :pagination, :security_exclusions, :base_row, :rows, :display
 
-
   def sort_key
     criteria['sort']
   end
@@ -127,7 +126,8 @@ class MeasureFilter < ActiveRecord::Base
     @criteria ||= HashWithIndifferentAccess.new
     if key
       if value!=nil && value!='' && value!=['']
-        @criteria[key]=(value.kind_of?(Array) ? value : value.to_s)
+        value = (value.kind_of?(Array) ? value : value.to_s)
+        @criteria[key]=value
       else
         @criteria.delete(key)
       end
@@ -136,7 +136,17 @@ class MeasureFilter < ActiveRecord::Base
 
   # API used by Displays
   def set_criteria_default_value(key, value)
-    set_criteria_value(key, value) unless criteria.has_key?(key)
+    @criteria ||= HashWithIndifferentAccess.new
+    unless @criteria.has_key?(key)
+      if key
+        if value!=nil && value!='' && value!=['']
+          value = (value.kind_of?(Array) ? value : value.to_s)
+          @criteria[key]=value
+        else
+          @criteria.delete(key)
+        end
+      end
+    end
   end
 
   def load_criteria_from_data
index 20a64295ace9507b69e5a7b42ef2daf6cf8af67a..2a109537790497454eef5e9d096a1e32ce4107de 100644 (file)
@@ -20,6 +20,7 @@
 require 'set'
 class MeasureFilterDisplayList < MeasureFilterDisplay
   KEY = :list
+  MAX_PAGE_SIZE = 200
 
   class Column
     attr_reader :key, :metric, :period
@@ -85,9 +86,9 @@ class MeasureFilterDisplayList < MeasureFilterDisplay
     # default values
     filter.set_criteria_default_value(:cols, ['metric:alert_status', 'name', 'date', 'metric:ncloc', 'metric:violations', 'links'])
     filter.set_criteria_default_value(:sort, 'name')
-    filter.set_criteria_default_value(:asc, 'true')
-    filter.set_criteria_default_value(:pageSize, '30')
-    filter.set_criteria_value(:pageSize, [filter.criteria[:pageSize].to_i, 200].min)
+    filter.set_criteria_default_value(:asc, true)
+    filter.set_criteria_default_value(:pageSize, 30)
+    filter.set_criteria_value(:pageSize, MAX_PAGE_SIZE) if filter.criteria(:pageSize).to_i>MAX_PAGE_SIZE
 
     @columns = []
     metrics = []
index f4d70b014d9c3459a25c8de2ccfb7b8fc359d1f7..bbaf3e8b765ee71e9388566b464af04dfe6e4460 100644 (file)
@@ -22,7 +22,7 @@ class MeasureFilterDisplayTreemap < MeasureFilterDisplay
   include ActionView::Helpers::UrlHelper
 
   KEY = :treemap
-  PROPERTY_KEYS = Set.new([:tmSize, :tmColor])
+  PROPERTY_KEYS = Set.new([:tmSize, :tmColor, :tmHeight])
   MAX_RESULTS = 1000
   DEFAULT_HEIGHT_PERCENTS = 55
   attr_reader :id, :size, :size_metric, :color_metric, :height_percents
@@ -37,8 +37,6 @@ class MeasureFilterDisplayTreemap < MeasureFilterDisplay
     @filter.metrics=([@size_metric, @color_metric].compact)
     @id_count = 0
 
-    filter.set_criteria_value(:sort, "metric:#{@size_metric.key}") if @size_metric
-    filter.set_criteria_value(:asc, 'true')
     filter.set_criteria_value(:pageSize, MAX_RESULTS)
     filter.set_criteria_value(:page, 1)
   end
index 9df1f2374d86d993f99e8fe5cb62a2df4a2fa377..83654fa0081a4dc538d3f11f03226727c85d6a8b 100644 (file)
@@ -5,8 +5,10 @@
       $j('#measure_filter_foot<%= widget_id -%>_pages').hide();
       $j('#measure_filter_foot<%= widget_id -%>_loading').show();
 
-      var url = decodeURI('<%= url_for filter.criteria.merge(:controller => 'measures', :action => 'search', :page => nil, :sort => nil, :asc => nil, :search => nil, :widget_id => widget_id, :trailing_slash => true) -%>');
-      url += '&sort=' + sort + '&asc=' + asc + '&page=' + page;
+      filterCriteria['sort']=sort;
+      filterCriteria['asc']=asc;
+      filterCriteria['page']=page;
+      var url=baseUrl + '/measures/search/?' + $j.param(filterCriteria);
 
       <% if widget_id %>
       $j('#measure_filter_list<%= widget_id -%>').load(url);
    display_favourites = logged_in?
    colspan = filter.display.columns.size
    colspan += 1 if display_favourites
-   table_columns = []
-   table_columns << '' if display_favourites
-   table_columns.concat(filter.display.columns.map { |c| c.key })
    if edit_mode
      content_for :script do
 %>
     <script>
-      var cols = [<%= table_columns.map{|col| "'#{col}'"}.join(',')-%>];
-
+      var colOffset = <%= display_favourites ? 1 : 0 -%>;
       function leftCol(id) {
         var cell = $j('#measures-table tr td[index=' + id + ']');
         var columnIndex = cell.parent().children().index(cell);
-        if (columnIndex > 0) {
+        if (columnIndex-colOffset > 0) {
           moveCol(columnIndex - 1, columnIndex);
         }
       }
       function rightCol(id) {
+        var cols = filterCriteria['cols']||[];
         var cell = $j('#measures-table tr td[index=' + id + ']');
         var columnIndex = cell.parent().children().index(cell);
-        if (columnIndex < cols.length - 1) {
+
+        if (columnIndex-colOffset < cols.length - 1) {
           moveCol(columnIndex, columnIndex + 1);
         }
       }
       function moveCol(from, to) {
-        var temp = cols[from];
-        cols[from] = cols[to];
-        cols[to] = temp;
+        var cols = filterCriteria['cols']||[];
+        var temp = cols[from-colOffset];
+        cols[from-colOffset] = cols[to-colOffset];
+        cols[to-colOffset] = temp;
         $j('#measures-table thead tr').each(function () {
           var tr = $j(this);
           var td1 = tr.find('th:eq(' + from + ')');
         });
       }
       function deleteCol(id) {
+        var cols = filterCriteria['cols']||[];
         var cell = $j('#measures-table tr td[index=' + id + ']');
         var columnIndex = cell.parent().children().index(cell);
-        cols.splice(columnIndex, 1);
+        cols.splice(columnIndex-colOffset, 1);
         $j('#measures-table thead tr').each(function () {
           var tr = $j(this);
           var td1 = tr.find('th:eq(' + columnIndex + ')');
 
         });
         $j("#add-metric").on("click", function (e) {
+          var cols = filterCriteria['cols']||[];
           var columnKey = $j("#select-metric option:selected").val();
           var period = $j("#select-period option:selected").val();
           if (period.length > 0) {
             columnKey += ':' + period;
           }
           cols.push(columnKey);
-          window.location = removeUrlAttr(decodeURI(window.location.href), 'cols\\[\\]') + '&' + $j.map(cols,function (a) {
-            return a ? 'cols[]=' + a : null;
-          }).join('&');
+          filterCriteria['edit']='true';
+          window.location = baseUrl + '/measures/search/<%= filter.id -%>?' + $j.param(filterCriteria);
         });
         $j("#exit-edit").on("click", function (e) {
-          var url = removeUrlAttr(decodeURI(window.location.href), 'cols\\[\\]');
-          url = removeUrlAttr(url, 'edit');
-          url += '&' + $j.map(cols,function (a) {
-            return a ? 'cols[]=' + a : null;
-          }).join('&');
-          window.location = url;
+          delete filterCriteria['edit'];
+          window.location = baseUrl + '/measures/search/<%= filter.id -%>?' + $j.param(filterCriteria);
         });
       });
     </script>
       </td>
       <td class="right">
         <a href="#" class="button" id="exit-edit"><%= message 'close' -%></a>
-        <% if filter.owner?(current_user) %>
-          <a id="save-columns" href="<%= url_for filter.criteria.merge({:edit => nil, :action => 'save_form', :id => filter.id}) -%>" class="button open-modal"><%= message('measure_filter.close_and_save') -%></a>
-        <% end %>
       </td>
     </tr>
   </table>
index 91f47ceae5149106a30be9d51d6de506c3a7ec24..9715c75e69446411a423cddbe22cb5366bca232c 100644 (file)
@@ -47,9 +47,6 @@
       <td valign="bottom"><%= image_tag 'loading.gif', :id => "tm-loading-#{treemap_id}", :style => 'display:none' -%></td>
       <td class="right" valign="bottom">
         <a href="#" class="button" id="exit-edit"><%= message 'close' -%></a>
-        <% if filter.owner?(current_user) %>
-          <a id="save-columns" href="<%= url_for params.merge({:action => 'save_form', :id => filter.id}) -%>" class="button open-modal"><%= message('measure_filter.close_and_save') -%></a>
-        <% end %>
       </td>
     </tr>
   </table>
     <script>
       $j(document).ready(function () {
         $j("#update-treemap").on("click", function (e) {
-          var url = removeUrlAttr(decodeURI(window.location.href), 'tmSize');
-          url = removeUrlAttr(url, 'tmColor');
-          url += '&tmSize=' + $j('#select-tm-size').val();
+          filterCriteria['tmSize']=$j('#select-tm-size').val();
           var color = $j('#select-tm-color').val();
           if (color != null && color != '') {
-            url += '&tmColor=' + color;
+            filterCriteria['tmColor']=color;
           }
-          window.location = url;
+          filterCriteria['edit']=true;
+          window.location = baseUrl + '/measures/search/<%= filter.id -%>?' + $j.param(filterCriteria);
         });
         $j("#exit-edit").on("click", function (e) {
-          window.location = removeUrlAttr(decodeURI(window.location.href), 'edit');
+          delete filterCriteria['edit'];
+          window.location = baseUrl + '/measures/search/<%= filter.id -%>?' + $j.param(filterCriteria);
         });
       });
     </script>
@@ -80,7 +77,7 @@
 <% if filter.rows.empty? %>
   <p><%= message('no_data') -%></p>
 <% elsif filter.pagination.pages>1 %>
-  <p><%= message('measure_filter.errors.TOO_MANY_RESULTS') -%></p>
+  <p><%= message('measure_filter.error.TOO_MANY_RESULTS') -%></p>
 <% else %>
   <div id="tm-<%= treemap_id -%>" class="treemap width100">
     <%= filter.display.html -%>
index 530cfefd2e4a74a071e9df4c764376acc7b325e7..14af4202db54f0129911fee0013e6e1107d9fe99 100644 (file)
@@ -28,6 +28,8 @@
   </fieldset>
 </form>
 <script>
-  $j("#edit-filter-form").modalForm();
+  $j("#edit-filter-form").modalForm({success: function (data) {
+    window.location = baseUrl + '/measures/filter/' + data;
+  }});
   $j('#name').select();
 </script>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/measures/_save_as_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/measures/_save_as_form.html.erb
new file mode 100644 (file)
index 0000000..d834e3a
--- /dev/null
@@ -0,0 +1,37 @@
+<form id="save-as-filter-form" method="post" action="<%= ApplicationController.root_context -%>/measures/save_as">
+  <input type="hidden" name="id" value="<%= @filter.id -%>">
+  <input type="hidden" name="data" value="<%= u(@filter.data) -%>">
+  <fieldset>
+    <div class="form-head">
+      <h2>Save Filter</h2>
+    </div>
+
+    <div class="form-body">
+      <% @filter.errors.each do |attr, msg| %>
+        <p class="error"><%= h msg -%></p>
+      <% end %>
+      <div class="form-field">
+        <label for="name">Name <em class="mandatory">*</em></label>
+        <input id="name" name="name" type="text" size="50" maxlength="100" value="<%= h @filter.name -%>"/>
+      </div>
+      <div class="form-field">
+        <label for="description">Description</label>
+        <input id="description" name="description" type="text" size="50" maxlength="4000" value="<%= h @filter.description -%>"/>
+      </div>
+      <div class="form-field">
+        <label for="shared">Shared with all users</label>
+        <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if @filter.shared -%>/>
+      </div>
+    </div>
+    <div class="form-foot">
+      <input type="submit" value="<%= h message('save') -%>" id="save-as-submit"/>
+      <a href="#" onclick="return closeModalWindow()" id="save-as-cancel"><%= h message('cancel') -%></a>
+    </div>
+  </fieldset>
+</form>
+<script>
+  $j("#save-as-filter-form").modalForm({success:function (data) {
+    window.location = baseUrl + '/measures/filter/' + data;
+  }});
+  $j('#name').select();
+</script>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/measures/_save_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/measures/_save_form.html.erb
deleted file mode 100644 (file)
index f848acd..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<form id="save-filter-form" method="post" action="<%= ApplicationController.root_context -%>/measures/save">
-  <input type="hidden" name="id" value="<%= @filter.id -%>">
-  <input type="hidden" name="data" value="<%= u(@filter.data) -%>">
-  <fieldset>
-    <div class="form-head">
-      <h2>Save Filter</h2>
-    </div>
-
-    <div class="form-body">
-      <% @filter.errors.each do |attr, msg| %>
-        <p class="error"><%= h msg -%></p>
-      <% end %>
-      <div class="form-field">
-        <label for="name">Name <em class="mandatory">*</em></label>
-        <input id="name" name="name" type="text" size="50" maxlength="100" value="<%= h @filter.name -%>"/>
-      </div>
-      <div class="form-field">
-        <label for="description">Description</label>
-        <input id="description" name="description" type="text" size="50" maxlength="4000" value="<%= h @filter.description -%>"/>
-      </div>
-      <div class="form-field">
-        <label for="shared">Shared with all users</label>
-        <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if @filter.shared -%>/>
-      </div>
-    </div>
-    <div class="form-foot">
-      <input type="submit" value="<%= h message('save') -%>" id="save-submit"/>
-      <a href="#" onclick="return closeModalWindow()" id="save-cancel"><%= h message('cancel') -%></a>
-    </div>
-  </fieldset>
-</form>
-<script>
-  $j("#save-filter-form").modalForm({success:function (data) {
-    window.location = baseUrl + '/measures/filter/' + data;
-  }});
-  $j('#name').select();
-</script>
\ No newline at end of file
index 045cb0ac4d1b7f3cfa60a7d8ad814d857ba7260b..4e116fb07639b899087e390a94eab1173e616776 100644 (file)
@@ -3,7 +3,9 @@
     <%= render :partial => 'measures/sidebar' -%>
   </div>
 
-  <% if @filter %>
+  <% if @filter && @filter.display %>
+    <script>var filterCriteria = <%= @filter.criteria.to_json -%>;</script>
+
     <div class="page-split-right">
       <div id="content">
         <div class="line-block marginbottom10">
 
             <%
                if @filter.display
-                edit_mode = (params[:edit]=='true')
-                unless edit_mode
+                 edit_mode = (params[:edit]=='true')
+                 unless edit_mode
             %>
+                <li>
+                  <a id="change-display" href="<%= url_for @filter.criteria.merge({:action => 'search', :edit => true, :id => @filter.id}) -%>"><%= message("measure_filter.#{@filter.display.key}.change") -%></a>
+                </li>
+              <% end %>
               <li>
-                <a id="change-display" href="<%= url_for params.merge({:edit => true, :id => @filter.id}) -%>"><%= message("measure_filter.#{@filter.display.key}.change") -%></a>
+                <%= message 'measure_filter.display_as' -%>:
+                <% MeasureFilterDisplay.keys.each do |display_key| %>
+                  <%= link_to_if display_key!=@filter.display.key, message("measure_filter.display.#{display_key}"), @filter.criteria.merge(:action => 'search', :display => display_key, :id => @filter.id), :id => "display_as_#{display_key}" -%>
+                <% end %>
               </li>
             <% end %>
-            <li>
-              <%= message 'measure_filter.display_as' -%>:
-              <% MeasureFilterDisplay.keys.each do |display_key| %>
-                <%= 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>
+                <li><a id="copy" href="<%= url_for :action => 'copy_form', :id => @filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a></li>
               <% end %>
-              <% if @filter.owner?(current_user) %>
-                <li><a id="save" href="<%= url_for params.merge({:action => 'save_form', :id => @filter.id}) -%>" class="link-action open-modal"><%= message('save') -%></a></li>
+              <% if !defined?(@unchanged) && @filter.id && @filter.owner?(current_user)%>
+                <li>
+                  <%= link_to message('save'), params.merge({:action => 'save', :id => @filter.id}), :class => 'link-action', :id => 'save', :method => :post -%>
+                </li>
+              <% end %>
+              <% unless @filter.id %>
+                <li>
+                  <a id="save_as" href="<%= url_for params.merge({:action => 'save_as_form', :id => @filter.id}) -%>" class="link-action open-modal"><%= message('save_as') -%></a>
+                </li>
               <% end %>
             <% end %>
           </ul>
           <div class="page_title" id="filter-title">
-            <% if @filter.name.present? %>
+            <% if @filter.id && @filter.name.present? %>
               <span class="h3"><%= h @filter.name -%></span>
               <span class="note">
                 <% if !@filter.shared %>
@@ -52,6 +61,9 @@
               <% if @filter.description.present? %>
                 - <span class="note"><%= h @filter.description -%></span>
               <% end %>
+              <% if @filter.owner?(current_user) %>
+                &nbsp;<a href="<%= url_for :action => 'edit_form', :id => @filter.id -%>" class="open-modal" id="edit-filter"><%= image_tag 'pencil-small.png', :alt => message('edit') -%></a>
+              <% end %>
             <% end %>
           </div>
         </div>