Skip to content

Commit

Permalink
Merge commit 'core/user-segment-filter-changes' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
cykod committed Jul 25, 2010
2 parents 7fa44dc + 183c17f commit 00e8612
Show file tree
Hide file tree
Showing 20 changed files with 237 additions and 87 deletions.
1 change: 1 addition & 0 deletions app/controllers/members_controller.rb
Expand Up @@ -413,6 +413,7 @@ def builder_help
def builder
@segment = UserSegment.find_by_id params[:path][0] if params[:path]
@builder = @segment ? UserSegment::OperationBuilder.create_builder(@segment) : UserSegment::OperationBuilder.new(nil)
@segment = nil if params[:copy]
@filter = params[:filter]
@builder.build(UserSegment::OperationBuilder.get_prebuilt_filter(@filter)) if @filter

Expand Down
4 changes: 2 additions & 2 deletions app/models/end_user_cache_segment_field.rb
Expand Up @@ -3,10 +3,10 @@ class EndUserCacheSegmentField < UserSegment::FieldHandler

def self.user_segment_fields_handler_info
{
:name => 'User Cache Fields',
:name => 'User Query Fields',
:domain_model_class => EndUserCache
}
end

register_field :cache, UserSegment::CoreType::MatchType, :field => :data, :name => 'Cache', :search_only => true
register_field :cache, UserSegment::CoreType::MatchType, :field => :data, :name => 'Query', :search_only => true, :builder_name => 'Search for users with?'
end
8 changes: 4 additions & 4 deletions app/models/end_user_segment_field.rb
Expand Up @@ -11,10 +11,10 @@ def self.user_segment_fields_handler_info

register_field :email, UserSegment::CoreType::StringType, :name => 'Email', :sortable => true, :search_only => true
register_field :gender, EndUserSegmentType::GenderType, :name => 'Gender', :sortable => true
register_field :created, UserSegment::CoreType::DateTimeType, :field => :created_at, :name => 'Created', :sortable => true
register_field :registered, UserSegment::CoreType::BooleanType, :name => 'Registered', :sortable => true
register_field :activated, UserSegment::CoreType::BooleanType, :name => 'Activated', :sortable => true
register_field :user_level, UserSegment::CoreType::NumberType, :name => 'User Level', :sortable => true
register_field :created, UserSegment::CoreType::DateTimeType, :field => :created_at, :name => 'Created', :sortable => true, :builder_name => 'Created when?'
register_field :registered, UserSegment::CoreType::BooleanType, :name => 'Registered', :sortable => true, :builder_name => 'Show registered accounts?'
register_field :activated, UserSegment::CoreType::BooleanType, :name => 'Activated', :sortable => true, :builder_name => 'Show activated accounts?'
register_field :user_level, UserSegment::CoreType::SimpleNumberType, :name => 'User Level', :sortable => true
register_field :dob, UserSegment::CoreType::DateTimeType, :name => 'DOB', :sortable => true
register_field :last_name, UserSegment::CoreType::StringType, :name => 'Last Name', :sortable => true
register_field :first_name, UserSegment::CoreType::StringType, :name => 'First Name', :sortable => true
Expand Down
16 changes: 12 additions & 4 deletions app/models/user_segment/core_type.rb
@@ -1,7 +1,7 @@

class UserSegment::CoreType

@@datetime_format_options = ['second', 'seconds', 'minute', 'minutes', 'hour', 'hours', 'day', 'days', 'week', 'weeks', 'month', 'months', 'year', 'years']
@@datetime_format_options = [['Second/s', 'seconds'], ['Minute/s', 'minutes'], ['Hour/s', 'hours'], ['Day/s', 'days'], ['Week/s', 'weeks'], ['Month/s', 'months'], ['Year/s', 'years']]
def self.datetime_format_options
@@datetime_format_options
end
Expand Down Expand Up @@ -29,11 +29,19 @@ def self.between(cls, group_field, field, from, to)
end
end

@@number_type_operators = ['>', '>=', '=', '<=', '<']
@@number_type_operators = [['Greater than', '>'], ['Greater than or equal to', '>='], ['Equal to', '='], ['Less than or equal to', '<='], ['Lest than', '<']]
def self.number_type_operators
@@number_type_operators
end

class SimpleNumberType < UserSegment::FieldType
register_operation :is, [['Operator', :option, {:options => UserSegment::CoreType.number_type_operators}], ['Value', :integer]]

def self.is(cls, group_field, field, operator, value)
cls.scoped(:conditions => ["#{field} #{operator} ?", value])
end
end

class NumberType < UserSegment::FieldType
register_operation :is, [['Operator', :option, {:options => UserSegment::CoreType.number_type_operators}], ['Value', :integer]]

Expand Down Expand Up @@ -75,13 +83,13 @@ def self.count(cls, group_field, field, operator, value)
end

class StringType < UserSegment::FieldType
register_operation :like, [['String', :string]], :description => 'use % for wild card matches'
register_operation :like, [['String', :string]], :name => 'Contains', :description => 'use % for wild card matches'

def self.like(cls, group_field, field, string)
cls.scoped(:conditions => ["#{field} like ?", string])
end

register_operation :is, [['String', :string]], :description => 'exact match'
register_operation :is, [['String', :string]], :name => 'Matches', :description => 'exact match'

def self.is(cls, group_field, field, string)
cls.scoped(:conditions => ["#{field} = ?", string])
Expand Down
4 changes: 4 additions & 0 deletions app/models/user_segment/field.rb
Expand Up @@ -122,6 +122,10 @@ def complex_operation
self.operation_info[:complex] if self.operation_info
end

def builder_name
self.handler_class.user_segment_fields[self.field.to_sym][:builder_name] if self.handler_class && self.handler_class.user_segment_fields && self.handler_class.user_segment_fields[self.field.to_sym]
end

def type_class
@type_class ||= self.handler_class.user_segment_fields[self.field.to_sym][:type] if self.handler_class && self.handler_class.user_segment_fields && self.handler_class.user_segment_fields[self.field.to_sym]
end
Expand Down
3 changes: 3 additions & 0 deletions app/models/user_segment/field_handler.rb
Expand Up @@ -38,6 +38,8 @@ def self.has_field?(field)
#
# [:name]
# the display name of the field, by default humanizes the field
# [:builder_name]
# the name to use inside the filter builder
# [:field]
# the actual field in the model, by default uses field
# [:display_field]
Expand All @@ -61,6 +63,7 @@ def self.register_field(field, type, options={})

fields[field.to_sym] = options.merge(:type => type, :handler => self)
fields[field.to_sym][:name] ||= field.to_s.humanize
fields[field.to_sym][:builder_name] ||= fields[field.to_sym][:name]
fields[field.to_sym][:field] ||= field.to_sym
fields[field.to_sym][:display_field] ||= fields[field.to_sym][:field]

Expand Down
2 changes: 2 additions & 0 deletions app/models/user_segment/field_type.rb
Expand Up @@ -28,6 +28,8 @@ def self.has_operation?(operation)
# [:class]
# When the argument is type :model. It uses this class to determine the select options.
# Additional options
# [:name]
# The name of the operation used in the operation builder list of operation options for a field.
# [:description]
# A brief description used when creating the help
# [:complex]
Expand Down
19 changes: 12 additions & 7 deletions app/models/user_segment/operation_builder.rb
@@ -1,6 +1,6 @@

class UserSegment::OperationBuilder < HashModel
attributes :operator => nil, :field => nil, :operation => nil, :arguments => nil, :condition => nil, :parent => nil
attributes :operator => nil, :field => nil, :operation => nil, :arguments => nil, :condition => nil, :parent => nil, :index => 1, :previous_operator => nil

def strict?; true; end
def arguments; @arguments ||= []; end
Expand All @@ -10,7 +10,7 @@ def operator_options
end

def condition_options
[['', nil], ['Combined', 'and'], ['Or', 'or'], ['And', 'with']]
[['Select function', nil], ['Combined', 'and'], ['Or', 'or'], ['And', 'with']]
end

def validate
Expand All @@ -21,6 +21,7 @@ def validate

def build(opts={})
self.operator = opts[:operator]
self.previous_operator = opts[:previous_operator]
self.operator = nil if self.operator.blank?
self.field = opts[:field]
self.operation = opts[:operation]
Expand All @@ -41,7 +42,7 @@ def build(opts={})

if ! self.condition.blank?
parent = self.condition == 'and' ? self : nil
child = (opts[:child] || {}).merge(:parent => parent)
child = (opts[:child] || {}).merge(:parent => parent, :previous_operator => self.operator || self.previous_operator)
self.child_field.build(child)
self.user_segment_field.child = self.child_field.user_segment_field.to_h if self.condition == 'and'
end
Expand All @@ -68,7 +69,7 @@ def field_group_options
if self.parent.nil? || self.parent.user_segment_field.handler == handler
options = []
handler[:class].user_segment_fields.each do |field, values|
options << [values[:name], field.to_s] unless seen_options[field.to_s]
options << ['- ' + values[:name], field.to_s] unless seen_options[field.to_s]
seen_options[field.to_s] = 1
end
options.sort! { |a, b| a[0] <=> b[0] }
Expand All @@ -91,6 +92,10 @@ def already_complex
end
end

def field_builder_name
self.user_segment_field.builder_name if self.user_segment_field
end

def operation_options
return @operation_options if @operation_options
@operation_options = []
Expand Down Expand Up @@ -151,7 +156,7 @@ def to_expr
end

def child_field
@child_field ||= UserSegment::OperationBuilder.new nil
@child_field ||= UserSegment::OperationBuilder.new :index => self.index+1
end

def self.create_builder(user_segment)
Expand All @@ -162,13 +167,13 @@ def self.create_builder(user_segment)

def self.prebuilt_filters
[
['New registered users in the last week', {:field => 'registered', :operation => 'is', :argument0 => true, :condition => 'and', :child => {:field => 'created', :operation => 'since', :argument0 => 1, :argument1 => 'week'}}],
['New registered users in the last week', {:field => 'registered', :operation => 'is', :argument0 => true, :condition => 'and', :child => {:field => 'created', :operation => 'since', :argument0 => 1, :argument1 => 'weeks'}}],
['Users that have not logged in the last 7 days', {:operator => 'not', :field => 'user_action', :operation => 'is', :argument0 => '/editor/auth/login', :condition => 'and', :child => {:field => 'occurred', :operation => 'since', :argument0 => 7, :argument1 => 'days'}}]
]
end

def self.prebuilt_filters_options
[['Custom', 'custom']] + self.prebuilt_filters.collect { |filter| [filter[0], filter[0].gsub(/[^a-zA-Z0-9]/, '').downcase] }
self.prebuilt_filters.collect { |filter| [filter[0], filter[0].gsub(/[^a-zA-Z0-9]/, '').downcase] }
end

def self.get_prebuilt_filter(filter)
Expand Down
2 changes: 1 addition & 1 deletion app/views/members/_edit_segment_filter.html.erb
Expand Up @@ -5,7 +5,7 @@
<%= f.radio_buttons :order_direction, UserSegment.order_direction_select_options %>
<% if @segment.segment_type == 'filtered' -%>
<% f.custom_field 'Filter' do %>
<div><a href="javascript:void(0)" onclick='openWindow("<%= url_for(:action => "builder", :path => @segment.id) %>" ,"operationBuilder",460,500,"yes","yes");'><%= @segment.id ? 'Edit filter'.t : 'Create a filter'.t %></a></div>
<div><a href="javascript:void(0)" onclick='openWindow("<%= url_for(:action => "builder", :path => @segment.id) %>" ,"operationBuilder",550,500,"yes","yes");'><%= @segment.id ? 'Edit filter'.t : 'Create a filter'.t %></a></div>

<div>
<%= "<div style='padding: 2px 0 2px 0' class='error'>#{f.output_error_message('Filter', :segment_options_text)}</div>" if @segment.errors[:segment_options_text] %>
Expand Down
54 changes: 33 additions & 21 deletions app/views/members/_operation_form.html.erb
Expand Up @@ -21,36 +21,48 @@ OperationBuilder = {
}
</script>

<fieldset id="operation_builder" style="padding:10px 10px 20px 10px; margin:5px;">
<legend style="font-size:14px; font-weight:bold;">Rule Builder</legend>
<% cms_unstyled_form_for :builder, @builder, :html => {:id => 'builder', :onsubmit => 'OperationBuilder.appendExpression(); return false;', :class => "admin_form"} do |f| -%>
<ul style="list-style:none; color:#3B648C; font-weight: bold;">
<li style="display:inline;">
<%= 'I am'.t %>
(<%= f.check_boxes :operator, [['not', 'not']], :single => true, :separator => '' %>)
</li>

<li style="display:inline;">
<%= 'looking for'.t %>
<%= f.grouped_select :field, @builder.field_group_options, {}, :onchange => 'OperationBuilder.updateOperations();', :style => 'width:250px;' %>
</li>

<div id="operation" class="operation">

<table width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td class="heading"><%= @segment ? 'Edit the filter'.t : 'Create a filter' %></td>
<td class="heading" align="right"><a href="javascript:void(0)" onclick='openWindow("/website/members/builder_help" ,"operationBuilderHelper",750,600,"yes","yes");'>Filter Glossary ?</a></td>
</tr>
<tr><th colspan="2"><div style="padding:5px 0 5px 0;"><%= 'Condition #%s' / '1' %></div></th></tr>
<tr><td colspan="2" class="spacer"></td></tr>

<tr>
<td width="240">
<%= f.radio_buttons :operator, [['Show me accounts with', nil], ['Do not show me accounts with', 'not']], :label => '', :separator => '<br/><br/>' %>
<%
@last_operator_text = @builder.operator == 'not' ? 'Do not show me accounts with'.t : 'Show me accounts with'.t
%>
</td>
<td valign="middle">
<%= f.grouped_select :field, @builder.field_group_options, {}, :onchange => 'OperationBuilder.updateOperations();' %><br/><br/>
</td>
</tr>

<tbody id="operation" class="operation">
<% if @builder.operation_options.length > 0 -%>
<%= render :partial => 'operation_form_operation', :locals => {:builder => @builder, :form_id => 'builder'} %>
<% end -%>
</div>
</tbody>

<li id="expression">
<tr>
<td colspan="2" id="expression" style="padding: 0 15px 0 10px;">
<%= render :partial => 'operation_form_expression' %>
</li>
</td>
</tr>

<li style="padding-top:5px; width:400px; text-align:right;">
<tr>
<td colspan="2" align="right" style="padding: 10px;">
<%= f.cancel_submit_buttons 'Close', (@segment ? 'Update Filter' : 'Add to Filter'), {:onclick => 'window.close();'} %>
</li>
</td>
</tr>

<% end -%>
</table>

</fieldset>
<% end -%>
<%= observe_form 'builder', :frequency => 1, :function => 'OperationBuilder.updateExpression();' %>
12 changes: 3 additions & 9 deletions app/views/members/_operation_form_arguments.html.erb
@@ -1,11 +1,7 @@
<% cms_unstyled_fields_for form_id, builder do |f| %>
<table style="padding:5px;">
<% builder.operation_arguments.each_with_index do |arg, idx| -%>
<tr>
<td class="label" valign="baseline"><%= builder.operation_argument_names[idx] %></td>
<td class="data" valign="baseline">

<li>
<%
arg_options = builder.operation_argument_options[idx]
description = arg_options[:description]
Expand All @@ -20,9 +16,7 @@
<% else -%>
<%= f.text_field "argument#{idx}" %>
<% end -%>
</td>
</tr>
<%= "<tr><td>&nbsp;</td><td class='description'>#{description}</td></tr>" if description %>
<%= "<br/><span class='description'>#{description}</span>" if description %>
</li>
<% end -%>
</table>
<% end %>
18 changes: 12 additions & 6 deletions app/views/members/_operation_form_child.html.erb
@@ -1,15 +1,21 @@
<% cms_unstyled_fields_for form_id, builder do |f| -%>

<tr>
<td width="240">
<% if condition == 'with' -%>
<li>
<%= f.check_boxes :operator, [['not', 'not']], :single => true, :separator => '' %>
</li>
<%= f.radio_buttons :operator, [['Show me accounts with', nil], ['Do not show me accounts with', 'not']], :label => '', :separator => '<br/><br/>' %>
<br/>
<% else -%>
<span style="padding-left:10px;"><%= builder.previous_operator == 'not' ? 'Do not show me accounts with'.t : 'Show me accounts with'.t %></span>
<br/><br/><br/>
<% end -%>

<li>
<%= f.grouped_select :field, builder.field_group_options, {}, :onchange => 'OperationBuilder.updateOperations();', :style => 'width:250px;' %>
</li>
</td>
<td valign="middle">
<%= f.grouped_select :field, builder.field_group_options, {}, :onchange => 'OperationBuilder.updateOperations();' %><br/><br/><br/>
</td>
</tr>

<% if builder.operation_options.length > 0 -%>
<div class="operation">
Expand Down
2 changes: 1 addition & 1 deletion app/views/members/_operation_form_expression.html.erb
@@ -1,5 +1,5 @@
<% cms_unstyled_fields_for :builder, @builder do |f| -%>
<div style="padding:10px 0 2px 0;">Filter (readonly)</div>
<%= f.text_area_tag :expr, @builder.to_expr, :style => 'width:400px; height: 100px;', :readonly => true, :wrap => 'off' %>
<%= f.text_area_tag :expr, @builder.to_expr, :style => 'width:100%; height: 100px;', :readonly => true, :wrap => 'off' %>
<% end -%>

0 comments on commit 00e8612

Please sign in to comment.