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

Fix cancel button #40

Merged
merged 11 commits into from
Jan 23, 2019
34 changes: 22 additions & 12 deletions src/lib/y2configuration_management/salt/form_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ class FormController
# @param form [Y2ConfigurationManagement::Salt::Form]
# @param pillar [Y2ConfigurationManagement::Salt::Pillar]
def initialize(form, pillar)
@data = FormData.new(form, pillar)
@form = form
@pillar = pillar
@state = FormControllerState.new
data = FormData.from_pillar(form, pillar)
@state = FormControllerState.new(data)
end

# Renders the main form's dialog
Expand All @@ -83,7 +83,7 @@ def show_main_dialog
#
# @param locator [String] Locator of the element
def get(locator)
@data.get(locator)
form_data.get(locator)
end

# Opens a new dialog in order to add a new element to a collection
Expand All @@ -105,7 +105,7 @@ def edit(relative_locator)
# @param relative_locator [FormElementLocator] Elements's locator
def remove(relative_locator)
locator = state.locator.join(relative_locator)
@data.remove_item(locator)
form_data.remove_item(locator)
refresh_top_form
end

Expand Down Expand Up @@ -149,8 +149,8 @@ def show_popup(widget)
# version.
def next_handler
state.form_widget.store
data.update(form.root.locator, state.form_widget.result)
pillar.data = data.to_h.fetch("root", {})
form_data.update(form.root.locator, state.form_widget.result)
pillar.data = form_data.to_h.fetch("root", {})
puts pillar.dump
true
end
Expand All @@ -167,7 +167,7 @@ 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
state.close_form(rollback: result.nil?)
refresh_top_form
end

Expand All @@ -186,13 +186,16 @@ def run_popup(action, relative_locator)

# Updates the form data depending on the action
#
# When result is `nil`, it just restores the backup.
#
# @param result [Hash,nil] Result to process
def update_form_data(result)
return if result.nil?
return nil if result.nil?

if state.action == :add
@data.add_item(state.locator, result)
form_data.add_item(state.locator, result)
else
@data.update(state.locator, result)
form_data.update(state.locator, result)
end
end

Expand All @@ -205,9 +208,9 @@ def add_or_update_parent
parent = state.form_widget.result

if state.action == :edit
@data.update(state.locator, parent)
form_data.update(state.locator, parent)
else
@data.add_item(state.locator, parent)
form_data.add_item(state.locator, parent)
locator = state.locator.join(get(state.locator).size - 1)
state.replace(:edit, locator)
end
Expand All @@ -223,6 +226,13 @@ def item_form_for(locator)
form_widget.title = element.name
form_widget
end

# Returns the current form data
#
# @return [FormData] Form data
def form_data
@state.form_data
end
end
end
end
51 changes: 46 additions & 5 deletions src/lib/y2configuration_management/salt/form_controller_state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ module Salt
# Stores the UI state information
#
# This class holds information related to FormController. Basically, it behaves like a stack
# which contains informaion about the open widget forms. This data could have been stored
# directly in the FormController instance, but it has been extracted in order to keep the
# controller as simple as possible.
# which contains informaion about the open widget forms and the current form data. Those items
# could have been stored directly in the FormController instance, but they have been extracted
# in order to keep the controller as simple as possible.
class FormControllerState
# Constructor
def initialize
#
# @param data [FormData] Initial form data
def initialize(data)
@form_widgets = []
@locators = []
@actions = []
@form_data_snapshots = [data]
end

# Registers that a new form has been open
Expand All @@ -46,6 +49,7 @@ def open_form(action, locator, form_widget)
@form_widgets << form_widget
@locators << locator
@actions << action
backup_data
end

# Most recently open form widget
Expand All @@ -70,16 +74,53 @@ def locator
end

# Replaces the current action/locator
#
# @param new_action [Symbol]
# @param new_locator [FormElementLocator]
def replace(new_action, new_locator)
@actions[-1] = new_action
@locators[-1] = new_locator
end

# Removes the information related to the most recently open form widget
def close_form
def close_form(rollback: false)
@form_widgets.pop
@locators.pop
@actions.pop
if rollback
restore_backup
else
remove_backup
end
end

# Current form data
#
# @return [FormData]
def form_data
@form_data_snapshots.last
end

private

# Performs a backup of the current form data
def backup_data
@form_data_snapshots << form_data.copy
end

# Restores the last backup of the current form data by removing the current snapshot
def restore_backup
@form_data_snapshots.pop
end

# @return [Integer] Position of the previous backup
PREVIOUS_SNAPSHOT_POSITION = -2

# Clears the last backup if it exists
Copy link
Contributor

@teclator teclator Jan 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It removes the previous backup if I get correctly, isn't it?

def remove_backup
# This is another way of saying `top = @fdi.pop; @fdi.last = top`,
# or "shorten the snapshot stack but commit the last element"
@form_data_snapshots.delete_at(PREVIOUS_SNAPSHOT_POSITION)
end
end
end
Expand Down
92 changes: 23 additions & 69 deletions src/lib/y2configuration_management/salt/form_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,36 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "y2configuration_management/salt/pillar"
require "y2configuration_management/salt/form_data_reader"

module Y2ConfigurationManagement
module Salt
# This class holds data for a given Salt Formula Form
#
# @todo The support for collections is rather simple and nesting collections is not supported.
# We might consider using JSON Patch to modify the data.
class FormData
# @return [Y2ConfigurationManagement::Salt::Form] Form
attr_reader :form
# @return [Y2ConfigurationManagement::Salt::Pillar] Pillar
attr_reader :pillar

class << self
# @param form [Form] Form definition
# @param pillar [Pillar] Pillar to read the data from
# @return [FormData] Form data merging defaults and pillar values
def from_pillar(form, pillar)
FormDataReader.new(form, pillar).form_data
end
end

# Constructor
#
# @param form [Y2ConfigurationManagement::Salt::Form] Form
# @param pillar [Y2ConfigurationManagement::Salt::Form] Pillar
def initialize(form, pillar = Pillar.new(data: {}))
@data = data_for_form(form, pillar.data)
# @param form [Form] Form definition
# @param data [Hash] Initial data in hash form
def initialize(form, initial = {})
@form = form
@pillar = pillar
@data = initial
end

# Returns the value of a given element
#
# @param locator [String] Locator of the element
# @param locator [FormElementLocator] Locator of the element
def get(locator)
find_by_locator(@data, locator) || default_for(locator)
end
Expand Down Expand Up @@ -88,13 +91,20 @@ def to_h
data_for_pillar(@data)
end

# Returns a copy of this object
#
# @return [FormData]
def copy
self.class.new(form, Marshal.load(Marshal.dump(@data)))
end

private

# Recursively finds a value
#
# @param data [Hash,Array] Data structure to search for the value
# @param locator [FormElementLocator] Value locator
# @return [Object] Value
# @return [Object,nil] Found value; nil if no value was found for the given locator
def find_by_locator(data, locator)
return nil if data.nil?
return data if locator.first.nil?
Expand All @@ -116,62 +126,6 @@ def default_for(locator)
element ? element.default : nil
end

# Builds a hash to keep the form data
#
# @param form [Y2ConfigurationManagement::Salt::Form]
# @param data [Hash] Pillar data
# @return [Hash]
def data_for_form(form, data)
data_for_element(form.root, data)
end

# Builds a hash to keep the form element data
#
# @param element [Y2ConfigurationManagement::Salt::FormElement]
# @param data [Hash] Pillar data
# @return [Hash]
def data_for_element(element, data)
if element.is_a?(Container)
defaults = element.elements.reduce({}) { |a, e| a.merge(data_for_element(e, data)) }
{ element.id => defaults }
else
value = find_in_pillar_data(data, element.locator.rest) # FIXME: remove '.root'
value ||= element.default
{ element.id => data_from_pillar_collection(value, element) }
end
end

# Converts from a Pillar collection into form data
#
# Basically, a collection might be an array or a hash. The internal representation, however,
# is always an array, so it is needed to do the conversion.
#
# @param element [Y2ConfigurationManagement::Salt::FormElement]
# @param value [Array,Hash]
# @return
def data_from_pillar_collection(collection, element)
return nil if collection.nil?
return collection unless element.respond_to?(:keyed?) && element.keyed?
collection.map do |k, v|
{ "$key" => k }.merge(v)
end
end

# Finds a value within a Pillar
#
# @todo This API might be available through the Pillar class.
#
# @param data [Hash,Array] Data structure from the Pillar
# @param locator [FormElementLocator] Value locator
# @return [Object] Value
def find_in_pillar_data(data, locator)
return nil if data.nil?
return data if locator.first.nil?
key = locator.first
key = key.is_a?(Symbol) ? key.to_s : key
find_in_pillar_data(data[key], locator.rest)
end

# Returns data in a format to be used by the Pillar
#
# @param data [Object]
Expand Down