Fix 17911: Looker parsing improvements for liquid templating and view/model aliasing (#17912)

* Looker parsing improvements for liquid templating and view/model aliasing

* add python-liquid dependency to looker plugin requirements

* move to static method with 'openmetadata' context and add rendering tests

* remove backtick stripping

---------

Co-authored-by: Imri Paran <imri.paran@gmail.com>
This commit is contained in:
sam-mccarty-mavenclinic 2024-09-27 07:55:15 -04:00 committed by GitHub
parent e373fddbde
commit 0dd3e97170
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 4 deletions

View File

@ -258,6 +258,7 @@ plugins: Dict[str, Set[str]] = {
VERSIONS["lkml"], VERSIONS["lkml"],
"gitpython~=3.1.34", "gitpython~=3.1.34",
VERSIONS["giturlparse"], VERSIONS["giturlparse"],
"python-liquid",
}, },
"mlflow": {"mlflow-skinny>=2.3.0"}, "mlflow": {"mlflow-skinny>=2.3.0"},
"mongo": {VERSIONS["mongo"], VERSIONS["pandas"], VERSIONS["numpy"]}, "mongo": {VERSIONS["mongo"], VERSIONS["pandas"], VERSIONS["numpy"]},

View File

@ -28,6 +28,7 @@ from typing import Dict, Iterable, List, Optional, Sequence, Set, Type, Union, c
import giturlparse import giturlparse
import lkml import lkml
from liquid import Template
from looker_sdk.sdk.api40.methods import Looker40SDK from looker_sdk.sdk.api40.methods import Looker40SDK
from looker_sdk.sdk.api40.models import Dashboard as LookerDashboard from looker_sdk.sdk.api40.models import Dashboard as LookerDashboard
from looker_sdk.sdk.api40.models import ( from looker_sdk.sdk.api40.models import (
@ -450,11 +451,11 @@ class LookerSource(DashboardServiceSource):
view.name, "Data model (View) filtered out." view.name, "Data model (View) filtered out."
) )
continue continue
view_name = view.from_ if view.from_ else view.name
yield from self._process_view( yield from self._process_view(
view_name=ViewName(view.name), explore=model view_name=ViewName(view_name), explore=model
) )
if len(model.joins) == 0 and model.sql_table_name: if model.view_name:
yield from self._process_view( yield from self._process_view(
view_name=ViewName(model.view_name), explore=model view_name=ViewName(model.view_name), explore=model
) )
@ -570,7 +571,8 @@ class LookerSource(DashboardServiceSource):
db_service_names = self.get_db_service_names() db_service_names = self.get_db_service_names()
if view.sql_table_name: if view.sql_table_name:
source_table_name = self._clean_table_name(view.sql_table_name) sql_table_name = self._render_table_name(view.sql_table_name)
source_table_name = self._clean_table_name(sql_table_name)
# View to the source is only there if we are informing the dbServiceNames # View to the source is only there if we are informing the dbServiceNames
for db_service_name in db_service_names or []: for db_service_name in db_service_names or []:
@ -726,6 +728,33 @@ class LookerSource(DashboardServiceSource):
return table_name.lower().split(" as ")[0].strip() return table_name.lower().split(" as ")[0].strip()
@staticmethod
def _render_table_name(table_name: str) -> str:
"""
sql_table_names might contain Liquid templates
when defining an explore. e.g,:
sql_table_name:
{% if openmetadata %}
event
{% elsif event.created_week._in_query %}
event_by_week
{% else %}
event
{% endif %} ;;
we should render the template and give the option
to render a specific value during metadata ingestion
using the "openmetadata" context argument
:param table_name: table name with possible templating
:return: rendered table name
"""
try:
context = {"openmetadata": True}
template = Template(table_name)
sql_table_name = template.render(context)
except Exception:
sql_table_name = table_name
return sql_table_name
@staticmethod @staticmethod
def get_dashboard_sources(dashboard_details: LookerDashboard) -> Set[str]: def get_dashboard_sources(dashboard_details: LookerDashboard) -> Set[str]:
""" """

View File

@ -300,6 +300,53 @@ class LookerUnitTest(TestCase):
self.assertEqual(self.looker._clean_table_name("TABLE AS ALIAS"), "table") self.assertEqual(self.looker._clean_table_name("TABLE AS ALIAS"), "table")
def test_render_table_name(self):
"""
Check that table is rendered correctly if "openmetadata" or default condition apply, or no templating is present
"""
tagged_table_name_template = """
{%- if openmetadata -%}
`BQ-project.dataset.sample_data`
{%- elsif prod -%}
`BQ-project.dataset.sample_data`
{%- elsif dev -%}
`BQ-project.{{_user_attributes['dbt_dev_schema']}}.sample_data`
{%- endif -%}
"""
default_table_name_template = """
{%- if prod -%}
`BQ-project.dataset.sample_data`
{%- elsif dev -%}
`BQ-project.{{_user_attributes['dbt_dev_schema']}}.sample_data`
{%- else -%}
`BQ-project.dataset.sample_data`
{%- endif -%}
"""
untagged_table_name_template = """
{%- if prod -%}
`BQ-project.dataset.sample_data`
{%- elsif dev -%}
`BQ-project.{{_user_attributes['dbt_dev_schema']}}.sample_data`
{%- endif -%}
"""
table_name_plain = "`BQ-project.dataset.sample_data`"
self.assertEqual(
self.looker._render_table_name(tagged_table_name_template),
"`BQ-project.dataset.sample_data`",
)
self.assertEqual(
self.looker._render_table_name(default_table_name_template),
"`BQ-project.dataset.sample_data`",
)
self.assertNotEqual(
self.looker._render_table_name(untagged_table_name_template),
"`BQ-project.dataset.sample_data`",
)
self.assertEqual(
self.looker._render_table_name(table_name_plain),
"`BQ-project.dataset.sample_data`",
)
def test_get_dashboard_sources(self): def test_get_dashboard_sources(self):
""" """
Check how we are building the sources Check how we are building the sources