module GoodData::Helpers

Constants

AES_256_CBC_CIPHER
ENCODED_HIDDEN_PARAMS_KEY
ENCODED_PARAMS_KEY

Public Class Methods

create_lookup(collection, on) click to toggle source
# File lib/gooddata/helpers/global_helpers_params.rb, line 204
def create_lookup(collection, on)
  lookup = {}
  if on.is_a?(Array)
    collection.each do |e|
      key = e.values_at(*on)
      lookup[key] = [] unless lookup.key?(key)
      lookup[key] << e
    end
  else
    collection.each do |e|
      key = e[on]
      lookup[key] = [] unless lookup.key?(key)
      lookup[key] << e
    end
  end
  lookup
end
decode_params(params, options = {}) click to toggle source

Decodes params as they came from the platform. @params Parameter hash need to be decoded @option options [Boolean] :resolve_reference_params Resolve reference parameters in gd_encoded_params or not @return [Hash] Decoded parameters

# File lib/gooddata/helpers/global_helpers_params.rb, line 61
def decode_params(params, options = {})
  key = ENCODED_PARAMS_KEY.to_s
  hidden_key = ENCODED_HIDDEN_PARAMS_KEY.to_s
  data_params = params[key] || '{}'
  hidden_data_params = if params.key?(hidden_key) && params[hidden_key].nil?
                         "{\"#{hidden_key}\" : null}"
                       elsif params.key?(hidden_key)
                         params[hidden_key]
                       else
                         '{}'
                       end

  reference_values = []
  # Replace reference parameters by the actual values. Use backslash to escape a reference parameter, e.g: \${not_a_param},
  # the ${not_a_param} will not be replaced
  if options[:resolve_reference_params]
    data_params, reference_values = resolve_reference_params(data_params, params)
    hidden_data_params, = resolve_reference_params(hidden_data_params, params)
  end

  begin
    parsed_data_params = data_params.is_a?(Hash) ? data_params : JSON.parse(data_params)
  rescue JSON::ParserError => exception
    reason = exception.message
    reference_values.each { |secret_value| reason.gsub!("\"#{secret_value}\"", '"***"') }
    raise exception.class, "Error reading json from '#{key}', reason: #{reason}"
  end

  begin
    parsed_hidden_data_params = hidden_data_params.is_a?(Hash) ? hidden_data_params : JSON.parse(hidden_data_params)
  rescue JSON::ParserError => exception
    raise exception.class, "Error reading json from '#{hidden_key}'"
  end

  # Add the nil on ENCODED_HIDDEN_PARAMS_KEY
  # if the data was retrieved from API You will not have the actual values so encode -> decode is not losless. The nil on the key prevents the server from deleting the key
  parsed_hidden_data_params[ENCODED_HIDDEN_PARAMS_KEY] = nil unless parsed_hidden_data_params.empty?

  params.delete(key)
  params.delete(hidden_key)
  params = GoodData::Helpers.deep_merge(params, parsed_data_params)
  params = GoodData::Helpers.deep_merge(params, parsed_hidden_data_params)

  if options[:convert_pipe_delimited_params]
    convert_pipe_delimited_params = lambda do |args|
      args = args.select { |k, _| k.include? "|" }
      lines = args.keys.map do |k|
        hash = {}
        last_a = nil
        last_e = nil
        k.split("|").reduce(hash) do |a, e|
          last_a = a
          last_e = e
          a[e] = {}
        end
        last_a[last_e] = args[k]
        hash
      end

      lines.reduce({}) do |a, e|
        GoodData::Helpers.deep_merge(a, e)
      end
    end

    pipe_delimited_params = convert_pipe_delimited_params.call(params)
    params.delete_if do |k, _|
      k.include?('|')
    end
    params = GoodData::Helpers.deep_merge(params, pipe_delimited_params)
  end

  params
end
decrypt(database64, key) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 249
def decrypt(database64, key)
  if key.nil? || key.empty?
    GoodData.logger.warn('WARNING: No encryption key provided.')
    return 'no_key_provided'
  end

  data = Base64.decode64(database64)

  cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
  cipher.decrypt
  cipher.key = cipher_key = Digest::SHA256.digest(key)
  random_iv = data[0..15] # extract iv from first 16 bytes
  data = data[16..data.size - 1]
  cipher.iv = Digest::SHA256.digest(random_iv + cipher_key)[0..15]
  decrypted = cipher.update(data)
  decrypted << cipher.final
  decrypted
end
deep_dup(an_object) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 174
def deep_dup(an_object)
  case an_object
  when Array
    an_object.map { |it| GoodData::Helpers.deep_dup(it) }
  when Hash
    an_object.each_with_object(an_object.dup) do |(key, value), hash|
      hash[GoodData::Helpers.deep_dup(key)] = GoodData::Helpers.deep_dup(value)
    end
  when Object
    an_object.duplicable? ? an_object.dup : an_object
  end
end
deep_merge(source, target) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 187
def deep_merge(source, target)
  GoodData::Helpers::DeepMergeableHash[source].deep_merge(target)
end
diff(old_list, new_list, options = {}) click to toggle source

A helper which allows you to diff two lists of objects. The objects can be arbitrary objects as long as they respond to to_hash because the diff is eventually done on hashes. It allows you to specify several options to allow you to limit on what the sameness test is done

@param [Array<Object>] old_list List of objects that serves as a base for comparison @param [Array<Object>] new_list List of objects that is compared agianst the old_list @return [Hash] A structure that contains the result of the comparison. There are four keys. :added contains the list that are in new_list but were not in the old_list :added contains the list that are in old_list but were not in the new_list :same contains objects that are in both lists and they are the same :changed contains list of objects that changed along ith original, the new one and the list of changes

# File lib/gooddata/helpers/global_helpers_params.rb, line 149
def diff(old_list, new_list, options = {})
  old_list = old_list.map(&:to_hash)
  new_list = new_list.map(&:to_hash)

  fields = options[:fields]
  lookup_key = options[:key]

  old_lookup = Hash[old_list.map { |v| [v[lookup_key], v] }]

  res = {
    :added => [],
    :removed => [],
    :changed => [],
    :same => []
  }

  new_list.each do |new_obj|
    old_obj = old_lookup[new_obj[lookup_key]]
    if old_obj.nil?
      res[:added] << new_obj
      next
    end

    if fields
      sliced_old_obj = old_obj.slice(*fields)
      sliced_new_obj = new_obj.slice(*fields)
    else
      sliced_old_obj = old_obj
      sliced_new_obj = new_obj
    end
    if sliced_old_obj != sliced_new_obj
      difference = sliced_new_obj.to_a - sliced_old_obj.to_a
      differences = Hash[*difference.mapcat { |x| x }]
      res[:changed] << {
        old_obj: old_obj,
        new_obj: new_obj,
        diff: differences
      }
    else
      res[:same] << old_obj
    end
  end

  new_lookup = Hash[new_list.map { |v| [v[lookup_key], v] }]
  old_list.each do |old_obj|
    new_obj = new_lookup[old_obj[lookup_key]]
    if new_obj.nil?
      res[:removed] << old_obj
      next
    end
  end

  res
end
encode_hidden_params(params) click to toggle source

Encodes hidden parameters for passing them to GD execution platform. @param [Hash] params Parameters to be encoded @return [Hash] Encoded parameters

# File lib/gooddata/helpers/global_helpers_params.rb, line 53
def encode_hidden_params(params)
  encode_params(params, ENCODED_HIDDEN_PARAMS_KEY)
end
encode_params(params, data_key) click to toggle source

Encodes parameters for passing them to GD execution platform. Core types are kept and complex types (arrays, structures, etc) are JSON encoded into key hash “gd_encoded_params” or “gd_encoded_hidden_params”, depending on the ‘hidden’ method param. The two different keys are used because the params and hidden params are merged by the platform and if we use the same key, the param would be overwritten.

Core types are following:

  • Boolean (true, false)

  • Fixnum

  • Float

  • Nil

  • String

@param [Hash] params Parameters to be encoded @return [Hash] Encoded parameters

# File lib/gooddata/helpers/global_helpers_params.rb, line 28
def encode_params(params, data_key)
  res = {}
  nested = {}
  core_types = [FalseClass, Integer, Float, NilClass, TrueClass, String]
  params.each do |k, v|
    if core_types.include?(v.class)
      res[k] = v
    else
      nested[k] = v
    end
  end
  res[data_key] = nested.to_json unless nested.empty?
  res
end
encode_public_params(params) click to toggle source

Encodes public parameters for passing them to GD execution platform. @param [Hash] params Parameters to be encoded @return [Hash] Encoded parameters

# File lib/gooddata/helpers/global_helpers_params.rb, line 46
def encode_public_params(params)
  encode_params(params, ENCODED_PARAMS_KEY)
end
encrypt(data, key) click to toggle source

encrypts data with the given key. returns a binary data with the unhashed random iv in the first 16 bytes

# File lib/gooddata/helpers/global_helpers.rb, line 225
def encrypt(data, key)
  cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
  cipher.encrypt
  cipher.key = key = Digest::SHA256.digest(key)
  random_iv = cipher.random_iv
  cipher.iv = Digest::SHA256.digest(random_iv + key)[0..15]
  encrypted = cipher.update(data)
  encrypted << cipher.final
  # add unhashed iv to front of encrypted data

  Base64.encode64(random_iv + encrypted)
end
error(msg) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 40
def error(msg)
  GoodData.logger.error(msg)
  exit 1
end
find_goodfile(pwd = `pwd`.strip!, options = {}) click to toggle source

FIXME: Windows incompatible

# File lib/gooddata/helpers/global_helpers.rb, line 46
def find_goodfile(pwd = `pwd`.strip!, options = {})
  root = Pathname(options[:root] || '/')
  pwd = Pathname(pwd).expand_path
  loop do
    gf = pwd + '.gooddata'
    return gf if File.exist?(gf)
    pwd = pwd.parent
    break if root == pwd
  end
  nil
end
get_path(an_object, path = [], default = nil) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 85
def get_path(an_object, path = [], default = nil)
  return an_object if path.empty?
  return default if an_object.nil?

  path.reduce(an_object) do |a, e|
    a && a.key?(e) ? a[e] : default
  end
end
hash_dfs(thing) { |thing, key| ... } click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 102
def hash_dfs(thing, &block)
  if !thing.is_a?(Hash) && !thing.is_a?(Array) # rubocop:disable Style/GuardClause
  elsif thing.is_a?(Array)
    thing.each do |child|
      hash_dfs(child, &block)
    end
  else
    thing.each do |key, val|
      yield(thing, key)
      hash_dfs(val, &block)
    end
  end
end
home_directory() click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 98
def home_directory
  running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
end
interpolate_error_message(error) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 167
def interpolate_error_message(error)
  return unless error && error['error'] && error['error']['message']
  message = error['error']['message']
  params = error['error']['parameters']
  sprintf(message, *params)
end
interpolate_error_messages(errors) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 163
def interpolate_error_messages(errors)
  errors.map { |e| interpolate_error_message(e) }
end
join(master, slave, on, on2, options = {}) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 123
def join(master, slave, on, on2, options = {})
  full_outer = options[:full_outer]
  inner = options[:inner]

  lookup = create_lookup(slave, on2)
  marked_lookup = {}
  results = master.reduce([]) do |a, line|
    matching_values = lookup[line.values_at(*on)] || []
    marked_lookup[line.values_at(*on)] = 1
    if matching_values.empty? && !inner
      a << line.to_hash
    else
      matching_values.each do |matching_value|
        a << matching_value.to_hash.merge(line.to_hash)
      end
    end
    a
  end

  if full_outer
    (lookup.keys - marked_lookup.keys).each do |key|
      GoodData.logger.info(lookup[key])
      results << lookup[key].first.to_hash
    end
  end
  results
end
last_uri_part(uri) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 94
def last_uri_part(uri)
  uri.split('/').last
end
parse_http_exception(e) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 201
def parse_http_exception(e)
  JSON.parse(e.response)
end
prepare_mapping(what, for_what = nil, options = {}) click to toggle source

It takes what should be mapped to what and creates a mapping that is suitable for other internal methods. This means looking up the objects and returning it as array of pairs. The input can be given in several ways

  1. Hash. For example it could look like

{‘label.states.name’ => ‘label.state.id’}

2 Arrays. In such case the arrays are zipped together. First item will be swapped for the first item in the second array etc.

‘label.states.name’], [‘label.state.id’

@param what [Hash | Array] List/Hash of objects to be swapped @param for_what [Array] List of objects to be swapped @return [Array<GoodData::MdObject>] List of pairs of objects

# File lib/gooddata/helpers/global_helpers.rb, line 71
def prepare_mapping(what, for_what = nil, options = {})
  project = options[:project] || (for_what.is_a?(Hash) && for_what[:project]) || fail('Project has to be provided')
  mapping = if what.is_a?(Hash)
              whats = what.keys
              to_whats = what.values
              whats.zip(to_whats)
            elsif what.is_a?(Array) && for_what.is_a?(Array)
              whats.zip(to_whats)
            else
              [[what, for_what]]
            end
  mapping.pmap { |f, t| [project.objects(f), project.objects(t)] }
end
running_on_a_mac?() click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 155
def running_on_a_mac?
  RUBY_PLATFORM =~ /-darwin\d/
end
running_on_windows?() click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 151
def running_on_windows?
  RUBY_PLATFORM =~ /mswin32|mingw32/
end
simple_decrypt(database64, key) click to toggle source

Simple decrypt data with given key

# File lib/gooddata/helpers/global_helpers.rb, line 269
def simple_decrypt(database64, key)
  if key.nil? || key.empty?
    GoodData.logger.warn('WARNING: No encryption key provided.')
    return 'no_key_provided'
  end

  data = Base64.decode64(database64)

  cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
  cipher.decrypt
  cipher.key = key
  decrypted = cipher.update(data)
  decrypted << cipher.final
  decrypted
end
simple_encrypt(data, key) click to toggle source

Simple encrypt data with given key

# File lib/gooddata/helpers/global_helpers.rb, line 239
def simple_encrypt(data, key)
  cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
  cipher.encrypt
  cipher.key = key
  encrypted = cipher.update(data)
  encrypted << cipher.final

  Base64.encode64(encrypted)
end
stringify_values(value) click to toggle source
# File lib/gooddata/helpers/global_helpers_params.rb, line 222
def stringify_values(value)
  case value
  when nil
    value
  when Hash
    Hash[
      value.map do |k, v|
        [k, stringify_values(v)]
      end
    ]
  when Array
    value.map do |v|
      stringify_values(v)
    end
  else
    value.to_s
  end
end
titleize(str) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 116
def titleize(str)
  titleized = str.gsub(/[\.|_](.)/, &:upcase)
  titleized = titleized.tr('_', ' ')
  titleized[0] = titleized[0].upcase
  titleized
end
to_boolean(param) click to toggle source

Turns a boolean or string ‘true’ into boolean. Useful for bricks.

@param [Object] Something @return [Boolean] Returns true or false if the input is ‘true’ or true

# File lib/gooddata/helpers/global_helpers.rb, line 219
def to_boolean(param)
  param == 'true' || param == true ? true : false
end
underline(x) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 159
def underline(x)
  '=' * x.size
end
undot(params) click to toggle source
# File lib/gooddata/helpers/global_helpers.rb, line 191
def undot(params)
  # for each key-value config given
  params.map do |k, v|
    # dot notation to hash
    k.split('__').reverse.reduce(v) do |memo, obj|
      GoodData::Helper.DeepMergeableHash[{ obj => memo }]
    end
  end
end
zeroes(m, n, val = 0) click to toggle source

Creates a matrix with zeroes in all places. It is implemented as an Array of Arrays. First rows then columns.

@param [Integer] m Number of rows @param [Integer] n Number of cols @param [Integer] val Alternatively can fill in positions with different values than zeroes. Defualt is zero. @return [Array<Array>] Returns a matrix of zeroes

# File lib/gooddata/helpers/global_helpers.rb, line 211
def zeroes(m, n, val = 0)
  m.times.map { n.times.map { val } }
end

Private Class Methods

resolve_reference_params(data_params, params) click to toggle source
# File lib/gooddata/helpers/global_helpers_params.rb, line 243
def resolve_reference_params(data_params, params)
  reference_values = []
  regexps = Regexp.union(/\\\\/, /\\\$/, /\$\{([^\n\{\}]+)\}/)
  resolve_reference = lambda do |v|
    if v.is_a? Hash
      Hash[
        v.map do |k, v2|
          [k, resolve_reference.call(v2)]
        end
      ]
    elsif v.is_a? Array
      v.map do |v2|
        resolve_reference.call(v2)
      end
    elsif !v.is_a?(String)
      v
    else
      v.gsub(regexps) do |match|
        if match =~ /\\\\/
          data_params.is_a?(Hash) ? '\\' : '\\\\' # rubocop: disable Metrics/BlockNesting
        elsif match =~ /\\\$/
          '$'
        elsif match =~ /\$\{([^\n\{\}]+)\}/
          val = params["#{$1}"]
          if val
            reference_values << val
            val
          else
            GoodData.logger.warn "Reference '#{$1}' is not found!"
            match
          end
        end
      end
    end
  end

  data_params = if data_params.is_a? Hash
                  Hash[data_params.map do |k, v|
                    [k, resolve_reference.call(v)]
                  end]
                else
                  resolve_reference.call(data_params)
                end

  [data_params, reference_values]
end