lookml-generator/tests/test_funnel_analysis.py

323 строки
12 KiB
Python

from textwrap import dedent
from unittest.mock import Mock
import lkml
import pytest
from generator.explores import FunnelAnalysisExplore
from generator.views import FunnelAnalysisView
from .utils import print_and_test
@pytest.fixture()
def funnel_analysis_view():
return FunnelAnalysisView(
"glean_app",
[
{
"funnel_analysis": "events_daily_table",
"event_types": "`mozdata.glean_app.event_types`",
"step_1": "event_types",
"step_2": "event_types",
}
],
)
@pytest.fixture()
def funnel_analysis_explore(tmp_path, funnel_analysis_view):
(tmp_path / "funnel_analysis.view.lkml").write_text(
lkml.dump(funnel_analysis_view.to_lookml(Mock(), None))
)
return FunnelAnalysisExplore(
"funnel_analysis",
{"base_view": "funnel_analysis"},
tmp_path,
)
def test_view_from_db_views(funnel_analysis_view):
db_views = {
"glean_app": {
"events_daily": [
["moz-fx-data-shared-prod", "glean_app_derived", "events_daily_v1"]
],
"event_types": [
["moz-fx-data-shared-prod", "glean_app_derived", "event_types_v1"]
],
}
}
channels = [
{"channel": "release", "dataset": "glean_app"},
{"channel": "beta", "dataset": "glean_app_beta"},
]
actual = next(
FunnelAnalysisView.from_db_views("glean_app", True, channels, db_views, 2)
)
assert actual == funnel_analysis_view
def test_view_from_dict(funnel_analysis_view):
actual = FunnelAnalysisView.from_dict(
"glean_app",
"funnel_analysis",
{
"type": "funnel_analysis_view",
"tables": [
{
"funnel_analysis": "events_daily_table",
"event_types": "`mozdata.glean_app.event_types`",
"step_1": "event_types",
"step_2": "event_types",
}
],
},
)
assert actual == funnel_analysis_view
def test_explore_from_views(funnel_analysis_view):
expected = FunnelAnalysisExplore(
"funnel_analysis", {"base_view": "funnel_analysis"}
)
views = [funnel_analysis_view]
actual = next(FunnelAnalysisExplore.from_views(views))
assert actual == expected
def test_view_lookml(funnel_analysis_view):
expected = {
"includes": ["events_daily_table.view.lkml"],
"views": [
{
"name": "funnel_analysis",
"extends": ["events_daily_table"],
"dimensions": [
{
"name": "completed_step_1",
"description": "Whether the user completed step 1 on the associated day.",
"type": "yesno",
"sql": dedent(
"""
REGEXP_CONTAINS(
${TABLE}.events, mozfun.event_analysis.create_funnel_regex(
[${step_1.match_string}],
True
)
)
"""
),
},
{
"name": "completed_step_2",
"description": "Whether the user completed step 2 on the associated day.",
"type": "yesno",
"sql": dedent(
"""
REGEXP_CONTAINS(
${TABLE}.events, mozfun.event_analysis.create_funnel_regex(
[${step_1.match_string}, ${step_2.match_string}],
True
)
)
"""
),
},
],
"measures": [
{
"name": "count_completed_step_1",
"description": (
"The number of times that step 1 was completed. "
"Grouping by day makes this a count of users who completed "
"step 1 on each day."
),
"type": "count",
"filters": [
{"completed_step_1": "yes"},
],
},
{
"name": "count_completed_step_2",
"description": (
"The number of times that step 2 was completed. "
"Grouping by day makes this a count of users who completed "
"step 2 on each day."
),
"type": "count",
"filters": [
{"completed_step_1": "yes"},
{"completed_step_2": "yes"},
],
},
{
"name": "fraction_completed_step_1",
"description": "Of the user-days that completed Step 1, the fraction that completed step 1.",
"type": "number",
"value_format": "0.00%",
"sql": "SAFE_DIVIDE(${count_completed_step_1}, ${count_completed_step_1})",
},
{
"name": "fraction_completed_step_2",
"description": "Of the user-days that completed Step 1, the fraction that completed step 2.",
"type": "number",
"value_format": "0.00%",
"sql": "SAFE_DIVIDE(${count_completed_step_2}, ${count_completed_step_1})",
},
],
},
{
"name": "event_types",
"derived_table": {
"sql": dedent(
"""
SELECT
mozfun.event_analysis.aggregate_match_strings(
ARRAY_AGG(
DISTINCT CONCAT(
{% if _filters['property_name'] or _filters['property_value'] -%}
COALESCE(mozfun.event_analysis.escape_metachars(property_value.value), ''),
{% endif -%}
mozfun.event_analysis.event_index_to_match_string(et.index)
)
)
) AS match_string
FROM
`mozdata.glean_app.event_types` AS et
LEFT JOIN
UNNEST(COALESCE(event_properties, [])) AS properties
LEFT JOIN
UNNEST(properties.value) AS property_value
WHERE
{% condition category %} category {% endcondition %}
AND {% condition event %} event {% endcondition %}
AND {% condition property_name %} properties.key {% endcondition %}
AND {% condition property_value %} property_value.key {% endcondition %}
"""
)
},
"filters": [
{
"name": "category",
"type": "string",
"description": "The event category, as defined in metrics.yaml.",
"suggest_explore": "event_names",
"suggest_dimension": "event_names.category",
},
{
"name": "event",
"description": "The event name.",
"type": "string",
"suggest_explore": "event_names",
"suggest_dimension": "event_names.event",
},
{
"name": "property_name",
"description": "The event property name.",
"type": "string",
"suggest_explore": "event_names",
"suggest_dimension": "event_names.property_name",
},
{
"name": "property_value",
"description": "The event property value.",
"type": "string",
"suggest_explore": "event_names",
"suggest_dimension": "event_names.property_value",
},
],
"dimensions": [
{
"name": "match_string",
"hidden": "yes",
"sql": "${TABLE}.match_string",
}
],
},
{
"name": "step_1",
"extends": ["event_types"],
},
{
"name": "step_2",
"extends": ["event_types"],
},
{
"name": "event_names",
"derived_table": {
"sql": (
"SELECT category, "
" event, "
" property.key AS property_name, "
" property_value.key AS property_value, "
" property_value.index as property_index "
"FROM `mozdata.glean_app.event_types` "
"LEFT JOIN UNNEST(event_properties) AS property "
"LEFT JOIN UNNEST(property.value) AS property_value "
)
},
"dimensions": [
{
"name": "category",
"type": "string",
"sql": "${TABLE}.category",
},
{
"name": "event",
"type": "string",
"sql": "${TABLE}.event",
},
{
"name": "property_name",
"type": "string",
"sql": "${TABLE}.property_name",
},
{
"name": "property_value",
"type": "string",
"sql": "${TABLE}.property_value",
},
],
},
],
}
actual = funnel_analysis_view.to_lookml(Mock(), None)
print_and_test(expected=expected, actual=actual)
def test_explore_lookml(funnel_analysis_explore):
expected = [
{
"name": "funnel_analysis",
"description": "Count funnel completion over time. Funnels are limited to a single day.",
"view_label": " User-Day Funnels",
"always_filter": {
"filters": [
{"submission_date": "14 days"},
]
},
"joins": [
{
"name": "step_1",
"relationship": "many_to_one",
"type": "cross",
},
{
"name": "step_2",
"relationship": "many_to_one",
"type": "cross",
},
],
"sql_always_where": "${funnel_analysis.submission_date} >= '2010-01-01'",
},
{"name": "event_names", "hidden": "yes"},
]
actual = funnel_analysis_explore.to_lookml(None, None)
print_and_test(expected=expected, actual=actual)