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.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +34 -1
- data/CLI.md +1 -1
- data/authors.sh +4 -0
- data/lib/gooddata.rb +1 -1
- data/lib/gooddata/cli/commands/api_cmd.rb +0 -2
- data/lib/gooddata/cli/commands/auth_cmd.rb +0 -3
- data/lib/gooddata/cli/commands/console_cmd.rb +1 -2
- data/lib/gooddata/cli/commands/domain_cmd.rb +0 -2
- data/lib/gooddata/cli/commands/process_cmd.rb +0 -2
- data/lib/gooddata/cli/commands/project_cmd.rb +0 -2
- data/lib/gooddata/cli/commands/projects_cmd.rb +0 -2
- data/lib/gooddata/cli/commands/run_ruby_cmd.rb +2 -3
- data/lib/gooddata/cli/commands/scaffold_cmd.rb +0 -3
- data/lib/gooddata/cli/commands/user_cmd.rb +0 -2
- data/lib/gooddata/cli/shared.rb +1 -2
- data/lib/gooddata/commands/datawarehouse.rb +24 -0
- data/lib/gooddata/commands/process.rb +0 -1
- data/lib/gooddata/commands/project.rb +1 -1
- data/lib/gooddata/commands/scaffold.rb +0 -1
- data/lib/gooddata/core/connection.rb +376 -0
- data/lib/gooddata/core/logging.rb +13 -0
- data/lib/gooddata/core/rest.rb +40 -16
- data/lib/gooddata/exceptions/user_in_different_domain.rb +11 -0
- data/lib/gooddata/extensions/enumerable.rb +8 -0
- data/lib/gooddata/goodzilla/goodzilla.rb +24 -0
- data/lib/gooddata/helpers/global_helpers.rb +126 -12
- data/lib/gooddata/mixins/author.rb +11 -5
- data/lib/gooddata/mixins/is_dimension.rb +13 -0
- data/lib/gooddata/mixins/md_object_indexer.rb +17 -1
- data/lib/gooddata/mixins/md_object_query.rb +10 -2
- data/lib/gooddata/mixins/md_relations.rb +2 -2
- data/lib/gooddata/mixins/rest_resource.rb +1 -0
- data/lib/gooddata/models/data_result.rb +0 -1
- data/lib/gooddata/models/datawarehouse.rb +90 -0
- data/lib/gooddata/models/domain.rb +202 -76
- data/lib/gooddata/models/execution.rb +11 -0
- data/lib/gooddata/models/from_wire.rb +4 -4
- data/lib/gooddata/models/invitation.rb +0 -5
- data/lib/gooddata/models/membership.rb +121 -91
- data/lib/gooddata/models/metadata.rb +1 -2
- data/lib/gooddata/models/metadata/attribute.rb +7 -0
- data/lib/gooddata/models/metadata/dashboard.rb +1 -1
- data/lib/gooddata/models/metadata/dimension.rb +52 -0
- data/lib/gooddata/models/metadata/fact.rb +1 -1
- data/lib/gooddata/models/metadata/label.rb +21 -7
- data/lib/gooddata/models/metadata/metric.rb +1 -23
- data/lib/gooddata/models/metadata/report.rb +2 -2
- data/lib/gooddata/models/metadata/report_definition.rb +22 -2
- data/lib/gooddata/models/metadata/variable.rb +81 -0
- data/lib/gooddata/models/model.rb +2 -1
- data/lib/gooddata/models/process.rb +3 -4
- data/lib/gooddata/models/profile.rb +50 -82
- data/lib/gooddata/models/project.rb +170 -213
- data/lib/gooddata/models/project_blueprint.rb +14 -5
- data/lib/gooddata/models/project_creator.rb +2 -2
- data/lib/gooddata/models/schedule.rb +10 -8
- data/lib/gooddata/models/to_wire.rb +2 -2
- data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +67 -0
- data/lib/gooddata/models/user_filters/user_filter.rb +96 -0
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +409 -0
- data/lib/gooddata/{rest/connections/connections.rb → models/user_filters/user_filters.rb} +1 -0
- data/lib/gooddata/models/user_filters/variable_user_filter.rb +14 -0
- data/lib/gooddata/rest/client.rb +32 -21
- data/lib/gooddata/rest/connection.rb +283 -11
- data/lib/gooddata/rest/connections/rest_client_connection.rb +47 -109
- data/lib/gooddata/version.rb +1 -1
- data/spec/data/column_based_permissions.csv +7 -0
- data/spec/data/column_based_permissions2.csv +6 -0
- data/spec/data/hello_world_process/hello_world.rb +3 -1
- data/spec/data/line_based_permissions.csv +3 -0
- data/spec/data/m_n_model/blueprint.json +76 -0
- data/spec/data/{model_view.json → wire_models/model_view.json} +0 -0
- data/spec/data/wire_models/nu_model.json +3046 -0
- data/spec/helpers/process_helper.rb +2 -2
- data/spec/helpers/project_helper.rb +29 -0
- data/spec/helpers/schedule_helper.rb +1 -1
- data/spec/integration/command_datawarehouse_spec.rb +32 -0
- data/spec/integration/create_project_spec.rb +0 -1
- data/spec/integration/full_process_schedule_spec.rb +13 -5
- data/spec/integration/full_project_spec.rb +2 -1
- data/spec/integration/over_to_user_filters_spec.rb +92 -0
- data/spec/integration/project_spec.rb +233 -0
- data/spec/integration/rest_spec.rb +209 -0
- data/spec/integration/user_filters_spec.rb +193 -0
- data/spec/integration/variables_spec.rb +196 -0
- data/spec/unit/commands/command_auth_spec.rb +0 -7
- data/spec/unit/commands/command_process_spec.rb +10 -13
- data/spec/unit/core/connection_spec.rb +0 -19
- data/spec/unit/helpers/global_helpers_spec.rb +57 -0
- data/spec/unit/models/domain_spec.rb +80 -40
- data/spec/unit/models/from_wire_spec.rb +8 -1
- data/spec/unit/models/params_spec.rb +6 -6
- data/spec/unit/models/profile_spec.rb +23 -22
- data/spec/unit/models/project_blueprint_spec.rb +1 -6
- data/spec/unit/models/project_spec.rb +331 -286
- data/spec/unit/models/schedule_spec.rb +39 -14
- data/spec/unit/models/user_filters_spec.rb +89 -0
- data/spec/unit/models/variable_spec.rb +259 -0
- metadata +31 -7
- data/lib/gooddata/rest/connections/dummy_connection.rb +0 -52
- data/spec/unit/core/rest_spec.rb +0 -106
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b9ebfa575ef6b28f90189e3429f1b4563daa44ca
|
|
4
|
+
data.tar.gz: 1ca90d7f9414423d00baf8dd87634e8e356dd61e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e1f73a4b8df1f90db97fd13752e336f0c59e05ebf1713471ea6c48f01fddc7702f0f53f4822e74604c3ac70074383058d0a5b91a8e8cdeb24ed0c8e13eb2e197
|
|
7
|
+
data.tar.gz: 332429c9389f81b6475c31e495a44f93ba9154f7c4886770578a71eb29c82a4140dae49263be12ab6e5cfea6745ecd17d849ac84e97a8301b66ad30f0af299fb
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
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"=>"
|
|
262
|
+
"authorizationToken"=>"IOUYYUY8786",
|
|
263
263
|
"guidedNavigation"=>"1",
|
|
264
264
|
"isPublic"=>"0",
|
|
265
265
|
"driver"=>"Pg",
|
data/authors.sh
ADDED
data/lib/gooddata.rb
CHANGED
|
@@ -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
|
-
'
|
|
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
|
data/lib/gooddata/cli/shared.rb
CHANGED
|
@@ -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
|
-
'
|
|
129
|
+
'project_live_session: '
|
|
130
130
|
end])
|
|
131
131
|
end
|
|
132
132
|
rescue GoodData::ProjectNotFound
|
|
@@ -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
|