generator/views/client_counts_view.py (86 lines of code) (raw):
"""Class to describe a Client Counts View."""
from __future__ import annotations
from copy import deepcopy
from typing import Any, Dict, Iterator, List, Optional, Union
from .view import View, ViewDict
class ClientCountsView(View):
"""A view for Client Counting measures."""
type: str = "client_counts_view"
default_dimension_groups: List[Dict[str, Union[str, List[str]]]] = [
{
"name": "since_first_seen",
"type": "duration",
"description": "Amount of time that has passed since the client was first seen.",
"sql_start": "CAST(${TABLE}.first_seen_date AS TIMESTAMP)",
"sql_end": "CAST(${TABLE}.submission_date AS TIMESTAMP)",
"intervals": ["day", "week", "month", "year"],
}
]
default_dimensions: List[Dict[str, str]] = [
{
"name": "have_completed_period",
"type": "yesno",
"description": "Only for use with cohort analysis. "
"Filter on true to remove the tail of incomplete data from cohorts. "
"Indicates whether the cohort for this row have all had a chance to complete this interval. "
"For example, new clients from yesterday have not all had a chance to send a ping for today.",
"sql": """
DATE_ADD(
{% if client_counts.first_seen_date._is_selected %}
DATE_ADD(DATE(${client_counts.first_seen_date}), INTERVAL 1 DAY)
{% elsif client_counts.first_seen_week._is_selected %}
DATE_ADD(DATE(${client_counts.first_seen_week}), INTERVAL 1 WEEK)
{% elsif client_counts.first_seen_month._is_selected %}
DATE_ADD(PARSE_DATE('%Y-%m', ${client_counts.first_seen_month}), INTERVAL 1 MONTH)
{% elsif client_counts.first_seen_year._is_selected %}
DATE_ADD(DATE(${client_counts.first_seen_year}, 1, 1), INTERVAL 1 YEAR)
{% endif %}
,
{% if client_counts.days_since_first_seen._is_selected %}
INTERVAL CAST(${client_counts.days_since_first_seen} AS INT64) DAY
{% elsif client_counts.weeks_since_first_seen._is_selected %}
INTERVAL CAST(${client_counts.weeks_since_first_seen} AS INT64) WEEK
{% elsif client_counts.months_since_first_seen._is_selected %}
INTERVAL CAST(${client_counts.months_since_first_seen} AS INT64) MONTH
{% elsif client_counts.years_since_first_seen._is_selected %}
INTERVAL CAST(${client_counts.months_since_first_seen} AS INT64) YEAR
{% endif %}
) < current_date
""",
}
]
default_measures: List[Dict[str, Union[str, List[Dict[str, str]]]]] = [
{
"name": "client_count",
"type": "number",
"description": "The number of clients, "
"determined by whether they sent a baseline ping on the day in question.",
"sql": "COUNT(DISTINCT ${TABLE}.client_id)",
}
]
def __init__(
self,
namespace: str,
tables: List[Dict[str, str]],
name: str = "client_counts",
):
"""Get an instance of a ClientCountsView."""
super().__init__(namespace, name, ClientCountsView.type, tables)
@classmethod
def from_db_views(
klass,
namespace: str,
is_glean: bool,
channels: List[Dict[str, str]],
db_views: dict,
) -> Iterator[ClientCountsView]:
"""Get Client Count Views from db views and app variants."""
# We can guarantee there will always be at least one channel,
# because this comes from the associated _get_glean_repos in
# namespaces.py
dataset = next(
(channel for channel in channels if channel.get("channel") == "release"),
channels[0],
)["dataset"]
for view_id, references in db_views[dataset].items():
if view_id == "baseline_clients_daily" or view_id == "clients_daily":
yield ClientCountsView(
namespace, [{"table": f"mozdata.{dataset}.{view_id}"}]
)
@classmethod
def from_dict(
klass, namespace: str, name: str, _dict: ViewDict
) -> ClientCountsView:
"""Get a view from a name and dict definition."""
return ClientCountsView(namespace, _dict["tables"], name)
def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]:
"""Generate LookML for this view."""
table = self.tables[0]["table"]
base_view = "baseline_clients_daily_table"
if table is not None:
base_view = table.split(".")[-1] + "_table"
view_defn: Dict[str, Any] = {
"extends": [base_view],
"name": self.name,
}
# add dimensions and dimension groups
view_defn["dimensions"] = deepcopy(ClientCountsView.default_dimensions)
view_defn["dimension_groups"] = deepcopy(
ClientCountsView.default_dimension_groups
)
# add measures
view_defn["measures"] = self.get_measures()
return {
"includes": [base_view + ".view.lkml"],
"views": [view_defn],
}
def get_measures(self) -> List[Dict[str, Union[str, List[Dict[str, str]]]]]:
"""Generate measures for the Growth Accounting Framework."""
return deepcopy(ClientCountsView.default_measures)