google-analytics-cli 0.1.0__py3-none-any.whl
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.
- ga_cli/__init__.py +8 -0
- ga_cli/api/__init__.py +0 -0
- ga_cli/api/client.py +142 -0
- ga_cli/auth/__init__.py +35 -0
- ga_cli/auth/credentials.py +126 -0
- ga_cli/auth/oauth.py +322 -0
- ga_cli/auth/service_account.py +155 -0
- ga_cli/commands/__init__.py +0 -0
- ga_cli/commands/access_bindings.py +254 -0
- ga_cli/commands/access_reports.py +201 -0
- ga_cli/commands/account_summaries.py +68 -0
- ga_cli/commands/accounts.py +297 -0
- ga_cli/commands/agent_cmd.py +776 -0
- ga_cli/commands/annotations.py +264 -0
- ga_cli/commands/audiences.py +223 -0
- ga_cli/commands/auth_cmd.py +205 -0
- ga_cli/commands/bigquery_links.py +309 -0
- ga_cli/commands/calculated_metrics.py +312 -0
- ga_cli/commands/channel_groups.py +223 -0
- ga_cli/commands/completions_cmd.py +55 -0
- ga_cli/commands/config_cmd.py +113 -0
- ga_cli/commands/custom_dimensions.py +272 -0
- ga_cli/commands/custom_metrics.py +305 -0
- ga_cli/commands/data_retention.py +153 -0
- ga_cli/commands/data_streams.py +277 -0
- ga_cli/commands/event_create_rules.py +250 -0
- ga_cli/commands/event_edit_rules.py +292 -0
- ga_cli/commands/firebase_links.py +142 -0
- ga_cli/commands/google_ads_links.py +225 -0
- ga_cli/commands/key_events.py +269 -0
- ga_cli/commands/mp_secrets.py +265 -0
- ga_cli/commands/properties.py +330 -0
- ga_cli/commands/property_settings.py +287 -0
- ga_cli/commands/reports.py +726 -0
- ga_cli/commands/upgrade_cmd.py +148 -0
- ga_cli/config/__init__.py +0 -0
- ga_cli/config/constants.py +61 -0
- ga_cli/config/store.py +115 -0
- ga_cli/main.py +110 -0
- ga_cli/utils/__init__.py +20 -0
- ga_cli/utils/describe.py +129 -0
- ga_cli/utils/dry_run.py +40 -0
- ga_cli/utils/errors.py +150 -0
- ga_cli/utils/output.py +209 -0
- ga_cli/utils/pagination.py +93 -0
- google_analytics_cli-0.1.0.dist-info/METADATA +321 -0
- google_analytics_cli-0.1.0.dist-info/RECORD +49 -0
- google_analytics_cli-0.1.0.dist-info/WHEEL +4 -0
- google_analytics_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Event edit rule management commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import questionary
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from ..api.client import get_admin_alpha_client
|
|
11
|
+
from ..config.store import get_effective_value
|
|
12
|
+
from ..utils import handle_error, info, output, require_options, resolve_output_format, success
|
|
13
|
+
from ..utils.pagination import paginate_all
|
|
14
|
+
|
|
15
|
+
event_edit_rules_app = typer.Typer(
|
|
16
|
+
name="event-edit-rules",
|
|
17
|
+
help="Manage event edit rules",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load_json_config(config_file: str) -> dict:
|
|
23
|
+
"""Load and parse a JSON config file."""
|
|
24
|
+
config_path = Path(config_file)
|
|
25
|
+
if not config_path.exists():
|
|
26
|
+
raise typer.BadParameter(f"Config file not found: {config_file}")
|
|
27
|
+
try:
|
|
28
|
+
return json.loads(config_path.read_text())
|
|
29
|
+
except json.JSONDecodeError as exc:
|
|
30
|
+
raise typer.BadParameter(f"Invalid JSON in config file: {exc}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@event_edit_rules_app.command("list")
|
|
34
|
+
def list_cmd(
|
|
35
|
+
property_id: Optional[str] = typer.Option(
|
|
36
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
37
|
+
),
|
|
38
|
+
stream_id: str = typer.Option(
|
|
39
|
+
..., "--stream-id", "-s", help="Data stream ID"
|
|
40
|
+
),
|
|
41
|
+
output_format: Optional[str] = typer.Option(
|
|
42
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
"""List event edit rules for a data stream."""
|
|
46
|
+
try:
|
|
47
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
48
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
49
|
+
effective_format = resolve_output_format(output_format)
|
|
50
|
+
|
|
51
|
+
admin = get_admin_alpha_client()
|
|
52
|
+
parent = f"properties/{effective_property}/dataStreams/{stream_id}"
|
|
53
|
+
rules = paginate_all(
|
|
54
|
+
lambda **kw: admin.properties()
|
|
55
|
+
.dataStreams()
|
|
56
|
+
.eventEditRules()
|
|
57
|
+
.list(parent=parent, **kw)
|
|
58
|
+
.execute(),
|
|
59
|
+
"eventEditRules",
|
|
60
|
+
pageSize=200,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
output(
|
|
64
|
+
rules,
|
|
65
|
+
effective_format,
|
|
66
|
+
columns=[
|
|
67
|
+
"name",
|
|
68
|
+
"displayName",
|
|
69
|
+
"processingOrder",
|
|
70
|
+
],
|
|
71
|
+
headers=[
|
|
72
|
+
"Resource Name",
|
|
73
|
+
"Display Name",
|
|
74
|
+
"Processing Order",
|
|
75
|
+
],
|
|
76
|
+
)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
handle_error(e)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@event_edit_rules_app.command("get")
|
|
82
|
+
def get_cmd(
|
|
83
|
+
property_id: Optional[str] = typer.Option(
|
|
84
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
85
|
+
),
|
|
86
|
+
stream_id: str = typer.Option(
|
|
87
|
+
..., "--stream-id", "-s", help="Data stream ID"
|
|
88
|
+
),
|
|
89
|
+
rule_id: str = typer.Option(
|
|
90
|
+
..., "--rule-id", "-r", help="Event edit rule ID"
|
|
91
|
+
),
|
|
92
|
+
output_format: Optional[str] = typer.Option(
|
|
93
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
94
|
+
),
|
|
95
|
+
):
|
|
96
|
+
"""Get details for an event edit rule."""
|
|
97
|
+
try:
|
|
98
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
99
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
100
|
+
effective_format = resolve_output_format(output_format)
|
|
101
|
+
|
|
102
|
+
admin = get_admin_alpha_client()
|
|
103
|
+
resource_name = (
|
|
104
|
+
f"properties/{effective_property}/dataStreams/{stream_id}"
|
|
105
|
+
f"/eventEditRules/{rule_id}"
|
|
106
|
+
)
|
|
107
|
+
rule = (
|
|
108
|
+
admin.properties()
|
|
109
|
+
.dataStreams()
|
|
110
|
+
.eventEditRules()
|
|
111
|
+
.get(name=resource_name)
|
|
112
|
+
.execute()
|
|
113
|
+
)
|
|
114
|
+
output(rule, effective_format)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
handle_error(e)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@event_edit_rules_app.command("create")
|
|
120
|
+
def create_cmd(
|
|
121
|
+
property_id: Optional[str] = typer.Option(
|
|
122
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
123
|
+
),
|
|
124
|
+
stream_id: str = typer.Option(
|
|
125
|
+
..., "--stream-id", "-s", help="Data stream ID"
|
|
126
|
+
),
|
|
127
|
+
config_file: str = typer.Option(
|
|
128
|
+
..., "--config", "-c", help="Path to JSON event edit rule config file"
|
|
129
|
+
),
|
|
130
|
+
output_format: Optional[str] = typer.Option(
|
|
131
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
132
|
+
),
|
|
133
|
+
):
|
|
134
|
+
"""Create an event edit rule from a JSON config file."""
|
|
135
|
+
try:
|
|
136
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
137
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
138
|
+
effective_format = resolve_output_format(output_format)
|
|
139
|
+
|
|
140
|
+
body = _load_json_config(config_file)
|
|
141
|
+
|
|
142
|
+
admin = get_admin_alpha_client()
|
|
143
|
+
parent = f"properties/{effective_property}/dataStreams/{stream_id}"
|
|
144
|
+
rule = (
|
|
145
|
+
admin.properties()
|
|
146
|
+
.dataStreams()
|
|
147
|
+
.eventEditRules()
|
|
148
|
+
.create(parent=parent, body=body)
|
|
149
|
+
.execute()
|
|
150
|
+
)
|
|
151
|
+
output(rule, effective_format)
|
|
152
|
+
except typer.BadParameter:
|
|
153
|
+
raise
|
|
154
|
+
except Exception as e:
|
|
155
|
+
handle_error(e)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@event_edit_rules_app.command("update")
|
|
159
|
+
def update_cmd(
|
|
160
|
+
property_id: Optional[str] = typer.Option(
|
|
161
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
162
|
+
),
|
|
163
|
+
stream_id: str = typer.Option(
|
|
164
|
+
..., "--stream-id", "-s", help="Data stream ID"
|
|
165
|
+
),
|
|
166
|
+
rule_id: str = typer.Option(
|
|
167
|
+
..., "--rule-id", "-r", help="Event edit rule ID"
|
|
168
|
+
),
|
|
169
|
+
config_file: str = typer.Option(
|
|
170
|
+
..., "--config", "-c", help="Path to JSON file with fields to update"
|
|
171
|
+
),
|
|
172
|
+
output_format: Optional[str] = typer.Option(
|
|
173
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
174
|
+
),
|
|
175
|
+
):
|
|
176
|
+
"""Update an event edit rule from a JSON config file."""
|
|
177
|
+
try:
|
|
178
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
179
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
180
|
+
effective_format = resolve_output_format(output_format)
|
|
181
|
+
|
|
182
|
+
body = _load_json_config(config_file)
|
|
183
|
+
|
|
184
|
+
if not body:
|
|
185
|
+
raise typer.BadParameter("Config file must contain at least one field to update.")
|
|
186
|
+
|
|
187
|
+
update_mask = ",".join(body.keys())
|
|
188
|
+
|
|
189
|
+
admin = get_admin_alpha_client()
|
|
190
|
+
resource_name = (
|
|
191
|
+
f"properties/{effective_property}/dataStreams/{stream_id}"
|
|
192
|
+
f"/eventEditRules/{rule_id}"
|
|
193
|
+
)
|
|
194
|
+
rule = (
|
|
195
|
+
admin.properties()
|
|
196
|
+
.dataStreams()
|
|
197
|
+
.eventEditRules()
|
|
198
|
+
.patch(
|
|
199
|
+
name=resource_name,
|
|
200
|
+
body=body,
|
|
201
|
+
updateMask=update_mask,
|
|
202
|
+
)
|
|
203
|
+
.execute()
|
|
204
|
+
)
|
|
205
|
+
output(rule, effective_format)
|
|
206
|
+
except typer.BadParameter:
|
|
207
|
+
raise
|
|
208
|
+
except Exception as e:
|
|
209
|
+
handle_error(e)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@event_edit_rules_app.command("delete")
|
|
213
|
+
def delete_cmd(
|
|
214
|
+
property_id: Optional[str] = typer.Option(
|
|
215
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
216
|
+
),
|
|
217
|
+
stream_id: str = typer.Option(
|
|
218
|
+
..., "--stream-id", "-s", help="Data stream ID"
|
|
219
|
+
),
|
|
220
|
+
rule_id: str = typer.Option(
|
|
221
|
+
..., "--rule-id", "-r", help="Event edit rule ID"
|
|
222
|
+
),
|
|
223
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
224
|
+
):
|
|
225
|
+
"""Delete an event edit rule."""
|
|
226
|
+
try:
|
|
227
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
228
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
229
|
+
|
|
230
|
+
if not yes:
|
|
231
|
+
confirmed = questionary.confirm(
|
|
232
|
+
f"Delete event edit rule {rule_id}? This cannot be undone."
|
|
233
|
+
).ask()
|
|
234
|
+
if not confirmed:
|
|
235
|
+
info("Cancelled.")
|
|
236
|
+
raise typer.Exit()
|
|
237
|
+
|
|
238
|
+
admin = get_admin_alpha_client()
|
|
239
|
+
resource_name = (
|
|
240
|
+
f"properties/{effective_property}/dataStreams/{stream_id}"
|
|
241
|
+
f"/eventEditRules/{rule_id}"
|
|
242
|
+
)
|
|
243
|
+
admin.properties().dataStreams().eventEditRules().delete(
|
|
244
|
+
name=resource_name
|
|
245
|
+
).execute()
|
|
246
|
+
success(f"Event edit rule {rule_id} deleted.")
|
|
247
|
+
except typer.Exit:
|
|
248
|
+
raise
|
|
249
|
+
except Exception as e:
|
|
250
|
+
handle_error(e)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@event_edit_rules_app.command("reorder")
|
|
254
|
+
def reorder_cmd(
|
|
255
|
+
property_id: Optional[str] = typer.Option(
|
|
256
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
257
|
+
),
|
|
258
|
+
stream_id: str = typer.Option(
|
|
259
|
+
..., "--stream-id", "-s", help="Data stream ID"
|
|
260
|
+
),
|
|
261
|
+
rule_ids: str = typer.Option(
|
|
262
|
+
...,
|
|
263
|
+
"--rule-ids",
|
|
264
|
+
help="Comma-separated rule IDs in desired processing order (all rules must be included)",
|
|
265
|
+
),
|
|
266
|
+
):
|
|
267
|
+
"""Reorder event edit rules on a data stream."""
|
|
268
|
+
try:
|
|
269
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
270
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
271
|
+
|
|
272
|
+
parent = f"properties/{effective_property}/dataStreams/{stream_id}"
|
|
273
|
+
ids = [rid.strip() for rid in rule_ids.split(",") if rid.strip()]
|
|
274
|
+
|
|
275
|
+
if not ids:
|
|
276
|
+
raise typer.BadParameter("--rule-ids must contain at least one rule ID.")
|
|
277
|
+
|
|
278
|
+
body = {
|
|
279
|
+
"eventEditRules": [
|
|
280
|
+
f"{parent}/eventEditRules/{rid}" for rid in ids
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
admin = get_admin_alpha_client()
|
|
285
|
+
admin.properties().dataStreams().eventEditRules().reorder(
|
|
286
|
+
parent=parent, body=body
|
|
287
|
+
).execute()
|
|
288
|
+
success("Event edit rules reordered.")
|
|
289
|
+
except typer.BadParameter:
|
|
290
|
+
raise
|
|
291
|
+
except Exception as e:
|
|
292
|
+
handle_error(e)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Firebase link management commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..api.client import get_admin_client
|
|
9
|
+
from ..config.store import get_effective_value
|
|
10
|
+
from ..utils import (
|
|
11
|
+
handle_dry_run,
|
|
12
|
+
handle_error,
|
|
13
|
+
info,
|
|
14
|
+
output,
|
|
15
|
+
require_options,
|
|
16
|
+
resolve_output_format,
|
|
17
|
+
success,
|
|
18
|
+
)
|
|
19
|
+
from ..utils.pagination import paginate_all
|
|
20
|
+
|
|
21
|
+
firebase_links_app = typer.Typer(
|
|
22
|
+
name="firebase-links",
|
|
23
|
+
help="Manage Firebase links",
|
|
24
|
+
no_args_is_help=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@firebase_links_app.command("list")
|
|
29
|
+
def list_cmd(
|
|
30
|
+
property_id: Optional[str] = typer.Option(
|
|
31
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
32
|
+
),
|
|
33
|
+
output_format: Optional[str] = typer.Option(
|
|
34
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
35
|
+
),
|
|
36
|
+
):
|
|
37
|
+
"""List Firebase links for a property."""
|
|
38
|
+
try:
|
|
39
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
40
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
41
|
+
effective_format = resolve_output_format(output_format)
|
|
42
|
+
|
|
43
|
+
admin = get_admin_client()
|
|
44
|
+
links = paginate_all(
|
|
45
|
+
lambda **kw: admin.properties()
|
|
46
|
+
.firebaseLinks()
|
|
47
|
+
.list(parent=f"properties/{effective_property}", **kw)
|
|
48
|
+
.execute(),
|
|
49
|
+
"firebaseLinks",
|
|
50
|
+
pageSize=200,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
output(
|
|
54
|
+
links,
|
|
55
|
+
effective_format,
|
|
56
|
+
columns=["name", "project", "createTime"],
|
|
57
|
+
headers=["Resource Name", "Project", "Create Time"],
|
|
58
|
+
)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
handle_error(e)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@firebase_links_app.command("create")
|
|
64
|
+
def create_cmd(
|
|
65
|
+
property_id: Optional[str] = typer.Option(
|
|
66
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
67
|
+
),
|
|
68
|
+
project: str = typer.Option(
|
|
69
|
+
..., "--project", help="Firebase project resource name (e.g., projects/my-project)"
|
|
70
|
+
),
|
|
71
|
+
dry_run: bool = typer.Option(
|
|
72
|
+
False, "--dry-run", help="Preview the request without executing"
|
|
73
|
+
),
|
|
74
|
+
output_format: Optional[str] = typer.Option(
|
|
75
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
76
|
+
),
|
|
77
|
+
):
|
|
78
|
+
"""Create a Firebase link."""
|
|
79
|
+
try:
|
|
80
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
81
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
82
|
+
effective_format = resolve_output_format(output_format)
|
|
83
|
+
|
|
84
|
+
body = {"project": project}
|
|
85
|
+
if dry_run:
|
|
86
|
+
handle_dry_run("create", "POST", f"properties/{effective_property}", body)
|
|
87
|
+
|
|
88
|
+
admin = get_admin_client()
|
|
89
|
+
link = (
|
|
90
|
+
admin.properties()
|
|
91
|
+
.firebaseLinks()
|
|
92
|
+
.create(parent=f"properties/{effective_property}", body=body)
|
|
93
|
+
.execute()
|
|
94
|
+
)
|
|
95
|
+
output(link, effective_format)
|
|
96
|
+
except typer.Exit:
|
|
97
|
+
raise
|
|
98
|
+
except Exception as e:
|
|
99
|
+
handle_error(e)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@firebase_links_app.command("delete")
|
|
103
|
+
def delete_cmd(
|
|
104
|
+
property_id: Optional[str] = typer.Option(
|
|
105
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
106
|
+
),
|
|
107
|
+
link_id: str = typer.Option(
|
|
108
|
+
..., "--link-id", help="Firebase link ID"
|
|
109
|
+
),
|
|
110
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
111
|
+
dry_run: bool = typer.Option(
|
|
112
|
+
False, "--dry-run", help="Preview the request without executing"
|
|
113
|
+
),
|
|
114
|
+
):
|
|
115
|
+
"""Delete a Firebase link."""
|
|
116
|
+
try:
|
|
117
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
118
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
119
|
+
|
|
120
|
+
if dry_run:
|
|
121
|
+
handle_dry_run(
|
|
122
|
+
"delete", "DELETE",
|
|
123
|
+
f"properties/{effective_property}/firebaseLinks/{link_id}",
|
|
124
|
+
None,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if not yes:
|
|
128
|
+
confirmed = questionary.confirm(
|
|
129
|
+
f"Delete Firebase link {link_id}? This cannot be undone."
|
|
130
|
+
).ask()
|
|
131
|
+
if not confirmed:
|
|
132
|
+
info("Cancelled.")
|
|
133
|
+
raise typer.Exit()
|
|
134
|
+
|
|
135
|
+
admin = get_admin_client()
|
|
136
|
+
resource_name = f"properties/{effective_property}/firebaseLinks/{link_id}"
|
|
137
|
+
admin.properties().firebaseLinks().delete(name=resource_name).execute()
|
|
138
|
+
success(f"Firebase link {link_id} deleted.")
|
|
139
|
+
except typer.Exit:
|
|
140
|
+
raise
|
|
141
|
+
except Exception as e:
|
|
142
|
+
handle_error(e)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Google Ads link management commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..api.client import get_admin_client
|
|
9
|
+
from ..config.store import get_effective_value
|
|
10
|
+
from ..utils import (
|
|
11
|
+
handle_dry_run,
|
|
12
|
+
handle_error,
|
|
13
|
+
info,
|
|
14
|
+
output,
|
|
15
|
+
require_options,
|
|
16
|
+
resolve_output_format,
|
|
17
|
+
success,
|
|
18
|
+
)
|
|
19
|
+
from ..utils.pagination import paginate_all
|
|
20
|
+
|
|
21
|
+
google_ads_links_app = typer.Typer(
|
|
22
|
+
name="google-ads-links",
|
|
23
|
+
help="Manage Google Ads links",
|
|
24
|
+
no_args_is_help=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@google_ads_links_app.command("list")
|
|
29
|
+
def list_cmd(
|
|
30
|
+
property_id: Optional[str] = typer.Option(
|
|
31
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
32
|
+
),
|
|
33
|
+
output_format: Optional[str] = typer.Option(
|
|
34
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
35
|
+
),
|
|
36
|
+
):
|
|
37
|
+
"""List Google Ads links for a property."""
|
|
38
|
+
try:
|
|
39
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
40
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
41
|
+
effective_format = resolve_output_format(output_format)
|
|
42
|
+
|
|
43
|
+
admin = get_admin_client()
|
|
44
|
+
links = paginate_all(
|
|
45
|
+
lambda **kw: admin.properties()
|
|
46
|
+
.googleAdsLinks()
|
|
47
|
+
.list(parent=f"properties/{effective_property}", **kw)
|
|
48
|
+
.execute(),
|
|
49
|
+
"googleAdsLinks",
|
|
50
|
+
pageSize=200,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
output(
|
|
54
|
+
links,
|
|
55
|
+
effective_format,
|
|
56
|
+
columns=[
|
|
57
|
+
"name",
|
|
58
|
+
"customerId",
|
|
59
|
+
"canManageClients",
|
|
60
|
+
"adsPersonalizationEnabled",
|
|
61
|
+
"createTime",
|
|
62
|
+
],
|
|
63
|
+
headers=[
|
|
64
|
+
"Resource Name",
|
|
65
|
+
"Customer ID",
|
|
66
|
+
"Can Manage Clients",
|
|
67
|
+
"Ads Personalization",
|
|
68
|
+
"Create Time",
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
handle_error(e)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@google_ads_links_app.command("create")
|
|
76
|
+
def create_cmd(
|
|
77
|
+
property_id: Optional[str] = typer.Option(
|
|
78
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
79
|
+
),
|
|
80
|
+
customer_id: str = typer.Option(
|
|
81
|
+
..., "--customer-id", help="Google Ads customer ID"
|
|
82
|
+
),
|
|
83
|
+
ads_personalization: bool = typer.Option(
|
|
84
|
+
True,
|
|
85
|
+
"--ads-personalization/--no-ads-personalization",
|
|
86
|
+
help="Enable ads personalization",
|
|
87
|
+
),
|
|
88
|
+
dry_run: bool = typer.Option(
|
|
89
|
+
False, "--dry-run", help="Preview the request without executing"
|
|
90
|
+
),
|
|
91
|
+
output_format: Optional[str] = typer.Option(
|
|
92
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
93
|
+
),
|
|
94
|
+
):
|
|
95
|
+
"""Create a Google Ads link."""
|
|
96
|
+
try:
|
|
97
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
98
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
99
|
+
effective_format = resolve_output_format(output_format)
|
|
100
|
+
|
|
101
|
+
body = {
|
|
102
|
+
"customerId": customer_id,
|
|
103
|
+
"adsPersonalizationEnabled": ads_personalization,
|
|
104
|
+
}
|
|
105
|
+
if dry_run:
|
|
106
|
+
handle_dry_run("create", "POST", f"properties/{effective_property}", body)
|
|
107
|
+
|
|
108
|
+
admin = get_admin_client()
|
|
109
|
+
link = (
|
|
110
|
+
admin.properties()
|
|
111
|
+
.googleAdsLinks()
|
|
112
|
+
.create(parent=f"properties/{effective_property}", body=body)
|
|
113
|
+
.execute()
|
|
114
|
+
)
|
|
115
|
+
output(link, effective_format)
|
|
116
|
+
except typer.Exit:
|
|
117
|
+
raise
|
|
118
|
+
except Exception as e:
|
|
119
|
+
handle_error(e)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@google_ads_links_app.command("update")
|
|
123
|
+
def update_cmd(
|
|
124
|
+
property_id: Optional[str] = typer.Option(
|
|
125
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
126
|
+
),
|
|
127
|
+
link_id: str = typer.Option(
|
|
128
|
+
..., "--link-id", help="Google Ads link ID"
|
|
129
|
+
),
|
|
130
|
+
ads_personalization: Optional[bool] = typer.Option(
|
|
131
|
+
None,
|
|
132
|
+
"--ads-personalization/--no-ads-personalization",
|
|
133
|
+
help="Enable or disable ads personalization",
|
|
134
|
+
),
|
|
135
|
+
dry_run: bool = typer.Option(
|
|
136
|
+
False, "--dry-run", help="Preview the request without executing"
|
|
137
|
+
),
|
|
138
|
+
output_format: Optional[str] = typer.Option(
|
|
139
|
+
None, "--output", "-o", help="Output format (json, table, compact)"
|
|
140
|
+
),
|
|
141
|
+
):
|
|
142
|
+
"""Update a Google Ads link."""
|
|
143
|
+
try:
|
|
144
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
145
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
146
|
+
effective_format = resolve_output_format(output_format)
|
|
147
|
+
|
|
148
|
+
body = {}
|
|
149
|
+
mask_fields = []
|
|
150
|
+
if ads_personalization is not None:
|
|
151
|
+
body["adsPersonalizationEnabled"] = ads_personalization
|
|
152
|
+
mask_fields.append("adsPersonalizationEnabled")
|
|
153
|
+
|
|
154
|
+
if not mask_fields:
|
|
155
|
+
raise typer.BadParameter(
|
|
156
|
+
"At least one field must be specified: "
|
|
157
|
+
"--ads-personalization / --no-ads-personalization"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
resource_name = f"properties/{effective_property}/googleAdsLinks/{link_id}"
|
|
161
|
+
if dry_run:
|
|
162
|
+
handle_dry_run(
|
|
163
|
+
"update", "PATCH", resource_name,
|
|
164
|
+
body, update_mask=",".join(mask_fields),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
admin = get_admin_client()
|
|
168
|
+
link = (
|
|
169
|
+
admin.properties()
|
|
170
|
+
.googleAdsLinks()
|
|
171
|
+
.patch(
|
|
172
|
+
name=resource_name,
|
|
173
|
+
body=body,
|
|
174
|
+
updateMask=",".join(mask_fields),
|
|
175
|
+
)
|
|
176
|
+
.execute()
|
|
177
|
+
)
|
|
178
|
+
output(link, effective_format)
|
|
179
|
+
except (typer.BadParameter, typer.Exit):
|
|
180
|
+
raise
|
|
181
|
+
except Exception as e:
|
|
182
|
+
handle_error(e)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@google_ads_links_app.command("delete")
|
|
186
|
+
def delete_cmd(
|
|
187
|
+
property_id: Optional[str] = typer.Option(
|
|
188
|
+
None, "--property-id", "-p", help="Property ID (numeric)"
|
|
189
|
+
),
|
|
190
|
+
link_id: str = typer.Option(
|
|
191
|
+
..., "--link-id", help="Google Ads link ID"
|
|
192
|
+
),
|
|
193
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
194
|
+
dry_run: bool = typer.Option(
|
|
195
|
+
False, "--dry-run", help="Preview the request without executing"
|
|
196
|
+
),
|
|
197
|
+
):
|
|
198
|
+
"""Delete a Google Ads link."""
|
|
199
|
+
try:
|
|
200
|
+
effective_property = get_effective_value(property_id, "default_property_id")
|
|
201
|
+
require_options({"property_id": effective_property}, ["property_id"])
|
|
202
|
+
|
|
203
|
+
if dry_run:
|
|
204
|
+
handle_dry_run(
|
|
205
|
+
"delete", "DELETE",
|
|
206
|
+
f"properties/{effective_property}/googleAdsLinks/{link_id}",
|
|
207
|
+
None,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if not yes:
|
|
211
|
+
confirmed = questionary.confirm(
|
|
212
|
+
f"Delete Google Ads link {link_id}? This cannot be undone."
|
|
213
|
+
).ask()
|
|
214
|
+
if not confirmed:
|
|
215
|
+
info("Cancelled.")
|
|
216
|
+
raise typer.Exit()
|
|
217
|
+
|
|
218
|
+
admin = get_admin_client()
|
|
219
|
+
resource_name = f"properties/{effective_property}/googleAdsLinks/{link_id}"
|
|
220
|
+
admin.properties().googleAdsLinks().delete(name=resource_name).execute()
|
|
221
|
+
success(f"Google Ads link {link_id} deleted.")
|
|
222
|
+
except typer.Exit:
|
|
223
|
+
raise
|
|
224
|
+
except Exception as e:
|
|
225
|
+
handle_error(e)
|