gooddata 0.6.24 → 0.6.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +54 -0
  3. data/CHANGELOG.md +3 -0
  4. data/DEPENDENCIES.md +155 -154
  5. data/README.md +15 -6
  6. data/Rakefile +5 -3
  7. data/dependency_decisions.yml +2 -0
  8. data/gooddata.gemspec +2 -3
  9. data/lib/gooddata/cli/cli.rb +1 -3
  10. data/lib/gooddata/cli/commands/auth_cmd.rb +16 -7
  11. data/lib/gooddata/cli/commands/project_cmd.rb +16 -178
  12. data/lib/gooddata/cli/shared.rb +46 -44
  13. data/lib/gooddata/commands/auth.rb +4 -0
  14. data/lib/gooddata/commands/project.rb +7 -24
  15. data/lib/gooddata/exceptions/object_migration.rb +4 -0
  16. data/lib/gooddata/exceptions/segment_not_empty.rb +18 -0
  17. data/lib/gooddata/extensions/object.rb +12 -0
  18. data/lib/gooddata/goodzilla/goodzilla.rb +56 -9
  19. data/lib/gooddata/helpers/global_helpers.rb +92 -0
  20. data/lib/gooddata/mixins/md_finders.rb +2 -8
  21. data/lib/gooddata/mixins/md_grantees.rb +42 -0
  22. data/lib/gooddata/mixins/md_id_to_uri.rb +1 -8
  23. data/lib/gooddata/mixins/md_object_id.rb +1 -1
  24. data/lib/gooddata/mixins/md_object_indexer.rb +5 -8
  25. data/lib/gooddata/mixins/md_object_query.rb +2 -2
  26. data/lib/gooddata/mixins/not_group.rb +17 -0
  27. data/lib/gooddata/mixins/rest_getters.rb +2 -2
  28. data/lib/gooddata/mixins/rest_resource.rb +1 -0
  29. data/lib/gooddata/mixins/to_json.rb +11 -0
  30. data/lib/gooddata/mixins/uri_getter.rb +9 -0
  31. data/lib/gooddata/models/blueprint/anchor_field.rb +14 -0
  32. data/lib/gooddata/models/blueprint/project_blueprint.rb +15 -1
  33. data/lib/gooddata/models/blueprint/to_wire.rb +10 -0
  34. data/lib/gooddata/models/client.rb +178 -0
  35. data/lib/gooddata/models/client_synchronization_result.rb +31 -0
  36. data/lib/gooddata/models/client_synchronization_result_details.rb +41 -0
  37. data/lib/gooddata/models/datawarehouse.rb +1 -5
  38. data/lib/gooddata/models/domain.rb +85 -1
  39. data/lib/gooddata/models/execution.rb +0 -2
  40. data/lib/gooddata/models/execution_detail.rb +0 -2
  41. data/lib/gooddata/models/from_wire.rb +10 -0
  42. data/lib/gooddata/models/invitation.rb +1 -1
  43. data/lib/gooddata/models/links.rb +1 -1
  44. data/lib/gooddata/models/membership.rb +10 -6
  45. data/lib/gooddata/models/metadata.rb +98 -11
  46. data/lib/gooddata/models/metadata/attribute.rb +6 -7
  47. data/lib/gooddata/models/metadata/dashboard.rb +41 -75
  48. data/lib/gooddata/models/metadata/dashboard/dashboard_item.rb +20 -4
  49. data/lib/gooddata/models/metadata/dashboard/filter_apply_item.rb +37 -0
  50. data/lib/gooddata/models/metadata/dashboard/filter_item.rb +49 -0
  51. data/lib/gooddata/models/metadata/dashboard/geo_chart_item.rb +56 -0
  52. data/lib/gooddata/models/metadata/dashboard/headline_item.rb +56 -0
  53. data/lib/gooddata/models/metadata/dashboard/iframe_item.rb +46 -0
  54. data/lib/gooddata/models/metadata/dashboard/report_item.rb +49 -8
  55. data/lib/gooddata/models/metadata/dashboard/text_item.rb +55 -0
  56. data/lib/gooddata/models/metadata/dashboard_tab.rb +83 -30
  57. data/lib/gooddata/models/metadata/dataset.rb +0 -2
  58. data/lib/gooddata/models/metadata/dimension.rb +1 -3
  59. data/lib/gooddata/models/metadata/fact.rb +1 -3
  60. data/lib/gooddata/models/metadata/label.rb +1 -3
  61. data/lib/gooddata/models/metadata/metric.rb +11 -42
  62. data/lib/gooddata/models/metadata/report.rb +7 -18
  63. data/lib/gooddata/models/metadata/report_definition.rb +21 -113
  64. data/lib/gooddata/models/metadata/scheduled_mail.rb +274 -0
  65. data/lib/gooddata/models/metadata/scheduled_mail/dashboard_attachment.rb +62 -0
  66. data/lib/gooddata/models/metadata/scheduled_mail/report_attachment.rb +64 -0
  67. data/lib/gooddata/models/metadata/variable.rb +8 -2
  68. data/lib/gooddata/models/model.rb +2 -9
  69. data/lib/gooddata/models/process.rb +7 -29
  70. data/lib/gooddata/models/profile.rb +1 -1
  71. data/lib/gooddata/models/project.rb +131 -167
  72. data/lib/gooddata/models/project_creator.rb +2 -7
  73. data/lib/gooddata/models/project_metadata.rb +2 -10
  74. data/lib/gooddata/models/project_role.rb +4 -10
  75. data/lib/gooddata/models/report_data_result.rb +3 -5
  76. data/lib/gooddata/models/schedule.rb +4 -31
  77. data/lib/gooddata/models/segment.rb +192 -0
  78. data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +2 -2
  79. data/lib/gooddata/models/user_filters/user_filter_builder.rb +1 -1
  80. data/lib/gooddata/models/user_filters/variable_user_filter.rb +11 -0
  81. data/lib/gooddata/models/user_group.rb +241 -0
  82. data/lib/gooddata/rest/connection.rb +81 -16
  83. data/lib/gooddata/rest/object.rb +29 -0
  84. data/lib/gooddata/rest/object_factory.rb +6 -1
  85. data/lib/gooddata/rest/resource.rb +7 -1
  86. data/lib/gooddata/version.rb +1 -1
  87. data/spec/environment/default.rb +19 -16
  88. data/spec/environment/develop.rb +10 -10
  89. data/spec/environment/hotfix.rb +6 -6
  90. data/spec/environment/production.rb +14 -14
  91. data/spec/environment/release.rb +6 -6
  92. data/spec/environment/staging.rb +9 -9
  93. data/spec/environment/staging_3.rb +14 -15
  94. data/spec/integration/blueprint_with_grain_spec.rb +72 -0
  95. data/spec/integration/clients_spec.rb +135 -0
  96. data/spec/integration/date_dim_switch_spec.rb +142 -0
  97. data/spec/integration/full_project_spec.rb +3 -3
  98. data/spec/integration/project_spec.rb +20 -0
  99. data/spec/integration/segments_spec.rb +141 -0
  100. data/spec/integration/user_group_spec.rb +127 -0
  101. data/spec/spec_helper.rb +4 -0
  102. data/spec/unit/models/domain_spec.rb +7 -1
  103. data/spec/unit/models/metric_spec.rb +0 -8
  104. data/spec/unit/models/profile_spec.rb +1 -1
  105. data/spec/unit/models/report_result_data_spec.rb +6 -0
  106. metadata +38 -38
  107. data/lib/gooddata/cli/commands/api_cmd.rb +0 -34
  108. data/lib/gooddata/cli/commands/console_cmd.rb +0 -40
  109. data/lib/gooddata/cli/commands/domain_cmd.rb +0 -46
  110. data/lib/gooddata/cli/commands/process_cmd.rb +0 -145
  111. data/lib/gooddata/cli/commands/projects_cmd.rb +0 -23
  112. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +0 -77
  113. data/lib/gooddata/cli/commands/scaffold_cmd.rb +0 -35
  114. data/lib/gooddata/cli/commands/user_cmd.rb +0 -24
@@ -38,17 +38,12 @@ module GoodData
38
38
 
39
39
  def migrate_datasets(spec, opts = {})
40
40
  opts = { client: GoodData.connection }.merge(opts)
41
- client = opts[:client]
42
41
  dry_run = opts[:dry_run]
43
- fail ArgumentError, 'No :client specified' if client.nil?
44
42
 
45
- p = opts[:project]
46
- fail ArgumentError, 'No :project specified' if p.nil?
43
+ client, project = GoodData.get_client_and_project(opts)
47
44
 
48
- project = client.projects(p)
49
- fail ArgumentError, 'Wrong :project specified' if project.nil?
50
45
  bp = ProjectBlueprint.new(spec)
51
- uri = "/gdc/projects/#{project.pid}/model/diff"
46
+ uri = "/gdc/projects/#{project.pid}/model/diff?includeGrain=true"
52
47
  result = client.post(uri, bp.to_wire)
53
48
 
54
49
  link = result['asyncTask']['link']['poll']
@@ -12,11 +12,7 @@ module GoodData
12
12
  end
13
13
 
14
14
  def [](key, opts = { :client => GoodData.connection, :project => GoodData.project })
15
- client = opts[:client]
16
- fail ArgumentError, 'No :client specified' if client.nil?
17
-
18
- project = opts[:project]
19
- fail ArgumentError, 'No :project specified' if project.nil?
15
+ client, project = GoodData.get_client_and_project(opts)
20
16
 
21
17
  if key == :all
22
18
  uri = "/gdc/projects/#{project.pid}/dataload/metadata"
@@ -43,11 +39,7 @@ module GoodData
43
39
  end
44
40
 
45
41
  def []=(key, opts = { :client => GoodData.connection, :project => GoodData.project }, val = nil)
46
- client = opts[:client]
47
- fail ArgumentError, 'No :client specified' if client.nil?
48
-
49
- project = opts[:project]
50
- fail ArgumentError, 'No :project specified' if project.nil?
42
+ client, project = GoodData.get_client_and_project(opts)
51
43
 
52
44
  data = {
53
45
  :metadataItem => {
@@ -13,16 +13,10 @@ require_relative '../rest/rest'
13
13
  require_relative '../mixins/rest_resource'
14
14
 
15
15
  module GoodData
16
- class ProjectRole < GoodData::Rest::Object
17
- attr_accessor :json
18
-
19
- include GoodData::Mixin::RestResource
20
-
21
- root_key :projectRole
22
-
23
- include GoodData::Mixin::Author
24
- include GoodData::Mixin::Contributor
25
- include GoodData::Mixin::Timestamps
16
+ class ProjectRole < Rest::Resource
17
+ include Mixin::Author
18
+ include Mixin::Contributor
19
+ include Mixin::Timestamps
26
20
 
27
21
  EMPTY_OBJECT = {
28
22
  'projectRole' => {
@@ -5,15 +5,13 @@
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  module GoodData
8
- class ReportDataResult < Rest::Object
8
+ class ReportDataResult < Rest::Resource
9
9
  class << self
10
10
  # Does all the needed parsing on the apyload coming from the API and returns an instance of ReportDataResult
11
11
  #
12
12
  # @param [Hash] data Data coming from the API
13
13
  # @return [GoodData::ReportDataResult] Returns new report data result
14
- def from_xtab(data, options = {})
15
- client = options[:client]
16
- project = options[:project]
14
+ def from_xtab(data)
17
15
  top = top_headers(data)
18
16
  left = left_headers(data)
19
17
  jank = GoodData::Helpers.zeroes(rows(top), cols(left), nil)
@@ -23,7 +21,7 @@ module GoodData
23
21
  a = jank.zip(top).map { |x, y| x + y }
24
22
  b = left.zip(stuff).map { |x, y| x + y }
25
23
  result = a + b
26
- client ? client.create(ReportDataResult, data: result, top: rows(top), left: cols(left), project: project) : ReportDataResult.new(data: result, top: rows(top), left: cols(left))
24
+ ReportDataResult.new(data: result, top: rows(top), left: cols(left))
27
25
  end
28
26
 
29
27
  private
@@ -15,12 +15,6 @@ module GoodData
15
15
  class Schedule < Rest::Resource
16
16
  attr_reader :dirty, :json
17
17
 
18
- alias_method :data, :json
19
- alias_method :raw_data, :json
20
-
21
- include GoodData::Mixin::RestResource
22
- root_key :schedule
23
-
24
18
  SCHEDULE_TEMPLATE = {
25
19
  :schedule => {
26
20
  :type => nil,
@@ -36,14 +30,7 @@ module GoodData
36
30
  # @param id [String] URL, ID of schedule or :all
37
31
  # @return [GoodData::Schedule|Array<GoodData::Schedule>] List of schedules
38
32
  def [](id, opts = { :client => GoodData.connection, :project => GoodData.project })
39
- c = client(opts)
40
- fail ArgumentError, 'No :client specified' if c.nil?
41
-
42
- p = opts[:project]
43
- fail ArgumentError, 'No :project specified' if p.nil?
44
-
45
- project = GoodData::Project[p, opts]
46
- fail ArgumentError, 'Wrong :project specified' if project.nil?
33
+ c, project = GoodData.get_client_and_project(opts)
47
34
 
48
35
  if id == :all
49
36
  GoodData::Schedule.all(opts)
@@ -62,14 +49,7 @@ module GoodData
62
49
  # Returns list of all schedules for active project
63
50
  # @return [Array<GoodData::Schedule>] List of schedules
64
51
  def all(opts = { :client => GoodData.connection, :project => GoodData.project })
65
- c = client(opts)
66
- fail ArgumentError, 'No :client specified' if c.nil?
67
-
68
- p = opts[:project]
69
- fail ArgumentError, 'No :project specified' if p.nil?
70
-
71
- project = GoodData::Project[p, opts]
72
- fail ArgumentError, 'Wrong :project specified' if project.nil?
52
+ c, project = GoodData.get_client_and_project(opts)
73
53
 
74
54
  tmp = c.get "/gdc/projects/#{project.pid}/schedules"
75
55
  tmp['schedules']['items'].map { |schedule| c.create(GoodData::Schedule, schedule, project: project) }
@@ -83,20 +63,13 @@ module GoodData
83
63
  # @param options [Hash] Optional options
84
64
  # @return [GoodData::Schedule] New GoodData::Schedule instance
85
65
  def create(process_id, trigger, executable, options = {})
86
- c = client(options)
87
- fail ArgumentError, 'No :client specified' if c.nil?
88
-
89
- p = options[:project]
90
- fail ArgumentError, 'No :project specified' if p.nil?
91
-
92
- project = GoodData::Project[p, options]
93
- fail ArgumentError, 'Wrong :project specified' if project.nil?
66
+ c, project = GoodData.get_client_and_project(options)
94
67
 
95
68
  fail 'Process ID has to be provided' if process_id.blank?
96
69
  fail 'Executable has to be provided' if executable.blank?
97
70
  fail 'Trigger schedule has to be provided' if trigger.blank?
98
71
 
99
- schedule = c.create(GoodData::Schedule, GoodData::Helpers.deep_stringify_keys(GoodData::Helpers.deep_dup(SCHEDULE_TEMPLATE)), client: c, project: p)
72
+ schedule = c.create(GoodData::Schedule, GoodData::Helpers.deep_stringify_keys(GoodData::Helpers.deep_dup(SCHEDULE_TEMPLATE)), client: c, project: project)
100
73
 
101
74
  default_opts = {
102
75
  :type => 'MSETL',
@@ -0,0 +1,192 @@
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
+ require_relative './client'
8
+ require_relative './domain'
9
+ require_relative '../models/client_synchronization_result'
10
+
11
+ require_relative '../mixins/data_property_reader'
12
+ require_relative '../mixins/links'
13
+ require_relative '../mixins/rest_resource'
14
+ require_relative '../mixins/uri_getter'
15
+
16
+ module GoodData
17
+ class Segment < Rest::Resource
18
+ SYNCHRONIZE_URI = '/gdc/domains/%s/segments/%s/synchronizeClients'
19
+
20
+ attr_accessor :domain
21
+
22
+ data_property_reader 'id'
23
+
24
+ include Mixin::Links
25
+ include Mixin::UriGetter
26
+
27
+ SEGMENT_TEMPLATE = {
28
+ :segment => {
29
+ :id => nil,
30
+ :masterProject => nil
31
+ }
32
+ }
33
+
34
+ class << self
35
+ # Returns list of all segments or a particular segment
36
+ #
37
+ # @param id [String|Symbol] Uri of the segment required or :all for all segments.
38
+ # @return [Array<GoodData::Segment>] List of segments for a particular domain
39
+ def [](id, opts = {})
40
+ domain = opts[:domain]
41
+ fail ArgumentError, 'No :domain specified' if domain.nil?
42
+
43
+ client = domain.client
44
+ fail ArgumentError, 'No client specified' if client.nil?
45
+
46
+ if id == :all
47
+ GoodData::Segment.all(opts)
48
+ else
49
+ result = client.get(domain.segments_uri + "/segments/#{CGI.escape(id)}")
50
+ client.create(GoodData::Segment, result.merge('domain' => domain))
51
+ end
52
+ end
53
+
54
+ # Returns list of all segments for domain
55
+ #
56
+ # @param opts [Hash] Options. Should contain :domain for which you want to get the segments.
57
+ # @return [Array<GoodData::Segment>] List of segments for a particular domain
58
+ def all(opts = {})
59
+ domain = opts[:domain]
60
+ fail 'Domain has to be passed in options' unless domain
61
+ client = domain.client
62
+
63
+ results = client.get(domain.segments_uri + '/segments')
64
+ GoodData::Helpers.get_path(results, %w(segments items)).map { |i| client.create(GoodData::Segment, i.merge('domain' => domain)) }
65
+ end
66
+
67
+ # Creates new segment from parameters passed
68
+ #
69
+ # @param data [Hash] Data for segment namely :segment_id and :master_project is accepted. Master_project can be given as either a PID or a Project instance
70
+ # @param options [Hash] Trigger of schedule. Can be cron string or reference to another schedule.
71
+ # @return [GoodData::Segment] New Segment instance
72
+ def create(data = {}, options = {})
73
+ segment_id = data[:segment_id]
74
+ fail 'Custom ID has to be provided' if segment_id.blank?
75
+ client = options[:client]
76
+ segment = client.create(GoodData::Segment, GoodData::Helpers.deep_stringify_keys(SEGMENT_TEMPLATE).merge('domain' => options[:domain]))
77
+ segment.tap do |s|
78
+ s.segment_id = segment_id
79
+ s.master_project = data[:master_project]
80
+ end
81
+ end
82
+ end
83
+
84
+ def initialize(data)
85
+ super
86
+ @domain = data.delete('domain')
87
+ @json = data
88
+ end
89
+
90
+ # Segment id getter for the Segment. Called segment_id since id is a reserved word in ruby world
91
+ #
92
+ # @return [String] Segment id
93
+ def segment_id
94
+ data['id']
95
+ end
96
+
97
+ # Segment id setter for the Segment. Called segment_id since id is a reserved word in ruby world
98
+ #
99
+ # @param an_id [String] Id of the segment.
100
+ # @return [String] Segment id
101
+ def segment_id=(an_id)
102
+ data['id'] = an_id
103
+ self
104
+ end
105
+
106
+ # Master project id getter for the Segment.
107
+ #
108
+ # @return [String] Segment id
109
+ def master_project=(a_project)
110
+ data['masterProject'] = a_project.respond_to?(:uri) ? a_project.uri : a_project
111
+ self
112
+ end
113
+
114
+ alias_method :master=, :master_project=
115
+
116
+ # Master project id getter for the Segment.
117
+ #
118
+ # @return [String] Project uri
119
+ def master_project_uri
120
+ data['masterProject']
121
+ end
122
+
123
+ alias_method :master_uri, :master_project_uri
124
+
125
+ # Master project getter for the Segment. It returns the instance not just the URI
126
+ #
127
+ # @return [GoodData::Project] Project associated with the segment
128
+ def master_project
129
+ client.projects(master_project_uri)
130
+ end
131
+
132
+ alias_method :master, :master_project
133
+
134
+ def create_client(data)
135
+ client = GoodData::Client.create(data, segment: self)
136
+ client.save
137
+ end
138
+
139
+ # Returns all the clients associated with the segment. Since this is potentially paging operation it returns an Enumerable.
140
+ #
141
+ # @return [Enumerable] Clients associated with the segment
142
+ def clients(tenant_id = :all)
143
+ GoodData::Client[tenant_id, domain: domain, segment: self]
144
+ end
145
+
146
+ # Creates or updates a segment instance on the API.
147
+ #
148
+ # @return [GoodData::Segment] Segment instance
149
+ def save
150
+ if uri
151
+ client.put(uri, json)
152
+ else
153
+ res = client.post(domain.segments_uri + '/segments', json)
154
+ @json = res
155
+ end
156
+ self
157
+ end
158
+
159
+ # Runs async process that walks thorugh segments and provisions projects if necessary.
160
+ #
161
+ # @return [Array] Returns array of results
162
+ def synchronize_clients
163
+ sync_uri = SYNCHRONIZE_URI % [domain.obj_id, id]
164
+ res = client.post sync_uri, nil
165
+
166
+ # wait until the instance is created
167
+ res = client.poll_on_response(res['asyncTask']['links']['poll'], :sleep_interval => 1) do |r|
168
+ r['synchronizationResult'].nil?
169
+ end
170
+
171
+ client.create(ClientSynchronizationResult, res)
172
+ end
173
+
174
+ # Deletes a segment instance on the API.
175
+ #
176
+ # @return [GoodData::Segment] Segment instance
177
+ def delete(options = {})
178
+ force = options[:force] == true ? true : false
179
+ clients.peach(&:delete) if force
180
+ client.delete(uri) if uri
181
+ self
182
+ rescue RestClient::BadRequest => e
183
+ payload = GoodData::Helpers.parse_http_exception(e)
184
+ case GoodData::Helpers.get_path(payload)
185
+ when 'gdc.c4.conflict.domain.segment.contains_clients'
186
+ throw SegmentNotEmpty
187
+ else
188
+ raise e
189
+ end
190
+ end
191
+ end
192
+ end
@@ -21,11 +21,11 @@ module GoodData
21
21
  c = client(options)
22
22
  project = options[:project]
23
23
  filters = query('userFilter', nil, options)
24
- count = 10_000
24
+ count = 1_000
25
25
  offset = 0
26
26
  user_lookup = {}
27
27
  loop do
28
- result = c.get("/gdc/md/#{project.pid}/userfilters?count=1000&offset=#{offset}")
28
+ result = c.get("/gdc/md/#{project.pid}/userfilters?count=#{count}&offset=#{offset}")
29
29
  result['userFilters']['items'].each do |item|
30
30
  item['userFilters'].each do |f|
31
31
  user_lookup[f] = item['user']
@@ -186,7 +186,7 @@ module GoodData
186
186
  # so it precaches the values and still be able to function for larger ones even
187
187
  # though that would mean tons of requests
188
188
  def self.get_small_labels(labels_cache)
189
- labels_cache.values.select { |label| label.values_count < 100_000 }
189
+ labels_cache.values.select { |label| label && label.values_count && label.values_count < 100_000 }
190
190
  end
191
191
 
192
192
  # Creates a MAQL expression(s) based on the filter defintion.
@@ -16,5 +16,16 @@ module GoodData
16
16
  @json[:uri] = res['uri']
17
17
  self
18
18
  end
19
+
20
+ # 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.
21
+ #
22
+ # @param [Array<Array>]Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping.
23
+ # @return [GoodData::VariableUserFilter]
24
+ def replace(mapping)
25
+ x = GoodData::MdObject.replace_quoted(self, mapping)
26
+ x = GoodData::MdObject.replace_bracketed(x, mapping)
27
+ vals = GoodData::MdObject.find_replaceable_values(x, mapping)
28
+ GoodData::MdObject.replace_bracketed(x, vals)
29
+ end
19
30
  end
20
31
  end
@@ -0,0 +1,241 @@
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
+ require_relative '../rest/rest'
8
+ require_relative '../rest/resource'
9
+ require_relative '../mixins/author'
10
+ require_relative '../mixins/contributor'
11
+ require_relative '../mixins/links'
12
+ require_relative '../mixins/rest_resource'
13
+ require_relative '../mixins/uri_getter'
14
+
15
+ module GoodData
16
+ # Representation of User Group
17
+ #
18
+ # Use user groups to manage user access to dashboards on the GoodData Portal.
19
+ # Create groups to more quickly manage permissions for users with
20
+ # the the same role or who need similar access to dashboards.
21
+ # Groups can be part of groups.
22
+ class UserGroup < Rest::Resource
23
+ include Mixin::Author
24
+ include Mixin::Contributor
25
+ include Mixin::Links
26
+ include Mixin::UriGetter
27
+
28
+ EMPTY_OBJECT = {
29
+ 'userGroup' => {
30
+ 'content' => {
31
+ 'name' => nil,
32
+ 'description' => nil,
33
+ 'project' => nil
34
+ }
35
+ }
36
+ }
37
+
38
+ class << self
39
+ # Returns list of all segments or a particular segment
40
+ #
41
+ # @param id [String|Symbol] Uri of the segment required or :all for all segments.
42
+ # @return [Array<GoodData::Segment>] List of segments for a particular domain
43
+ def [](id, opts = {})
44
+ # TODO: Replace with GoodData.get_client_and_project(opts)
45
+ project = opts[:project]
46
+ fail 'Project has to be passed in options' unless project
47
+ fail 'Project has to be of type GoodData::Project' unless project.is_a?(GoodData::Project)
48
+ client = project.client
49
+
50
+ results = client.get('/gdc/userGroups', params: { :project => project.pid, :user => opts[:user] }.compact)
51
+ groups = GoodData::Helpers.get_path(results, %w(userGroups items)).map { |i| client.create(GoodData::UserGroup, i, :project => project) }
52
+ id == :all ? groups : groups.find { |g| g.obj_id == id || g.name == id }
53
+ end
54
+
55
+ # Create new user group
56
+ #
57
+ # @param data [Hash] Initial data
58
+ # @return [UserGroup] Newly created user group
59
+ def create(data)
60
+ new_data = GoodData::Helpers.deep_dup(EMPTY_OBJECT).tap do |d|
61
+ d['userGroup']['content']['name'] = data[:name]
62
+ d['userGroup']['content']['description'] = data[:description]
63
+ d['userGroup']['content']['project'] = data[:project].respond_to?(:uri) ? data[:project].uri : data[:project]
64
+ end
65
+
66
+ client.create(GoodData::UserGroup, GoodData::Helpers.deep_stringify_keys(new_data))
67
+ end
68
+
69
+ # Constructs payload for user management/manipulation
70
+ #
71
+ # @return [Hash] Created payload
72
+ def construct_payload(users, operation)
73
+ users = users.is_a?(Array) ? users : [users]
74
+
75
+ {
76
+ modifyMembers: {
77
+ operation: operation,
78
+ items: users.map do |user|
79
+ uri = user.respond_to?(:uri) ? user.uri : user
80
+ fail 'You cannot add group as member of another group as of now.' if uri =~ %r{^\/gdc\/userGroups\/}
81
+ uri
82
+ end
83
+ }
84
+ }
85
+ end
86
+
87
+ # URI used for membership manipulation/managementv
88
+ #
89
+ # @param client [Client] Client used for communication with platform
90
+ # @param users [User | String | Array<User> | Array<String>] User(s) to be modified
91
+ # @param operation [String] Operation to be performed - ADD, SET, REMOVE
92
+ # @param uri [String] URI to be used for operation
93
+ # @return [String] URI used for membership manipulation/management
94
+ def modify_users(client, users, operation, uri)
95
+ payload = construct_payload(users, operation)
96
+ client.post(uri, payload)
97
+ end
98
+ end
99
+
100
+ # Initialize object with json
101
+ #
102
+ # @return [UserGroup] User Group object initialized with json
103
+ def initialize(json)
104
+ @json = json
105
+ self
106
+ end
107
+
108
+ # Add member(s) to user group
109
+ #
110
+ # @param [String | User | Array<User>] Users to add to user group
111
+ # @return [nil] Nothing is returned
112
+ def add_members(user)
113
+ UserGroup.modify_users(client, user, 'ADD', uri_modify_members)
114
+ end
115
+
116
+ alias_method :add_member, :add_members
117
+
118
+ # Gets user group name
119
+ #
120
+ # @return [String] User group name
121
+ def name
122
+ content['name']
123
+ end
124
+
125
+ # Sets user group name
126
+ #
127
+ # @param name [String] New user group name
128
+ # @return [String] New user group name
129
+ def name=(name)
130
+ content['name'] = name
131
+ name
132
+ end
133
+
134
+ # Gets user group description
135
+ #
136
+ # @return [String] User group description
137
+ def description
138
+ content['description']
139
+ end
140
+
141
+ # Sets user group description
142
+ #
143
+ # @param name [String] New user group description
144
+ # @return [String] New user group description
145
+ def description=(name)
146
+ content['description'] = name
147
+ end
148
+
149
+ # Gets Users with this Role
150
+ #
151
+ # @return [Array<GoodData::Profile>] List of users
152
+ def members
153
+ url = GoodData::Helpers.get_path(data, %w(links members))
154
+ return [] unless url
155
+ Enumerator.new do |y|
156
+ loop do
157
+ res = client.get url
158
+ res['userGroupMembers']['paging']['next']
159
+ res['userGroupMembers']['items'].each do |member|
160
+ case member.keys.first
161
+ when 'user'
162
+ y << client.create(GoodData::Profile, client.get(GoodData::Helpers.get_path(member, %w(user links self))), :project => project)
163
+ when 'userGroup'
164
+ y << client.create(UserGroup, client.get(GoodData::Helpers.get_path(member, %w(userGroup links self))), :project => project)
165
+ end
166
+ end
167
+ url = res['userGroupMembers']['paging']['next']
168
+ break unless url
169
+ end
170
+ end
171
+ end
172
+
173
+ # Verifies if user is in a group or any nested group and returns true if it does
174
+ #
175
+ # @return [Boolean] Retruns true if member is member of the group or any of its members
176
+ def member?(a_member)
177
+ # could be better on API directly?
178
+ uri = a_member.respond_to?(:uri) ? a_member.uri : a_member
179
+ members.map(&:uri).include?(uri)
180
+ end
181
+
182
+ # Save user group
183
+ # New group is created if needed else existing one is updated
184
+ #
185
+ # @return [UserGroup] Created or updated user group
186
+ def save
187
+ res = if uri
188
+ # get rid of unsupprted keys
189
+ data = json['userGroup']
190
+ client.put(uri, 'userGroup' => data.except('meta', 'links'))
191
+ else
192
+ client.post('/gdc/userGroups', @json)
193
+ end
194
+ @json = client.get(res['uri'])
195
+ self
196
+ end
197
+
198
+ # Remove member(s) from user group
199
+ #
200
+ # @param [String | User | Array<User>] Users to remove from user group
201
+ # @return [nil] Nothing is returned
202
+ def remove_members(user)
203
+ UserGroup.modify_users(client, user, 'REMOVE', uri_modify_members)
204
+ end
205
+
206
+ alias_method :remove_member, :remove_members
207
+
208
+ # Set member(s) to user group.
209
+ # Only users passed to this call will be new members of user group.
210
+ # Old members not passed to this method will be removed!
211
+ #
212
+ # @param [String | User | Array<User>] Users to set as members of user group
213
+ # @return [nil] Nothing is returned
214
+ def set_members(user) # rubocop:disable Style/AccessorMethodName
215
+ UserGroup.modify_users(client, user, 'SET', uri_modify_members)
216
+ end
217
+
218
+ alias_method :set_member, :set_members
219
+
220
+ # URI used for membership manipulation/management
221
+ #
222
+ # @return [String] URI used for membership manipulation/management
223
+ def uri_modify_members
224
+ links['modifyMembers']
225
+ end
226
+
227
+ # Is it a user group?
228
+ #
229
+ # @return [Boolean] Return true if it is a user group
230
+ def user_group?
231
+ true
232
+ end
233
+
234
+ # Checks if two user groups are same
235
+ #
236
+ # @return [Boolean] Return true if the two groups are same
237
+ def ==(other)
238
+ uri == other.uri
239
+ end
240
+ end
241
+ end