Module: Tarot

Defined in:
lib/tarot.rb

Overview

Módulo para interagir com a API do Metabase.

Defined Under Namespace

Modules: Config Classes: Database, DatabaseNotFound, LoginError, QueryError, TarotError

Constant Summary collapse

SECRET_TOKEN_PATH =

Caminho para o arquivo que carrega o token de autenticação do usuário + a data de expiração do token

"#{Dir.pwd}/.secret_token".freeze

Class Method Summary collapse

Class Method Details

.clear_tokenObject

Limpa o token de sessão, armazenado localmente.



244
245
246
# File 'lib/tarot.rb', line 244

def clear_token
  File.write(SECRET_TOKEN_PATH, '{}')
end

.consultation(filepath, &block) ⇒ void

This method returns an undefined value.

Grava os resultados de um bloco em um arquivo YAML com o mesmo nome passado pelo argumento filepath (porém terminando em .yaml), incluindo metadados de quando foi gerado, e do script que gerou o arquivo.

Examples:

Exemplo

# exemplos/test.rb
consultation(__FILE__) do
  db('minha db').query!('SELECT * FROM plans WHERE id = 3;')
end
# Isto irá criar um arquivo 'exemplos/test.yaml' com os resultados do bloco e metadados.

Parameters:

  • filepath (String)

    Caminho do arquivo de origem, cujo conteúdo será usado nos metadados. É esperado que se preencha com __FILE__.

Yield Returns:

  • (Hash)

    Bloco que retorna um hash com os dados a serem gravados.



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/tarot.rb', line 128

def consultation(filepath, &block)
  target_file = filepath.gsub(/\.rb\z/, '.yaml')

  File.write(target_file, YAML.dump({
    metadata: {
      generated_at: DateTime.now.iso8601,
      generated_by: File.read(filepath)
    },
    results: block.call()
  }))
  puts "Recorded #{target_file}"
end

.database(name) ⇒ Database Also known as: db

Encontra uma database pelo nome/alias.

Parameters:

  • name (String)

    Nome ou alias da database.

Returns:

Raises:



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/tarot.rb', line 145

def database(name)
  if Config.data.database_aliases.keys.include?(name.to_sym)
    name = Config.data.database_aliases[name.to_sym]
  end

  result = databases.find { |d| d.name == name }

  unless result
    message = <<~TEXT

      Database not found: #{name}
      To see available databases, run: tarot dbs
    TEXT

    raise DatabaseNotFound, message
  end

  result
end

.database_namesString

Retorna uma lista do nome de todos os nomes de databases disponíveis. Caso exista um alias para um nome, ele substituirá o nome.

Returns:

  • (String)

    String com o nomes das databases.



169
170
171
172
173
174
175
176
177
178
# File 'lib/tarot.rb', line 169

def database_names
  names = Tarot.databases.map(&:name)
  aliases = Tarot::Config.data.database_aliases.keys.map(&:to_s)

  names.filter! do |name|
    !Tarot::Config.data.database_aliases.values.include?(name)
  end

  (names + aliases)
end

.databasesArray<Database>

Retorna uma lista de todas as databases disponíveis.

Returns:

  • (Array<Database>)

    Array de objetos Database.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/tarot.rb', line 182

def databases
  unless @databases
    uri = Config.data.url + '/api/database'
    http = Net::HTTP.new(uri.hostname, uri.port)
    http.use_ssl = uri.scheme == 'https'

    request = Net::HTTP::Get.new(uri)
    request['Accept'] = 'application/json'
    request['X-Metabase-Session'] = fetch_token

    response = http.request(request)
    @databases = JSON.parse(response.body).map do |payload|
      Database.new(payload['id'], payload['name'])
    end
  end
  @databases
end

.fetch_tokenString

Obtém o token de sessão do Metabase, solicitando ao usuário se necessário.

Returns:

  • (String)

    Token de sessão.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/tarot.rb', line 265

def fetch_token
  token_json = File.exist?(SECRET_TOKEN_PATH) ? JSON.load(File.read(SECRET_TOKEN_PATH)) : {}
  token = token_json['token']

  if token.nil?
    uri = Config.data.url + '/api/session'
    http = Net::HTTP.new(uri.hostname, uri.port)
    http.use_ssl = uri.scheme == 'https'

    request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
     = {}
    puts 'Login to Metabase'
    print 'Username: '
    ['username'] = STDIN.gets.chomp
    print 'Password: '
    ['password'] = STDIN.noecho(&:gets).chomp
    puts "\n\n"

    request.body = JSON.dump()
    begin
      response = http.request(request)
    rescue OpenSSL::SSL::SSLError
      puts "#{Config.data.url.to_s} seems to be unreachable at the moment"
    end

    token = JSON.parse(response.body)['id']

    raise(LoginError, 'Wrong credentials') if token.nil?

    File.write(SECRET_TOKEN_PATH, JSON.dump(token: token, expire_at: DateTime.now + Config.data.session_expire_days))
    puts 'Authorized! :>'
  else
    expire_at = DateTime.parse(token_json['expire_at'])
    if DateTime.now >= expire_at
      puts 'Your token expired'
      clear_token
      fetch_token
    end
  end

  token
end

Imprime a cheatsheet na tela



249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/tarot.rb', line 249

def print_cheatsheet
  puts <<~TEXT
                                    Tarot Cheatsheet

      ┌─────────────────────────┬────────────────────────────────────────────────────┐
      │ Ver databases           │ puts database_names                                │
      │ Puxar database por nome | db('nome')                                         │
      │ Fazer query             │ minha_db.query!('SELECT * FROM mytable')           │
      │ Limpar dados de sessão  │ clear_token                                        │
      │ Ver essa mensagem       │ print_cheatsheet                                   │
      └─────────────────────────┴────────────────────────────────────────────────────┘
  TEXT
end

.query(database, sql) ⇒ Object

Executa uma consulta SQL e retorna o resultado.

Parameters:

  • database (Database)

    Base de dados onde executar a consulta. Use Tarot.database(‘minah db’) para conseguir esse objeto.

  • sql (String)

    Consulta SQL a ser executada.

Returns:

  • (Object)

    Resultado da API contendo a resposta da consulta ou um JSON com os erros que ocorreram.



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/tarot.rb', line 216

def query(database, sql)
  uri = Config.data.url + '/api/dataset/json'
  http = Net::HTTP.new(uri.hostname, uri.port)
  http.use_ssl = uri.scheme == 'https'

  request = Net::HTTP::Post.new(uri,
   'Accept' => 'application/json',
   'X-Metabase-Session' => fetch_token
  )
  request.content_type = 'application/x-www-form-urlencoded'

  params = {
    'query': JSON.dump({
      'type' => 'native',
      'database' => database.id,
      'parameters' => [],
      'native' => {
        'query' => sql,
        'template-tags' => {}
      },
    })
  }
  request.body = URI.encode_www_form(params)
  response = http.request(request)
  JSON.parse(response.body) rescue response.body
end

.query!(database, sql) ⇒ Object

Executa uma consulta SQL e retorna o resultado. Levanta erro conforme resposta da API.

Parameters:

  • database (Database)

    Base de dados onde executar a consulta.

  • sql (String)

    Consulta SQL a ser executada.

Returns:

  • (Object)

    Resultado da consulta.

Raises:

  • (QueryError)

    Se ocorrer um erro na consulta.



205
206
207
208
209
210
# File 'lib/tarot.rb', line 205

def query!(database, sql)
  result = query(database, sql)
  raise QueryError, result['error'] if result.is_a?(Hash) && result['error']

  result
end