gooddata 2.1.11-java → 2.1.17-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gdc-ii-config.yaml +1 -1
  3. data/.sonar.settings +4 -0
  4. data/.travis.yml +4 -3
  5. data/CHANGELOG.md +34 -0
  6. data/Dockerfile +9 -3
  7. data/LICENSE +4418 -17
  8. data/LICENSE.rb +1 -1
  9. data/README.md +19 -1
  10. data/Rakefile +8 -1
  11. data/SDK_VERSION +1 -1
  12. data/VERSION +1 -1
  13. data/bin/run_brick.rb +3 -0
  14. data/bin/test_projects_cleanup.rb +7 -3
  15. data/ci/postgresql/pom.xml +57 -0
  16. data/dev-gooddata-sso.pub.encrypted +40 -40
  17. data/gdc_fossa_lcm.yaml +2 -0
  18. data/gdc_fossa_ruby_sdk.yaml +5 -0
  19. data/gooddata.gemspec +4 -5
  20. data/k8s/charts/lcm-bricks/Chart.yaml +1 -1
  21. data/k8s/charts/lcm-bricks/templates/prometheus/alertingRules.yaml +32 -12
  22. data/lib/gooddata.rb +2 -0
  23. data/lib/gooddata/cloud_resources/{cloud_resouce_factory.rb → cloud_resource_factory.rb} +8 -0
  24. data/lib/gooddata/cloud_resources/cloud_resources.rb +1 -1
  25. data/lib/gooddata/cloud_resources/postgresql/drivers/.gitkeepme +0 -0
  26. data/lib/gooddata/cloud_resources/postgresql/postgresql_client.rb +107 -0
  27. data/lib/gooddata/commands/scaffold.rb +9 -10
  28. data/lib/gooddata/core/nil_logger.rb +3 -1
  29. data/lib/gooddata/helpers/data_helper.rb +1 -2
  30. data/lib/gooddata/helpers/global_helpers.rb +6 -5
  31. data/lib/gooddata/helpers/global_helpers_params.rb +2 -2
  32. data/lib/gooddata/lcm/actions/base_action.rb +0 -2
  33. data/lib/gooddata/lcm/actions/collect_tagged_objects.rb +2 -1
  34. data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +116 -0
  35. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +10 -1
  36. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +4 -0
  37. data/lib/gooddata/lcm/actions/synchronize_users.rb +7 -6
  38. data/lib/gooddata/lcm/lcm2.rb +1 -2
  39. data/lib/gooddata/lcm/types/base_type.rb +0 -2
  40. data/lib/gooddata/mixins/md_object_query.rb +8 -6
  41. data/lib/gooddata/models/blueprint/project_blueprint.rb +0 -2
  42. data/lib/gooddata/models/client.rb +14 -12
  43. data/lib/gooddata/models/data_source.rb +664 -0
  44. data/lib/gooddata/models/domain.rb +1 -2
  45. data/lib/gooddata/models/from_wire.rb +1 -0
  46. data/lib/gooddata/models/metadata/scheduled_mail.rb +1 -1
  47. data/lib/gooddata/models/process.rb +11 -3
  48. data/lib/gooddata/models/project.rb +128 -19
  49. data/lib/gooddata/models/user_filters/user_filter_builder.rb +0 -1
  50. data/lib/gooddata/models/user_group.rb +0 -1
  51. data/lib/gooddata/rest/connection.rb +6 -4
  52. data/rubydev_public.gpg.encrypted +51 -51
  53. data/rubydev_secret_keys.gpg.encrypted +109 -109
  54. metadata +16 -23
  55. data/DEPENDENCIES.md +0 -880
@@ -4,7 +4,7 @@
4
4
  # This source code is licensed under the BSD-style license found in the
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
- require 'erubis'
7
+ require 'erb'
8
8
  require 'fileutils'
9
9
  require 'pathname'
10
10
 
@@ -18,15 +18,14 @@ module GoodData
18
18
  # TODO: Add option for custom output dir
19
19
  def project(name)
20
20
  fail ArgumentError, 'No name specified' if name.nil?
21
-
22
21
  FileUtils.mkdir(name)
23
22
  FileUtils.cd(name) do
24
23
  FileUtils.mkdir('model')
25
24
  FileUtils.cd('model') do
26
25
  input = File.read(TEMPLATES_PATH + 'project/model/model.rb.erb')
27
- eruby = Erubis::Eruby.new(input)
26
+ erb = ERB.new(input)
28
27
  File.open('model.rb', 'w') do |f|
29
- f.write(eruby.result(:name => name))
28
+ f.write(erb.result_with_hash(:name => name))
30
29
  end
31
30
  end
32
31
 
@@ -36,9 +35,9 @@ module GoodData
36
35
  end
37
36
 
38
37
  input = File.read(TEMPLATES_PATH + 'project/Goodfile.erb')
39
- eruby = Erubis::Eruby.new(input)
38
+ erb = ERB.new(input)
40
39
  File.open('Goodfile', 'w') do |f|
41
- f.write(eruby.result)
40
+ f.write(erb.result)
42
41
  end
43
42
  end
44
43
  end
@@ -51,15 +50,15 @@ module GoodData
51
50
  FileUtils.mkdir(name)
52
51
  FileUtils.cd(name) do
53
52
  input = File.read(TEMPLATES_PATH + 'bricks/brick.rb.erb')
54
- eruby = Erubis::Eruby.new(input)
53
+ erb = ERB.new(input)
55
54
  File.open('brick.rb', 'w') do |f|
56
- f.write(eruby.result)
55
+ f.write(erb.result)
57
56
  end
58
57
 
59
58
  input = File.read(TEMPLATES_PATH + 'bricks/main.rb.erb')
60
- eruby = Erubis::Eruby.new(input)
59
+ erb = ERB.new(input)
61
60
  File.open('main.rb', 'w') do |f|
62
- f.write(eruby.result)
61
+ f.write(erb.result)
63
62
  end
64
63
  end
65
64
  end
@@ -1,4 +1,5 @@
1
- # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
1
+ # frozen_string_literal: true
2
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
2
3
  # This source code is licensed under the BSD-style license found in the
3
4
  # LICENSE file in the root directory of this source tree.
4
5
 
@@ -11,6 +12,7 @@ module GoodData
11
12
  @level = nil
12
13
  end
13
14
 
15
+ # No body define need for dummy logger
14
16
  def debug(*_args)
15
17
  end
16
18
 
@@ -44,9 +44,8 @@ module GoodData
44
44
  realize_link
45
45
  when 's3'
46
46
  realize_s3(params)
47
- when 'redshift', 'snowflake', 'bigquery'
47
+ when 'redshift', 'snowflake', 'bigquery', 'postgresql'
48
48
  raise GoodData::InvalidEnvError, "DataSource does not support type \"#{source}\" on the platform #{RUBY_PLATFORM}" unless RUBY_PLATFORM =~ /java/
49
-
50
49
  require_relative '../cloud_resources/cloud_resources'
51
50
  realize_cloud_resource(source, params)
52
51
  else
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
  #
3
- # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
3
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
4
4
  # This source code is licensed under the BSD-style license found in the
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
@@ -34,6 +34,7 @@ module GoodData
34
34
  end
35
35
 
36
36
  set_const :GD_MAX_RETRY, (ENV['GD_MAX_RETRY'] && ENV['GD_MAX_RETRY'].to_i) || 12
37
+ AES_256_CBC_CIPHER = 'aes-256-cbc'
37
38
 
38
39
  class << self
39
40
  def error(msg)
@@ -222,7 +223,7 @@ module GoodData
222
223
  # encrypts data with the given key. returns a binary data with the
223
224
  # unhashed random iv in the first 16 bytes
224
225
  def encrypt(data, key)
225
- cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
226
+ cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
226
227
  cipher.encrypt
227
228
  cipher.key = key = Digest::SHA256.digest(key)
228
229
  random_iv = cipher.random_iv
@@ -236,7 +237,7 @@ module GoodData
236
237
 
237
238
  # Simple encrypt data with given key
238
239
  def simple_encrypt(data, key)
239
- cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
240
+ cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
240
241
  cipher.encrypt
241
242
  cipher.key = key
242
243
  encrypted = cipher.update(data)
@@ -253,7 +254,7 @@ module GoodData
253
254
 
254
255
  data = Base64.decode64(database64)
255
256
 
256
- cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
257
+ cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
257
258
  cipher.decrypt
258
259
  cipher.key = cipher_key = Digest::SHA256.digest(key)
259
260
  random_iv = data[0..15] # extract iv from first 16 bytes
@@ -273,7 +274,7 @@ module GoodData
273
274
 
274
275
  data = Base64.decode64(database64)
275
276
 
276
- cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
277
+ cipher = OpenSSL::Cipher::Cipher.new(AES_256_CBC_CIPHER)
277
278
  cipher.decrypt
278
279
  cipher.key = key
279
280
  decrypted = cipher.update(data)
@@ -242,7 +242,7 @@ module GoodData
242
242
 
243
243
  def resolve_reference_params(data_params, params)
244
244
  reference_values = []
245
- regexps = Regexp.union(/\\\\/, /\\\$/, /\$\{(\w+)\}/)
245
+ regexps = Regexp.union(/\\\\/, /\\\$/, /\$\{([^\n\{\}]+)\}/)
246
246
  resolve_reference = lambda do |v|
247
247
  if v.is_a? Hash
248
248
  Hash[
@@ -262,7 +262,7 @@ module GoodData
262
262
  data_params.is_a?(Hash) ? '\\' : '\\\\' # rubocop: disable Metrics/BlockNesting
263
263
  elsif match =~ /\\\$/
264
264
  '$'
265
- elsif match =~ /\$\{(\w+)\}/
265
+ elsif match =~ /\$\{([^\n\{\}]+)\}/
266
266
  val = params["#{$1}"]
267
267
  if val
268
268
  reference_values << val
@@ -8,8 +8,6 @@ require 'gooddata/extensions/integer'
8
8
  require 'gooddata/extensions/string'
9
9
  require 'gooddata/extensions/nil'
10
10
 
11
- require 'active_support/core_ext/hash/compact'
12
-
13
11
  require_relative '../dsl/dsl'
14
12
  require_relative '../helpers/helpers'
15
13
  require_relative '../types/types'
@@ -50,7 +50,8 @@ module GoodData
50
50
  if transfer_all
51
51
  vizs = MdObject.query('visualizationObject', MdObject, client: development_client, project: from_project)
52
52
  viz_widgets = MdObject.query('visualizationWidget', MdObject, client: development_client, project: from_project)
53
- objects = (from_project.reports.to_a + from_project.metrics.to_a + from_project.variables.to_a + vizs.to_a + viz_widgets.to_a).map(&:uri)
53
+ theme = MdObject.query('theme', MdObject, client: development_client, project: from_project)
54
+ objects = (from_project.reports.to_a + from_project.metrics.to_a + from_project.variables.to_a + vizs.to_a + viz_widgets.to_a + theme.to_a).map(&:uri)
54
55
  elsif production_tags.any?
55
56
  objects = from_project.find_by_tag(production_tags)
56
57
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+ # (C) 2019-2020 GoodData Corporation
3
+ require_relative 'base_action'
4
+
5
+ # Migrate date dimension urn:gooddata:date or urn:custom:date to urn:custom_v2:date
6
+ module GoodData
7
+ module LCM2
8
+ class MigrateGdcDateDimension < BaseAction
9
+ DESCRIPTION = 'Migrate Gdc Date Dimension'
10
+ DATE_DIMENSION_CUSTOM_V2 = 'urn:custom_v2:date'
11
+ DATE_DIMENSION_OLD = %w[urn:gooddata:date urn:custom:date]
12
+
13
+ PARAMS = define_params(self) do
14
+ description 'Client Used for Connecting to GD'
15
+ param :gdc_gd_client, instance_of(Type::GdClientType), required: true
16
+
17
+ description 'Specifies how to synchronize LDM and resolve possible conflicts'
18
+ param :synchronize_ldm, instance_of(Type::SynchronizeLDM), required: false, default: 'diff_against_master_with_fallback'
19
+
20
+ description 'Synchronization Info'
21
+ param :synchronize, array_of(instance_of(Type::SynchronizationInfoType)), required: true, generated: true
22
+ end
23
+
24
+ RESULT_HEADER = %i[from to status]
25
+
26
+ class << self
27
+ def call(params)
28
+ results = []
29
+ params.synchronize.map do |segment_info|
30
+ result = migrate_date_dimension(params, segment_info)
31
+ results.concat(result)
32
+ end
33
+
34
+ {
35
+ results: results
36
+ }
37
+ end
38
+
39
+ def migrate_date_dimension(params, segment_info)
40
+ results = []
41
+ client = params.gdc_gd_client
42
+ latest_blueprint = segment_info[:from_blueprint]
43
+ # don't migrate when latest master doesn't contain custom v2 date.
44
+ return results unless contain_v2?(latest_blueprint)
45
+
46
+ previous_blueprint = segment_info[:previous_master]&.blueprint
47
+ # check latest master and previous master
48
+ master_upgrade_datasets = get_upgrade_dates(latest_blueprint, previous_blueprint) if params[:synchronize_ldm].downcase == 'diff_against_master' && previous_blueprint
49
+ unless master_upgrade_datasets&.empty?
50
+ segment_info[:to].pmap do |entry|
51
+ pid = entry[:pid]
52
+ to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
53
+ to_blueprint = to_project.blueprint
54
+ upgrade_datasets = get_upgrade_dates(latest_blueprint, to_blueprint)
55
+ next if upgrade_datasets.empty?
56
+
57
+ message = get_upgrade_message(upgrade_datasets)
58
+
59
+ results << {
60
+ from: segment_info[:from],
61
+ to: pid,
62
+ status: to_project.upgrade_custom_v2(message)
63
+ }
64
+ end
65
+ end
66
+
67
+ results
68
+ end
69
+
70
+ def get_upgrade_dates(src_blueprint, dest_blueprint)
71
+ dest_dates = get_date_dimensions(dest_blueprint) if dest_blueprint
72
+ src_dates = get_date_dimensions(src_blueprint) if src_blueprint
73
+
74
+ return false if dest_dates.empty? || src_dates.empty?
75
+
76
+ upgrade_datasets = []
77
+ dest_dates.each do |dest|
78
+ src_dim = get_date_dimension(src_blueprint, dest[:id])
79
+ next unless src_dim
80
+
81
+ upgrade_datasets << src_dim[:identifier] if upgrade?(src_dim, dest) && src_dim[:identifier]
82
+ end
83
+
84
+ upgrade_datasets
85
+ end
86
+
87
+ def get_upgrade_message(upgrade_datasets)
88
+ {
89
+ upgrade: {
90
+ dateDatasets: {
91
+ upgrade: "exact",
92
+ datasets: upgrade_datasets
93
+ }
94
+ }
95
+ }
96
+ end
97
+
98
+ def upgrade?(src_dim, dest_dim)
99
+ src_dim[:urn] == DATE_DIMENSION_CUSTOM_V2 && DATE_DIMENSION_OLD.any? { |e| dest_dim[:urn] == e }
100
+ end
101
+
102
+ def contain_v2?(blueprint)
103
+ get_date_dimensions(blueprint).any? { |e| e[:urn] == DATE_DIMENSION_CUSTOM_V2 }
104
+ end
105
+
106
+ def get_date_dimension(blueprint, id)
107
+ GoodData::Model::ProjectBlueprint.find_date_dimension(blueprint, id)
108
+ end
109
+
110
+ def get_date_dimensions(blueprint)
111
+ GoodData::Model::ProjectBlueprint.date_dimensions(blueprint)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -49,6 +49,8 @@ module GoodData
49
49
  param :include_deprecated, instance_of(Type::BooleanType), required: false, default: false
50
50
  end
51
51
 
52
+ RESULT_HEADER = %i[from to status]
53
+
52
54
  class << self
53
55
  def call(params)
54
56
  results = []
@@ -76,9 +78,9 @@ module GoodData
76
78
  include_deprecated = params.include_deprecated.to_b
77
79
  from_pid = segment_info[:from]
78
80
  from = params.development_client.projects(from_pid) || fail("Invalid 'from' project specified - '#{from_pid}'")
79
-
80
81
  GoodData.logger.info "Creating Blueprint, project: '#{from.title}', PID: #{from_pid}"
81
82
  blueprint = from.blueprint(include_ca: params.include_computed_attributes.to_b)
83
+ segment_info[:from_blueprint] = blueprint
82
84
  maql_diff = nil
83
85
  previous_master = segment_info[:previous_master]
84
86
  diff_against_master = %w(diff_against_master_with_fallback diff_against_master)
@@ -89,6 +91,13 @@ module GoodData
89
91
  maql_diff_params << :excludeFactRule if exclude_fact_rule
90
92
  maql_diff_params << :includeDeprecated if include_deprecated
91
93
  maql_diff = previous_master.maql_diff(blueprint: blueprint, params: maql_diff_params)
94
+ chunks = maql_diff['projectModelDiff']['updateScripts']
95
+ if chunks.empty?
96
+ GoodData.logger.info "Synchronize LDM to clients will not proceed in mode \
97
+ '#{params[:synchronize_ldm].downcase}' due to no LDM changes in the new master project. \
98
+ If you had changed LDM of clients manually, please use mode 'diff_against_clients' \
99
+ to force synchronize LDM to clients"
100
+ end
92
101
  end
93
102
 
94
103
  segment_info[:to] = segment_info[:to].pmap do |entry|
@@ -167,6 +167,10 @@ module GoodData
167
167
  fail "Client id cannot be empty" if client_id.blank?
168
168
 
169
169
  c = all_clients.detect { |specific_client| specific_client.id == client_id }
170
+ if c.nil?
171
+ params.gdc_logger.warn "Client #{client_id} is not found"
172
+ next
173
+ end
170
174
  if params.segments && !segment_uris.include?(c.segment_uri)
171
175
  params.gdc_logger.warn "Client #{client_id} is outside segments_filter #{params.segments}"
172
176
  next
@@ -182,11 +182,11 @@ module GoodData
182
182
  # value of a project id in the data since he does not know it upfront
183
183
  # and we cannot influence its value.
184
184
  common_params = {
185
- domain: domain,
186
- whitelists: whitelists,
187
- ignore_failures: ignore_failures,
188
- remove_users_from_project: remove_users_from_project,
189
- do_not_touch_users_that_are_not_mentioned: do_not_touch_users_that_are_not_mentioned,
185
+ domain: domain,
186
+ whitelists: whitelists,
187
+ ignore_failures: ignore_failures,
188
+ remove_users_from_project: remove_users_from_project,
189
+ do_not_touch_users_that_are_not_mentioned: do_not_touch_users_that_are_not_mentioned,
190
190
  create_non_existing_user_groups: create_non_existing_user_groups,
191
191
  user_groups_cache: nil
192
192
  }
@@ -198,7 +198,8 @@ module GoodData
198
198
  domain.create_users(new_users.uniq { |u| u[:login] || u[:email] })
199
199
  when 'remove_from_organization'
200
200
  user_ids = new_users.uniq { |u| u[:login] || u[:email] }.map { |u| u[:login] || u[:email] }
201
- users = user_ids.map { |u| domain.users(u, client: client) }
201
+ users = user_ids.map { |u| domain.users(u, client: client) }.reject(&:nil?)
202
+ params.gdc_logger.info "#{user_ids.count - users.count} users were not found (or were deleted) in domain #{domain_name}" if user_ids.count > users.count
202
203
  params.gdc_logger.warn "Deleting #{users.count} users from domain #{domain_name}"
203
204
 
204
205
  GoodData.gd_logger.info("Synchronizing in mode=#{mode}, domain=#{domain_name}, data_rows=#{users.count}")
@@ -13,8 +13,6 @@ require 'gooddata/extensions/integer'
13
13
  require 'gooddata/extensions/string'
14
14
  require 'gooddata/extensions/nil'
15
15
 
16
- require 'active_support/core_ext/hash/compact'
17
-
18
16
  require_relative 'actions/actions'
19
17
  require_relative 'dsl/dsl'
20
18
  require_relative 'helpers/helpers'
@@ -138,6 +136,7 @@ module GoodData
138
136
  EnsureTechnicalUsersDomain,
139
137
  EnsureTechnicalUsersProject,
140
138
  SynchronizeLdm,
139
+ MigrateGdcDateDimension,
141
140
  SynchronizeClients,
142
141
  SynchronizeComputedAttributes,
143
142
  CollectDymanicScheduleParams,
@@ -8,8 +8,6 @@
8
8
  require_relative '../dsl/dsl'
9
9
  require_relative '../helpers/helpers'
10
10
 
11
- require 'active_support/core_ext/hash/compact'
12
-
13
11
  require 'gooddata/extensions/class'
14
12
 
15
13
  module GoodData
@@ -1,12 +1,14 @@
1
1
  # encoding: UTF-8
2
2
  #
3
- # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
3
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
4
4
  # This source code is licensed under the BSD-style license found in the
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  module GoodData
8
8
  module Mixin
9
9
  module MdObjectQuery
10
+ ERROR_MESSAGE_NO_PROJECT = 'No :project specified'
11
+
10
12
  # Method intended to get all objects of that type in a specified project
11
13
  #
12
14
  # @param options [Hash] the options hash
@@ -38,7 +40,7 @@ module GoodData
38
40
  fail ArgumentError, 'No :client specified' if client.nil?
39
41
 
40
42
  p = options[:project]
41
- fail ArgumentError, 'No :project specified' if p.nil?
43
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if p.nil?
42
44
 
43
45
  project = GoodData::Project[p, options]
44
46
  fail ArgumentError, 'Wrong :project specified' if project.nil?
@@ -98,10 +100,10 @@ module GoodData
98
100
  # Returns which objects uses this MD resource
99
101
  def usedby(uri, key = nil, opts = { :client => GoodData.connection, :project => GoodData.project })
100
102
  p = opts[:project]
101
- fail ArgumentError, 'No :project specified' if p.nil?
103
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if p.nil?
102
104
 
103
105
  project = GoodData::Project[p, opts]
104
- fail ArgumentError, 'No :project specified' if project.nil?
106
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if project.nil?
105
107
 
106
108
  dependency("#{project.md['usedby2']}/#{uri_obj_id(uri)}", key, opts)
107
109
  end
@@ -111,10 +113,10 @@ module GoodData
111
113
  # Returns which objects this MD resource uses
112
114
  def using(uri, key = nil, opts = { :client => GoodData.connection, :project => GoodData.project })
113
115
  p = opts[:project]
114
- fail ArgumentError, 'No :project specified' if p.nil?
116
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if p.nil?
115
117
 
116
118
  project = GoodData::Project[p, opts]
117
- fail ArgumentError, 'No :project specified' if project.nil?
119
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if project.nil?
118
120
 
119
121
  dependency("#{project.md['using2']}/#{uri_obj_id(uri)}", key, opts)
120
122
  end
@@ -2,8 +2,6 @@
2
2
  # This source code is licensed under the BSD-style license found in the
3
3
  # LICENSE file in the root directory of this source tree.
4
4
 
5
- require 'active_support/core_ext/hash/compact'
6
-
7
5
  module GoodData
8
6
  module Model
9
7
  class ProjectBlueprint