Class: GoodData::Rest::Client

Inherits:
Object
  • Object
show all
Includes:
Mixin::Inspector
Defined in:
lib/gooddata/rest/client.rb

Overview

User's interface to GoodData Platform.

MUST provide way to use - DELETE, GET, POST, PUT SHOULD provide way to use - HEAD, Bulk GET ...

Constant Summary collapse

DEFAULT_CONNECTION_IMPLEMENTATION =

Constants

GoodData::Rest::Connection
DEFAULT_SLEEP_INTERVAL =
10
DEFAULT_POLL_TIME_LIMIT =

5 hours

5 * 60 * 60
@@instance =

Class variables

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mixin::Inspector

#inspect, inspected

Constructor Details

#initialize(opts) ⇒ Client

Constructor of client

Parameters:

  • opts (Hash)

    Client options

Options Hash (opts):

  • :username (String)

    Username used for authentication

  • :password (String)

    Password used for authentication

  • :connection_factory (Object)

    Object able to create new instances of GoodData::Rest::Connection

  • :connection (GoodData::Rest::Connection)

    Existing GoodData::Rest::Connection



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/gooddata/rest/client.rb', line 153

def initialize(opts)
  # TODO: Decide if we want to pass the options directly or not
  @opts = opts

  @connection_factory = @opts[:connection_factory] || DEFAULT_CONNECTION_IMPLEMENTATION

  # TODO: See previous TODO
  # Create connection
  @connection = opts[:connection] || @connection_factory.new(opts)

  # Connect
  connect

  # Create factory bound to previously created connection
  @factory = ObjectFactory.new(self)
end

Instance Attribute Details

#connectionObject (readonly)

Decide if we need provide direct access to connection



41
42
43
# File 'lib/gooddata/rest/client.rb', line 41

def connection
  @connection
end

#factoryObject (readonly)

TODO: Decide if we need provide direct access to factory



44
45
46
# File 'lib/gooddata/rest/client.rb', line 44

def factory
  @factory
end

#optsObject (readonly)

Returns the value of attribute opts.



45
46
47
# File 'lib/gooddata/rest/client.rb', line 45

def opts
  @opts
end

Class Method Details

.connect(username, password = 'aaaa', opts = {}) ⇒ GoodData::Rest::Client

Globally available way to connect (and create client and set global instance)

HACK

To make transition from old implementation to new one following HACK IS TEMPORARILY ENGAGED!

  1. First call of #connect sets the GoodData::Rest::Client.instance (static, singleton instance)
  2. There are METHOD functions with same signature as their CLASS counterparts using singleton instance

Example

client = GoodData.connect('[email protected]', 's3cr3tp4sw0rd')

Parameters:

  • username (String)

    Username to be used for authentication

  • password (String) (defaults to: 'aaaa')

    Password to be used for authentication

Returns:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/gooddata/rest/client.rb', line 69

def connect(username, password = 'aaaa', opts = {})
  execution_id = ""
  if username.is_a?(Hash) && username.key?(:execution_id)
    execution_id = username[:execution_id]
    username.delete(:execution_id)
  end

  if opts.key?(:execution_id)
    execution_id = opts[:execution_id]
    opts.delete(:execution_id)
  end

  if username.nil? && password.nil?
    username = ENV['GD_GEM_USER']
    password = ENV['GD_GEM_PASSWORD']
  end

  username = GoodData::Helpers.symbolize_keys(username) if username.is_a?(Hash)

  new_opts = opts.dup
  if username.is_a?(Hash) && username.key?(:sst_token)
    new_opts = new_opts.merge(username)
  elsif username.is_a? Hash
    new_opts = new_opts.merge(username)
    new_opts[:username] = username[:login] || username[:user] || username[:username]
    new_opts[:password] = username[:password]
  elsif username.nil? && password.nil? && opts.blank?
    new_opts = Helpers::AuthHelper.read_credentials
  else
    new_opts[:username] = username
    new_opts[:password] = password
  end

  new_opts = { verify_ssl: true, execution_id: execution_id }.merge(new_opts)
  if username.is_a?(Hash) && username[:cookies]
    new_opts[:sst_token] = username[:cookies]['GDCAuthSST']
    new_opts[:cookies] = username[:cookies]
  end

  unless new_opts[:sst_token]
    fail ArgumentError, 'No username specified' if new_opts[:username].nil?
    fail ArgumentError, 'No password specified' if new_opts[:password].nil?
  end

  if username.is_a?(Hash) && username.key?(:server)
    new_opts[:server] = username[:server]
  end

  client = Client.new(new_opts)
  GoodData.logger.info("Connected to server with webdav path #{client.user_webdav_path}")

  # HACK: This line assigns class instance # if not done yet
  @@instance = client # rubocop:disable ClassVars
end

.connect_sso(sso) ⇒ Object



124
125
126
# File 'lib/gooddata/rest/client.rb', line 124

def connect_sso(sso)
  @@instance = Client.new(sso) # rubocop:disable ClassVars
end

.connectionObject Also known as: client



135
136
137
# File 'lib/gooddata/rest/client.rb', line 135

def connection
  @@instance
end

.disconnectObject



128
129
130
131
132
133
# File 'lib/gooddata/rest/client.rb', line 128

def disconnect
  if @@instance # rubocop:disable Style/GuardClause
    @@instance.disconnect
    @@instance = nil # rubocop:disable ClassVars
  end
end

.retryable(options = {}, &block) ⇒ Object

Retry block if exception thrown



140
141
142
# File 'lib/gooddata/rest/client.rb', line 140

def retryable(options = {}, &block)
  GoodData::Rest::Connection.retryable(options, &block)
end

Instance Method Details

#connectObject



203
204
205
206
207
208
# File 'lib/gooddata/rest/client.rb', line 203

def connect
  username = @opts[:username]
  password = @opts[:password]

  @connection.connect(username, password, @opts)
end

#create(klass, data = {}, opts = {}) ⇒ Object

Factory stuff



229
230
231
# File 'lib/gooddata/rest/client.rb', line 229

def create(klass, data = {}, opts = {})
  @factory.create(klass, data, opts)
end

#create_datawarehouse(opts = {}) ⇒ Object



222
223
224
# File 'lib/gooddata/rest/client.rb', line 222

def create_datawarehouse(opts = {})
  GoodData::DataWarehouse.create({ client: self }.merge(opts))
end

#create_project(options = { title: 'Project' }) ⇒ Object



170
171
172
# File 'lib/gooddata/rest/client.rb', line 170

def create_project(options = { title: 'Project' })
  GoodData::Project.create({ client: self }.merge(options))
end

#create_project_from_blueprint(blueprint, options = {}) ⇒ Object



174
175
176
# File 'lib/gooddata/rest/client.rb', line 174

def create_project_from_blueprint(blueprint, options = {})
  GoodData::Model::ProjectCreator.migrate(options.merge(spec: blueprint, client: self))
end

#delete(uri, opts = {}) ⇒ Object

Rest

HTTP DELETE

Parameters:

  • uri (String)

    Target URI



273
274
275
# File 'lib/gooddata/rest/client.rb', line 273

def delete(uri, opts = {})
  @connection.delete uri, opts.merge(stats_on: stats_on?)
end

#disconnectObject



210
211
212
213
214
215
216
# File 'lib/gooddata/rest/client.rb', line 210

def disconnect
  if stats_on?
    GoodData.logger.info("API call statistics to server #{@connection.server}")
    GoodData.logger.info(@connection.stats_table.to_s)
  end
  @connection.disconnect
end

#domain(domain_name) ⇒ Object



178
179
180
# File 'lib/gooddata/rest/client.rb', line 178

def domain(domain_name)
  GoodData::Domain[domain_name, :client => self]
end

#download(source_relative_path, target_file_path, options = {}) ⇒ Object

Downloads file from staging

Parameters:

  • source_relative_path (String)

    path relative to @param options[:staging_url]

  • target_file_path (String)

    path to be downloaded to

  • options (Hash) (defaults to: {})

    must contain :staging_url key (file will be downloaded from :staging_url + source_relative_path)



390
391
392
# File 'lib/gooddata/rest/client.rb', line 390

def download(source_relative_path, target_file_path, options = {})
  @connection.download source_relative_path, target_file_path, options
end

#download_from_user_webdav(source_relative_path, target_file_path, options = { client: GoodData.client }) ⇒ Object



394
395
396
397
# File 'lib/gooddata/rest/client.rb', line 394

def download_from_user_webdav(source_relative_path, target_file_path, options = { client: GoodData.client })
  download(source_relative_path, target_file_path, options.merge(:directory => options[:directory],
                                                                 :staging_url => user_webdav_path))
end

#find(klass, opts = {}) ⇒ Object



233
234
235
# File 'lib/gooddata/rest/client.rb', line 233

def find(klass, opts = {})
  @factory.find(klass, opts)
end

#generate_request_idObject



263
264
265
# File 'lib/gooddata/rest/client.rb', line 263

def generate_request_id
  @connection.generate_request_id
end

#get(uri, opts = {}, &block) ⇒ Object

HTTP GET

Parameters:

  • uri (String)

    Target URI



280
281
282
# File 'lib/gooddata/rest/client.rb', line 280

def get(uri, opts = {}, & block)
  @connection.get uri, opts.merge(stats_on: stats_on?), & block
end


408
409
410
# File 'lib/gooddata/rest/client.rb', line 408

def links
  GoodData::Helpers.get_path(get('/gdc'), %w(about links))
end

#poll_on_code(link, options = {}) ⇒ Hash

Generalizaton of poller. Since we have quite a variation of how async proceses are handled this is a helper that should help you with resources where the information about "Are we done" is the http code of response. By default we repeat as long as the code == 202. You can change the code if necessary. It expects the URI as an input where it can poll. It returns the value of last poll. In majority of cases these are the data that you need.

Parameters:

  • link (String)

    Link for polling

  • options (Hash) (defaults to: {})

    Options

Returns:

  • (Hash)

    Result of polling



317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/gooddata/rest/client.rb', line 317

def poll_on_code(link, options = {})
  code = options[:code] || 202
  process = options[:process]

  response = poll_on_response(link, options.merge(:process => false)) do |resp|
    resp.code == code
  end

  if process == false
    response
  else
    get(link)
  end
end

#poll_on_response(link, options = {}, &bl) ⇒ Hash

Generalizaton of poller. Since we have quite a variation of how async proceses are handled this is a helper that should help you with resources where the information about "Are we done" is inside the response. It expects the URI as an input where it can poll and a block that should return either true (meaning sleep and repeat) or false (meaning we are done). It returns the value of last poll. In majority of cases these are the data that you need

Parameters:

  • link (String)

    Link for polling

  • options (Hash) (defaults to: {})

    Options

Returns:

  • (Hash)

    Result of polling



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/gooddata/rest/client.rb', line 341

def poll_on_response(link, options = {}, &bl)
  time_limit = options[:time_limit] || DEFAULT_POLL_TIME_LIMIT
  process = options[:process] == false ? false : true

  # get the first status and start the timer
  response = get(link, process: process)
  poll_start = Time.now
  retry_time = GoodData::Rest::Connection::RETRY_TIME_INITIAL_VALUE
  while bl.call(response)
    limit_breached = time_limit && (Time.now - poll_start > time_limit)
    if limit_breached
      fail ExecutionLimitExceeded, "The time limit #{time_limit} secs for polling on #{link} is over"
    end
    sleep retry_time
    retry_time *= GoodData::Rest::Connection::RETRY_TIME_COEFFICIENT
    GoodData::Rest::Client.retryable(:tries => Helpers::GD_MAX_RETRY, :refresh_token => proc { connection.refresh_token }) do
      response = get(link, process: process)
    end
  end
  response
end

#post(uri, data, opts = {}) ⇒ Object

HTTP POST

Parameters:

  • uri (String)

    Target URI



373
374
375
# File 'lib/gooddata/rest/client.rb', line 373

def post(uri, data, opts = {})
  @connection.post uri, data, opts.merge(stats_on: stats_on?)
end

#processes(id = :all) ⇒ Object



199
200
201
# File 'lib/gooddata/rest/client.rb', line 199

def processes(id = :all)
  GoodData::Process[id, client: self]
end

#project_is_accessible?(id) ⇒ Boolean

Returns:

  • (Boolean)


182
183
184
185
186
187
188
189
# File 'lib/gooddata/rest/client.rb', line 182

def project_is_accessible?(id)
  GoodData.logger.warn 'Beware! project_is_accessible is deprecated and should not be used.'
  begin # rubocop:disable RedundantBegin TODO: remove this after droping JRuby which does not support rescue without begin
    projects(id)
  rescue RestClient::NotFound
    false
  end
end

#project_webdav_path(opts = { project: GoodData.project }) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/gooddata/rest/client.rb', line 284

def project_webdav_path(opts = { project: GoodData.project })
  p = opts[:project]
  fail ArgumentError, 'No :project specified' if p.nil?

  project = GoodData::Project[p, opts]
  fail ArgumentError, 'Wrong :project specified' if project.nil?

  url = project.links['uploads']
  fail 'Project WebDAV not supported in this Data Center' unless url

  GoodData.logger.warn 'Beware! Project webdav is deprecated and should not be used.'
  url
end

#projects(id = :all, limit = nil, offset = nil) ⇒ Object



191
192
193
194
195
196
197
# File 'lib/gooddata/rest/client.rb', line 191

def projects(id = :all, limit = nil, offset = nil)
  if limit.nil?
    GoodData::Project[id, client: self]
  else
    GoodData::Project.all({ client: self }, limit, offset)
  end
end

#put(uri, data, opts = {}) ⇒ Object

HTTP PUT

Parameters:

  • uri (String)

    Target URI



366
367
368
# File 'lib/gooddata/rest/client.rb', line 366

def put(uri, data, opts = {})
  @connection.put uri, data, opts.merge(stats_on: stats_on?)
end

#resource(res_name) ⇒ Object

Gets resource by name



238
239
240
241
# File 'lib/gooddata/rest/client.rb', line 238

def resource(res_name)
  GoodData.logger.info("Getting resource '#{res_name}'")
  nil
end

#stats_offObject



251
252
253
# File 'lib/gooddata/rest/client.rb', line 251

def stats_off
  @stats = false
end

#stats_onObject



255
256
257
# File 'lib/gooddata/rest/client.rb', line 255

def stats_on
  @stats = true
end

#stats_on?Boolean

Returns:

  • (Boolean)


259
260
261
# File 'lib/gooddata/rest/client.rb', line 259

def stats_on?
  @stats
end

#upload(file, options = {}) ⇒ Object

Uploads file to staging

Parameters:

  • file (String)

    file to be uploaded

  • options (Hash) (defaults to: {})

    must contain :staging_url key (file will be uploaded to :staging_url + File.basename(file))



381
382
383
# File 'lib/gooddata/rest/client.rb', line 381

def upload(file, options = {})
  @connection.upload file, options
end

#upload_to_user_webdav(file, options = {}) ⇒ Object



399
400
401
402
# File 'lib/gooddata/rest/client.rb', line 399

def upload_to_user_webdav(file, options = {})
  upload(file, options.merge(:directory => options[:directory],
                             :staging_url => user_webdav_path))
end

#user(id = nil) ⇒ Object



243
244
245
246
247
248
249
# File 'lib/gooddata/rest/client.rb', line 243

def user(id = nil)
  if id
    create(GoodData::Profile, get(id))
  else
    create(GoodData::Profile, @connection.user)
  end
end

#user_webdav_pathObject



298
299
300
301
302
303
304
305
306
# File 'lib/gooddata/rest/client.rb', line 298

def user_webdav_path
  uri = if opts[:webdav_server]
          opts[:webdav_server]
        else
          links.find { |i| i['category'] == 'uploads' }['link']
        end
  res = uri.chomp('/') + '/'
  res[0] == '/' ? "#{connection.server}#{res}" : res
end

#warehouses(id = :all) ⇒ Object



218
219
220
# File 'lib/gooddata/rest/client.rb', line 218

def warehouses(id = :all)
  GoodData::DataWarehouse[id, client: self]
end

#with_project(pid, &block) ⇒ Object



404
405
406
# File 'lib/gooddata/rest/client.rb', line 404

def with_project(pid, &block)
  GoodData.with_project(pid, client: self, &block)
end