Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Automate Expression Methods #6655

Closed
wants to merge 13 commits into from
15 changes: 12 additions & 3 deletions app/controllers/application_controller/filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ def exp_button
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 ["not", "discard", "commit", "remove"].include?(params[:pressed])
page << javascript_hide("exp_buttons_on")
page << javascript_hide("exp_buttons_not")
Expand Down Expand Up @@ -964,15 +968,20 @@ def adv_search_build(model)
end
@edit.delete(:exp_token) # Remove any existing atom being edited
else # Create new exp fields
@edit = {}
@edit ||= {}
@edit[@expkey] ||= Expression.new
@edit[@expkey][:expression] = [] # Store exps in an array
@edit[@expkey][:expression] = {"???" => "???"} # Set as new exp element
@edit[@expkey][:use_mytags] = true # Include mytags in tag search atoms
@edit[:custom_search] = false # setting default to false
@edit[:new] = {}
@edit[:new][@expkey] = @edit[@expkey][:expression] # Copy to new exp
@edit[:new] ||= {}
if @edit[:new][@expkey]
@edit[@expkey][:expression] = @edit[:new][@expkey] # Copy to new exp
else
@edit[:new][@expkey] = @edit[@expkey][:expression] # Copy to new exp
end
@edit[@expkey].history.reset(@edit[@expkey][:expression])
exp_array(:init, @edit[@expkey][:expression]) # Initialize the exp array
@edit[:adv_search_open] = false
@edit[@expkey][:exp_model] = model.to_s
end
Expand Down
49 changes: 47 additions & 2 deletions app/controllers/miq_ae_class_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -737,8 +737,17 @@ def set_method_form_vars
@edit[:new][:scope] = "instance"
@edit[:new][:language] = "ruby"
@edit[:new][:available_locations] = MiqAeMethod.available_locations
@edit[:new][:available_expression_objects] = MiqAeMethod.available_expression_objects.sort
@edit[:new][:location] = @ae_method.location.nil? ? "inline" : @ae_method.location
@edit[:new][:data] = @ae_method.data.to_s
if @edit[:new][:location] == "expression"
hash = YAML.load(@ae_method.data)
if hash[:db] && hash[:expression]
@edit[:new][:expression] = hash[:expression]
expression_setup(hash[:db])
end
else
@edit[:new][:data] = @ae_method.data.to_s
end
if @edit[:new][:location] == "inline" && !@ae_method.data
@edit[:new][:data] = MiqAeMethod.default_method_text
end
Expand Down Expand Up @@ -857,6 +866,18 @@ def fields_form_field_changed
end
end

def expression_setup(db)
@edit[:expression_method] = true
@edit[:new][:exp_object] = db
adv_search_build(db)
end

def expression_cleanup
@edit[:expression_method] = false
# @edit[:new][:expression] = nil
# @edit[:expression] = nil
end

# AJAX driven routine to check for changes in ANY field on the form
def form_method_field_changed
if !@sb[:form_vars_set] # workaround to prevent an error that happens when IE sends a transaction form form even after save button is clicked when there is text_area in the form
Expand All @@ -865,6 +886,15 @@ def form_method_field_changed
return unless load_edit("aemethod_edit__#{params[:id]}", "replace_cell__explorer")
@prev_location = @edit[:new][:location]
get_method_form_vars

if @edit[:new][:location] == 'expression'
@edit[:new][:exp_object] ||= @edit[:new][:available_expression_objects].first
exp_object = params[:cls_exp_object] || params[:exp_object] || @edit[:new][:exp_object]
expression_setup(exp_object) if exp_object
else
expression_cleanup
end

if row_selected_in_grid?
@refresh_div = "class_methods_div"
@refresh_partial = "class_methods"
Expand All @@ -890,6 +920,7 @@ def form_method_field_changed
@edit[:default_verify_status] = @edit[:new][:location] == "inline" && @edit[:new][:data] && @edit[:new][:data] != ""
render :update do |page|
page << javascript_prologue
page.replace_html('form_div', :partial => 'method_form', :locals => {:prefix => ""}) if @edit[:new][:location] == 'expression'
page.replace_html(@refresh_div, :partial => @refresh_partial) if @refresh_div && @prev_location != @edit[:new][:location]
# page.replace_html("hider_1", :partial=>"method_data", :locals=>{:field_name=>@field_name}) if @prev_location != @edit[:new][:location]
if params[:cls_field_datatype]
Expand Down Expand Up @@ -948,6 +979,7 @@ def form_method_field_changed
end
end
page << javascript_for_miq_button_visibility_changed(@changed)
page << "miqSparkle(false)"
end
end
end
Expand Down Expand Up @@ -1198,6 +1230,11 @@ def create
end
end

def data_for_expression
{:db => @edit[:new][:exp_object],
:expression => @edit[:new][:expression]}.to_yaml
end

def create_method
assert_privileges("miq_ae_method_new")
@in_a_form = true
Expand Down Expand Up @@ -2165,7 +2202,11 @@ def set_method_record_vars(miqaemethod)
miqaemethod.scope = @edit[:new][:scope]
miqaemethod.location = @edit[:new][:location]
miqaemethod.language = @edit[:new][:language]
miqaemethod.data = @edit[:new][:data]
miqaemethod.data = if @edit[:new][:location] == 'expression'
data_for_expression
else
@edit[:new][:data]
end
miqaemethod.class_id = from_cid(@edit[:ae_class_id])
end

Expand Down Expand Up @@ -2522,6 +2563,10 @@ def get_method_node_info(id)
inputs = @record.inputs
@sb[:squash_state] = true
@sb[:active_tab] = "methods"
if @record.location == 'expression'
hash = YAML.load(@record.data)
@expression = hash[:expression] ? MiqExpression.new(hash[:expression]).to_human : ""
end
domain_overrides
set_right_cell_text(x_node, @record)
end
Expand Down
1 change: 1 addition & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,7 @@ def clear_search_status
QS_VALID_USER_INPUT_OPERATORS = ["=", "!=", ">", ">=", "<", "<=", "INCLUDES", "STARTS WITH", "ENDS WITH", "CONTAINS"]
QS_VALID_FIELD_TYPES = [:string, :boolean, :integer, :float, :percent, :bytes, :megabytes]
def qs_show_user_input_checkbox?
return true if @edit[:expression_method]
return false unless @edit[:adv_search_open] # Only allow user input for advanced searches
return false unless QS_VALID_USER_INPUT_OPERATORS.include?(@edit[@expkey][:exp_key])
val = (@edit[@expkey][:exp_typ] == "field" && # Field atoms with certain field types return true
Expand Down
29 changes: 22 additions & 7 deletions app/views/miq_ae_class/_method_form.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
:maxlength => MAX_NAME_LEN,
:class => "form-control",
"data-miq_observe" => obs)
- if @ae_method.created_on
.form-group
%label.col-md-2.control-label
= _("Created On")
.col-md-8
= h(format_timezone(@ae_method.created_on, Time.zone, "gtl"))
.form-group
%label.col-md-2.control-label
= _('Location')
Expand All @@ -39,16 +45,25 @@
"data-miq_observe" => {:url => url}.to_json)
:javascript
miqInitSelectPicker();
miqSelectPickerEvent("#{prefix}method_location", "#{url}")
- if @ae_method.created_on
miqSelectPickerEvent("#{prefix}method_location", "#{url}", {beforeSend: true})
- if @edit[:new][:location] == 'expression'
.form-group
%label.col-md-2.control-label
= _("Created On")
= _('Expression Object')
.col-md-8
= h(format_timezone(@ae_method.created_on, Time.zone, "gtl"))
%hr
%h3= (@edit[:new][:location] == 'builtin') ? _('Builtin name') : _("Data")
= render :partial => "method_data", :locals => {:field_name => "#{prefix}method"}
= select_tag("#{prefix}exp_object",
options_for_select(@edit[:new][:available_expression_objects],
@edit[:new][:exp_object]),
:class => "selectpicker",
"data-miq_observe" => {:url => url}.to_json)
:javascript
miqInitSelectPicker();
miqSelectPickerEvent("#{prefix}exp_object", "#{url}", {beforeSend: true})
= render :partial => 'layouts/exp_editor'
- else
%hr
%h3= (@edit[:new][:location] == 'builtin') ? _('Builtin name') : _("Data")
= render :partial => "method_data", :locals => {:field_name => "#{prefix}method"}
%hr
%h3= _("Input Parameters")
%table.table.table-bordered.table-striped
Expand Down
2 changes: 2 additions & 0 deletions app/views/miq_ae_class/_method_inputs.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
:mode => "ruby",
:line_numbers => true,
:read_only => true}
- elsif @ae_method.location == 'expression'
= @expression
- else
= @ae_method.data
-# show inputs parameters grid if there are any inputs
Expand Down
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1869,7 +1869,8 @@
x_button
x_history
x_show
)
) + adv_search_post +
exp_post
},
:miq_ae_customization => {
:get => %w(
Expand Down
1 change: 1 addition & 0 deletions lib/miq_automation_engine/engine/miq_ae_engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require 'engine/miq_ae_uri'
require 'engine/miq_ae_path'
require 'engine/miq_ae_domain_search'
require 'engine/miq_ae_expression_method'

module MiqAeEngine
DEFAULT_ATTRIBUTES = %w( User::user MiqServer::miq_server object_name )
Expand Down
147 changes: 147 additions & 0 deletions lib/miq_automation_engine/engine/miq_ae_expression_method.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
module MiqAeEngine
class MiqAeExpressionMethod
include MiqExpression::FilterSubstMixin
def initialize(method_obj, obj, inputs)
@edit = {}
@name = method_obj.name
@workspace = obj.workspace
@inputs = inputs
@attributes = inputs['distinct'] || inputs['attributes'] || %w(name)
load_expression(method_obj.data)
process_filter
end

def run
@search_objects = Rbac.search(:filter => MiqExpression.new(@exp),
:class => @exp_object,
:results_format => :objects).first
@search_objects.empty? ? error_handler : set_result
end

private

def load_expression(data)
raise MiqAeException::MethodExpressionEmpty, "Empty expression" if data.blank?
begin
hash = YAML.load(data)
if hash[:expression] && hash[:db]
@exp = hash[:expression]
@exp_object = hash[:db]
else
raise MiqAeException::MethodExpressionInvalid, "Invalid expression #{data}"
end
rescue
raise MiqAeException::MethodExpressionInvalid, "Invalid expression #{data}"
end
end

def process_filter
exp_table = exp_build_table(@exp)
qs_tokens = create_tokens(exp_table, @exp)
values = get_args(qs_tokens.keys.length)
values.each_with_index { |v, index| qs_tokens[index + 1][:value] = v }
exp_replace_qs_tokens(@exp, qs_tokens)
end

def result_hash(obj)
@attributes.each_with_object({}) do |attr, hash|
hash[attr] = result_simple(obj, attr)
end
end

def result_array
multiple = @attributes.count > 1
result = @search_objects.collect do |obj|
multiple ? @attributes.collect { |attr| result_simple(obj, attr) } : result_simple(obj, @attributes.first)
end
@inputs['distinct'].blank? ? result : result.uniq
end

def result_dialog_hash
key = @inputs['key'] || 'id'
@search_objects.each_with_object({}) do |obj, hash|
hash[result_simple(obj, key)] = result_simple(obj, @attributes.first)
end
end

def result_simple(obj, attr)
raise MiqAeException::MethodNotDefined,
"Undefined method #{attr} in class #{obj.class}" unless obj.respond_to?(attr.to_sym)
obj.send(attr.to_sym)
end

def set_result
target_object.attributes[attribute_name] = exp_value
@workspace.root['ae_result'] = 'ok'
end

def error_handler
disposition = @inputs['on_empty'] || 'error'
case disposition.to_sym
when :error
set_error
when :warn
set_warn
when :abort
set_abort
end
end

def set_error
$miq_ae_logger.error("Expression method ends")
@workspace.root['ae_result'] = 'error'
end

def set_abort
$miq_ae_logger.error("Expression method aborted")
raise MiqAeException::AbortInstantiation, "Expression method #{@name} aborted"
end

def set_warn
$miq_ae_logger.warn("Expression method ends")
@workspace.root['ae_result'] = 'warn'
set_default_value
end

def set_default_value
return unless @inputs.key?('default')
target_object.attributes[attribute_name] = @inputs['default']
end

def attribute_name
@inputs['result_attr'] || 'values'
end

def target_object
@workspace.get_obj_from_path(@inputs['result_obj'] || '.').tap do |obj|
raise MiqAeException::MethodExpressionTargetObjectMissing, @inputs['result_obj'] unless obj
end
end

def exp_value
type = @inputs['result_type'] || 'dialog_hash'
case type.downcase.to_sym
when :hash
result_hash(@search_objects.first)
when :dialog_hash
result_dialog_hash
when :array
result_array
when :simple
result_simple(@search_objects.first, @inputs['attributes'].first)
else
raise MiqAeException::MethodExpressionResultTypeInvalid, "Invalid Result type, should be hash, array or dialog_hash"
end
end

def get_args(num_token)
params = []
(1..num_token).each do |i|
key = "arg#{i}"
raise MiqAeException::MethodParameterNotFound, key unless @inputs.key?(key)
params[i - 1] = @inputs[key]
end
params
end
end
end
9 changes: 7 additions & 2 deletions lib/miq_automation_engine/engine/miq_ae_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ def self.invoke_inline(aem, obj, inputs)
raise MiqAeException::InvalidMethod, "Inline Method Language [#{aem.language}] not supported"
end

def self.invoke_expression(aem, obj, inputs)
exp_method = MiqAeEngine::MiqAeExpressionMethod.new(aem, obj, inputs)
exp_method.run
end

def self.invoke_uri(aem, obj, _inputs)
scheme, userinfo, host, port, registry, path, opaque, query, fragment = URI.split(aem.data)
raise MiqAeException::MethodNotFound, "Specified URI [#{aem.data}] in Method [#{aem.name}] has unsupported scheme of #{scheme}; supported scheme is file" unless scheme.downcase == "file"
Expand Down Expand Up @@ -44,7 +49,7 @@ def self.invoke(obj, aem, args)
aem.inputs.each do |f|
key = f.name
value = args[key]
value = obj.attributes[key] || f.default_value if value.nil?
value = obj.attributes[key] || obj.substitute_value(f.default_value) if value.nil?
inputs[key] = MiqAeObject.convert_value_based_on_datatype(value, f["datatype"])

if obj.attributes[key] && f["datatype"] != "string"
Expand All @@ -59,7 +64,7 @@ def self.invoke(obj, aem, args)

if obj.workspace.readonly?
$miq_ae_logger.info("Workspace Instantiation is READONLY -- skipping method [#{aem.fqname}] with inputs [#{inputs.inspect}]")
elsif ["inline", "builtin", "uri"].include?(aem.location.downcase.strip)
elsif %w(inline builtin uri expression).include?(aem.location.downcase.strip)
$miq_ae_logger.info("Invoking [#{aem.location}] method [#{aem.fqname}] with inputs [#{inputs.inspect}]")
return MiqAeEngine::MiqAeMethod.send("invoke_#{aem.location.downcase.strip}", aem, obj, inputs)
end
Expand Down
Loading