gooddata 0.6.18 → 0.6.19

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