gooddata 0.6.18 → 0.6.19

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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +8 -19
  4. data/Guardfile +5 -0
  5. data/README.md +1 -3
  6. data/bin/gooddata +1 -1
  7. data/gooddata.gemspec +6 -4
  8. data/lib/gooddata.rb +1 -1
  9. data/lib/gooddata/bricks/middleware/aws_middleware.rb +24 -0
  10. data/lib/gooddata/cli/commands/console_cmd.rb +1 -1
  11. data/lib/gooddata/cli/commands/project_cmd.rb +29 -9
  12. data/lib/gooddata/cli/hooks.rb +9 -3
  13. data/lib/gooddata/commands/datawarehouse.rb +1 -7
  14. data/lib/gooddata/commands/project.rb +4 -3
  15. data/lib/gooddata/core/logging.rb +14 -2
  16. data/lib/gooddata/exceptions/execution_limit_exceeded.rb +9 -0
  17. data/lib/gooddata/exceptions/uncomputable_report.rb +8 -0
  18. data/lib/gooddata/exceptions/validation_error.rb +1 -1
  19. data/lib/gooddata/goodzilla/goodzilla.rb +5 -1
  20. data/lib/gooddata/helpers/data_helper.rb +40 -9
  21. data/lib/gooddata/mixins/md_finders.rb +35 -0
  22. data/lib/gooddata/models/blueprint/anchor_field.rb +46 -0
  23. data/lib/gooddata/models/blueprint/attribute_field.rb +25 -0
  24. data/lib/gooddata/models/blueprint/blueprint.rb +7 -0
  25. data/lib/gooddata/models/blueprint/blueprint_field.rb +66 -0
  26. data/lib/gooddata/models/{dashboard_builder.rb → blueprint/dashboard_builder.rb} +0 -0
  27. data/lib/gooddata/models/{schema_blueprint.rb → blueprint/dataset_blueprint.rb} +176 -117
  28. data/lib/gooddata/models/blueprint/date_dimension.rb +10 -0
  29. data/lib/gooddata/models/blueprint/fact_field.rb +16 -0
  30. data/lib/gooddata/models/blueprint/label_field.rb +39 -0
  31. data/lib/gooddata/models/{project_blueprint.rb → blueprint/project_blueprint.rb} +366 -168
  32. data/lib/gooddata/models/blueprint/project_builder.rb +79 -0
  33. data/lib/gooddata/models/blueprint/reference_field.rb +39 -0
  34. data/lib/gooddata/models/blueprint/schema_blueprint.rb +156 -0
  35. data/lib/gooddata/models/blueprint/schema_builder.rb +85 -0
  36. data/lib/gooddata/models/{to_manifest.rb → blueprint/to_manifest.rb} +25 -20
  37. data/lib/gooddata/models/{to_wire.rb → blueprint/to_wire.rb} +33 -52
  38. data/lib/gooddata/models/datawarehouse.rb +2 -2
  39. data/lib/gooddata/models/domain.rb +3 -2
  40. data/lib/gooddata/models/execution.rb +2 -2
  41. data/lib/gooddata/models/execution_detail.rb +7 -2
  42. data/lib/gooddata/models/from_wire.rb +60 -71
  43. data/lib/gooddata/models/from_wire_parse.rb +125 -125
  44. data/lib/gooddata/models/metadata.rb +14 -0
  45. data/lib/gooddata/models/metadata/dashboard.rb +2 -2
  46. data/lib/gooddata/models/metadata/label.rb +1 -1
  47. data/lib/gooddata/models/metadata/report.rb +6 -5
  48. data/lib/gooddata/models/metadata/report_definition.rb +44 -59
  49. data/lib/gooddata/models/model.rb +131 -43
  50. data/lib/gooddata/models/process.rb +13 -11
  51. data/lib/gooddata/models/profile.rb +12 -1
  52. data/lib/gooddata/models/project.rb +223 -19
  53. data/lib/gooddata/models/project_creator.rb +4 -15
  54. data/lib/gooddata/models/schedule.rb +1 -0
  55. data/lib/gooddata/models/user_filters/user_filter_builder.rb +2 -2
  56. data/lib/gooddata/rest/client.rb +18 -18
  57. data/lib/gooddata/rest/connection.rb +113 -94
  58. data/lib/gooddata/version.rb +1 -1
  59. data/lib/templates/project/model/model.rb.erb +15 -16
  60. data/spec/data/blueprints/additional_dataset_module.json +32 -0
  61. data/spec/data/blueprints/big_blueprint_not_pruned.json +2079 -0
  62. data/spec/data/blueprints/invalid_blueprint.json +103 -0
  63. data/spec/data/blueprints/m_n_model.json +104 -0
  64. data/spec/data/blueprints/model_module.json +25 -0
  65. data/spec/data/blueprints/test_blueprint.json +38 -0
  66. data/spec/data/blueprints/test_project_model_spec.json +106 -0
  67. data/spec/data/gd_gse_data_manifest.json +34 -34
  68. data/spec/data/manifests/test_blueprint.json +32 -0
  69. data/spec/data/{manifest_test_project.json → manifests/test_project.json} +9 -18
  70. data/spec/data/wire_models/test_blueprint.json +63 -0
  71. data/spec/data/wire_test_project.json +5 -5
  72. data/spec/environment/default.rb +33 -0
  73. data/spec/environment/develop.rb +26 -0
  74. data/spec/environment/environment.rb +14 -0
  75. data/spec/environment/hotfix.rb +17 -0
  76. data/spec/environment/production.rb +31 -0
  77. data/spec/environment/release.rb +17 -0
  78. data/spec/helpers/blueprint_helper.rb +10 -7
  79. data/spec/helpers/cli_helper.rb +24 -22
  80. data/spec/helpers/connection_helper.rb +27 -25
  81. data/spec/helpers/crypto_helper.rb +7 -5
  82. data/spec/helpers/csv_helper.rb +5 -3
  83. data/spec/helpers/process_helper.rb +15 -10
  84. data/spec/helpers/project_helper.rb +40 -33
  85. data/spec/helpers/schedule_helper.rb +15 -9
  86. data/spec/helpers/spec_helper.rb +11 -0
  87. data/spec/integration/blueprint_updates_spec.rb +93 -0
  88. data/spec/integration/command_datawarehouse_spec.rb +2 -1
  89. data/spec/integration/command_projects_spec.rb +9 -8
  90. data/spec/integration/create_from_template_spec.rb +1 -1
  91. data/spec/integration/create_project_spec.rb +1 -1
  92. data/spec/integration/full_process_schedule_spec.rb +1 -1
  93. data/spec/integration/full_project_spec.rb +91 -30
  94. data/spec/integration/over_to_user_filters_spec.rb +24 -28
  95. data/spec/integration/partial_md_export_import_spec.rb +4 -4
  96. data/spec/integration/project_spec.rb +1 -1
  97. data/spec/integration/rest_spec.rb +1 -1
  98. data/spec/integration/user_filters_spec.rb +19 -24
  99. data/spec/integration/variables_spec.rb +7 -9
  100. data/spec/logging_in_logging_out_spec.rb +1 -1
  101. data/spec/spec_helper.rb +10 -1
  102. data/spec/unit/bricks/middleware/aws_middelware_spec.rb +47 -0
  103. data/spec/unit/core/connection_spec.rb +2 -2
  104. data/spec/unit/core/logging_spec.rb +12 -4
  105. data/spec/unit/helpers/data_helper_spec.rb +60 -0
  106. data/spec/unit/models/blueprint/attributes_spec.rb +24 -0
  107. data/spec/unit/models/blueprint/dataset_spec.rb +116 -0
  108. data/spec/unit/models/blueprint/labels_spec.rb +39 -0
  109. data/spec/unit/models/blueprint/project_blueprint_spec.rb +643 -0
  110. data/spec/unit/models/blueprint/reference_spec.rb +24 -0
  111. data/spec/unit/models/{schema_builder_spec.rb → blueprint/schema_builder_spec.rb} +12 -4
  112. data/spec/unit/models/blueprint/to_wire_spec.rb +169 -0
  113. data/spec/unit/models/domain_spec.rb +13 -2
  114. data/spec/unit/models/from_wire_spec.rb +277 -98
  115. data/spec/unit/models/metadata_spec.rb +22 -4
  116. data/spec/unit/models/model_spec.rb +49 -39
  117. data/spec/unit/models/profile_spec.rb +1 -0
  118. data/spec/unit/models/project_spec.rb +7 -7
  119. data/spec/unit/models/schedule_spec.rb +20 -0
  120. data/spec/unit/models/to_manifest_spec.rb +31 -11
  121. data/spec/unit/rest/polling_spec.rb +86 -0
  122. metadata +102 -30
  123. data/lib/gooddata/models/project_builder.rb +0 -136
  124. data/lib/gooddata/models/schema_builder.rb +0 -77
  125. data/out.txt +0 -0
  126. data/spec/data/additional_dataset_module.json +0 -18
  127. data/spec/data/blueprint_invalid.json +0 -38
  128. data/spec/data/m_n_model/blueprint.json +0 -76
  129. data/spec/data/model_module.json +0 -18
  130. data/spec/data/test_project_model_spec.json +0 -76
  131. data/spec/unit/models/attribute_column_spec.rb +0 -7
  132. data/spec/unit/models/project_blueprint_spec.rb +0 -239
  133. data/spec/unit/models/to_wire_spec.rb +0 -71
@@ -0,0 +1,10 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'schema_blueprint'
4
+
5
+ module GoodData
6
+ module Model
7
+ class DateDimension < GoodData::Model::SchemaBlueprint
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'blueprint_field'
4
+
5
+ module GoodData
6
+ module Model
7
+ class FactBlueprintField < BlueprintField
8
+ # Returns gd_data_type
9
+ #
10
+ # @return [String] returns gd_data_type of the fact
11
+ def gd_data_type
12
+ data[:gd_data_type] || Model::DEFAULT_FACT_DATATYPE
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'blueprint_field'
4
+
5
+ module GoodData
6
+ module Model
7
+ class LabelBlueprintField < BlueprintField
8
+ # Returns the attribute this label is referencing to
9
+ #
10
+ # @return [AttributeBlueprintField] the object representing attribute in the blueprint
11
+ def attribute
12
+ dataset_blueprint.attribute_for_label(self)
13
+ end
14
+
15
+ # Returns gd_data_type
16
+ #
17
+ # @return [String] returns gd_data_type of the label
18
+ def gd_data_type
19
+ data[:gd_data_type] || Model::DEFAULT_ATTRIBUTE_DATATYPE
20
+ end
21
+
22
+ # Returns gd_data_type
23
+ #
24
+ # @return [String] returns gd_type of the label
25
+ def gd_type
26
+ data[:gd_type] || Model::DEFAULT_TYPE
27
+ end
28
+
29
+ # Validates the fields in the label
30
+ #
31
+ # @return [Array] returns list of the errors represented by hash structures
32
+ def validate
33
+ validate_presence_of(:id, :reference).map do |e|
34
+ { type: :error, message: "Field \"#{e}\" is not defined or empty for label \"#{id}\"" }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -29,14 +29,27 @@ module GoodData
29
29
  end
30
30
  end
31
31
 
32
+ # Removes column from from the blueprint
33
+ #
34
+ # @param project [Hash | GoodData::Model::ProjectBlueprint] Project blueprint
35
+ # @param dataset [Hash | GoodData::Model::DatasetBlueprint] Dataset blueprint
36
+ # @param column_id [String] Column id
37
+ # @return [Hash | GoodData::Model::ProjectBlueprint] Returns changed blueprint
38
+ def self.remove_column!(project, dataset, column_id)
39
+ dataset = find_dataset(project, dataset)
40
+ col = dataset[:columns].find { |c| c[:id] == column_id }
41
+ dataset[:columns].delete(col)
42
+ project
43
+ end
44
+
32
45
  # Removes dataset from blueprint. Dataset can be given as either a name
33
46
  # or a DatasetBlueprint or a Hash representation.
34
47
  #
35
- # @param project [Hash] Project blueprint
48
+ # @param project [Hash | GoodData::Model::ProjectBlueprint] Project blueprint
36
49
  # @param dataset_name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset to be removed
37
- # @return [Hash] project with removed dataset
38
- def self.remove_dataset(project, dataset_name)
39
- dataset = dataset_name.is_a?(String) ? find_dataset(project, dataset_name) : dataset_name
50
+ # @return [Hash] new project with removed dataset
51
+ def self.remove_dataset(project, dataset_id)
52
+ dataset = dataset_id.is_a?(String) ? find_dataset(project, dataset_id) : dataset_name
40
53
  index = project[:datasets].index(dataset)
41
54
  dupped_project = project.deep_dup
42
55
  dupped_project[:datasets].delete_at(index)
@@ -47,11 +60,12 @@ module GoodData
47
60
  # or a DatasetBlueprint or a Hash representation. This version mutates
48
61
  # the dataset in place
49
62
  #
50
- # @param project [Hash] Project blueprint
63
+ # @param project [Hash | GoodData::Model::ProjectBlueprint] Project blueprint
51
64
  # @param dataset_name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset to be removed
52
65
  # @return [Hash] project with removed dataset
53
- def self.remove_dataset!(project, dataset_name)
54
- dataset = dataset_name.is_a?(String) ? find_dataset(project, dataset_name) : dataset_name
66
+ def self.remove_dataset!(project, dataset_id)
67
+ project = project.to_hash
68
+ dataset = dataset_id.is_a?(String) ? find_dataset(project, dataset_id) : dataset_id
55
69
  index = project[:datasets].index(dataset)
56
70
  project[:datasets].delete_at(index)
57
71
  project
@@ -63,11 +77,12 @@ module GoodData
63
77
  # @param project [GoodData::Model::ProjectBlueprint | Hash] Project blueprint
64
78
  # @param options [Hash] options
65
79
  # @return [Array<Hash>]
66
- def self.datasets(project, options = {})
80
+ def self.datasets(project_blueprint, options = {})
81
+ project_blueprint = project_blueprint.to_hash
67
82
  include_date_dimensions = options[:include_date_dimensions] || options[:dd]
68
- ds = (project.to_hash[:datasets] || [])
83
+ ds = (project_blueprint.to_hash[:datasets] || [])
69
84
  if include_date_dimensions
70
- ds + date_dimensions(project)
85
+ ds + date_dimensions(project_blueprint)
71
86
  else
72
87
  ds
73
88
  end
@@ -78,8 +93,8 @@ module GoodData
78
93
  # @param project [GoodData::Model::ProjectBlueprint | Hash] Project blueprint
79
94
  # @param name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset
80
95
  # @return [Boolean]
81
- def self.dataset?(project, name)
82
- find_dataset(project, name)
96
+ def self.dataset?(project, name, options = {})
97
+ find_dataset(project, name, options)
83
98
  true
84
99
  rescue
85
100
  false
@@ -91,16 +106,11 @@ module GoodData
91
106
  # @param obj [GoodData::Model::DatasetBlueprint | String | Hash] Dataset
92
107
  # @param options [Hash] options
93
108
  # @return [GoodData::Model::DatasetBlueprint]
94
- def self.find_dataset(project, obj, options = {})
95
- include_date_dimensions = options[:include_date_dimensions] || options[:dd]
109
+ def self.find_dataset(project_blueprint, obj, options = {})
96
110
  return obj.to_hash if DatasetBlueprint.dataset_blueprint?(obj)
97
- all_datasets = if include_date_dimensions
98
- datasets(project) + date_dimensions(project)
99
- else
100
- datasets(project)
101
- end
102
- name = obj.respond_to?(:key?) ? obj[:name] : obj
103
- ds = all_datasets.find { |d| d[:name] == name }
111
+ all_datasets = datasets(project_blueprint, options)
112
+ name = obj.respond_to?(:to_hash) ? obj.to_hash[:id] : obj
113
+ ds = all_datasets.find { |d| d[:id] == name }
104
114
  fail "Dataset #{name} could not be found" if ds.nil?
105
115
  ds
106
116
  end
@@ -109,8 +119,9 @@ module GoodData
109
119
  #
110
120
  # @param project [GoodData::Model::ProjectBlueprint | Hash] Project blueprint
111
121
  # @return [Array<Hash>]
112
- def self.date_dimensions(project)
113
- project.to_hash[:date_dimensions] || []
122
+ def self.date_dimensions(project_blueprint)
123
+ project_blueprint.to_hash[:date_dimensions] || []
124
+ # dims.map {|dim| DateDimension.new(dim, project_blueprint)}
114
125
  end
115
126
 
116
127
  # Returns true if a date dimension of a given name exists in a bleuprint
@@ -132,8 +143,8 @@ module GoodData
132
143
  # @param name [string] Date dimension
133
144
  # @return [Hash]
134
145
  def self.find_date_dimension(project, name)
135
- ds = date_dimensions(project).find { |d| d[:name] == name }
136
- fail "Date dimension #{name} could not be found" if ds.nil?
146
+ ds = date_dimensions(project).find { |d| d[:id] == name }
147
+ fail "Date dimension #{name} could not be found" unless ds
137
148
  ds
138
149
  end
139
150
 
@@ -145,6 +156,10 @@ module GoodData
145
156
  datasets(project).mapcat { |d| DatasetBlueprint.fields(d) }
146
157
  end
147
158
 
159
+ # Changes the dataset through a builder. You provide a block and an istance of
160
+ # GoodData::Model::ProjectBuilder is passed in as the only parameter
161
+ #
162
+ # @return [GoodData::Model::ProjectBlueprint] returns changed project blueprint
148
163
  def change(&block)
149
164
  builder = ProjectBuilder.create_from_data(self)
150
165
  block.call(builder)
@@ -157,34 +172,132 @@ module GoodData
157
172
  #
158
173
  # @param options [Hash] options
159
174
  # @return [Array<GoodData::Model::DatasetBlueprint>]
160
- def datasets(options = {})
161
- ProjectBlueprint.datasets(to_hash, options).map { |d| DatasetBlueprint.new(d) }
175
+ def datasets(id = :all, options = {})
176
+ id = id.respond_to?(:id) ? id.id : id
177
+ dss = ProjectBlueprint.datasets(self, options).map do |d|
178
+ case d[:type]
179
+ when :date_dimension
180
+ DateDimension.new(d, self)
181
+ when :dataset
182
+ DatasetBlueprint.new(d, self)
183
+ end
184
+ end
185
+ id == :all ? dss : dss.find { |d| d.id == id }
162
186
  end
163
187
 
188
+ # Adds dataset to the blueprint
189
+ #
190
+ # @param a_dataset [Hash | GoodData::Model::SchemaBlueprint] dataset to be added
191
+ # @param index [Integer] number specifying at which position the new dataset should be added. If not specified it is added at the end
192
+ # @return [GoodData::Model::ProjectBlueprint] returns project blueprint
164
193
  def add_dataset!(a_dataset, index = nil)
165
194
  if index.nil? || index > datasets.length
166
195
  data[:datasets] << a_dataset.to_hash
167
196
  else
168
197
  data[:datasets].insert(index, a_dataset.to_hash)
169
198
  end
199
+ self
170
200
  end
171
201
 
172
- # Removes dataset from blueprint. Dataset can be given as either a name
173
- # or a DatasetBlueprint or a Hash representation.
202
+ def add_date_dimension!(a_dimension, index = nil)
203
+ dim = a_dimension.to_hash
204
+ if index.nil? || index > date_dimensions.length
205
+ data[:date_dimensions] << dim
206
+ else
207
+ data[:date_dimensions].insert(index, dim)
208
+ end
209
+ self
210
+ end
211
+
212
+ # Adds column to particular dataset in the blueprint
174
213
  #
175
- # @param dataset_name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset to be removed
176
- # @return [Hash] project with removed dataset
177
- def remove_dataset(dataset_name)
178
- ProjectBlueprint.remove_dataset(to_hash, dataset_name)
214
+ # @param dataset [Hash | GoodData::Model::SchemaBlueprint] dataset to be added
215
+ # @param column_definition [Hash] Column definition to be added
216
+ # @return [GoodData::Model::ProjectBlueprint] returns project blueprint
217
+ def add_column!(dataset, column_definition)
218
+ ds = ProjectBlueprint.find_dataset(to_hash, dataset)
219
+ ds[:columns] << column_definition
220
+ self
179
221
  end
180
222
 
181
- # Removes dataset from blueprint. Dataset can be given as either a name
182
- # or a DatasetBlueprint or a Hash representation.
223
+ # Removes column to particular dataset in the blueprint
183
224
  #
184
- # @param dataset_name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset to be removed
185
- # @return [Hash] project with removed dataset
186
- def remove_dataset!(dataset_name)
187
- ProjectBlueprint.remove_dataset!(to_hash, dataset_name)
225
+ # @param dataset [Hash | GoodData::Model::SchemaBlueprint] dataset to be added
226
+ # @param id [String] id of the column to be removed
227
+ # @return [GoodData::Model::ProjectBlueprint] returns project blueprint
228
+ def remove_column!(dataset, id)
229
+ ProjectBlueprint.remove_column!(to_hash, dataset, id)
230
+ self
231
+ end
232
+
233
+ # Moves column to particular dataset in the blueprint. It currently supports moving
234
+ # of attributes and facts only. The rest of the fields probably does not make sense
235
+ # In case of attribute it moves its labels as well.
236
+ #
237
+ # @param id [GoodData::Model::BlueprintField] column to be moved
238
+ # @param from_dataset [Hash | GoodData::Model::SchemaBlueprint] dataset from which the field should be moved
239
+ # @param to_dataset [Hash | GoodData::Model::SchemaBlueprint] dataset to which the field should be moved
240
+ # @return [GoodData::Model::ProjectBlueprint] returns project blueprint
241
+ def move!(col, from_dataset, to_dataset)
242
+ from_dataset = find_dataset(from_dataset)
243
+ to_dataset = find_dataset(to_dataset)
244
+ column = if col.is_a?(String)
245
+ from_dataset.find_column_by_id(col)
246
+ else
247
+ from_dataset.find_column(col)
248
+ end
249
+ fail "Column #{col} cannot be found in dataset #{from_dataset.id}" unless column
250
+ stuff = case column.type
251
+ when :attribute
252
+ [column] + column.labels
253
+ when :fact
254
+ [column]
255
+ when :reference
256
+ [column]
257
+ else
258
+ fail 'Duplicate does not support moving #{col.type} type of field'
259
+ end
260
+ stuff = stuff.map(&:data)
261
+ stuff.each { |c| remove_column!(from_dataset, c[:id]) }
262
+ stuff.each { |c| add_column!(to_dataset, c) }
263
+ self
264
+ end
265
+
266
+ def duplicate!(col, from_dataset, to_dataset)
267
+ from_dataset = find_dataset(from_dataset)
268
+ to_dataset = find_dataset(to_dataset)
269
+ column = if col.is_a?(String)
270
+ from_dataset.find_column_by_id(col)
271
+ else
272
+ from_dataset.find_column(col)
273
+ end
274
+ fail "Column #{col} cannot be found in dataset #{from_dataset.id}" unless column
275
+ stuff = case column.type
276
+ when :attribute
277
+ [column] + column.labels
278
+ when :fact
279
+ [column]
280
+ when :reference
281
+ [column]
282
+ else
283
+ fail 'Duplicate does not support moving #{col.type} type of field'
284
+ end
285
+ stuff.map(&:data).each { |c| add_column!(to_dataset, c) }
286
+ self
287
+ end
288
+
289
+ # Returns list of attributes from all the datasets in a blueprint
290
+ #
291
+ # @return [Array<Hash>]
292
+ def attributes
293
+ datasets.reduce([]) { |a, e| a.concat(e.attributes) }
294
+ end
295
+
296
+ # Returns list of attributes and anchors from all the datasets in a blueprint
297
+ #
298
+ # @return [Array<Hash>]
299
+ def attributes_and_anchors
300
+ datasets.mapcat(&:attributes_and_anchors)
188
301
  end
189
302
 
190
303
  # Is this a project blueprint?
@@ -198,15 +311,48 @@ module GoodData
198
311
  #
199
312
  # @return [Array<Hash>]
200
313
  def date_dimensions
201
- ProjectBlueprint.date_dimensions(to_hash)
314
+ ProjectBlueprint.date_dimensions(self).map { |dd| GoodData::Model::DateDimension.new(dd, self) }
202
315
  end
203
316
 
204
317
  # Returns true if a dataset contains a particular dataset false otherwise
205
318
  #
206
319
  # @param name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset
207
320
  # @return [Boolean]
208
- def dataset?(name)
209
- ProjectBlueprint.dataset?(to_hash, name)
321
+ def dataset?(name, options = {})
322
+ ProjectBlueprint.dataset?(to_hash, name, options)
323
+ end
324
+
325
+ # Returns SLI manifest for one dataset. This is used by our API to allow
326
+ # loading data. The method is on project blueprint because you need
327
+ # acces to whole project to be able to generate references
328
+ #
329
+ # @param dataset [GoodData::Model::DatasetBlueprint | Hash | String] Dataset
330
+ # @param mode [String] Method of loading. FULL or INCREMENTAL
331
+ # @return [Array<Hash>] a title
332
+ def dataset_to_manifest(dataset, mode = 'FULL')
333
+ ToManifest.dataset_to_manifest(self, dataset, mode)
334
+ end
335
+
336
+ # Duplicated blueprint
337
+ #
338
+ # @param a_blueprint [GoodData::Model::DatasetBlueprint] Dataset blueprint to be merged
339
+ # @return [GoodData::Model::DatasetBlueprint]
340
+ def dup
341
+ ProjectBlueprint.new(data.deep_dup)
342
+ end
343
+
344
+ # Returns list of facts from all the datasets in a blueprint
345
+ #
346
+ # @return [Array<Hash>]
347
+ def facts
348
+ datasets.mapcat(&:facts)
349
+ end
350
+
351
+ # Returns list of fields from all the datasets in a blueprint
352
+ #
353
+ # @return [Array<Hash>]
354
+ def fields
355
+ datasets.flat_map(&:fields)
210
356
  end
211
357
 
212
358
  # Returns dataset specified. It can check even for a date dimension
@@ -215,7 +361,9 @@ module GoodData
215
361
  # @param options [Hash] options
216
362
  # @return [GoodData::Model::DatasetBlueprint]
217
363
  def find_dataset(name, options = {})
218
- DatasetBlueprint.new(ProjectBlueprint.find_dataset(to_hash, name, options))
364
+ ds = datasets(name, options)
365
+ fail "Dataset \"#{name}\" could not be found" unless ds
366
+ ds
219
367
  end
220
368
 
221
369
  # Returns a dataset of a given name. If a dataset is not found it throws an exeception
@@ -227,6 +375,15 @@ module GoodData
227
375
  DatasetBlueprint.new(ds)
228
376
  end
229
377
 
378
+ # Return list of datasets that are centers of the stars in datamart.
379
+ # This means these datasets are not referenced by anybody else
380
+ # In a good blueprint design these should be fact tables
381
+ #
382
+ # @return [Array<Hash>]
383
+ def find_star_centers
384
+ datasets.select { |d| d.referenced_by.empty? }
385
+ end
386
+
230
387
  # Constructor
231
388
  #
232
389
  # @param init_data [ProjectBlueprint | Hash] Blueprint or a blueprint definition. If passed a hash it is used as data for new instance. If there is a ProjectBlueprint passed it is duplicated and a new instance is created.
@@ -239,65 +396,20 @@ module GoodData
239
396
  else
240
397
  init_data
241
398
  end
242
- @data = some_data.deep_dup
243
- end
244
-
245
- # Validate the blueprint in particular if all references reference existing datasets and valid fields inside them.
246
- #
247
- # @return [Array] array of errors
248
- def validate_references
249
- if datasets.count == 1
250
- []
251
- else
252
- x = datasets.reduce([]) { |a, e| e.anchor? ? a << [e.name] : a } + date_dimensions.map { |y| [y[:name]] }
253
- refs = datasets.reduce([]) do |a, e|
254
- a.concat(e.references)
255
- end
256
- refs.reduce([]) do |a, e|
257
- x.include?([e[:dataset]]) ? a : a.concat([e])
399
+ @data = some_data.deep_dup.symbolize_keys
400
+ (@data[:datasets] || []).each do |d|
401
+ d[:type] = d[:type].to_sym
402
+ d[:columns].each do |c|
403
+ c[:type] = c[:type].to_sym
258
404
  end
259
405
  end
260
- end
261
-
262
- # Validate the blueprint and all its datasets return array of errors that are found.
263
- #
264
- # @return [Array] array of errors
265
- def validate
266
- refs_errors = validate_references
267
- labels_errors = datasets.reduce([]) { |a, e| a.concat(e.validate) }
268
- refs_errors.concat(labels_errors)
269
- end
270
-
271
- # Validate the blueprint and all its datasets and return true if model is valid. False otherwise.
272
- #
273
- # @return [Boolean] is model valid?
274
- def valid?
275
- validate.empty?
276
- end
277
-
278
- # Returns list of datasets which are referenced by given dataset. This can be
279
- # optionally switched to return even date dimensions
280
- #
281
- # @param project [GoodData::Model::DatasetBlueprint | Hash | String] Dataset blueprint
282
- # @return [Array<Hash>]
283
- def referenced_by(dataset)
284
- find_dataset(dataset, include_date_dimensions: true).references.map do |ref|
285
- find_dataset(ref[:dataset], include_date_dimensions: true)
406
+ (@data[:date_dimensions] || []).each do |d|
407
+ d[:type] = d[:type].to_sym
286
408
  end
287
409
  end
288
410
 
289
- # Returns list of attributes from all the datasets in a blueprint
290
- #
291
- # @return [Array<Hash>]
292
- def attributes
293
- datasets.reduce([]) { |a, e| a.concat(e.attributes) }
294
- end
295
-
296
- # Returns list of attributes and anchors from all the datasets in a blueprint
297
- #
298
- # @return [Array<Hash>]
299
- def attributes_and_anchors
300
- datasets.mapcat(&:attributes_and_anchors)
411
+ def id
412
+ data[:id]
301
413
  end
302
414
 
303
415
  # Returns list of labels from all the datasets in a blueprint
@@ -307,36 +419,6 @@ module GoodData
307
419
  datasets.mapcat(&:labels)
308
420
  end
309
421
 
310
- # Returns list of facts from all the datasets in a blueprint
311
- #
312
- # @return [Array<Hash>]
313
- def facts
314
- datasets.mapcat(&:facts)
315
- end
316
-
317
- # Returns list of fields from all the datasets in a blueprint
318
- #
319
- # @return [Array<Hash>]
320
- def fields
321
- ProjectBlueprint.fields(to_hash)
322
- end
323
-
324
- # Returns list of attributes that can break facts in a given dataset.
325
- # This basically means that it is giving you all attributes from the
326
- # datasets that are references by given dataset. Currently does not
327
- # work transitively
328
- #
329
- # @param project [GoodData::Model::DatasetBlueprint | Hash | String] Dataset blueprint
330
- # @return [Array<Hash>]
331
- def can_break(dataset)
332
- dataset = find_dataset(dataset) if dataset.is_a?(String)
333
- (referenced_by(dataset) + [dataset]).mapcat do |ds|
334
- ds.attributes_and_anchors.map do |attr|
335
- [ds, attr]
336
- end
337
- end
338
- end
339
-
340
422
  # Experimental but a basis for automatic check of health of a project
341
423
  #
342
424
  # @param project [GoodData::Model::DatasetBlueprint | Hash | String] Dataset blueprint
@@ -380,16 +462,61 @@ module GoodData
380
462
  errors
381
463
  end
382
464
 
383
- # Return list of datasets that are centers of the stars in datamart.
384
- # This means these datasets are not referenced by anybody else
385
- # In a good blueprint design these should be fact tables
465
+ # Merging two blueprints. The self blueprint is changed in place
386
466
  #
467
+ # @param a_blueprint [GoodData::Model::DatasetBlueprint] Dataset blueprint to be merged
468
+ # @return [GoodData::Model::ProjectBlueprint]
469
+ def merge!(a_blueprint)
470
+ temp_blueprint = merge(a_blueprint)
471
+ @data = temp_blueprint.data
472
+ self
473
+ end
474
+
475
+ # Returns list of datasets which are referenced by given dataset. This can be
476
+ # optionally switched to return even date dimensions
477
+ #
478
+ # @param project [GoodData::Model::DatasetBlueprint | Hash | String] Dataset blueprint
387
479
  # @return [Array<Hash>]
388
- def find_star_centers
389
- referenced = datasets.mapcat { |d| referenced_by(d) }
390
- referenced.flatten!
391
- res = datasets.map(&:to_hash) - referenced.map(&:to_hash)
392
- res.map { |d| DatasetBlueprint.new(d) }
480
+ def referenced_by(dataset)
481
+ find_dataset(dataset, include_date_dimensions: true).referencing
482
+ end
483
+
484
+ def referencing(dataset)
485
+ datasets(:all, include_date_dimensions: true)
486
+ .flat_map(&:references)
487
+ .select { |r| r.dataset == dataset }
488
+ .map(&:dataset_blueprint)
489
+ end
490
+
491
+ # Removes dataset from blueprint. Dataset can be given as either a name
492
+ # or a DatasetBlueprint or a Hash representation.
493
+ #
494
+ # @param dataset_name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset to be removed
495
+ # @return [Hash] project with removed dataset
496
+ def remove_dataset(dataset_name)
497
+ ProjectBlueprint.remove_dataset(to_hash, dataset_name)
498
+ self
499
+ end
500
+
501
+ # Removes dataset from blueprint. Dataset can be given as either a name
502
+ # or a DatasetBlueprint or a Hash representation.
503
+ #
504
+ # @param dataset_name [GoodData::Model::DatasetBlueprint | String | Hash] Dataset to be removed
505
+ # @return [Hash] project with removed dataset
506
+ def remove_dataset!(dataset_id)
507
+ ProjectBlueprint.remove_dataset!(to_hash, dataset_id)
508
+ self
509
+ end
510
+
511
+ # Removes all the labels from the anchor. This is a typical operation that people want to
512
+ # perform on fact tables
513
+ #
514
+ # @return [GoodData::Model::ProjectBlueprint] Returns changed blueprint
515
+ def strip_anchor!(dataset)
516
+ from_dataset = find_dataset(dataset)
517
+ stuff = dataset.anchor.labels.map(&:data)
518
+ stuff.each { |column| remove_column!(from_dataset, column[:id]) }
519
+ self
393
520
  end
394
521
 
395
522
  # Returns some reports that might get you started. They are just simple
@@ -405,8 +532,7 @@ module GoodData
405
532
  star, metrics = e
406
533
  metrics.each(&:save)
407
534
  reports_stubs = metrics.map do |m|
408
- breaks = can_break(star).map { |ds, aM| ds.identifier_for(aM) }
409
- # [breaks.sample((breaks.length/10.0).ceil), m]
535
+ breaks = broken_by(star).map { |ds, aM| ds.identifier_for(aM) }
410
536
  [breaks, m]
411
537
  end
412
538
  a.concat(reports_stubs)
@@ -435,13 +561,41 @@ module GoodData
435
561
  stars.zip(metrics)
436
562
  end
437
563
 
438
- # Merging two blueprints. The self blueprint is changed in place
439
- #
440
- # @param a_blueprint [GoodData::Model::DatasetBlueprint] Dataset blueprint to be merged
441
- # @return [GoodData::Model::ProjectBlueprint]
442
- def merge!(a_blueprint)
443
- temp_blueprint = merge(a_blueprint)
444
- @data = temp_blueprint.data
564
+ def to_blueprint
565
+ self
566
+ end
567
+
568
+ def refactor_split_df(dataset)
569
+ fail ValidationError unless valid?
570
+ o = find_dataset(dataset)
571
+ new_dataset = GoodData::Model::DatasetBlueprint.new({ type: :dataset, id: "#{o.id}_dim", columns: [] }, self)
572
+ new_dataset.change do |d|
573
+ d.add_anchor('vymysli_id')
574
+ d.add_label('label.vymysli_id', reference: 'vymysli_id')
575
+ end
576
+ nb = merge(new_dataset.to_blueprint)
577
+ o.attributes.each { |a| nb.move!(a, o, new_dataset.id) }
578
+ old = nb.find_dataset(dataset)
579
+ old.attributes.each do |a|
580
+ remove_column!(old, a)
581
+ end
582
+ old.change do |d|
583
+ d.add_reference(new_dataset.id)
584
+ end
585
+ nb
586
+ end
587
+
588
+ def refactor_split_facts(dataset, column_names, new_dataset_title)
589
+ fail ValidationError unless valid?
590
+ change do |p|
591
+ p.add_dataset(new_dataset_title) do |d|
592
+ d.add_anchor("#{new_dataset_title}.id")
593
+ end
594
+ end
595
+ dataset_to_refactor = find_dataset(dataset)
596
+ new_dataset = find_dataset(new_dataset_title)
597
+ column_names.each { |c| move!(c, dataset_to_refactor, new_dataset) }
598
+ dataset_to_refactor.references.each { |ref| duplicate!(ref, dataset_to_refactor, new_dataset) }
445
599
  self
446
600
  end
447
601
 
@@ -454,32 +608,48 @@ module GoodData
454
608
  temp_blueprint = dup
455
609
  return temp_blueprint unless a_blueprint
456
610
  a_blueprint.datasets.each do |dataset|
457
- if temp_blueprint.dataset?(dataset.name)
458
- local_dataset = temp_blueprint.find_dataset(dataset.name)
611
+ if temp_blueprint.dataset?(dataset.id)
612
+ local_dataset = temp_blueprint.find_dataset(dataset.id)
459
613
  index = temp_blueprint.datasets.index(local_dataset)
460
614
  local_dataset.merge!(dataset)
461
- temp_blueprint.remove_dataset!(local_dataset.name)
615
+ temp_blueprint.remove_dataset!(local_dataset.id)
462
616
  temp_blueprint.add_dataset!(local_dataset, index)
463
617
  else
464
618
  temp_blueprint.add_dataset!(dataset.dup)
465
619
  end
466
620
  end
621
+ a_blueprint.date_dimensions.each do |dd|
622
+ if temp_blueprint.dataset?(dd.id, dd: true)
623
+ local_dim = temp_blueprint.find_dataset(dd.id, dd: true)
624
+ fail "Unable to merge date dimensions #{dd.id} with defintion #{dd.data} with #{local_dim.data}" unless local_dim.data == dd.data
625
+ else
626
+ temp_blueprint.add_date_dimension!(dd.dup)
627
+ end
628
+ end
467
629
  temp_blueprint
468
630
  end
469
631
 
470
- # Duplicated blueprint
632
+ # Helper for storing the project blueprint into a file as JSON.
471
633
  #
472
- # @param a_blueprint [GoodData::Model::DatasetBlueprint] Dataset blueprint to be merged
473
- # @return [GoodData::Model::DatasetBlueprint]
474
- def dup
475
- ProjectBlueprint.new(data.deep_dup)
634
+ # @param filename [String] Name of the file where the blueprint should be stored
635
+ def store_to_file(filename)
636
+ File.open(filename, 'w') do |f|
637
+ f << JSON.pretty_generate(to_hash)
638
+ end
476
639
  end
477
640
 
478
641
  # Returns title of a dataset. If not present it is generated from the name
479
642
  #
480
643
  # @return [String] a title
481
644
  def title
482
- Model.title(to_hash)
645
+ Model.title(to_hash) if to_hash[:title]
646
+ end
647
+
648
+ # Returns title of a dataset. If not present it is generated from the name
649
+ #
650
+ # @return [String] a title
651
+ def title=(a_title)
652
+ @data[:title] = a_title
483
653
  end
484
654
 
485
655
  # Returns Wire representation. This is used by our API to generate and
@@ -487,6 +657,7 @@ module GoodData
487
657
  #
488
658
  # @return [Hash] a title
489
659
  def to_wire
660
+ validate
490
661
  ToWire.to_wire(data)
491
662
  end
492
663
 
@@ -495,20 +666,10 @@ module GoodData
495
666
  #
496
667
  # @return [Array<Hash>] a title
497
668
  def to_manifest
669
+ validate
498
670
  ToManifest.to_manifest(to_hash)
499
671
  end
500
672
 
501
- # Returns SLI manifest for one dataset. This is used by our API to allow
502
- # loading data. The method is on project blueprint because you need
503
- # acces to whole project to be able to generate references
504
- #
505
- # @param dataset [GoodData::Model::DatasetBlueprint | Hash | String] Dataset
506
- # @param mode [String] Method of loading. FULL or INCREMENTAL
507
- # @return [Array<Hash>] a title
508
- def dataset_to_manifest(dataset, mode = 'FULL')
509
- ToManifest.dataset_to_manifest(self, dataset, mode)
510
- end
511
-
512
673
  # Returns hash representation of blueprint
513
674
  #
514
675
  # @return [Hash] a title
@@ -516,12 +677,49 @@ module GoodData
516
677
  @data
517
678
  end
518
679
 
519
- def ==(other)
520
- to_hash == other.to_hash
680
+ # Validate the blueprint in particular if all references reference existing datasets and valid fields inside them.
681
+ #
682
+ # @return [Array] array of errors
683
+ def validate_references
684
+ stuff = datasets(:all, include_date_dimensions: true).flat_map(&:references).select do |ref|
685
+ begin
686
+ ref.dataset
687
+ false
688
+ rescue RuntimeError
689
+ true
690
+ end
691
+ end
692
+ stuff.map { |r| { type: :bad_reference, reference: r.data, referencing_dataset: r.data[:dataset] } }
693
+ end
694
+
695
+ # Validate the blueprint and all its datasets return array of errors that are found.
696
+ #
697
+ # @return [Array] array of errors
698
+ def validate
699
+ errors = []
700
+ errors.concat validate_references
701
+ errors.concat datasets.reduce([]) { |a, e| a.concat(e.validate) }
702
+ errors.concat datasets.reduce([]) { |a, e| a.concat(e.validate_gd_data_type_errors) }
703
+ errors
704
+ rescue
705
+ raise GoodData::ValidationError
706
+ end
707
+
708
+ # Validate the blueprint and all its datasets and return true if model is valid. False otherwise.
709
+ #
710
+ # @return [Boolean] is model valid?
711
+ def valid?
712
+ validate.empty?
521
713
  end
522
714
 
523
- def eql?(other)
524
- to_hash == other.to_hash
715
+ def ==(other)
716
+ # to_hash == other.to_hash
717
+ return false unless id == other.id
718
+ return false unless title == other.title
719
+ left = to_hash[:datasets].map { |d| d[:columns].to_set }.to_set
720
+ right = other.to_hash[:datasets].map { |d| d[:columns].to_set }.to_set
721
+ return false unless left == right
722
+ true
525
723
  end
526
724
  end
527
725
  end