2021-03-16 00:49:32 +03:00
|
|
|
"""Generate lookml from namespaces."""
|
2021-03-23 00:08:38 +03:00
|
|
|
import logging
|
2021-03-16 00:49:32 +03:00
|
|
|
from pathlib import Path
|
2021-05-06 00:22:45 +03:00
|
|
|
from typing import Dict, Iterable, Optional
|
2021-03-16 00:49:32 +03:00
|
|
|
|
|
|
|
import click
|
2021-09-20 21:45:22 +03:00
|
|
|
import lkml
|
2021-03-16 00:49:32 +03:00
|
|
|
import yaml
|
|
|
|
from google.cloud import bigquery
|
|
|
|
|
2021-11-09 18:47:34 +03:00
|
|
|
from . import operational_monitoring_utils
|
2021-10-13 18:37:51 +03:00
|
|
|
from .dashboards import DASHBOARD_TYPES
|
2021-04-30 19:31:29 +03:00
|
|
|
from .explores import EXPLORE_TYPES
|
2021-04-30 20:08:32 +03:00
|
|
|
from .namespaces import _get_glean_apps
|
2021-04-30 19:31:29 +03:00
|
|
|
from .views import VIEW_TYPES, View, ViewDict
|
2021-03-16 00:49:32 +03:00
|
|
|
|
|
|
|
|
2021-05-06 00:22:45 +03:00
|
|
|
def _generate_views(
|
|
|
|
client, out_dir: Path, views: Iterable[View], v1_name: Optional[str]
|
|
|
|
) -> Iterable[Path]:
|
2021-04-14 14:56:17 +03:00
|
|
|
for view in views:
|
2021-05-06 20:41:10 +03:00
|
|
|
logging.info(
|
2021-05-07 06:35:34 +03:00
|
|
|
f"Generating lookml for view {view.name} in {view.namespace} of type {view.view_type}"
|
2021-05-06 20:37:37 +03:00
|
|
|
)
|
2021-04-14 14:56:17 +03:00
|
|
|
path = out_dir / f"{view.name}.view.lkml"
|
2021-05-06 23:22:18 +03:00
|
|
|
lookml = view.to_lookml(client, v1_name)
|
2021-09-20 21:45:22 +03:00
|
|
|
path.write_text(lkml.dump(lookml))
|
2021-03-23 00:08:38 +03:00
|
|
|
yield path
|
|
|
|
|
|
|
|
|
|
|
|
def _generate_explores(
|
2021-06-03 00:30:49 +03:00
|
|
|
client,
|
|
|
|
out_dir: Path,
|
|
|
|
namespace: str,
|
|
|
|
explores: dict,
|
|
|
|
views_dir: Path,
|
|
|
|
v1_name: Optional[
|
|
|
|
str
|
|
|
|
], # v1_name for Glean explores: see: https://mozilla.github.io/probe-scraper/#tag/library
|
2021-11-09 18:47:34 +03:00
|
|
|
namespace_data: dict,
|
2021-03-23 00:08:38 +03:00
|
|
|
) -> Iterable[Path]:
|
|
|
|
for explore_name, defn in explores.items():
|
2021-05-07 06:35:34 +03:00
|
|
|
logging.info(f"Generating lookml for explore {explore_name} in {namespace}")
|
2021-04-30 19:31:29 +03:00
|
|
|
explore = EXPLORE_TYPES[defn["type"]].from_dict(explore_name, defn, views_dir)
|
2021-03-23 00:08:38 +03:00
|
|
|
file_lookml = {
|
2021-04-16 04:42:11 +03:00
|
|
|
# Looker validates all included files,
|
|
|
|
# so if we're not explicit about files here, validation takes
|
|
|
|
# forever as looker re-validates all views for every explore (if we used *).
|
|
|
|
"includes": [
|
|
|
|
f"/looker-hub/{namespace}/views/{view}.view.lkml"
|
|
|
|
for view in explore.get_dependent_views()
|
|
|
|
],
|
2021-11-09 18:47:34 +03:00
|
|
|
"explores": explore.to_lookml(client, v1_name, namespace_data),
|
2021-03-23 00:08:38 +03:00
|
|
|
}
|
|
|
|
path = out_dir / (explore_name + ".explore.lkml")
|
2021-09-20 21:45:22 +03:00
|
|
|
path.write_text(lkml.dump(file_lookml))
|
2021-03-23 00:08:38 +03:00
|
|
|
yield path
|
|
|
|
|
|
|
|
|
2021-10-13 18:37:51 +03:00
|
|
|
def _generate_dashboards(
|
2021-11-09 18:47:34 +03:00
|
|
|
client,
|
|
|
|
dash_dir: Path,
|
|
|
|
namespace: str,
|
|
|
|
dashboards: dict,
|
|
|
|
namespace_data: dict,
|
2021-10-13 18:37:51 +03:00
|
|
|
):
|
|
|
|
for dashboard_name, dashboard_info in dashboards.items():
|
|
|
|
logging.info(f"Generating lookml for dashboard {dashboard_name} in {namespace}")
|
|
|
|
dashboard = DASHBOARD_TYPES[dashboard_info["type"]].from_dict(
|
|
|
|
namespace, dashboard_name, dashboard_info
|
|
|
|
)
|
|
|
|
|
2021-11-26 19:50:45 +03:00
|
|
|
dashboard_lookml = dashboard.to_lookml(client, namespace_data)
|
2021-10-13 18:37:51 +03:00
|
|
|
dash_path = dash_dir / f"{dashboard_name}.dashboard.lookml"
|
|
|
|
dash_path.write_text(dashboard_lookml)
|
|
|
|
yield dash_path
|
|
|
|
|
|
|
|
|
2021-05-06 00:22:45 +03:00
|
|
|
def _get_views_from_dict(views: Dict[str, ViewDict], namespace: str) -> Iterable[View]:
|
2021-04-14 14:56:17 +03:00
|
|
|
for view_name, view_info in views.items():
|
2021-05-03 20:34:36 +03:00
|
|
|
yield VIEW_TYPES[view_info["type"]].from_dict( # type: ignore
|
2021-05-05 20:28:48 +03:00
|
|
|
namespace, view_name, view_info
|
2021-05-03 20:37:13 +03:00
|
|
|
)
|
2021-04-14 14:56:17 +03:00
|
|
|
|
|
|
|
|
2021-05-06 00:22:45 +03:00
|
|
|
def _glean_apps_to_v1_map(glean_apps):
|
|
|
|
return {d["name"]: d["v1_name"] for d in glean_apps}
|
|
|
|
|
|
|
|
|
2021-11-09 18:47:34 +03:00
|
|
|
def _append_data(bq_client, data_functions, view, namespace_data):
|
|
|
|
# For now, assume that data functions are applied
|
|
|
|
# to each table. Later it could be a mapping so only
|
|
|
|
# certain functions are applied to certain tables.
|
|
|
|
for function_name in data_functions:
|
|
|
|
if function_name not in namespace_data:
|
|
|
|
namespace_data[function_name] = {}
|
|
|
|
|
|
|
|
namespace_data[function_name][view.name] = {}
|
|
|
|
|
|
|
|
for table in view.tables:
|
|
|
|
table_name = table["table"]
|
|
|
|
data_function = getattr(operational_monitoring_utils, function_name)
|
|
|
|
namespace_data[function_name][view.name][table_name] = data_function(
|
|
|
|
bq_client, table_name
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-04-30 20:08:32 +03:00
|
|
|
def _lookml(namespaces, glean_apps, target_dir):
|
2021-03-16 00:49:32 +03:00
|
|
|
client = bigquery.Client()
|
2021-05-21 00:28:29 +03:00
|
|
|
|
|
|
|
namespaces_content = namespaces.read()
|
|
|
|
_namespaces = yaml.safe_load(namespaces_content)
|
2021-03-16 00:49:32 +03:00
|
|
|
target = Path(target_dir)
|
2021-05-21 00:28:29 +03:00
|
|
|
target.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
# Write namespaces file to target directory, for use
|
|
|
|
# by the Glean Dictionary and other tools
|
|
|
|
with open(target / "namespaces.yaml", "w") as target_namespaces_file:
|
|
|
|
target_namespaces_file.write(namespaces_content)
|
|
|
|
|
2021-05-06 00:22:45 +03:00
|
|
|
v1_mapping = _glean_apps_to_v1_map(glean_apps)
|
2021-04-14 14:56:17 +03:00
|
|
|
for namespace, lookml_objects in _namespaces.items():
|
2021-03-23 00:08:38 +03:00
|
|
|
logging.info(f"\nGenerating namespace {namespace}")
|
|
|
|
|
|
|
|
view_dir = target / namespace / "views"
|
2021-03-16 00:49:32 +03:00
|
|
|
view_dir.mkdir(parents=True, exist_ok=True)
|
2021-11-09 18:47:34 +03:00
|
|
|
views = list(_get_views_from_dict(lookml_objects.get("views", {}), namespace))
|
|
|
|
|
|
|
|
namespace_data = {}
|
|
|
|
for view in views:
|
|
|
|
_append_data(
|
|
|
|
client, lookml_objects.get("data_functions", []), view, namespace_data
|
|
|
|
)
|
2021-03-23 00:08:38 +03:00
|
|
|
|
|
|
|
logging.info(" Generating views")
|
2021-05-06 00:22:45 +03:00
|
|
|
v1_name: Optional[str] = v1_mapping.get(namespace)
|
|
|
|
for view_path in _generate_views(client, view_dir, views, v1_name):
|
2021-03-23 00:08:38 +03:00
|
|
|
logging.info(f" ...Generating {view_path}")
|
|
|
|
|
|
|
|
explore_dir = target / namespace / "explores"
|
|
|
|
explore_dir.mkdir(parents=True, exist_ok=True)
|
2021-04-14 14:56:17 +03:00
|
|
|
explores = lookml_objects.get("explores", {})
|
2021-03-23 00:08:38 +03:00
|
|
|
logging.info(" Generating explores")
|
|
|
|
for explore_path in _generate_explores(
|
2021-11-09 18:47:34 +03:00
|
|
|
client, explore_dir, namespace, explores, view_dir, v1_name, namespace_data
|
2021-03-23 00:08:38 +03:00
|
|
|
):
|
|
|
|
logging.info(f" ...Generating {explore_path}")
|
2021-04-29 00:30:41 +03:00
|
|
|
|
2021-10-13 18:37:51 +03:00
|
|
|
logging.info(" Generating dashboards")
|
2021-11-26 19:50:45 +03:00
|
|
|
dashboard_dir = target / namespace / "dashboards"
|
2021-10-13 18:37:51 +03:00
|
|
|
dashboard_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
dashboards = lookml_objects.get("dashboards", {})
|
|
|
|
for dashboard_path in _generate_dashboards(
|
2021-11-26 19:50:45 +03:00
|
|
|
client, dashboard_dir, namespace, dashboards, namespace_data
|
2021-10-13 18:37:51 +03:00
|
|
|
):
|
|
|
|
logging.info(f" ...Generating {dashboard_path}")
|
|
|
|
|
2021-04-29 00:30:41 +03:00
|
|
|
|
|
|
|
@click.command(help=__doc__)
|
|
|
|
@click.option(
|
|
|
|
"--namespaces",
|
|
|
|
default="namespaces.yaml",
|
|
|
|
type=click.File(),
|
|
|
|
help="Path to a yaml namespaces file",
|
|
|
|
)
|
2021-04-30 20:08:32 +03:00
|
|
|
@click.option(
|
|
|
|
"--app-listings-uri",
|
|
|
|
default="https://probeinfo.telemetry.mozilla.org/v2/glean/app-listings",
|
|
|
|
help="URI for probeinfo service v2 glean app listings",
|
|
|
|
)
|
2021-04-29 00:30:41 +03:00
|
|
|
@click.option(
|
|
|
|
"--target-dir",
|
|
|
|
default="looker-hub/",
|
|
|
|
type=click.Path(),
|
|
|
|
help="Path to a directory where lookml will be written",
|
|
|
|
)
|
2021-04-30 20:08:32 +03:00
|
|
|
def lookml(namespaces, app_listings_uri, target_dir):
|
2021-04-29 00:30:41 +03:00
|
|
|
"""Generate lookml from namespaces."""
|
2021-04-30 20:08:32 +03:00
|
|
|
glean_apps = _get_glean_apps(app_listings_uri)
|
|
|
|
return _lookml(namespaces, glean_apps, target_dir)
|