gooddata-edge 0.6.27.edge

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 (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