class Eco::API::Organization::TagTree

Provides helpers to deal with tagtrees.

Attributes

children_count[R]
depth[R]
enviro[R]
nodes[R]
path[R]
tag[R]

Public Class Methods

new(tagtree = [], depth: -1, path: [], enviro: nil) click to toggle source

@example Node format:

{"tag": "NODE NAME", "nodes": subtree}

@example Tree/subtree format:

[[Node], ...]

@example Input format example:

tree = [{"tag" => "AUSTRALIA", "nodes" => [
    {"tag" => "SYDNEY", "nodes" => []}
]}]
tree = TagTree.new(tree.to_json)

@param tagtree [String] representation of the tagtree in json.

# File lib/eco/api/organization/tag_tree.rb, line 21
def initialize(tagtree = [], depth: -1, path: [], enviro: nil)
  case tagtree
  when String
    @source = JSON.parse(tagtree)
  else
    @source = tagtree
  end
  fatal("You are trying to initialize a TagTree with a null tagtree") if !@source
  fatal("Expecting Environment object. Given: #{enviro}") if enviro && !enviro.is_a?(API::Common::Session::Environment)
  @enviro = enviro

  @depth = depth
  @tag = @source.is_a?(Array) ? nil : @source.dig('tag')&.upcase

  @path  = path || []
  @path.push(@tag) unless !@tag

  nodes = @source.is_a?(Array) ? @source : @source.dig('nodes') || []
  @nodes = nodes.map {|cnode| TagTree.new(cnode, depth: @depth + 1, path: @path.dup, enviro: @enviro)}
  @children_count = @nodes.count

  init_hashes
end

Public Instance Methods

as_json() click to toggle source

@return [Array] where `Hash` is a `node` `{“tag” => TAG, “nodes”: Array}`

# File lib/eco/api/organization/tag_tree.rb, line 66
def as_json
  nodes_json = nodes.map {|node| node.as_json}
  if top?
    nodes_json
  else
    {
      "tag"   => tag,
      "nodes" => nodes_json
    }
  end
end
default_tag(*values) click to toggle source

Helper to decide which among the tags will be the default.

  • take the deepest tag (the one that is further down in the tree)

  • if there are different options (several nodes at the same depth):

    * take the common node between them (i.e. you have Hamilton and Auckland -> take New Zealand)
    * if there's no common node between them, take the `first`, unless they are at top level of the tree
    * to the above, take the `first` also on top level, but only if there's 1 level for the entire tree

@param [Array<String>] values list of tags. @return [String] default tag.

# File lib/eco/api/organization/tag_tree.rb, line 203
def default_tag(*values)
  values = filter_tags(values)
  nodes = []; depth = -1
  values.each do |tag|
    raise("Couldn't find the node of #{tag} in the tag-tree definition") unless cnode = node(tag)

    if cnode.depth > depth
      nodes = [cnode]
      depth = cnode.depth
    elsif cnode.depth == depth
      nodes.push(cnode)
    end
  end

  default_tag = nil
  if nodes.length > 1
    common      = nodes.reduce(self.tags.reverse) {|com, cnode| com & cnode.path.reverse}
    default_tag = common.first if common.length > 0 && depth > 0
  end
  default_tag = nodes.first&.tag  if !default_tag && ( (depth > 0) || flat?)
  default_tag
end
diff(tagtree, differences: {}, level: 0, **options) click to toggle source

@return [Array] with the differences

# File lib/eco/api/organization/tag_tree.rb, line 56
def diff(tagtree, differences: {}, level: 0, **options)
  require 'hashdiff'
  Hashdiff.diff(self.as_json, tagtree.as_json, **options.slice(:array_path, :similarity, :use_lcs))
end
dup() click to toggle source

@return [Eco::API::Organization::TagTree]

# File lib/eco/api/organization/tag_tree.rb, line 51
def dup
  self.class.new(as_json)
end
empty?() click to toggle source

@return [Boolean] `true` if there are tags in the node, `false` otherwise.

# File lib/eco/api/organization/tag_tree.rb, line 79
def empty?
  @has_tags.empty?
end
filter_tags(list) click to toggle source

Filters tags out that do not belong to the tree @param list [Array<String>] source tags. @return [Array<String>]

# File lib/eco/api/organization/tag_tree.rb, line 145
def filter_tags(list)
  return [] unless list && list.is_a?(Array)
  list.select {|str| tag?(str)}
end
flat?() click to toggle source

@return [Integer] if there's only top level.

# File lib/eco/api/organization/tag_tree.rb, line 94
def flat?
  self.total_depth <= 0
end
node(key) click to toggle source

Finds a subtree node. @param key [String] parent node of subtree. @return [TagTree, nil] if the tag `key` is a node, returns that node.

# File lib/eco/api/organization/tag_tree.rb, line 137
def node(key)
  return nil unless tag?(key)
  @hash_tags[key.upcase]
end
subtag?(key) click to toggle source

Verifies if a tag exists in the subtree(s). @param key [String] tag to verify. @return [Boolean]

# File lib/eco/api/organization/tag_tree.rb, line 123
def subtag?(key)
  subtags.include?(key&.upcase)
end
subtags() click to toggle source

Gets all but the upper level tags of the current node tree. @return [Array<String>]

# File lib/eco/api/organization/tag_tree.rb, line 116
def subtags
  tags - tags(depth: depth)
end
tag=(value) click to toggle source

Updates the tag of the current tree

# File lib/eco/api/organization/tag_tree.rb, line 46
def tag=(value)
  @tag = value
end
tag?(key) click to toggle source

Verifies if a tag exists in the tree. @param key [String] tag to verify. @return [Boolean]

# File lib/eco/api/organization/tag_tree.rb, line 130
def tag?(key)
  @hash_tags.key?(key&.upcase)
end
tags(depth: nil) click to toggle source

Gets all the tags of the current node tree. @note

- this will include the upper level tag(s) as well
- to get all but the upper level tag(s) use `subtags` method instead

@param depth [Integer] if empty, returns the list of tag nodes of that level. Otherwise the list of tag nodes of the entire subtree. @return [Array<String>]

# File lib/eco/api/organization/tag_tree.rb, line 104
def tags(depth: nil)
  if !depth || depth < 0
    @hash_tags.keys
  else
    @hash_tags.select do |t, n|
      n.depth == depth
    end.keys
  end
end
top?() click to toggle source
# File lib/eco/api/organization/tag_tree.rb, line 61
def top?
  depth == -1
end
total_depth() click to toggle source

@return [Integer] the highest `depth` of all the children.

# File lib/eco/api/organization/tag_tree.rb, line 84
def total_depth
  @total_depth ||= if children_count > 0
      deepest_node = nodes.max_by {|node| node.total_depth}
      deepest_node.depth
    else
      depth
    end
end
user_tags(initial: [], final: [], preserve_custom: true, add_custom: false) click to toggle source

Helper to assign tags to a person account.

  • It preserves the `:initial` order, in case the `:final` tags are the same

@example Usage example:

tree = [{"tag" => "Australia", "nodes" => [
     {"tag" => "SYDNEY", "nodes" => []},
     {"tag" => "MELBOURNE", "nodes" => []}
]}]

tree = TagTree.new(tree.to_json)
original = ["SYDNEY", "RISK"]
final    = ["MELBOURNE", "EVENT"]

tree.user_tags(initial: original, final: final) # out: ["MELBOURNE", "RISK"]
tree.user_tags(initial: original, final: final, preserve_custom: false) # out: ["MELBOURNE"]
tree.user_tags(initial: original, final: final, add_custom: true) # out: ["MELBOURNE", "RISK", "EVENT"]
tree.user_tags(initial: original, final: final, preserve_custom: false, add_custom: true) # out: ["MELBOURNE", "EVENT"]

@param initial [Array<String>] original tags a person has in their account. @param final [Array<String>] target tags the person should have in their account afterwards. @param preserve_custom [Boolean] indicates if original tags that are not in the tree should be added/preserved. @param add_custom [Boolean] indicates if target tags that are not in the tree should be really added. @return [Array<String>] with the treated final tags.

# File lib/eco/api/organization/tag_tree.rb, line 183
def user_tags(initial: [], final: [], preserve_custom: true, add_custom: false)
  initial = [initial].flatten.compact
  final   = [final].flatten.compact
  raise "Expected Array for initial: and final:" unless initial.is_a?(Array) && final.is_a?(Array)
  final    = filter_tags(final) unless add_custom
  custom   = initial - filter_tags(initial)
  final    = final + custom     if preserve_custom
  new_tags = final - initial
  # keep same order as they where
  (initial & final) + new_tags
end

Protected Instance Methods

hash() click to toggle source
# File lib/eco/api/organization/tag_tree.rb, line 228
def hash
  @hash_tags
end
hash_paths() click to toggle source
# File lib/eco/api/organization/tag_tree.rb, line 232
def hash_paths
  @hash_paths
end

Private Instance Methods

fatal(msg) click to toggle source
# File lib/eco/api/organization/tag_tree.rb, line 251
def fatal(msg)
  raise msg if !@enviro
  @enviro.logger.fatal(msg)
  raise msg
end
init_hashes() click to toggle source
# File lib/eco/api/organization/tag_tree.rb, line 238
def init_hashes
  @hash_tags  = {}
  @hash_tags[@tag] = self unless !@tag
  @hash_tags = @nodes.reduce(@hash_tags) do |h,n|
    h.merge(n.hash)
  end
  @hash_paths = {}
  @hash_paths[@tag] = @path
  @hash_paths = @nodes.reduce(@hash_paths) do |h,n|
    h.merge(n.hash_paths)
  end
end
warn(msg) click to toggle source
# File lib/eco/api/organization/tag_tree.rb, line 257
def warn(msg)
  raise msg if !@enviro
  @enviro.logger.warn(msg)
end