# -*- coding: utf-8 -*- # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT import json import logging import dataclasses from typing import Any, Optional, Union from xray.apiclient import get_client from .models.analytics_data import AnalyticsData from xray.internal.utils import read_sys_id def report_analytics( data: str, advice_id: Optional[Union[str, int]], source: str, event: Optional[str] = None, feature: Optional[str] = None, variant_id: Optional[str] = None, ) -> Any: """ Receives analytics data as a JSON string """ logger = logging.getLogger('analytics') logger.info( 'Prepare analytics report: advice_id: "%s", source: "%s", event: "%s", feature: "%s"', str(advice_id), str(source), str(event), str(feature), ) if data is None: return data = _parse(data, logger) if not data: return # if advice_id passed -> add it to analytics report if advice_id: advice_id = _filter_advice(advice_id) if not advice_id: return data['advice_id'] = str(advice_id) try: system_id = read_sys_id() except Exception: logger.exception('Cannot obtain system_id for analytics report') system_id = None data['system_id'] = system_id if feature: data['feature'] = feature if variant_id: data['variant_id'] = variant_id if 'source' not in data: data['source'] = source if source == 'WORDPRESS_PLUGIN': data['source'] = 'wp_smartadvice' if event is not None: data['event'] = event validated_data = _validate(data, logger) if validated_data is not None: api_client_object = get_client('adviser') client = api_client_object() return client.report(validated_data) def _parse(data: str, logger: logging.Logger) -> Optional[dict]: """ Trying to parse receiving JSON """ try: v = json.loads(data) except ValueError: logger.error('[Analytics] Decoding analytics JSON has failed') else: if not isinstance(v, dict): logger.error('[Analytics] Expected a JSON object', extra={'type': type(v).__name__}) return return v def _validate(data: dict, logger: logging.Logger) -> Optional[dict]: """ Validates data. """ try: validated_data = AnalyticsData(**data) except (TypeError, AttributeError): # F-18: catch AttributeError from non-string/non-dict input as well. # F-22: do not log the body-bearing extra={'err': str(e)}. logger.error('[Analytics] Not supported field detected') else: return dataclasses.asdict(validated_data) def _filter_advice(advice_ids: Union[str, int]) -> Union[str, int]: """ Remove IM360 advisements from the list """ if advice_ids.isnumeric(): return advice_ids advice_dict = advice_ids.split(',') advice_dict = filter(lambda id: not id.startswith('IM360'), advice_dict) return ','.join(advice_dict)