gooddata-edge 0.6.27.edge

Sign up to get free protection for your applications and to get access to all the features.
Files changed (364) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +36 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +89 -0
  6. data/.yardopts +22 -0
  7. data/CHANGELOG.md +196 -0
  8. data/CLI.md +439 -0
  9. data/DEPENDENCIES.md +817 -0
  10. data/Gemfile +4 -0
  11. data/Guardfile +5 -0
  12. data/LICENSE +22 -0
  13. data/LICENSE.rb +5 -0
  14. data/README.md +75 -0
  15. data/Rakefile +179 -0
  16. data/TODO.md +32 -0
  17. data/authors.sh +4 -0
  18. data/bin/gooddata +7 -0
  19. data/dependency_decisions.yml +104 -0
  20. data/gooddata +9 -0
  21. data/gooddata.gemspec +63 -0
  22. data/lib/gooddata.rb +31 -0
  23. data/lib/gooddata/app/app.rb +16 -0
  24. data/lib/gooddata/bricks/base_downloader.rb +86 -0
  25. data/lib/gooddata/bricks/brick.rb +38 -0
  26. data/lib/gooddata/bricks/bricks.rb +15 -0
  27. data/lib/gooddata/bricks/middleware/aws_middleware.rb +29 -0
  28. data/lib/gooddata/bricks/middleware/base_middleware.rb +56 -0
  29. data/lib/gooddata/bricks/middleware/bench_middleware.rb +24 -0
  30. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +37 -0
  31. data/lib/gooddata/bricks/middleware/decode_params_middleware.rb +20 -0
  32. data/lib/gooddata/bricks/middleware/fs_download_middleware.rb +48 -0
  33. data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +36 -0
  34. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +39 -0
  35. data/lib/gooddata/bricks/middleware/logger_middleware.rb +29 -0
  36. data/lib/gooddata/bricks/middleware/middleware.rb +12 -0
  37. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +61 -0
  38. data/lib/gooddata/bricks/middleware/stdout_middleware.rb +23 -0
  39. data/lib/gooddata/bricks/middleware/twitter_middleware.rb +29 -0
  40. data/lib/gooddata/bricks/middleware/undot_params_middleware.rb +37 -0
  41. data/lib/gooddata/bricks/pipeline.rb +32 -0
  42. data/lib/gooddata/bricks/utils.rb +18 -0
  43. data/lib/gooddata/cli/cli.rb +27 -0
  44. data/lib/gooddata/cli/commands/auth_cmd.rb +29 -0
  45. data/lib/gooddata/cli/commands/domain_cmd.rb +28 -0
  46. data/lib/gooddata/cli/commands/project_cmd.rb +45 -0
  47. data/lib/gooddata/cli/hooks.rb +57 -0
  48. data/lib/gooddata/cli/shared.rb +61 -0
  49. data/lib/gooddata/cli/terminal.rb +20 -0
  50. data/lib/gooddata/client.rb +67 -0
  51. data/lib/gooddata/commands/api.rb +64 -0
  52. data/lib/gooddata/commands/auth.rb +107 -0
  53. data/lib/gooddata/commands/base.rb +12 -0
  54. data/lib/gooddata/commands/commands.rb +12 -0
  55. data/lib/gooddata/commands/datasets.rb +148 -0
  56. data/lib/gooddata/commands/datawarehouse.rb +20 -0
  57. data/lib/gooddata/commands/domain.rb +40 -0
  58. data/lib/gooddata/commands/process.rb +67 -0
  59. data/lib/gooddata/commands/project.rb +175 -0
  60. data/lib/gooddata/commands/projects.rb +20 -0
  61. data/lib/gooddata/commands/role.rb +36 -0
  62. data/lib/gooddata/commands/runners.rb +47 -0
  63. data/lib/gooddata/commands/scaffold.rb +69 -0
  64. data/lib/gooddata/commands/user.rb +39 -0
  65. data/lib/gooddata/connection.rb +127 -0
  66. data/lib/gooddata/core/core.rb +12 -0
  67. data/lib/gooddata/core/logging.rb +105 -0
  68. data/lib/gooddata/core/nil_logger.rb +23 -0
  69. data/lib/gooddata/core/project.rb +74 -0
  70. data/lib/gooddata/core/rest.rb +149 -0
  71. data/lib/gooddata/core/user.rb +20 -0
  72. data/lib/gooddata/data/data.rb +12 -0
  73. data/lib/gooddata/data/guesser.rb +122 -0
  74. data/lib/gooddata/exceptions/attr_element_not_found.rb +16 -0
  75. data/lib/gooddata/exceptions/command_failed.rb +11 -0
  76. data/lib/gooddata/exceptions/exceptions.rb +12 -0
  77. data/lib/gooddata/exceptions/execution_limit_exceeded.rb +13 -0
  78. data/lib/gooddata/exceptions/filter_maqlization.rb +16 -0
  79. data/lib/gooddata/exceptions/malformed_user.rb +15 -0
  80. data/lib/gooddata/exceptions/no_project_error.rb +15 -0
  81. data/lib/gooddata/exceptions/object_migration.rb +32 -0
  82. data/lib/gooddata/exceptions/project_not_found.rb +13 -0
  83. data/lib/gooddata/exceptions/segment_not_empty.rb +18 -0
  84. data/lib/gooddata/exceptions/uncomputable_report.rb +13 -0
  85. data/lib/gooddata/exceptions/user_in_different_domain.rb +15 -0
  86. data/lib/gooddata/exceptions/validation_error.rb +16 -0
  87. data/lib/gooddata/extensions/big_decimal.rb +17 -0
  88. data/lib/gooddata/extensions/enumerable.rb +39 -0
  89. data/lib/gooddata/extensions/extensions.rb +10 -0
  90. data/lib/gooddata/extensions/false.rb +15 -0
  91. data/lib/gooddata/extensions/hash.rb +38 -0
  92. data/lib/gooddata/extensions/nil.rb +15 -0
  93. data/lib/gooddata/extensions/numeric.rb +15 -0
  94. data/lib/gooddata/extensions/object.rb +27 -0
  95. data/lib/gooddata/extensions/symbol.rb +15 -0
  96. data/lib/gooddata/extensions/true.rb +15 -0
  97. data/lib/gooddata/extract.rb +21 -0
  98. data/lib/gooddata/goodzilla/goodzilla.rb +159 -0
  99. data/lib/gooddata/helpers/auth_helpers.rb +75 -0
  100. data/lib/gooddata/helpers/csv_helper.rb +61 -0
  101. data/lib/gooddata/helpers/data_helper.rb +116 -0
  102. data/lib/gooddata/helpers/global_helpers.rb +331 -0
  103. data/lib/gooddata/helpers/global_helpers_params.rb +172 -0
  104. data/lib/gooddata/helpers/helpers.rb +10 -0
  105. data/lib/gooddata/mixins/author.rb +26 -0
  106. data/lib/gooddata/mixins/content_getter.rb +15 -0
  107. data/lib/gooddata/mixins/content_property_reader.rb +17 -0
  108. data/lib/gooddata/mixins/content_property_writer.rb +17 -0
  109. data/lib/gooddata/mixins/contributor.rb +20 -0
  110. data/lib/gooddata/mixins/data_getter.rb +15 -0
  111. data/lib/gooddata/mixins/data_property_reader.rb +19 -0
  112. data/lib/gooddata/mixins/data_property_writer.rb +19 -0
  113. data/lib/gooddata/mixins/inspector.rb +53 -0
  114. data/lib/gooddata/mixins/is_attribute.rb +17 -0
  115. data/lib/gooddata/mixins/is_dimension.rb +17 -0
  116. data/lib/gooddata/mixins/is_fact.rb +17 -0
  117. data/lib/gooddata/mixins/is_label.rb +19 -0
  118. data/lib/gooddata/mixins/links.rb +15 -0
  119. data/lib/gooddata/mixins/md_finders.rb +77 -0
  120. data/lib/gooddata/mixins/md_grantees.rb +42 -0
  121. data/lib/gooddata/mixins/md_id_to_uri.rb +34 -0
  122. data/lib/gooddata/mixins/md_json.rb +15 -0
  123. data/lib/gooddata/mixins/md_lock.rb +87 -0
  124. data/lib/gooddata/mixins/md_object_id.rb +15 -0
  125. data/lib/gooddata/mixins/md_object_indexer.rb +64 -0
  126. data/lib/gooddata/mixins/md_object_query.rb +128 -0
  127. data/lib/gooddata/mixins/md_relations.rb +43 -0
  128. data/lib/gooddata/mixins/meta_getter.rb +17 -0
  129. data/lib/gooddata/mixins/meta_property_reader.rb +19 -0
  130. data/lib/gooddata/mixins/meta_property_writer.rb +19 -0
  131. data/lib/gooddata/mixins/mixins.rb +19 -0
  132. data/lib/gooddata/mixins/not_attribute.rb +17 -0
  133. data/lib/gooddata/mixins/not_exportable.rb +15 -0
  134. data/lib/gooddata/mixins/not_fact.rb +17 -0
  135. data/lib/gooddata/mixins/not_group.rb +17 -0
  136. data/lib/gooddata/mixins/not_label.rb +19 -0
  137. data/lib/gooddata/mixins/not_metric.rb +19 -0
  138. data/lib/gooddata/mixins/obj_id.rb +15 -0
  139. data/lib/gooddata/mixins/rest_getters.rb +17 -0
  140. data/lib/gooddata/mixins/rest_resource.rb +47 -0
  141. data/lib/gooddata/mixins/root_key_getter.rb +15 -0
  142. data/lib/gooddata/mixins/root_key_setter.rb +15 -0
  143. data/lib/gooddata/mixins/timestamps.rb +19 -0
  144. data/lib/gooddata/mixins/to_json.rb +11 -0
  145. data/lib/gooddata/mixins/uri_getter.rb +9 -0
  146. data/lib/gooddata/models/blueprint/anchor_field.rb +64 -0
  147. data/lib/gooddata/models/blueprint/attribute_field.rb +29 -0
  148. data/lib/gooddata/models/blueprint/blueprint.rb +11 -0
  149. data/lib/gooddata/models/blueprint/blueprint_field.rb +70 -0
  150. data/lib/gooddata/models/blueprint/dashboard_builder.rb +30 -0
  151. data/lib/gooddata/models/blueprint/dataset_blueprint.rb +449 -0
  152. data/lib/gooddata/models/blueprint/date_dimension.rb +14 -0
  153. data/lib/gooddata/models/blueprint/fact_field.rb +20 -0
  154. data/lib/gooddata/models/blueprint/label_field.rb +43 -0
  155. data/lib/gooddata/models/blueprint/project_blueprint.rb +746 -0
  156. data/lib/gooddata/models/blueprint/project_builder.rb +83 -0
  157. data/lib/gooddata/models/blueprint/reference_field.rb +43 -0
  158. data/lib/gooddata/models/blueprint/schema_blueprint.rb +160 -0
  159. data/lib/gooddata/models/blueprint/schema_builder.rb +89 -0
  160. data/lib/gooddata/models/blueprint/to_manifest.rb +181 -0
  161. data/lib/gooddata/models/blueprint/to_wire.rb +154 -0
  162. data/lib/gooddata/models/client.rb +182 -0
  163. data/lib/gooddata/models/client_synchronization_result.rb +31 -0
  164. data/lib/gooddata/models/client_synchronization_result_details.rb +41 -0
  165. data/lib/gooddata/models/datawarehouse.rb +92 -0
  166. data/lib/gooddata/models/domain.rb +479 -0
  167. data/lib/gooddata/models/execution.rb +115 -0
  168. data/lib/gooddata/models/execution_detail.rb +81 -0
  169. data/lib/gooddata/models/from_wire.rb +160 -0
  170. data/lib/gooddata/models/invitation.rb +75 -0
  171. data/lib/gooddata/models/links.rb +50 -0
  172. data/lib/gooddata/models/membership.rb +441 -0
  173. data/lib/gooddata/models/metadata.rb +272 -0
  174. data/lib/gooddata/models/metadata/attribute.rb +134 -0
  175. data/lib/gooddata/models/metadata/dashboard.rb +108 -0
  176. data/lib/gooddata/models/metadata/dashboard/dashboard_item.rb +76 -0
  177. data/lib/gooddata/models/metadata/dashboard/filter_apply_item.rb +37 -0
  178. data/lib/gooddata/models/metadata/dashboard/filter_item.rb +64 -0
  179. data/lib/gooddata/models/metadata/dashboard/geo_chart_item.rb +56 -0
  180. data/lib/gooddata/models/metadata/dashboard/headline_item.rb +56 -0
  181. data/lib/gooddata/models/metadata/dashboard/iframe_item.rb +46 -0
  182. data/lib/gooddata/models/metadata/dashboard/report_item.rb +92 -0
  183. data/lib/gooddata/models/metadata/dashboard/text_item.rb +55 -0
  184. data/lib/gooddata/models/metadata/dashboard_tab.rb +141 -0
  185. data/lib/gooddata/models/metadata/dataset.rb +64 -0
  186. data/lib/gooddata/models/metadata/dimension.rb +54 -0
  187. data/lib/gooddata/models/metadata/fact.rb +44 -0
  188. data/lib/gooddata/models/metadata/label.rb +128 -0
  189. data/lib/gooddata/models/metadata/metadata.rb +12 -0
  190. data/lib/gooddata/models/metadata/metric.rb +198 -0
  191. data/lib/gooddata/models/metadata/report.rb +247 -0
  192. data/lib/gooddata/models/metadata/report_definition.rb +264 -0
  193. data/lib/gooddata/models/metadata/scheduled_mail.rb +274 -0
  194. data/lib/gooddata/models/metadata/scheduled_mail/dashboard_attachment.rb +62 -0
  195. data/lib/gooddata/models/metadata/scheduled_mail/report_attachment.rb +64 -0
  196. data/lib/gooddata/models/metadata/variable.rb +91 -0
  197. data/lib/gooddata/models/model.rb +282 -0
  198. data/lib/gooddata/models/models.rb +12 -0
  199. data/lib/gooddata/models/module_constants.rb +31 -0
  200. data/lib/gooddata/models/process.rb +316 -0
  201. data/lib/gooddata/models/profile.rb +426 -0
  202. data/lib/gooddata/models/project.rb +1514 -0
  203. data/lib/gooddata/models/project_creator.rb +126 -0
  204. data/lib/gooddata/models/project_metadata.rb +67 -0
  205. data/lib/gooddata/models/project_role.rb +79 -0
  206. data/lib/gooddata/models/report_data_result.rb +266 -0
  207. data/lib/gooddata/models/schedule.rb +518 -0
  208. data/lib/gooddata/models/segment.rb +201 -0
  209. data/lib/gooddata/models/tab_builder.rb +27 -0
  210. data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +76 -0
  211. data/lib/gooddata/models/user_filters/user_filter.rb +100 -0
  212. data/lib/gooddata/models/user_filters/user_filter_builder.rb +512 -0
  213. data/lib/gooddata/models/user_filters/user_filters.rb +13 -0
  214. data/lib/gooddata/models/user_filters/variable_user_filter.rb +31 -0
  215. data/lib/gooddata/models/user_group.rb +241 -0
  216. data/lib/gooddata/rest/README.md +37 -0
  217. data/lib/gooddata/rest/client.rb +389 -0
  218. data/lib/gooddata/rest/connection.rb +765 -0
  219. data/lib/gooddata/rest/object.rb +69 -0
  220. data/lib/gooddata/rest/object_factory.rb +76 -0
  221. data/lib/gooddata/rest/resource.rb +27 -0
  222. data/lib/gooddata/rest/rest.rb +24 -0
  223. data/lib/gooddata/version.rb +23 -0
  224. data/lib/templates/bricks/brick.rb.erb +7 -0
  225. data/lib/templates/bricks/main.rb.erb +5 -0
  226. data/lib/templates/project/Goodfile.erb +4 -0
  227. data/lib/templates/project/data/commits.csv +4 -0
  228. data/lib/templates/project/data/devs.csv +4 -0
  229. data/lib/templates/project/data/repos.csv +3 -0
  230. data/lib/templates/project/model/model.rb.erb +20 -0
  231. data/spec/bricks/bricks_spec.rb +112 -0
  232. data/spec/bricks/default-config.json +8 -0
  233. data/spec/data/.gooddata +4 -0
  234. data/spec/data/blueprints/additional_dataset_module.json +32 -0
  235. data/spec/data/blueprints/big_blueprint_not_pruned.json +2079 -0
  236. data/spec/data/blueprints/invalid_blueprint.json +103 -0
  237. data/spec/data/blueprints/m_n_model.json +104 -0
  238. data/spec/data/blueprints/model_module.json +25 -0
  239. data/spec/data/blueprints/test_blueprint.json +38 -0
  240. data/spec/data/blueprints/test_project_model_spec.json +106 -0
  241. data/spec/data/cc/data/source/commits.csv +4 -0
  242. data/spec/data/cc/data/source/devs.csv +4 -0
  243. data/spec/data/cc/data/source/repos.csv +3 -0
  244. data/spec/data/cc/devel.prm +0 -0
  245. data/spec/data/cc/graph/graph.grf +11 -0
  246. data/spec/data/cc/workspace.prm +19 -0
  247. data/spec/data/column_based_permissions.csv +7 -0
  248. data/spec/data/column_based_permissions2.csv +6 -0
  249. data/spec/data/gd_gse_data_blueprint.json +1371 -0
  250. data/spec/data/gd_gse_data_manifest.json +1424 -0
  251. data/spec/data/gd_gse_data_model.json +1772 -0
  252. data/spec/data/gooddata_version_process/gooddata_version.rb +9 -0
  253. data/spec/data/gooddata_version_process/gooddata_version.zip +0 -0
  254. data/spec/data/hello_world_process/hello_world.rb +9 -0
  255. data/spec/data/hello_world_process/hello_world.zip +0 -0
  256. data/spec/data/line_based_permissions.csv +3 -0
  257. data/spec/data/manifests/test_blueprint.json +32 -0
  258. data/spec/data/manifests/test_project.json +107 -0
  259. data/spec/data/reports/left_attr_report.json +108 -0
  260. data/spec/data/reports/metric_only_one_line.json +83 -0
  261. data/spec/data/reports/report_1.json +197 -0
  262. data/spec/data/reports/top_attr_report.json +108 -0
  263. data/spec/data/ruby_params_process/ruby_params.rb +9 -0
  264. data/spec/data/ruby_process/deep_files/deep_stuff.txt +1 -0
  265. data/spec/data/ruby_process/process.rb +8 -0
  266. data/spec/data/ruby_process/stuff.txt +1 -0
  267. data/spec/data/superfluous_titles_view.json +81 -0
  268. data/spec/data/test-ci-data.csv +2 -0
  269. data/spec/data/users.csv +12 -0
  270. data/spec/data/wire_models/model_view.json +1775 -0
  271. data/spec/data/wire_models/nu_model.json +3046 -0
  272. data/spec/data/wire_models/test_blueprint.json +63 -0
  273. data/spec/data/wire_test_project.json +150 -0
  274. data/spec/environment/default.rb +41 -0
  275. data/spec/environment/develop.rb +31 -0
  276. data/spec/environment/environment.rb +18 -0
  277. data/spec/environment/hotfix.rb +21 -0
  278. data/spec/environment/production.rb +35 -0
  279. data/spec/environment/release.rb +21 -0
  280. data/spec/environment/staging.rb +30 -0
  281. data/spec/environment/staging_3.rb +36 -0
  282. data/spec/helpers/blueprint_helper.rb +26 -0
  283. data/spec/helpers/cli_helper.rb +36 -0
  284. data/spec/helpers/connection_helper.rb +41 -0
  285. data/spec/helpers/crypto_helper.rb +17 -0
  286. data/spec/helpers/csv_helper.rb +18 -0
  287. data/spec/helpers/process_helper.rb +33 -0
  288. data/spec/helpers/project_helper.rb +59 -0
  289. data/spec/helpers/schedule_helper.rb +31 -0
  290. data/spec/helpers/spec_helper.rb +15 -0
  291. data/spec/integration/blueprint_updates_spec.rb +101 -0
  292. data/spec/integration/blueprint_with_grain_spec.rb +72 -0
  293. data/spec/integration/clients_spec.rb +134 -0
  294. data/spec/integration/command_datawarehouse_spec.rb +39 -0
  295. data/spec/integration/command_projects_spec.rb +32 -0
  296. data/spec/integration/create_from_template_spec.rb +22 -0
  297. data/spec/integration/create_project_spec.rb +24 -0
  298. data/spec/integration/date_dim_switch_spec.rb +142 -0
  299. data/spec/integration/deprecated_load_spec.rb +58 -0
  300. data/spec/integration/full_process_schedule_spec.rb +298 -0
  301. data/spec/integration/full_project_spec.rb +569 -0
  302. data/spec/integration/over_to_user_filters_spec.rb +94 -0
  303. data/spec/integration/partial_md_export_import_spec.rb +42 -0
  304. data/spec/integration/project_spec.rb +264 -0
  305. data/spec/integration/rest_spec.rb +213 -0
  306. data/spec/integration/schedule_spec.rb +626 -0
  307. data/spec/integration/segments_spec.rb +141 -0
  308. data/spec/integration/user_filters_spec.rb +290 -0
  309. data/spec/integration/user_group_spec.rb +127 -0
  310. data/spec/integration/variables_spec.rb +188 -0
  311. data/spec/logging_in_logging_out_spec.rb +93 -0
  312. data/spec/spec_helper.rb +95 -0
  313. data/spec/unit/bricks/bricks_spec.rb +35 -0
  314. data/spec/unit/bricks/middleware/aws_middelware_spec.rb +51 -0
  315. data/spec/unit/bricks/middleware/bench_middleware_spec.rb +15 -0
  316. data/spec/unit/bricks/middleware/bulk_salesforce_middleware_spec.rb +15 -0
  317. data/spec/unit/bricks/middleware/gooddata_middleware_spec.rb +15 -0
  318. data/spec/unit/bricks/middleware/logger_middleware_spec.rb +15 -0
  319. data/spec/unit/bricks/middleware/restforce_middleware_spec.rb +15 -0
  320. data/spec/unit/bricks/middleware/stdout_middleware_spec.rb +15 -0
  321. data/spec/unit/bricks/middleware/twitter_middleware_spec.rb +15 -0
  322. data/spec/unit/cli/cli_spec.rb +17 -0
  323. data/spec/unit/cli/commands/cmd_auth_spec.rb +17 -0
  324. data/spec/unit/commands/command_projects_spec.rb +22 -0
  325. data/spec/unit/core/connection_spec.rb +57 -0
  326. data/spec/unit/core/logging_spec.rb +133 -0
  327. data/spec/unit/core/nil_logger_spec.rb +13 -0
  328. data/spec/unit/core/project_spec.rb +54 -0
  329. data/spec/unit/extensions/hash_spec.rb +23 -0
  330. data/spec/unit/godzilla/goodzilla_spec.rb +78 -0
  331. data/spec/unit/helpers/csv_helper_spec.rb +22 -0
  332. data/spec/unit/helpers/data_helper_spec.rb +61 -0
  333. data/spec/unit/helpers/global_helpers_spec.rb +111 -0
  334. data/spec/unit/helpers_spec.rb +86 -0
  335. data/spec/unit/models/blueprint/attributes_spec.rb +29 -0
  336. data/spec/unit/models/blueprint/dataset_spec.rb +121 -0
  337. data/spec/unit/models/blueprint/labels_spec.rb +44 -0
  338. data/spec/unit/models/blueprint/project_blueprint_spec.rb +648 -0
  339. data/spec/unit/models/blueprint/reference_spec.rb +29 -0
  340. data/spec/unit/models/blueprint/schema_builder_spec.rb +38 -0
  341. data/spec/unit/models/blueprint/to_wire_spec.rb +174 -0
  342. data/spec/unit/models/domain_spec.rb +144 -0
  343. data/spec/unit/models/execution_spec.rb +108 -0
  344. data/spec/unit/models/from_wire_spec.rb +296 -0
  345. data/spec/unit/models/invitation_spec.rb +17 -0
  346. data/spec/unit/models/membership_spec.rb +132 -0
  347. data/spec/unit/models/metadata_spec.rb +104 -0
  348. data/spec/unit/models/metric_spec.rb +117 -0
  349. data/spec/unit/models/model_spec.rb +82 -0
  350. data/spec/unit/models/params_spec.rb +118 -0
  351. data/spec/unit/models/profile_spec.rb +215 -0
  352. data/spec/unit/models/project_creator_spec.rb +127 -0
  353. data/spec/unit/models/project_role_spec.rb +94 -0
  354. data/spec/unit/models/project_spec.rb +162 -0
  355. data/spec/unit/models/report_result_data_spec.rb +199 -0
  356. data/spec/unit/models/schedule_spec.rb +418 -0
  357. data/spec/unit/models/to_manifest_spec.rb +63 -0
  358. data/spec/unit/models/unit_project_spec.rb +125 -0
  359. data/spec/unit/models/user_filters_spec.rb +95 -0
  360. data/spec/unit/models/variable_spec.rb +265 -0
  361. data/spec/unit/rest/polling_spec.rb +89 -0
  362. data/spec/unit/rest/resource_spec.rb +10 -0
  363. data/yard-server.sh +3 -0
  364. metadata +1125 -0
@@ -0,0 +1,201 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2015 GoodData Corporation. All rights reserved.
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ require_relative './client'
8
+ require_relative './domain'
9
+ require_relative '../models/client_synchronization_result'
10
+
11
+ require_relative '../mixins/data_property_reader'
12
+ require_relative '../mixins/links'
13
+ require_relative '../mixins/rest_resource'
14
+ require_relative '../mixins/uri_getter'
15
+
16
+ module GoodData
17
+ class Segment < Rest::Resource
18
+ SYNCHRONIZE_URI = '/gdc/domains/%s/segments/%s/synchronizeClients'
19
+
20
+ attr_accessor :domain
21
+
22
+ data_property_reader 'id'
23
+
24
+ include Mixin::Links
25
+ include Mixin::UriGetter
26
+
27
+ SEGMENT_TEMPLATE = {
28
+ :segment => {
29
+ :id => nil,
30
+ :masterProject => nil
31
+ }
32
+ }
33
+
34
+ class << self
35
+ # Returns list of all segments or a particular segment
36
+ #
37
+ # @param id [String|Symbol] Uri of the segment required or :all for all segments.
38
+ # @return [Array<GoodData::Segment>] List of segments for a particular domain
39
+ def [](id, opts = {})
40
+ domain = opts[:domain]
41
+ fail ArgumentError, 'No :domain specified' if domain.nil?
42
+
43
+ client = domain.client
44
+ fail ArgumentError, 'No client specified' if client.nil?
45
+
46
+ if id == :all
47
+ GoodData::Segment.all(opts)
48
+ else
49
+ result = client.get(domain.segments_uri + "/segments/#{CGI.escape(id)}")
50
+ client.create(GoodData::Segment, result.merge('domain' => domain))
51
+ end
52
+ end
53
+
54
+ # Returns list of all segments for domain
55
+ #
56
+ # @param opts [Hash] Options. Should contain :domain for which you want to get the segments.
57
+ # @return [Array<GoodData::Segment>] List of segments for a particular domain
58
+ def all(opts = {})
59
+ domain = opts[:domain]
60
+ fail 'Domain has to be passed in options' unless domain
61
+ client = domain.client
62
+
63
+ results = client.get(domain.segments_uri + '/segments')
64
+ GoodData::Helpers.get_path(results, %w(segments items)).map { |i| client.create(GoodData::Segment, i.merge('domain' => domain)) }
65
+ end
66
+
67
+ # Creates new segment from parameters passed
68
+ #
69
+ # @param data [Hash] Data for segment namely :segment_id and :master_project is accepted. Master_project can be given as either a PID or a Project instance
70
+ # @param options [Hash] Trigger of schedule. Can be cron string or reference to another schedule.
71
+ # @return [GoodData::Segment] New Segment instance
72
+ def create(data = {}, options = {})
73
+ segment_id = data[:segment_id]
74
+ fail 'Custom ID has to be provided' if segment_id.blank?
75
+ client = options[:client]
76
+ segment = client.create(GoodData::Segment, GoodData::Helpers.deep_stringify_keys(SEGMENT_TEMPLATE).merge('domain' => options[:domain]))
77
+ segment.tap do |s|
78
+ s.segment_id = segment_id
79
+ s.master_project = data[:master_project]
80
+ end
81
+ end
82
+ end
83
+
84
+ def initialize(data)
85
+ super
86
+ @domain = data.delete('domain')
87
+ @json = data
88
+ end
89
+
90
+ # Segment id getter for the Segment. Called segment_id since id is a reserved word in ruby world
91
+ #
92
+ # @return [String] Segment id
93
+ def segment_id
94
+ data['id']
95
+ end
96
+
97
+ # Segment id setter for the Segment. Called segment_id since id is a reserved word in ruby world
98
+ #
99
+ # @param an_id [String] Id of the segment.
100
+ # @return [String] Segment id
101
+ def segment_id=(an_id)
102
+ data['id'] = an_id
103
+ self
104
+ end
105
+
106
+ # Master project id getter for the Segment.
107
+ #
108
+ # @return [String] Segment id
109
+ def master_project=(a_project)
110
+ data['masterProject'] = a_project.respond_to?(:uri) ? a_project.uri : a_project
111
+ self
112
+ end
113
+
114
+ alias_method :master=, :master_project=
115
+
116
+ # Master project id getter for the Segment.
117
+ #
118
+ # @return [String] Project uri
119
+ def master_project_id
120
+ GoodData::Helpers.last_uri_part(master_project_uri)
121
+ end
122
+
123
+ alias_method :master_id, :master_project_id
124
+
125
+ # Master project uri getter for the Segment.
126
+ #
127
+ # @return [String] Project uri
128
+ def master_project_uri
129
+ data['masterProject']
130
+ end
131
+
132
+ alias_method :master_uri, :master_project_uri
133
+
134
+ # Master project getter for the Segment. It returns the instance not just the URI
135
+ #
136
+ # @return [GoodData::Project] Project associated with the segment
137
+ def master_project
138
+ client.projects(master_project_uri)
139
+ end
140
+
141
+ alias_method :master, :master_project
142
+
143
+ def create_client(data)
144
+ client = GoodData::Client.create(data, segment: self)
145
+ client.save
146
+ end
147
+
148
+ # Returns all the clients associated with the segment. Since this is potentially paging operation it returns an Enumerable.
149
+ #
150
+ # @return [Enumerable] Clients associated with the segment
151
+ def clients(tenant_id = :all)
152
+ GoodData::Client[tenant_id, domain: domain, segment: self]
153
+ end
154
+
155
+ # Creates or updates a segment instance on the API.
156
+ #
157
+ # @return [GoodData::Segment] Segment instance
158
+ def save
159
+ if uri
160
+ client.put(uri, json)
161
+ else
162
+ res = client.post(domain.segments_uri + '/segments', json)
163
+ @json = res
164
+ end
165
+ self
166
+ end
167
+
168
+ # Runs async process that walks thorugh segments and provisions projects if necessary.
169
+ #
170
+ # @return [Array] Returns array of results
171
+ def synchronize_clients
172
+ sync_uri = SYNCHRONIZE_URI % [domain.obj_id, id]
173
+ res = client.post sync_uri, nil
174
+
175
+ # wait until the instance is created
176
+ res = client.poll_on_response(res['asyncTask']['links']['poll'], :sleep_interval => 1) do |r|
177
+ r['synchronizationResult'].nil?
178
+ end
179
+
180
+ client.create(ClientSynchronizationResult, res)
181
+ end
182
+
183
+ # Deletes a segment instance on the API.
184
+ #
185
+ # @return [GoodData::Segment] Segment instance
186
+ def delete(options = {})
187
+ force = options[:force] == true ? true : false
188
+ clients.peach(&:delete) if force
189
+ client.delete(uri) if uri
190
+ self
191
+ rescue RestClient::BadRequest => e
192
+ payload = GoodData::Helpers.parse_http_exception(e)
193
+ case GoodData::Helpers.get_path(payload)
194
+ when 'gdc.c4.conflict.domain.segment.contains_clients'
195
+ throw SegmentNotEmpty
196
+ else
197
+ raise e
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2015 GoodData Corporation. All rights reserved.
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ module GoodData
8
+ module Model
9
+ class TabBuilder
10
+ def initialize(title)
11
+ @title = title
12
+ @stuff = []
13
+ end
14
+
15
+ def add_report(options = {})
16
+ @stuff << { :type => :report }.merge(options)
17
+ end
18
+
19
+ def to_hash
20
+ {
21
+ :title => @title,
22
+ :items => @stuff
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2015 GoodData Corporation. All rights reserved.
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ require_relative 'user_filter'
8
+
9
+ module GoodData
10
+ class MandatoryUserFilter < UserFilter
11
+ class << self
12
+ def [](id, options = { client: GoodData.connection, project: GoodData.project })
13
+ if id == :all
14
+ all(options)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def all(options = { client: GoodData.connection, project: GoodData.project })
21
+ c = client(options)
22
+ project = options[:project]
23
+ filters = query('userFilter', nil, options)
24
+ count = 1_000
25
+ offset = 0
26
+ user_lookup = {}
27
+ loop do
28
+ result = c.get("/gdc/md/#{project.pid}/userfilters?count=#{count}&offset=#{offset}")
29
+ result['userFilters']['items'].each do |item|
30
+ item['userFilters'].each do |f|
31
+ user_lookup[f] = item['user']
32
+ end
33
+ end
34
+ break if result['userFilters']['length'] < offset
35
+ offset += count
36
+ end
37
+ mufs = filters.map do |filter_data|
38
+ payload = {
39
+ 'expression' => filter_data['userFilter']['content']['expression'],
40
+ 'related' => user_lookup[filter_data['userFilter']['meta']['uri']],
41
+ 'level' => :user,
42
+ 'type' => :filter,
43
+ 'uri' => filter_data['userFilter']['meta']['uri']
44
+ }
45
+ c.create(GoodData::MandatoryUserFilter, payload, project: project)
46
+ end
47
+ mufs.enum_for
48
+ end
49
+
50
+ def count(options = { client: GoodData.connection, project: GoodData.project })
51
+ c = client(options)
52
+ project = options[:project]
53
+ c.get(project.md['query'] + '/userfilters/')['query']['entries'].count
54
+ end
55
+ end
56
+
57
+ # Creates or updates the mandatory user filter on the server
58
+ #
59
+ # @return [GoodData::MandatoryUserFilter]
60
+ def save
61
+ data = {
62
+ 'userFilter' => {
63
+ 'content' => {
64
+ 'expression' => expression
65
+ },
66
+ 'meta' => {
67
+ 'category' => 'userFilter',
68
+ 'title' => related_uri
69
+ }
70
+ }
71
+ }
72
+ res = client.post(project.md['obj'], data)
73
+ @json[:uri] = res['uri']
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,100 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2015 GoodData Corporation. All rights reserved.
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ module GoodData
8
+ class UserFilter < GoodData::MdObject
9
+ def initialize(data)
10
+ @dirty = false
11
+ @json = GoodData::Helpers.symbolize_keys(data)
12
+ end
13
+
14
+ def ==(other)
15
+ other.class == self.class && other.related_uri == related_uri && other.expression == expression
16
+ end
17
+ alias_method :eql?, :==
18
+
19
+ def hash
20
+ [related_uri, expression].hash
21
+ end
22
+
23
+ # Returns the uri of the object this filter is related to. It can be either project or a user
24
+ #
25
+ # @return [String] Uri of related object
26
+ def related_uri
27
+ @json[:related]
28
+ end
29
+
30
+ # Returns the the object of this filter is related to. It can be either project or a user
31
+ #
32
+ # @return [GoodData::Project | GoodData::Profile] Related object
33
+ def related
34
+ uri = related_uri
35
+ level == :project ? client.projects(uri) : client.create(GoodData::Profile, client.get(uri))
36
+ end
37
+
38
+ # Returns the the object of this filter is related to. It can be either project or a user
39
+ #
40
+ # @return [GoodData::Project | GoodData::Profile] Related object
41
+ def variable
42
+ uri = @json[:prompt]
43
+ GoodData::Variable[uri, client: client, project: project]
44
+ end
45
+
46
+ # Returns the level this filter is applied on. Either project or user. This is useful for
47
+ # variables where you can have both types. Project level is the default that is applied when
48
+ # user does not have assigned a value. When both user and project value and user value is missing
49
+ # value, you will get 'uncomputable report' errors.
50
+ #
51
+ # @return [Symbol] level on which this filter will be applied
52
+ def level
53
+ @json[:level].to_sym
54
+ end
55
+
56
+ # Returns the MAQL expression of the filter
57
+ #
58
+ # @return [String] MAQL expression
59
+ def expression
60
+ @json[:expression]
61
+ end
62
+
63
+ # Allows to set the MAQL expression of the filter
64
+ #
65
+ # @param expression [String] MAQL expression
66
+ # @return [String] MAQL expression
67
+ def expression=(expression)
68
+ @dirty = true
69
+ @json[:expression] = expression
70
+ end
71
+
72
+ # Gives you URI of the filter
73
+ #
74
+ # @return [String]
75
+ def uri
76
+ @json[:uri]
77
+ end
78
+
79
+ # Allows to set URI of the filter
80
+ #
81
+ # @return [String]
82
+ def uri=(uri)
83
+ @json[:uri] = uri
84
+ end
85
+
86
+ # Returns pretty version of the expression
87
+ #
88
+ # @return [String]
89
+ def pretty_expression
90
+ SmallGoodZilla.pretty_print(expression, client: client, project: project)
91
+ end
92
+
93
+ # Deletes the filter from the server
94
+ #
95
+ # @return [String]
96
+ def delete
97
+ client.delete(uri)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,512 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2015 GoodData Corporation. All rights reserved.
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ module GoodData
8
+ module UserFilterBuilder
9
+ # Main Entry function. Gets values and processes them to get filters
10
+ # that are suitable for other function to process.
11
+ # Values can be read from file or provided inline as an array.
12
+ # The results are then preprocessed. It is possible to provide
13
+ # multiple values for an attribute tries to deduplicate the values if
14
+ # they are not unique. Allows for setting over/to filters and allows for
15
+ # setting up filters from multiple columns. It is specially designed so many
16
+ # aspects of configuration are modifiable so you do have to preprocess the
17
+ # data as little as possible ideally you should be able to use data that
18
+ # came directly from the source system and that are intended for use in
19
+ # other parts of ETL.
20
+ #
21
+ # @param options [Hash]
22
+ # @return [Boolean]
23
+ def self.get_filters(file, options = {})
24
+ values = get_values(file, options)
25
+ reduce_results(values)
26
+ end
27
+
28
+ # Function that tells you if the file should be read line_wise. This happens
29
+ # if you have only one label defined and you do not have columns specified
30
+ #
31
+ # @param options [Hash]
32
+ # @return [Boolean]
33
+ def self.row_based?(options = {})
34
+ options[:labels].count == 1 && !options[:labels].first.key?(:column)
35
+ end
36
+
37
+ def self.read_file(file, options = {})
38
+ memo = {}
39
+ params = row_based?(options) ? { headers: false } : { headers: true }
40
+
41
+ CSV.foreach(file, params.merge(return_headers: false)) do |e|
42
+ key, data = process_line(e, options)
43
+ memo[key] = [] unless memo.key?(key)
44
+ memo[key].concat(data)
45
+ end
46
+ memo
47
+ end
48
+
49
+ # Processes a line from source file. It is processed in
50
+ # 2 formats. First mode is column_based.
51
+ # It means getting all specific columns.
52
+ # These are specified either by index or name. Multiple
53
+ # values are provided by several rows for the same user
54
+ #
55
+ # Second mode is row based which means there are no headers
56
+ # and number of columns can be variable. Each row specifies multiple
57
+ # values for one user. It is implied that the file provides values
58
+ # for just one label
59
+ #
60
+ # @param options [Hash]
61
+ # @return
62
+ def self.process_line(line, options = {})
63
+ index = options[:user_column] || 0
64
+ login = line[index]
65
+
66
+ results = options[:labels].mapcat do |label|
67
+ column = label[:column] || Range.new(1, -1)
68
+ values = column.is_a?(Range) ? line.slice(column) : [line[column]]
69
+ [create_filter(label, values.compact)]
70
+ end
71
+ [login, results]
72
+ end
73
+
74
+ def self.create_filter(label, values)
75
+ {
76
+ :label => label[:label],
77
+ :values => values,
78
+ :over => label[:over],
79
+ :to => label[:to]
80
+ }
81
+ end
82
+
83
+ # Processes values in a map reduce way so the result is as readable as possible and
84
+ # poses minimal impact on the API
85
+ #
86
+ # @param options [Hash]
87
+ # @return [Array]
88
+ def self.reduce_results(data)
89
+ data.map { |k, v| { login: k, filters: UserFilterBuilder.collect_labels(v) } }
90
+ end
91
+
92
+ # Groups the values by particular label. And passes each group to deduplication
93
+ # @param options [Hash]
94
+ # @return
95
+ def self.collect_labels(data)
96
+ data.group_by { |x| [x[:label], x[:over], x[:to]] }.map { |l, v| { label: l[0], over: l[1], to: l[2], values: UserFilterBuilder.collect_values(v) } }
97
+ end
98
+
99
+ # Collects specific values and deduplicates if necessary
100
+ def self.collect_values(data)
101
+ data.mapcat do |e|
102
+ e[:values]
103
+ end.uniq
104
+ end
105
+
106
+ def self.create_cache(data, key)
107
+ data.reduce({}) do |a, e|
108
+ a[e.send(key)] = e
109
+ a
110
+ end
111
+ end
112
+
113
+ def self.get_missing_users(filters, options = {})
114
+ users_cache = options[:users_cache]
115
+ filters.reject { |u| users_cache.key?(u[:login]) }
116
+ end
117
+
118
+ def self.verify_existing_users(filters, options = {})
119
+ users_must_exist = options[:users_must_exist] == false ? false : true
120
+ users_cache = options[:users_cache]
121
+ domain = options[:domain]
122
+
123
+ if users_must_exist
124
+ missing_users = filters.reject do |u|
125
+ next true if users_cache.key?(u[:login])
126
+ domain_user = (domain && domain.find_user_by_login(u[:login]))
127
+ users_cache[domain_user.login] = domain_user if domain_user
128
+ next true if domain_user
129
+ false
130
+ end
131
+ fail "#{missing_users.count} users are not part of the project and variable cannot be resolved since :users_must_exist is set to true (#{missing_users.join(', ')})" unless missing_users.empty?
132
+ end
133
+ end
134
+
135
+ def self.create_label_cache(result, options = {})
136
+ project = options[:project]
137
+
138
+ result.reduce({}) do |a, e|
139
+ e[:filters].map do |filter|
140
+ a[filter[:label]] = project.labels(filter[:label]) unless a.key?(filter[:label])
141
+ end
142
+ a
143
+ end
144
+ end
145
+
146
+ def self.create_lookups_cache(small_labels)
147
+ small_labels.reduce({}) do |a, e|
148
+ lookup = e.values(:limit => 1_000_000).reduce({}) do |a1, e1|
149
+ a1[e1[:value]] = e1[:uri]
150
+ a1
151
+ end
152
+ a[e.uri] = lookup
153
+ a
154
+ end
155
+ end
156
+
157
+ def self.create_attrs_cache(filters, options = {})
158
+ project = options[:project]
159
+
160
+ labels = filters.flat_map do |f|
161
+ f[:filters]
162
+ end
163
+
164
+ over_cache = labels.reduce({}) do |a, e|
165
+ a[e[:over]] = e[:over]
166
+ a
167
+ end
168
+ to_cache = labels.reduce({}) do |a, e|
169
+ a[e[:to]] = e[:to]
170
+ a
171
+ end
172
+ cache = over_cache.merge(to_cache)
173
+ attr_cache = {}
174
+ cache.each_pair do |k, v|
175
+ begin
176
+ attr_cache[k] = project.attributes(v)
177
+ rescue
178
+ nil
179
+ end
180
+ end
181
+ attr_cache
182
+ end
183
+
184
+ # Walks over provided labels and picks those that have fewer than certain amount of values
185
+ # This tries to balance for speed when working with small datasets (like users)
186
+ # so it precaches the values and still be able to function for larger ones even
187
+ # though that would mean tons of requests
188
+ def self.get_small_labels(labels_cache)
189
+ labels_cache.values.select { |label| label && label.values_count && label.values_count < 100_000 }
190
+ end
191
+
192
+ # Creates a MAQL expression(s) based on the filter defintion.
193
+ # Takes the filter definition looks up any necessary values and provides API executable MAQL
194
+ def self.create_expression(filter, labels_cache, lookups_cache, attr_cache, options = {})
195
+ errors = []
196
+ values = filter[:values]
197
+ label = labels_cache[filter[:label]]
198
+ element_uris = values.map do |v|
199
+ begin
200
+ if lookups_cache.key?(label.uri)
201
+ if lookups_cache[label.uri].key?(v)
202
+ lookups_cache[label.uri][v]
203
+ else
204
+ fail
205
+ end
206
+ else
207
+ label.find_value_uri(v)
208
+ end
209
+ rescue
210
+ errors << {
211
+ type: :error,
212
+ label: label.title,
213
+ value: v
214
+ }
215
+ nil
216
+ end
217
+ end
218
+ expression = if element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :muf
219
+ '1 <> 1'
220
+ elsif element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :variable
221
+ nil
222
+ elsif element_uris.compact.empty?
223
+ 'TRUE'
224
+ elsif filter[:over] && filter[:to]
225
+ over = attr_cache[filter[:over]]
226
+ to = attr_cache[filter[:to]]
227
+ "([#{label.attribute_uri}] IN (#{element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ')})) OVER [#{over && over.uri}] TO [#{to && to.uri}]"
228
+ else
229
+ "[#{label.attribute_uri}] IN (#{element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ')})"
230
+ end
231
+ if options[:ignore_missing_values]
232
+ [expression, []]
233
+ else
234
+ [expression, errors]
235
+ end
236
+ end
237
+
238
+ # Encapuslates the creation of filter
239
+ def self.create_user_filter(expression, related)
240
+ {
241
+ related: related,
242
+ level: :user,
243
+ expression: expression,
244
+ type: :filter
245
+ }
246
+ end
247
+
248
+ # Resolves and creates maql statements from filter definitions.
249
+ # This method does not perform any modifications on API but
250
+ # collects all the information that is needed to do so.
251
+ # Method collects all info from the user and current state in project and compares.
252
+ # Returns suggestion of what should be deleted and what should be created
253
+ # If there is some discrepancies in the data (missing values, nonexistent users) it
254
+ # finishes and collects all the errors at once
255
+ #
256
+ # @param filters [Array<Hash>] Filters definition
257
+ # @return [Array] first is list of MAQL statements
258
+ def self.maqlify_filters(filters, options = {})
259
+ fail_early = options[:fail_early] == false ? false : true
260
+ users_cache = options[:users_cache]
261
+ labels_cache = create_label_cache(filters, options)
262
+ small_labels = get_small_labels(labels_cache)
263
+ lookups_cache = create_lookups_cache(small_labels)
264
+ attrs_cache = create_attrs_cache(filters, options)
265
+ create_filter_proc = proc do |login, f|
266
+ expression, errors = create_expression(f, labels_cache, lookups_cache, attrs_cache, options)
267
+ profiles_uri = if options[:type] == :muf
268
+ '/gdc/account/profile/' + login
269
+ elsif options[:type] == :variable
270
+ (users_cache[login] && users_cache[login].uri)
271
+ else
272
+ fail 'Unsuported type in maqlify_filters.'
273
+ end
274
+
275
+ if profiles_uri && expression
276
+ [create_user_filter(expression, profiles_uri)] + errors
277
+ else
278
+ [] + errors
279
+ end
280
+ end
281
+
282
+ # if fail early process until first error
283
+ results = if fail_early
284
+ x = filters.inject([true, []]) do |(enough, a), e|
285
+ login = e[:login]
286
+ if enough
287
+ y = e[:filters].pmapcat { |f| create_filter_proc.call(login, f) }
288
+ [!y.any? { |r| r[:type] == :error }, a.concat(y)]
289
+ else
290
+ [false, a]
291
+ end
292
+ end
293
+ x.last
294
+ else
295
+ filters.flat_map do |filter|
296
+ login = filter[:login]
297
+ filter[:filters].pmapcat { |f| create_filter_proc.call(login, f) }
298
+ end
299
+ end
300
+ results.group_by { |i| i[:type] }.values_at(:filter, :error).map { |i| i || [] }
301
+ end
302
+
303
+ def self.resolve_user_filter(user = [], project = [])
304
+ user ||= []
305
+ project ||= []
306
+ to_create = user - project
307
+ to_delete = project - user
308
+ { :create => to_create, :delete => to_delete }
309
+ end
310
+
311
+ # Gets user defined filters and values from project regardless if they
312
+ # come from Mandatory Filters or Variable filters and tries to
313
+ # resolve what needs to be removed an what needs to be updated
314
+ def self.resolve_user_filters(user_filters, vals)
315
+ project_vals_lookup = vals.group_by(&:related_uri)
316
+ user_vals_lookup = user_filters.group_by(&:related_uri)
317
+
318
+ a = vals.map { |x| [x.related_uri, x] }
319
+ b = user_filters.map { |x| [x.related_uri, x] }
320
+
321
+ users_to_try = a.map(&:first).concat(b.map(&:first)).uniq
322
+ results = users_to_try.map do |user|
323
+ resolve_user_filter(user_vals_lookup[user], project_vals_lookup[user])
324
+ end
325
+
326
+ to_create = results.map { |x| x[:create] }.flatten.group_by(&:related_uri)
327
+ to_delete = results.map { |x| x[:delete] }.flatten.group_by(&:related_uri)
328
+ [to_create, to_delete]
329
+ end
330
+
331
+ # Executes the update for variables. It resolves what is new and needed to update.
332
+ # @param filters [Array<Hash>] Filter Definitions
333
+ # @param filters [Variable] Variable instance to be updated
334
+ # @param options [Hash]
335
+ # @option options [Boolean] :dry_run If dry run is true. No changes to he proejct are made but list of changes is provided
336
+ # @return [Array] list of filters that needs to be created and deleted
337
+ def self.execute_variables(filters, var, options = {})
338
+ client = options[:client]
339
+ project = options[:project]
340
+ dry_run = options[:dry_run]
341
+ to_create, to_delete = execute(filters, var.user_values, VariableUserFilter, options.merge(type: :variable))
342
+ return [to_create, to_delete] if dry_run
343
+
344
+ # TODO: get values that are about to be deleted and created and update them.
345
+ # This will make sure there is no downitme in filter existence
346
+ unless options[:do_not_touch_filters_that_are_not_mentioned]
347
+ to_delete.each { |_, group| group.each(&:delete) }
348
+ end
349
+ data = to_create.values.flatten.map(&:to_hash).map { |var_val| var_val.merge(prompt: var.uri) }
350
+ data.each_slice(200) do |slice|
351
+ client.post("/gdc/md/#{project.obj_id}/variables/user", :variables => slice)
352
+ end
353
+ [to_create, to_delete]
354
+ end
355
+
356
+ def self.execute_mufs(user_filters, options = {})
357
+ client = options[:client]
358
+ project = options[:project]
359
+ ignore_missing_values = options[:ignore_missing_values]
360
+ users_must_exist = options[:users_must_exist] == false ? false : true
361
+ dry_run = options[:dry_run]
362
+
363
+ filters = normalize_filters(user_filters)
364
+ user_filters, errors = maqlify_filters(filters, options.merge(users_must_exist: users_must_exist, type: :muf))
365
+
366
+ fail GoodData::FilterMaqlizationError, errors if !ignore_missing_values && !errors.empty?
367
+ filters = user_filters.map { |data| client.create(MandatoryUserFilter, data, project: project) }
368
+ to_create, to_delete = resolve_user_filters(filters, project.data_permissions)
369
+
370
+ GoodData.logger.warn("Data permissions computed: #{to_create.count} to create and #{to_delete.count} to delete")
371
+ return { created: to_create, deleted: to_delete } if dry_run
372
+
373
+ create_results = to_create.each_slice(100).flat_map do |batch|
374
+ batch.pmapcat do |related_uri, group|
375
+ group.each(&:save)
376
+ res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
377
+ items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
378
+
379
+ payload = {
380
+ 'userFilters' => {
381
+ 'items' => [{
382
+ 'user' => related_uri,
383
+ 'userFilters' => items.concat(group.map(&:uri))
384
+ }]
385
+ }
386
+ }
387
+ res = client.post("/gdc/md/#{project.pid}/userfilters", payload)
388
+
389
+ # turn the errors from hashes into array of hashes
390
+ res['userFiltersUpdateResult'].flat_map { |k, v| v.map { |r| { status: k.to_sym, user: r, type: :create } } }.map { |result| result[:status] == :failed ? result.merge(GoodData::Helpers.symbolize_keys(result[:user])) : result }
391
+ end
392
+ end
393
+ delete_results = unless options[:do_not_touch_filters_that_are_not_mentioned]
394
+ to_delete.each_slice(100).flat_map do |batch|
395
+ batch.flat_map do |related_uri, group|
396
+ results = []
397
+ if related_uri
398
+ res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
399
+ items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
400
+ payload = {
401
+ 'userFilters' => {
402
+ 'items' => [
403
+ {
404
+ 'user' => related_uri,
405
+ 'userFilters' => items - group.map(&:uri)
406
+ }
407
+ ]
408
+ }
409
+ }
410
+ res = client.post("/gdc/md/#{project.pid}/userfilters", payload)
411
+ results.concat(res['userFiltersUpdateResult']
412
+ .flat_map { |k, v| v.map { |r| { status: k.to_sym, user: r, type: :delete } } }
413
+ .map { |result| result[:status] == :failed ? result.merge(GoodData::Helpers.symbolize_keys(result[:user])) : result })
414
+ end
415
+ group.peach(&:delete)
416
+ results
417
+ end
418
+ end
419
+ end
420
+ { created: to_create, deleted: to_delete, results: create_results + (delete_results || []) }
421
+ end
422
+
423
+ private
424
+
425
+ # Reads values from File/Array. Abstracts away the fact if it is column based,
426
+ # row based or in file or provided inline as an array
427
+ # @param file [String | Array] File or array of values to be parsed for filters
428
+ # @param options [Hash] Filter definitions
429
+ # @return [Array<Hash>]
430
+ def self.get_values(file, options)
431
+ file.is_a?(Array) ? read_array(file, options) : read_file(file, options)
432
+ end
433
+
434
+ # Reads array of values which are expected to be in a line wise manner
435
+ # [
436
+ # ['john.doe@example.com', 'Engineering', 'Marketing']
437
+ # ]
438
+ # @param data [Array<Array>]
439
+ def self.read_array(data, options = {})
440
+ memo = {}
441
+ data.each do |e|
442
+ key, data = process_line(e, options)
443
+ memo[key] = [] unless memo.key?(key)
444
+ memo[key].concat(data)
445
+ end
446
+ memo
447
+ end
448
+
449
+ # Executes the procedure necessary for loading user filters. This method has what
450
+ # is common for both implementations. Funcion
451
+ # * makes sure that filters are in normalized form.
452
+ # * verifies that users are in the project (and domain)
453
+ # * creates maql expressions of the filters provided
454
+ # * resolves the filters against current values in the project
455
+ # @param user_filters [Array] Filters that user is trying to set up
456
+ # @param project_filters [Array] List of filters currently in the project
457
+ # @param klass [Class] Class can be aither UserFilter or VariableFilter
458
+ # @param options [Hash] Filter definitions
459
+ # @return [Array<Hash>]
460
+ def self.execute(user_filters, project_filters, klass, options = {})
461
+ client = options[:client]
462
+ project = options[:project]
463
+
464
+ ignore_missing_values = options[:ignore_missing_values]
465
+ users_must_exist = options[:users_must_exist] == false ? false : true
466
+ filters = normalize_filters(user_filters)
467
+ # domain = options[:domain]
468
+ # users = domain ? project.users : project.users
469
+ users = project.users
470
+ users_cache = create_cache(users, :login)
471
+ missing_users = get_missing_users(filters, options.merge(users_cache: users_cache))
472
+ user_filters, errors = if missing_users.empty?
473
+ verify_existing_users(filters, project: project, users_must_exist: users_must_exist, users_cache: users_cache)
474
+ maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist))
475
+ elsif missing_users.count < 100
476
+ verify_existing_users(filters, project: project, users_must_exist: users_must_exist, users_cache: users_cache)
477
+ maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist))
478
+ else
479
+ users_cache = create_cache(users, :login)
480
+ verify_existing_users(filters, project: project, users_must_exist: users_must_exist, users_cache: users_cache)
481
+ maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist))
482
+ end
483
+
484
+ fail GoodData::FilterMaqlizationError, errors if !ignore_missing_values && !errors.empty?
485
+ filters = user_filters.map { |data| client.create(klass, data, project: project) }
486
+ resolve_user_filters(filters, project_filters)
487
+ end
488
+
489
+ # Gets definition of filters from user. They might either come in the full definition
490
+ # as hash or a simplified version. The simplified version do not cover all the possible
491
+ # features but it is much simpler to remember and suitable for quick hacking around
492
+ # @param filters [Array<Array | Hash>]
493
+ # @return [Array<Hash>]
494
+ def self.normalize_filters(filters)
495
+ filters.map do |filter|
496
+ if filter.is_a?(Hash)
497
+ filter
498
+ else
499
+ {
500
+ :login => filter.first,
501
+ :filters => [
502
+ {
503
+ :label => filter[1],
504
+ :values => filter[2..-1]
505
+ }
506
+ ]
507
+ }
508
+ end
509
+ end
510
+ end
511
+ end
512
+ end