# encoding: UTF-8 # # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved. # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. require_relative '../metadata' require_relative 'metadata' # GoodData Module module GoodData # Report Definition # TODO: Add more doc ... class ReportDefinition < GoodData::MdObject class << self # Method intended to get all objects of that type in a specified project # # @param options [Hash] the options hash # @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default # @return [Array | Array] Return the appropriate metadata objects or their representation def all(options = { :client => GoodData.connection, :project => GoodData.project }) query('reportDefinition', ReportDefinition, options) end def create_metrics_part(left, top) stuff = Array(left) + Array(top) stuff.select { |item| item.respond_to?(:metric?) && item.metric? }.map do |metric| create_metric_part(metric) end end alias_method :create_measures_part, :create_metrics_part def create_metric_part(metric) { 'alias' => metric.title, 'uri' => metric.uri } end alias_method :create_measure_part, :create_metric_part def create_attribute_part(attrib) { 'attribute' => { 'alias' => '', 'totals' => [], 'uri' => attrib.uri } } end # Method creates the list of filter representaion suitable for posting on the api. It can currently recognize 2 types of filters. Variable filters and attribute filters. Method for internal usage # # @param filters [GoodData::Variable|Array] # @param options [Hash] the options hash # @return [Array] Returns the structure that is stored internally in the report definition and later psted on the API def create_filters_part(filters, options = {}) project = options[:project] vars = filters.select { |f| f.is_a?(GoodData::Variable) }.map { |v| { expression: "[#{v.uri}]" } } category = filters.select { |f| f.is_a?(Array) }.map { |v| GoodData::SmallGoodZilla.create_category_filter(v, project) } vars + category end def create_part(stuff) stuff = Array(stuff) parts = stuff.reduce([]) do |memo, item| if item.respond_to?(:metric?) && item.metric? memo else memo << create_attribute_part(item) end memo end if stuff.any? { |item| item.respond_to?(:metric?) && item.metric? } parts << 'metricGroup' end parts end def find(stuff, opts = { :client => GoodData.connection, :project => GoodData.project }) _client, project = GoodData.get_client_and_project(opts) stuff.map do |item| obj = if item.is_a?(String) begin project.objects(item) rescue RestClient::ResourceNotFound raise "Object given by id \"#{item}\" could not be found" end elsif item.is_a?(Hash) && item.keys.include?(:title) case item[:type].to_s when 'metric' GoodData::Metric.find_first_by_title(item[:title], opts) when 'attribute' GoodData::Attribute.find_first_by_title(item[:title], opts) end elsif item.is_a?(Hash) && (item.keys.include?(:id) || item.keys.include?(:identifier)) id = item[:id] || item[:identifier] case item[:type].to_s when 'metric' project.metrics(id) when 'attribute' project.attributes(id) when 'label' projects.labels(id) end else item end if obj.respond_to?(:attribute?) && obj.attribute? obj.display_forms.first else obj end end end def execute(options = {}) left = Array(options[:left]) top = Array(options[:top]) metrics = (left + top).select { |item| item.respond_to?(:metric?) && item.metric? } unsaved_metrics = metrics.reject(&:saved?) unsaved_metrics.each { |m| m.title = 'Untitled metric' unless m.title } begin unsaved_metrics.each(&:save) GoodData::ReportDefinition.create(options).execute ensure unsaved_metrics.each { |m| m.delete if m && m.saved? } end end def create(options = { :client => GoodData.connection, :project => GoodData.project }) client, project = GoodData.get_client_and_project(options) left = Array(options[:left]) top = Array(options[:top]) filters = options[:filters] || [] left = ReportDefinition.find(left, options) top = ReportDefinition.find(top, options) # TODO: Put somewhere for i18n fail_msg = 'All metrics in report definition must be saved' fail fail_msg unless (left + top).all?(&:saved?) pars = { 'reportDefinition' => { 'content' => { 'grid' => { 'sort' => { 'columns' => [], 'rows' => [] }, 'columnWidths' => [], 'columns' => ReportDefinition.create_part(top), 'metrics' => ReportDefinition.create_metrics_part(left, top), 'rows' => ReportDefinition.create_part(left) }, 'format' => 'grid', 'filters' => ReportDefinition.create_filters_part(filters, :project => project) }, 'meta' => { 'tags' => '', 'summary' => '', 'title' => 'Untitled report definition' } } } # TODO: write test for report definitions with explicit identifiers pars['reportDefinition']['meta']['identifier'] = options[:identifier] if options[:identifier] client.create(ReportDefinition, pars, :project => project) end end def attribute_parts cols = content['grid']['columns'] || [] rows = content['grid']['rows'] || [] items = cols + rows items.select { |item| item.is_a?(Hash) && item.keys.first == 'attribute' } end def attributes labels.map(&:attribute) end # Removes the color mapping from report definition # # @return [GoodData::ReportDefinition] Returns self def reset_color_mapping! global_chart_options = GoodData::Helpers.get_path(content, %w(chart styles global)) global_chart_options['colorMapping'] = [] if global_chart_options self end # Return true if the report definition is a chart # # @return [Boolean] Return true if report definition is a chart def chart? !table? end def labels attribute_parts.map { |part| project.labels(part['attribute']['uri']) } end def metric_parts content['grid']['metrics'] end alias_method :measure_parts, :metric_parts def metrics metric_parts.map { |i| project.metrics(i['uri']) } end def execute(opts = {}) result = if saved? pars = { 'report_req' => { 'reportDefinition' => uri } } client.post '/gdc/xtab2/executor3', pars else data = { report_req: { definitionContent: { content: to_hash, projectMetadata: project.links['metadata'] } } } uri = "/gdc/app/projects/#{project.pid}/execute" client.post(uri, data) end GoodData::Report.data_result(result, opts.merge(client: client)) end def filters content['filters'].map { |f| f['expression'] } end # Method used for replacing values in their state according to mapping. Can be used to replace any values but it is typically used to replace the URIs. Returns a new object of the same type. # # @param [Array]Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping. # @return [GoodData::ReportDefinition] def replace(mapping) x = GoodData::MdObject.replace_quoted(self, mapping) x = GoodData::MdObject.replace_bracketed(x, mapping) vals = GoodData::MdObject.find_replaceable_values(self, mapping) GoodData::MdObject.replace_quoted(x, vals) GoodData::MdObject.replace_bracketed(x, vals) end # Return true if the report definition is a table # # @return [Boolean] Return true if report definition is a table def table? content['format'] == 'grid' end end end