require 'java' require 'uri' require_relative './api_helpers.rb' require_relative './query_helper.rb' require_relative './api_formatters.rb' module Mondrian::REST class Api < Grape::API version '1', using: :header, vendor: 'mondrian_rest' default_format :json helpers Mondrian::REST::APIHelpers helpers Mondrian::REST::QueryHelper helpers do def logger Api.logger end end resource :mdx do content_type :xls, 'application/vnd.ms-excel' formatter :xls, Mondrian::REST::Formatters::XLS content_type :csv, 'text/csv' formatter :csv, Mondrian::REST::Formatters::CSV content_type :json, 'application/json' formatter :json, Mondrian::REST::Formatters::AggregationJSON content_type :jsonrecords, 'application/x-jsonrecords' formatter :jsonrecords, Mondrian::REST::Formatters::JSONRecords desc 'Execute an MDX query against a cube' content_type :txt, 'text/plain' params do optional :parents, type: Boolean, desc: "Include members' ancestors" optional :debug, type: Boolean, desc: 'Include generated MDX', default: false optional :properties, type: Array, desc: 'Include member properties' optional :caption, type: Array, desc: 'Replace caption with property', default: [] end post do status 200 rbody = request.body.read.force_encoding('utf-8') mdx(rbody) end end resource :flush do params do requires :secret, type: String, desc: 'Secret key' end content_type :json, 'application/json' desc 'Flush the schema cache' get do if ENV['MONDRIAN_REST_SECRET'].nil? error!('Please set MONDRIAN_REST_SECRET to use this endpoint', 403) end if params[:secret] != ENV['MONDRIAN_REST_SECRET'] error!('Invalid secret key.', 403) end { 'status' => olap_flush } end end resource :cubes do content_type :json, 'application/json' default_format :json desc "Returns the cubes defined in this server's schema" get do { 'cubes' => olap.cube_names.map { |cn| olap.cube(cn).to_h } } end route_param :cube_name do desc 'Return a cube' params do requires :cube_name, type: String, desc: 'Cube name' end get do cube = get_cube_or_404(params[:cube_name]) cube.to_h end resource :members do desc 'return a member by its full name' params do requires :full_name, type: String, regexp: /[a-z0-9\.,\-\s%\[\]\(\)]+/i end get do member_full_name = URI.decode(params[:full_name]) m = get_member(get_cube_or_404(params[:cube_name]), member_full_name) if m.nil? error!("Member `#{member_full_name}` not found in cube `#{params[:cube_name]}`", 404) end m.to_h.merge( ancestors: m.ancestors.map(&:to_h), dimension: m.dimension_info ) end end resource :aggregate do content_type :xls, 'application/vnd.ms-excel' formatter :xls, Mondrian::REST::Formatters::XLS content_type :csv, 'text/csv' formatter :csv, Mondrian::REST::Formatters::CSV content_type :json, 'application/json' formatter :json, Mondrian::REST::Formatters::AggregationJSON content_type :jsonrecords, 'application/x-jsonrecords' formatter :jsonrecords, Mondrian::REST::Formatters::JSONRecords rescue_from PropertyError do |e| error!({ error: e }, 400) end desc 'aggregate from query parameters' params do optional :measures, type: Array optional :cut, type: Array, desc: 'Specification of slicer axis' optional :drilldown, type: Array, desc: 'Dimension(s) to be drilled down' optional :nonempty, type: Boolean, desc: 'Only return non empty cells' optional :sparse, type: Boolean, desc: 'Skip rows where all measures are null (only applies to CSV, XLS and JSONRECORDS)', default: !java.lang.System.getProperty('mondrian-rest.sparseDefault').nil? optional :distinct, type: Boolean, desc: 'Apply DISTINCT() to every axis' optional :parents, type: Boolean, desc: "Include members' ancestors" optional :debug, type: Boolean, desc: 'Include generated MDX', default: false optional :properties, type: Array, desc: 'Include member properties' optional :caption, type: Array, desc: 'Replace caption with property', default: [] optional :filter, type: Array, desc: "Filter by measure value. Accepts: #{Mondrian::REST::QueryHelper::VALID_FILTER_OPS.join(', ')}" optional :order, type: String, desc: 'Sort by measure or property name' optional :order_desc, type: Boolean, desc: 'Sort direction (true is descending)', default: false optional :offset, type: Integer, desc: 'Offset this resultset' optional :limit, type: Integer, desc: 'Limit number of results' end get do run_from_params(params) end post do run_from_params(params) end end resource :dimensions do route_param :dimension_name do desc "Return a dimension's members" params do requires :cube_name, type: String, desc: 'Cube name' requires :dimension_name, type: String, desc: 'Dimension name' end get do cube = get_cube_or_404(params[:cube_name]) dimension = cube.dimension(params[:dimension_name]) dimension.to_h(get_members: true) end resource :hierarchies do route_param :hierarchy_name do resource :levels do route_param :level_name do resource :members do params do optional :member_properties, type: Array, default: [] optional :caption, type: String, desc: 'Replace caption with property', default: nil optional :children, type: Boolean, default: false end get do get_members(params) end end end end end end resource :levels do route_param :level_name do resource :members do params do optional :member_properties, type: Array, default: [] optional :caption, type: String, desc: 'Replace caption with property', default: nil optional :children, type: Boolean, default: false end get do get_members(params) end route_param :member_key, type: String, requirements: { member_key: /[A-Za-z0-9\.\-\s%]+/i } do params do optional :caption, type: String, desc: 'Replace caption with property', default: nil optional :member_properties, type: Array, default: [] optional :children, type: Boolean, default: false end get do cube = get_cube_or_404(params[:cube_name]) dimension = cube.dimension(params[:dimension_name]) level = dimension.hierarchies[0].level(params[:level_name]) member = level.members.detect do |m| m.property_value('MEMBER_KEY').to_s == params[:member_key] end error!('member not found', 404) if member.nil? member .to_h(params[:member_properties], params[:caption], params[:children]) .merge(ancestors: member.ancestors.map(&:to_h)) end end end end end end end end end end end