xref: /aosp_15_r20/external/pigweed/pw_cli_analytics/py/pw_cli_analytics/config.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2024 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Report usage to Google Analytics."""
15
16import logging
17import os
18from pathlib import Path
19from typing import Any, Generator
20
21import requests  # pylint: disable=unused-import
22
23import pw_config_loader.json_config_loader_mixin
24
25_LOG: logging.Logger = logging.getLogger(__name__)
26
27_PW_PROJECT_ROOT = Path(os.environ['PW_PROJECT_ROOT'])
28
29CONFIG_SECTION_TITLE = ('pw', 'pw_cli_analytics')
30DEFAULT_PROJECT_FILE = _PW_PROJECT_ROOT / 'pigweed.json'
31DEFAULT_PROJECT_USER_FILE = _PW_PROJECT_ROOT / '.pw_cli_analytics.user.json'
32DEFAULT_USER_FILE = Path(os.path.expanduser('~/.pw_cli_analytics.json'))
33ENVIRONMENT_VAR = 'PW_CLI_ANALYTICS_CONFIG_FILE'
34
35_DEFAULT_CONFIG = {
36    'api_secret': 'm7q0D-9ETtKrGqHAcQK2kQ',
37    'measurement_id': 'G-NY45VS0X1F',
38    'debug_url': 'https://www.google-analytics.com/debug/mp/collect',
39    'prod_url': 'https://www.google-analytics.com/mp/collect',
40    'report_command_line': False,
41    'report_project_name': False,
42    'report_remote_url': False,
43    'report_subcommand_name': 'limited',
44    'uuid': None,
45    'enabled': None,
46}
47
48
49class AnalyticsPrefs(
50    pw_config_loader.json_config_loader_mixin.JsonConfigLoaderMixin
51):
52    """Preferences for reporting analytics data."""
53
54    def __init__(
55        self,
56        *,
57        project_file: Path | None = DEFAULT_PROJECT_FILE,
58        project_user_file: Path | None = DEFAULT_PROJECT_USER_FILE,
59        user_file: Path | None = DEFAULT_USER_FILE,
60        **kwargs,
61    ) -> None:
62        super().__init__(**kwargs)
63
64        self.config_init(
65            config_section_title=CONFIG_SECTION_TITLE,
66            project_file=project_file,
67            project_user_file=project_user_file,
68            user_file=user_file,
69            default_config=_DEFAULT_CONFIG,
70            environment_var=ENVIRONMENT_VAR,
71            skip_files_without_sections=True,
72        )
73
74    def __iter__(self):
75        return iter(_DEFAULT_CONFIG.keys())
76
77    def __getitem__(self, key):
78        return self._config[key]
79
80    def handle_overloaded_value(  # pylint: disable=no-self-use
81        self,
82        key: str,
83        stage: pw_config_loader.json_config_loader_mixin.Stage,
84        original_value: Any,
85        overriding_value: Any,
86    ) -> Any:
87        """Overload this in subclasses to handle of overloaded values."""
88        Stage = pw_config_loader.json_config_loader_mixin.Stage
89
90        # This is a user-specific value. Don't accept it from anywhere but the
91        # user file.
92        if key == 'uuid':
93            if stage == Stage.USER_FILE:
94                return overriding_value
95            return original_value
96
97        # If any level says that data collection should be disabled, disable it.
98        # The default value is None, not False, and the default in generated
99        # user files is True. But if the project says enabled is False we'll
100        # keep that, regardless of what any other files say.
101        if key == 'enabled':
102            if original_value is False:
103                return original_value
104            return overriding_value
105
106        # URLs can by changed by any config.
107        if key in ('debug_url', 'prod_url'):
108            return overriding_value
109
110        # What's left is the details of what to report. In general, these should
111        # only be set by the project file and the user/project file.
112        if stage in (Stage.PROJECT_FILE, Stage.USER_PROJECT_FILE):
113            return overriding_value
114
115        # Only honor user file settings about what to report when they disable
116        # things. Don't honor them when they enable things.
117        if stage == Stage.USER_FILE:
118            if overriding_value in (False, 'never'):
119                return overriding_value
120            if original_value == 'always' and overriding_value == 'limited':
121                return overriding_value
122
123        return original_value
124
125    def items(self) -> Generator[tuple[str, Any], None, None]:
126        """Yield all the key/value pairs in the config."""
127        for key in _DEFAULT_CONFIG.keys():
128            yield (key, self._config[key])
129