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,24 @@
1
+ # encoding: UTF-8
2
+ require 'gooddata'
3
+
4
+ describe GoodData::Model::AttributeBlueprintField do
5
+
6
+ before(:each) do
7
+ @model_view = MultiJson.load(File.read('./spec/data/wire_models/model_view.json'))
8
+ @blueprint = GoodData::Model::FromWire.from_wire(@model_view)
9
+ @dataset = @blueprint.datasets('dataset.account')
10
+ @attribute = @dataset.attributes('attr.account.region')
11
+ end
12
+
13
+ describe '#labels' do
14
+ it 'should return labels on dataset' do
15
+ expect(@attribute.labels.count).to eq 1
16
+ end
17
+ end
18
+
19
+ describe '#dataset' do
20
+ it 'should return dataset of the attribtue field' do
21
+ expect(@attribute.dataset_blueprint).to eq @dataset
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: UTF-8
2
+ require 'gooddata'
3
+
4
+ describe GoodData::Model::DatasetBlueprint do
5
+
6
+ before(:each) do
7
+ @model_view = MultiJson.load(File.read('./spec/data/wire_models/model_view.json'))
8
+ @blueprint = GoodData::Model::FromWire.from_wire(@model_view)
9
+ @dataset = @blueprint.datasets('dataset.opportunityanalysis')
10
+ @small_blueprint = GoodData::Model::ProjectBlueprint.build('my_bp') do |p|
11
+ p.add_dataset('dataset.countries') do |d|
12
+ d.add_anchor('attr.country')
13
+ d.add_label('label.country.name', reference: 'attr.country')
14
+ end
15
+ p.add_dataset('dataset.repos') do |d|
16
+ d.add_anchor('attr.repository')
17
+ d.add_label('label.repo.name', reference: 'attr.repository')
18
+ d.add_reference('dataset.countries')
19
+ end
20
+ p.add_dataset('dataset.devs') do |d|
21
+ d.add_anchor('attr.dev')
22
+ d.add_label('label.dev.name', reference: 'attr.dev')
23
+ end
24
+ p.add_dataset('dataset.commits') do |d|
25
+ d.add_anchor('attr.commits')
26
+ d.add_attribute('attr.quality')
27
+ d.add_label('label.quality', reference: 'attr.quality')
28
+ d.add_fact('more_numbers')
29
+ d.add_reference('dataset.repos')
30
+ d.add_reference('dataset.devs')
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#facts' do
36
+ it 'should return facts on dataset' do
37
+ expect(@dataset.facts.count).to eq 2
38
+ expect(@dataset.facts.map(&:id).to_set).to eq [
39
+ 'fact.opportunityanalysis.buckets_to_display',
40
+ 'fact.opportunityanalysis.month_fact'
41
+ ].to_set
42
+ end
43
+
44
+ it 'should be able to pick specific fact' do
45
+ expect(@dataset.facts('fact.opportunityanalysis.buckets_to_display').id).to eq 'fact.opportunityanalysis.buckets_to_display'
46
+ end
47
+ end
48
+
49
+ describe '#attributes' do
50
+ it 'should return attributes on dataset' do
51
+ expect(@dataset.attributes.count).to eq 2
52
+ expect(@dataset.attributes.map(&:id).to_set).to eq [
53
+ 'attr.opportunityanalysis.month',
54
+ 'attr.opportunityanalysis.cohorttype'
55
+ ].to_set
56
+ end
57
+
58
+ it 'should return attributes on dataset' do
59
+ expect(@dataset.attributes('attr.opportunityanalysis.cohorttype').id).to eq 'attr.opportunityanalysis.cohorttype'
60
+ end
61
+ end
62
+
63
+ describe '#labels' do
64
+ it 'should return labels on dataset' do
65
+ expect(@dataset.labels.count).to eq 4
66
+ expect(@dataset.labels.map(&:id).to_set).to eq [
67
+ 'label.opportunityanalysis.month.monthsortingnew',
68
+ 'label.opportunityanalysis.month',
69
+ 'label.opportunityanalysis.cohorttype',
70
+ 'label.opportunityanalysis.techoppanalysis'
71
+ ].to_set
72
+ end
73
+ end
74
+
75
+ describe '#references' do
76
+ it 'should return references on dataset' do
77
+ expect(@dataset.references.count).to eq 2
78
+ expect(@dataset.references.map {|r| r.data[:dataset]}).to eq [
79
+ 'dataset.opp_records',
80
+ 'dataset.consolidatedmarketingstatus'
81
+ ]
82
+ end
83
+ end
84
+
85
+ describe '#referenced_by' do
86
+ it 'should return datasets that are referencing this one' do
87
+ expect(@dataset.referenced_by).to eq []
88
+ dimension = @blueprint.datasets('dataset.opp_records')
89
+ expect(dimension.referenced_by.map(&:id)).to eq ['dataset.opportunityanalysis']
90
+ end
91
+ end
92
+
93
+ describe '#referencing' do
94
+ it 'should return datasets that are referenced by this one (the references in the dataset leads to those datasets)' do
95
+ expect(@dataset.referencing.map(&:id)).to eq ['dataset.opp_records', 'dataset.consolidatedmarketingstatus']
96
+ dimension = @blueprint.datasets('dataset.productline')
97
+ expect(dimension.referencing.map(&:id)).to be_empty
98
+ dimension = @blueprint.datasets('dataset.opp_records')
99
+ expect(dimension.referencing.map(&:id).count).to eq 28
100
+ end
101
+ end
102
+
103
+ describe '#breaks' do
104
+ it 'should return attributes that the dataset can break' do
105
+ expect(@small_blueprint.datasets('dataset.countries').breaks.map(&:id)).to eq ['attr.country', 'attr.repository', 'attr.quality']
106
+ expect(@small_blueprint.datasets('dataset.devs').breaks.map(&:id)).to eq ['attr.dev', 'attr.quality']
107
+ end
108
+ end
109
+
110
+ describe '#broken_by' do
111
+ it 'should return attributes that the dataset can be broken by' do
112
+ expect(@small_blueprint.datasets('dataset.commits').broken_by.map(&:id)).to eq ['attr.quality', 'attr.repository', 'attr.country', 'attr.dev']
113
+ expect(@small_blueprint.datasets('dataset.devs').broken_by.map(&:id)).to eq ['attr.dev']
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+ require 'gooddata'
3
+
4
+ describe GoodData::Model::LabelBlueprintField do
5
+
6
+ before(:each) do
7
+ @model_view = MultiJson.load(File.read('./spec/data/wire_models/model_view.json'))
8
+ @blueprint = GoodData::Model::FromWire.from_wire(@model_view)
9
+ @dataset = @blueprint.datasets('dataset.opportunityanalysis')
10
+ @attribute = @dataset.attributes('attr.opportunityanalysis.month')
11
+ end
12
+
13
+ describe '#attribute' do
14
+ it 'should return attribute on label' do
15
+ labels = @attribute.labels
16
+ expect(labels.count).to eq 2
17
+
18
+ expect(labels[0].attribute).to eq @attribute
19
+ expect(labels[1].attribute).to eq @attribute
20
+
21
+ expect(labels[0].dataset_blueprint).to eq @dataset
22
+ expect(labels[1].dataset_blueprint).to eq @dataset
23
+ end
24
+ end
25
+
26
+ describe '#gd_type' do
27
+ it 'should return attribute on label' do
28
+ label = @blueprint.datasets('dataset.opportunityanalysis').labels('label.opportunityanalysis.techoppanalysis')
29
+ expect(label.gd_type).to eq 'GDC.text'
30
+ end
31
+ end
32
+
33
+ describe '#gd_datatype' do
34
+ it 'should return attribute on label' do
35
+ label = @blueprint.datasets('dataset.opportunityanalysis').labels('label.opportunityanalysis.techoppanalysis')
36
+ expect(label.gd_data_type).to eq 'VARCHAR(128)'
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,643 @@
1
+ # encoding: UTF-8
2
+ require 'gooddata'
3
+
4
+ describe GoodData::Model::ProjectBlueprint do
5
+
6
+ before(:each) do
7
+ @blueprint = GoodData::Model::ProjectBlueprint.from_json('./spec/data/blueprints/test_project_model_spec.json')
8
+ @invalid_blueprint = GoodData::Model::ProjectBlueprint.from_json('./spec/data/blueprints/invalid_blueprint.json')
9
+ @spec_blueprint = GoodData::Model::FromWire.from_wire(MultiJson.load(File.read('./spec/data/wire_models/model_view.json')))
10
+ @repos = @blueprint.find_dataset('dataset.repos')
11
+ @commits = @blueprint.find_dataset('dataset.commits')
12
+ end
13
+
14
+ describe '#title' do
15
+ it "should return the title" do
16
+ expect(@blueprint.title).to eq "RubyGem Dev Week test"
17
+ end
18
+ end
19
+
20
+ describe '#valid?' do
21
+ it 'valid blueprint should be marked as valid' do
22
+ expect(@blueprint.valid?).to eq true
23
+ end
24
+
25
+ it 'model should be invalid if it contains more than one anchor' do
26
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
27
+ p.add_dataset("dataset.repos") do |d|
28
+ d.add_anchor("repo_id")
29
+ d.add_anchor("repo_id2")
30
+ d.add_fact("numbers")
31
+ d.add_attribute("name")
32
+ end
33
+ end
34
+ expect(bp.valid?).to be_falsey
35
+ errors = bp.validate
36
+ expect(errors.map {|x| x[:type]}.to_set).to eq [:attribute_without_label, :more_than_on_anchor].to_set
37
+ expect(errors.count).to eq 2
38
+ end
39
+
40
+ it 'model should be invalid if it contains no anchor' do
41
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
42
+ p.add_dataset("dataset.repos") do |d|
43
+ d.add_fact("numbers")
44
+ d.add_attribute("name")
45
+ d.add_label('some_label', reference: 'name')
46
+ end
47
+ end
48
+ expect(bp.valid?).to be_falsey
49
+ errors = bp.validate
50
+ expect(errors.first[:type]).to eq :no_anchor
51
+ expect(errors.count).to eq 1
52
+ end
53
+
54
+ it 'model should be invalid if it has invalid gd data type' do
55
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
56
+ p.add_dataset("dataset.repos") do |d|
57
+ d.add_anchor("attr.repository", label_id: 'label.repo.name', label_gd_data_type: "INTEGERX")
58
+ d.add_attribute("attr.attribute1", title: 'Some attribute')
59
+ d.add_label('label.attribute1.name', gd_data_type: "SOMEOTHER", reference: "attr.attribute1")
60
+ end
61
+ end
62
+ expect(bp.valid?).to be_falsey
63
+ errors = bp.validate
64
+ expect(errors.count).to eq 2
65
+ end
66
+
67
+ it 'model should be valid if it has int specified as integer and default should be decimal' do
68
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
69
+ p.add_dataset("dataset.repos") do |d|
70
+ d.add_anchor("attr.repository")
71
+ d.add_label('label.repository.name', reference: 'attr.repository')
72
+ d.add_attribute("attr.attribute1", title: 'Some attribute')
73
+ d.add_label('label.attribute1.name', reference: 'attr.attribute1')
74
+ d.add_fact('some_numbers', gd_data_type: 'INT')
75
+ d.add_fact('more_numbers')
76
+ end
77
+ end
78
+ bp.valid?.should == true
79
+ errors = bp.validate
80
+ expect(errors.count).to eq 0
81
+ facts = bp.to_wire[:diffRequest][:targetModel][:projectModel][:datasets].first[:dataset][:facts]
82
+ expect(facts[0][:fact][:dataType]).to eq 'INT'
83
+ expect(facts[1][:fact][:dataType]).to eq 'DECIMAL(12,2)'
84
+ end
85
+
86
+ it 'invalid blueprint should be marked as invalid' do
87
+ expect(@invalid_blueprint.valid?).to eq false
88
+ end
89
+ end
90
+
91
+ describe '#validate' do
92
+ it 'valid blueprint should give you empty array of errors' do
93
+ expect(@blueprint.validate).to be_empty
94
+ end
95
+
96
+ it 'invalid blueprint should give you list of violating references' do
97
+ errors = @invalid_blueprint.validate
98
+ expect(errors.size).to eq 1
99
+ expect(errors).to eq([{
100
+ :type=>:wrong_label_reference,
101
+ :label=>"some_label_id",
102
+ :wrong_reference=>"attr.repos.repo_id ERROR"
103
+ }])
104
+ end
105
+
106
+ it 'invalid label is caught correctly' do
107
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
108
+ p.add_dataset("dataset.repos") do |d|
109
+ d.add_anchor("attr.repository", label_id: 'label.repo.name')
110
+ d.add_attribute("attr.attribute1", title: 'Some attribute')
111
+ d.add_label('label.attribute1.name', reference: 'attr.attribute23123')
112
+ d.add_label('label.attribute1.ssn', reference: 'attr.attribute23123')
113
+ d.add_fact('some_numbers', gd_data_type: 'INT')
114
+ d.add_fact('more_numbers')
115
+ end
116
+ end
117
+ expect(bp.valid?).to be_falsey
118
+ errors = bp.validate
119
+ expect(errors.count).to eq 3
120
+ expect(errors).to eq [
121
+ {
122
+ :type=>:wrong_label_reference,
123
+ :label=>"label.attribute1.name",
124
+ :wrong_reference=>"attr.attribute23123"
125
+ },
126
+ {
127
+ :type=>:wrong_label_reference,
128
+ :label=>"label.attribute1.ssn",
129
+ :wrong_reference=>"attr.attribute23123"
130
+ },
131
+ {
132
+ :type=>:attribute_without_label,
133
+ :attribute=>"attr.attribute1"
134
+ }
135
+ ]
136
+ end
137
+ end
138
+
139
+ describe '#remove_dataset!' do
140
+ it "should be able to remove dataset by name" do
141
+ expect(@blueprint.datasets.count).to eq 3
142
+ bp = @blueprint.remove_dataset!('dataset.repos')
143
+ expect(bp).to be_kind_of(GoodData::Model::ProjectBlueprint)
144
+ expect(@blueprint.datasets.count).to eq 2
145
+ end
146
+
147
+ it "should be able to remove dataset by reference" do
148
+ expect(@blueprint.datasets.count).to eq 3
149
+ dataset = @blueprint.find_dataset('dataset.repos')
150
+ bp = @blueprint.remove_dataset!(dataset)
151
+ expect(bp).to be_kind_of(GoodData::Model::ProjectBlueprint)
152
+ expect(@blueprint.datasets.count).to eq 2
153
+ end
154
+
155
+ it "should be able to remove dataset by name" do
156
+ expect(@blueprint.datasets.count).to eq 3
157
+ bp = GoodData::Model::ProjectBlueprint.remove_dataset!(@blueprint, 'dataset.repos')
158
+ expect(bp).to be_kind_of(Hash)
159
+ expect(@blueprint.datasets.count).to eq 2
160
+ end
161
+
162
+ it "should be able to remove dataset by reference" do
163
+ expect(@blueprint.datasets.count).to eq 3
164
+ dataset = @blueprint.find_dataset('dataset.repos')
165
+ bp = GoodData::Model::ProjectBlueprint.remove_dataset!(@blueprint, dataset)
166
+ expect(bp).to be_kind_of(Hash)
167
+ expect(@blueprint.datasets.count).to eq 2
168
+ end
169
+ end
170
+
171
+ describe '#references' do
172
+ it 'references return empty array if there is no reference' do
173
+ refs = @blueprint.find_dataset('dataset.devs').references
174
+ expect(refs).to be_empty
175
+ end
176
+ end
177
+
178
+ describe '#find_dataset' do
179
+ it 'should be able to get dataset by identifier' do
180
+ ds = @blueprint.find_dataset('dataset.devs')
181
+ expect(ds.id).to eq 'dataset.devs'
182
+ expect(ds).to be_kind_of(GoodData::Model::DatasetBlueprint)
183
+ end
184
+
185
+ it 'should throw an error if the dataset with a given name could not be found' do
186
+ expect { @blueprint.find_dataset('nonexisting_dataset') }.to raise_error
187
+ end
188
+
189
+ it "should be pssible to find a dataset using dataset" do
190
+ ds = @blueprint.find_dataset('dataset.devs')
191
+ sds = @blueprint.find_dataset(ds)
192
+ expect(ds).to eq sds
193
+ end
194
+ end
195
+
196
+ describe '#to_blueprint' do
197
+ it "should be possible to create ProjectBlueprint from SchemaBuilder" do
198
+ builder = GoodData::Model::SchemaBuilder.create("stuff") do |d|
199
+ d.add_anchor("anchor_id")
200
+ d.add_attribute("id", title: "My Id")
201
+ d.add_label("label", reference: "id")
202
+ d.add_fact("amount", title: "Amount")
203
+ end
204
+ bp2 = builder.to_blueprint
205
+ expect(bp2.valid?).to eq true
206
+ end
207
+
208
+ it "should be possible to create ProjectBlueprint from SchemaBuilder" do
209
+ builder = GoodData::Model::SchemaBuilder.create("stuff") do |d|
210
+ d.add_anchor("anchor_id")
211
+ d.add_attribute("id", title: "My Id")
212
+ d.add_label("label", reference: "id")
213
+ d.add_fact("amount", title: "Amount")
214
+ end
215
+
216
+ bp1 = GoodData::Model::ProjectBlueprint.new(builder)
217
+ expect(bp1.valid?).to eq true
218
+ end
219
+ end
220
+
221
+ describe '#dataset?' do
222
+ it 'should be able to tell me if ceratain dataset by name is in the blueprint' do
223
+ expect(@blueprint.dataset?('dataset.devs')).to be_truthy
224
+ end
225
+ end
226
+
227
+ describe '#anchor?' do
228
+ it 'should tell you it has anchor when it does' do
229
+ expect(@repos.anchor?).to eq true
230
+ end
231
+ end
232
+
233
+ describe '#anchor' do
234
+ it 'should tell you anchor does have labels' do
235
+ expect(@commits.anchor.labels.count).to eq 0
236
+ end
237
+
238
+ it 'anchor should have labels' do
239
+ expect(@repos.anchor.labels.first.id).to eq 'some_label_id'
240
+ end
241
+ end
242
+
243
+ describe '#attributes' do
244
+ it 'attribute should have labels' do
245
+ expect(@repos.attributes.first.labels.first.id).to eq 'some_attr_label_id'
246
+ end
247
+
248
+ it "should return attributes form all datasets" do
249
+ expect(@blueprint.attributes.count).to eq 1
250
+ end
251
+
252
+ it 'should be able to grab attribute' do
253
+ expect(@repos.labels.size).to eq 2
254
+ expect(@repos.labels('some_attr_label_id').attribute).to eq @repos.attributes('some_attr_id')
255
+ expect(@repos.labels('some_label_id').attribute.id).to eq 'attr.repos.repo_id'
256
+ end
257
+ end
258
+
259
+ describe '#facts' do
260
+ it 'commits should have one fact' do
261
+ expect(@commits.facts.size).to eq 1
262
+ end
263
+
264
+ it 'commits should have one fact' do
265
+ expect(@repos.facts.size).to eq 0
266
+ end
267
+
268
+ it "should return facts form all datasets" do
269
+ expect(@blueprint.facts.count).to eq 1
270
+ end
271
+ end
272
+
273
+ describe '#labels' do
274
+ it 'Anchor on repos should have a label' do
275
+ expect(@repos.anchor.labels.size).to eq 1
276
+ end
277
+
278
+ it 'should not have a label for a dataset without anchor with label' do
279
+ expect(@commits.anchor.labels).to eq []
280
+ end
281
+
282
+ it "should return labels form all datasets" do
283
+ expect(@blueprint.labels.count).to eq 4
284
+ end
285
+ end
286
+
287
+ describe '#attributes_and_anchors' do
288
+ it "should return labels form all datasets" do
289
+ expect(@blueprint.attributes_and_anchors.count).to eq 4
290
+ end
291
+ end
292
+
293
+ describe '#merge' do
294
+ it "should be able to merge models without mutating the original" do
295
+ additional_blueprint = GoodData::Model::ProjectBlueprint.from_json("./spec/data/blueprints/additional_dataset_module.json")
296
+ expect(@blueprint.datasets.count).to eq 3
297
+ new_bp = @blueprint.merge(additional_blueprint)
298
+ expect(@blueprint.datasets.count).to eq 3
299
+ expect(new_bp.datasets.count).to eq 4
300
+ end
301
+
302
+ it "should perform merge in associative matter. Order should not matter." do
303
+ a = GoodData::Model::ProjectBlueprint.build("p") do |p|
304
+ p.add_date_dimension("updated_on")
305
+ p.add_dataset('stuff') do |d|
306
+ d.add_anchor('stuff_id')
307
+ d.add_label('name', reference: 'stuff_id')
308
+ d.add_date('updated_on')
309
+ end
310
+ end
311
+ b = GoodData::Model::ProjectBlueprint.build("p") do |p|
312
+ p.add_date_dimension("created_on")
313
+ p.add_dataset('stuff') do |d|
314
+ d.add_attribute('attr_id')
315
+ d.add_label('attr_name', reference: 'attr_id')
316
+ d.add_date('created_on')
317
+ end
318
+ end
319
+ # those two are the same. Notice that we have made the titles the same
320
+ # Merging titles is not associative
321
+ a_b = a.merge(b)
322
+ b_a = b.merge(a)
323
+ expect(a_b.valid?).to eq true
324
+ expect(b_a.valid?).to eq true
325
+ expect(b_a).to eq a_b
326
+ end
327
+
328
+ it "should perform merge in associative matter. Order should not matter." do
329
+ a = GoodData::Model::ProjectBlueprint.build("p") do |p|
330
+ p.add_date_dimension("updated_on")
331
+ p.add_dataset('stuff') do |d|
332
+ d.add_anchor('stuff_id')
333
+ d.add_label('name', reference: 'stuff_id')
334
+ d.add_date('updated_on')
335
+ end
336
+ end
337
+ b = GoodData::Model::ProjectBlueprint.build("p") do |p|
338
+ p.add_date_dimension("created_on")
339
+ p.add_dataset('stuff') do |d|
340
+ d.add_attribute('attr_id')
341
+ d.add_label('attr_name', reference: 'attr_id')
342
+ d.add_date('created_on')
343
+ end
344
+ end
345
+ # those two are the same. Notice that we have made the titles the same
346
+ # Merging titles is not associative
347
+ a_b = a.merge(b)
348
+ b_a = b.merge(a)
349
+ expect(a_b.valid?).to eq true
350
+ expect(b_a.valid?).to eq true
351
+ expect(b_a).to eq a_b
352
+ end
353
+
354
+ it "should fail if unable to merge date dimensions (they are different)." do
355
+ a = GoodData::Model::ProjectBlueprint.build("p") do |p|
356
+ p.add_date_dimension("created_on", title: 'title A')
357
+ p.add_dataset('stuff') do |d|
358
+ d.add_anchor('stuff_id')
359
+ d.add_label('name', reference: 'stuff_id')
360
+ d.add_date('created_on')
361
+ end
362
+ end
363
+ b = GoodData::Model::ProjectBlueprint.build("p") do |p|
364
+ p.add_date_dimension("created_on", title: 'title B')
365
+ p.add_dataset('stuff') do |d|
366
+ d.add_attribute('attr_id')
367
+ d.add_label('attr_name', reference: 'attr_id')
368
+ d.add_date('created_on')
369
+ end
370
+ end
371
+ expect {
372
+ c = a.merge(b)
373
+ }.to raise_exception 'Unable to merge date dimensions created_on with defintion {:type=>:date_dimension, :urn=>nil, :id=>"created_on", :title=>"title B"} with {:type=>:date_dimension, :urn=>nil, :id=>"created_on", :title=>"title A"}'
374
+ end
375
+ end
376
+
377
+ describe '#merge!' do
378
+ it "should be able to merge models" do
379
+ additional_blueprint = GoodData::Model::ProjectBlueprint.from_json("./spec/data/blueprints/additional_dataset_module.json")
380
+ expect(@blueprint.datasets.count).to eq 3
381
+ @blueprint.merge!(additional_blueprint)
382
+ expect(@blueprint.datasets.count).to eq 4
383
+ end
384
+ end
385
+
386
+ it "should be able to add datasets on the fly" do
387
+ builder = GoodData::Model::SchemaBuilder.new("stuff") do |d|
388
+ d.add_attribute("id", title: "My Id")
389
+ d.add_fact("amount", title: "Amount")
390
+ end
391
+ dataset = builder.to_blueprint
392
+ expect(@blueprint.datasets.count).to eq 3
393
+ @blueprint.add_dataset!(dataset)
394
+ expect(@blueprint.datasets.count).to eq 4
395
+ end
396
+
397
+
398
+
399
+ it "should be able to serialize itself to a hash" do
400
+ ser = @blueprint.to_hash
401
+ ser.is_a?(Hash)
402
+ expect(ser.keys).to eq [:title, :datasets, :date_dimensions]
403
+ end
404
+
405
+ it "should be able to tell you whether a dataset is referencing any others including date dimensions" do
406
+ d = @blueprint.datasets('dataset.commits')
407
+ referenced_datasets = @blueprint.referenced_by(d)
408
+ expect(referenced_datasets.count).to eq 3
409
+ end
410
+
411
+ it "should be able to find star centers - datasets that are not referenced by any other - these are typical fact tables" do
412
+ centers = @blueprint.find_star_centers
413
+ expect(centers.count).to eq 1
414
+ expect(centers.first.id).to eq 'dataset.commits'
415
+ end
416
+
417
+ it "should be able to return all attributes or anchors that can break metrics computed in the context of given dataset" do
418
+ commits = @blueprint.datasets('dataset.commits')
419
+ expect(commits.broken_by.count).to eq 3
420
+ expect(commits.broken_by.map(&:id)).to eq ["attr.devs.dev_id", "some_attr_id", "attr.repos.repo_id"]
421
+ end
422
+
423
+ it 'blueprint can be set without date reference and default format is set' do
424
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
425
+ p.add_date_dimension("committed_on")
426
+
427
+ p.add_dataset("dataset.repos") do |d|
428
+ d.add_anchor("attr.repository")
429
+ d.add_label('label.repo.name')
430
+ d.add_attribute("attr.attribute1", title: 'Some attribute')
431
+ d.add_label('label.attribute1.name', reference: 'attr.attribute1')
432
+ d.add_label('label.attribute1.ssn', reference: 'attr.attribute1')
433
+ d.add_fact('some_numbers', gd_data_type: 'INT')
434
+ d.add_fact('more_numbers')
435
+ d.add_date('opportunity_comitted', dataset: 'committed_on')
436
+ end
437
+ end
438
+ expect(bp.datasets.flat_map { |d| d.find_columns_by_type(:date) }.map { |a| a.format }).to eq [GoodData::Model::DEFAULT_DATE_FORMAT]
439
+ end
440
+
441
+ it 'blueprint can be set with explicit date' do
442
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
443
+ p.add_date_dimension("committed_on")
444
+
445
+ p.add_dataset("dataset.repos") do |d|
446
+ d.add_anchor("attr.repository", label_id: 'label.repo.name')
447
+ d.add_attribute("attr.attribute1")
448
+ d.add_label('label.attribute1.name', title: 'Some attribute', reference: 'attr.attribute1')
449
+ d.add_label('label.attribute1.ssn', reference: 'attr.attribute1')
450
+ d.add_fact('some_numbers', gd_data_type: 'INT')
451
+ d.add_fact('more_numbers')
452
+ d.add_date('opportunity_comitted', dataset: 'committed_on', format: 'yyyy/MM/dd')
453
+ end
454
+ end
455
+ expect(bp.valid?).to be_truthy
456
+ expect(bp.datasets.flat_map { |d| d.find_columns_by_type(:date) }.map { |a| a.format }).to eq ['yyyy/MM/dd']
457
+ end
458
+
459
+ describe '#remove' do
460
+ it 'can remove the anchor' do
461
+ bp = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
462
+ p.add_dataset("dataset.repos") do |d|
463
+ d.add_anchor("attr.repository", label_id: 'label.repo.name')
464
+ d.add_label('label.repository.name', title: 'Some attribute', reference: 'attr.repository')
465
+ end
466
+ end
467
+ expect(bp.datasets('dataset.repos').anchor.labels.count).to eq 1
468
+ bp.datasets('dataset.repos').anchor.remove!
469
+ expect(bp.datasets('dataset.repos').anchor.labels.count).to eq 0
470
+ end
471
+ end
472
+
473
+ describe '#move!' do
474
+ it 'can move attribute around' do
475
+ expect(@blueprint.datasets('dataset.repos').fields.count).to eq 4
476
+ expect(@blueprint.datasets('dataset.commits').fields.count).to eq 5
477
+ attr_before = @blueprint.datasets('dataset.repos').attributes('some_attr_id')
478
+ expect(attr_before).to_not be_nil
479
+ expect(@blueprint.datasets('dataset.commits').attributes('some_attr_id')).to be_nil
480
+ expect(attr_before.labels.first.dataset_blueprint.id).to eq 'dataset.repos'
481
+ @blueprint.move!('some_attr_id', 'dataset.repos', 'dataset.commits')
482
+
483
+ attr_after = @blueprint.datasets('dataset.commits').attributes('some_attr_id')
484
+
485
+ expect(@blueprint.datasets('dataset.repos').fields.count).to eq 2
486
+ expect(@blueprint.datasets('dataset.commits').fields.count).to eq 7
487
+ expect(@blueprint.datasets('dataset.repos').attributes('some_attr_id')).to be_nil
488
+ expect(attr_after).to_not be_nil
489
+ expect(attr_after.labels.first.dataset_blueprint.id).to eq 'dataset.commits'
490
+ end
491
+
492
+ it 'can move fact around' do
493
+ @blueprint.move!('fact.commits.lines_changed', 'dataset.commits', 'dataset.repos')
494
+ expect(@blueprint.datasets('dataset.commits').facts.count).to eq 0
495
+ expect(@blueprint.datasets('dataset.repos').facts.count).to eq 1
496
+ end
497
+
498
+ it 'crashes gracefully when nonexistent field is being moved' do
499
+ expect {
500
+ @blueprint.move!('nonexistent_field', 'dataset.commits', 'dataset.repos')
501
+ }.to raise_exception 'Column nonexistent_field cannot be found in dataset dataset.commits'
502
+ end
503
+
504
+ it 'crashes gracefully when datasets does not exist' do
505
+ expect {
506
+ @blueprint.move!('nonexistent_field', 'dataset.A', 'dataset.repos')
507
+ }.to raise_exception 'Dataset "dataset.A" could not be found'
508
+ end
509
+
510
+ it 'crashes gracefully when datasets does not exist' do
511
+ expect {
512
+ @blueprint.move!('nonexistent_field', 'dataset.commits', 'dataset.B')
513
+ }.to raise_exception 'Dataset "dataset.B" could not be found'
514
+ end
515
+ end
516
+
517
+ it 'should be able to refactor facts from attributes' do
518
+ blueprint = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
519
+ p.add_dataset('opportunities') do |d|
520
+ d.add_anchor('opportunities.id')
521
+ d.add_fact('opportunities.age')
522
+ d.add_fact('opportunities.amount')
523
+ d.add_attribute('opportunities.name')
524
+ d.add_label('label.opportunities.name', reference: 'opportunities.name')
525
+ d.add_attribute('opportunities.region')
526
+ d.add_label('label.opportunities.region', reference: 'opportunities.region')
527
+ d.add_reference('user_id', dataset: 'users')
528
+ d.add_reference('account_id', dataset: 'accounts')
529
+ end
530
+
531
+ p.add_dataset('users') do |d|
532
+ d.add_anchor('users.id')
533
+ d.add_attribute('users.name')
534
+ d.add_label('label.users.name', reference: 'users.name')
535
+ end
536
+
537
+ p.add_dataset('accounts') do |d|
538
+ d.add_anchor('accounts.id')
539
+ d.add_attribute('accounts.name')
540
+ d.add_label('label.accounts.region', reference: 'accounts.name')
541
+ end
542
+ end
543
+
544
+ refactored = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
545
+ p.add_dataset('opportunities') do |d|
546
+ d.add_anchor('opportunities.id')
547
+ d.add_fact('opportunities.age')
548
+ d.add_fact('opportunities.amount')
549
+ d.add_reference('user_id', dataset: 'users')
550
+ d.add_reference('accounts')
551
+ d.add_reference('accounts')
552
+ d.add_reference('opportunities_dim')
553
+ end
554
+
555
+ p.add_dataset('opportunities_dim') do |d|
556
+ d.add_anchor('vymysli_id')
557
+ d.add_label('label.vymysli_id', reference: 'vymysli_id')
558
+
559
+ d.add_attribute('opportunities.name')
560
+ d.add_label('label.opportunities.name', reference: 'opportunities.name')
561
+ d.add_attribute('opportunities.region')
562
+ d.add_label('label.opportunities.region', reference: 'opportunities.region')
563
+ end
564
+
565
+ p.add_dataset('users') do |d|
566
+ d.add_anchor('users.id')
567
+ d.add_attribute('users.name')
568
+ d.add_label('label.users.name', reference: 'users.name')
569
+ end
570
+
571
+ p.add_dataset('accounts') do |d|
572
+ d.add_anchor('accounts.id')
573
+ d.add_attribute('accounts.name')
574
+ d.add_label('label.accounts.region', reference: 'accounts.name')
575
+ end
576
+ end
577
+ expect(blueprint.refactor_split_df('opportunities')).to eq refactored
578
+ end
579
+
580
+ it 'should be able to refactor facts as a split into 2 datasets' do
581
+ blueprint = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
582
+ p.add_dataset('opportunities') do |d|
583
+ d.add_anchor('opportunities.id')
584
+ d.add_fact('opportunities.age')
585
+ d.add_fact('opportunities.amount')
586
+ d.add_attribute('opportunities.name')
587
+ d.add_label('label.opportunities.name', reference: 'opportunities.name')
588
+ d.add_attribute('opportunities.region')
589
+ d.add_label('label.opportunities.region', reference: 'opportunities.region')
590
+ d.add_reference('user_id', dataset: 'users')
591
+ d.add_reference('account_id', dataset: 'accounts')
592
+ end
593
+
594
+ p.add_dataset('users') do |d|
595
+ d.add_anchor('users.id')
596
+ d.add_attribute('users.name')
597
+ d.add_label('label.users.name', reference: 'users.name')
598
+ end
599
+
600
+ p.add_dataset('accounts') do |d|
601
+ d.add_anchor('accounts.id')
602
+ d.add_attribute('accounts.name')
603
+ d.add_label('label.accounts.region', reference: 'accounts.name')
604
+ end
605
+ end
606
+
607
+ refactored = GoodData::Model::ProjectBlueprint.build("my_bp") do |p|
608
+ p.add_dataset('opportunities') do |d|
609
+ d.add_anchor('opportunities.id')
610
+ d.add_fact('opportunities.amount')
611
+ d.add_attribute('opportunities.name')
612
+ d.add_label('label.opportunities.name', reference: 'opportunities.name')
613
+ d.add_attribute('opportunities.region')
614
+ d.add_label('label.opportunities.region', reference: 'opportunities.region')
615
+ d.add_reference('user_id', dataset: 'users')
616
+ d.add_reference('account_id', dataset: 'accounts')
617
+ end
618
+
619
+ p.add_dataset('users') do |d|
620
+ d.add_anchor('users.id')
621
+ d.add_attribute('users.name')
622
+ d.add_label('label.users.name', reference: 'users.name')
623
+ end
624
+
625
+ p.add_dataset('accounts') do |d|
626
+ d.add_anchor('accounts.id')
627
+ d.add_attribute('accounts.name')
628
+ d.add_label('label.accounts.region', reference: 'accounts.name')
629
+ end
630
+
631
+ p.add_dataset('opportunities_age_fact') do |d|
632
+ d.add_anchor('opportunities_age_fact.id')
633
+ d.add_fact('opportunities.age')
634
+ d.add_reference('user_id', dataset: 'users')
635
+ d.add_reference('account_id', dataset: 'accounts')
636
+ end
637
+ end
638
+
639
+ col_names = ['opportunities.age']
640
+ # that should be possible to express with #refactor_split_facts
641
+ expect(blueprint.refactor_split_facts('opportunities', col_names, 'opportunities_age_fact')).to eq refactored
642
+ end
643
+ end