google-analytics-cli 0.1.3__tar.gz → 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/PKG-INFO +5 -3
  2. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/README.md +4 -2
  3. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/pyproject.toml +1 -1
  4. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/agent_cmd.py +8 -2
  5. google_analytics_cli-0.1.5/src/ga_cli/commands/enhanced_measurement.py +157 -0
  6. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/property_settings.py +1 -103
  7. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/upgrade_cmd.py +11 -8
  8. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/main.py +2 -0
  9. google_analytics_cli-0.1.5/tests/test_enhanced_measurement.py +179 -0
  10. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_property_settings.py +0 -171
  11. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_upgrade_cmd.py +47 -4
  12. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/uv.lock +1 -1
  13. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.api-snapshots/analyticsadmin_v1alpha.json +0 -0
  14. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.api-snapshots/analyticsadmin_v1beta.json +0 -0
  15. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.api-snapshots/analyticsdata_v1alpha.json +0 -0
  16. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.api-snapshots/analyticsdata_v1beta.json +0 -0
  17. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.github/workflows/api-watch.yml +0 -0
  18. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.github/workflows/ci.yml +0 -0
  19. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.github/workflows/release.yml +0 -0
  20. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.gitignore +0 -0
  21. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/.python-version +0 -0
  22. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/install.sh +0 -0
  23. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/scripts/check_api_changes.py +0 -0
  24. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/__init__.py +0 -0
  25. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/api/__init__.py +0 -0
  26. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/api/client.py +0 -0
  27. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/auth/__init__.py +0 -0
  28. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/auth/credentials.py +0 -0
  29. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/auth/oauth.py +0 -0
  30. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/auth/service_account.py +0 -0
  31. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/__init__.py +0 -0
  32. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/access_bindings.py +0 -0
  33. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/access_reports.py +0 -0
  34. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/account_summaries.py +0 -0
  35. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/accounts.py +0 -0
  36. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/annotations.py +0 -0
  37. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/audiences.py +0 -0
  38. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/auth_cmd.py +0 -0
  39. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/bigquery_links.py +0 -0
  40. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/calculated_metrics.py +0 -0
  41. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/channel_groups.py +0 -0
  42. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/completions_cmd.py +0 -0
  43. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/config_cmd.py +0 -0
  44. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/custom_dimensions.py +0 -0
  45. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/custom_metrics.py +0 -0
  46. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/data_retention.py +0 -0
  47. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/data_streams.py +0 -0
  48. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/event_create_rules.py +0 -0
  49. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/event_edit_rules.py +0 -0
  50. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/firebase_links.py +0 -0
  51. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/google_ads_links.py +0 -0
  52. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/key_events.py +0 -0
  53. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/mp_secrets.py +0 -0
  54. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/properties.py +0 -0
  55. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/commands/reports.py +0 -0
  56. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/config/__init__.py +0 -0
  57. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/config/constants.py +0 -0
  58. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/config/store.py +0 -0
  59. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/utils/__init__.py +0 -0
  60. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/utils/describe.py +0 -0
  61. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/utils/dry_run.py +0 -0
  62. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/utils/errors.py +0 -0
  63. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/utils/filters.py +0 -0
  64. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/utils/output.py +0 -0
  65. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/src/ga_cli/utils/pagination.py +0 -0
  66. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/__init__.py +0 -0
  67. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/conftest.py +0 -0
  68. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_access_bindings.py +0 -0
  69. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_access_reports.py +0 -0
  70. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_account_summaries.py +0 -0
  71. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_accounts.py +0 -0
  72. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_agent_cmd.py +0 -0
  73. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_annotations.py +0 -0
  74. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_api_client.py +0 -0
  75. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_api_diff.py +0 -0
  76. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_audiences.py +0 -0
  77. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_auth_cmd.py +0 -0
  78. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_bigquery_links.py +0 -0
  79. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_calculated_metrics.py +0 -0
  80. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_channel_groups.py +0 -0
  81. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_completions_cmd.py +0 -0
  82. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_config_cmd.py +0 -0
  83. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_credentials.py +0 -0
  84. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_custom_dimensions.py +0 -0
  85. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_custom_metrics.py +0 -0
  86. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_data_retention.py +0 -0
  87. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_data_streams.py +0 -0
  88. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_describe.py +0 -0
  89. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_dry_run.py +0 -0
  90. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_errors.py +0 -0
  91. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_event_create_rules.py +0 -0
  92. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_event_edit_rules.py +0 -0
  93. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_filters.py +0 -0
  94. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_firebase_links.py +0 -0
  95. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_google_ads_links.py +0 -0
  96. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_install_script.sh +0 -0
  97. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_key_events.py +0 -0
  98. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_mp_secrets.py +0 -0
  99. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_output.py +0 -0
  100. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_pagination.py +0 -0
  101. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_properties.py +0 -0
  102. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_quiet_nocolor.py +0 -0
  103. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_reports.py +0 -0
  104. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_reports_batch.py +0 -0
  105. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_reports_funnel.py +0 -0
  106. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_reports_pivot.py +0 -0
  107. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_service_account.py +0 -0
  108. {google_analytics_cli-0.1.3 → google_analytics_cli-0.1.5}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-analytics-cli
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Command-line interface for Google Analytics 4
5
5
  Project-URL: Homepage, https://github.com/daidalytics/google-analytics-cli
6
6
  Project-URL: Repository, https://github.com/daidalytics/google-analytics-cli
@@ -71,7 +71,8 @@ Manage GA4 accounts, properties, data streams, and run reports — all from your
71
71
  - **Event create & edit rules** — Manage server-side event creation and modification rules
72
72
  - **Firebase links** — List, create, and delete Firebase project links
73
73
  - **Google Ads links** — List, create, update, and delete Google Ads account links
74
- - **Property settings** — View and update attribution, Google Signals, and enhanced measurement settings
74
+ - **Property settings** — View and update attribution and Google Signals settings
75
+ - **Enhanced measurement** — View and update enhanced measurement settings per data stream
75
76
 
76
77
  ### Interactive usage
77
78
 
@@ -229,7 +230,8 @@ export GA_CLI_SERVICE_ACCOUNT="/path/to/key.json"
229
230
  | `ga event-edit-rules` | `list`, `get`, `create`, `update`, `delete`, `reorder` | Manage event editing rules |
230
231
  | `ga firebase-links` | `list`, `create`, `delete` | Manage Firebase links |
231
232
  | `ga google-ads-links` | `list`, `create`, `update`, `delete` | Manage Google Ads links |
232
- | `ga property-settings` | `attribution`, `google-signals`, `enhanced-measurement` | View and update property settings |
233
+ | `ga enhanced-measurement` | `get`, `update` | Manage enhanced measurement settings |
234
+ | `ga property-settings` | `attribution`, `google-signals` | View and update property settings |
233
235
  | `ga reports` | `run`, `pivot`, `batch`, `funnel`, `check-compatibility`, `metadata`, `realtime`, `build` | Run and build reports |
234
236
  | `ga agent` | `guide` | AI agent quick reference |
235
237
  | `ga upgrade` | `--check`, `--force` | Check for and install updates |
@@ -38,7 +38,8 @@ Manage GA4 accounts, properties, data streams, and run reports — all from your
38
38
  - **Event create & edit rules** — Manage server-side event creation and modification rules
39
39
  - **Firebase links** — List, create, and delete Firebase project links
40
40
  - **Google Ads links** — List, create, update, and delete Google Ads account links
41
- - **Property settings** — View and update attribution, Google Signals, and enhanced measurement settings
41
+ - **Property settings** — View and update attribution and Google Signals settings
42
+ - **Enhanced measurement** — View and update enhanced measurement settings per data stream
42
43
 
43
44
  ### Interactive usage
44
45
 
@@ -196,7 +197,8 @@ export GA_CLI_SERVICE_ACCOUNT="/path/to/key.json"
196
197
  | `ga event-edit-rules` | `list`, `get`, `create`, `update`, `delete`, `reorder` | Manage event editing rules |
197
198
  | `ga firebase-links` | `list`, `create`, `delete` | Manage Firebase links |
198
199
  | `ga google-ads-links` | `list`, `create`, `update`, `delete` | Manage Google Ads links |
199
- | `ga property-settings` | `attribution`, `google-signals`, `enhanced-measurement` | View and update property settings |
200
+ | `ga enhanced-measurement` | `get`, `update` | Manage enhanced measurement settings |
201
+ | `ga property-settings` | `attribution`, `google-signals` | View and update property settings |
200
202
  | `ga reports` | `run`, `pivot`, `batch`, `funnel`, `check-compatibility`, `metadata`, `realtime`, `build` | Run and build reports |
201
203
  | `ga agent` | `guide` | AI agent quick reference |
202
204
  | `ga upgrade` | `--check`, `--force` | Check for and install updates |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "google-analytics-cli"
3
- version = "0.1.3"
3
+ version = "0.1.5"
4
4
  description = "Command-line interface for Google Analytics 4"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -231,9 +231,15 @@ Requires both `--property-id` and `--stream-id`. Create/update use `--config` JS
231
231
  ```bash
232
232
  ga property-settings attribution [-p PROPERTY_ID] [--attribution-model MODEL] [--acquisition-lookback VAL] [--other-lookback VAL] [--ads-export-scope VAL] [-o json]
233
233
  ga property-settings google-signals [-p PROPERTY_ID] [--state GOOGLE_SIGNALS_ENABLED|GOOGLE_SIGNALS_DISABLED] [-o json]
234
- ga property-settings enhanced-measurement [-p PROPERTY_ID] -s STREAM_ID [--scrolls/--no-scrolls] [--outbound-clicks/--no-outbound-clicks] [...] [-o json]
235
234
  ```
236
- Get/set hybrid: no update flags = GET, any update flag = PATCH. Enhanced measurement requires `--stream-id`.
235
+ Get/set hybrid: no update flags = GET, any update flag = PATCH.
236
+
237
+ ### Enhanced Measurement Settings (alpha)
238
+ ```bash
239
+ ga enhanced-measurement get -p PROPERTY_ID -s STREAM_ID [-o json]
240
+ ga enhanced-measurement update -p PROPERTY_ID -s STREAM_ID [--scrolls/--no-scrolls] [--outbound-clicks/--no-outbound-clicks] [--site-search/--no-site-search] [--video-engagement/--no-video-engagement] [--file-downloads/--no-file-downloads] [--page-changes/--no-page-changes] [--form-interactions/--no-form-interactions] [--stream-enabled/--no-stream-enabled] [--search-query-parameter VAL] [--uri-query-parameter VAL] [--dry-run] [-o json]
241
+ ```
242
+ Requires both `--property-id` and `--stream-id`. `get` retrieves current settings; `update` requires at least one toggle flag.
237
243
 
238
244
  ### Reports
239
245
  ```bash
@@ -0,0 +1,157 @@
1
+ """Enhanced measurement settings commands for a data stream."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from ..api.client import get_admin_alpha_client
8
+ from ..config.store import get_effective_value
9
+ from ..utils import (
10
+ handle_dry_run,
11
+ handle_error,
12
+ output,
13
+ require_options,
14
+ resolve_output_format,
15
+ success,
16
+ )
17
+
18
+ enhanced_measurement_app = typer.Typer(
19
+ name="enhanced-measurement",
20
+ help="Manage enhanced measurement settings for a data stream",
21
+ no_args_is_help=True,
22
+ )
23
+
24
+
25
+ def _resource_name(property_id: str, stream_id: str) -> str:
26
+ return (
27
+ f"properties/{property_id}/dataStreams/{stream_id}"
28
+ "/enhancedMeasurementSettings"
29
+ )
30
+
31
+
32
+ @enhanced_measurement_app.command("get")
33
+ def get_cmd(
34
+ property_id: Optional[str] = typer.Option(
35
+ None, "--property-id", "-p", help="Property ID (numeric)"
36
+ ),
37
+ stream_id: str = typer.Option(
38
+ ..., "--stream-id", "-s", help="Data stream ID"
39
+ ),
40
+ output_format: Optional[str] = typer.Option(
41
+ None, "--output", "-o", help="Output format (json, table, compact)"
42
+ ),
43
+ ):
44
+ """Get enhanced measurement settings for a data stream."""
45
+ try:
46
+ effective_property = get_effective_value(property_id, "default_property_id")
47
+ require_options({"property_id": effective_property}, ["property_id"])
48
+ effective_format = resolve_output_format(output_format)
49
+
50
+ admin = get_admin_alpha_client()
51
+ settings = (
52
+ admin.properties()
53
+ .dataStreams()
54
+ .getEnhancedMeasurementSettings(
55
+ name=_resource_name(effective_property, stream_id)
56
+ )
57
+ .execute()
58
+ )
59
+ output(settings, effective_format)
60
+ except Exception as e:
61
+ handle_error(e)
62
+
63
+
64
+ @enhanced_measurement_app.command("update")
65
+ def update_cmd(
66
+ property_id: Optional[str] = typer.Option(
67
+ None, "--property-id", "-p", help="Property ID (numeric)"
68
+ ),
69
+ stream_id: str = typer.Option(
70
+ ..., "--stream-id", "-s", help="Data stream ID"
71
+ ),
72
+ stream_enabled: Optional[bool] = typer.Option(
73
+ None,
74
+ "--stream-enabled/--no-stream-enabled",
75
+ help="Enable/disable enhanced measurement for this stream",
76
+ ),
77
+ scrolls: Optional[bool] = typer.Option(
78
+ None, "--scrolls/--no-scrolls", help="Capture scroll events"
79
+ ),
80
+ outbound_clicks: Optional[bool] = typer.Option(
81
+ None, "--outbound-clicks/--no-outbound-clicks", help="Capture outbound click events"
82
+ ),
83
+ site_search: Optional[bool] = typer.Option(
84
+ None, "--site-search/--no-site-search", help="Capture site search events"
85
+ ),
86
+ video_engagement: Optional[bool] = typer.Option(
87
+ None, "--video-engagement/--no-video-engagement", help="Capture video engagement events"
88
+ ),
89
+ file_downloads: Optional[bool] = typer.Option(
90
+ None, "--file-downloads/--no-file-downloads", help="Capture file download events"
91
+ ),
92
+ page_changes: Optional[bool] = typer.Option(
93
+ None, "--page-changes/--no-page-changes", help="Capture page change (history) events"
94
+ ),
95
+ form_interactions: Optional[bool] = typer.Option(
96
+ None, "--form-interactions/--no-form-interactions", help="Capture form interaction events"
97
+ ),
98
+ search_query_parameter: Optional[str] = typer.Option(
99
+ None, "--search-query-parameter", help="URL query parameters for site search"
100
+ ),
101
+ uri_query_parameter: Optional[str] = typer.Option(
102
+ None, "--uri-query-parameter", help="Additional URL query parameters"
103
+ ),
104
+ dry_run: bool = typer.Option(
105
+ False, "--dry-run", help="Preview the request without executing"
106
+ ),
107
+ output_format: Optional[str] = typer.Option(
108
+ None, "--output", "-o", help="Output format (json, table, compact)"
109
+ ),
110
+ ):
111
+ """Update enhanced measurement settings for a data stream."""
112
+ try:
113
+ effective_property = get_effective_value(property_id, "default_property_id")
114
+ require_options({"property_id": effective_property}, ["property_id"])
115
+ effective_format = resolve_output_format(output_format)
116
+
117
+ field_map = {
118
+ "streamEnabled": stream_enabled,
119
+ "scrollsEnabled": scrolls,
120
+ "outboundClicksEnabled": outbound_clicks,
121
+ "siteSearchEnabled": site_search,
122
+ "videoEngagementEnabled": video_engagement,
123
+ "fileDownloadsEnabled": file_downloads,
124
+ "pageChangesEnabled": page_changes,
125
+ "formInteractionsEnabled": form_interactions,
126
+ "searchQueryParameter": search_query_parameter,
127
+ "uriQueryParameter": uri_query_parameter,
128
+ }
129
+ body = {k: v for k, v in field_map.items() if v is not None}
130
+
131
+ if not body:
132
+ raise typer.BadParameter(
133
+ "At least one update flag must be specified (e.g. --scrolls, "
134
+ "--stream-enabled, --search-query-parameter)."
135
+ )
136
+
137
+ update_mask = ",".join(body.keys())
138
+ resource = _resource_name(effective_property, stream_id)
139
+
140
+ if dry_run:
141
+ handle_dry_run("update", "PATCH", resource, body, update_mask=update_mask)
142
+
143
+ admin = get_admin_alpha_client()
144
+ settings = (
145
+ admin.properties()
146
+ .dataStreams()
147
+ .updateEnhancedMeasurementSettings(
148
+ name=resource, updateMask=update_mask, body=body
149
+ )
150
+ .execute()
151
+ )
152
+ success("Enhanced measurement settings updated.")
153
+ output(settings, effective_format)
154
+ except (typer.BadParameter, typer.Exit):
155
+ raise
156
+ except Exception as e:
157
+ handle_error(e)
@@ -1,4 +1,4 @@
1
- """Property settings commands (attribution, Google Signals, enhanced measurement)."""
1
+ """Property settings commands (attribution, Google Signals)."""
2
2
 
3
3
  from typing import Optional
4
4
 
@@ -183,105 +183,3 @@ def google_signals_cmd(
183
183
  raise
184
184
  except Exception as e:
185
185
  handle_error(e)
186
-
187
-
188
- # --- Enhanced Measurement Settings ---
189
-
190
-
191
- @property_settings_app.command("enhanced-measurement")
192
- def enhanced_measurement_cmd(
193
- property_id: Optional[str] = typer.Option(
194
- None, "--property-id", "-p", help="Property ID (numeric)"
195
- ),
196
- stream_id: str = typer.Option(
197
- ..., "--stream-id", "-s", help="Data stream ID"
198
- ),
199
- stream_enabled: Optional[bool] = typer.Option(
200
- None,
201
- "--stream-enabled/--no-stream-enabled",
202
- help="Enable/disable enhanced measurement for this stream",
203
- ),
204
- scrolls: Optional[bool] = typer.Option(
205
- None, "--scrolls/--no-scrolls", help="Capture scroll events"
206
- ),
207
- outbound_clicks: Optional[bool] = typer.Option(
208
- None, "--outbound-clicks/--no-outbound-clicks", help="Capture outbound click events"
209
- ),
210
- site_search: Optional[bool] = typer.Option(
211
- None, "--site-search/--no-site-search", help="Capture site search events"
212
- ),
213
- video_engagement: Optional[bool] = typer.Option(
214
- None, "--video-engagement/--no-video-engagement", help="Capture video engagement events"
215
- ),
216
- file_downloads: Optional[bool] = typer.Option(
217
- None, "--file-downloads/--no-file-downloads", help="Capture file download events"
218
- ),
219
- page_changes: Optional[bool] = typer.Option(
220
- None, "--page-changes/--no-page-changes", help="Capture page change (history) events"
221
- ),
222
- form_interactions: Optional[bool] = typer.Option(
223
- None, "--form-interactions/--no-form-interactions", help="Capture form interaction events"
224
- ),
225
- search_query_parameter: Optional[str] = typer.Option(
226
- None, "--search-query-parameter", help="URL query parameters for site search"
227
- ),
228
- uri_query_parameter: Optional[str] = typer.Option(
229
- None, "--uri-query-parameter", help="Additional URL query parameters"
230
- ),
231
- output_format: Optional[str] = typer.Option(
232
- None, "--output", "-o", help="Output format (json, table, compact)"
233
- ),
234
- ):
235
- """Get or update enhanced measurement settings.
236
-
237
- With no update flags, displays current settings.
238
- """
239
- try:
240
- effective_property = get_effective_value(property_id, "default_property_id")
241
- require_options({"property_id": effective_property}, ["property_id"])
242
- effective_format = resolve_output_format(output_format)
243
-
244
- resource_name = (
245
- f"properties/{effective_property}/dataStreams/{stream_id}"
246
- "/enhancedMeasurementSettings"
247
- )
248
- admin = get_admin_alpha_client()
249
-
250
- field_map = {
251
- "streamEnabled": stream_enabled,
252
- "scrollsEnabled": scrolls,
253
- "outboundClicksEnabled": outbound_clicks,
254
- "siteSearchEnabled": site_search,
255
- "videoEngagementEnabled": video_engagement,
256
- "fileDownloadsEnabled": file_downloads,
257
- "pageChangesEnabled": page_changes,
258
- "formInteractionsEnabled": form_interactions,
259
- "searchQueryParameter": search_query_parameter,
260
- "uriQueryParameter": uri_query_parameter,
261
- }
262
- body = {k: v for k, v in field_map.items() if v is not None}
263
-
264
- if body:
265
- update_mask = ",".join(body.keys())
266
- settings = (
267
- admin.properties()
268
- .dataStreams()
269
- .updateEnhancedMeasurementSettings(
270
- name=resource_name, updateMask=update_mask, body=body
271
- )
272
- .execute()
273
- )
274
- success("Enhanced measurement settings updated.")
275
- else:
276
- settings = (
277
- admin.properties()
278
- .dataStreams()
279
- .getEnhancedMeasurementSettings(name=resource_name)
280
- .execute()
281
- )
282
-
283
- output(settings, effective_format)
284
- except typer.BadParameter:
285
- raise
286
- except Exception as e:
287
- handle_error(e)
@@ -20,7 +20,7 @@ upgrade_app = typer.Typer(
20
20
  name="upgrade", help="Check for and install updates", invoke_without_command=True
21
21
  )
22
22
 
23
- PYPI_URL = "https://pypi.org/pypi/ga-cli/json"
23
+ PYPI_URL = "https://pypi.org/pypi/google-analytics-cli/json"
24
24
  PYPI_TIMEOUT = 5
25
25
  UPDATE_CHECK_INTERVAL = 86400 # 24 hours in seconds
26
26
 
@@ -49,11 +49,13 @@ def _is_newer(latest: str, current: str) -> bool:
49
49
  def _detect_installer() -> list[str]:
50
50
  """Detect the best install command to upgrade ga-cli.
51
51
 
52
- Checks for pipx first, then falls back to pip via the current interpreter.
52
+ Checks for uv first, then pipx, then falls back to pip.
53
53
  """
54
+ if shutil.which("uv"):
55
+ return ["uv", "pip", "install", "--upgrade", "google-analytics-cli"]
54
56
  if shutil.which("pipx"):
55
- return ["pipx", "upgrade", "ga-cli"]
56
- return [sys.executable, "-m", "pip", "install", "--upgrade", "ga-cli"]
57
+ return ["pipx", "upgrade", "google-analytics-cli"]
58
+ return [sys.executable, "-m", "pip", "install", "--upgrade", "google-analytics-cli"]
57
59
 
58
60
 
59
61
  @upgrade_app.callback(invoke_without_command=True)
@@ -78,11 +80,12 @@ def upgrade_cmd(
78
80
  if force:
79
81
  info(f"Force-reinstalling ga-cli {current}...")
80
82
  cmd = _detect_installer()
81
- # Append --force-reinstall for pip-based installs
82
- if cmd[0] != "pipx":
83
- cmd.append("--force-reinstall")
83
+ if cmd[0] == "pipx":
84
+ cmd = ["pipx", "install", "--force", "google-analytics-cli"]
85
+ elif cmd[0] == "uv":
86
+ cmd.append("--reinstall")
84
87
  else:
85
- cmd = ["pipx", "install", "--force", "ga-cli"]
88
+ cmd.append("--force-reinstall")
86
89
  result = subprocess.run(cmd, capture_output=True, text=True)
87
90
  if result.returncode == 0:
88
91
  success(f"Reinstalled ga-cli {current}.")
@@ -19,6 +19,7 @@ from .commands.custom_dimensions import custom_dimensions_app
19
19
  from .commands.custom_metrics import custom_metrics_app
20
20
  from .commands.data_retention import data_retention_app
21
21
  from .commands.data_streams import data_streams_app
22
+ from .commands.enhanced_measurement import enhanced_measurement_app
22
23
  from .commands.event_create_rules import event_create_rules_app
23
24
  from .commands.event_edit_rules import event_edit_rules_app
24
25
  from .commands.firebase_links import firebase_links_app
@@ -53,6 +54,7 @@ app.add_typer(audiences_app, name="audiences")
53
54
  app.add_typer(bigquery_links_app, name="bigquery-links")
54
55
  app.add_typer(calculated_metrics_app, name="calculated-metrics")
55
56
  app.add_typer(channel_groups_app, name="channel-groups")
57
+ app.add_typer(enhanced_measurement_app, name="enhanced-measurement")
56
58
  app.add_typer(event_create_rules_app, name="event-create-rules")
57
59
  app.add_typer(event_edit_rules_app, name="event-edit-rules")
58
60
  app.add_typer(firebase_links_app, name="firebase-links")
@@ -0,0 +1,179 @@
1
+ """Tests for enhanced measurement settings commands."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ from typer.testing import CliRunner
6
+
7
+ from ga_cli.config.store import UserConfig, save_config
8
+ from ga_cli.main import app
9
+
10
+ runner = CliRunner()
11
+
12
+ SAMPLE_ENHANCED = {
13
+ "name": "properties/123/dataStreams/456/enhancedMeasurementSettings",
14
+ "streamEnabled": True,
15
+ "scrollsEnabled": True,
16
+ "outboundClicksEnabled": True,
17
+ "siteSearchEnabled": True,
18
+ "videoEngagementEnabled": True,
19
+ "fileDownloadsEnabled": True,
20
+ "pageChangesEnabled": True,
21
+ "formInteractionsEnabled": False,
22
+ "searchQueryParameter": "q,s,search",
23
+ "uriQueryParameter": "",
24
+ }
25
+
26
+ _PATCH_TARGET = "ga_cli.commands.enhanced_measurement.get_admin_alpha_client"
27
+
28
+
29
+ def _mock_admin_alpha_client():
30
+ """Create a mock Admin API alpha client with enhanced measurement methods."""
31
+ mock_client = MagicMock()
32
+ ds = mock_client.properties.return_value.dataStreams.return_value
33
+ ds.getEnhancedMeasurementSettings.return_value.execute.return_value = SAMPLE_ENHANCED
34
+ ds.updateEnhancedMeasurementSettings.return_value.execute.return_value = SAMPLE_ENHANCED
35
+ return mock_client
36
+
37
+
38
+ class TestEnhancedMeasurementGet:
39
+ def test_get(self):
40
+ mock_client = _mock_admin_alpha_client()
41
+
42
+ with patch(_PATCH_TARGET, return_value=mock_client):
43
+ result = runner.invoke(
44
+ app,
45
+ ["enhanced-measurement", "get", "-p", "123", "-s", "456"],
46
+ )
47
+
48
+ assert result.exit_code == 0
49
+ ds = mock_client.properties.return_value.dataStreams.return_value
50
+ ds.getEnhancedMeasurementSettings.assert_called_once()
51
+
52
+ def test_get_json(self):
53
+ mock_client = _mock_admin_alpha_client()
54
+
55
+ with patch(_PATCH_TARGET, return_value=mock_client):
56
+ result = runner.invoke(
57
+ app,
58
+ [
59
+ "enhanced-measurement", "get",
60
+ "-p", "123", "-s", "456", "-o", "json",
61
+ ],
62
+ )
63
+
64
+ assert result.exit_code == 0
65
+ assert '"scrollsEnabled"' in result.output
66
+
67
+ def test_get_uses_config_default(self):
68
+ save_config(UserConfig(default_property_id="123"))
69
+ mock_client = _mock_admin_alpha_client()
70
+
71
+ with patch(_PATCH_TARGET, return_value=mock_client):
72
+ result = runner.invoke(
73
+ app,
74
+ ["enhanced-measurement", "get", "-s", "456"],
75
+ )
76
+
77
+ assert result.exit_code == 0
78
+
79
+ def test_missing_stream_id(self):
80
+ result = runner.invoke(
81
+ app,
82
+ ["enhanced-measurement", "get", "-p", "123"],
83
+ )
84
+ assert result.exit_code != 0
85
+
86
+
87
+ class TestEnhancedMeasurementUpdate:
88
+ def test_update_single_boolean(self):
89
+ mock_client = _mock_admin_alpha_client()
90
+
91
+ with patch(_PATCH_TARGET, return_value=mock_client):
92
+ result = runner.invoke(
93
+ app,
94
+ [
95
+ "enhanced-measurement", "update",
96
+ "-p", "123", "-s", "456",
97
+ "--no-scrolls",
98
+ ],
99
+ )
100
+
101
+ assert result.exit_code == 0
102
+ assert "updated" in result.output.lower()
103
+ ds = mock_client.properties.return_value.dataStreams.return_value
104
+ call_args = ds.updateEnhancedMeasurementSettings.call_args[1]
105
+ assert call_args["body"]["scrollsEnabled"] is False
106
+ assert "scrollsEnabled" in call_args["updateMask"]
107
+
108
+ def test_update_multiple_booleans(self):
109
+ mock_client = _mock_admin_alpha_client()
110
+
111
+ with patch(_PATCH_TARGET, return_value=mock_client):
112
+ result = runner.invoke(
113
+ app,
114
+ [
115
+ "enhanced-measurement", "update",
116
+ "-p", "123", "-s", "456",
117
+ "--form-interactions",
118
+ "--no-video-engagement",
119
+ ],
120
+ )
121
+
122
+ assert result.exit_code == 0
123
+ ds = mock_client.properties.return_value.dataStreams.return_value
124
+ call_args = ds.updateEnhancedMeasurementSettings.call_args[1]
125
+ assert call_args["body"]["formInteractionsEnabled"] is True
126
+ assert call_args["body"]["videoEngagementEnabled"] is False
127
+
128
+ def test_update_search_query_parameter(self):
129
+ mock_client = _mock_admin_alpha_client()
130
+
131
+ with patch(_PATCH_TARGET, return_value=mock_client):
132
+ result = runner.invoke(
133
+ app,
134
+ [
135
+ "enhanced-measurement", "update",
136
+ "-p", "123", "-s", "456",
137
+ "--search-query-parameter", "q,search,query",
138
+ ],
139
+ )
140
+
141
+ assert result.exit_code == 0
142
+ ds = mock_client.properties.return_value.dataStreams.return_value
143
+ call_args = ds.updateEnhancedMeasurementSettings.call_args[1]
144
+ assert call_args["body"]["searchQueryParameter"] == "q,search,query"
145
+
146
+ def test_update_no_flags_errors(self):
147
+ result = runner.invoke(
148
+ app,
149
+ ["enhanced-measurement", "update", "-p", "123", "-s", "456"],
150
+ )
151
+ assert result.exit_code != 0
152
+
153
+ def test_update_api_error(self):
154
+ from googleapiclient.errors import HttpError
155
+
156
+ mock_client = _mock_admin_alpha_client()
157
+ ds = mock_client.properties.return_value.dataStreams.return_value
158
+ ds.updateEnhancedMeasurementSettings.return_value.execute.side_effect = HttpError(
159
+ resp=MagicMock(status=400), content=b'{"error": {"message": "Bad request"}}'
160
+ )
161
+
162
+ with patch(_PATCH_TARGET, return_value=mock_client):
163
+ result = runner.invoke(
164
+ app,
165
+ [
166
+ "enhanced-measurement", "update",
167
+ "-p", "123", "-s", "456",
168
+ "--no-scrolls",
169
+ ],
170
+ )
171
+
172
+ assert result.exit_code == 3
173
+
174
+ def test_missing_stream_id(self):
175
+ result = runner.invoke(
176
+ app,
177
+ ["enhanced-measurement", "update", "-p", "123", "--no-scrolls"],
178
+ )
179
+ assert result.exit_code != 0