gooddata-edge 0.6.27.edge
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +36 -0
- data/.rspec +3 -0
- data/.rubocop.yml +89 -0
- data/.yardopts +22 -0
- data/CHANGELOG.md +196 -0
- data/CLI.md +439 -0
- data/DEPENDENCIES.md +817 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/LICENSE.rb +5 -0
- data/README.md +75 -0
- data/Rakefile +179 -0
- data/TODO.md +32 -0
- data/authors.sh +4 -0
- data/bin/gooddata +7 -0
- data/dependency_decisions.yml +104 -0
- data/gooddata +9 -0
- data/gooddata.gemspec +63 -0
- data/lib/gooddata.rb +31 -0
- data/lib/gooddata/app/app.rb +16 -0
- data/lib/gooddata/bricks/base_downloader.rb +86 -0
- data/lib/gooddata/bricks/brick.rb +38 -0
- data/lib/gooddata/bricks/bricks.rb +15 -0
- data/lib/gooddata/bricks/middleware/aws_middleware.rb +29 -0
- data/lib/gooddata/bricks/middleware/base_middleware.rb +56 -0
- data/lib/gooddata/bricks/middleware/bench_middleware.rb +24 -0
- data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +37 -0
- data/lib/gooddata/bricks/middleware/decode_params_middleware.rb +20 -0
- data/lib/gooddata/bricks/middleware/fs_download_middleware.rb +48 -0
- data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +36 -0
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +39 -0
- data/lib/gooddata/bricks/middleware/logger_middleware.rb +29 -0
- data/lib/gooddata/bricks/middleware/middleware.rb +12 -0
- data/lib/gooddata/bricks/middleware/restforce_middleware.rb +61 -0
- data/lib/gooddata/bricks/middleware/stdout_middleware.rb +23 -0
- data/lib/gooddata/bricks/middleware/twitter_middleware.rb +29 -0
- data/lib/gooddata/bricks/middleware/undot_params_middleware.rb +37 -0
- data/lib/gooddata/bricks/pipeline.rb +32 -0
- data/lib/gooddata/bricks/utils.rb +18 -0
- data/lib/gooddata/cli/cli.rb +27 -0
- data/lib/gooddata/cli/commands/auth_cmd.rb +29 -0
- data/lib/gooddata/cli/commands/domain_cmd.rb +28 -0
- data/lib/gooddata/cli/commands/project_cmd.rb +45 -0
- data/lib/gooddata/cli/hooks.rb +57 -0
- data/lib/gooddata/cli/shared.rb +61 -0
- data/lib/gooddata/cli/terminal.rb +20 -0
- data/lib/gooddata/client.rb +67 -0
- data/lib/gooddata/commands/api.rb +64 -0
- data/lib/gooddata/commands/auth.rb +107 -0
- data/lib/gooddata/commands/base.rb +12 -0
- data/lib/gooddata/commands/commands.rb +12 -0
- data/lib/gooddata/commands/datasets.rb +148 -0
- data/lib/gooddata/commands/datawarehouse.rb +20 -0
- data/lib/gooddata/commands/domain.rb +40 -0
- data/lib/gooddata/commands/process.rb +67 -0
- data/lib/gooddata/commands/project.rb +175 -0
- data/lib/gooddata/commands/projects.rb +20 -0
- data/lib/gooddata/commands/role.rb +36 -0
- data/lib/gooddata/commands/runners.rb +47 -0
- data/lib/gooddata/commands/scaffold.rb +69 -0
- data/lib/gooddata/commands/user.rb +39 -0
- data/lib/gooddata/connection.rb +127 -0
- data/lib/gooddata/core/core.rb +12 -0
- data/lib/gooddata/core/logging.rb +105 -0
- data/lib/gooddata/core/nil_logger.rb +23 -0
- data/lib/gooddata/core/project.rb +74 -0
- data/lib/gooddata/core/rest.rb +149 -0
- data/lib/gooddata/core/user.rb +20 -0
- data/lib/gooddata/data/data.rb +12 -0
- data/lib/gooddata/data/guesser.rb +122 -0
- data/lib/gooddata/exceptions/attr_element_not_found.rb +16 -0
- data/lib/gooddata/exceptions/command_failed.rb +11 -0
- data/lib/gooddata/exceptions/exceptions.rb +12 -0
- data/lib/gooddata/exceptions/execution_limit_exceeded.rb +13 -0
- data/lib/gooddata/exceptions/filter_maqlization.rb +16 -0
- data/lib/gooddata/exceptions/malformed_user.rb +15 -0
- data/lib/gooddata/exceptions/no_project_error.rb +15 -0
- data/lib/gooddata/exceptions/object_migration.rb +32 -0
- data/lib/gooddata/exceptions/project_not_found.rb +13 -0
- data/lib/gooddata/exceptions/segment_not_empty.rb +18 -0
- data/lib/gooddata/exceptions/uncomputable_report.rb +13 -0
- data/lib/gooddata/exceptions/user_in_different_domain.rb +15 -0
- data/lib/gooddata/exceptions/validation_error.rb +16 -0
- data/lib/gooddata/extensions/big_decimal.rb +17 -0
- data/lib/gooddata/extensions/enumerable.rb +39 -0
- data/lib/gooddata/extensions/extensions.rb +10 -0
- data/lib/gooddata/extensions/false.rb +15 -0
- data/lib/gooddata/extensions/hash.rb +38 -0
- data/lib/gooddata/extensions/nil.rb +15 -0
- data/lib/gooddata/extensions/numeric.rb +15 -0
- data/lib/gooddata/extensions/object.rb +27 -0
- data/lib/gooddata/extensions/symbol.rb +15 -0
- data/lib/gooddata/extensions/true.rb +15 -0
- data/lib/gooddata/extract.rb +21 -0
- data/lib/gooddata/goodzilla/goodzilla.rb +159 -0
- data/lib/gooddata/helpers/auth_helpers.rb +75 -0
- data/lib/gooddata/helpers/csv_helper.rb +61 -0
- data/lib/gooddata/helpers/data_helper.rb +116 -0
- data/lib/gooddata/helpers/global_helpers.rb +331 -0
- data/lib/gooddata/helpers/global_helpers_params.rb +172 -0
- data/lib/gooddata/helpers/helpers.rb +10 -0
- data/lib/gooddata/mixins/author.rb +26 -0
- data/lib/gooddata/mixins/content_getter.rb +15 -0
- data/lib/gooddata/mixins/content_property_reader.rb +17 -0
- data/lib/gooddata/mixins/content_property_writer.rb +17 -0
- data/lib/gooddata/mixins/contributor.rb +20 -0
- data/lib/gooddata/mixins/data_getter.rb +15 -0
- data/lib/gooddata/mixins/data_property_reader.rb +19 -0
- data/lib/gooddata/mixins/data_property_writer.rb +19 -0
- data/lib/gooddata/mixins/inspector.rb +53 -0
- data/lib/gooddata/mixins/is_attribute.rb +17 -0
- data/lib/gooddata/mixins/is_dimension.rb +17 -0
- data/lib/gooddata/mixins/is_fact.rb +17 -0
- data/lib/gooddata/mixins/is_label.rb +19 -0
- data/lib/gooddata/mixins/links.rb +15 -0
- data/lib/gooddata/mixins/md_finders.rb +77 -0
- data/lib/gooddata/mixins/md_grantees.rb +42 -0
- data/lib/gooddata/mixins/md_id_to_uri.rb +34 -0
- data/lib/gooddata/mixins/md_json.rb +15 -0
- data/lib/gooddata/mixins/md_lock.rb +87 -0
- data/lib/gooddata/mixins/md_object_id.rb +15 -0
- data/lib/gooddata/mixins/md_object_indexer.rb +64 -0
- data/lib/gooddata/mixins/md_object_query.rb +128 -0
- data/lib/gooddata/mixins/md_relations.rb +43 -0
- data/lib/gooddata/mixins/meta_getter.rb +17 -0
- data/lib/gooddata/mixins/meta_property_reader.rb +19 -0
- data/lib/gooddata/mixins/meta_property_writer.rb +19 -0
- data/lib/gooddata/mixins/mixins.rb +19 -0
- data/lib/gooddata/mixins/not_attribute.rb +17 -0
- data/lib/gooddata/mixins/not_exportable.rb +15 -0
- data/lib/gooddata/mixins/not_fact.rb +17 -0
- data/lib/gooddata/mixins/not_group.rb +17 -0
- data/lib/gooddata/mixins/not_label.rb +19 -0
- data/lib/gooddata/mixins/not_metric.rb +19 -0
- data/lib/gooddata/mixins/obj_id.rb +15 -0
- data/lib/gooddata/mixins/rest_getters.rb +17 -0
- data/lib/gooddata/mixins/rest_resource.rb +47 -0
- data/lib/gooddata/mixins/root_key_getter.rb +15 -0
- data/lib/gooddata/mixins/root_key_setter.rb +15 -0
- data/lib/gooddata/mixins/timestamps.rb +19 -0
- data/lib/gooddata/mixins/to_json.rb +11 -0
- data/lib/gooddata/mixins/uri_getter.rb +9 -0
- data/lib/gooddata/models/blueprint/anchor_field.rb +64 -0
- data/lib/gooddata/models/blueprint/attribute_field.rb +29 -0
- data/lib/gooddata/models/blueprint/blueprint.rb +11 -0
- data/lib/gooddata/models/blueprint/blueprint_field.rb +70 -0
- data/lib/gooddata/models/blueprint/dashboard_builder.rb +30 -0
- data/lib/gooddata/models/blueprint/dataset_blueprint.rb +449 -0
- data/lib/gooddata/models/blueprint/date_dimension.rb +14 -0
- data/lib/gooddata/models/blueprint/fact_field.rb +20 -0
- data/lib/gooddata/models/blueprint/label_field.rb +43 -0
- data/lib/gooddata/models/blueprint/project_blueprint.rb +746 -0
- data/lib/gooddata/models/blueprint/project_builder.rb +83 -0
- data/lib/gooddata/models/blueprint/reference_field.rb +43 -0
- data/lib/gooddata/models/blueprint/schema_blueprint.rb +160 -0
- data/lib/gooddata/models/blueprint/schema_builder.rb +89 -0
- data/lib/gooddata/models/blueprint/to_manifest.rb +181 -0
- data/lib/gooddata/models/blueprint/to_wire.rb +154 -0
- data/lib/gooddata/models/client.rb +182 -0
- data/lib/gooddata/models/client_synchronization_result.rb +31 -0
- data/lib/gooddata/models/client_synchronization_result_details.rb +41 -0
- data/lib/gooddata/models/datawarehouse.rb +92 -0
- data/lib/gooddata/models/domain.rb +479 -0
- data/lib/gooddata/models/execution.rb +115 -0
- data/lib/gooddata/models/execution_detail.rb +81 -0
- data/lib/gooddata/models/from_wire.rb +160 -0
- data/lib/gooddata/models/invitation.rb +75 -0
- data/lib/gooddata/models/links.rb +50 -0
- data/lib/gooddata/models/membership.rb +441 -0
- data/lib/gooddata/models/metadata.rb +272 -0
- data/lib/gooddata/models/metadata/attribute.rb +134 -0
- data/lib/gooddata/models/metadata/dashboard.rb +108 -0
- data/lib/gooddata/models/metadata/dashboard/dashboard_item.rb +76 -0
- data/lib/gooddata/models/metadata/dashboard/filter_apply_item.rb +37 -0
- data/lib/gooddata/models/metadata/dashboard/filter_item.rb +64 -0
- data/lib/gooddata/models/metadata/dashboard/geo_chart_item.rb +56 -0
- data/lib/gooddata/models/metadata/dashboard/headline_item.rb +56 -0
- data/lib/gooddata/models/metadata/dashboard/iframe_item.rb +46 -0
- data/lib/gooddata/models/metadata/dashboard/report_item.rb +92 -0
- data/lib/gooddata/models/metadata/dashboard/text_item.rb +55 -0
- data/lib/gooddata/models/metadata/dashboard_tab.rb +141 -0
- data/lib/gooddata/models/metadata/dataset.rb +64 -0
- data/lib/gooddata/models/metadata/dimension.rb +54 -0
- data/lib/gooddata/models/metadata/fact.rb +44 -0
- data/lib/gooddata/models/metadata/label.rb +128 -0
- data/lib/gooddata/models/metadata/metadata.rb +12 -0
- data/lib/gooddata/models/metadata/metric.rb +198 -0
- data/lib/gooddata/models/metadata/report.rb +247 -0
- data/lib/gooddata/models/metadata/report_definition.rb +264 -0
- data/lib/gooddata/models/metadata/scheduled_mail.rb +274 -0
- data/lib/gooddata/models/metadata/scheduled_mail/dashboard_attachment.rb +62 -0
- data/lib/gooddata/models/metadata/scheduled_mail/report_attachment.rb +64 -0
- data/lib/gooddata/models/metadata/variable.rb +91 -0
- data/lib/gooddata/models/model.rb +282 -0
- data/lib/gooddata/models/models.rb +12 -0
- data/lib/gooddata/models/module_constants.rb +31 -0
- data/lib/gooddata/models/process.rb +316 -0
- data/lib/gooddata/models/profile.rb +426 -0
- data/lib/gooddata/models/project.rb +1514 -0
- data/lib/gooddata/models/project_creator.rb +126 -0
- data/lib/gooddata/models/project_metadata.rb +67 -0
- data/lib/gooddata/models/project_role.rb +79 -0
- data/lib/gooddata/models/report_data_result.rb +266 -0
- data/lib/gooddata/models/schedule.rb +518 -0
- data/lib/gooddata/models/segment.rb +201 -0
- data/lib/gooddata/models/tab_builder.rb +27 -0
- data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +76 -0
- data/lib/gooddata/models/user_filters/user_filter.rb +100 -0
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +512 -0
- data/lib/gooddata/models/user_filters/user_filters.rb +13 -0
- data/lib/gooddata/models/user_filters/variable_user_filter.rb +31 -0
- data/lib/gooddata/models/user_group.rb +241 -0
- data/lib/gooddata/rest/README.md +37 -0
- data/lib/gooddata/rest/client.rb +389 -0
- data/lib/gooddata/rest/connection.rb +765 -0
- data/lib/gooddata/rest/object.rb +69 -0
- data/lib/gooddata/rest/object_factory.rb +76 -0
- data/lib/gooddata/rest/resource.rb +27 -0
- data/lib/gooddata/rest/rest.rb +24 -0
- data/lib/gooddata/version.rb +23 -0
- data/lib/templates/bricks/brick.rb.erb +7 -0
- data/lib/templates/bricks/main.rb.erb +5 -0
- data/lib/templates/project/Goodfile.erb +4 -0
- data/lib/templates/project/data/commits.csv +4 -0
- data/lib/templates/project/data/devs.csv +4 -0
- data/lib/templates/project/data/repos.csv +3 -0
- data/lib/templates/project/model/model.rb.erb +20 -0
- data/spec/bricks/bricks_spec.rb +112 -0
- data/spec/bricks/default-config.json +8 -0
- data/spec/data/.gooddata +4 -0
- data/spec/data/blueprints/additional_dataset_module.json +32 -0
- data/spec/data/blueprints/big_blueprint_not_pruned.json +2079 -0
- data/spec/data/blueprints/invalid_blueprint.json +103 -0
- data/spec/data/blueprints/m_n_model.json +104 -0
- data/spec/data/blueprints/model_module.json +25 -0
- data/spec/data/blueprints/test_blueprint.json +38 -0
- data/spec/data/blueprints/test_project_model_spec.json +106 -0
- data/spec/data/cc/data/source/commits.csv +4 -0
- data/spec/data/cc/data/source/devs.csv +4 -0
- data/spec/data/cc/data/source/repos.csv +3 -0
- data/spec/data/cc/devel.prm +0 -0
- data/spec/data/cc/graph/graph.grf +11 -0
- data/spec/data/cc/workspace.prm +19 -0
- data/spec/data/column_based_permissions.csv +7 -0
- data/spec/data/column_based_permissions2.csv +6 -0
- data/spec/data/gd_gse_data_blueprint.json +1371 -0
- data/spec/data/gd_gse_data_manifest.json +1424 -0
- data/spec/data/gd_gse_data_model.json +1772 -0
- data/spec/data/gooddata_version_process/gooddata_version.rb +9 -0
- data/spec/data/gooddata_version_process/gooddata_version.zip +0 -0
- data/spec/data/hello_world_process/hello_world.rb +9 -0
- data/spec/data/hello_world_process/hello_world.zip +0 -0
- data/spec/data/line_based_permissions.csv +3 -0
- data/spec/data/manifests/test_blueprint.json +32 -0
- data/spec/data/manifests/test_project.json +107 -0
- data/spec/data/reports/left_attr_report.json +108 -0
- data/spec/data/reports/metric_only_one_line.json +83 -0
- data/spec/data/reports/report_1.json +197 -0
- data/spec/data/reports/top_attr_report.json +108 -0
- data/spec/data/ruby_params_process/ruby_params.rb +9 -0
- data/spec/data/ruby_process/deep_files/deep_stuff.txt +1 -0
- data/spec/data/ruby_process/process.rb +8 -0
- data/spec/data/ruby_process/stuff.txt +1 -0
- data/spec/data/superfluous_titles_view.json +81 -0
- data/spec/data/test-ci-data.csv +2 -0
- data/spec/data/users.csv +12 -0
- data/spec/data/wire_models/model_view.json +1775 -0
- data/spec/data/wire_models/nu_model.json +3046 -0
- data/spec/data/wire_models/test_blueprint.json +63 -0
- data/spec/data/wire_test_project.json +150 -0
- data/spec/environment/default.rb +41 -0
- data/spec/environment/develop.rb +31 -0
- data/spec/environment/environment.rb +18 -0
- data/spec/environment/hotfix.rb +21 -0
- data/spec/environment/production.rb +35 -0
- data/spec/environment/release.rb +21 -0
- data/spec/environment/staging.rb +30 -0
- data/spec/environment/staging_3.rb +36 -0
- data/spec/helpers/blueprint_helper.rb +26 -0
- data/spec/helpers/cli_helper.rb +36 -0
- data/spec/helpers/connection_helper.rb +41 -0
- data/spec/helpers/crypto_helper.rb +17 -0
- data/spec/helpers/csv_helper.rb +18 -0
- data/spec/helpers/process_helper.rb +33 -0
- data/spec/helpers/project_helper.rb +59 -0
- data/spec/helpers/schedule_helper.rb +31 -0
- data/spec/helpers/spec_helper.rb +15 -0
- data/spec/integration/blueprint_updates_spec.rb +101 -0
- data/spec/integration/blueprint_with_grain_spec.rb +72 -0
- data/spec/integration/clients_spec.rb +134 -0
- data/spec/integration/command_datawarehouse_spec.rb +39 -0
- data/spec/integration/command_projects_spec.rb +32 -0
- data/spec/integration/create_from_template_spec.rb +22 -0
- data/spec/integration/create_project_spec.rb +24 -0
- data/spec/integration/date_dim_switch_spec.rb +142 -0
- data/spec/integration/deprecated_load_spec.rb +58 -0
- data/spec/integration/full_process_schedule_spec.rb +298 -0
- data/spec/integration/full_project_spec.rb +569 -0
- data/spec/integration/over_to_user_filters_spec.rb +94 -0
- data/spec/integration/partial_md_export_import_spec.rb +42 -0
- data/spec/integration/project_spec.rb +264 -0
- data/spec/integration/rest_spec.rb +213 -0
- data/spec/integration/schedule_spec.rb +626 -0
- data/spec/integration/segments_spec.rb +141 -0
- data/spec/integration/user_filters_spec.rb +290 -0
- data/spec/integration/user_group_spec.rb +127 -0
- data/spec/integration/variables_spec.rb +188 -0
- data/spec/logging_in_logging_out_spec.rb +93 -0
- data/spec/spec_helper.rb +95 -0
- data/spec/unit/bricks/bricks_spec.rb +35 -0
- data/spec/unit/bricks/middleware/aws_middelware_spec.rb +51 -0
- data/spec/unit/bricks/middleware/bench_middleware_spec.rb +15 -0
- data/spec/unit/bricks/middleware/bulk_salesforce_middleware_spec.rb +15 -0
- data/spec/unit/bricks/middleware/gooddata_middleware_spec.rb +15 -0
- data/spec/unit/bricks/middleware/logger_middleware_spec.rb +15 -0
- data/spec/unit/bricks/middleware/restforce_middleware_spec.rb +15 -0
- data/spec/unit/bricks/middleware/stdout_middleware_spec.rb +15 -0
- data/spec/unit/bricks/middleware/twitter_middleware_spec.rb +15 -0
- data/spec/unit/cli/cli_spec.rb +17 -0
- data/spec/unit/cli/commands/cmd_auth_spec.rb +17 -0
- data/spec/unit/commands/command_projects_spec.rb +22 -0
- data/spec/unit/core/connection_spec.rb +57 -0
- data/spec/unit/core/logging_spec.rb +133 -0
- data/spec/unit/core/nil_logger_spec.rb +13 -0
- data/spec/unit/core/project_spec.rb +54 -0
- data/spec/unit/extensions/hash_spec.rb +23 -0
- data/spec/unit/godzilla/goodzilla_spec.rb +78 -0
- data/spec/unit/helpers/csv_helper_spec.rb +22 -0
- data/spec/unit/helpers/data_helper_spec.rb +61 -0
- data/spec/unit/helpers/global_helpers_spec.rb +111 -0
- data/spec/unit/helpers_spec.rb +86 -0
- data/spec/unit/models/blueprint/attributes_spec.rb +29 -0
- data/spec/unit/models/blueprint/dataset_spec.rb +121 -0
- data/spec/unit/models/blueprint/labels_spec.rb +44 -0
- data/spec/unit/models/blueprint/project_blueprint_spec.rb +648 -0
- data/spec/unit/models/blueprint/reference_spec.rb +29 -0
- data/spec/unit/models/blueprint/schema_builder_spec.rb +38 -0
- data/spec/unit/models/blueprint/to_wire_spec.rb +174 -0
- data/spec/unit/models/domain_spec.rb +144 -0
- data/spec/unit/models/execution_spec.rb +108 -0
- data/spec/unit/models/from_wire_spec.rb +296 -0
- data/spec/unit/models/invitation_spec.rb +17 -0
- data/spec/unit/models/membership_spec.rb +132 -0
- data/spec/unit/models/metadata_spec.rb +104 -0
- data/spec/unit/models/metric_spec.rb +117 -0
- data/spec/unit/models/model_spec.rb +82 -0
- data/spec/unit/models/params_spec.rb +118 -0
- data/spec/unit/models/profile_spec.rb +215 -0
- data/spec/unit/models/project_creator_spec.rb +127 -0
- data/spec/unit/models/project_role_spec.rb +94 -0
- data/spec/unit/models/project_spec.rb +162 -0
- data/spec/unit/models/report_result_data_spec.rb +199 -0
- data/spec/unit/models/schedule_spec.rb +418 -0
- data/spec/unit/models/to_manifest_spec.rb +63 -0
- data/spec/unit/models/unit_project_spec.rb +125 -0
- data/spec/unit/models/user_filters_spec.rb +95 -0
- data/spec/unit/models/variable_spec.rb +265 -0
- data/spec/unit/rest/polling_spec.rb +89 -0
- data/spec/unit/rest/resource_spec.rb +10 -0
- data/yard-server.sh +3 -0
- metadata +1125 -0
@@ -0,0 +1,1514 @@
|
|
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 'csv'
|
8
|
+
require 'zip'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'multi_json'
|
11
|
+
require 'pmap'
|
12
|
+
require 'zip'
|
13
|
+
|
14
|
+
require_relative '../exceptions/no_project_error'
|
15
|
+
|
16
|
+
require_relative '../helpers/auth_helpers'
|
17
|
+
|
18
|
+
require_relative '../rest/resource'
|
19
|
+
require_relative '../mixins/author'
|
20
|
+
require_relative '../mixins/contributor'
|
21
|
+
require_relative '../mixins/rest_resource'
|
22
|
+
require_relative '../mixins/uri_getter'
|
23
|
+
|
24
|
+
require_relative 'membership'
|
25
|
+
require_relative 'process'
|
26
|
+
require_relative 'project_role'
|
27
|
+
require_relative 'blueprint/blueprint'
|
28
|
+
|
29
|
+
require_relative 'metadata/scheduled_mail'
|
30
|
+
require_relative 'metadata/scheduled_mail/dashboard_attachment'
|
31
|
+
require_relative 'metadata/scheduled_mail/report_attachment'
|
32
|
+
|
33
|
+
module GoodData
|
34
|
+
class Project < Rest::Resource
|
35
|
+
USERSPROJECTS_PATH = '/gdc/account/profile/%s/projects'
|
36
|
+
PROJECTS_PATH = '/gdc/projects'
|
37
|
+
PROJECT_PATH = '/gdc/projects/%s'
|
38
|
+
SLIS_PATH = '/ldm/singleloadinterface'
|
39
|
+
DEFAULT_INVITE_MESSAGE = 'Join us!'
|
40
|
+
DEFAULT_ENVIRONMENT = 'PRODUCTION'
|
41
|
+
|
42
|
+
EMPTY_OBJECT = {
|
43
|
+
'project' => {
|
44
|
+
'meta' => {
|
45
|
+
'summary' => 'No summary'
|
46
|
+
},
|
47
|
+
'content' => {
|
48
|
+
'guidedNavigation' => 1,
|
49
|
+
'driver' => 'Pg',
|
50
|
+
'environment' => GoodData::Helpers::AuthHelper.read_environment
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
attr_accessor :connection, :json
|
56
|
+
|
57
|
+
include Mixin::Author
|
58
|
+
include Mixin::Contributor
|
59
|
+
include Mixin::UriGetter
|
60
|
+
|
61
|
+
class << self
|
62
|
+
# Returns an array of all projects accessible by
|
63
|
+
# current user
|
64
|
+
def all(opts = { client: GoodData.connection })
|
65
|
+
c = client(opts)
|
66
|
+
c.user.projects
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a Project object identified by given string
|
70
|
+
# The following identifiers are accepted
|
71
|
+
# - /gdc/md/<id>
|
72
|
+
# - /gdc/projects/<id>
|
73
|
+
# - <id>
|
74
|
+
#
|
75
|
+
def [](id, opts = { client: GoodData.connection })
|
76
|
+
return id if id.instance_of?(GoodData::Project) || id.respond_to?(:project?) && id.project?
|
77
|
+
|
78
|
+
if id == :all
|
79
|
+
Project.all({ client: GoodData.connection }.merge(opts))
|
80
|
+
else
|
81
|
+
if id.to_s !~ %r{^(\/gdc\/(projects|md)\/)?[a-zA-Z\d]+$}
|
82
|
+
fail(ArgumentError, 'wrong type of argument. Should be either project ID or path')
|
83
|
+
end
|
84
|
+
|
85
|
+
id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ %r{/}
|
86
|
+
|
87
|
+
c = client(opts)
|
88
|
+
fail ArgumentError, 'No :client specified' if c.nil?
|
89
|
+
|
90
|
+
response = c.get(PROJECT_PATH % id)
|
91
|
+
c.factory.create(Project, response)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Clones project along with etl and schedules
|
96
|
+
#
|
97
|
+
# @param project [Project] Project to be cloned from
|
98
|
+
# @param [options] Options that are passed into project.clone
|
99
|
+
# @return [GoodData::Project] New cloned project
|
100
|
+
def clone_with_etl(project, options = {})
|
101
|
+
a_clone = project.clone(options)
|
102
|
+
GoodData::Project.transfer_etl(project.client, project, a_clone)
|
103
|
+
a_clone
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_object(data = {})
|
107
|
+
c = client(data)
|
108
|
+
new_data = GoodData::Helpers.deep_dup(EMPTY_OBJECT).tap do |d|
|
109
|
+
d['project']['meta']['title'] = data[:title]
|
110
|
+
d['project']['meta']['summary'] = data[:summary] if data[:summary]
|
111
|
+
d['project']['meta']['projectTemplate'] = data[:template] if data[:template]
|
112
|
+
d['project']['content']['guidedNavigation'] = data[:guided_navigation] if data[:guided_navigation]
|
113
|
+
|
114
|
+
token = data[:auth_token] || data[:token]
|
115
|
+
|
116
|
+
d['project']['content']['authorizationToken'] = token if token
|
117
|
+
d['project']['content']['driver'] = data[:driver] if data[:driver]
|
118
|
+
d['project']['content']['environment'] = data[:environment] if data[:environment]
|
119
|
+
end
|
120
|
+
c.create(Project, new_data)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Create a project from a given attributes
|
124
|
+
# Expected keys:
|
125
|
+
# - :title (mandatory)
|
126
|
+
# - :summary
|
127
|
+
# - :template (default /projects/blank)
|
128
|
+
#
|
129
|
+
def create(opts = { :client => GoodData.connection }, &block)
|
130
|
+
GoodData.logger.info "Creating project #{opts[:title]}"
|
131
|
+
|
132
|
+
c = client(opts)
|
133
|
+
fail ArgumentError, 'No :client specified' if c.nil?
|
134
|
+
|
135
|
+
opts = { :auth_token => Helpers::AuthHelper.read_token }.merge(opts)
|
136
|
+
auth_token = opts[:auth_token] || opts[:token]
|
137
|
+
fail ArgumentError, 'You have to provide your token for creating projects as :auth_token parameter' if auth_token.nil? || auth_token.empty?
|
138
|
+
|
139
|
+
project = create_object(opts)
|
140
|
+
project.save
|
141
|
+
# until it is enabled or deleted, recur. This should still end if there is a exception thrown out from RESTClient. This sometimes happens from WebApp when request is too long
|
142
|
+
while project.state.to_s != 'enabled'
|
143
|
+
if project.deleted?
|
144
|
+
# if project is switched to deleted state, fail. This is usually problem of creating a template which is invalid.
|
145
|
+
fail 'Project was marked as deleted during creation. This usually means you were trying to create from template and it failed.'
|
146
|
+
end
|
147
|
+
sleep(3)
|
148
|
+
project.reload!
|
149
|
+
end
|
150
|
+
|
151
|
+
if block
|
152
|
+
GoodData.with_project(project) do |p|
|
153
|
+
block.call(p)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
sleep 3
|
157
|
+
project
|
158
|
+
end
|
159
|
+
|
160
|
+
def find(_opts = {}, client = GoodData::Rest::Client.client)
|
161
|
+
user = client.user
|
162
|
+
user.projects['projects'].map do |project|
|
163
|
+
client.create(GoodData::Project, project)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def create_from_blueprint(blueprint, options = {})
|
168
|
+
GoodData::Model::ProjectCreator.migrate(options.merge(spec: blueprint, client: GoodData.connection))
|
169
|
+
end
|
170
|
+
|
171
|
+
# Takes one CSV line and creates hash from data extracted
|
172
|
+
#
|
173
|
+
# @param row CSV row
|
174
|
+
def user_csv_import(row)
|
175
|
+
{
|
176
|
+
'user' => {
|
177
|
+
'content' => {
|
178
|
+
'email' => row[0],
|
179
|
+
'login' => row[1],
|
180
|
+
'firstname' => row[2],
|
181
|
+
'lastname' => row[3]
|
182
|
+
},
|
183
|
+
'meta' => {}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
# Clones project along with etl and schedules.
|
189
|
+
#
|
190
|
+
# @param client [GoodData::Rest::Client] GoodData client to be used for connection
|
191
|
+
# @param from_project [GoodData::Project | GoodData::Segment | GoodData:Client | String] Object to be cloned from. Can be either segment in which case we take the master, client in which case we take its project, string in which case we treat is as an project object or directly project
|
192
|
+
def transfer_etl(client, from_project, to_project)
|
193
|
+
from_project = case from_project
|
194
|
+
when GoodData::Client
|
195
|
+
from_project.project
|
196
|
+
when GoodData::Segment
|
197
|
+
from_project.master_project
|
198
|
+
else
|
199
|
+
client.projects(from_project)
|
200
|
+
end
|
201
|
+
|
202
|
+
to_project = case to_project
|
203
|
+
when GoodData::Client
|
204
|
+
to_project.project
|
205
|
+
when GoodData::Segment
|
206
|
+
to_project.master_project
|
207
|
+
else
|
208
|
+
client.projects(to_project)
|
209
|
+
end
|
210
|
+
|
211
|
+
from_project.processes.each do |process|
|
212
|
+
Dir.mktmpdir('etl_transfer') do |dir|
|
213
|
+
dir = Pathname(dir)
|
214
|
+
filename = dir + 'process.zip'
|
215
|
+
File.open(filename, 'w') do |f|
|
216
|
+
f << process.download
|
217
|
+
end
|
218
|
+
to_process = to_project.processes.find { |p| p.name == process.name }
|
219
|
+
to_process ? to_process.deploy(filename, type: process.type, name: process.name) : to_project.deploy_process(filename, type: process.type, name: process.name)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
res = (from_project.processes + to_project.processes).map { |p| [p, p.name, p.type] }
|
223
|
+
res.group_by { |x| [x[1], x[2]] }
|
224
|
+
.select { |_, procs| procs.length == 1 }
|
225
|
+
.flat_map { |_, procs| procs.select { |p| p[0].project.pid == to_project.pid }.map { |p| p[0] } }
|
226
|
+
.peach(&:delete)
|
227
|
+
transfer_schedules(from_project, to_project)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Clones project along with etl and schedules.
|
231
|
+
#
|
232
|
+
# @param client [GoodData::Rest::Client] GoodData client to be used for connection
|
233
|
+
# @param from_project [GoodData::Project | GoodData::Segment | GoodData:Client | String] Object to be cloned from. Can be either segment in which case we take the master, client in which case we take its project, string in which case we treat is as an project object or directly project
|
234
|
+
def transfer_schedules(from_project, to_project)
|
235
|
+
cache = to_project.processes.sort_by(&:name).zip(from_project.processes.sort_by(&:name)).flat_map { |remote, local| local.schedules.map { |schedule| [remote, local, schedule] } }
|
236
|
+
|
237
|
+
remote_schedules = to_project.schedules
|
238
|
+
remote_stuff = remote_schedules.map do |s|
|
239
|
+
v = s.to_hash
|
240
|
+
after_schedule = remote_schedules.find { |s2| s.trigger_id == s2.obj_id }
|
241
|
+
v[:after] = s.trigger_id && after_schedule && after_schedule.name
|
242
|
+
v[:remote_schedule] = s
|
243
|
+
v[:params] = v[:params].except("EXECUTABLE", "PROCESS_ID")
|
244
|
+
v.compact
|
245
|
+
end
|
246
|
+
|
247
|
+
local_schedules = from_project.schedules
|
248
|
+
local_stuff = local_schedules.map do |s|
|
249
|
+
v = s.to_hash
|
250
|
+
after_schedule = local_schedules.find { |s2| s.trigger_id == s2.obj_id }
|
251
|
+
v[:after] = s.trigger_id && after_schedule && after_schedule.name
|
252
|
+
v[:remote_schedule] = s
|
253
|
+
v[:params] = v[:params].except("EXECUTABLE", "PROCESS_ID")
|
254
|
+
v.compact
|
255
|
+
end
|
256
|
+
|
257
|
+
diff = GoodData::Helpers.diff(remote_stuff, local_stuff, key: :name, fields: [:name, :cron, :after, :params, :hidden_params, :reschedule])
|
258
|
+
stack = diff[:added].map { |x| [:added, x] } + diff[:changed].map { |x| [:changed, x] }
|
259
|
+
schedule_cache = remote_schedules.reduce({}) do |a, e|
|
260
|
+
a[e.name] = e
|
261
|
+
a
|
262
|
+
end
|
263
|
+
messages = []
|
264
|
+
loop do
|
265
|
+
break if stack.empty?
|
266
|
+
state, changed_schedule = stack.shift
|
267
|
+
if state == :added
|
268
|
+
schedule_spec = changed_schedule
|
269
|
+
if schedule_spec[:after] && !schedule_cache[schedule_spec[:after]]
|
270
|
+
stack << [state, schedule_spec]
|
271
|
+
next
|
272
|
+
end
|
273
|
+
remote_process, process_spec = cache.find { |_remote, _local, schedule| schedule.name == schedule_spec[:name] }
|
274
|
+
messages << { message: "Creating schedule #{schedule_spec[:name]} for process #{remote_process.name}" }
|
275
|
+
executable = schedule_spec[:executable] || (process_spec["process_type"] == 'ruby' ? 'main.rb' : 'main.grf')
|
276
|
+
params = {
|
277
|
+
params: schedule_spec[:params].merge('PROJECT_ID' => to_project.pid),
|
278
|
+
hidden_params: schedule_spec[:hidden_params],
|
279
|
+
name: schedule_spec[:name],
|
280
|
+
reschedule: schedule_spec[:reschedule]
|
281
|
+
}
|
282
|
+
created_schedule = remote_process.create_schedule(schedule_spec[:cron] || schedule_cache[schedule_spec[:after]], executable, params)
|
283
|
+
schedule_cache[created_schedule.name] = created_schedule
|
284
|
+
else
|
285
|
+
schedule_spec = changed_schedule[:new_obj]
|
286
|
+
if schedule_spec[:after] && !schedule_cache[schedule_spec[:after]]
|
287
|
+
stack << [state, schedule_spec]
|
288
|
+
next
|
289
|
+
end
|
290
|
+
remote_process, process_spec = cache.find { |i| i[2].name == schedule_spec[:name] }
|
291
|
+
schedule = changed_schedule[:old_obj][:remote_schedule]
|
292
|
+
messages << { message: "Updating schedule #{schedule_spec[:name]} for process #{remote_process.name}" }
|
293
|
+
schedule.params = (schedule_spec[:params] || {})
|
294
|
+
schedule.cron = schedule_spec[:cron] if schedule_spec[:cron]
|
295
|
+
schedule.after = schedule_cache[schedule_spec[:after]] if schedule_spec[:after]
|
296
|
+
schedule.hidden_params = schedule_spec[:hidden_params] || {}
|
297
|
+
schedule.executable = schedule_spec[:executable] || (process_spec["process_type"] == 'ruby' ? 'main.rb' : 'main.grf')
|
298
|
+
schedule.reschedule = schedule_spec[:reschedule]
|
299
|
+
schedule.name = schedule_spec[:name]
|
300
|
+
schedule.save
|
301
|
+
schedule_cache[schedule.name] = schedule
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
diff[:removed].each do |removed_schedule|
|
306
|
+
messages << { message: "Removing schedule #{removed_schedule[:name]}" }
|
307
|
+
removed_schedule[:remote_schedule].delete
|
308
|
+
end
|
309
|
+
messages
|
310
|
+
# messages.map {|m| m.merge({custom_project_id: custom_project_id})}
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def add_dashboard(dashboard)
|
315
|
+
GoodData::Dashboard.create(dashboard, :client => client, :project => self)
|
316
|
+
end
|
317
|
+
|
318
|
+
alias_method :create_dashboard, :add_dashboard
|
319
|
+
|
320
|
+
def add_user_group(data)
|
321
|
+
g = GoodData::UserGroup.create(data.merge(project: self))
|
322
|
+
|
323
|
+
begin
|
324
|
+
g.save
|
325
|
+
rescue RestClient::Conflict
|
326
|
+
user_groups(data[:name])
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
alias_method :create_group, :add_user_group
|
331
|
+
|
332
|
+
# Creates a metric in a project
|
333
|
+
#
|
334
|
+
# @param [options] Optional report options
|
335
|
+
# @return [GoodData::Report] Instance of new report
|
336
|
+
def add_metric(metric, options = {})
|
337
|
+
default = { client: client, project: self }
|
338
|
+
if metric.is_a?(String)
|
339
|
+
GoodData::Metric.xcreate(metric, options.merge(default))
|
340
|
+
else
|
341
|
+
GoodData::Metric.xcreate(options[:expression], metric.merge(options.merge(default)))
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
alias_method :create_metric, :add_metric
|
346
|
+
|
347
|
+
alias_method :add_measure, :add_metric
|
348
|
+
alias_method :create_measure, :add_metric
|
349
|
+
|
350
|
+
# Creates new instance of report in context of project
|
351
|
+
#
|
352
|
+
# @param [options] Optional report options
|
353
|
+
# @return [GoodData::Report] Instance of new report
|
354
|
+
def add_report(options = {})
|
355
|
+
report = GoodData::Report.create(options.merge(client: client, project: self))
|
356
|
+
report.save
|
357
|
+
end
|
358
|
+
|
359
|
+
alias_method :create_report, :add_report
|
360
|
+
|
361
|
+
# Creates new instance of report definition in context of project
|
362
|
+
# This report definition can be used for creating of GoodData::Report
|
363
|
+
#
|
364
|
+
# @param [json] Raw report definition json
|
365
|
+
# @return [GoodData::ReportDefinition] Instance of new report definition
|
366
|
+
def add_report_definition(json)
|
367
|
+
rd = GoodData::ReportDefinition.new(json)
|
368
|
+
rd.client = client
|
369
|
+
rd.project = self
|
370
|
+
rd.save
|
371
|
+
end
|
372
|
+
|
373
|
+
alias_method :create_report_definition, :add_report_definition
|
374
|
+
|
375
|
+
# Returns an indication whether current user is admin in this project
|
376
|
+
#
|
377
|
+
# @return [Boolean] True if user has admin role in the project, false otherwise.
|
378
|
+
def am_i_admin?
|
379
|
+
user_has_role?(client.user, 'admin')
|
380
|
+
end
|
381
|
+
|
382
|
+
# Helper for getting attributes of a project
|
383
|
+
#
|
384
|
+
# @param [String | Number | Object] Anything that you can pass to GoodData::Attribute[id]
|
385
|
+
# @return [GoodData::Attribute | Array<GoodData::Attribute>] fact instance or list
|
386
|
+
def attributes(id = :all)
|
387
|
+
GoodData::Attribute[id, project: self, client: client]
|
388
|
+
end
|
389
|
+
|
390
|
+
def attribute_by_identifier(identifier)
|
391
|
+
GoodData::Attribute.find_first_by_identifier(identifier, project: self, client: client)
|
392
|
+
end
|
393
|
+
|
394
|
+
def attributes_by_identifier(identifier)
|
395
|
+
GoodData::Attribute.find_by_identifier(identifier, project: self, client: client)
|
396
|
+
end
|
397
|
+
|
398
|
+
def attribute_by_title(title)
|
399
|
+
GoodData::Attribute.find_first_by_title(title, project: self, client: client)
|
400
|
+
end
|
401
|
+
|
402
|
+
def attributes_by_title(title)
|
403
|
+
GoodData::Attribute.find_by_title(title, project: self, client: client)
|
404
|
+
end
|
405
|
+
|
406
|
+
# Gets project blueprint from the server
|
407
|
+
#
|
408
|
+
# @return [GoodData::ProjectRole] Project role if found
|
409
|
+
def blueprint(options = {})
|
410
|
+
result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true })
|
411
|
+
polling_url = result['asyncTask']['link']['poll']
|
412
|
+
model = client.poll_on_code(polling_url, options)
|
413
|
+
bp = GoodData::Model::FromWire.from_wire(model)
|
414
|
+
bp.title = title
|
415
|
+
bp
|
416
|
+
end
|
417
|
+
|
418
|
+
# Returns web interface URI of project
|
419
|
+
#
|
420
|
+
# @return [String] Project URL
|
421
|
+
def browser_uri(options = {})
|
422
|
+
grey = options[:grey]
|
423
|
+
server = client.connection.server_url
|
424
|
+
if grey
|
425
|
+
"#{server}#{uri}"
|
426
|
+
else
|
427
|
+
"#{server}/#s=#{uri}"
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# Clones project
|
432
|
+
#
|
433
|
+
# @param options [Hash] Export options
|
434
|
+
# @option options [Boolean] :data Clone project with data
|
435
|
+
# @option options [Boolean] :users Clone project with users
|
436
|
+
# @option options [String] :authorized_users Comma separated logins of authorized users. Users that can use the export
|
437
|
+
# @return [GoodData::Project] Newly created project
|
438
|
+
def clone(options = {})
|
439
|
+
a_title = options[:title] || "Clone of #{title}"
|
440
|
+
|
441
|
+
begin
|
442
|
+
# Create the project first so we know that it is passing.
|
443
|
+
# What most likely is wrong is the token and the export actaully takes majority of the time
|
444
|
+
new_project = GoodData::Project.create(options.merge(:title => a_title, :client => client, :driver => content[:driver]))
|
445
|
+
export_token = export_clone(options)
|
446
|
+
new_project.import_clone(export_token)
|
447
|
+
rescue
|
448
|
+
new_project.delete if new_project
|
449
|
+
raise
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# Gives you list of datasets. These are not blueprint datasets but model datasets coming from meta
|
454
|
+
# data server.
|
455
|
+
#
|
456
|
+
# @param id [Symbol | String | GoodData::MdObject] Export options
|
457
|
+
# @return [Array<GoodData::Dataset> | GoodData::Dataset] Dataset or list of datasets in the project
|
458
|
+
def datasets(id = :all)
|
459
|
+
GoodData::Dataset[id, project: self, client: client]
|
460
|
+
end
|
461
|
+
|
462
|
+
def dimensions(id = :all)
|
463
|
+
GoodData::Dimension[id, client: client, project: self]
|
464
|
+
end
|
465
|
+
|
466
|
+
# Export a clone from a project to be later imported.
|
467
|
+
# If you do not want to do anything special and you do not need fine grained
|
468
|
+
# controle use clone method which does all the heavy lifting for you.
|
469
|
+
#
|
470
|
+
# @param options [Hash] Export options
|
471
|
+
# @option options [Boolean] :data Clone project with data
|
472
|
+
# @option options [Boolean] :users Clone project with users
|
473
|
+
# @option options [String] :authorized_users Comma separated logins of authorized users. Users that can use the export
|
474
|
+
# @return [String] token of the export
|
475
|
+
def export_clone(options = {})
|
476
|
+
with_data = options[:data].nil? ? true : options[:data]
|
477
|
+
with_users = options[:users].nil? ? false : options[:users]
|
478
|
+
|
479
|
+
export = {
|
480
|
+
:exportProject => {
|
481
|
+
:exportUsers => with_users ? 1 : 0,
|
482
|
+
:exportData => with_data ? 1 : 0
|
483
|
+
}
|
484
|
+
}
|
485
|
+
export[:exportProject][:authorizedUsers] = options[:authorized_users] if options[:authorized_users]
|
486
|
+
|
487
|
+
result = client.post("/gdc/md/#{obj_id}/maintenance/export", export)
|
488
|
+
status_url = result['exportArtifact']['status']['uri']
|
489
|
+
client.poll_on_response(status_url) do |body|
|
490
|
+
body['taskState']['status'] == 'RUNNING'
|
491
|
+
end
|
492
|
+
result['exportArtifact']['token']
|
493
|
+
end
|
494
|
+
|
495
|
+
def user_groups(id = :all, options = {})
|
496
|
+
GoodData::UserGroup[id, options.merge(project: self)]
|
497
|
+
end
|
498
|
+
|
499
|
+
# Imports a clone into current project. The project has to be freshly
|
500
|
+
# created.
|
501
|
+
#
|
502
|
+
# @param export_token [String] Export token of the package to be imported
|
503
|
+
# @return [Project] current project
|
504
|
+
def import_clone(export_token, options = {})
|
505
|
+
import = {
|
506
|
+
:importProject => {
|
507
|
+
:token => export_token
|
508
|
+
}
|
509
|
+
}
|
510
|
+
|
511
|
+
result = client.post("/gdc/md/#{obj_id}/maintenance/import", import)
|
512
|
+
status_url = result['uri']
|
513
|
+
client.poll_on_response(status_url, options) do |body|
|
514
|
+
body['taskState']['status'] == 'RUNNING'
|
515
|
+
end
|
516
|
+
self
|
517
|
+
end
|
518
|
+
|
519
|
+
def compute_report(spec = {})
|
520
|
+
GoodData::ReportDefinition.execute(spec.merge(client: client, project: self))
|
521
|
+
end
|
522
|
+
|
523
|
+
def compute_metric(expression)
|
524
|
+
GoodData::Metric.xexecute(expression, client: client, project: self)
|
525
|
+
end
|
526
|
+
|
527
|
+
alias_method :compute_measure, :compute_metric
|
528
|
+
|
529
|
+
def create_schedule(process, date, executable, options = {})
|
530
|
+
s = GoodData::Schedule.create(process, date, executable, options.merge(client: client, project: self))
|
531
|
+
s.save
|
532
|
+
end
|
533
|
+
|
534
|
+
def create_variable(data)
|
535
|
+
GoodData::Variable.create(data, client: client, project: self)
|
536
|
+
end
|
537
|
+
|
538
|
+
# Helper for getting dashboards of a project
|
539
|
+
#
|
540
|
+
# @param id [String | Number | Object] Anything that you can pass to GoodData::Dashboard[id]
|
541
|
+
# @return [GoodData::Dashboard | Array<GoodData::Dashboard>] dashboard instance or list
|
542
|
+
def dashboards(id = :all)
|
543
|
+
GoodData::Dashboard[id, project: self, client: client]
|
544
|
+
end
|
545
|
+
|
546
|
+
def data_permissions(id = :all)
|
547
|
+
GoodData::MandatoryUserFilter[id, client: client, project: self]
|
548
|
+
end
|
549
|
+
|
550
|
+
# Deletes project
|
551
|
+
def delete
|
552
|
+
fail "Project '#{title}' with id #{uri} is already deleted" if deleted?
|
553
|
+
client.delete(uri)
|
554
|
+
end
|
555
|
+
|
556
|
+
# Returns true if project is in deleted state
|
557
|
+
#
|
558
|
+
# @return [Boolean] Returns true if object deleted. False otherwise.
|
559
|
+
def deleted?
|
560
|
+
state == :deleted
|
561
|
+
end
|
562
|
+
|
563
|
+
# Helper for getting rid of all data in the project
|
564
|
+
#
|
565
|
+
# @option options [Boolean] :force has to be added otherwise the operation is not performed
|
566
|
+
# @return [Array] Result of executing MAQLs
|
567
|
+
def delete_all_data(options = {})
|
568
|
+
return false unless options[:force]
|
569
|
+
datasets.pmap(&:delete_data)
|
570
|
+
end
|
571
|
+
|
572
|
+
# Deletes dashboards for project
|
573
|
+
def delete_dashboards
|
574
|
+
Dashboard.all.map { |data| Dashboard[data['link']] }.each(&:delete)
|
575
|
+
end
|
576
|
+
|
577
|
+
def deploy_process(path, options = {})
|
578
|
+
GoodData::Process.deploy(path, options.merge(client: client, project: self))
|
579
|
+
end
|
580
|
+
|
581
|
+
# Executes DML expression. See (https://developer.gooddata.com/article/deleting-records-from-datasets)
|
582
|
+
# for some examples and explanations
|
583
|
+
#
|
584
|
+
# @param dml [String] DML expression
|
585
|
+
# @return [Hash] Result of executing DML
|
586
|
+
def execute_dml(dml, options = {})
|
587
|
+
uri = "/gdc/md/#{pid}/dml/manage"
|
588
|
+
result = client.post(uri, manage: { maql: dml })
|
589
|
+
polling_uri = result['uri']
|
590
|
+
|
591
|
+
client.poll_on_response(polling_uri, options) do |body|
|
592
|
+
body && body['taskState'] && body['taskState']['status'] == 'WAIT'
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# Executes MAQL expression and waits for it to be finished.
|
597
|
+
#
|
598
|
+
# @param maql [String] MAQL expression
|
599
|
+
# @return [Hash] Result of executing MAQL
|
600
|
+
def execute_maql(maql, options = {})
|
601
|
+
ldm_links = client.get(md[GoodData::Model::LDM_CTG])
|
602
|
+
ldm_uri = Links.new(ldm_links)[GoodData::Model::LDM_MANAGE_CTG]
|
603
|
+
response = client.post(ldm_uri, manage: { maql: maql })
|
604
|
+
polling_uri = response['entries'].first['link']
|
605
|
+
|
606
|
+
client.poll_on_response(polling_uri, options) do |body|
|
607
|
+
body && body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
# Helper for getting facts of a project
|
612
|
+
#
|
613
|
+
# @param [String | Number | Object] Anything that you can pass to GoodData::Fact[id]
|
614
|
+
# @return [GoodData::Fact | Array<GoodData::Fact>] fact instance or list
|
615
|
+
def facts(id = :all)
|
616
|
+
GoodData::Fact[id, project: self, client: client]
|
617
|
+
end
|
618
|
+
|
619
|
+
def fact_by_title(title)
|
620
|
+
GoodData::Fact.find_first_by_title(title, project: self, client: client)
|
621
|
+
end
|
622
|
+
|
623
|
+
def facts_by_title(title)
|
624
|
+
GoodData::Fact.find_by_title(title, project: self, client: client)
|
625
|
+
end
|
626
|
+
|
627
|
+
def find_attribute_element_value(uri)
|
628
|
+
GoodData::Attribute.find_element_value(uri, client: client, project: self)
|
629
|
+
end
|
630
|
+
|
631
|
+
# Get WebDav directory for project data
|
632
|
+
# @return [String]
|
633
|
+
def project_webdav_path
|
634
|
+
client.project_webdav_path(:project => self)
|
635
|
+
end
|
636
|
+
|
637
|
+
# Gets project role by its identifier
|
638
|
+
#
|
639
|
+
# @param [String] role_name Title of role to look for
|
640
|
+
# @return [GoodData::ProjectRole] Project role if found
|
641
|
+
def get_role_by_identifier(role_name, role_list = roles)
|
642
|
+
role_name = role_name.downcase.gsub(/role$/, '')
|
643
|
+
role_list.each do |role|
|
644
|
+
tmp_role_name = role.identifier.downcase.gsub(/role$/, '')
|
645
|
+
return role if tmp_role_name == role_name
|
646
|
+
end
|
647
|
+
nil
|
648
|
+
end
|
649
|
+
|
650
|
+
# Gets project role byt its summary
|
651
|
+
#
|
652
|
+
# @param [String] role_summary Summary of role to look for
|
653
|
+
# @return [GoodData::ProjectRole] Project role if found
|
654
|
+
def get_role_by_summary(role_summary, role_list = roles)
|
655
|
+
role_list.each do |role|
|
656
|
+
return role if role.summary.downcase == role_summary.downcase
|
657
|
+
end
|
658
|
+
nil
|
659
|
+
end
|
660
|
+
|
661
|
+
# Gets project role by its name
|
662
|
+
#
|
663
|
+
# @param [String] role_title Title of role to look for
|
664
|
+
# @return [GoodData::ProjectRole] Project role if found
|
665
|
+
def get_role_by_title(role_title, role_list = roles)
|
666
|
+
role_list.each do |role|
|
667
|
+
return role if role.title.downcase == role_title.downcase
|
668
|
+
end
|
669
|
+
nil
|
670
|
+
end
|
671
|
+
|
672
|
+
# Gets project role
|
673
|
+
#
|
674
|
+
# @param [String] role_title Title of role to look for
|
675
|
+
# @return [GoodData::ProjectRole] Project role if found
|
676
|
+
def get_role(role_name, role_list = roles)
|
677
|
+
return role_name if role_name.is_a? GoodData::ProjectRole
|
678
|
+
|
679
|
+
role_name.downcase!
|
680
|
+
role_list.each do |role|
|
681
|
+
return role if role.uri == role_name ||
|
682
|
+
role.identifier.downcase == role_name ||
|
683
|
+
role.identifier.downcase.gsub(/role$/, '') == role_name ||
|
684
|
+
role.title.downcase == role_name ||
|
685
|
+
role.summary.downcase == role_name
|
686
|
+
end
|
687
|
+
nil
|
688
|
+
end
|
689
|
+
|
690
|
+
# Gets user by its login or uri in various shapes
|
691
|
+
# It does not find by other information because that is not unique. If you want to search by name or email please
|
692
|
+
# use fuzzy_get_user.
|
693
|
+
#
|
694
|
+
# @param [String] name Name to look for
|
695
|
+
# @param [Array<GoodData::User>]user_list Optional cached list of users used for look-ups
|
696
|
+
# @return [GoodDta::Membership] User
|
697
|
+
def get_user(slug, user_list = users)
|
698
|
+
search_crit = if slug.respond_to?(:login)
|
699
|
+
slug.login || slug.uri
|
700
|
+
elsif slug.is_a?(Hash)
|
701
|
+
slug[:login] || slug[:uri]
|
702
|
+
else
|
703
|
+
slug
|
704
|
+
end
|
705
|
+
return nil unless search_crit
|
706
|
+
user_list.find do |user|
|
707
|
+
user.uri == search_crit.downcase ||
|
708
|
+
user.login.downcase == search_crit.downcase
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
def upload_file(file, options = {})
|
713
|
+
GoodData.upload_to_project_webdav(file, options.merge(project: self))
|
714
|
+
end
|
715
|
+
|
716
|
+
def download_file(file, where)
|
717
|
+
GoodData.download_from_project_webdav(file, where, project: self)
|
718
|
+
end
|
719
|
+
|
720
|
+
def environment
|
721
|
+
json['project']['content']['environment']
|
722
|
+
end
|
723
|
+
|
724
|
+
# Gets user by its email, full_name, login or uri
|
725
|
+
alias_method :member, :get_user
|
726
|
+
|
727
|
+
# Gets user by its email, full_name, login or uri.
|
728
|
+
#
|
729
|
+
# @param [String] name Name to look for
|
730
|
+
# @param [Array<GoodData::User>]user_list Optional cached list of users used for look-ups
|
731
|
+
# @return [GoodDta::Membership] User
|
732
|
+
def fuzzy_get_user(name, user_list = users)
|
733
|
+
return name if name.instance_of?(GoodData::Membership)
|
734
|
+
return member(name) if name.instance_of?(GoodData::Profile)
|
735
|
+
name = name.is_a?(Hash) ? name[:login] || name[:uri] : name
|
736
|
+
return nil unless name
|
737
|
+
name.downcase!
|
738
|
+
user_list.select do |user|
|
739
|
+
user.uri.downcase == name ||
|
740
|
+
user.login.downcase == name ||
|
741
|
+
user.email.downcase == name
|
742
|
+
end
|
743
|
+
nil
|
744
|
+
end
|
745
|
+
|
746
|
+
# Checks whether user has particular role in given proejct
|
747
|
+
#
|
748
|
+
# @param user [GoodData::Profile | GoodData::Membership | String] User in question. Can be passed by login (String), profile or membershi objects
|
749
|
+
# @param role_name [String || GoodData::ProjectRole] Project role cna be given by either string or GoodData::ProjectRole object
|
750
|
+
# @return [Boolean] Tru if user has role_name
|
751
|
+
def user_has_role?(user, role_name)
|
752
|
+
member = get_user(user)
|
753
|
+
role = get_role(role_name)
|
754
|
+
member.roles.include?(role)
|
755
|
+
rescue
|
756
|
+
false
|
757
|
+
end
|
758
|
+
|
759
|
+
# Initializes object instance from raw wire JSON
|
760
|
+
#
|
761
|
+
# @param json Json used for initialization
|
762
|
+
def initialize(json)
|
763
|
+
super
|
764
|
+
@json = json
|
765
|
+
end
|
766
|
+
|
767
|
+
# Invites new user to project
|
768
|
+
#
|
769
|
+
# @param email [String] User to be invited
|
770
|
+
# @param role [String] Role URL or Role ID to be used
|
771
|
+
# @param msg [String] Optional invite message
|
772
|
+
#
|
773
|
+
# TODO: Return invite object
|
774
|
+
def invite(email, role, msg = DEFAULT_INVITE_MESSAGE)
|
775
|
+
puts "Inviting #{email}, role: #{role}"
|
776
|
+
|
777
|
+
role_url = nil
|
778
|
+
if role.index('/gdc/') != 0
|
779
|
+
tmp = get_role(role)
|
780
|
+
role_url = tmp.uri if tmp
|
781
|
+
else
|
782
|
+
role_url = role if role_url.nil?
|
783
|
+
end
|
784
|
+
|
785
|
+
data = {
|
786
|
+
:invitations => [
|
787
|
+
{
|
788
|
+
:invitation => {
|
789
|
+
:content => {
|
790
|
+
:email => email,
|
791
|
+
:role => role_url,
|
792
|
+
:action => {
|
793
|
+
:setMessage => msg
|
794
|
+
}
|
795
|
+
}
|
796
|
+
}
|
797
|
+
}
|
798
|
+
]
|
799
|
+
}
|
800
|
+
|
801
|
+
url = "/gdc/projects/#{pid}/invitations"
|
802
|
+
client.post(url, data)
|
803
|
+
end
|
804
|
+
|
805
|
+
# Returns invitations to project
|
806
|
+
#
|
807
|
+
# @return [Array<GoodData::Invitation>] List of invitations
|
808
|
+
def invitations
|
809
|
+
invitations = client.get @json['project']['links']['invitations']
|
810
|
+
invitations['invitations'].pmap do |invitation|
|
811
|
+
client.create GoodData::Invitation, invitation
|
812
|
+
end
|
813
|
+
end
|
814
|
+
|
815
|
+
# Returns project related links
|
816
|
+
#
|
817
|
+
# @return [Hash] Project related links
|
818
|
+
def links
|
819
|
+
data['links']
|
820
|
+
end
|
821
|
+
|
822
|
+
# Helper for getting labels of a project
|
823
|
+
#
|
824
|
+
# @param [String | Number | Object] Anything that you can pass to
|
825
|
+
# GoodData::Label[id] + it supports :all as welll
|
826
|
+
# @return [GoodData::Fact | Array<GoodData::Fact>] fact instance or list
|
827
|
+
def labels(id = :all, opts = {})
|
828
|
+
if id == :all
|
829
|
+
attributes.pmapcat(&:labels).uniq
|
830
|
+
else
|
831
|
+
GoodData::Label[id, opts.merge(project: self, client: client)]
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
def md
|
836
|
+
@md ||= client.create(Links, client.get(data['links']['metadata']))
|
837
|
+
end
|
838
|
+
|
839
|
+
# Get data from project specific metadata storage
|
840
|
+
#
|
841
|
+
# @param [Symbol | String] :all or nothing for all keys or a string for value of specific key
|
842
|
+
# @return [Hash] key Hash of stored data
|
843
|
+
def metadata(key = :all)
|
844
|
+
GoodData::ProjectMetadata[key, client: client, project: self]
|
845
|
+
end
|
846
|
+
|
847
|
+
# Set data for specific key in project specific metadata storage
|
848
|
+
#
|
849
|
+
# @param [String] key key of the value to be stored
|
850
|
+
# @return [String] val value to be stored
|
851
|
+
def set_metadata(key, val)
|
852
|
+
GoodData::ProjectMetadata[key, client: client, project: self] = val
|
853
|
+
end
|
854
|
+
|
855
|
+
# Helper for getting metrics of a project
|
856
|
+
#
|
857
|
+
# @return [Array<GoodData::Metric>] matric instance or list
|
858
|
+
def metrics(id = :all, opts = { :full => true })
|
859
|
+
GoodData::Metric[id, opts.merge(project: self, client: client)]
|
860
|
+
end
|
861
|
+
|
862
|
+
alias_method :measures, :metrics
|
863
|
+
|
864
|
+
def metric_by_title(title)
|
865
|
+
GoodData::Metric.find_first_by_title(title, project: self, client: client)
|
866
|
+
end
|
867
|
+
|
868
|
+
alias_method :measure_by_title, :metric_by_title
|
869
|
+
|
870
|
+
def metrics_by_title(title)
|
871
|
+
GoodData::Metric.find_by_title(title, project: self, client: client)
|
872
|
+
end
|
873
|
+
|
874
|
+
alias_method :measures_by_title, :metrics_by_title
|
875
|
+
|
876
|
+
# Checks if the profile is member of project
|
877
|
+
#
|
878
|
+
# @param [GoodData::Profile] profile - Profile to be checked
|
879
|
+
# @param [Array<GoodData::Membership>] list Optional list of members to check against
|
880
|
+
# @return [Boolean] true if is member else false
|
881
|
+
def member?(profile, list = members)
|
882
|
+
!member(profile, list).nil?
|
883
|
+
end
|
884
|
+
|
885
|
+
def members?(profiles, list = members)
|
886
|
+
profiles.map { |p| member?(p, list) }
|
887
|
+
end
|
888
|
+
|
889
|
+
# Gets raw resource ID
|
890
|
+
#
|
891
|
+
# @return [String] Raw resource ID
|
892
|
+
def obj_id
|
893
|
+
uri.split('/').last
|
894
|
+
end
|
895
|
+
|
896
|
+
alias_method :pid, :obj_id
|
897
|
+
|
898
|
+
# Helper for getting objects of a project
|
899
|
+
#
|
900
|
+
# @return [Array<GoodData::MdObject>] object instance or list
|
901
|
+
def objects(id, opts = {})
|
902
|
+
GoodData::MdObject[id, opts.merge(project: self, client: client)]
|
903
|
+
end
|
904
|
+
|
905
|
+
# Transfer objects from one project to another
|
906
|
+
#
|
907
|
+
# @param [Array<GoodData::MdObject | String>, String, GoodData::MdObject] objs Any representation of the object or a list of those
|
908
|
+
# @param [Hash] options The options to migration.
|
909
|
+
# @option options [Number] :time_limit Time in seconds before the blocking call will fail. See GoodData::Rest::Client.poll_on_response for additional details
|
910
|
+
# @option options [Number] :sleep_interval Interval between polls on the status of the migration.
|
911
|
+
# @return [String] Returns token that you can use as input for object_import
|
912
|
+
def objects_export(objs, options = {})
|
913
|
+
fail 'Nothing to migrate. You have to pass list of objects, ids or uris that you would like to migrate' if objs.nil?
|
914
|
+
objs = Array(objs)
|
915
|
+
fail 'Nothing to migrate. The list you provided is empty' if objs.empty?
|
916
|
+
|
917
|
+
objs = objs.pmap { |obj| [obj, objects(obj)] }
|
918
|
+
fail ObjectsExportError, "Exporting objects failed with messages. Object #{objs.select { |_, obj| obj.nil? }.map { |o, _| o }.join(', ')} could not be found." if objs.any? { |_, obj| obj.nil? }
|
919
|
+
export_payload = {
|
920
|
+
:partialMDExport => {
|
921
|
+
:uris => objs.map { |_, obj| obj.uri }
|
922
|
+
}
|
923
|
+
}
|
924
|
+
result = client.post("#{md['maintenance']}/partialmdexport", export_payload)
|
925
|
+
polling_url = result['partialMDArtifact']['status']['uri']
|
926
|
+
token = result['partialMDArtifact']['token']
|
927
|
+
|
928
|
+
polling_result = client.poll_on_response(polling_url, options) do |body|
|
929
|
+
body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
|
930
|
+
end
|
931
|
+
if polling_result['wTaskStatus'] && polling_result['wTaskStatus']['status'] == 'ERROR'
|
932
|
+
messages = GoodData::Helpers.interpolate_error_messages(polling_result['wTaskStatus']['messages']).join(' ')
|
933
|
+
fail ObjectsExportError, "Exporting objects failed with messages. #{messages}"
|
934
|
+
end
|
935
|
+
token
|
936
|
+
end
|
937
|
+
|
938
|
+
# Import objects from import token. If you do not need specifically this method what you are probably looking for is transfer_objects. This is a lower level method.
|
939
|
+
#
|
940
|
+
# @param [String] token Migration token ID
|
941
|
+
# @param [Hash] options The options to migration.
|
942
|
+
# @option options [Number] :time_limit Time in seconds before the blocking call will fail. See GoodData::Rest::Client.poll_on_response for additional details
|
943
|
+
# @option options [Number] :sleep_interval Interval between polls on the status of the migration.
|
944
|
+
# @return [Boolean] Returns true if it succeeds or throws exceoption
|
945
|
+
def objects_import(token, options = {})
|
946
|
+
fail 'You need to provide a token for object import' if token.blank?
|
947
|
+
|
948
|
+
import_payload = {
|
949
|
+
:partialMDImport => {
|
950
|
+
:token => token,
|
951
|
+
:overwriteNewer => '1',
|
952
|
+
:updateLDMObjects => '0'
|
953
|
+
}
|
954
|
+
}
|
955
|
+
|
956
|
+
result = client.post("#{md['maintenance']}/partialmdimport", import_payload)
|
957
|
+
polling_url = result['uri']
|
958
|
+
|
959
|
+
polling_result = client.poll_on_response(polling_url, options) do |body|
|
960
|
+
body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
|
961
|
+
end
|
962
|
+
|
963
|
+
if polling_result['wTaskStatus']['status'] == 'ERROR'
|
964
|
+
messages = GoodData::Helpers.interpolate_error_messages(polling_result['wTaskStatus']['messages']).join(' ')
|
965
|
+
fail ObjectsImportError, "Importing objects failed with messages. #{messages}"
|
966
|
+
end
|
967
|
+
true
|
968
|
+
end
|
969
|
+
|
970
|
+
# Transfer objects from one project to another
|
971
|
+
#
|
972
|
+
# @param [Array<GoodData::MdObject | String>, String, GoodData::MdObject] objects Any representation of the object or a list of those
|
973
|
+
# @param [Hash] options The options to migration.
|
974
|
+
# @option options [GoodData::Project | String | Array<String> | Array<GoodData::Project>] :project Project(s) to migrate to
|
975
|
+
# @option options [Number] :batch_size Number of projects that are migrated at the same time. Default is 10
|
976
|
+
#
|
977
|
+
# @return [Boolean | Array<Hash>] Return either true or throws exception if you passed only one project. If you provided an array returns list of hashes signifying sucees or failure. Take note that in case of list of projects it does not throw exception
|
978
|
+
def partial_md_export(objects, options = {})
|
979
|
+
projects = options[:project]
|
980
|
+
batch_size = options[:batch_size] || 10
|
981
|
+
token = objects_export(objects)
|
982
|
+
|
983
|
+
if projects.is_a?(Array)
|
984
|
+
projects.each_slice(batch_size).flat_map do |batch|
|
985
|
+
batch.pmap do |proj|
|
986
|
+
begin
|
987
|
+
target_project = client.projects(proj)
|
988
|
+
target_project.objects_import(token, options)
|
989
|
+
{
|
990
|
+
project: target_project,
|
991
|
+
result: true
|
992
|
+
}
|
993
|
+
rescue RestClient::Exception => e
|
994
|
+
{
|
995
|
+
project: proj,
|
996
|
+
exception: e,
|
997
|
+
result: false,
|
998
|
+
reason: GoodData::Helpers.interpolate_error_message(MultiJson.load(e.response))
|
999
|
+
}
|
1000
|
+
rescue GoodData::ObjectsImportError => e
|
1001
|
+
{
|
1002
|
+
project: target_project,
|
1003
|
+
result: false,
|
1004
|
+
reason: e.message
|
1005
|
+
}
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
else
|
1010
|
+
target_project = client.projects(projects)
|
1011
|
+
target_project.objects_import(token, options)
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
alias_method :transfer_objects, :partial_md_export
|
1016
|
+
|
1017
|
+
# Helper for getting processes of a project
|
1018
|
+
#
|
1019
|
+
# @param [String | Number | Object] Anything that you can pass to GoodData::Report[id]
|
1020
|
+
# @return [GoodData::Report | Array<GoodData::Report>] report instance or list
|
1021
|
+
def processes(id = :all)
|
1022
|
+
GoodData::Process[id, project: self, client: client]
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
# Checks if this object instance is project
|
1026
|
+
#
|
1027
|
+
# @return [Boolean] Return true for all instances
|
1028
|
+
def project?
|
1029
|
+
true
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
def info
|
1033
|
+
results = blueprint.datasets.pmap do |ds|
|
1034
|
+
[ds, ds.count(self)]
|
1035
|
+
end
|
1036
|
+
puts title
|
1037
|
+
puts GoodData::Helpers.underline(title)
|
1038
|
+
puts
|
1039
|
+
puts "Datasets - #{results.count}"
|
1040
|
+
puts
|
1041
|
+
results.each do |x|
|
1042
|
+
dataset, count = x
|
1043
|
+
dataset.title.tap do |t|
|
1044
|
+
puts t
|
1045
|
+
puts GoodData::Helpers.underline(t)
|
1046
|
+
puts "Size - #{count} rows"
|
1047
|
+
puts "#{dataset.attributes_and_anchors.count} attributes, #{dataset.facts.count} facts, #{dataset.references.count} references"
|
1048
|
+
puts
|
1049
|
+
end
|
1050
|
+
end
|
1051
|
+
nil
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
# Forces project to reload
|
1055
|
+
def reload!
|
1056
|
+
if saved?
|
1057
|
+
response = client.get(uri)
|
1058
|
+
@json = response
|
1059
|
+
end
|
1060
|
+
self
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
# Method used for walking through objects in project and trying to replace all occurences of some object for another object. This is typically used as a means for exchanging Date dimensions.
|
1064
|
+
#
|
1065
|
+
# @param mapping [Array<Array>] Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping.
|
1066
|
+
def replace_from_mapping(mapping, opts = {})
|
1067
|
+
default = {
|
1068
|
+
:purge => false,
|
1069
|
+
:dry_run => false
|
1070
|
+
}
|
1071
|
+
opts = default.merge(opts)
|
1072
|
+
dry_run = opts[:dry_run]
|
1073
|
+
|
1074
|
+
if opts[:purge]
|
1075
|
+
GoodData.logger.info 'Purging old project definitions'
|
1076
|
+
reports.peach(&:purge_report_of_unused_definitions!)
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
fail ArgumentError, 'No mapping specified' if mapping.blank?
|
1080
|
+
rds = report_definitions
|
1081
|
+
|
1082
|
+
{
|
1083
|
+
# data_permissions: data_permissions,
|
1084
|
+
variables: variables,
|
1085
|
+
dashboards: dashboards,
|
1086
|
+
metrics: metrics,
|
1087
|
+
report_definitions: rds
|
1088
|
+
}.each do |key, collection|
|
1089
|
+
puts "Replacing #{key}"
|
1090
|
+
collection.peach do |item|
|
1091
|
+
new_item = item.replace(mapping)
|
1092
|
+
if new_item.json != item.json
|
1093
|
+
if dry_run
|
1094
|
+
GoodData.logger.info "Would save #{new_item.uri}. Running in dry run mode"
|
1095
|
+
else
|
1096
|
+
GoodData.logger.info "Saving #{new_item.uri}"
|
1097
|
+
new_item.save
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
end
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
GoodData.logger.info 'Replacing hidden metrics'
|
1104
|
+
local_metrics = rds.pmapcat { |rd| rd.using('metric') }.select { |m| m['deprecated'] == '1' }
|
1105
|
+
puts "Found #{local_metrics.count} metrics"
|
1106
|
+
local_metrics.pmap { |m| metrics(m['link']) }.peach do |item|
|
1107
|
+
new_item = item.replace(mapping)
|
1108
|
+
if new_item.json != item.json
|
1109
|
+
if dry_run
|
1110
|
+
GoodData.logger.info "Would save #{new_item.uri}. Running in dry run mode"
|
1111
|
+
else
|
1112
|
+
GoodData.logger.info "Saving #{new_item.uri}"
|
1113
|
+
new_item.save
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
GoodData.logger.info 'Replacing variable values'
|
1119
|
+
variables.each do |var|
|
1120
|
+
var.values.peach do |val|
|
1121
|
+
val.replace(mapping).save unless dry_run
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
nil
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
# Helper for getting reports of a project
|
1128
|
+
#
|
1129
|
+
# @param [String | Number | Object] Anything that you can pass to GoodData::Report[id]
|
1130
|
+
# @return [GoodData::Report | Array<GoodData::Report>] report instance or list
|
1131
|
+
def reports(id = :all)
|
1132
|
+
GoodData::Report[id, project: self, client: client]
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
# Helper for getting report definitions of a project
|
1136
|
+
#
|
1137
|
+
# @param [String | Number | Object] Anything that you can pass to GoodData::ReportDefinition[id]
|
1138
|
+
# @return [GoodData::ReportDefinition | Array<GoodData::ReportDefinition>] report definition instance or list
|
1139
|
+
def report_definitions(id = :all, options = {})
|
1140
|
+
GoodData::ReportDefinition[id, options.merge(project: self, client: client)]
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
# Gets the list or project roles
|
1144
|
+
#
|
1145
|
+
# @return [Array<GoodData::ProjectRole>] List of roles
|
1146
|
+
def roles
|
1147
|
+
url = "/gdc/projects/#{pid}/roles"
|
1148
|
+
|
1149
|
+
tmp = client.get(url)
|
1150
|
+
tmp['projectRoles']['roles'].pmap do |role_url|
|
1151
|
+
json = client.get role_url
|
1152
|
+
client.create(GoodData::ProjectRole, json, project: self)
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
# Saves project
|
1157
|
+
def save
|
1158
|
+
data_to_send = GoodData::Helpers.deep_dup(raw_data)
|
1159
|
+
data_to_send['project']['content'].delete('cluster')
|
1160
|
+
data_to_send['project']['content'].delete('isPublic')
|
1161
|
+
data_to_send['project']['content'].delete('state')
|
1162
|
+
response = if uri
|
1163
|
+
client.post(PROJECT_PATH % pid, data_to_send)
|
1164
|
+
client.get uri
|
1165
|
+
else
|
1166
|
+
result = client.post(PROJECTS_PATH, data_to_send)
|
1167
|
+
client.get result['uri']
|
1168
|
+
end
|
1169
|
+
@json = response
|
1170
|
+
self
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
# Schedules an email with dashboard or report content
|
1174
|
+
def schedule_mail(options = GoodData::ScheduledMail::DEFAULT_OPTS)
|
1175
|
+
GoodData::ScheduledMail.create(options.merge(client: client, project: self))
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
def scheduled_mails(options = { :full => false })
|
1179
|
+
GoodData::ScheduledMail[:all, options.merge(project: self, client: client)]
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
# @param [String | Number | Object] Anything that you can pass to GoodData::Schedule[id]
|
1183
|
+
# @return [GoodData::Schedule | Array<GoodData::Schedule>] schedule instance or list
|
1184
|
+
def schedules(id = :all)
|
1185
|
+
GoodData::Schedule[id, project: self, client: client]
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
# Gets SLIs data
|
1189
|
+
#
|
1190
|
+
# @return [GoodData::Metadata] SLI Metadata
|
1191
|
+
def slis
|
1192
|
+
link = "#{data['links']['metadata']}#{SLIS_PATH}"
|
1193
|
+
|
1194
|
+
# FIXME: Review what to do with passed extra argument
|
1195
|
+
Metadata.new client.get(link)
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
# Gets project state
|
1199
|
+
#
|
1200
|
+
# @return [String] Project state
|
1201
|
+
def state
|
1202
|
+
data['content']['state'].downcase.to_sym if data['content'] && data['content']['state']
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
Project.metadata_property_reader :summary, :title
|
1206
|
+
|
1207
|
+
# Gets project title
|
1208
|
+
#
|
1209
|
+
# @return [String] Project title
|
1210
|
+
def title=(a_title)
|
1211
|
+
data['meta']['title'] = a_title if data['meta']
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
# Uploads file to project
|
1215
|
+
#
|
1216
|
+
# @param file File to be uploaded
|
1217
|
+
# @param schema Schema to be used
|
1218
|
+
def upload(data, blueprint, dataset_name, options = {})
|
1219
|
+
GoodData::Model.upload_data(data, blueprint, dataset_name, options.merge(client: client, project: self))
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
def upload_multiple(data, blueprint, options = {})
|
1223
|
+
GoodData::Model.upload_multiple_data(data, blueprint, options.merge(client: client, project: self))
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
def uri
|
1227
|
+
data['links']['self'] if data && data['links'] && data['links']['self']
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
# List of user filters within this project
|
1231
|
+
#
|
1232
|
+
# @return [Array<GoodData::MandatoryUserFilter>] List of mandatory user
|
1233
|
+
def user_filters
|
1234
|
+
url = "/gdc/md/#{pid}/userfilters"
|
1235
|
+
|
1236
|
+
tmp = client.get(url)
|
1237
|
+
tmp['userFilters']['items'].pmap do |filter|
|
1238
|
+
client.create(GoodData::MandatoryUserFilter, filter, project: self)
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
# List of users in project
|
1243
|
+
#
|
1244
|
+
#
|
1245
|
+
# @return [Array<GoodData::User>] List of users
|
1246
|
+
def users(opts = {})
|
1247
|
+
client = client(opts)
|
1248
|
+
Enumerator.new do |y|
|
1249
|
+
offset = opts[:offset] || 0
|
1250
|
+
limit = opts[:limit] || 1_000
|
1251
|
+
loop do
|
1252
|
+
tmp = client.get("/gdc/projects/#{pid}/users", params: { offset: offset, limit: limit })
|
1253
|
+
tmp['users'].each do |user_data|
|
1254
|
+
user = client.create(GoodData::Membership, user_data, project: self)
|
1255
|
+
y << user if opts[:all] || user && user.enabled?
|
1256
|
+
end
|
1257
|
+
break if tmp['users'].count < limit
|
1258
|
+
offset += limit
|
1259
|
+
end
|
1260
|
+
end
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
alias_method :members, :users
|
1264
|
+
|
1265
|
+
def whitelist_users(new_users, users_list, whitelist, mode = :exclude)
|
1266
|
+
return [new_users, users_list] unless whitelist
|
1267
|
+
|
1268
|
+
new_whitelist_proc = proc do |user|
|
1269
|
+
whitelist.any? { |wl| wl.is_a?(Regexp) ? user[:login] =~ wl : user[:login].include?(wl) }
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
whitelist_proc = proc do |user|
|
1273
|
+
whitelist.any? { |wl| wl.is_a?(Regexp) ? user.login =~ wl : user.login.include?(wl) }
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
if mode == :include
|
1277
|
+
[new_users.select(&new_whitelist_proc), users_list.select(&whitelist_proc)]
|
1278
|
+
elsif mode == :exclude
|
1279
|
+
[new_users.reject(&new_whitelist_proc), users_list.reject(&whitelist_proc)]
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
# Imports users
|
1284
|
+
def import_users(new_users, options = {})
|
1285
|
+
role_list = roles
|
1286
|
+
users_list = users(all: true)
|
1287
|
+
new_users = new_users.map { |x| (x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash }
|
1288
|
+
|
1289
|
+
GoodData.logger.warn("Importing users to project (#{pid})")
|
1290
|
+
|
1291
|
+
whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
|
1292
|
+
|
1293
|
+
# First check that if groups are provided we have them set up
|
1294
|
+
check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq)
|
1295
|
+
|
1296
|
+
# conform the role on list of new users so we can diff them with the users coming from the project
|
1297
|
+
diffable_new_with_default_role = whitelisted_new_users.map do |u|
|
1298
|
+
u[:role] = Array(u[:role] || u[:roles] || 'readOnlyUser')
|
1299
|
+
u
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
intermediate_new = diffable_new_with_default_role.map do |u|
|
1303
|
+
u[:role] = u[:role].map do |r|
|
1304
|
+
role = get_role(r, role_list)
|
1305
|
+
role && role.uri
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
if u[:role].all?(&:nil?)
|
1309
|
+
u[:type] = :error
|
1310
|
+
u[:reason] = 'Invalid role(s) specified'
|
1311
|
+
else
|
1312
|
+
u[:type] = :ok
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
u[:status] = 'ENABLED'
|
1316
|
+
u
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
intermediate_new_by_type = intermediate_new.group_by { |i| i[:type] }
|
1320
|
+
diffable_new = intermediate_new_by_type[:ok] || []
|
1321
|
+
|
1322
|
+
# Diff users. Only login and role is important for the diff
|
1323
|
+
diff = GoodData::Helpers.diff(whitelisted_users, diffable_new, key: :login, fields: [:login, :role, :status])
|
1324
|
+
|
1325
|
+
# Create new users
|
1326
|
+
u = diff[:added].map { |x| { user: x, role: x[:role] } }
|
1327
|
+
|
1328
|
+
results = []
|
1329
|
+
GoodData.logger.warn("Creating #{diff[:added].count} users in project (#{pid})")
|
1330
|
+
results.concat(create_users(u, roles: role_list, project_users: whitelisted_users))
|
1331
|
+
|
1332
|
+
# # Update existing users
|
1333
|
+
GoodData.logger.warn("Updating #{diff[:changed].count} users in project (#{pid})")
|
1334
|
+
list = diff[:changed].map { |x| { user: x[:new_obj], role: x[:new_obj][:role] || x[:new_obj][:roles] } }
|
1335
|
+
results.concat(set_users_roles(list, roles: role_list, project_users: whitelisted_users))
|
1336
|
+
|
1337
|
+
# Remove old users
|
1338
|
+
to_remove = diff[:removed].reject { |user| user[:status] == 'DISABLED' || user[:status] == :disabled }
|
1339
|
+
GoodData.logger.warn("Removing #{to_remove.count} users from project (#{pid})")
|
1340
|
+
results.concat(disable_users(to_remove))
|
1341
|
+
|
1342
|
+
# reassign to groups
|
1343
|
+
mappings = new_users.map(&:to_hash).flat_map do |user|
|
1344
|
+
groups = user[:user_group] || []
|
1345
|
+
groups.map { |g| [user[:login], g] }
|
1346
|
+
end
|
1347
|
+
unless mappings.empty?
|
1348
|
+
users_lookup = users.reduce({}) do |a, e|
|
1349
|
+
a[e.login] = e
|
1350
|
+
a
|
1351
|
+
end
|
1352
|
+
mappings.group_by { |_, g| g }.each do |g, mapping|
|
1353
|
+
# find group + set users
|
1354
|
+
# CARE YOU DO NOT KNOW URI
|
1355
|
+
user_groups(g).set_members(mapping.map { |user, _| user }.map { |login| users_lookup[login] && users_lookup[login].uri })
|
1356
|
+
end
|
1357
|
+
mentioned_groups = mappings.map(&:last).uniq
|
1358
|
+
groups_to_cleanup = user_groups.reject { |g| mentioned_groups.include?(g.name) }
|
1359
|
+
# clean all groups not mentioned with exception of whitelisted users
|
1360
|
+
groups_to_cleanup.each do |g|
|
1361
|
+
g.set_members(whitelist_users(g.members.map(&:to_hash), [], options[:whitelists], :include).first.map { |x| x[:uri] })
|
1362
|
+
end
|
1363
|
+
end
|
1364
|
+
results
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
def disable_users(list)
|
1368
|
+
list = list.map(&:to_hash)
|
1369
|
+
url = "#{uri}/users"
|
1370
|
+
payloads = list.map do |u|
|
1371
|
+
generate_user_payload(u[:uri], 'DISABLED')
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
payloads.each_slice(100).mapcat do |payload|
|
1375
|
+
result = client.post(url, 'users' => payload)
|
1376
|
+
result['projectUsersUpdateResult'].mapcat { |k, v| v.map { |x| { type: k.to_sym, uri: x } } }
|
1377
|
+
end
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
def check_groups(specified_groups)
|
1381
|
+
groups = user_groups.map(&:name)
|
1382
|
+
missing_groups = specified_groups - groups
|
1383
|
+
fail "All groups have to be specified before you try to import users. Groups that are currently in project are #{groups.join(',')} and you asked for #{missing_groups.join(',')}" unless missing_groups.empty?
|
1384
|
+
end
|
1385
|
+
|
1386
|
+
# Update user
|
1387
|
+
#
|
1388
|
+
# @param user User to be updated
|
1389
|
+
# @param desired_roles Roles to be assigned to user
|
1390
|
+
# @param role_list Optional cached list of roles used for lookups
|
1391
|
+
def set_user_roles(login, desired_roles, options = {})
|
1392
|
+
user_uri, roles = resolve_roles(login, desired_roles, options)
|
1393
|
+
url = "#{uri}/users"
|
1394
|
+
payload = generate_user_payload(user_uri, 'ENABLED', roles)
|
1395
|
+
res = client.post(url, payload)
|
1396
|
+
failure = GoodData::Helpers.get_path(res, %w(projectUsersUpdateResult failed))
|
1397
|
+
fail ArgumentError, "User #{user_uri} could not be aded. #{failure.first['message']}" unless failure.blank?
|
1398
|
+
res
|
1399
|
+
end
|
1400
|
+
alias_method :add_user, :set_user_roles
|
1401
|
+
|
1402
|
+
# Update list of users
|
1403
|
+
#
|
1404
|
+
# @param list List of users to be updated
|
1405
|
+
# @param role_list Optional list of cached roles to prevent unnecessary server round-trips
|
1406
|
+
def set_users_roles(list, options = {})
|
1407
|
+
return [] if list.empty?
|
1408
|
+
role_list = options[:roles] || roles
|
1409
|
+
project_users = options[:project_users] || users
|
1410
|
+
|
1411
|
+
intermediate_users = list.flat_map do |user_hash|
|
1412
|
+
user = user_hash[:user] || user_hash[:login]
|
1413
|
+
desired_roles = user_hash[:role] || user_hash[:roles] || 'readOnlyUser'
|
1414
|
+
begin
|
1415
|
+
login, roles = resolve_roles(user, desired_roles, options.merge(project_users: project_users, roles: role_list))
|
1416
|
+
[{ :type => :successful, user: login, roles: roles }]
|
1417
|
+
rescue => e
|
1418
|
+
[{ :type => :failed, :reason => e.message, user: login, roles: roles }]
|
1419
|
+
end
|
1420
|
+
end
|
1421
|
+
|
1422
|
+
# User can fail pre sending to API during resolving roles. We add only users that passed that step.
|
1423
|
+
users_by_type = intermediate_users.group_by { |u| u[:type] }
|
1424
|
+
users_to_add = users_by_type[:successful] || []
|
1425
|
+
|
1426
|
+
payloads = users_to_add.map { |u| generate_user_payload(u[:user], 'ENABLED', u[:roles]) }
|
1427
|
+
results = payloads.each_slice(100).map do |payload|
|
1428
|
+
client.post("#{uri}/users", 'users' => payload)
|
1429
|
+
end
|
1430
|
+
# this ugly line turns the hash of errors into list of errors with types so we can process them easily
|
1431
|
+
typed_results = results.flat_map { |x| x['projectUsersUpdateResult'].flat_map { |k, v| v.map { |v_2| v_2.is_a?(String) ? { type: k.to_sym, user: v_2 } : GoodData::Helpers.symbolize_keys(v_2).merge(type: k.to_sym) } } }
|
1432
|
+
# we have to concat errors from role resolution and API result
|
1433
|
+
typed_results + (users_by_type[:failed] || [])
|
1434
|
+
end
|
1435
|
+
|
1436
|
+
alias_method :add_users, :set_users_roles
|
1437
|
+
alias_method :create_users, :set_users_roles
|
1438
|
+
|
1439
|
+
def add_data_permissions(filters, options = {})
|
1440
|
+
GoodData::UserFilterBuilder.execute_mufs(filters, { client: client, project: self }.merge(options))
|
1441
|
+
end
|
1442
|
+
|
1443
|
+
def add_variable_permissions(filters, var, options = {})
|
1444
|
+
GoodData::UserFilterBuilder.execute_variables(filters, var, { client: client, project: self }.merge(options))
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
# Run validation on project
|
1448
|
+
# Valid settins for validation are (default all):
|
1449
|
+
# ldm - Checks the consistency of LDM objects.
|
1450
|
+
# pdm Checks LDM to PDM mapping consistency, also checks PDM reference integrity.
|
1451
|
+
# metric_filter - Checks metadata for inconsistent metric filters.
|
1452
|
+
# invalid_objects - Checks metadata for invalid/corrupted objects.
|
1453
|
+
# asyncTask response
|
1454
|
+
def validate(filters = %w(ldm pdm metric_filter invalid_objects), options = {})
|
1455
|
+
response = client.post "#{md['validate-project']}", 'validateProject' => filters
|
1456
|
+
polling_link = response['asyncTask']['link']['poll']
|
1457
|
+
client.poll_on_response(polling_link, options) do |body|
|
1458
|
+
body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
|
1459
|
+
end
|
1460
|
+
end
|
1461
|
+
|
1462
|
+
def variables(id = :all, options = { client: client, project: self })
|
1463
|
+
GoodData::Variable[id, options]
|
1464
|
+
end
|
1465
|
+
|
1466
|
+
def update_from_blueprint(blueprint, options = {})
|
1467
|
+
GoodData::Model::ProjectCreator.migrate(options.merge(spec: blueprint, token: options[:auth_token], client: client, project: self))
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
def resolve_roles(login, desired_roles, options = {})
|
1471
|
+
user = if login.is_a?(String) && login.include?('@')
|
1472
|
+
'/gdc/account/profile/' + login
|
1473
|
+
elsif login.is_a?(String)
|
1474
|
+
login
|
1475
|
+
elsif login.is_a?(Hash) && login[:login]
|
1476
|
+
'/gdc/account/profile/' + login[:login]
|
1477
|
+
elsif login.is_a?(Hash) && login[:uri]
|
1478
|
+
login[:uri]
|
1479
|
+
elsif login.respond_to?(:uri) && login.uri
|
1480
|
+
login.uri
|
1481
|
+
elsif login.respond_to?(:login) && login.login
|
1482
|
+
'/gdc/account/profile/' + login.login
|
1483
|
+
else
|
1484
|
+
fail "Unsupported user specification #{login}"
|
1485
|
+
end
|
1486
|
+
|
1487
|
+
role_list = options[:roles] || roles
|
1488
|
+
desired_roles = Array(desired_roles)
|
1489
|
+
roles = desired_roles.map do |role_name|
|
1490
|
+
role = get_role(role_name, role_list)
|
1491
|
+
fail ArgumentError, "Invalid role '#{role_name}' specified for user '#{user.email}'" if role.nil?
|
1492
|
+
role.uri
|
1493
|
+
end
|
1494
|
+
[user, roles]
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
private
|
1498
|
+
|
1499
|
+
def generate_user_payload(user_uri, status = 'ENABLED', roles_uri = nil)
|
1500
|
+
payload = {
|
1501
|
+
'user' => {
|
1502
|
+
'content' => {
|
1503
|
+
'status' => status
|
1504
|
+
},
|
1505
|
+
'links' => {
|
1506
|
+
'self' => user_uri
|
1507
|
+
}
|
1508
|
+
}
|
1509
|
+
}
|
1510
|
+
payload['user']['content']['userRoles'] = roles_uri if roles_uri
|
1511
|
+
payload
|
1512
|
+
end
|
1513
|
+
end
|
1514
|
+
end
|