gooddata 0.6.11 → 0.6.12

Sign up to get free protection for your applications and to get access to all the features.
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