From d959f0bdd411cc5266f089f289ba9a4f609d1ffc Mon Sep 17 00:00:00 2001 From: Will Deng Date: Mon, 4 Dec 2023 13:53:19 -0500 Subject: [PATCH] added tests --- schemas/dbt/manifest/v11.json | 76 ++++++++++++++++++- tests/functional/metrics/fixtures.py | 38 ++++++++++ tests/functional/metrics/test_metrics.py | 57 ++++++++++++++ ..._semantic_layer_nodes_satisfy_protocols.py | 18 +++++ 4 files changed, 188 insertions(+), 1 deletion(-) diff --git a/schemas/dbt/manifest/v11.json b/schemas/dbt/manifest/v11.json index 83db47ea3bf..003e985a168 100644 --- a/schemas/dbt/manifest/v11.json +++ b/schemas/dbt/manifest/v11.json @@ -4753,6 +4753,76 @@ "name" ] }, + "ConstantPropertyInput": { + "type": "object", + "title": "ConstantPropertyInput", + "properties": { + "base_property": { + "type": "string" + }, + "conversion_property": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "base_property", + "conversion_property" + ] + }, + "ConversionTypeParams": { + "type": "object", + "title": "ConversionTypeParams", + "properties": { + "base_measure": { + "$ref": "#/$defs/MetricInputMeasure" + }, + "conversion_measure": { + "$ref": "#/$defs/MetricInputMeasure" + }, + "entity": { + "type": "string" + }, + "calculation":{ + "enum": [ + "conversions", + "conversion_rate", + ], + "default": "conversion_rate" + }, + "window": { + "anyOf": [ + { + "$ref": "#/$defs/MetricTimeWindow" + }, + { + "type": "null" + } + ], + "default": null + }, + "constant_properties": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/ConstantPropertyInput" + } + }, + { + "type": "null" + } + ], + "default": null + } + }, + "additionalProperties": false, + "required": [ + "base_measure", + "conversion_measure", + "entity" + ] + }, "MetricTypeParams": { "type": "object", "title": "MetricTypeParams", @@ -4848,7 +4918,11 @@ } ], "default": null - } + }, + "conversion_type_params": { + "$ref": "#/$defs/ConversionTypeParams", + "default": null + }, }, "additionalProperties": false }, diff --git a/tests/functional/metrics/fixtures.py b/tests/functional/metrics/fixtures.py index 89eeb930043..5a8373fbe5d 100644 --- a/tests/functional/metrics/fixtures.py +++ b/tests/functional/metrics/fixtures.py @@ -626,3 +626,41 @@ meta: my_meta: 'testing' """ + +conversion_semantic_model_purchasing_yml = """ +version: 2 + +semantic_models: + - name: semantic_purchasing + model: ref('purchasing') + measures: + - name: num_orders + agg: COUNT + expr: purchased_at + - name: num_visits + agg: SUM + expr: 1 + dimensions: + - name: purchased_at + type: TIME + entities: + - name: purchase + type: primary + expr: '1' + defaults: + agg_time_dimension: purchased_at + +""" + +conversion_metric_yml = """ +version: 2 +metrics: + - name: converted_orders_over_visits + label: Number of orders converted from visits + type: conversion + type_params: + conversion_type_params: + base_measure: num_visits + conversion_measure: num_orders + entity: purchase +""" diff --git a/tests/functional/metrics/test_metrics.py b/tests/functional/metrics/test_metrics.py index 3cc0ea412b7..db60b2f749a 100644 --- a/tests/functional/metrics/test_metrics.py +++ b/tests/functional/metrics/test_metrics.py @@ -7,6 +7,8 @@ from tests.functional.metrics.fixtures import ( + conversion_semantic_model_purchasing_yml, + conversion_metric_yml, mock_purchase_data_csv, models_people_sql, models_people_metrics_yml, @@ -339,3 +341,58 @@ def test_simple_metric( # initial run with pytest.raises(ParsingError): run_dbt(["run"]) + + +class TestConversionMetric: + @pytest.fixture(scope="class") + def models(self): + return { + "downstream_model.sql": downstream_model_sql, + "purchasing.sql": purchasing_model_sql, + "metricflow_time_spine.sql": metricflow_time_spine_sql, + "semantic_models.yml": conversion_semantic_model_purchasing_yml, + "conversion_metric.yml": conversion_metric_yml, + } + + def test_conversion_metric( + self, + project, + ): + # initial parse + results = run_dbt(["parse"]) + + # make sure all the metrics are in the manifest + manifest = get_manifest(project.project_root) + metric_ids = list(manifest.metrics.keys()) + expected_metric_ids = [ + "metric.test.converted_orders_over_visits", + ] + assert metric_ids == expected_metric_ids + + # make sure the downstream_model depends on these metrics + metric_names = ["converted_orders_over_visits"] + downstream_model = manifest.nodes["model.test.downstream_model"] + assert sorted(downstream_model.metrics[0]) == [ + [metric_name] for metric_name in metric_names + ] + assert sorted(downstream_model.depends_on.nodes) == [ + "metric.test.converted_orders_over_visits", + ] + assert sorted(downstream_model.config["metric_names"]) == metric_names + + # actually compile + results = run_dbt(["compile", "--select", "downstream_model"]) + compiled_code = results[0].node.compiled_code + + # make sure all these metrics properties show up in compiled SQL + for metric_name in manifest.metrics: + parsed_metric_node = manifest.metrics[metric_name] + for property in [ + "name", + "label", + "type", + "type_params", + "filter", + ]: + expected_value = getattr(parsed_metric_node, property) + assert f"{property}: {expected_value}" in compiled_code diff --git a/tests/unit/test_semantic_layer_nodes_satisfy_protocols.py b/tests/unit/test_semantic_layer_nodes_satisfy_protocols.py index b144603409f..8a3f588d69f 100644 --- a/tests/unit/test_semantic_layer_nodes_satisfy_protocols.py +++ b/tests/unit/test_semantic_layer_nodes_satisfy_protocols.py @@ -2,6 +2,8 @@ import copy from dbt.contracts.graph.nodes import ( + ConstantPropertyInput, + ConversionTypeParams, Metric, MetricInput, MetricInputMeasure, @@ -233,6 +235,21 @@ def complex_metric_input_measure(where_filter) -> MetricInputMeasure: ) +@pytest.fixture(scope="session") +def conversion_type_params( + simple_metric_input_measure, metric_time_window +) -> ConversionTypeParams: + return ConversionTypeParams( + base_measure=simple_metric_input_measure, + conversion_measure=simple_metric_input_measure, + entity="entity", + window=metric_time_window, + constant_properties=[ + ConstantPropertyInput(base_property="base", conversion_property="conversion") + ], + ) + + @pytest.fixture(scope="session") def complex_metric_type_params( metric_time_window, simple_metric_input, simple_metric_input_measure @@ -245,6 +262,7 @@ def complex_metric_type_params( window=metric_time_window, grain_to_date=TimeGranularity.DAY, metrics=[simple_metric_input], + conversion_type_params=conversion_type_params, )