"""Mode logic layer.""" import json import re import time from collections import OrderedDict import requests from oto.response import ( Response, create_fatal_response, create_not_found_response, ) from reporting import config from reporting.constants import mode as mc from reporting.utils.mode import ( api_url, dict_from_collection, generate_signed_url, is_report_query, json_url_from_query_result, prefix_with_environment, query_run_result_url_from_query_run, query_url_from_query_run, sort_and_encode_params, ) def generate_embed_src( report_token, params, cache_for_day, custom_cache_seconds=False ): """Generate a signed embed url for accessing Mode analytics data. Args: report_token (int): id of the report to run/retrieve data for params (dict): dictionary of parameters and filters to pass to report query Returns: Response: response with signed embed_url """ report_url_base = '{mode_url}/{report_token}'.format( mode_url=mc.MODE_REPORTS_URL, report_token=report_token ) timestamp = int(time.time()) cache_time = mc.MAX_AGE_ONE_HOUR if custom_cache_seconds: cache_time = custom_cache_seconds if cache_for_day: cache_time = mc.MAX_AGE_ONE_DAY request_params = { 'access_key': config.MODE_ACCESS_KEY, 'max_age': cache_time, 'timestamp': timestamp, } request_params.update(params) request_params = sort_and_encode_params(request_params) request_url = '{report_url_base}/embed?{request_params}'.format( report_url_base=report_url_base, request_params=request_params ) signed_embed_src = generate_signed_url( request_url, config.MODE_SECRET_KEY, timestamp ) return Response(status=200, message={'signed_embed_src': signed_embed_src}) def get_report_data(report_token, report_run_token): """Fetch the JSON content of a query for a report. Args: report_token: token of the report to retrieve filter content. report_run_token: token that uniquely identifies a report run. Returns: Response: response with object containing content for report filters. """ query_runs_response = get_query_runs(report_token, report_run_token) if not query_runs_response: return query_runs_response query_runs = query_runs_response.message.get('query_runs') for query_run in query_runs: query_response = get_query_from_query_run(query_run) if not query_response: return query_response query = query_response.message.get('query') if is_report_query(query): query_run_content_response = get_content_from_query_run(query_run) if not query_run_content_response: return query_run_content_response content = query_run_content_response.message.get('content') columns = query_run_content_response.message.get('columns') data = { 'name': query['name'], 'columns': columns, 'content': content, } return Response(status=200, message=data) def report_run_status(report_token, report_run_token): """Construct a call to Mode API and return the status of report run. Args: report_token: token of the report to retrieve status info. report_run_token: token that uniquely identifies a report run. Returns: Response: response with signed embed_url. """ report_url = api_url( path='reports/{report_token}/runs/{report_run_token}'.format( report_run_token=report_run_token, report_token=report_token ) ) mode_response = requests.get(report_url, auth=config.MODE_API_AUTH_KEYS) if mode_response.status_code != 200: if mode_response.status_code == 404: return create_not_found_response( message='The report or run was not found.' ) return create_fatal_response( message='There was a problem performing your request.' ) mode_state = mode_response.json()['state'] if not mode_state == mc.REPORT_RUN_COMPLETE: return Response(status=204) get_report_data_response = get_report_data(report_token, report_run_token) if not get_report_data_response: return get_report_data_response return Response( status=200, message={'report_data': get_report_data_response.message} ) def get_query_runs(report_token, report_run_token): """Fetch the query_runs for a report run. Args: report_token: token of the report. report_run_token: token of the report run. Returns: Response: response object with the query runs. """ query_runs_url = api_url( path=( 'reports/{report_token}/runs/{report_run_token}/query_runs' ).format(report_run_token=report_run_token, report_token=report_token) ) mode_response = requests.get( query_runs_url, auth=config.MODE_API_AUTH_KEYS ) if mode_response.status_code == 200: query_runs = mode_response.json()['_embedded']['query_runs'] if len(query_runs) < 1: return create_not_found_response( message='The data for the report or run was not found.' ) return Response(status=200, message={'query_runs': query_runs}) if mode_response.status_code == 404: return create_not_found_response( message='The data for the report or run was not found.' ) return create_fatal_response( message='There was a problem performing your request.' ) def get_query_from_query_run(query_run): """Generate Mode query object from query_run object. Args: query_run: object representation of a Mode query_run. Returns: Response: response with the Mode query object. """ query_url = query_url_from_query_run(query_run) mode_response = requests.get(query_url, auth=config.MODE_API_AUTH_KEYS) if mode_response.status_code == 200: return Response(status=200, message={'query': mode_response.json()}) if mode_response.status_code == 404: return create_not_found_response( message='The data for the report or run was not found.' ) return create_fatal_response( message='There was a problem performing your request.' ) def get_content_from_query_run(query_run): """Get the JSON contents of a query_run. Args: query_run: object representation of a Mode query_run. Returns: Response: response with the JSON contents of query_run result. """ query_results_response = get_query_run_result_from_query_run(query_run) if not query_results_response: return query_results_response json_url = json_url_from_query_result( query_results_response.message.get('query_run_result') ) mode_response = requests.get(json_url, auth=config.MODE_API_AUTH_KEYS) if mode_response.status_code == 200: columns = json.loads(mode_response.text, object_pairs_hook=OrderedDict) return Response( status=200, message={ 'columns': list(columns[0].keys()) if columns else [], 'content': mode_response.json(), }, ) if mode_response.status_code == 404: return create_not_found_response( message='The data for the report or run was not found.' ) return create_fatal_response( message='There was a problem performing your request.' ) def get_query_run_result_from_query_run(query_run): """Generate Mode query_result object from query_run object. Args: query_run: object representation of a Mode query_run. Returns: Response: response with the Mode query_result object. """ query_run_results_url = query_run_result_url_from_query_run(query_run) mode_response = requests.get( query_run_results_url, auth=config.MODE_API_AUTH_KEYS ) if mode_response.status_code == 200: return Response( status=200, message={'query_run_result': mode_response.json()} ) if mode_response.status_code == 404: return create_not_found_response( message='The data for the report or run was not found.' ) return create_fatal_response( message='There was a problem performing your request.' ) def initiate_report_run(report_token, params): """Initiate a report run for the given parameters. Args: report_token (str): Id of the report to run params (dict): dictionary of parameters and filters for the report Returns: Response: response with report run token """ report_url_base = '{mode_url}/{report_token}'.format( mode_url=mc.MODE_REPORTS_URL, report_token=report_token ) query_params = sort_and_encode_params(params) report_run_url = '{report_url_base}?{query_params}'.format( report_url_base=report_url_base, query_params=query_params ) response = requests.get( report_run_url, auth=config.MODE_API_AUTH_KEYS, allow_redirects=False ) if response.status_code != 302: if response.status_code == 404: return create_not_found_response('No report found') error_message = 'There was a problem completing your request' return create_fatal_response(error_message) response_location_header = response.headers['Location'] run_token = re.search('runs/(.+)$', response_location_header).group(1) return Response(status=200, message={'run_token': run_token}) def get_space_token(environment_space_name): """Get a space token given a Mode space name. Args: environment_space_name (str): Name of space where the report lives prefixed by the environment name, i.e. qa_workstation_physical. Returns: Response: response containing space token. """ mode_spaces_url = api_url(path='spaces') mode_response = requests.get( mode_spaces_url, auth=config.MODE_API_AUTH_KEYS ) if mode_response.status_code != 200: if mode_response.status_code == 404: return create_not_found_response( message='The requested space was not found.' ) return create_fatal_response( message='There was a problem completing your request.' ) mode_response_json = mode_response.json() spaces = mode_response_json['_embedded']['spaces'] current_space = dict_from_collection(spaces, environment_space_name) if not current_space: return create_not_found_response('No space found.') space_token = current_space.get('token') return Response(status=200, message={'space_token': space_token}) def get_report_token(space_name, report_name): """Get a report token given a Mode space and report name. Args: space_name (str): Name of the space where the report exists in mode, for example, `workstation_physical`. report_name (str): Name of report we are attempting to access, for example `product_view_report`. Returns: Response: response with report token. """ env_space_name = prefix_with_environment(space_name) env_report_name = prefix_with_environment(report_name) space_token_response = get_space_token(env_space_name) if not space_token_response: return space_token_response mode_reports_url = api_url(path='spaces/{}/reports').format( space_token_response.message.get('space_token') ) mode_response = requests.get( mode_reports_url, auth=config.MODE_API_AUTH_KEYS ) if mode_response.status_code != 200: if mode_response.status_code == 404: return create_not_found_response( message='The requested space was not found.' ) return create_fatal_response( message='There was a problem completing your request.' ) mode_response_json = mode_response.json() reports = mode_response_json['_embedded']['reports'] current_report = dict_from_collection(reports, env_report_name) if not current_report: return create_not_found_response('No report found.') report_token = current_report.get('token') return Response(status=200, message={'report_token': report_token})