gooddata 0.6.11 → 0.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.travis.yml +5 -0
  4. data/CHANGELOG.md +34 -1
  5. data/CLI.md +1 -1
  6. data/authors.sh +4 -0
  7. data/lib/gooddata.rb +1 -1
  8. data/lib/gooddata/cli/commands/api_cmd.rb +0 -2
  9. data/lib/gooddata/cli/commands/auth_cmd.rb +0 -3
  10. data/lib/gooddata/cli/commands/console_cmd.rb +1 -2
  11. data/lib/gooddata/cli/commands/domain_cmd.rb +0 -2
  12. data/lib/gooddata/cli/commands/process_cmd.rb +0 -2
  13. data/lib/gooddata/cli/commands/project_cmd.rb +0 -2
  14. data/lib/gooddata/cli/commands/projects_cmd.rb +0 -2
  15. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +2 -3
  16. data/lib/gooddata/cli/commands/scaffold_cmd.rb +0 -3
  17. data/lib/gooddata/cli/commands/user_cmd.rb +0 -2
  18. data/lib/gooddata/cli/shared.rb +1 -2
  19. data/lib/gooddata/commands/datawarehouse.rb +24 -0
  20. data/lib/gooddata/commands/process.rb +0 -1
  21. data/lib/gooddata/commands/project.rb +1 -1
  22. data/lib/gooddata/commands/scaffold.rb +0 -1
  23. data/lib/gooddata/core/connection.rb +376 -0
  24. data/lib/gooddata/core/logging.rb +13 -0
  25. data/lib/gooddata/core/rest.rb +40 -16
  26. data/lib/gooddata/exceptions/user_in_different_domain.rb +11 -0
  27. data/lib/gooddata/extensions/enumerable.rb +8 -0
  28. data/lib/gooddata/goodzilla/goodzilla.rb +24 -0
  29. data/lib/gooddata/helpers/global_helpers.rb +126 -12
  30. data/lib/gooddata/mixins/author.rb +11 -5
  31. data/lib/gooddata/mixins/is_dimension.rb +13 -0
  32. data/lib/gooddata/mixins/md_object_indexer.rb +17 -1
  33. data/lib/gooddata/mixins/md_object_query.rb +10 -2
  34. data/lib/gooddata/mixins/md_relations.rb +2 -2
  35. data/lib/gooddata/mixins/rest_resource.rb +1 -0
  36. data/lib/gooddata/models/data_result.rb +0 -1
  37. data/lib/gooddata/models/datawarehouse.rb +90 -0
  38. data/lib/gooddata/models/domain.rb +202 -76
  39. data/lib/gooddata/models/execution.rb +11 -0
  40. data/lib/gooddata/models/from_wire.rb +4 -4
  41. data/lib/gooddata/models/invitation.rb +0 -5
  42. data/lib/gooddata/models/membership.rb +121 -91
  43. data/lib/gooddata/models/metadata.rb +1 -2
  44. data/lib/gooddata/models/metadata/attribute.rb +7 -0
  45. data/lib/gooddata/models/metadata/dashboard.rb +1 -1
  46. data/lib/gooddata/models/metadata/dimension.rb +52 -0
  47. data/lib/gooddata/models/metadata/fact.rb +1 -1
  48. data/lib/gooddata/models/metadata/label.rb +21 -7
  49. data/lib/gooddata/models/metadata/metric.rb +1 -23
  50. data/lib/gooddata/models/metadata/report.rb +2 -2
  51. data/lib/gooddata/models/metadata/report_definition.rb +22 -2
  52. data/lib/gooddata/models/metadata/variable.rb +81 -0
  53. data/lib/gooddata/models/model.rb +2 -1
  54. data/lib/gooddata/models/process.rb +3 -4
  55. data/lib/gooddata/models/profile.rb +50 -82
  56. data/lib/gooddata/models/project.rb +170 -213
  57. data/lib/gooddata/models/project_blueprint.rb +14 -5
  58. data/lib/gooddata/models/project_creator.rb +2 -2
  59. data/lib/gooddata/models/schedule.rb +10 -8
  60. data/lib/gooddata/models/to_wire.rb +2 -2
  61. data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +67 -0
  62. data/lib/gooddata/models/user_filters/user_filter.rb +96 -0
  63. data/lib/gooddata/models/user_filters/user_filter_builder.rb +409 -0
  64. data/lib/gooddata/{rest/connections/connections.rb → models/user_filters/user_filters.rb} +1 -0
  65. data/lib/gooddata/models/user_filters/variable_user_filter.rb +14 -0
  66. data/lib/gooddata/rest/client.rb +32 -21
  67. data/lib/gooddata/rest/connection.rb +283 -11
  68. data/lib/gooddata/rest/connections/rest_client_connection.rb +47 -109
  69. data/lib/gooddata/version.rb +1 -1
  70. data/spec/data/column_based_permissions.csv +7 -0
  71. data/spec/data/column_based_permissions2.csv +6 -0
  72. data/spec/data/hello_world_process/hello_world.rb +3 -1
  73. data/spec/data/line_based_permissions.csv +3 -0
  74. data/spec/data/m_n_model/blueprint.json +76 -0
  75. data/spec/data/{model_view.json → wire_models/model_view.json} +0 -0
  76. data/spec/data/wire_models/nu_model.json +3046 -0
  77. data/spec/helpers/process_helper.rb +2 -2
  78. data/spec/helpers/project_helper.rb +29 -0
  79. data/spec/helpers/schedule_helper.rb +1 -1
  80. data/spec/integration/command_datawarehouse_spec.rb +32 -0
  81. data/spec/integration/create_project_spec.rb +0 -1
  82. data/spec/integration/full_process_schedule_spec.rb +13 -5
  83. data/spec/integration/full_project_spec.rb +2 -1
  84. data/spec/integration/over_to_user_filters_spec.rb +92 -0
  85. data/spec/integration/project_spec.rb +233 -0
  86. data/spec/integration/rest_spec.rb +209 -0
  87. data/spec/integration/user_filters_spec.rb +193 -0
  88. data/spec/integration/variables_spec.rb +196 -0
  89. data/spec/unit/commands/command_auth_spec.rb +0 -7
  90. data/spec/unit/commands/command_process_spec.rb +10 -13
  91. data/spec/unit/core/connection_spec.rb +0 -19
  92. data/spec/unit/helpers/global_helpers_spec.rb +57 -0
  93. data/spec/unit/models/domain_spec.rb +80 -40
  94. data/spec/unit/models/from_wire_spec.rb +8 -1
  95. data/spec/unit/models/params_spec.rb +6 -6
  96. data/spec/unit/models/profile_spec.rb +23 -22
  97. data/spec/unit/models/project_blueprint_spec.rb +1 -6
  98. data/spec/unit/models/project_spec.rb +331 -286
  99. data/spec/unit/models/schedule_spec.rb +39 -14
  100. data/spec/unit/models/user_filters_spec.rb +89 -0
  101. data/spec/unit/models/variable_spec.rb +259 -0
  102. metadata +31 -7
  103. data/lib/gooddata/rest/connections/dummy_connection.rb +0 -52
  104. data/spec/unit/core/rest_spec.rb +0 -106
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 599d5759ccffd91b5e59aa9fde03eccee81c4138
4
- data.tar.gz: 95ec2b6b381f940b686fe29c201c99ec1b1300c9
3
+ metadata.gz: b9ebfa575ef6b28f90189e3429f1b4563daa44ca
4
+ data.tar.gz: 1ca90d7f9414423d00baf8dd87634e8e356dd61e
5
5
  SHA512:
6
- metadata.gz: b82f86ca736a3dbb5d6402c1085a3062c6d3213ff5f2f782f115496408d97d230779870be2923fcb5aa472615c539d65b08c52b022628b06467d2e6ad15816aa
7
- data.tar.gz: 018ad7f05291a4f5ce238254d3f24ab3680d2dc0ab4a374990004db785bd1e9b15534cf553d88958ead9eb5bc5b82c8b94507cdc876955144d155283bc016bb7
6
+ metadata.gz: e1f73a4b8df1f90db97fd13752e336f0c59e05ebf1713471ea6c48f01fddc7702f0f53f4822e74604c3ac70074383058d0a5b91a8e8cdeb24ed0c8e13eb2e197
7
+ data.tar.gz: 332429c9389f81b6475c31e495a44f93ba9154f7c4886770578a71eb29c82a4140dae49263be12ab6e5cfea6745ecd17d849ac84e97a8301b66ad30f0af299fb
data/.gitignore CHANGED
@@ -27,3 +27,9 @@ Gemfile.lock
27
27
 
28
28
  # YARD
29
29
  .yardoc/
30
+
31
+ # Local scripts
32
+ .scripts/
33
+
34
+ # Temp files
35
+ tmp/
data/.travis.yml CHANGED
@@ -1,5 +1,9 @@
1
1
  language: ruby
2
2
 
3
+ # cache: bundler
4
+
5
+ sudo: false
6
+
3
7
  os:
4
8
  - linux
5
9
  - osx
@@ -18,6 +22,7 @@ rvm:
18
22
  - jruby-19mode
19
23
  # - rbx-2.2.9
20
24
  - 2.1
25
+ - 2.2
21
26
 
22
27
  before_install:
23
28
  - gem update --system
data/CHANGELOG.md CHANGED
@@ -1,12 +1,45 @@
1
1
  # GoodData Ruby SDK Changelog
2
2
 
3
+ ## 0.6.12 (in progress)
4
+
5
+ - Ability to create a Data Warehouse (ADS)
6
+ - Retry all requests 3 times when SystemCallError, RestClient::InternalServerError or RestClient::RequestTimeout
7
+ - Automatic 429/TooManyRequests Handler
8
+ - When creating user login and email can be different now
9
+ - Automatic client disconnect at_exit of ruby script
10
+ - When creating user login and email can be different now
11
+ - Fixed Domain#add_user (GH issue #354)
12
+ - Support for GoodData.connect ENV['GD_GEM_USER'], ENV['GD_GEM_PASSWORD']
13
+ - Added Schedule#execute(:wait => true|false) option
14
+ - Merge GoodData::Rest::Connection and GoodData::Rest::Connection::RestClientConnection
15
+ - Unified expection handler for REST API and WebDav Access (using GoodData::Rest::Connection.retryable)
16
+ - GoodData#stats_on, GoodData#stats_off, GoodData::Rest::Client#stats_on, GoodData#stats_off
17
+ - GoodData::Mixin::MdObjectQuery#using now accepts :full => true|false option
18
+ - GoodData::MdObject#[] automatically returns proper type (ie. GoodData::Report)
19
+ - Improved user management
20
+ - Added simple GoodData::Dimension
21
+
3
22
  ## 0.6.11
23
+
4
24
  - Ability to download deployed process
5
25
  - Added locking objects capabilities
6
26
  - Added removing color mapping form a report definition
7
27
  - Report defintions are deleted along with a report
28
+ - Report definitions are deleted along with a report
29
+ - Improved process deployment and schedules
30
+ - Parameters in processes and schedules are now able to take complex parameters
31
+ - #create_metric is significantly faster
32
+ - Pretty_expression for metric should not fail on missing data
33
+ - Extended notation can be switched off when using create_metric
34
+ - Implemented retry on connection related issues
35
+ - All executions should use latest resource version
36
+ - Uploading files to webdav should use streaming and be more memory efficient
37
+ - Ability to pass absolute path to file upload
38
+ - Allowing special chars in uploaded file
39
+ - GooddataMiddleware doesn't require username+password, when it has SST
8
40
 
9
41
  ## 0.6.10
42
+
10
43
  - Fixed client default missing in ProjectMetadata
11
44
  - Listing schedules on processes is working
12
45
  - Scrubing params in logs is back
@@ -15,8 +48,8 @@
15
48
  - Schedule can be enabled/disabled
16
49
  - Added pselect helper function
17
50
 
18
-
19
51
  ## 0.6.9
52
+
20
53
  - Fixing issues with creating models.
21
54
  - Adding couple more helpers for report/metric computation
22
55
  - Rewriting several full_* specs to use the new syntax
data/CLI.md CHANGED
@@ -259,7 +259,7 @@ Shows basic info about a project
259
259
  tomaskorcak@kx-mac:~/$ gooddata -p tk6192gsnav58crp6o1ahsmtuniq8khb project show
260
260
  {"content"=>
261
261
  {"cluster"=>"",
262
- "authorizationToken"=>"INTNA000000SRVS",
262
+ "authorizationToken"=>"IOUYYUY8786",
263
263
  "guidedNavigation"=>"1",
264
264
  "isPublic"=>"0",
265
265
  "driver"=>"Pg",
data/authors.sh ADDED
@@ -0,0 +1,4 @@
1
+ #! /usr/bin/env bash
2
+
3
+ git shortlog -s -n
4
+
data/lib/gooddata.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require 'pmap'
4
- $pmap_default_thread_count = 20 # rubocop:disable GlobalVars
4
+ $pmap_default_thread_count = 100 # rubocop:disable GlobalVars
5
5
 
6
6
  # GoodData Module
7
7
  module GoodData
@@ -9,7 +9,6 @@ GoodData::CLI.module_eval do
9
9
  desc 'Some basic API stuff directly from CLI'
10
10
  arg_name 'info|test|get|delete'
11
11
  command :api do |c|
12
-
13
12
  c.desc 'Info about the API version etc'
14
13
  c.command :info do |info|
15
14
  info.action do |global_options, options, _args|
@@ -27,6 +26,5 @@ GoodData::CLI.module_eval do
27
26
  pp GoodData::Command::Api.get(args[0])
28
27
  end
29
28
  end
30
-
31
29
  end
32
30
  end
@@ -4,10 +4,8 @@ require_relative '../shared'
4
4
  require_relative '../../commands/auth'
5
5
 
6
6
  GoodData::CLI.module_eval do
7
-
8
7
  desc 'Work with your locally stored credentials'
9
8
  command :auth do |c|
10
-
11
9
  c.desc 'Store your credentials to ~/.gooddata so client does not have to ask you every single time'
12
10
  c.command :store do |store|
13
11
  store.action do |_global_options, _options, _args|
@@ -15,5 +13,4 @@ GoodData::CLI.module_eval do
15
13
  end
16
14
  end
17
15
  end
18
-
19
16
  end
@@ -7,7 +7,6 @@ require_relative '../shared'
7
7
  GoodData::CLI.module_eval do
8
8
  desc 'Interactive session with gooddata sdk loaded'
9
9
  command :console do |c|
10
-
11
10
  c.action do |global_options, _options, _args|
12
11
  username = global_options[:username]
13
12
  fail ArgumentError, 'No username specified' if username.nil? || username.empty?
@@ -28,7 +27,7 @@ GoodData::CLI.module_eval do
28
27
  puts "Use 'exit' to quit the live session. Use 'q' to jump out of displaying a large output."
29
28
  binding.pry(:quiet => true,
30
29
  :prompt => [proc do |_target_self, _nest_level, _pry|
31
- 'sdk_live_sesion: '
30
+ 'sdk_live_session: '
32
31
  end])
33
32
  end
34
33
  client.disconnect
@@ -4,10 +4,8 @@ require_relative '../shared'
4
4
  require_relative '../../commands/domain'
5
5
 
6
6
  GoodData::CLI.module_eval do
7
-
8
7
  desc 'Manage domain'
9
8
  command :domain do |c|
10
-
11
9
  c.desc 'Add user to domain'
12
10
  c.command :add_user do |add_user|
13
11
  add_user.action do |global_options, options, args|
@@ -16,11 +16,9 @@ def read_params_file(filename)
16
16
  end
17
17
 
18
18
  GoodData::CLI.module_eval do
19
-
20
19
  desc 'Work with deployed processes'
21
20
  arg_name 'Describe arguments to list here'
22
21
  command :process do |c|
23
-
24
22
  c.desc 'Use when you need to redeploy a specific process'
25
23
  c.default_value nil
26
24
  c.flag :process_id
@@ -7,7 +7,6 @@ require_relative '../shared'
7
7
  require_relative '../../commands/project'
8
8
 
9
9
  GoodData::CLI.module_eval do
10
-
11
10
  desc 'Manage your project'
12
11
  arg_name 'project_command'
13
12
  command :project do |c|
@@ -140,7 +139,6 @@ GoodData::CLI.module_eval do
140
139
  c.desc 'If you are in a gooddata project blueprint it will apply the changes. If you do not provide a project id it will build it from scratch and create a project for you.'
141
140
  c.command :update do |show|
142
141
  show.action do |global_options, options, _args|
143
-
144
142
  opts = options.merge(global_options)
145
143
  GoodData.connect(opts)
146
144
  spec, project_id = GoodData::Command::Project.get_spec_and_project_id('.')
@@ -4,10 +4,8 @@ require_relative '../shared'
4
4
  require_relative '../../commands/projects'
5
5
 
6
6
  GoodData::CLI.module_eval do
7
-
8
7
  desc 'Manage your projects'
9
8
  command :projects do |c|
10
-
11
9
  c.desc "Lists user's projects"
12
10
  c.command :list do |list|
13
11
  list.action do |global_options, options, _args|
@@ -15,11 +15,9 @@ def load_undot(filename)
15
15
  end
16
16
 
17
17
  GoodData::CLI.module_eval do
18
-
19
18
  desc 'Run ruby bricks either locally or remotely deployed on our server. Currently private alpha.'
20
19
  # arg_name 'show'
21
20
  command :run_ruby do |c|
22
-
23
21
  c.desc 'Directory of the ruby brick'
24
22
  c.default_value nil
25
23
  c.flag [:d, :dir]
@@ -30,7 +28,7 @@ GoodData::CLI.module_eval do
30
28
 
31
29
  c.desc 'Params file path. Inside should be hash of key values. These params override any defaults given in bricks.'
32
30
  c.default_value nil
33
- c.flag [:params]
31
+ c.flag [:params, :paramfile]
34
32
 
35
33
  c.desc 'Remote system credentials file path. Inside should be hash of key values.'
36
34
  c.default_value nil
@@ -55,6 +53,7 @@ GoodData::CLI.module_eval do
55
53
  else
56
54
  { 'config' => {} }
57
55
  end
56
+
58
57
  # if there are some GDC_* params in config, put them on the level above
59
58
  gdc_params = options[:expanded_params]['config'].select { |k, _| k =~ /GDC_.*/ }
60
59
  options[:expanded_params].merge!(gdc_params)
@@ -6,11 +6,9 @@ require_relative '../shared'
6
6
  require_relative '../../commands/scaffold'
7
7
 
8
8
  GoodData::CLI.module_eval do
9
-
10
9
  desc 'Scaffold things'
11
10
  arg_name 'show'
12
11
  command :scaffold do |c|
13
-
14
12
  c.desc 'Scaffold a gooddata project blueprint'
15
13
  c.command :project do |project|
16
14
  project.action do |_global_options, _options, args|
@@ -30,5 +28,4 @@ GoodData::CLI.module_eval do
30
28
  end
31
29
  end
32
30
  end
33
-
34
31
  end
@@ -6,7 +6,6 @@ require_relative '../../commands/role'
6
6
  require_relative '../../commands/user'
7
7
 
8
8
  GoodData::CLI.module_eval do
9
-
10
9
  desc 'User management'
11
10
  command :user do |c|
12
11
  c.desc 'Show your profile'
@@ -18,5 +17,4 @@ GoodData::CLI.module_eval do
18
17
  end
19
18
  end
20
19
  end
21
-
22
20
  end
@@ -5,11 +5,11 @@ require 'gli'
5
5
  require_relative '../version'
6
6
  require_relative '../core/core'
7
7
  require_relative '../extensions/extensions'
8
+ require_relative '../exceptions/exceptions'
8
9
 
9
10
  include GLI::App
10
11
 
11
12
  GoodData::CLI.module_eval do
12
-
13
13
  program_desc 'GoodData Ruby gem - a wrapper over GoodData API and several useful abstractions to make your everyday usage of GoodData easier.'
14
14
 
15
15
  version GoodData::VERSION
@@ -51,5 +51,4 @@ GoodData::CLI.module_eval do
51
51
  desc 'Http logger on stdout'
52
52
  arg_name 'logger'
53
53
  switch [:l, :logger]
54
-
55
54
  end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ module GoodData
4
+ module Command
5
+ # Also known as ADS and DSS
6
+ class DataWarehouse
7
+ class << self
8
+ # Create new project based on options supplied
9
+ def create(options = { client: GoodData.connection })
10
+ title = options[:title]
11
+ description = options[:summary] || options[:description]
12
+ token = options[:token] || options[:auth_token]
13
+ client = options[:client]
14
+ GoodData::DataWarehouse.create(
15
+ :title => title,
16
+ :description => description,
17
+ :auth_token => token,
18
+ :client => client
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -52,7 +52,6 @@ module GoodData
52
52
  verbose = options[:v]
53
53
  dir = Pathname(dir)
54
54
  name = options[:name] || "Temporary deploy[#{dir}][#{options[:project_name]}]"
55
-
56
55
  GoodData::Process.with_deploy(dir, options.merge(:name => name, :project_id => ProjectHelper::PROJECT_ID)) do |process|
57
56
  puts HighLine.color('Executing', HighLine::BOLD) if verbose
58
57
  process.execute(executable, options)
@@ -126,7 +126,7 @@ module GoodData
126
126
  puts "Use 'exit' to quit the live session. Use 'q' to jump out of displaying a large output."
127
127
  binding.pry(:quiet => true,
128
128
  :prompt => [proc do |_target_self, _nest_level, _pry|
129
- 'project_live_sesion: '
129
+ 'project_live_session: '
130
130
  end])
131
131
  end
132
132
  rescue GoodData::ProjectNotFound
@@ -17,7 +17,6 @@ module GoodData
17
17
 
18
18
  FileUtils.mkdir(name)
19
19
  FileUtils.cd(name) do
20
-
21
20
  FileUtils.mkdir('model')
22
21
  FileUtils.cd('model') do
23
22
  input = File.read(TEMPLATES_PATH + 'project/model/model.rb.erb')
@@ -0,0 +1,376 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'multi_json'
4
+ require 'rest-client'
5
+
6
+ require_relative '../version'
7
+
8
+ module GoodData
9
+ # # GoodData HTTP wrapper
10
+ #
11
+ # Provides a convenient HTTP wrapper for talking with the GoodData API.
12
+ #
13
+ # Remember that the connection is shared amongst the entire application.
14
+ # Therefore you can't be logged in to more than _one_ GoodData account.
15
+ # per session. Simultaneous connections to multiple GoodData accounts is not
16
+ # supported at this time.
17
+ #
18
+ # The GoodData API is a RESTful API that communicates using JSON. This wrapper
19
+ # makes sure that the session is stored between requests and that the JSON is
20
+ # parsed both when sending and receiving.
21
+ #
22
+ # ## Usage
23
+ #
24
+ # Before a connection can be made to the GoodData API, you have to supply the user credentials like this:
25
+ #
26
+ # Connection.new(username, password)
27
+ #
28
+ # To send a HTTP request use either the get, post or delete methods documented below.
29
+ #
30
+ class Connection
31
+ DEFAULT_URL = 'https://secure.gooddata.com'
32
+ LOGIN_PATH = '/gdc/account/login'
33
+ TOKEN_PATH = '/gdc/account/token'
34
+
35
+ attr_reader(:auth_token, :url)
36
+ attr_accessor :status, :options
37
+
38
+ # Options:
39
+ # * :tries - Number of retries to perform. Defaults to 1.
40
+ # * :on - The Exception on which a retry will be performed. Defaults to Exception, which retries on any Exception.
41
+ #
42
+ # ### Example
43
+ #
44
+ # retryable(:tries => 1, :on => OpenURI::HTTPError) do
45
+ # # your code here
46
+ # end
47
+ #
48
+ def retryable(options = {})
49
+ opts = { :tries => 1, :on => Exception }.merge(options)
50
+
51
+ retry_exception, retries = opts[:on], opts[:tries]
52
+
53
+ begin
54
+ return yield
55
+ rescue retry_exception
56
+ retry if (retries -= 1) > 0
57
+ end
58
+
59
+ yield
60
+ end
61
+
62
+ # Set the GoodData account credentials.
63
+ #
64
+ # This have to be performed before any calls to the API.
65
+ #
66
+ # @param username The GoodData account username
67
+ # @param password The GoodData account password
68
+ #
69
+ def initialize(username, password, options = {})
70
+ @status = :not_connected
71
+ @username = username
72
+ @password = password
73
+ @url = options[:server] || DEFAULT_URL
74
+ @auth_token = options[:gdc_temporary_token]
75
+ @options = options
76
+
77
+ @headers = options[:headers] || {}
78
+
79
+ default_headers = {
80
+ :content_type => :json,
81
+ :accept => [:json, :zip],
82
+ :user_agent => GoodData.gem_version_string
83
+ }
84
+ default_headers.merge! @headers
85
+
86
+ @server = RestClient::Resource.new @url,
87
+ :timeout => @options[:timeout],
88
+ :headers => default_headers
89
+
90
+ @server = create_server_connection(@url, @options)
91
+ end
92
+
93
+ # Returns the user JSON object of the currently logged in GoodData user account.
94
+ def user
95
+ ensure_connection
96
+ @user
97
+ end
98
+
99
+ # Performs a HTTP GET request.
100
+ #
101
+ # Retuns the JSON response formatted as a Hash object.
102
+ #
103
+ # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
104
+ #
105
+ # ### Examples
106
+ #
107
+ # Connection.new(username, password).get '/gdc/projects'
108
+ #
109
+ def get(path, options = {})
110
+ GoodData.logger.debug "GET #{@server}#{path}"
111
+ ensure_connection
112
+ b = proc { @server[path].get cookies }
113
+ process_response(options, &b)
114
+ end
115
+
116
+ # Performs a HTTP POST request.
117
+ #
118
+ # Retuns the JSON response formatted as a Hash object.
119
+ #
120
+ # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
121
+ # @param data The payload data in the format of a Hash object
122
+ #
123
+ # ### Examples
124
+ #
125
+ # Connection.new(username, password).post '/gdc/projects', { ... }
126
+ #
127
+ def post(path, data, options = {})
128
+ GoodData.logger.debug("POST #{@server}#{path}, payload: #{scrub_params(data, [:password, :login, :authorizationToken])}")
129
+ ensure_connection
130
+ payload = data.is_a?(Hash) ? data.to_json : data
131
+ b = proc { @server[path].post payload, cookies }
132
+ process_response(options, &b)
133
+ end
134
+
135
+ # Performs a HTTP PUT request.
136
+ #
137
+ # Retuns the JSON response formatted as a Hash object.
138
+ #
139
+ # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
140
+ # @param data The payload data in the format of a Hash object
141
+ #
142
+ # ### Examples
143
+ #
144
+ # Connection.new(username, password).put '/gdc/projects', { ... }
145
+ #
146
+ def put(path, data, options = {})
147
+ payload = data.is_a?(Hash) ? data.to_json : data
148
+ GoodData.logger.debug "PUT #{@server}#{path}, payload: #{payload}"
149
+ ensure_connection
150
+ b = proc { @server[path].put payload, cookies }
151
+ process_response(options, &b)
152
+ end
153
+
154
+ # Performs a HTTP DELETE request.
155
+ #
156
+ # Retuns the JSON response formatted as a Hash object.
157
+ #
158
+ # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
159
+ #
160
+ # ### Examples
161
+ #
162
+ # Connection.new(username, password).delete '/gdc/project/1'
163
+ #
164
+ def delete(path, options = {})
165
+ GoodData.logger.debug "DELETE #{@server}#{path}"
166
+ ensure_connection
167
+ b = proc { @server[path].delete cookies }
168
+ process_response(options, &b)
169
+ end
170
+
171
+ # Get the cookies associated with the current connection.
172
+ def cookies
173
+ @cookies ||= { :cookies => {} }
174
+ end
175
+
176
+ # Set the cookies used when communicating with the GoodData API.
177
+ def merge_cookies!(cookies)
178
+ self.cookies
179
+ @cookies[:cookies].merge! cookies
180
+ end
181
+
182
+ # Returns true if a connection have been established to the GoodData API
183
+ # and the login was successful.
184
+ def logged_in?
185
+ @status == :logged_in
186
+ end
187
+
188
+ def url=(url = nil)
189
+ @url = url || DEFAULT_URL
190
+ @server = create_server_connection(@url, @options)
191
+ end
192
+
193
+ # The connection will automatically be established once it's needed, which it
194
+ # usually is when either the user, get, post or delete method is called. If you
195
+ # want to force a connection (or a re-connect) you can use this method.
196
+ def connect!
197
+ connect
198
+ end
199
+
200
+ # Uploads a file to GoodData server
201
+ # /uploads/ resources are special in that they use a different
202
+ # host and a basic authentication.
203
+ def upload(file, options = {})
204
+ ensure_connection
205
+
206
+ dir = options[:directory] || ''
207
+ staging_uri = options[:staging_url].to_s
208
+ url = dir.empty? ? staging_uri : URI.join(staging_uri, "#{dir}/").to_s
209
+
210
+ # Make a directory, if needed
211
+ unless dir.empty?
212
+ method = :get
213
+ GoodData.logger.debug "#{method}: #{url}"
214
+ begin
215
+ # first check if it does exits
216
+ RestClient::Request.execute({
217
+ :method => method,
218
+ :url => url,
219
+ :timeout => @options[:timeout],
220
+ :headers => {
221
+ :user_agent => GoodData.gem_version_string
222
+ }
223
+ }.merge(cookies))
224
+ rescue RestClient::Exception => e
225
+ if e.http_code == 404
226
+ method = :mkcol
227
+ GoodData.logger.debug "#{method}: #{url}"
228
+ RestClient::Request.execute({
229
+ :method => method,
230
+ :url => url,
231
+ :timeout => @options[:timeout],
232
+ :headers => {
233
+ :user_agent => GoodData.gem_version_string
234
+ }
235
+ }.merge(cookies))
236
+ end
237
+ end
238
+ end
239
+
240
+ payload = options[:stream] ? 'file' : File.read(file)
241
+ filename = options[:filename] || options[:stream] ? 'randome-filename.txt' : File.basename(file)
242
+
243
+ # Upload the file
244
+ # puts "uploading the file #{URI.join(url, filename).to_s}"
245
+ req = RestClient::Request.new(
246
+ :method => :put,
247
+ :url => URI.join(url, filename).to_s,
248
+ :timeout => @options[:timeout],
249
+ :headers => {
250
+ :user_agent => GoodData.gem_version_string
251
+ },
252
+ :payload => payload,
253
+ :raw_response => true,
254
+ :user => @username,
255
+ :password => @password
256
+ )
257
+ # .merge(cookies))
258
+ req.execute
259
+ true
260
+ end
261
+
262
+ def download(what, where, options = {})
263
+ staging_uri = options[:staging_url].to_s
264
+ url = staging_uri + what
265
+ req = RestClient::Request.new({
266
+ :method => 'GET',
267
+ :url => url,
268
+ :user => @username,
269
+ :password => @password
270
+ }.merge(cookies))
271
+
272
+ if where.is_a?(String)
273
+ File.open(where, 'w') do |f|
274
+ req.execute do |chunk, _, _|
275
+ f.write chunk
276
+ end
277
+ end
278
+ else
279
+ # Assume it is a IO stream
280
+ req.execute do |chunk, _, _|
281
+ where.write chunk
282
+ end
283
+ end
284
+ end
285
+
286
+ def connected?
287
+ @status == :logged_in
288
+ end
289
+
290
+ def disconnect
291
+ return if !connected? && !GoodData.connection.user['state']
292
+ GoodData.delete(GoodData.connection.user['state'])
293
+ @status = :not_connected
294
+ end
295
+
296
+ private
297
+
298
+ def create_server_connection(url, options)
299
+ RestClient::Resource.new url,
300
+ :timeout => options[:timeout],
301
+ :headers => {
302
+ :content_type => :json,
303
+ :accept => [:json, :zip],
304
+ :user_agent => GoodData.gem_version_string
305
+ }
306
+ end
307
+
308
+ def ensure_connection
309
+ connect if @status == :not_connected
310
+ end
311
+
312
+ def connect
313
+ GoodData.logger.info 'Connecting to GoodData...'
314
+ @status = :connecting
315
+ authenticate
316
+ end
317
+
318
+ def authenticate
319
+ credentials = {
320
+ 'postUserLogin' => {
321
+ 'login' => @username,
322
+ 'password' => @password,
323
+ 'remember' => 1
324
+ }
325
+ }
326
+ GoodData.logger.debug 'Logging in...'
327
+ @user = post(LOGIN_PATH, credentials, :dont_reauth => true)['userLogin']
328
+ refresh_token :dont_reauth => true # avoid infinite loop if refresh_token fails with 401
329
+
330
+ @status = :logged_in
331
+ end
332
+
333
+ def process_response(options = {}, &block)
334
+ begin
335
+ response = block.call
336
+ rescue RestClient::Unauthorized
337
+ raise $ERROR_INFO if options[:dont_reauth]
338
+ refresh_token
339
+ response = block.call
340
+ end
341
+ merge_cookies! response.cookies
342
+ content_type = response.headers[:content_type]
343
+ return response if options[:process] == false
344
+
345
+ if content_type == 'application/json' || content_type == 'application/json;charset=UTF-8'
346
+ result = response.to_str == '""' ? {} : MultiJson.load(response.to_str)
347
+ GoodData.logger.debug "Response: #{result.inspect}"
348
+ elsif content_type == 'application/zip'
349
+ result = response
350
+ GoodData.logger.debug 'Response: a zipped stream'
351
+ elsif response.headers[:content_length].to_s == '0'
352
+ result = nil
353
+ GoodData.logger.debug 'Response: Empty response possibly 204'
354
+ elsif response.code == 204
355
+ result = nil
356
+ GoodData.logger.debug 'Response: 204 no content'
357
+ else
358
+ fail "Unsupported response content type '%s':\n%s" % [content_type, response.to_str[0..127]]
359
+ end
360
+ result
361
+ rescue RestClient::Exception => e
362
+ GoodData.logger.debug "Response: #{e.response}"
363
+ raise $ERROR_INFO
364
+ end
365
+
366
+ def refresh_token(options = {})
367
+ GoodData.logger.debug 'Getting authentication token...'
368
+ begin
369
+ get TOKEN_PATH, :dont_reauth => true # avoid infinite loop GET fails with 401
370
+ rescue RestClient::Unauthorized
371
+ raise $ERROR_INFO if options[:dont_reauth]
372
+ authenticate
373
+ end
374
+ end
375
+ end
376
+ end