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