ManageIQ/manageiq-ui-classic

View on GitHub
app/controllers/application_controller/filter.rb

Summary

Maintainability
F
1 wk
Test Coverage
F
33%
# Filter/search/expression methods included in application.rb
module ApplicationController::Filter
  extend ActiveSupport::Concern
  include ::MiqExpression::SubstMixin
  include ApplicationController::ExpressionHtml
  included do
    helper_method :filters_present?
  end

  def filters_present?
    @def_searches.present? || @my_searches.present? || self.respond_to?(:display_tree)
  end


  # Handle buttons pressed in the expression editor
  def exp_button
    @edit = session[:edit]
    case params[:pressed]
    when "undo", "redo"
      @edit[@expkey][:expression] = @edit[@expkey].history.rewind(params[:pressed])
      @edit[:new][@expkey] = copy_hash(@edit[@expkey][:expression])
    when "not"
      exp_add_not(@edit[@expkey][:expression], @edit[@expkey][:exp_token])
    when "and", "or"
      if @edit[@expkey][:exp_typ] == 'tags'
        @edit[@expkey][:exp_tag] = nil
        @edit[@expkey][:exp_key] = 'CONTAINS'
      end
      exp_add_joiner(@edit[@expkey][:expression], @edit[@expkey][:exp_token], params[:pressed])
    when "commit"
      exp_commit(@edit[@expkey][:exp_token])
    when "remove"
      exp_remove_cmd
    when "discard"
      # Copy back the latest expression or empty expression, if nil
      @edit[@expkey].val1 = nil
      @edit[@expkey].val2 = nil
      @edit[@expkey][:exp_tag] = nil if @edit[@expkey][:exp_tag]
      @edit[@expkey][:expression] = @edit[:new][@expkey].nil? ? {"???" => "???"} : copy_hash(@edit[:new][@expkey])
      @edit.delete(:edit_exp)
    else
      add_flash(_("Button not yet implemented"), :error)
    end

    if flash_errors?
      javascript_flash(:flash_div_id => 'exp_editor_flash')
    else
      if %w[commit not remove].include?(params[:pressed])
        copy = copy_hash(@edit[@expkey][:expression])
        copy.deep_delete(:token)
        @edit[:new][@expkey] = copy
        @edit[@expkey].history.push(@edit[:new][@expkey])
      end
      unless %w[and or].include?(params[:pressed]) # Unless adding an AND or OR token
        @edit[@expkey][:exp_token] = nil           # clear the current selected token
      end
      changed = (@edit[:new] != @edit[:current])
      @edit[@expkey][:exp_table] = exp_build_table(@edit[@expkey][:expression])
      render :update do |page|
        page << javascript_prologue
        page << "miqScrollTop();" if @flash_array.present?
        # Don't need to replace flash div as it's included throught
        # exp_editor. That is rendered either throught adv_search_body or directly.
        if !@edit[:adv_search_open].nil?
          page.replace("adv_search_body", :partial => "layouts/adv_search_body", :locals => {:force => true})
          page.replace("adv_search_footer", :partial => "layouts/adv_search_footer", :locals => {:force => true})
        else
          page.replace("exp_editor_div", :partial => "layouts/exp_editor")
        end

        if @edit[:expression_method]
          page.replace("exp_editor_div", :partial => "layouts/exp_editor")
        end

        if %w[not discard commit remove].include?(params[:pressed])
          page << javascript_hide("exp_buttons_on")
          page << javascript_hide("exp_buttons2_on")
          page << javascript_hide("exp_buttons2_not")
          page << javascript_show("exp_buttons_off")
          page << javascript_show("exp_buttons2_off")
        end
        if changed != session[:changed]
          session[:changed] = changed
          page << javascript_for_miq_button_visibility(changed)
        end
        page << "miqSparkle(false);" # Need to turn off sparkle in case original ajax element gets replaced
      end
    end
  end

  # A token was pressed on the exp editor
  def exp_token_pressed
    @edit = session[:edit]
    token = params[:token].to_i
    if token == @edit[@expkey][:exp_token] || # User selected same token as already selected
       (@edit[@expkey][:exp_token] && @edit[:edit_exp].key?("???")) # or new token in process
      javascript_flash(:flash_div_id => 'exp_editor_flash', :spinner_off => true)
    else
      exp = exp_find_by_token(@edit[@expkey][:expression], token)
      @edit[:edit_exp] = copy_hash(exp)
      begin
        @edit[@expkey].update_from_exp_tree(@edit[:edit_exp])
      rescue StandardError => bang
        @exp_atom_errors = [_("There is an error in the selected expression element, perhaps it was imported or edited manually."),
                            _("This element should be removed and recreated or you can report the error to your %{product} administrator.") % {:product => Vmdb::Appliance.PRODUCT_NAME},
                            _("Error details: %{message}") % {:message => bang}]
      end
      @edit[@expkey][:exp_token] = token
      render :update do |page|
        page << javascript_prologue
        page.replace("flash_msg_div", :partial => "layouts/flash_msg")
        page << "miqScrollTop();" if @flash_array.present?
        page.replace("exp_editor_div", :partial => "layouts/exp_editor")
        page << "$('#exp_#{token}').css({'text-decoration': 'underline'})"
        page << javascript_hide("exp_buttons_off")
        page << javascript_hide("exp_buttons2_off")
        page << javascript_show("exp_buttons_on")
        if exp.key?("not") || @parent_is_not
          page << javascript_hide("exp_buttons2_on")
          page << javascript_show("exp_buttons2_not")
        else
          page << javascript_hide("exp_buttons2_not")
          page << javascript_show("exp_buttons2_on")
        end

        page << ENABLE_CALENDAR if @edit[@expkey].calendar_needed?

        if @edit[@expkey][:exp_key] && @edit[@expkey][:exp_field]
          @edit[@expkey].render_values_to(page)
        end
        page << "miqSparkle(false);" # Need to turn off sparkle in case original ajax element gets replaced
      end
    end
  end

  # Handle items changed in the expression editor
  def exp_changed
    @edit = session[:edit]
    @edit[@expkey].update_from_expression_editor(params)
    # See if only a text value changed
    if params[:chosen_value] || params[:chosen_regkey] || params[:chosen_regval] ||
       params[:chosen_cvalue || params[:chosen_suffix]] || params[:alias]
      render :update do |page|
        page << javascript_prologue
      end
    elsif @refresh_div.to_s == 'flash_msg_div'
      javascript_flash(:flash_div_id => 'exp_editor_flash')
    else
      render :update do |page|
        page << javascript_prologue
        page.replace("exp_editor_flash", :partial => "layouts/flash_msg", :locals => {:flash_div_id => 'exp_editor_flash'})
        page << "miqScrollTop();" if @flash_array.present?
        page.replace("exp_atom_editor_div", :partial => "layouts/exp_atom/editor")

        page << ENABLE_CALENDAR if @edit[@expkey].calendar_needed?
        @edit[@expkey].render_values_to(page)
        page << "miqSparkle(false);" # Need to turn off sparkle in case original ajax element gets replaced
      end
    end
  end

  def adv_search_toggle_on
    # Rebuild the pulldowns
    @edit[@expkey].prefill_val_types

    render :update do |page|
      page << javascript_prologue
      page << "ManageIQ.explorer.clearSearchToggle(#{clear_search_status});"
      page.replace("adv_search_body", :partial => "layouts/adv_search_body", :locals => {:force => true})
      page.replace("adv_search_footer", :partial => "layouts/adv_search_footer", :locals => {:force => true})
      page << ENABLE_CALENDAR if @edit[@expkey].calendar_needed?
      @edit[@expkey].render_values_to(page)
      page << set_spinner_off
    end
  end

  def adv_search_toggle_off
    render :update do |page|
      page << javascript_prologue
      page << javascript_hide("advsearchModal")
      page << javascript_hide("blocker_div")
      page << set_spinner_off
    end
  end

  def adv_search_toggle
    @edit = session[:edit]

    @edit[:adv_search_open] ? adv_search_toggle_off : adv_search_toggle_on
    @edit[:adv_search_open] = !@edit[:adv_search_open]

    # Rememeber this settting in the model settings
    if session.fetch_path(:adv_search, @edit[@expkey][:exp_model])
      session[:adv_search][@edit[@expkey][:exp_model]][:adv_search_open] = @edit[:adv_search_open]
    end
  end

  # One of the form buttons was pressed on the advanced search panel
  def listnav_search_selected(id = nil)
    id ||= params[:id]
    @edit = session[:edit]
    @edit[:selected] = true # Set a flag, this is checked whether to load initial default or clear was clicked
    if id.to_i.zero?
      clear_selected_search
    else
      load_selected_search(id)
    end

    unless @explorer
      respond_to do |format|
        format.js do
          if @quick_search_active
            quick_search_show
          else
            javascript_redirect :action => 'show_list' # Redirect to build the list screen
          end
        end
        format.html do
          redirect_to :action => 'show_list' # Redirect to build the list screen
        end
      end
    end
  end

  def clear_selected_search
    @edit[:adv_search_applied] = nil
    @edit[@expkey][:expression] = {"???" => "???"}                            # Set as new exp element
    @edit[:new][@expkey] = copy_hash(@edit[@expkey][:expression])             # Copy to new exp
    @edit[@expkey].history.reset(@edit[@expkey][:expression])
    @edit[@expkey][:exp_table] = exp_build_table(@edit[@expkey][:expression]) # Rebuild the expression table
    @edit[@expkey][:selected] = {:id => 0}                                    # Save the last search loaded
    @edit[:adv_search_name] = @edit[:new_search_name] = nil                   # Clear search name
    @edit[:adv_search_report] = nil                                           # Clear the report name
    session[:adv_search] ||= {}                                               # Create/reuse the adv search hash
    session[:adv_search][@edit[@expkey][:exp_model]] = copy_hash(@edit)       # Save by model name in settings
  end

  def load_selected_search(id)
    @expkey = :expression                                                     # Reset to use default expression key
    @edit[:new] = {}
    s = MiqSearch.find(id.to_s)
    @edit[:new][@expkey] = s.filter.exp
    if s.quick_search?
      @quick_search_active = true
      @edit[:qs_prev_x_node] = x_node if @edit[:in_explorer]                  # Remember current tree node
      @edit[@expkey][:pre_qs_selected] = @edit[@expkey][:selected]            # Save previous selected search
      @edit[:qs_prev_adv_search_applied] = @edit[:adv_search_applied]         # Save any existing adv search
    end
    @edit[@expkey].select_filter(s)
    @edit[:new_search_name] = @edit[:adv_search_name] = @edit.fetch_path(@expkey, :selected, :description)
    @edit[@expkey][:expression] = copy_hash(@edit[:new][@expkey])
    @edit[@expkey][:exp_table] = exp_build_table(@edit[@expkey][:expression]) # Build the expression table
    @edit[@expkey].history.reset(@edit[@expkey][:expression])
    @edit[@expkey][:exp_token] = nil                                          # Clear the current selected token
    @edit[:adv_search_applied] = {}

    adv_search_set_text                                                       # Set search text filter suffix
    @edit[:adv_search_applied][:exp] = @edit[:new][@expkey]                   # Save the expression to be applied
    @edit[@expkey].exp_token = nil                                            # Remove any existing atom being edited
    @edit[:adv_search_open] = false                                           # Close the adv search box
    session[:adv_search] ||= {}                                               # Create/reuse the adv search hash
    session[:adv_search][@edit[@expkey][:exp_model]] = copy_hash(@edit)       # Save by model name in settings
  end

  def clear_default_search
    # @edit[@expkey][:exp_last_loaded] = nil                                  # Clear the last search loaded
    @edit[@expkey][:selected] = {:id => 0, :description => "All"}             # Save the last search loaded
    @edit[:adv_search_applied] = nil
    search_expression_reset_fields
  end

  def load_default_search(id)
    @edit ||= {}
    @expkey = :expression                                                     # Reset to use default expression key
    @edit[@expkey] ||= Expression.new
    @edit[@expkey][:expression] = []                                          # Store exps in an array
    @edit[:new] = {}
    @edit[:new][@expkey] = @edit[@expkey][:expression]                        # Copy to new exp
    s = MiqSearch.find(id) unless id.zero?
    if s.nil? || s.search_key == "_hidden_" # search not found || admin changed default search to be hidden
      clear_default_search
    else
      @edit[:new][@expkey] = s.filter.exp
      @edit[@expkey].select_filter(s)
      @edit[:new_search_name] = @edit[:adv_search_name] = @edit[@expkey][:selected].nil? ? nil : @edit[@expkey][:selected][:description]
      @edit[@expkey][:expression] = copy_hash(@edit[:new][@expkey])
      @edit[@expkey][:exp_table] = exp_build_table(@edit[@expkey][:expression]) # Build the expression table
      @edit[@expkey].history.reset(@edit[@expkey][:expression])
      @edit[@expkey][:exp_token] = nil # Clear the current selected token
      @edit[:adv_search_applied] = {}
      adv_search_set_text # Set search text filter suffix
      @edit[:adv_search_applied][:exp] = copy_hash(@edit[:new][@expkey])      # Save the expression to be applied
      @edit[@expkey].exp_token = nil                                          # Remove any existing atom being edited
    end
    @edit[:adv_search_open] = false                                           # Close the adv search box
  end

  # Save default search
  def save_default_search
    @edit = session[:edit]
    @view = session[:view]
    cols_key = @view.scoped_association.nil? ? @view.db.to_sym : (@view.db + "-" + @view.scoped_association).to_sym
    if params[:id]
      if params[:id] != "0"
        s = MiqSearch.find_by(:id => params[:id])
        if s.nil?
          add_flash(_("The selected Filter record was not found"), :error)
        elsif s.quick_search?
          add_flash(_("The selected Filter can not be set as Default because it requires user input"), :error)
        end
      end
      if @flash_array.blank?
        @settings.store_path(:default_search, cols_key, params[:id].to_i)

        user_settings = current_user.settings || {}
        user_settings[:default_search] ||= {}
        user_settings[:default_search][cols_key] ||= {}
        user_settings[:default_search][cols_key] = settings(:default_search, cols_key)
        current_user.update(:settings => user_settings)
      end
    end
    build_listnav_search_list(@view.db) if @flash_array.blank?
    if @flash_array.blank?
      respond_to do |format|
        format.js do
          javascript_redirect :action => 'show_list' # Redirect to build the list screen
        end
        format.html do
          redirect_to :action => 'show_list' # Redirect to build the list screen
        end
      end
    else
      javascript_flash
    end
  end

  def quick_search_load_params_to_tokens
    # Capture any value/suffix entered
    params.each do |key, value|
      token_key = key.to_s.split("_").last.to_i
      if key.to_s.starts_with?("value_")
        @edit[:qs_tokens][token_key][:value] = value
      elsif key.to_s.starts_with?("suffix_")
        @edit[:qs_tokens][token_key][:suffix] = value
      end
    end
  end
  private :quick_search_load_params_to_tokens

  def quick_search_apply_click
    @edit[:adv_search_applied][:qs_exp] = copy_hash(@edit[:adv_search_applied][:exp] || {})
    exp_replace_qs_tokens(@edit[:adv_search_applied][:qs_exp], @edit[:qs_tokens])
    exp_remove_tokens(@edit[:adv_search_applied][:qs_exp])
    session[:adv_search] ||= {}
    session[:adv_search][@edit[@expkey][:exp_model]] = copy_hash(@edit) # Save by model name in settings

    # FIXME: this should really do $('#quicksearchbox').modal('hide'); ; for now, Apply closes the modal JS side
    if @edit[:in_explorer]
      replace_right_cell
    else
      javascript_redirect(:action => 'show_list')
    end
  end
  private :quick_search_apply_click

  def quick_search_cancel_click
    @edit[@expkey][:selected] = @edit[@expkey][:pre_qs_selected]                # Restore previous selected search
    @edit[:adv_search_applied] = @edit[:qs_prev_adv_search_applied]             # Restore previous adv search
    @edit[:adv_search_applied] = nil unless @edit.fetch_path(:adv_search_applied, :exp) # Remove adv search if no prev expression
    self.x_node = @edit[:qs_prev_x_node] if @edit[:in_explorer] && @edit[@expkey][:exp_value] != :user_input # Restore previous exp tree node
    session[:adv_search] ||= {}
    session[:adv_search][@edit[@expkey][:exp_model]] = copy_hash(@edit) # Save by model name in settings

    render :update do |page|
      page << javascript_prologue
      page << "miqTreeActivateNodeSilently('#{x_active_tree}', '#{x_node}');" if @edit[:in_explorer]
      page << "$('#quicksearchbox').modal('hide');"
      page << "miqSparkle(false);"
    end
  end
  private :quick_search_cancel_click

  # Handle input from the quick search box
  def quick_search
    @quick_search_active = true
    @edit = session[:edit] # Keep @edit alive as it contains all search info

    quick_search_load_params_to_tokens

    case params[:button]
    when 'apply'
      quick_search_apply_click
    when 'cancel'
      quick_search_cancel_click
    else
      any_empty = @edit[:qs_tokens].values.any? { |v| v[:value].to_s.empty? }
      render :update do |page|
        page << javascript_prologue
        page << javascript_for_miq_button_visibility(!any_empty, 'quick_search')
      end
    end
  end

  private

  # Popup/open the quick search box
  def quick_search_show
    @exp_token           = nil
    @quick_search_active = true
    @qs_exp_table        = exp_build_table(@edit[:adv_search_applied][:exp], true)
    @edit[:qs_tokens]    = create_tokens(@qs_exp_table, @edit[:adv_search_applied][:exp])

    render :update do |page|
      page << javascript_prologue
      page.replace(:user_input_filter, :partial => "layouts/user_input_filter")
      page << "$('#advsearchModal').modal('hide');"
      page << "$('#quicksearchbox').modal('show');"
      page << "miqSparkle(false);"
    end
  end

  # Set advanced search filter text
  def adv_search_set_text
    if @edit[@expkey].history.idx.zero? # Are we pointing at the first exp
      @edit[:adv_search_applied][:text] = if @edit[:new_search_name]
                                            _(" - Filtered by \"%{text}\"") % {:text => @edit[:new_search_name]}
                                          else
                                            _(" - Filtered by \"%{text}\" report") %
                                              {:text => @edit[:adv_search_report]}
                                          end
    else
      @edit[:custom_search] = true
      @edit[:adv_search_applied][:text] = _(" - Filtered by custom search")
    end
  end

  # Remove :token keys from an expression before setting in a record
  def exp_remove_tokens(exp)
    if exp.kind_of?(Array)      # Is this and AND or OR
      exp.each do |e|           #   yes, check each array item
        exp_remove_tokens(e)    # Remove tokens from children
      end
    else
      exp.delete(:token)        # Remove :token key from any expression hash

      # Chase down any other tokens in child expressions
      if exp["not"]
        exp_remove_tokens(exp["not"])
      elsif exp["and"]
        exp_remove_tokens(exp["and"])
      elsif exp["or"]
        exp_remove_tokens(exp["or"])
      end

    end
  end

  # Add a joiner (and/or) above an expression
  def exp_add_joiner(exp, token, joiner)
    if exp[:token] && exp[:token] == token                # If the token matches
      exp.keys.each do |key|                              # Find the key
        if key == :token
          exp.delete(key)                                 # Remove the :token key
        else
          exp[joiner] = [{key => exp[key]}]               # Chain in the current key under the joiner array
          exp.delete(key)                                 # Remove the current key
          exp[joiner].push("???" => "???")                # Add in the new key under the joiner
        end
      end
    else
      exp.each do |key, _value|                           # Go thru the next level down
        next if key == :token                             # Skip the :token key

        case key.upcase
        when "AND", "OR"                                  # If AND or OR, check all array items
          if key.downcase != joiner                       # Does the and/or match the joiner?
            exp[key].each_with_index do |item, _idx|      # No,
              exp_add_joiner(item, token, joiner)         #   check the lower expressions
            end
          else
            exp[key].each_with_index do |item, idx|       # Yes,
              if item[:token] && item[:token] == token    # Found the match
                exp[key].insert(idx + 1, "???" => "???")  # Add in the new key hash
              else
                exp_add_joiner(item, token, joiner)       # No match, check the lower expressions
              end
            end
          end
        when "NOT"                                        # If NOT, check the sub-hash
          exp_add_joiner(exp[key], token, joiner)         # Check lower for the matching token
        end
      end
    end
  end

  # Add a NOT above an expression
  def exp_add_not(exp, token)
    if exp[:token] && exp[:token] == token            # If the token matches
      exp.keys.each do |key|                          # Find the key
        next if key == :token                         # Skip the :token key
        next if exp[key].nil?                         # Check for the key already gone

        exp["not"] = {}                               # Create the "not" hash
        exp["not"][key] = exp[key]                    # copy the found key's value down into the "not" hash
        exp.delete(key)                               # Remove the existing key
      end
    else
      exp.each do |key, _value|                       # Go thru the next level down
        next if key == :token                         # Skip the :token key

        case key.upcase
        when "AND", "OR"                              # If AND or OR, check all array items
          exp[key].each_with_index do |item, _idx|
            exp_add_not(item, token)                  # See if the NOT applies each level down
          end
        when "NOT"                                    # If NOT, check the sub-hash
          exp_add_not(exp[key], token)                # See if the NOT applies to the next level down
        end
      end
    end
  end

  def exp_remove_cmd
    remove_top = exp_remove(@edit[@expkey][:expression], @edit[@expkey][:exp_token])
    if remove_top == true
      exp = @edit[@expkey][:expression]
      if exp["not"]                                       # If the top expression is a NOT
        exp["not"].each_key do |key|                      # Find the next lower key
          next if key == :token                           # Skip the :token key

          exp[key] = exp["not"][key]                      # Copy the key value up to the top
          exp.delete("not")                               # Delete the NOT key
        end
      else
        exp.clear                                         # Remove all existing keys
        exp["???"] = "???"                                # Set new exp key
        @edit[:edit_exp] = copy_hash(exp)
        @edit[@expkey].update_from_exp_tree(exp)
      end
    else
      @edit[:edit_exp] = nil
    end
  end

  def exp_commit_field(exp)
    @edit[@expkey][:exp_value] ||= ''
    if @edit[@expkey][:exp_field].nil?
      add_flash(_("A field must be chosen to commit this expression element"), :error)
    elsif @edit[@expkey][:exp_value] != :user_input &&
          (e = MiqExpression.atom_error(@edit[@expkey][:exp_field],
                                        @edit[@expkey][:exp_key],
                                        @edit[@expkey][:exp_value].kind_of?(Array) ?
                                          @edit[@expkey][:exp_value] :
                                          (@edit[@expkey][:exp_value].to_s + Expression.prefix_by_dot(@edit[@expkey].val1_suffix))))
      add_flash(_("Field Value Error: %{msg}") % {:msg => e}, :error)
    else
      # Change datetime and date values from single element arrays to text string
      if [:datetime, :date].include?(@edit[@expkey][:val1][:type])
        @edit[@expkey][:exp_value] = @edit[@expkey][:exp_value].first.to_s if @edit[@expkey][:exp_value].length == 1
      end

      exp.delete(@edit[@expkey][:exp_orig_key])                                                      # Remove the old exp fields
      exp[@edit[@expkey][:exp_key]] = {}                                                             # Add in the new key
      exp[@edit[@expkey][:exp_key]]["field"] = @edit[@expkey][:exp_field]                            # Set the field
      unless @edit[@expkey][:exp_key].include?("NULL") || @edit[@expkey][:exp_key].include?("EMPTY") # Check for "IS/IS NOT NULL/EMPTY"
        exp[@edit[@expkey][:exp_key]]["value"] = @edit[@expkey][:exp_value]                          #   else set the value
        unless exp[@edit[@expkey][:exp_key]]["value"] == :user_input
          exp[@edit[@expkey][:exp_key]]["value"] += Expression.prefix_by_dot(@edit[@expkey].val1_suffix).to_s
        end
      end
      exp[@edit[@expkey][:exp_key]]["alias"] = @edit[@expkey][:alias] if @edit.fetch_path(@expkey, :alias)
    end
  end

  def exp_commit_count(exp)
    if @edit[@expkey][:exp_value] != :user_input &&
       (e = MiqExpression.atom_error(:count, @edit[@expkey][:exp_key], @edit[@expkey][:exp_value]))
      add_flash(_("Field Value Error: %{msg}") % {:msg => e}, :error)
    else
      exp.delete(@edit[@expkey][:exp_orig_key])                           # Remove the old exp fields
      exp[@edit[@expkey][:exp_key]] = {}                                  # Add in the new key
      exp[@edit[@expkey][:exp_key]]["count"] = @edit[@expkey][:exp_count] # Set the count table
      exp[@edit[@expkey][:exp_key]]["value"] = @edit[@expkey][:exp_value] # Set the value
      exp[@edit[@expkey][:exp_key]]["alias"] = @edit[@expkey][:alias] if @edit.fetch_path(@expkey, :alias)
    end
  end

  def exp_commit_tag(exp)
    if @edit[@expkey][:exp_tag].nil?
      add_flash(_("A tag category must be chosen to commit this expression element"), :error)
    elsif @edit[@expkey][:exp_value].nil?
      add_flash(_("A tag value must be chosen to commit this expression element"), :error)
    elsif exp.present?
      exp.delete(@edit[@expkey][:exp_orig_key])                           # Remove the old exp fields
      exp[@edit[@expkey][:exp_key]] = {}                                  # Add in the new key
      exp[@edit[@expkey][:exp_key]]["tag"] = @edit[@expkey][:exp_tag]     # Set the tag
      exp[@edit[@expkey][:exp_key]]["value"] = @edit[@expkey][:exp_value] # Set the value
      exp[@edit[@expkey][:exp_key]]["alias"] = @edit[@expkey][:alias] if @edit.fetch_path(@expkey, :alias)
    end
  end

  def exp_commit_regkey(exp)
    if @edit[@expkey][:exp_regkey].blank?
      add_flash(_("A registry key name must be entered to commit this expression element"), :error)
    elsif @edit[@expkey][:exp_regval].blank? && @edit[@expkey][:exp_key] != "KEY EXISTS"
      add_flash(_("A registry value name must be entered to commit this expression element"), :error)
    elsif @edit[@expkey][:exp_key].include?("REGULAR EXPRESSION") && (e = MiqExpression.atom_error(:regexp, @edit[@expkey][:exp_key], @edit[@expkey][:exp_value]))
      add_flash(_("Registry Value Error: %{msg}") % {:msg => e}, :error)
    else
      exp.delete(@edit[@expkey][:exp_orig_key])                               # Remove the old exp fields
      exp[@edit[@expkey][:exp_key]] = {}                                      # Add in the new key
      exp[@edit[@expkey][:exp_key]]["regkey"] = @edit[@expkey][:exp_regkey]   # Set the key name
      unless @edit[@expkey][:exp_key].include?("KEY EXISTS")
        exp[@edit[@expkey][:exp_key]]["regval"] = @edit[@expkey][:exp_regval] # Set the value name
      end
      unless  @edit[@expkey][:exp_key].include?("NULL") ||                    # Check for "IS/IS NOT NULL/EMPTY" or "EXISTS"
              @edit[@expkey][:exp_key].include?("EMPTY") ||
              @edit[@expkey][:exp_key].include?("EXISTS")
        exp[@edit[@expkey][:exp_key]]["value"] = @edit[@expkey][:exp_value]   #   else set the data value
      end
    end
  end

  def exp_commit_find(exp)
    if @edit[@expkey][:exp_field].nil?
      add_flash(_("A find field must be chosen to commit this expression element"), :error)
    elsif %w[checkall checkany].include?(@edit[@expkey][:exp_check]) &&
          @edit[@expkey][:exp_cfield].nil?
      add_flash(_("A check field must be chosen to commit this expression element"), :error)
    elsif @edit[@expkey][:exp_check] == "checkcount" &&
          (@edit[@expkey][:exp_cvalue].nil? || is_integer?(@edit[@expkey][:exp_cvalue]) == false)
      add_flash(_("The check count value must be an integer to commit this expression element"), :error)
    elsif (e = MiqExpression.atom_error(@edit[@expkey][:exp_field],
                                        @edit[@expkey][:exp_skey],
                                        @edit[@expkey][:exp_value].kind_of?(Array) ?
                                          @edit[@expkey][:exp_value] :
                                          (@edit[@expkey][:exp_value].to_s + Expression.prefix_by_dot(@edit[@expkey].val1_suffix))))
      add_flash(_("Find Value Error: %{msg}") % {:msg => e}, :error)
    elsif (e = MiqExpression.atom_error(@edit[@expkey][:exp_check] == "checkcount" ? :count : @edit[@expkey][:exp_cfield],
                                        @edit[@expkey][:exp_ckey],
                                        @edit[@expkey][:exp_cvalue].kind_of?(Array) ?
                                          @edit[@expkey][:exp_cvalue] :
                                          (@edit[@expkey][:exp_cvalue].to_s + Expression.prefix_by_dot(@edit[@expkey].val2_suffix))))
      add_flash(_("Check Value Error: %{msg}") % {:msg => e}, :error)
    else
      # Change datetime and date values from single element arrays to text string
      if [:datetime, :date].include?(@edit[@expkey][:val1][:type])
        @edit[@expkey][:exp_value] = @edit[@expkey][:exp_value].first.to_s if @edit[@expkey][:exp_value].length == 1
      end
      if @edit[@expkey][:val2][:type] && [:datetime, :date].include?(@edit[@expkey][:val2][:type])
        @edit[@expkey][:exp_cvalue] = @edit[@expkey][:exp_cvalue].first.to_s if @edit[@expkey][:exp_cvalue].length == 1
      end

      exp.delete(@edit[@expkey][:exp_orig_key])                                             # Remove the old exp fields
      exp[@edit[@expkey][:exp_key]] = {}                                                    # Add in the new key
      exp[@edit[@expkey][:exp_key]]["search"] = {}                                          # Create the search hash
      skey = @edit[@expkey][:exp_skey]
      exp[@edit[@expkey][:exp_key]]["search"][skey] = {}                                    # Create the search operator hash
      exp[@edit[@expkey][:exp_key]]["search"][skey]["field"] = @edit[@expkey][:exp_field]   # Set the search field
      unless skey.include?("NULL") || skey.include?("EMPTY")                                # Check for "IS/IS NOT NULL/EMPTY"
        exp[@edit[@expkey][:exp_key]]["search"][skey]["value"] = @edit[@expkey][:exp_value] #   else set the value
        exp[@edit[@expkey][:exp_key]]["search"][skey]["value"] += Expression.prefix_by_dot(@edit[@expkey].val1_suffix).to_s
      end
      chk = @edit[@expkey][:exp_check]
      exp[@edit[@expkey][:exp_key]][chk] = {}                                               # Create the check hash
      ckey = @edit[@expkey][:exp_ckey]
      exp[@edit[@expkey][:exp_key]][chk][ckey] = {}                                         # Create the check operator hash
      exp[@edit[@expkey][:exp_key]][chk][ckey]["field"] = if @edit[@expkey][:exp_check] == "checkcount"
                                                            "<count>"                       # Indicate count is being checked
                                                          else
                                                            @edit[@expkey][:exp_cfield] # Set the check field
                                                          end
      unless ckey.include?("NULL") || ckey.include?("EMPTY")                                # Check for "IS/IS NOT NULL/EMPTY"
        exp[@edit[@expkey][:exp_key]][chk][ckey]["value"] = @edit[@expkey][:exp_cvalue]     #   else set the value
        exp[@edit[@expkey][:exp_key]][chk][ckey]["value"] += Expression.prefix_by_dot(@edit[@expkey].val2_suffix).to_s
      end
      exp[@edit[@expkey][:exp_key]]["search"][skey]["alias"] = @edit[@expkey][:alias] if @edit.fetch_path(@expkey, :alias)
    end
  end

  # Update the current expression part with the latest changes
  def exp_commit(token)
    exp = exp_find_by_token(@edit[@expkey][:expression], token.to_i)
    case @edit[@expkey][:exp_typ]
    when "field"       then exp_commit_field(exp)
    when "count"       then exp_commit_count(exp)
    when "tag", "tags" then exp_commit_tag(exp)
    when "regkey"      then exp_commit_regkey(exp)
    when "find"        then exp_commit_find(exp)
    else
      add_flash(_("Select an expression element type"), :error)
    end
  end

  # Remove an expression part based on the token
  def exp_remove(exp, token)
    return true if exp[:token] && exp[:token] == token # If the token matches

    # Tell caller to remove me
    keepkey, keepval, deletekey = nil                  # Holders for key, value pair to keep and key to delete
    exp.each do |key, _value|                          # Go thru each exp element
      next if key == :token                            # Skip the :token keys

      case key.upcase
      when "AND", "OR"                                 # If AND or OR
        exp[key].each_with_index do |item, idx|        #   check all array items
          if exp_remove(item, token)                   # See if this part should be removed
            if item.key?("not")                        # The item to remove is a NOT
              exp[key].insert(idx + 1, item["not"])    # Rechain the NOT child into the array
              exp[key].delete_at(idx)                  # Remove the NOT item
            else                                       # Item to remove is other than a NOT
              exp[key].delete_at(idx)                  # Remove it from the array
              if exp[key].length == 1                  # If only 1 part left
                exp[key][0].each do |k, _v|            # Find the key that's not :token
                  next if k == :token                  # Skip the :token key

                  keepkey = k                          # Hang on to the key to keep
                  keepval = exp[key][0][k]             #   and the value to keep
                  deletekey = key                      #   and the key to delete
                  break
                end
              end
            end
          end
        end
      when "NOT"                                       # If NOT, check the sub-hash
        if exp_remove(exp[key], token)                 # Next lower hash is to be removed
          exp.delete("not")                            # Remove the NOT hash
          return true                                  # Tell caller to remove me
        end
      else
        return false
      end
    end
    exp[keepkey] = keepval if keepkey                  # Copy the key value to keep up 1 level
    exp.delete(deletekey)                              # Remove the AND or OR hash
    false                                              # Done removing item, return
  end

  def build_listnav_search_list(db)
    @settings[:default_search] = current_user.settings[:default_search] # Get the user's default search settings again, incase default search was deleted
    default_search_db = settings(:default_search, db.to_sym).to_s
    if default_search_db.present? && default_search_db.to_i != 0 && MiqSearch.exists?(default_search_db)
      @default_search = MiqSearch.find(default_search_db)
    end

    temp = MiqSearch.new
    temp.description = _("ALL")
    temp.name = _("ALL")
    temp.id = 0
    @def_searches = MiqSearch.where(:db => [db, db.constantize.to_s]).visible_to_all.sort_by { |s| s.description.downcase }
    @def_searches = @def_searches.unshift(temp) unless @def_searches.empty?

    @def_searches.each do |search|
      search.description = search_description(search)
      @selected_filter = search if def_searches_active_filter?(search) == 'active'
    end
    @my_searches = MiqSearch.where(:search_type => "user", :search_key => session[:userid], :db => [db, db.constantize.to_s]).sort_by { |s| s.description.downcase }
    @my_searches.each do |search|
      @selected_filter = search if (my_searches_active_filter?(search) == 'active' && @selected_filter.nil?)
      search.description = search_description(search)
    end
  end

  ENABLE_CALENDAR = "miqBuildCalendar();".freeze
end