"""Analytics handlers. The analytics handlers provide endpoints to facilitate metric grouping and fetching. Some of those endpoints are currently used as replacements of VAPI, which requires an extra layer of information (vapi envelope). """ import functools import sys from flask import g from flask import request from oto import response as oto_response from oto.adaptors import flask as oto_flask from owsrequest import flask_request from analytics import api from analytics import config from analytics import context from analytics.api import app from analytics.consts import analytics as consts from analytics.consts import error from analytics.logic import aggregate_stats from analytics.logic import demographics from analytics.logic import geographics from analytics.logic import stores from analytics.logic import streams from analytics.models import ows_analytics from analytics.models.streams import Store from analytics.utils import grass_headers from analytics.utils.handlers import parse_artist_store_and_isrc_values from analytics.utils.handlers import parse_date_values from analytics.validation import request_args_validation @api.route( '///', metric='analytics.type.get', methods=['GET']) def get_analytics_for_type(entity_type, entity_id): """Get analytics for a specific entity. Args: entity_type (str): the type of entity in dynamodb. This is used to create the key. It can be: album. entity_id (str): the entity id. Returns: tuple: the response. """ current_user = context.get_current_user() account_id = current_user.user_id account_type = current_user.user_type start_date = request.args.get('from_date') end_date = request.args.get('to_date') transaction_types = request.args.get('transaction_types', '').split(',') entity_type = consts.EntityTypes.get_entity_from_name(entity_type) # The start date and end date are required. if not start_date or not end_date or not entity_type: return oto_flask.flaskify(oto_response.create_error_response( code=error.VALIDATION_ERROR_CODE, message=error.INVALID_DATE_RANGE_VALUES_MESSAGE)) owner = (getattr(consts.UserFields, account_type), account_id) response_format = functools.partial( aggregate_stats.format_response_for_analytics_frontend, owner=owner) if request.args.get('format') == 'mobile': response_format = functools.partial( aggregate_stats.format_response, owner=owner) response = aggregate_stats.fetch_transactions( entity_type, entity_id, start_date, end_date, transaction_types, format=response_format) response.message = dict(stats=dict(results=response.message)) return oto_flask.flaskify(response) @api.route( config.HEALTH_CHECK, methods=['GET']) def hello_world(): """Hello World health check.""" return oto_flask.flaskify(oto_response.Response(message='Hello World!')) @app.route('/stream-charts/') def get_streams_chart(): """Get source of streams charts for a single ISRC or vendor. Request params: from_date (str): start of date range to_date (str): end of date range stores (str): comma-delimited list of stores artist_ids (str): comma-delimited list of artist ids. Optional. isrcs (str): comma-delimited list of isrcs. Optional. distributors (str): comma-delimited list of distributors names. Optional. Returns: flask.Response with a dict of charts data points as message. """ validation = request_args_validation.validate_request_args(request.args) if not validation: return oto_flask.flaskify(validation) start_date, end_date = parse_date_values(request.args) artist_ids, store_ids, isrcs = parse_artist_store_and_isrc_values( request.args) account_type, account_id, orchard_user_id = \ grass_headers.get_grass_headers_and_orchard_user_id(request) feed_ids = sorted(list( ows_analytics.get_available_feeds( account_type, account_id, orchard_user_id).message.keys())) distributors = request.args.get('distributors', 'theorchard') distributors = distributors.split(',') return oto_flask.flaskify(streams.get_streams( start_date, end_date, account_type, account_id, store_ids, feed_ids, isrcs, artist_ids, distributors)) @app.route('/playlist-placements/') def get_playlist_placements(): """Get playlist placements for a single ISRC or vendor. Request params: from_date (str): start of date range to_date (str): end of date range stores (str): comma-delimited list of stores artist_ids (str): comma-delimited list of artist ids. Optional. isrcs (str): comma-delimited list of isrcs. Optional. distributors (str): comma-delimited list of distributors names. Optional. Returns: flask.Response with a dict of playlist placement items as message. """ grass_validation = flask_request.verify_grass_headers(request) if not grass_validation: return oto_flask.flaskify(grass_validation) account_type, account_id, orchard_user_id = \ grass_headers.get_grass_headers_and_orchard_user_id(request) validation = request_args_validation.validate_request_args(request.args) if not validation: return oto_flask.flaskify(validation) start_date, end_date = parse_date_values(request.args) artist_ids, store_ids, isrcs = parse_artist_store_and_isrc_values( request.args) feed_ids = sorted(list( ows_analytics.get_available_feeds( account_type, account_id, orchard_user_id).message.keys())) distributors = request.args.get('distributors', 'theorchard') distributors = distributors.split(',') limit = request.args.get('limit') if limit: limit = int(limit) return oto_flask.flaskify(streams.get_placements( start_date, end_date, account_type, account_id, store_ids, feed_ids, isrcs, artist_ids, distributors, limit)) @app.route('/playlist-metadata/') def get_playlist_metadata(): """Get playlist metadata by playlist_link for each playlist placement. Request params: playlists (list): list of playlists. distributors (str): comma-delimited list of distributors names. Optional. Returns: flask.Response: list of dict of playlist_link and related image and followers. """ # jpenner - temporary debugging to figure out source of large headers g.log.info('get_playlist_metadata header size: {} {}'.format( sys.getsizeof(request.headers), (len(str(request.headers))))) g.log.info(request.headers) playlist_links = request.args.getlist('playlist_links', None) distributors = request.args.get('distributors', 'theorchard') distributors = distributors.split(',') account_type, account_id, orchard_user_id = \ grass_headers.get_grass_headers_and_orchard_user_id(request) feed_ids = sorted(list( ows_analytics.get_available_feeds( account_type, account_id, orchard_user_id).message.keys())) if playlist_links: return oto_flask.flaskify( stores.get_playlists_metadata( playlist_links, feed_ids, distributors)) return oto_flask.flaskify( oto_response.create_error_response( code=error.VALIDATION_ERROR_CODE, message=error.REQUIRED_PARAMETER_IS_MISSING_MESSAGE.format( 'playlist_links'))) @app.route('/demographic_insights/') def get_demographic_insights(): """Get demographics insights data. Request params: from_date (str): start of date range to_date (str): end of date range stores (str): comma-delimited list of store ids. artist_ids (str): comma-delimited list of artist ids. Optional. isrcs (str): comma-delimited list of ISRCs. Optional. territory_code (str): territory code. Optional. distributors (str): comma-delimited list of distributors names. Optional. Returns: flask.Response with a dict of demographics insights data as message. """ validation = request_args_validation.validate_request_args(request.args) if not validation: return oto_flask.flaskify(validation) start_date, end_date = parse_date_values(request.args) artist_ids, store_ids, isrcs = parse_artist_store_and_isrc_values( request.args) territory_code = request.args.get('territory_code') distributors = request.args.get('distributors', 'theorchard') distributors = distributors.split(',') account_type, account_id, orchard_user_id = \ grass_headers.get_grass_headers_and_orchard_user_id(request) feed_ids = sorted(list( ows_analytics.get_available_feeds( account_type, account_id, orchard_user_id).message.keys())) return oto_flask.flaskify( demographics.get_demographic_insights( store_ids=store_ids, feed_ids=feed_ids, start_date=start_date, end_date=end_date, account_type=account_type, account_id=account_id, isrcs=isrcs, artist_ids=artist_ids, territory_code=territory_code, distributors=distributors)) @app.route('/geographic-insights-by-territory/') def get_geographic_insights(): """Get geographic insights data by territory. Request params: from_date (str): start of date range to_date (str): end of date range artist_ids (str): comma-delimited list of artist ids. Optional. store_ids (str): comma-delimited list of store ids. Optional. isrcs (str): comma-delimited list of ISRCs. Optional. distributors (str): comma-delimited list of distributors names. Optional. Returns: flask.Response with a dict of geographic insights data as message. """ grass_validation = flask_request.verify_grass_headers(request) if not grass_validation: return oto_flask.flaskify(grass_validation) account_type, account_id, orchard_user_id = \ grass_headers.get_grass_headers_and_orchard_user_id(request) validation = request_args_validation.validate_request_args(request.args) if not validation: return oto_flask.flaskify(validation) start_date, end_date = parse_date_values(request.args) artist_ids, store_ids, isrcs = parse_artist_store_and_isrc_values( request.args, [Store.SPOTIFY]) feed_ids = sorted(list( ows_analytics.get_available_feeds( account_type, account_id, orchard_user_id).message.keys())) distributors = request.args.get('distributors', 'theorchard') distributors = distributors.split(',') return oto_flask.flaskify( geographics.get_geographic_insights( start_date, end_date, isrcs, artist_ids, store_ids, feed_ids, account_type, account_id, distributors)) @app.route('/geographic-insights-by-region/') def get_geographic_insights_by_region(): """Get geographic insights data by region. Request params: from_date (str): start of date range to_date (str): end of date range artist_ids (str): comma-delimited list of artist ids. Optional. store_ids (str): comma-delimited list of store ids. Optional. isrcs (str): comma-delimited list of ISRCs. Optional. Returns: flask.Response with a dict of geographic insights data as message. """ grass_validation = flask_request.verify_grass_headers(request) if not grass_validation: return oto_flask.flaskify(grass_validation) account_type, account_id, orchard_user_id = \ grass_headers.get_grass_headers_and_orchard_user_id(request) validation = request_args_validation.validate_request_args(request.args) if not validation: return oto_flask.flaskify(validation) feed_ids = sorted(list( ows_analytics.get_available_feeds( account_type, account_id, orchard_user_id).message.keys())) start_date, end_date = parse_date_values(request.args) artist_ids, store_ids, isrcs = parse_artist_store_and_isrc_values( request.args, [Store.SPOTIFY]) distributors = request.args.get('ddistributor', 'theorchard') distributors = distributors.split(',') return oto_flask.flaskify( geographics.get_geographic_insights_by_region( start_date, end_date, isrcs, artist_ids, store_ids, feed_ids, account_type, account_id, distributors)) @app.route('/geographic-orchard-regions/') def get_geographic_orchard_regions(): """Get geographic orchard regions. Returns: flask.Response with a dict of geographic orchard regions as message. """ grass_validation = flask_request.verify_grass_headers(request) if not grass_validation: return oto_flask.flaskify(grass_validation) return oto_flask.flaskify(geographics.get_geographic_orchard_regions()) @app.route('/geographics-latest-date/') def get_geographics_latest_date(): """Get geographic orchard latest available date. Returns: flask.Response with a property called last_activity_date in message. """ grass_validation = flask_request.verify_grass_headers(request) if not grass_validation: return oto_flask.flaskify(grass_validation) account_type, account_id, orchard_user_id = \ grass_headers.get_grass_headers_and_orchard_user_id(request) feed_ids = sorted(list( ows_analytics.get_available_feeds( account_type, account_id, orchard_user_id).message.keys())) distributors = request.args.get('distributors', 'theorchard') distributors = distributors.split(',') return oto_flask.flaskify( geographics.get_geographics_latest_date(feed_ids, distributors))