app/controllers/application_controller/filter/expression.rb
module ApplicationController::Filter
EXP_TODAY = "Today".freeze
EXP_FROM = "FROM".freeze
EXP_IS = "IS".freeze
Expression = Struct.new(
:alias,
:expression,
:exp_available_tags,
:exp_available_fields,
:exp_cfield,
:exp_check,
:exp_ckey,
:exp_chosen_report,
:exp_chosen_search,
:exp_cvalue,
:exp_count,
:exp_field,
:exp_key,
:exp_last_loaded,
:exp_mode,
:exp_model,
:exp_orig_key,
:exp_regkey,
:exp_regval,
:exp_skey,
:exp_table,
:exp_tag,
:exp_token,
:exp_typ,
:exp_value,
:history,
:pre_qs_selected,
:use_mytags,
:selected,
:val1,
:val1_suffix,
:val2,
:val2_suffix,
:record_filter
) do
def initialize(*args)
super
self.history ||= ExpressionEditHistory.new
end
def drop_cache
@available_adv_searches = @tags_for_display_filters = nil
end
# fields on exp_model for check_all, check_any, and check_count operation
def exp_available_cfields
MiqExpression.miq_adv_search_lists(exp_model, :exp_available_finds).each_with_object([]) do |af, res|
next if af.last == exp_field
next unless af.last.split('-').first == exp_field.split('-').first
res.push([af.first.split(':').last, af.last])
end
end
def tags_for_display_filters
@tags_for_display_filters ||= MiqExpression.model_details(exp_model, :typ => 'tag', :include_model => true, :include_my_tags => use_mytags, :userid => User.current_user.userid)
end
def available_adv_searches
@available_adv_searches ||=
begin
global_expressions = MiqSearch.get_expressions(:db => exp_model, :search_type => 'global')
user_expressions = MiqSearch.get_expressions(:db => exp_model, :search_type => 'user',
:search_key => User.current_user.userid)
user_expressions = Array(user_expressions).sort
global_expressions = Array(global_expressions).sort
global_expressions.each { |ge| ge[0] = "Global - #{ge[0]}" }
global_expressions + user_expressions
end
end
def calendar_needed?
[val1, val2].compact.any? { |val| [:date, :datetime].include?(val[:type]) }
end
def render_values_to(page)
if val1.try(:type)
page << "ManageIQ.expEditor.first.type = '#{val1[:type]}';"
page << "ManageIQ.expEditor.first.title = '#{val1[:title]}';"
end
if val2.try(:type)
page << "ManageIQ.expEditor.second.type = '#{val2[:type]}';"
page << "ManageIQ.expEditor.second.title = '#{val2[:title]}';"
end
end
def prefill_val_types
self.val1 ||= {}
self.val2 ||= {}
val1[:type] = case exp_typ
when 'field'
if exp_key == EXP_IS && val1[:date_format] == 's'
:date
else
val_type_for(:exp_key, :exp_field)
end
when 'find'
if exp_skey == EXP_IS && val1[:date_format] == 's'
:date
else
val_type_for(:exp_skey, :exp_field)
end
when 'count'
:integer
when 'regkey'
:string
end
val2[:type] = if exp_typ == 'find'
if exp_ckey && val2[:date_format] == 's'
:date
else
exp_check == 'checkcount' ? :integer : val_type_for(:exp_ckey, :exp_cfield)
end
end
val1[:title] = MiqExpression::FORMAT_SUB_TYPES[val1[:type]][:title] if val1[:type]
val2[:title] = MiqExpression::FORMAT_SUB_TYPES[val2[:type]][:title] if val2[:type]
end
def build_search(name_given_by_user, global_search, userid)
if selected.nil? || # if no search was loaded
name_given_by_user != selected[:description] || # or user changed the name of loaded search
selected[:typ] == 'default' # or loaded search is default search, save it as my search
s = build_new_search(name_given_by_user)
if global_search
miq_search_set_details(s, :global, name_given_by_user)
else
miq_search_set_details(s, :user, name_given_by_user, userid)
end
else
s = MiqSearch.find(selected[:id])
if global_search
unless s.name == "global_#{name_given_by_user}" # it was already global before
s = build_new_search(name_given_by_user)
end
miq_search_set_details(s, :global, name_given_by_user)
else
unless s.name == "user_#{userid}_#{name_given_by_user}" # iw was already "My Search"
s = build_new_search(name_given_by_user)
end
end
end
s
end
# Set the selected and last loaded filter
def select_filter(miq_search, last_loaded = false)
self.selected = filter_info(miq_search)
self.exp_last_loaded = selected if last_loaded
end
# Get the id, name, description and typ of a filter
def filter_info(miq_search)
{:id => miq_search.id,
:name => miq_search.name,
:description => miq_search.description,
:typ => miq_search.search_type}
end
# Set the last loaded filter
def last_loaded_filter(miq_search)
self.exp_last_loaded = filter_info(miq_search)
end
def update_from_expression_editor(params)
if params[:chosen_typ] && params[:chosen_typ] != exp_typ
change_exp_typ(params[:chosen_typ])
else
case exp_typ
when 'field'
if params[:chosen_field] && params[:chosen_field] != exp_field
self.exp_field = params[:chosen_field]
self.exp_value = nil
self.val1_suffix = nil
if params[:chosen_field] == '<Choose>'
self.exp_field = nil
self.exp_key = nil
else
# for date time fields we should show After/Before etc. options
chosen_field_col_type = MiqExpression.parse_field_or_tag(params[:chosen_field]).try(:column_type)
if exp_model != '_display_filter_' &&
MiqExpression::Field.parse(exp_field).plural? &&
!%i[date datetime].include?(chosen_field_col_type) &&
chosen_field_col_type.object_id != :integer.object_id
self.exp_key = 'CONTAINS' # CONTAINS is valid only for plural tables
else
self.exp_key = nil unless MiqExpression.get_col_operators(exp_field).include?(exp_key)
self.exp_key ||= MiqExpression.get_col_operators(exp_field).first # Default to first operator
end
prefill_val_types
process_datetime_expression_field(:val1, :exp_key, :exp_value)
end
self.alias = nil
end
if params[:chosen_key] && params[:chosen_key] != exp_key
process_changed_expression(params, :chosen_key, :exp_key, :exp_value, :val1)
end
if params[:user_input]
self.exp_value = params[:user_input] == '1' ? :user_input : ''
end
when 'count'
if params[:chosen_count] && params[:chosen_count] != exp_count
if params[:chosen_count] == '<Choose>'
self.exp_count = nil
self.exp_key = nil
self.exp_value = nil
else
self.exp_count = params[:chosen_count]
self.exp_key = nil unless MiqExpression.get_col_operators(:count).include?(exp_key)
self.exp_key ||= MiqExpression.get_col_operators(:count).first
end
self.alias = nil
end
self.exp_key = params[:chosen_key] if params[:chosen_key]
if params[:user_input]
self.exp_value = params[:user_input] == '1' ? :user_input : nil
end
when 'tag', 'tags'
if params[:chosen_tag] && params[:chosen_tag] != exp_tag
self.exp_tag = params[:chosen_tag] == '<Choose>' ? nil : params[:chosen_tag]
self.exp_key = exp_model == '_display_filter_' ? '=' : 'CONTAINS'
self.exp_value = nil
self.alias = nil
end
if params[:user_input]
self.exp_value = params[:user_input] == '1' ? :user_input : nil
end
when 'regkey'
self.exp_regkey = params[:chosen_regkey] if params[:chosen_regkey]
self.exp_regval = params[:chosen_regval] if params[:chosen_regval]
self.exp_key = params[:chosen_key] if params[:chosen_key]
prefill_val_types
when 'find'
if params[:chosen_field] && params[:chosen_field] != exp_field
self.exp_field = params[:chosen_field]
self.exp_value = nil
self.val1_suffix = nil
if params[:chosen_field] == '<Choose>'
self.exp_field = nil
self.exp_skey = nil
else
self.exp_skey = nil unless MiqExpression.get_col_operators(exp_field).include?(exp_skey)
self.exp_skey ||= MiqExpression.get_col_operators(exp_field).first
prefill_val_types
process_datetime_expression_field(:val1, :exp_skey, :exp_value)
end
if (exp_cfield.present? && exp_field.present?) && # Clear expression check portion
(exp_cfield == exp_field || # if find field matches check field
exp_cfield.split('-').first != exp_field.split('-').first) # or user chose a different table field
self.exp_check = 'checkall'
self.exp_cfield = nil
self.exp_ckey = nil
self.exp_cvalue = nil
end
self.alias = nil
end
if params[:chosen_skey] && params[:chosen_skey] != exp_skey
process_changed_expression(params, :chosen_skey, :exp_skey, :exp_value, :val1)
end
if params[:chosen_check] && params[:chosen_check] != exp_check
self.exp_check = params[:chosen_check]
self.exp_cfield = nil
self.exp_ckey = exp_check == 'checkcount' ? '=' : nil
self.exp_cvalue = nil
self.val2_suffix = nil
end
if params[:chosen_cfield] && params[:chosen_cfield] != exp_cfield
self.exp_cfield = params[:chosen_cfield]
self.exp_cvalue = nil
self.val2_suffix = nil
if params[:chosen_cfield] == '<Choose>'
self.exp_cfield = nil
self.exp_ckey = nil
else
self.exp_ckey = nil unless MiqExpression.get_col_operators(exp_cfield).include?(exp_ckey)
self.exp_ckey ||= MiqExpression.get_col_operators(exp_cfield).first
prefill_val_types
process_datetime_expression_field(:val2, :exp_ckey, :exp_cvalue)
end
end
if params[:chosen_ckey] && params[:chosen_ckey] != exp_ckey
process_changed_expression(params, :chosen_ckey, :exp_ckey, :exp_cvalue, :val2)
end
self.exp_cvalue = params[:chosen_cvalue] if params[:chosen_cvalue]
end
# Check the value field for all exp types
if params[:chosen_value] && params[:chosen_value] != exp_value.to_s
self.exp_value = params[:chosen_value] == '<Choose>' ? nil : params[:chosen_value]
end
# Use alias checkbox
if params.key?(:use_alias)
self.alias = if params[:use_alias] == '1'
case exp_typ
when 'field', 'find'
MiqExpression.value2human(exp_field).split(':').last
when 'tag'
MiqExpression.value2human(exp_tag).split(':').last
when 'count'
MiqExpression.value2human(exp_count).split('.').last
end.strip
end
end
# Check the alias field
if params.key?(:alias) && params[:alias] != self.alias.to_s # Did the value change?
self.alias = params[:alias].strip.blank? ? nil : params[:alias]
end
# Check incoming date and time values
# Copy FIND exp_skey to exp_key so following IFs work properly
self.exp_key = exp_skey if exp_typ == 'FIND'
process_datetime_selector(params, '1_0', :exp_key) # First date selector
process_datetime_selector(params, '1_1') # 2nd date selector, only on FROM
process_datetime_selector(params, '2_0', :exp_ckey) # First date selector in FIND/CHECK
process_datetime_selector(params, '2_1') # 2nd date selector, only on FROM
# Check incoming FROM/THROUGH date/time choice values
if params[:chosen_from_1]
exp_value[0] = params[:chosen_from_1]
val1[:through_choices] = Expression.through_choices(params[:chosen_from_1])
if (exp_typ == 'field' && exp_key == EXP_FROM) ||
(exp_typ == 'find' && exp_skey == EXP_FROM)
# If the through value is not in the through choices, set it to the first choice
unless val1[:through_choices].include?(exp_value[1])
exp_value[1] = val1[:through_choices].first
end
end
end
exp_value[1] = params[:chosen_through_1] if params[:chosen_through_1]
if params[:chosen_from_2]
exp_cvalue[0] = params[:chosen_from_2]
val2[:through_choices] = Expression.through_choices(params[:chosen_from_2])
if exp_ckey == EXP_FROM
# If the through value is not in the through choices, set it to the first choice
unless val2[:through_choices].include?(exp_cvalue[1])
exp_cvalue[1] = val2[:through_choices].first
end
end
end
exp_cvalue[1] = params[:chosen_through_2] if params[:chosen_through_2]
end
# Check for changes in date format
if params[:date_format_1] && exp_value.present?
val1[:date_format] = params[:date_format_1]
exp_value.collect! { |_| params[:date_format_1] == 's' ? nil : EXP_TODAY }
val1[:through_choices] = Expression.through_choices(exp_value[0]) if params[:date_format_1] == 'r'
end
if params[:date_format_2] && exp_cvalue.present?
val2[:date_format] = params[:date_format_2]
exp_cvalue.collect! { |_| params[:date_format_2] == 's' ? nil : EXP_TODAY }
val2[:through_choices] = Expression.through_choices(exp_cvalue[0]) if params[:date_format_2] == 'r'
end
# Check for suffixes changed
self.val1_suffix = MiqExpression::BYTE_FORMAT_WHITELIST[params[:chosen_suffix]] if params[:chosen_suffix]
self.val2_suffix = MiqExpression::BYTE_FORMAT_WHITELIST[params[:chosen_suffix2]] if params[:chosen_suffix2]
end
def update_from_exp_tree(exp)
exp.delete(:token)
key = exp.keys.first
if exp[key]['field']
typ = 'field'
self.exp_field = exp[key]['field']
self.exp_value = exp[key]['value']
self.alias = exp[key]['alias']
elsif exp[key]['count']
typ = 'count'
self.exp_count = exp[key]['count']
self.exp_value = exp[key]['value']
self.alias = exp[key]['alias']
elsif exp[key]['tag']
typ = 'tag'
self.exp_tag = exp[key]['tag']
self.exp_value = exp[key]['value']
self.alias = exp[key]['alias']
elsif exp[key]['regkey']
typ = 'regkey'
self.exp_regkey = exp[key]['regkey']
self.exp_regval = exp[key]['regval']
self.exp_value = exp[key]['value']
elsif exp[key]['search']
typ = 'find'
skey = self.exp_skey = exp[key]['search'].keys.first # Get the search operator
self.exp_field = exp[key]['search'][skey]['field'] # Get the search field
self.alias = exp[key]['search'][skey]['alias'] # Get the field alias
self.exp_value = exp[key]['search'][skey]['value'] # Get the search value
if exp[key].key?('checkall') # Find the check hash key
chk = self.exp_check = 'checkall'
elsif exp[key].key?('checkany')
chk = self.exp_check = 'checkany'
elsif exp[key].key?('checkcount')
chk = self.exp_check = 'checkcount'
end
ckey = self.exp_ckey = exp[key][chk].keys.first # Get the check operator
self.exp_cfield = exp[key][chk][ckey]['field'] # Get the check field
self.exp_cvalue = exp[key][chk][ckey]['value'] # Get the check value
else
typ = nil
end
self.exp_key = key.upcase
self.exp_orig_key = key.upcase # Hang on to the original key for commit
self.exp_typ = typ
prefill_val_types
self.val1_suffix = self.val2_suffix = nil
unless exp_value == :user_input # Ignore user input fields
if val1 && val1[:type] == :bytes
if is_numeric?(exp_value) # Value is a number
self.val1_suffix = :bytes # Default to :bytes
self.exp_value = exp_value.to_s # Get the value
else # Value is a string
self.val1_suffix = exp_value.split('.').last.to_sym # Get the suffix
self.exp_value = exp_value.split('.')[0...-1].join('.') # Remove the suffix
end
end
end
if val2 && val2[:type] == :bytes
if is_numeric?(exp_cvalue) # Value is a number
self.val2_suffix = :bytes # Default to :bytes
self.exp_cvalue = exp_cvalue.to_s # Get the value
else # Value is a string
self.val2_suffix = exp_cvalue.split('.').last.to_sym # Get the suffix
self.exp_cvalue = exp_cvalue.split('.')[0...-1].join('.') # Remove the suffix
end
end
if val1 && [:datetime, :date].include?(val1[:type]) # Change datetime and date field values into arrays while editing
self.exp_value = Array.wrap(exp_value) # Turn date/time values into an array
val1[:date_format] = exp_value.first.to_s.include?('/') ? 's' : 'r'
if key == EXP_FROM && val1[:date_format] == 'r'
val1[:through_choices] = Expression.through_choices(exp_value[0])
end
end
if val2 && [:datetime, :date].include?(val2[:type])
self.exp_cvalue = Array.wrap(exp_cvalue) # Turn date/time cvalues into an array
val2[:date_format] = exp_cvalue.first&.include?('/') ? 's' : 'r'
if ckey == EXP_FROM && val2[:date_format] == 'r'
val2[:through_choices] = Expression.through_choices(exp_cvalue[0])
end
end
end
private
def change_exp_typ(chosen_typ)
self.exp_typ = chosen_typ
self.exp_key = self.alias = self.exp_skey = self.exp_ckey = self.exp_value = self.exp_cvalue = nil
self.exp_regkey = self.exp_regval = self.val1_suffix = self.val2_suffix = nil
case exp_typ
when '<Choose>'
self.exp_typ = nil
when 'field'
self.exp_field = nil
when 'count'
self.exp_count = nil
self.exp_key = MiqExpression.get_col_operators(:count).first
prefill_val_types
when 'tag'
self.exp_tag = nil
self.exp_key = 'CONTAINS'
when 'regkey'
self.exp_key = MiqExpression.get_col_operators(:regkey).first
prefill_val_types
when 'find'
self.exp_field = nil
self.exp_key = 'FIND'
self.exp_check = 'checkall'
self.exp_cfield = nil
end
end
def process_changed_expression(params, chosen_key, exp_key, exp_value, exp_valx)
# Remove the second exp_value if the operator changed from EXP_FROM
self[exp_value].delete_at(1) if self[exp_key] == EXP_FROM
# Set THROUGH value if changing to FROM
if params[chosen_key] == EXP_FROM
if self[exp_valx][:date_format] == 'r' # Format is relative
self[exp_valx][:through_choices] = self.class.through_choices(self[exp_value][0])
self[exp_value][1] = self[exp_valx][:through_choices].first
else # Format is specific, just add second value
self[exp_value][1] = nil
end
end
self[exp_key] = params[chosen_key]
prefill_val_types
# Convert to/from "<date>" and "<date time>" strings in the exp_value array for specific date/times
if self[exp_valx][:date_format] == 's'
if [:datetime, :date].include?(self[exp_valx][:type])
self[exp_value].each_with_index do |v, v_idx|
next if v.blank?
self[exp_value][v_idx] = if params[chosen_key] == EXP_IS || self[exp_valx][:type] == :date
v.split(' ').first if v.include?(':')
else
v + ' 00:00' unless v.include?(':')
end
end
end
end
end
def process_datetime_expression_field(value_key, exp_key, exp_value_key)
if [:date, :datetime].include?(self[value_key][:type]) # Seting value for date/time fields
self[value_key][:date_format] ||= 'r'
if self[exp_key] == EXP_FROM
self[exp_value_key] = self[value_key][:date_format] == 's' ? Array.new(2) : [EXP_TODAY, EXP_TODAY]
self[value_key][:through_choices] = [EXP_TODAY] if self[value_key][:date_format] == 'r'
else
self[exp_value_key] = self[value_key][:date_format] == 's' ? [] : [EXP_TODAY]
end
end
end
def process_datetime_selector(params, param_key_suffix, exp_key = nil)
param_date_key = "miq_date_#{param_key_suffix}".to_sym
param_time_key = "miq_time_#{param_key_suffix}".to_sym
return unless params[param_date_key] || params[param_time_key]
exp_value_index = param_key_suffix[-1].to_i
value_key = "val#{param_key_suffix[0]}".to_sym
exp_value_key = param_key_suffix.starts_with?('1') ? :exp_value : :exp_cvalue
date = params[param_date_key] || (params[param_time_key] && self[exp_value_key][exp_value_index].split(' ').first)
time = params[param_time_key] if params[param_time_key]
if time.to_s.blank? && self[value_key][:type] == :datetime && self[exp_key] != EXP_IS
time = '00:00' # If time is blank, add in midnight if needed
end
time = " #{time}" unless time.to_s.blank? # Prepend a blank, if time is non-blank
self[exp_value_key][exp_value_index] = "#{date}#{time}"
end
def build_new_search(name_given_by_user)
MiqSearch.new(:db => exp_model, :description => name_given_by_user)
end
def miq_search_set_details(search, type, name_given_by_user, userid = nil)
search.update(
:search_key => userid,
:name => "#{type == :global ? 'global' : "user_#{userid}"}_#{name_given_by_user}",
:search_type => type
)
end
def val_type_for(key, field)
if !self[key] || !self[field]
nil
elsif self[key].starts_with?('REG')
:regexp
else
typ = MiqExpression.get_col_info(self[field])[:format_sub_type]
if MiqExpression::FORMAT_SUB_TYPES.key?(typ)
typ
else
:string
end
end
end
# Return the through_choices pulldown array for FROM datetime/date operators
def self.through_choices(from_choice)
return nil if from_choice.nil?
tc = if ViewHelper::FROM_HOURS.include?(from_choice)
ViewHelper::FROM_HOURS
elsif ViewHelper::FROM_DAYS.include?(from_choice)
ViewHelper::FROM_DAYS
elsif ViewHelper::FROM_WEEKS.include?(from_choice)
ViewHelper::FROM_WEEKS
elsif ViewHelper::FROM_MONTHS.include?(from_choice)
ViewHelper::FROM_MONTHS
elsif ViewHelper::FROM_QUARTERS.include?(from_choice)
ViewHelper::FROM_QUARTERS
elsif ViewHelper::FROM_YEARS.include?(from_choice)
ViewHelper::FROM_YEARS
end
# Return the THROUGH choices based on the FROM choice
tc[0..tc.index(from_choice)]
end
def self.prefix_by_dot(suffix)
suffix ? ".#{suffix}" : ''
end
end
# TODO: expression is now manipulated with fetch_path
# We need to extract methods using fetch_path to Expression to avoid the fetch_path call
ApplicationController::Filter::Expression.include MoreCoreExtensions::Shared::Nested
end