gooddata 0.6.24 → 0.6.25
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/.rubocop.yml +54 -0
- data/CHANGELOG.md +3 -0
- data/DEPENDENCIES.md +155 -154
- data/README.md +15 -6
- data/Rakefile +5 -3
- data/dependency_decisions.yml +2 -0
- data/gooddata.gemspec +2 -3
- data/lib/gooddata/cli/cli.rb +1 -3
- data/lib/gooddata/cli/commands/auth_cmd.rb +16 -7
- data/lib/gooddata/cli/commands/project_cmd.rb +16 -178
- data/lib/gooddata/cli/shared.rb +46 -44
- data/lib/gooddata/commands/auth.rb +4 -0
- data/lib/gooddata/commands/project.rb +7 -24
- data/lib/gooddata/exceptions/object_migration.rb +4 -0
- data/lib/gooddata/exceptions/segment_not_empty.rb +18 -0
- data/lib/gooddata/extensions/object.rb +12 -0
- data/lib/gooddata/goodzilla/goodzilla.rb +56 -9
- data/lib/gooddata/helpers/global_helpers.rb +92 -0
- data/lib/gooddata/mixins/md_finders.rb +2 -8
- data/lib/gooddata/mixins/md_grantees.rb +42 -0
- data/lib/gooddata/mixins/md_id_to_uri.rb +1 -8
- data/lib/gooddata/mixins/md_object_id.rb +1 -1
- data/lib/gooddata/mixins/md_object_indexer.rb +5 -8
- data/lib/gooddata/mixins/md_object_query.rb +2 -2
- data/lib/gooddata/mixins/not_group.rb +17 -0
- data/lib/gooddata/mixins/rest_getters.rb +2 -2
- data/lib/gooddata/mixins/rest_resource.rb +1 -0
- data/lib/gooddata/mixins/to_json.rb +11 -0
- data/lib/gooddata/mixins/uri_getter.rb +9 -0
- data/lib/gooddata/models/blueprint/anchor_field.rb +14 -0
- data/lib/gooddata/models/blueprint/project_blueprint.rb +15 -1
- data/lib/gooddata/models/blueprint/to_wire.rb +10 -0
- data/lib/gooddata/models/client.rb +178 -0
- data/lib/gooddata/models/client_synchronization_result.rb +31 -0
- data/lib/gooddata/models/client_synchronization_result_details.rb +41 -0
- data/lib/gooddata/models/datawarehouse.rb +1 -5
- data/lib/gooddata/models/domain.rb +85 -1
- data/lib/gooddata/models/execution.rb +0 -2
- data/lib/gooddata/models/execution_detail.rb +0 -2
- data/lib/gooddata/models/from_wire.rb +10 -0
- data/lib/gooddata/models/invitation.rb +1 -1
- data/lib/gooddata/models/links.rb +1 -1
- data/lib/gooddata/models/membership.rb +10 -6
- data/lib/gooddata/models/metadata.rb +98 -11
- data/lib/gooddata/models/metadata/attribute.rb +6 -7
- data/lib/gooddata/models/metadata/dashboard.rb +41 -75
- data/lib/gooddata/models/metadata/dashboard/dashboard_item.rb +20 -4
- data/lib/gooddata/models/metadata/dashboard/filter_apply_item.rb +37 -0
- data/lib/gooddata/models/metadata/dashboard/filter_item.rb +49 -0
- data/lib/gooddata/models/metadata/dashboard/geo_chart_item.rb +56 -0
- data/lib/gooddata/models/metadata/dashboard/headline_item.rb +56 -0
- data/lib/gooddata/models/metadata/dashboard/iframe_item.rb +46 -0
- data/lib/gooddata/models/metadata/dashboard/report_item.rb +49 -8
- data/lib/gooddata/models/metadata/dashboard/text_item.rb +55 -0
- data/lib/gooddata/models/metadata/dashboard_tab.rb +83 -30
- data/lib/gooddata/models/metadata/dataset.rb +0 -2
- data/lib/gooddata/models/metadata/dimension.rb +1 -3
- data/lib/gooddata/models/metadata/fact.rb +1 -3
- data/lib/gooddata/models/metadata/label.rb +1 -3
- data/lib/gooddata/models/metadata/metric.rb +11 -42
- data/lib/gooddata/models/metadata/report.rb +7 -18
- data/lib/gooddata/models/metadata/report_definition.rb +21 -113
- data/lib/gooddata/models/metadata/scheduled_mail.rb +274 -0
- data/lib/gooddata/models/metadata/scheduled_mail/dashboard_attachment.rb +62 -0
- data/lib/gooddata/models/metadata/scheduled_mail/report_attachment.rb +64 -0
- data/lib/gooddata/models/metadata/variable.rb +8 -2
- data/lib/gooddata/models/model.rb +2 -9
- data/lib/gooddata/models/process.rb +7 -29
- data/lib/gooddata/models/profile.rb +1 -1
- data/lib/gooddata/models/project.rb +131 -167
- data/lib/gooddata/models/project_creator.rb +2 -7
- data/lib/gooddata/models/project_metadata.rb +2 -10
- data/lib/gooddata/models/project_role.rb +4 -10
- data/lib/gooddata/models/report_data_result.rb +3 -5
- data/lib/gooddata/models/schedule.rb +4 -31
- data/lib/gooddata/models/segment.rb +192 -0
- data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +2 -2
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +1 -1
- data/lib/gooddata/models/user_filters/variable_user_filter.rb +11 -0
- data/lib/gooddata/models/user_group.rb +241 -0
- data/lib/gooddata/rest/connection.rb +81 -16
- data/lib/gooddata/rest/object.rb +29 -0
- data/lib/gooddata/rest/object_factory.rb +6 -1
- data/lib/gooddata/rest/resource.rb +7 -1
- data/lib/gooddata/version.rb +1 -1
- data/spec/environment/default.rb +19 -16
- data/spec/environment/develop.rb +10 -10
- data/spec/environment/hotfix.rb +6 -6
- data/spec/environment/production.rb +14 -14
- data/spec/environment/release.rb +6 -6
- data/spec/environment/staging.rb +9 -9
- data/spec/environment/staging_3.rb +14 -15
- data/spec/integration/blueprint_with_grain_spec.rb +72 -0
- data/spec/integration/clients_spec.rb +135 -0
- data/spec/integration/date_dim_switch_spec.rb +142 -0
- data/spec/integration/full_project_spec.rb +3 -3
- data/spec/integration/project_spec.rb +20 -0
- data/spec/integration/segments_spec.rb +141 -0
- data/spec/integration/user_group_spec.rb +127 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/unit/models/domain_spec.rb +7 -1
- data/spec/unit/models/metric_spec.rb +0 -8
- data/spec/unit/models/profile_spec.rb +1 -1
- data/spec/unit/models/report_result_data_spec.rb +6 -0
- metadata +38 -38
- data/lib/gooddata/cli/commands/api_cmd.rb +0 -34
- data/lib/gooddata/cli/commands/console_cmd.rb +0 -40
- data/lib/gooddata/cli/commands/domain_cmd.rb +0 -46
- data/lib/gooddata/cli/commands/process_cmd.rb +0 -145
- data/lib/gooddata/cli/commands/projects_cmd.rb +0 -23
- data/lib/gooddata/cli/commands/run_ruby_cmd.rb +0 -77
- data/lib/gooddata/cli/commands/scaffold_cmd.rb +0 -35
- data/lib/gooddata/cli/commands/user_cmd.rb +0 -24
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2010-2015 GoodData Corporation. All rights reserved.
|
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
|
6
|
+
|
|
7
|
+
module GoodData
|
|
8
|
+
class DashboardAttachment
|
|
9
|
+
include GoodData::Mixin::RootKeyGetter
|
|
10
|
+
include GoodData::Mixin::DataGetter
|
|
11
|
+
|
|
12
|
+
attr_reader :scheduled_email
|
|
13
|
+
attr_accessor :json
|
|
14
|
+
|
|
15
|
+
DEFAULT_OPTS = {
|
|
16
|
+
:allTabs => 1,
|
|
17
|
+
:tabs => []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def initialize(scheduled_email, json)
|
|
21
|
+
@scheduled_email = scheduled_email
|
|
22
|
+
@json = json
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get all tabs flag
|
|
26
|
+
#
|
|
27
|
+
# @return [Fixnum] All dashboard tabs?
|
|
28
|
+
def all_tabs
|
|
29
|
+
data['allTabs']
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Set all tabs flag
|
|
33
|
+
#
|
|
34
|
+
# @param [String | Fixnum] new_all_tabs New value of all_tabs flag to be set
|
|
35
|
+
# @return [Fixnum] New value of all_tabs flag
|
|
36
|
+
def all_tabs=(new_all_tabs)
|
|
37
|
+
data['allTabs'] = new_all_tabs.to_i
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get selected tabs
|
|
41
|
+
#
|
|
42
|
+
# @return [Array<String>] List of selected tabs
|
|
43
|
+
def tabs
|
|
44
|
+
data['tabs']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Set selected tabs
|
|
48
|
+
#
|
|
49
|
+
# @param [Array<String>] new_tabs New list of selected tabs to be set
|
|
50
|
+
# @return [Array<String>] New list of selected tabs
|
|
51
|
+
def tabs=(new_tabs)
|
|
52
|
+
data['tabs'] = new_tabs
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get attachment URI
|
|
56
|
+
#
|
|
57
|
+
# @return [String] Attachment URI
|
|
58
|
+
def uri
|
|
59
|
+
data['uri']
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2010-2015 GoodData Corporation. All rights reserved.
|
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
|
6
|
+
|
|
7
|
+
module GoodData
|
|
8
|
+
class ReportAttachment
|
|
9
|
+
include GoodData::Mixin::RootKeyGetter
|
|
10
|
+
include GoodData::Mixin::DataGetter
|
|
11
|
+
|
|
12
|
+
attr_reader :scheduled_email
|
|
13
|
+
attr_accessor :json
|
|
14
|
+
|
|
15
|
+
DEFAULT_OPTS = {
|
|
16
|
+
:formats => %w(pdf xls),
|
|
17
|
+
:exportOptions => {
|
|
18
|
+
:pageOrientation => 'landscape'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def initialize(scheduled_email, json)
|
|
23
|
+
@scheduled_email = scheduled_email
|
|
24
|
+
@json = json
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Get export options settings
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash] Export options settings
|
|
30
|
+
def export_options
|
|
31
|
+
data['exportOptions']
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Set export options settings
|
|
35
|
+
#
|
|
36
|
+
# @param [Hash] new_export_options New export options settings to be set
|
|
37
|
+
# @return [Hash] New export options settings
|
|
38
|
+
def export_options=(new_export_options)
|
|
39
|
+
data['exportOptions'] = new_export_options
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get formats
|
|
43
|
+
#
|
|
44
|
+
# @return [Array<String>] List of selected formats
|
|
45
|
+
def formats
|
|
46
|
+
data['formats']
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Set formats
|
|
50
|
+
#
|
|
51
|
+
# @param [String | Array<String>] new_formats New list of selected formats to be set
|
|
52
|
+
# @return [Array<String>] New list of selected formats
|
|
53
|
+
def formats=(new_formats)
|
|
54
|
+
data['formats'] = new_formats.is_a?(Array) ? new_formats : [new_formats]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get attachment URI
|
|
58
|
+
#
|
|
59
|
+
# @return [String] Attachment URI
|
|
60
|
+
def uri
|
|
61
|
+
data['uri']
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -10,8 +10,6 @@ require_relative 'metadata'
|
|
|
10
10
|
|
|
11
11
|
module GoodData
|
|
12
12
|
class Variable < MdObject
|
|
13
|
-
root_key :prompt
|
|
14
|
-
|
|
15
13
|
class << self
|
|
16
14
|
# Method intended to get all objects of that type in a specified project
|
|
17
15
|
#
|
|
@@ -69,6 +67,14 @@ module GoodData
|
|
|
69
67
|
values.select { |x| x.level == :user }
|
|
70
68
|
end
|
|
71
69
|
|
|
70
|
+
# Method used for replacing values in their state according to mapping. Can be used to replace any values but it is typically used to replace the URIs. Returns a new object of the same type.
|
|
71
|
+
#
|
|
72
|
+
# @param [Array<Array>]Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping.
|
|
73
|
+
# @return [GoodData::Variable]
|
|
74
|
+
def replace(mapping)
|
|
75
|
+
GoodData::MdObject.replace_quoted(self, mapping)
|
|
76
|
+
end
|
|
77
|
+
|
|
72
78
|
# Retrieves variable values and returns only those related to project
|
|
73
79
|
#
|
|
74
80
|
# @return [Array<GoodData::VariableUserFilter>] Values of variable related to project
|
|
@@ -151,16 +151,9 @@ module GoodData
|
|
|
151
151
|
# @param options [Hash] Additional options
|
|
152
152
|
# @return [Hash] Batch upload result
|
|
153
153
|
def upload_multiple_data(data, project_blueprint, options = { :client => GoodData.connection, :project => GoodData.project })
|
|
154
|
-
client = options
|
|
155
|
-
fail ArgumentError, 'No :client specified' if client.nil?
|
|
154
|
+
client, project = GoodData.get_client_and_project(options)
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
fail ArgumentError, 'No :project specified' if p.nil?
|
|
159
|
-
|
|
160
|
-
project = GoodData::Project[p, options]
|
|
161
|
-
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
162
|
-
|
|
163
|
-
project = options[:project] || GoodData.project
|
|
156
|
+
project ||= GoodData.project
|
|
164
157
|
|
|
165
158
|
manifest = {
|
|
166
159
|
|
|
@@ -14,7 +14,7 @@ require_relative 'execution_detail'
|
|
|
14
14
|
require_relative 'schedule'
|
|
15
15
|
|
|
16
16
|
module GoodData
|
|
17
|
-
class Process <
|
|
17
|
+
class Process < Rest::Resource
|
|
18
18
|
attr_reader :data
|
|
19
19
|
|
|
20
20
|
alias_method :raw_data, :data
|
|
@@ -56,14 +56,7 @@ module GoodData
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def with_deploy(dir, options = {}, &block)
|
|
59
|
-
|
|
60
|
-
fail ArgumentError, 'No :client specified' if client.nil?
|
|
61
|
-
|
|
62
|
-
p = options[:project]
|
|
63
|
-
fail ArgumentError, 'No :project specified' if p.nil?
|
|
64
|
-
|
|
65
|
-
project = GoodData::Project[p, options]
|
|
66
|
-
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
59
|
+
_client, project = GoodData.get_client_and_project(options)
|
|
67
60
|
|
|
68
61
|
GoodData.with_project(project) do
|
|
69
62
|
params = options[:params].nil? ? [] : [options[:params]]
|
|
@@ -83,15 +76,7 @@ module GoodData
|
|
|
83
76
|
end
|
|
84
77
|
|
|
85
78
|
def upload_package(path, files_to_exclude, opts = { :client => GoodData.connection })
|
|
86
|
-
|
|
87
|
-
fail ArgumentError, 'No :client specified' if client.nil?
|
|
88
|
-
|
|
89
|
-
p = opts[:project]
|
|
90
|
-
fail ArgumentError, 'No :project specified' if p.nil?
|
|
91
|
-
|
|
92
|
-
project = GoodData::Project[p, opts]
|
|
93
|
-
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
94
|
-
|
|
79
|
+
GoodData.get_client_and_project(opts)
|
|
95
80
|
zip_and_upload(path, files_to_exclude, opts)
|
|
96
81
|
end
|
|
97
82
|
|
|
@@ -104,14 +89,7 @@ module GoodData
|
|
|
104
89
|
# @option options [String] :process_id ID of a process to be redeployed (do not set if you want to create a new process)
|
|
105
90
|
# @option options [Boolean] :verbose (false) Switch on verbose mode for detailed logging
|
|
106
91
|
def deploy(path, options = { :client => GoodData.client, :project => GoodData.project })
|
|
107
|
-
client = options
|
|
108
|
-
fail ArgumentError, 'No :client specified' if client.nil?
|
|
109
|
-
|
|
110
|
-
p = options[:project]
|
|
111
|
-
fail ArgumentError, 'No :project specified' if p.nil?
|
|
112
|
-
|
|
113
|
-
project = GoodData::Project[p, options]
|
|
114
|
-
fail ArgumentError, 'No :project specified' if project.nil?
|
|
92
|
+
client, project = GoodData.get_client_and_project(options)
|
|
115
93
|
|
|
116
94
|
path = Pathname(path) || fail('Path is not specified')
|
|
117
95
|
files_to_exclude = options[:files_to_exclude].nil? ? [] : options[:files_to_exclude].map { |pname| Pathname(pname) }
|
|
@@ -139,7 +117,7 @@ module GoodData
|
|
|
139
117
|
client.put("/gdc/projects/#{project.pid}/dataload/processes/#{process_id}", data)
|
|
140
118
|
end
|
|
141
119
|
|
|
142
|
-
process = client.create(Process, res, project:
|
|
120
|
+
process = client.create(Process, res, project: project)
|
|
143
121
|
puts HighLine.color("Deploy DONE #{path}", HighLine::GREEN) if verbose
|
|
144
122
|
process
|
|
145
123
|
end
|
|
@@ -284,8 +262,8 @@ module GoodData
|
|
|
284
262
|
client.post(executions_link,
|
|
285
263
|
:execution => {
|
|
286
264
|
:graph => executable.to_s,
|
|
287
|
-
:params => GoodData::Helpers.
|
|
288
|
-
:hiddenParams => GoodData::Helpers.
|
|
265
|
+
:params => GoodData::Helpers.encode_public_params(params),
|
|
266
|
+
:hiddenParams => GoodData::Helpers.encode_hidden_params(hidden_params)
|
|
289
267
|
})
|
|
290
268
|
end
|
|
291
269
|
end
|
|
@@ -19,13 +19,18 @@ require_relative '../rest/resource'
|
|
|
19
19
|
require_relative '../mixins/author'
|
|
20
20
|
require_relative '../mixins/contributor'
|
|
21
21
|
require_relative '../mixins/rest_resource'
|
|
22
|
+
require_relative '../mixins/uri_getter'
|
|
22
23
|
|
|
23
24
|
require_relative 'process'
|
|
24
25
|
require_relative 'project_role'
|
|
25
26
|
require_relative 'blueprint/blueprint'
|
|
26
27
|
|
|
28
|
+
require_relative 'metadata/scheduled_mail'
|
|
29
|
+
require_relative 'metadata/scheduled_mail/dashboard_attachment'
|
|
30
|
+
require_relative 'metadata/scheduled_mail/report_attachment'
|
|
31
|
+
|
|
27
32
|
module GoodData
|
|
28
|
-
class Project <
|
|
33
|
+
class Project < Rest::Resource
|
|
29
34
|
USERSPROJECTS_PATH = '/gdc/account/profile/%s/projects'
|
|
30
35
|
PROJECTS_PATH = '/gdc/projects'
|
|
31
36
|
PROJECT_PATH = '/gdc/projects/%s'
|
|
@@ -48,15 +53,9 @@ module GoodData
|
|
|
48
53
|
|
|
49
54
|
attr_accessor :connection, :json
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
include GoodData::Mixin::RestResource
|
|
55
|
-
|
|
56
|
-
Project.root_key :project
|
|
57
|
-
|
|
58
|
-
include GoodData::Mixin::Author
|
|
59
|
-
include GoodData::Mixin::Contributor
|
|
56
|
+
include Mixin::Author
|
|
57
|
+
include Mixin::Contributor
|
|
58
|
+
include Mixin::UriGetter
|
|
60
59
|
|
|
61
60
|
class << self
|
|
62
61
|
# Returns an array of all projects accessible by
|
|
@@ -129,7 +128,7 @@ module GoodData
|
|
|
129
128
|
project.save
|
|
130
129
|
# until it is enabled or deleted, recur. This should still end if there is a exception thrown out from RESTClient. This sometimes happens from WebApp when request is too long
|
|
131
130
|
while project.state.to_s != 'enabled'
|
|
132
|
-
if project.
|
|
131
|
+
if project.deleted?
|
|
133
132
|
# if project is switched to deleted state, fail. This is usually problem of creating a template which is invalid.
|
|
134
133
|
fail 'Project was marked as deleted during creation. This usually means you were trying to create from template and it failed.'
|
|
135
134
|
end
|
|
@@ -181,6 +180,18 @@ module GoodData
|
|
|
181
180
|
|
|
182
181
|
alias_method :create_dashboard, :add_dashboard
|
|
183
182
|
|
|
183
|
+
def add_user_group(data)
|
|
184
|
+
g = GoodData::UserGroup.create(data.merge(project: self))
|
|
185
|
+
|
|
186
|
+
begin
|
|
187
|
+
g.save
|
|
188
|
+
rescue RestClient::Conflict
|
|
189
|
+
user_groups(data[:name])
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
alias_method :create_group, :add_user_group
|
|
194
|
+
|
|
184
195
|
# Creates a metric in a project
|
|
185
196
|
#
|
|
186
197
|
# @param [options] Optional report options
|
|
@@ -259,7 +270,7 @@ module GoodData
|
|
|
259
270
|
#
|
|
260
271
|
# @return [GoodData::ProjectRole] Project role if found
|
|
261
272
|
def blueprint(options = {})
|
|
262
|
-
result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true })
|
|
273
|
+
result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true })
|
|
263
274
|
polling_url = result['asyncTask']['link']['poll']
|
|
264
275
|
model = client.poll_on_code(polling_url, options)
|
|
265
276
|
bp = GoodData::Model::FromWire.from_wire(model)
|
|
@@ -293,7 +304,7 @@ module GoodData
|
|
|
293
304
|
begin
|
|
294
305
|
# Create the project first so we know that it is passing.
|
|
295
306
|
# What most likely is wrong is the token and the export actaully takes majority of the time
|
|
296
|
-
new_project = GoodData::Project.create(options.merge(:title => a_title, :client => client))
|
|
307
|
+
new_project = GoodData::Project.create(options.merge(:title => a_title, :client => client, :driver => content[:driver]))
|
|
297
308
|
export_token = export_clone(options)
|
|
298
309
|
new_project.import_clone(export_token)
|
|
299
310
|
rescue
|
|
@@ -344,6 +355,10 @@ module GoodData
|
|
|
344
355
|
result['exportArtifact']['token']
|
|
345
356
|
end
|
|
346
357
|
|
|
358
|
+
def user_groups(id = :all, options = {})
|
|
359
|
+
GoodData::UserGroup[id, options.merge(project: self)]
|
|
360
|
+
end
|
|
361
|
+
|
|
347
362
|
# Imports a clone into current project. The project has to be freshly
|
|
348
363
|
# created.
|
|
349
364
|
#
|
|
@@ -397,10 +412,17 @@ module GoodData
|
|
|
397
412
|
|
|
398
413
|
# Deletes project
|
|
399
414
|
def delete
|
|
400
|
-
fail "Project '#{title}' with id #{uri} is already deleted" if
|
|
415
|
+
fail "Project '#{title}' with id #{uri} is already deleted" if deleted?
|
|
401
416
|
client.delete(uri)
|
|
402
417
|
end
|
|
403
418
|
|
|
419
|
+
# Returns true if project is in deleted state
|
|
420
|
+
#
|
|
421
|
+
# @return [Boolean] Returns true if object deleted. False otherwise.
|
|
422
|
+
def deleted?
|
|
423
|
+
state == :deleted
|
|
424
|
+
end
|
|
425
|
+
|
|
404
426
|
# Helper for getting rid of all data in the project
|
|
405
427
|
#
|
|
406
428
|
# @option options [Boolean] :force has to be added otherwise the operation is not performed
|
|
@@ -894,171 +916,68 @@ module GoodData
|
|
|
894
916
|
self
|
|
895
917
|
end
|
|
896
918
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
fail ArgumentError, 'No :new dimension specified' if opts[:new].nil?
|
|
908
|
-
|
|
909
|
-
# Merge with default options
|
|
910
|
-
opts = DEFAULT_REPLACE_DATE_DIMENSION_OPTIONS.merge(opts)
|
|
911
|
-
|
|
912
|
-
get_attribute = lambda do |attr|
|
|
913
|
-
return attr if attr.is_a?(GoodData::Attribute)
|
|
914
|
-
|
|
915
|
-
res = attribute_by_identifier(attr)
|
|
916
|
-
return res if res
|
|
917
|
-
|
|
918
|
-
attribute_by_title(attr)
|
|
919
|
-
end
|
|
920
|
-
|
|
921
|
-
if opts[:old] && opts[:new]
|
|
922
|
-
fail ArgumentError, 'You specified both :old => :new and :mapping' if opts[:mapping] && !opts[:mapping].empty?
|
|
923
|
-
|
|
924
|
-
attrs = attributes_by_title(/\(#{opts[:old]}\)$/)
|
|
925
|
-
|
|
926
|
-
attrs.each do |old_attr|
|
|
927
|
-
new_attr_title = old_attr.title.sub("(#{opts[:old]})", "(#{opts[:new]})")
|
|
928
|
-
new_attr = attribute_by_title(new_attr_title)
|
|
929
|
-
|
|
930
|
-
fail "Unable to find attribute '#{new_attr_title}' in date dimension '#{opts[:new]}'" if new_attr.nil?
|
|
931
|
-
|
|
932
|
-
opts[:mapping][old_attr] = new_attr
|
|
933
|
-
end
|
|
934
|
-
end
|
|
935
|
-
|
|
936
|
-
mufs = user_filters
|
|
937
|
-
|
|
938
|
-
# Replaces string anywhere in JSON with another string and returns back new JSON
|
|
939
|
-
json_replace = lambda do |object, old_uri, new_uri|
|
|
940
|
-
old_json = JSON.generate(object.json)
|
|
941
|
-
regexp_replace = Regexp.new(old_uri + '([^0-9])')
|
|
942
|
-
|
|
943
|
-
new_json = old_json.gsub(regexp_replace, "#{new_uri}\\1")
|
|
944
|
-
if old_json != new_json
|
|
945
|
-
object.json = JSON.parse(new_json)
|
|
946
|
-
object.save
|
|
947
|
-
end
|
|
948
|
-
object
|
|
949
|
-
end
|
|
919
|
+
# Method used for walking through objects in project and trying to replace all occurences of some object for another object. This is typically used as a means for exchanging Date dimensions.
|
|
920
|
+
#
|
|
921
|
+
# @param mapping [Array<Array>] Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping.
|
|
922
|
+
def replace_from_mapping(mapping, opts = {})
|
|
923
|
+
default = {
|
|
924
|
+
:purge => false,
|
|
925
|
+
:dry_run => false
|
|
926
|
+
}
|
|
927
|
+
opts = default.merge(opts)
|
|
928
|
+
dry_run = opts[:dry_run]
|
|
950
929
|
|
|
951
|
-
# delete old report definitions (only the last version of each report is kept)
|
|
952
930
|
if opts[:purge]
|
|
953
931
|
GoodData.logger.info 'Purging old project definitions'
|
|
954
932
|
reports.peach(&:purge_report_of_unused_definitions!)
|
|
955
933
|
end
|
|
956
934
|
|
|
957
|
-
fail ArgumentError, 'No
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
labels_mapping = {}
|
|
977
|
-
|
|
978
|
-
old_date.labels.each do |old_label|
|
|
979
|
-
new_label_title = old_label.title.sub("(#{opts[:old]})", "(#{opts[:new]})")
|
|
980
|
-
|
|
981
|
-
# Go through all labels, label_by_title has some issues
|
|
982
|
-
new_date.json['attribute']['content']['displayForms'].each do |label_tmp|
|
|
983
|
-
if label_tmp['meta']['title'] == new_label_title
|
|
984
|
-
new_label = labels(label_tmp['meta']['uri'])
|
|
985
|
-
labels_mapping[old_label] = new_label
|
|
935
|
+
fail ArgumentError, 'No mapping specified' if mapping.blank?
|
|
936
|
+
rds = report_definitions
|
|
937
|
+
|
|
938
|
+
{
|
|
939
|
+
# data_permissions: data_permissions,
|
|
940
|
+
variables: variables,
|
|
941
|
+
dashboards: dashboards,
|
|
942
|
+
metrics: metrics,
|
|
943
|
+
report_definitions: rds
|
|
944
|
+
}.each do |key, collection|
|
|
945
|
+
puts "Replacing #{key}"
|
|
946
|
+
collection.peach do |item|
|
|
947
|
+
new_item = item.replace(mapping)
|
|
948
|
+
if new_item.json != item.json
|
|
949
|
+
if dry_run
|
|
950
|
+
GoodData.logger.info "Would save #{new_item.uri}. Running in dry run mode"
|
|
951
|
+
else
|
|
952
|
+
GoodData.logger.info "Saving #{new_item.uri}"
|
|
953
|
+
new_item.save
|
|
986
954
|
end
|
|
987
955
|
end
|
|
988
956
|
end
|
|
957
|
+
end
|
|
989
958
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
affected_metric.save unless opts[:dry_run]
|
|
1002
|
-
end
|
|
1003
|
-
|
|
1004
|
-
# Then search which reports are still using this attribute after replacement in metric...
|
|
1005
|
-
dependent = old_date.usedby
|
|
1006
|
-
GoodData.logger.info 'Fixing reports (standard)...'
|
|
1007
|
-
dependent.each do |dependent_object|
|
|
1008
|
-
# This does not seem to work every time... some references are kept...
|
|
1009
|
-
next if dependent_object['category'] != 'reportDefinition'
|
|
1010
|
-
|
|
1011
|
-
affected_rd = report_definitions(dependent_object['link'])
|
|
1012
|
-
|
|
1013
|
-
GoodData.logger.info "reportDefinition (#{affected_rd.uri}) contains old date attribute '#{old_date.title}' ...replacing"
|
|
1014
|
-
affected_rd.replace(old_date.uri, new_date.uri)
|
|
1015
|
-
|
|
1016
|
-
# Affected_rd.replace(labels_mapping) #not sure if this is working correctly, try to do it one by one
|
|
1017
|
-
labels_mapping.each_pair do |old_label, new_label|
|
|
1018
|
-
affected_rd.replace(old_label.uri, new_label.uri)
|
|
1019
|
-
end
|
|
1020
|
-
|
|
1021
|
-
affected_rd.save unless opts[:dry_run]
|
|
1022
|
-
end
|
|
1023
|
-
|
|
1024
|
-
# Then search which dashboards and reports are still using this attribute after standard replacement in reports...
|
|
1025
|
-
dependent = old_date.usedby
|
|
1026
|
-
GoodData.logger.info 'Fixing reports (force) & dashboards...'
|
|
1027
|
-
|
|
1028
|
-
# If standard replace did not work, use force...
|
|
1029
|
-
dependent.each do |dependent_object|
|
|
1030
|
-
case dependent_object['category']
|
|
1031
|
-
when 'reportDefinition'
|
|
1032
|
-
affected_rd = report_definitions(dependent_object['link'])
|
|
1033
|
-
|
|
1034
|
-
GoodData.logger.info "reportDefinition '#{affected_rd.title}' (#{affected_rd.uri}) still contains old date attribute '#{old_date.title}' ...replacing by force"
|
|
1035
|
-
json_replace.call(affected_rd, old_date.uri, new_date.uri)
|
|
1036
|
-
|
|
1037
|
-
# Iterate over all labels
|
|
1038
|
-
labels_mapping.each_pair do |old_label, new_label|
|
|
1039
|
-
json_replace.call(affected_rd, old_label.uri, new_label.uri)
|
|
1040
|
-
end
|
|
1041
|
-
|
|
1042
|
-
affected_rd.save unless opts[:dry_run]
|
|
1043
|
-
when 'projectDashboard'
|
|
1044
|
-
affected_dashboard = dashboards(dependent_object['link'])
|
|
1045
|
-
|
|
1046
|
-
GoodData.logger.info "Dashboard '#{affected_dashboard.title}' (#{affected_dashboard.uri}) contains old date attribute '#{old_date.title}' ...replacing by force"
|
|
1047
|
-
json_replace.call(affected_dashboard, old_date.uri, new_date.uri)
|
|
1048
|
-
|
|
1049
|
-
# Iterate over all labels
|
|
1050
|
-
labels_mapping.each_pair do |old_label, new_label|
|
|
1051
|
-
json_replace.call(affected_dashboard, old_label.uri, new_label.uri)
|
|
1052
|
-
end
|
|
1053
|
-
|
|
1054
|
-
affected_dashboard.save unless opts[:dry_run]
|
|
959
|
+
GoodData.logger.info 'Replacing hidden metrics'
|
|
960
|
+
local_metrics = rds.pmapcat { |rd| rd.using('metric') }.select { |m| m['deprecated'] == '1' }
|
|
961
|
+
puts "Found #{local_metrics.count} metrics"
|
|
962
|
+
local_metrics.pmap { |m| metrics(m['link']) }.peach do |item|
|
|
963
|
+
new_item = item.replace(mapping)
|
|
964
|
+
if new_item.json != item.json
|
|
965
|
+
if dry_run
|
|
966
|
+
GoodData.logger.info "Would save #{new_item.uri}. Running in dry run mode"
|
|
967
|
+
else
|
|
968
|
+
GoodData.logger.info "Saving #{new_item.uri}"
|
|
969
|
+
new_item.save
|
|
1055
970
|
end
|
|
1056
971
|
end
|
|
972
|
+
end
|
|
1057
973
|
|
|
1058
|
-
|
|
1059
|
-
|
|
974
|
+
GoodData.logger.info 'Replacing variable values'
|
|
975
|
+
variables.each do |var|
|
|
976
|
+
var.values.peach do |val|
|
|
977
|
+
val.replace(mapping).save unless dry_run
|
|
1060
978
|
end
|
|
1061
979
|
end
|
|
980
|
+
nil
|
|
1062
981
|
end
|
|
1063
982
|
|
|
1064
983
|
# Helper for getting reports of a project
|
|
@@ -1107,6 +1026,15 @@ module GoodData
|
|
|
1107
1026
|
self
|
|
1108
1027
|
end
|
|
1109
1028
|
|
|
1029
|
+
# Schedules an email with dashboard or report content
|
|
1030
|
+
def schedule_mail(options = GoodData::ScheduledMail::DEFAULT_OPTS)
|
|
1031
|
+
GoodData::ScheduledMail.create(options.merge(client: client, project: self))
|
|
1032
|
+
end
|
|
1033
|
+
|
|
1034
|
+
def scheduled_mails(options = { :full => false })
|
|
1035
|
+
GoodData::ScheduledMail[:all, options.merge(project: self, client: client)]
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1110
1038
|
# @param [String | Number | Object] Anything that you can pass to GoodData::Schedule[id]
|
|
1111
1039
|
# @return [GoodData::Schedule | Array<GoodData::Schedule>] schedule instance or list
|
|
1112
1040
|
def schedules(id = :all)
|
|
@@ -1190,7 +1118,7 @@ module GoodData
|
|
|
1190
1118
|
|
|
1191
1119
|
alias_method :members, :users
|
|
1192
1120
|
|
|
1193
|
-
def whitelist_users(new_users, users_list, whitelist)
|
|
1121
|
+
def whitelist_users(new_users, users_list, whitelist, mode = :exclude)
|
|
1194
1122
|
return [new_users, users_list] unless whitelist
|
|
1195
1123
|
|
|
1196
1124
|
new_whitelist_proc = proc do |user|
|
|
@@ -1201,7 +1129,11 @@ module GoodData
|
|
|
1201
1129
|
whitelist.any? { |wl| wl.is_a?(Regexp) ? user.login =~ wl : user.login.include?(wl) }
|
|
1202
1130
|
end
|
|
1203
1131
|
|
|
1204
|
-
|
|
1132
|
+
if mode == :include
|
|
1133
|
+
[new_users.select(&new_whitelist_proc), users_list.select(&whitelist_proc)]
|
|
1134
|
+
elsif mode == :exclude
|
|
1135
|
+
[new_users.reject(&new_whitelist_proc), users_list.reject(&whitelist_proc)]
|
|
1136
|
+
end
|
|
1205
1137
|
end
|
|
1206
1138
|
|
|
1207
1139
|
# Imports users
|
|
@@ -1214,6 +1146,9 @@ module GoodData
|
|
|
1214
1146
|
|
|
1215
1147
|
whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
|
|
1216
1148
|
|
|
1149
|
+
# First check that if groups are provided we have them set up
|
|
1150
|
+
check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq)
|
|
1151
|
+
|
|
1217
1152
|
# conform the role on list of new users so we can diff them with the users coming from the project
|
|
1218
1153
|
diffable_new_with_default_role = whitelisted_new_users.map do |u|
|
|
1219
1154
|
u[:role] = Array(u[:role] || u[:roles] || 'readOnlyUser')
|
|
@@ -1259,6 +1194,30 @@ module GoodData
|
|
|
1259
1194
|
to_remove = diff[:removed].reject { |user| user[:status] == 'DISABLED' || user[:status] == :disabled }
|
|
1260
1195
|
GoodData.logger.warn("Removing #{to_remove.count} users from project (#{pid})")
|
|
1261
1196
|
results.concat(disable_users(to_remove))
|
|
1197
|
+
|
|
1198
|
+
# reassign to groups
|
|
1199
|
+
mappings = new_users.map(&:to_hash).flat_map do |user|
|
|
1200
|
+
groups = user[:user_group] || []
|
|
1201
|
+
groups.map { |g| [user[:login], g] }
|
|
1202
|
+
end
|
|
1203
|
+
unless mappings.empty?
|
|
1204
|
+
users_lookup = users.reduce({}) do |a, e|
|
|
1205
|
+
a[e.login] = e
|
|
1206
|
+
a
|
|
1207
|
+
end
|
|
1208
|
+
mappings.group_by { |_, g| g }.each do |g, mapping|
|
|
1209
|
+
# find group + set users
|
|
1210
|
+
# CARE YOU DO NOT KNOW URI
|
|
1211
|
+
user_groups(g).set_members(mapping.map { |user, _| user }.map { |login| users_lookup[login] && users_lookup[login].uri })
|
|
1212
|
+
end
|
|
1213
|
+
mentioned_groups = mappings.map(&:last).uniq
|
|
1214
|
+
groups_to_cleanup = user_groups.reject { |g| mentioned_groups.include?(g.name) }
|
|
1215
|
+
# clean all groups not mentioned with exception of whitelisted users
|
|
1216
|
+
groups_to_cleanup.each do |g|
|
|
1217
|
+
g.set_members(whitelist_users(g.members.map(&:to_hash), [], options[:whitelists], :include).first.map { |x| x[:uri] })
|
|
1218
|
+
end
|
|
1219
|
+
end
|
|
1220
|
+
results
|
|
1262
1221
|
end
|
|
1263
1222
|
|
|
1264
1223
|
def disable_users(list)
|
|
@@ -1274,6 +1233,12 @@ module GoodData
|
|
|
1274
1233
|
end
|
|
1275
1234
|
end
|
|
1276
1235
|
|
|
1236
|
+
def check_groups(specified_groups)
|
|
1237
|
+
groups = user_groups.map(&:name)
|
|
1238
|
+
missing_groups = specified_groups - groups
|
|
1239
|
+
fail "All groups have to be specified before you try to import users. Groups that are currently in project are #{groups.join(',')} and you asked for #{missing_groups.join(',')}" unless missing_groups.empty?
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1277
1242
|
# Update user
|
|
1278
1243
|
#
|
|
1279
1244
|
# @param user User to be updated
|
|
@@ -1288,7 +1253,6 @@ module GoodData
|
|
|
1288
1253
|
fail ArgumentError, "User #{user_uri} could not be aded. #{failure.first['message']}" unless failure.blank?
|
|
1289
1254
|
res
|
|
1290
1255
|
end
|
|
1291
|
-
|
|
1292
1256
|
alias_method :add_user, :set_user_roles
|
|
1293
1257
|
|
|
1294
1258
|
# Update list of users
|