Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,21 @@
],
"console": "integratedTerminal",
},
{
"name": "CLI: add standard callbacks",
"type": "debugpy",
"request": "launch",
"module": "adjust.cli",
"cwd": "/Users/jlopez/IdeaProjects/adjust-placeholders",
"args": [
"snapshot",
"modify",
"--matching-app",
"cats.*dogs",
"--add-standard-callbacks",
"https://invenio.sgn.com/v1/adjust/panda-pop?_google_cd=xxxx&_itunes_cd=yyyy&app_token=zzzz",
],
"console": "integratedTerminal",
},
]
}
11 changes: 11 additions & 0 deletions adjust/api/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ class Callback(BaseModel):
custom: bool
token: str | None

@classmethod
def empty_callback_for_trigger(cls, trigger: CallbackType | int) -> Callback:
return cls(
id=trigger,
type=trigger,
name=trigger,
urls=[],
custom=isinstance(trigger, int),
token=None,
)

@property
def url(self) -> str | None:
return " ".join(u.url for u in self.urls) if self.urls else None
Expand Down
3 changes: 0 additions & 3 deletions adjust/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
from .cli import main, cli # noqa: F401

if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions adjust/cli/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .cli import main

if __name__ == "__main__":
main()
49 changes: 48 additions & 1 deletion adjust/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from click_help_colors import HelpColorsGroup

from adjust.api.model import Callback, CallbackType, CallbackURL
from adjust.snapshot import (
snapshot_callback_count,
snapshot_diff,
Expand All @@ -18,7 +19,7 @@

from ..api import AdjustAPI
from ..utils import AddCounters
from .types import REGEX
from .types import CALLBACK_URL, REGEX


pass_api = click.make_pass_decorator(AdjustAPI)
Expand Down Expand Up @@ -90,6 +91,38 @@ def restore(api: AdjustAPI, snapshot_path: str, dry_run: bool) -> None:
click.echo(f"✅ Done. {'Would have updated' if dry_run else 'Updated'} {num_callbacks} callbacks.")


# Triggers used currently by our games (and their frequencies) are:
# 46 install
# 43 attribution_update
# 42 sk_install
# 37 reattribution
# 32 click
# 29 impression
# 25 rejected_reattribution
# 25 rejected_install
# 23 sk_event
# 9 sk_qualifier <-- Setting a threshold, standard triggers above this line
# 3 sk_install_direct
# 3 sk_cv_update
# 3 session
# 1 uninstall
# 1 subscription
# 1 reinstall
# 1 global
# 1 cost_update
# 1 att_consent
# 1 ad revenue
_STANDARD_TRIGGERS: list[CallbackType] = [
"install", "attribution_update", "sk_install", "reattribution",
"click", "impression", "rejected_reattribution", "rejected_install",
"sk_event",
# These callbacks are used only on a few games, therefore, we don't
# consider them standard triggers yet
# "sk_qualifier", "sk_install_direct", "sk_cv_update", "session",
# "uninstall", "subscription", "reinstall", "global", "cost_update",
# "att_consent", "ad_revenue",
]

@snapshot.command(help="Modify local snapshot")
@click.option(
"--snapshot",
Expand All @@ -110,6 +143,7 @@ def restore(api: AdjustAPI, snapshot_path: str, dry_run: bool) -> None:
@click.option("--matching-domain", multiple=True, type=REGEX, help="Only modify callbacks whose URL domain matches REGEX")
@click.option("--matching-path", multiple=True, type=REGEX, help="Only modify callbacks whose URL matches REGEX")
@click.option("--add-placeholder", "-a", multiple=True, metavar="PH", help="Add placeholder PH to all matching callbacks")
@click.option("--add-standard-callbacks", type=CALLBACK_URL, help="Add standard callbacks to all matching apps using base URL")
@click.option("--dry-run", "-n", is_flag=True, help="Do not update the snapshot, only simulate what would be done.")
@pass_api
def modify(
Expand All @@ -125,6 +159,7 @@ def modify(
matching_domain: list[re.Pattern],
matching_path: list[re.Pattern],
add_placeholder: list[str],
add_standard_callbacks: CallbackURL,
dry_run: bool,
) -> None:
counters = AddCounters()
Expand All @@ -138,6 +173,11 @@ def modify(
continue
if matching_app and not any(regex.match(app_name) for regex in matching_app):
continue
if add_standard_callbacks:
for trigger in _STANDARD_TRIGGERS:
if not any(c.id == trigger for c in callbacks):
callback = Callback.empty_callback_for_trigger(trigger)
callbacks.append(callback)
for callback in callbacks:
modified = False
for url in callback.urls:
Expand All @@ -157,6 +197,13 @@ def modify(
url.add_placeholder(ph)
counters.urls += 1
modified = True
if add_standard_callbacks and not any(u.netloc != add_standard_callbacks.netloc for u in callback.urls):
url = add_standard_callbacks.copy()
for placeholder in api.placeholders:
url.add_placeholder(f"{placeholder.category}_{placeholder.placeholder}")
callback.urls.append(add_standard_callbacks)
counters.urls += 1
modified = True
if modified:
counters.apps_seen.add(app_token)
counters.callbacks += 1
Expand Down
20 changes: 19 additions & 1 deletion adjust/cli/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

from typing import Optional

from adjust.api.model import CallbackURL


class RegexType(click.ParamType):
name = "regex"

def convert(self, arg: str, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> re.Pattern:
try:
return re.compile(arg)
return re.compile(arg, re.IGNORECASE)
except Exception as e:
self.fail(f"Invalid regex: {e}", param, ctx)

Expand All @@ -18,3 +20,19 @@ def get_metavar(self, param: click.Parameter) -> str | None:


REGEX = RegexType()


class CallbackURLType(click.ParamType):
name = "callback-url"

def convert(self, arg: str, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> CallbackURL:
try:
return CallbackURL.from_url(arg)
except Exception as e:
self.fail(f"Invalid callback URL: {e}", param, ctx)

def get_metavar(self, param: click.Parameter) -> str | None:
return "URL"


CALLBACK_URL = CallbackURLType()
Loading