Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Snowplow tracking for behavior flags #10721

Merged
merged 8 commits into from
Sep 18, 2024
Merged
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
6 changes: 6 additions & 0 deletions .changes/unreleased/Under the Hood-20240911-162730.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Under the Hood
body: Add Snowplow tracking for behavior flag deprecations
time: 2024-09-11T16:27:30.293832-04:00
custom:
Author: mikealfare
Issue: "10552"
3 changes: 3 additions & 0 deletions core/dbt/events/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
from functools import partial
from typing import Callable, List

from dbt.tracking import track_behavior_change_warn
from dbt_common.events.base_types import EventLevel, EventMsg
from dbt_common.events.event_manager_client import (
add_callback_to_manager,
add_logger_to_manager,
cleanup_event_logger,
get_event_manager,
Expand Down Expand Up @@ -68,6 +70,7 @@ def setup_event_logger(flags, callbacks: List[Callable[[EventMsg], None]] = [])
make_log_dir_if_missing(flags.LOG_PATH)
event_manager = get_event_manager()
event_manager.callbacks = callbacks.copy()
add_callback_to_manager(track_behavior_change_warn)

if flags.LOG_LEVEL != "none":
line_format = _line_format_from_str(flags.LOG_FORMAT, LineFormat.PlainText)
Expand Down
18 changes: 17 additions & 1 deletion core/dbt/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
SendingEvent,
TrackingInitializeFailure,
)
from dbt_common.events.functions import fire_event, get_invocation_id
from dbt_common.events.base_types import EventMsg
from dbt_common.events.functions import fire_event, get_invocation_id, msg_to_dict
from dbt_common.exceptions import NotImplementedError

sp_logger.setLevel(100)
Expand All @@ -36,6 +37,7 @@

ADAPTER_INFO_SPEC = "iglu:com.dbt/adapter_info/jsonschema/1-0-1"
DEPRECATION_WARN_SPEC = "iglu:com.dbt/deprecation_warn/jsonschema/1-0-0"
BEHAVIOR_CHANGE_WARN_SPEC = "iglu:com.dbt/behavior_change_warn/jsonschema/1-0-0"
EXPERIMENTAL_PARSER = "iglu:com.dbt/experimental_parser/jsonschema/1-0-0"
INVOCATION_ENV_SPEC = "iglu:com.dbt/invocation_env/jsonschema/1-0-0"
INVOCATION_SPEC = "iglu:com.dbt/invocation/jsonschema/1-0-2"
Expand Down Expand Up @@ -364,6 +366,20 @@ def track_deprecation_warn(options):
)


def track_behavior_change_warn(msg: EventMsg) -> None:
if msg.info.name != "BehaviorChangeEvent" or active_user is None:
return

context = [SelfDescribingJson(BEHAVIOR_CHANGE_WARN_SPEC, msg_to_dict(msg))]
track(
active_user,
category="dbt",
action=msg.info.name,
label=get_invocation_id(),
context=context,
)


def track_invocation_end(invocation_context, result_type=None):
data = {"progress": "end", "result_type": result_type, "result": None}
data.update(invocation_context)
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/events/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_clears_preexisting_event_manager_state(self) -> None:

setup_event_logger(get_flags())
assert len(manager.loggers) == 0
assert len(manager.callbacks) == 0
assert len(manager.callbacks) == 1 # snowplow tracker for behavior flags

def test_specify_max_bytes(
self,
Expand Down
65 changes: 65 additions & 0 deletions tests/unit/test_behavior_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import pytest

from dbt.tracking import (
disable_tracking,
initialize_from_flags,
track_behavior_change_warn,
)
from dbt_common.behavior_flags import Behavior
from dbt_common.events.event_manager_client import (
add_callback_to_manager,
cleanup_event_logger,
)


@pytest.fixture
def snowplow_tracker(mocker):
# initialize `active_user` without writing the cookie to disk
initialize_from_flags(True, "")
mocker.patch("dbt.tracking.User.set_cookie").return_value = {"id": 42}

# add the relevant callback to the event manager
add_callback_to_manager(track_behavior_change_warn)

# don't make a call, catch the request
snowplow_tracker = mocker.patch("dbt.tracking.tracker.track_struct_event")

yield snowplow_tracker

# teardown
cleanup_event_logger()
disable_tracking()


def test_false_evaluation_triggers_snowplow_tracking(snowplow_tracker):
behavior = Behavior(
[{"name": "my_flag", "default": False, "description": "This is a false flag."}], {}
)
if behavior.my_flag:
# trigger a False evaluation
assert False, "This flag should evaluate to false and skip this line"
assert snowplow_tracker.called


def test_true_evaluation_does_not_trigger_snowplow_tracking(snowplow_tracker):
behavior = Behavior(
[{"name": "my_flag", "default": True, "description": "This is a true flag."}], {}
)
if behavior.my_flag:
pass
else:
# trigger a True evaluation
assert False, "This flag should evaluate to false and skip this line"
assert not snowplow_tracker.called


def test_false_evaluation_does_not_trigger_snowplow_tracking_when_disabled(snowplow_tracker):
disable_tracking()

behavior = Behavior(
[{"name": "my_flag", "default": False, "description": "This is a false flag."}], {}
)
if behavior.my_flag:
# trigger a False evaluation
assert False, "This flag should evaluate to false and skip this line"
assert not snowplow_tracker.called
Loading