-
Notifications
You must be signed in to change notification settings - Fork 86
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
Fixes #21634 - Allows modification of output fields #275
Fixes #21634 - Allows modification of output fields #275
Conversation
Thanks @ofedoren! It looks good at a first glance. Would you mind adding some docs with examples how to change a commands output from a plugin? It would make testing easier plus we can refer to them in future. |
a7a8eda
to
a3f46d1
Compare
@tstrachota updated. I did some changes comparing to the last version of this commit and added some docs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for adding the docs. It was much easier to understand now.
Some changes are needed, I mentioned them all in the code.
There are some features that I'm missing, quite important ones in my opinion:
- finding fields, to make a modification on them. Eg.:
d.find_fields(:id => :name)[0].description = _('Some better description')
- inserting fields at a given position. Eg.:
d.insert_fields(:after_id => :operating_system) do # or before_id
label _('Ansible') do
# ...
end
end
lib/hammer_cli/output/definition.rb
Outdated
def at(path = []) | ||
return self if path.empty? | ||
field = expand_path(@fields, path.dup) | ||
return nil if field.nil? || !field.respond_to?(:output_definition) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should raise an error rather than return nil. In 99% of cases at will be directly followed by calling some other methods on the output definition. A meaningful error will make debugging much easier compared to "Undefined method 'append' for nil:NilClass"
.
lib/hammer_cli/output/definition.rb
Outdated
end | ||
end | ||
|
||
def select_fields!(by = {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there's some real-world use-case for this method. I can't imagine that somebody would want to keep just some fields and remove the rest.
It also doesn't work as expected. When I tested it on host info
HammerCLIForeman::Host::InfoCommand.output_definition.select_fields!(:labels => ['Id'], :deep_select => true)
the info command then did contain only two "Id" fields, but none of them displayed any data.
I think it's fine to simple remove this method.
lib/hammer_cli/output/definition.rb
Outdated
end | ||
end | ||
|
||
def replace_field(field_id, fields = nil, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is handy! I like using the block.
Does it also accept a single field? Using is as replace_field(:name, Field.new(:key => :surname))
would be nice too.
lib/hammer_cli/output/definition.rb
Outdated
def empty? | ||
@fields.empty? | ||
end | ||
|
||
private | ||
|
||
def build_definition(fields = nil, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using similar code at 2 places already. I think we could refactor it into a definition's public method:
def build(dsl = Dsl.new, &block)
dsl.build(&block) if block_given?
append(dsl.fields)
end
and also if we changed append to:
def append(fields)
unless fields.nil?
fields = [fields] unless fields.is_a? Array
@fields += fields
end
end
than it would be much easier for use at all the places and also decoupled from the dsl definition.
This method could then be just:
definition = self.class.new
definition.append(fields)
definition.build(&block)
definition
multiple_replace(i, definition.fields) | ||
end | ||
|
||
def at(path = []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that method. Could the path also accept a single symbol? Eg at(:interfaces)
doc/commands_modification.md
Outdated
deep_select: true/false, | ||
deep_delete: true/false, | ||
keys: [comma separated :field_key or/and :field_id], | ||
labels: [comma separated 'field_label'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you could write just "array"
lib/hammer_cli/output/definition.rb
Outdated
@@ -12,10 +12,117 @@ def append(fields) | |||
@fields += fields | |||
end | |||
|
|||
def delete_fields!(by = {}) | |||
return clear if by.empty? | |||
return deep_delete!(@fields, by) if by[:deep_delete] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use just by[:deep]
please. "delete" feels a bit redundant there.
a3f46d1
to
76b9076
Compare
@tstrachota, updated. If there is no need for select_fields! method, is there need for delete_fields! method then? I think they are very similar, so I removed it as well. I guess if there will be need to delete a field, it can be done via Also now it's possible to insert before/after or replace a field via one single method (see updated docs). I guess it's a good way to avoid code repetition, which in this case will be x3. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for you updates @ofedoren. Functionally-wise this is ok. I believe the code can be simplified at several places.
Would you mind describe also append
in you docs? I know it's already been there but it's key functionality. It took me some time to find out how to append fields after reading the docs (I nearly reported it as a missing feature ;-)).
test/unit/output/definition_test.rb
Outdated
end | ||
|
||
describe 'find_field' do | ||
# - |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing tests here?
doc/commands_modification.md
Outdated
# Expand a field with new definition | ||
HammerCLIForeman::Host::InfoCommand.output_definition | ||
.at(['some', :path]) | ||
.insert(:replace, :not_container_field_id, [Fields::Field.new(:first => :one)]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Examples will be more readable if you split the two ways of using insert
:
- with fields as an argument
- with dsl in a block
It's not very likely that both ways would be used in combination.
doc/commands_modification.md
Outdated
end | ||
# Simplified version | ||
HammerCLIForeman::Host::InfoCommand.output_definition | ||
.insert(:before, :field_id, Fields::Field.new(:first => :one)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs deserve correct parameters for Fields::Field.new
lib/hammer_cli/output/definition.rb
Outdated
return self if path.empty? | ||
field = expand_path(@fields, path.dup) | ||
if field.nil? || !field.respond_to?(:output_definition) | ||
raise ArgumentError, 'No output definition at path' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be very helpful if the error could mention what path was used
lib/hammer_cli/output/definition.rb
Outdated
index = @fields.find_index do |f| | ||
f.id == field_id | ||
end | ||
raise NameError, 'No such field' if index.nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, mentioning the name will make debugging a lot easier.
lib/hammer_cli/output/definition.rb
Outdated
def at(path = []) | ||
path = [path] unless path.is_a? Array | ||
return self if path.empty? | ||
field = expand_path(@fields, path.dup) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If path.dup
is there to avoid destructive effect of expand_path
, move it inside that method, please.
raise ArgumentError, 'No output definition at path' | ||
end | ||
field.output_definition | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw when I'm looking at the method... I think that you can easily drop expand_path
and re-use find_field
and at
recursively. This code should give you the same result:
def at(path = [])
path = [path] unless path.is_a? Array
return self if path.empty?
field = find_field(path[0])
if field.nil?
raise ArgumentError, "Field #{path[0]} not found"
end
if !field.respond_to?(:output_definition)
raise ArgumentError, "Field #{path[0]} doesn't have nested output definition"
end
field.output_definition.at(path[1..-1])
end
lib/hammer_cli/output/definition.rb
Outdated
dsl.build(&block) if block_given? | ||
append(dsl.fields) | ||
end | ||
|
||
def append(fields) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add support for passing &block
with dsl here too? I believe that this method is going to be used most so the more convenient it will be the better. It will also bring more consistency with insert
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: it would also make code in insert
simpler:
definition.append(fields, &block)
lib/hammer_cli/output/definition.rb
Outdated
@fields[field_index(field_id)] | ||
end | ||
|
||
def insert(mode = :after, field_id = nil, fields = nil, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about changing the signature to def insert(mode, field_id, fields = nil, &block)
?
Without field_id
the method will fail anyway so both parameters are required.
lib/hammer_cli/output/definition.rb
Outdated
index += 1 if mode == :after | ||
definition = self.class.new | ||
definition.append(fields) | ||
definition.build(&block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems too complicated to me. I guess the only reason why you created new definition is the fact that build
automatically appends the fields. How about moving injection of Dsl.new
into constructor and using just @dsl.build
here?
76b9076
to
dff778b
Compare
@tstrachota, updated. Thanks for suggestions for better method definitions! Also, before it gets merged, I want to be sure if this is an acceptable way of identifying a field by default: |
dff778b
to
19b60b8
Compare
@@ -4,14 +4,16 @@ module Fields | |||
|
|||
class Field | |||
|
|||
attr_reader :label | |||
attr_reader :path | |||
attr_accessor :label |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to enable the writers and allow change these form outside?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're using labels to set field's ID, which is used to address a field, I don't think it's a good idea now :)
But again, is this an acceptable way? We could allow label changing from outside, but then we shouldn't identify fields by labels I guess..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding setting the default for id I think it is okay. Maybe with 'key' preferred over the 'label'.
In the original comment I was referring more to all three attr_accessors in general. It does not feel correct to modify any of these attributes on existing field. Wouldn't it be better to replace filed with a new field?
I can imagine changing label in container or collection could be awkward, so ability to change label might be helpful in some cases. The :id would need to be a method getting the id dynamically.
lib/hammer_cli/output/dsl.rb
Outdated
@@ -20,6 +20,7 @@ def build(&block) | |||
def field(key, label, type=nil, options={}, &block) | |||
options[:path] = current_path.clone | |||
options[:path] << key if !key.nil? | |||
options[:field_id] = options[:field_id] || key || options[:key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is inconsistent with the Field class defaults. If field is created directly without the :field_id
the default id is 'label'. Via dsl the 'key' wins. What about not setting the default value in here and leave it up to Field class?
lib/hammer_cli/output/fields.rb
Outdated
|
||
def initialize(options={}) | ||
@hide_blank = options[:hide_blank].nil? ? false : options[:hide_blank] | ||
@hide_missing = options[:hide_missing].nil? ? true : options[:hide_missing] | ||
@path = options[:path] || [] | ||
@label = options[:label] | ||
@id = options[:field_id] || label || options[:key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason the option is called :field_id
and the attribute just id
? It is not consistent with other options.
@ofedoren, I've left some comments and questions inline. Besides that it looks nice and powerful. |
19b60b8
to
c4f3594
Compare
Updated with @mbacovsky suggestions. |
@ofedoren thanks for quick update! I'm sorry I didn't noticed earlier but I played with the feature and wanted to found out how it behaves when the id is not found. Similarly as in help extension it raises error which prevents whole plugin from loading. I'd prefer it to print warning, log whatever we have for debuging later and print as much as we can. The reason is to prevent e.g. one removed field in hammer-cli-foreman from loading whole other plugin. |
c4f3594
to
72ad363
Compare
@mbacovsky, updated. I added handling similar to one from help extension. So, if a field is not found, then the whole output extension block will fail, but the plugin will be loaded and other extension blocks will be applied (if there are no errors). Also, updated docs. |
[test hammer] |
@ofedoren a way better, thanks! While testing I found another issue. I had the I also found out that when the field is created via dsl it won't have the diff --git a/lib/hammer_cli/output/dsl.rb b/lib/hammer_cli/output/dsl.rb
index e0cf7ab..5cf6a38 100644
--- a/lib/hammer_cli/output/dsl.rb
+++ b/lib/hammer_cli/output/dsl.rb
@@ -19,7 +19,10 @@ module HammerCLI::Output
def field(key, label, type=nil, options={}, &block)
options[:path] = current_path.clone
- options[:path] << key if !key.nil?
+ unless key.nil?
+ options[:path] << key
+ options[:key] = key
+ end
options[:label] = label
type ||= Fields::Field Otherwise it looks good! |
lib/hammer_cli/output/definition.rb
Outdated
|
||
def field_index(field_id) | ||
index = @fields.find_index do |f| | ||
f.id == field_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had an idea to match the field by ANY of those identifiers like:
f.match_id?(field_id)
where the matcher is defined as
def match_id?(field_id)
@options[:id] == filed_id || @options[:key] == field_id || @label == field_id
end
It could prevent breaking the extension when e.g. the field_id is added.
What do you think?
72ad363
to
0bae9e8
Compare
@mbacovsky, updated. Thanks for Also, I'm not sure if there is a problem with addressing by labels for localized Hammer. It seems to be working if address with |
lib/hammer_cli/output/fields.rb
Outdated
end | ||
|
||
def match_id?(field_id) | ||
@options[:id] == field_id || @options[:key] == field_id || @label == field_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to add || @label == _(field_id)
here? It could seamlessly solve the issue with translations.
0bae9e8
to
f8173ad
Compare
@tstrachota, updated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm good. @mbacovsky ?
@@ -146,4 +146,3 @@ def self.main_makefile | |||
|
|||
domain = [HammerCLI::I18n::LocaleDomain.new, HammerCLI::I18n::SystemLocaleDomain.new].find { |d| d.available? } | |||
HammerCLI::I18n.add_domain(domain) if domain | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid changing files that aren't affected by the purpose of the patch.
[test hammer] |
@mbacovsky ping |
[test hammer] |
All comments have been addressed and the changes work fine. Merging, thanks @ofedoren this is an important feature! |
Thanks @ofedoren, @tstrachota! |
No description provided.