Skip to content

Commit

Permalink
Merge 1b76316 into 2d6b127
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Jan 17, 2019
2 parents 2d6b127 + 1b76316 commit c10b01d
Show file tree
Hide file tree
Showing 29 changed files with 872 additions and 178 deletions.
46 changes: 35 additions & 11 deletions src/lib/y2configuration_management/salt/form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

require "yaml"
require "yast"
require "y2configuration_management/salt/form_element_locator"

module Y2ConfigurationManagement
module Salt
Expand Down Expand Up @@ -56,7 +57,18 @@ def self.from_file(path)
nil
end

# Convenience method for looking for a particular FormElement.
# Recursively looks for a particular {FormElement}
#
# @example look for a FormElement by a specific name, locator or id
#
# f = Y2ConfigurationManagemenet.from_file("form.yml")
# f.find_element_by(name: "subnets") #=> <Collection @name="subnets">
# locator = FormElementLocator.from_string(".root.dhcpd")
# f.find_element_by(locator: locator) #=> <Container @name="dhcpd">
# f.find_element_by(id: "hosts") #=> <Container @id="hosts">
#
# @param arg [Hash]
# @return [FormElement, nil]
def find_element_by(arg)
root.find_element_by(arg)
end
Expand Down Expand Up @@ -92,7 +104,6 @@ def self.class_for(type)
#
# scalar values, groups and collections
class FormElement
LOCATOR_DELIMITER = ".".freeze
# @return [String] the key for the pillar
attr_reader :id
# @return [Symbol]
Expand Down Expand Up @@ -130,8 +141,9 @@ def initialize(id, spec, parent:)
#
# @return [String]
def locator
prefix = parent ? parent.locator : ""
"#{prefix}#{LOCATOR_DELIMITER}#{id}"
return FormElementLocator.new([id]) if parent.nil?
return parent.locator if parent.is_a?(Collection)
parent.locator.join(id)
end

private
Expand Down Expand Up @@ -183,15 +195,9 @@ def initialize(id, spec, parent:)

# Recursively looks for a particular {FormElement}
#
# @example look for a FormElement by a specific name, locator or id
#
# f = Y2ConfigurationManagemenet.from_file("form.yml")
# f.find_element_by(name: "subnets") #=> <Collection @name="subnets"
# f.find_element_by(locator: ".root.dhcpd") #=> <Container @name="dhcpd"
# f.find_element_by(id: "hosts") #=> <Container @id="hosts"
#
# @param arg [Hash]
# @return [FormElement, nil]
# @see Form#find_element_by
def find_element_by(arg)
return self if arg.any? { |k, v| public_send(k) == v }

Expand Down Expand Up @@ -221,10 +227,13 @@ def build_elements(spec)
class Collection < FormElement
# @return [Integer] lowest number of elements that needs to be defined
attr_reader :min_items

# @return [Integer] highest number of elements that needs to be defined
attr_reader :max_items

# @return [String] name for the members of the collection
attr_reader :item_name

# list of elements (let's see if we promote it to a class)
# or children or whatever
attr_reader :prototype
Expand All @@ -246,6 +255,21 @@ def initialize(id, spec, parent:)
@default = spec.fetch("$default", [])
end

# Recursively looks for a particular {FormElement}
#
# @param arg [Hash]
# @return [FormElement, nil]
# @see Form#find_element_by
def find_element_by(arg)
if prototype.is_a?(Array)
prototype.each do |element|
return element if element.find_element_by(arg)
end
end

prototype.find_element_by(arg)
end

private

# Return a single or group of {FormElement}s based on the prototype given
Expand Down
156 changes: 103 additions & 53 deletions src/lib/y2configuration_management/salt/form_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
require "y2configuration_management/salt/form_builder"
require "y2configuration_management/salt/form_data"
require "y2configuration_management/widgets/form_popup"
require "y2configuration_management/salt/form_controller_state"
require "yaml"

Yast.import "CWM"
Yast.import "Wizard"

module Y2ConfigurationManagement
module Salt
# This class takes care of driving the form for a Salt Formula.
# This class takes care of driving the forms for a Salt Formula.
#
# The constructor of this class takes a ({Y2ConfigurationManagement::Salt::Form a form
# description}) which will be used to build the UI when the #show_main_dialog method
Expand All @@ -47,6 +48,7 @@ module Salt
# controller = FormController.new(formula_form, formula_pillar)
# controller.show_main_dialog
class FormController
include Yast::Logger
include Yast::I18n
include Yast::UIShortcuts

Expand All @@ -58,55 +60,53 @@ def initialize(form, pillar)
@data = FormData.new(form, pillar)
@form = form
@pillar = pillar
@state = FormControllerState.new
end

# Renders the main form's dialog
def show_main_dialog
form_widget = form_builder.build(form.root.elements)
form_widget.value = get(form.root.locator)
state.open_form(:edit, form.root.locator, form_widget)
Yast::Wizard.CreateDialog
Yast::CWM.show(
HBox(main_form),
ret = Yast::CWM.show(
HBox(form_widget),
caption: form.root.name, next_handler: method(:next_handler)
)
state.close_form
ret
ensure
Yast::Wizard.CloseDialog
end

# Convenience method for returning the value of a given element
#
# @param locator [String] Locator of the element
# @param index [Integer] Element's index when locator refers to a collection
def get(locator, index = nil)
@data.get(locator, index)
def get(locator)
@data.get(locator)
end

# Opens a new dialog in order to add a new element to a collection
#
# @param locator [String] Collection's locator
def add(locator)
result = edit_item(locator, nil)
return if result.nil?
@data.add_item(locator, result.values.first)
refresh_main_form
# @param relative_locator [FormElementLocator] Collection's locator
def add(relative_locator)
add_or_edit_item(:add, relative_locator)
end

# Opens a new dialog in order to edit an element within a collection
#
# @param locator [String] Collection's locator
# @param index [Integer] Element's index
def edit(locator, index)
result = edit_item(locator, get(locator, index))
return if result.nil?
@data.update_item(locator, index, result.values.first)
refresh_main_form
# @param relative_locator [FormElementLocator] Elements's locator
def edit(relative_locator)
add_or_edit_item(:edit, relative_locator)
end

# Removes an element from a collection
#
# @param locator [String] Collection's locator
# @param index [Integer] Element's index
def remove(locator, index)
@data.remove_item(locator, index)
refresh_main_form
# @param relative_locator [FormElementLocator] Elements's locator
def remove(relative_locator)
locator = state.locator.join(relative_locator)
@data.remove_item(locator)
refresh_top_form
end

private
Expand All @@ -120,59 +120,109 @@ def remove(locator, index)
# @return [FormData]
attr_reader :data

# @return [State]
attr_reader :state

# Returns the form builder
#
# @return [Y2ConfigurationManagement::Salt::FormBuilder]
def form_builder
@form_builder ||= Y2ConfigurationManagement::Salt::FormBuilder.new(self)
end

# Renders the main form's dialog
def main_form
return @main_form if @main_form
@main_form = form_builder.build(form.root.elements)
@main_form.value = get(form.root.locator)
@main_form
end

# Refreshes the main form content
def refresh_main_form
data.update(form.root.locator, main_form.current_values)
main_form.refresh(get(form.root.locator))
end

# Displays a form to edit a given item
#
# @param locator [String] Collection locator
# @param item [Object] Item to edit
# @return [Hash,nil] edited data; `nil` when the user cancels the dialog
def edit_item(locator, item)
element = form.find_element_by(locator: locator)
widget_form = form_builder.build(element.prototype)
wid = locator[1..-1].split(".").last
widget_form.value = { wid => item }
show_popup(element.name, widget_form)
# Refreshes the most recently open form widget
def refresh_top_form
state.form_widget.refresh(get(state.locator))
end

# Displays a popup
#
# @param title [String] Popup title
# @param widget [Array<CWM::AbstractWidget>] Popup content (as an array of CWM widgets)
# @return [Hash,nil] Dialog's result
def show_popup(title, widget)
Widgets::FormPopup.new(title, widget).run
def show_popup(widget)
Widgets::FormPopup.new(widget.title, widget).run
widget.result
end

# @todo This version is just for debugging purposes. It should be replaced with a meaningful
# version.
def next_handler
main_form.store
data.update(form.root.locator, main_form.result)
state.form_widget.store
data.update(form.root.locator, state.form_widget.result)
pillar.data = data.to_h.fetch("root", {})
puts pillar.dump
true
end

# Displays a form to add/edit a given item and updates the form data
#
# When the locator refers to a collection, it assumes that the
# item should be added.
#
# @param action [Symbol] :add or :edit
# @param relative_locator [FormElementLocator] Collection locator relative to the popup
# @return [Hash,nil] edited data; `nil` when the user cancels the dialog
def add_or_edit_item(action, relative_locator)
add_or_update_parent
result = run_popup(action, relative_locator)
update_form_data(result)
state.close_form
refresh_top_form
end

# Displays the add/edit form and returns the user's input
#
# @param action [Symbol] :add or :edit
# @param relative_locator [FormElementLocator] Collection locator relative to the popup
# @return [Hash, nil] User's input or nil if the user pushed canceled the dialog
def run_popup(action, relative_locator)
abs_locator = state.locator.join(relative_locator)
form_widget = item_form_for(abs_locator)
form_widget.value = get(abs_locator) if action == :edit
state.open_form(action, abs_locator, form_widget)
show_popup(form_widget)
end

# Updates the form data depending on the action
#
# @param result [Hash,nil] Result to process
def update_form_data(result)
return if result.nil?
if state.action == :add
@data.add_item(state.locator, result)
else
@data.update(state.locator, result)
end
end

# Adds or updates the parent of a collection item
#
# When trying to add an element to a collection, it is necessary that the parent
# object exists.
def add_or_update_parent
state.form_widget.store
parent = state.form_widget.result

if state.action == :edit
@data.update(state.locator, parent)
else
@data.add_item(state.locator, parent)
locator = state.locator.join(get(state.locator).size - 1)
state.replace(:edit, locator)
end
end

# Builds a form widget for a given locator
#
# @param locator [FormElementLocator] Form element to represent
# @return [Y2ConfigurationManagement::Widgets::Form] Form widget
def item_form_for(locator)
element = form.find_element_by(locator: locator.unbounded)
form_widget = form_builder.build(element.prototype.elements)
form_widget.title = element.name
form_widget
end
end
end
end

0 comments on commit c10b01d

Please sign in to comment.