Compare commits

...

8 Commits

Author SHA1 Message Date
Will Deng
b8ace8ae21 added changelog 2023-12-06 18:15:08 -05:00
Will Deng
028ebf84c1 added tests 2023-12-06 13:55:03 -05:00
Will Deng
83977dbf31 updated v12 schemas 2023-12-06 13:55:03 -05:00
Will Deng
d428daa6a1 comment back manifest generating line 2023-12-06 13:54:23 -05:00
Will Deng
0f322ed8cb version bump on DSI 2023-12-06 13:54:23 -05:00
Will Deng
67f46f4d2b added step to populate input_measure for conversion metrics 2023-12-06 13:54:23 -05:00
Will Deng
fb81b74835 updated parser for ConversionTypeParams 2023-12-06 13:54:23 -05:00
Will Deng
c7559cdb4c added ConversionTypeParams classes 2023-12-06 13:03:16 -05:00
14 changed files with 702 additions and 28 deletions

View File

@@ -0,0 +1,7 @@
kind: Features
body: Adds support for parsing conversion metric related properties for the semantic
layer.
time: 2023-12-06T18:14:58.688221-05:00
custom:
Author: WilliamDee
Issue: "9203"

View File

@@ -5,7 +5,7 @@ from typing import Any, Dict, Iterator, List
DERIVED_METRICS = [MetricType.DERIVED, MetricType.RATIO]
BASE_METRICS = [MetricType.SIMPLE, MetricType.CUMULATIVE]
BASE_METRICS = [MetricType.SIMPLE, MetricType.CUMULATIVE, MetricType.CONVERSION]
class MetricReference(object):

View File

@@ -21,6 +21,7 @@ from dbt.contracts.graph.semantic_models import (
SourceFileMetadata,
)
from dbt.contracts.graph.unparsed import (
ConstantPropertyInput,
Docs,
ExposureType,
ExternalTable,
@@ -59,7 +60,11 @@ from dbt_semantic_interfaces.references import (
TimeDimensionReference,
)
from dbt_semantic_interfaces.references import MetricReference as DSIMetricReference
from dbt_semantic_interfaces.type_enums import MetricType, TimeGranularity
from dbt_semantic_interfaces.type_enums import (
ConversionCalculationType,
MetricType,
TimeGranularity,
)
from .model_config import (
NodeConfig,
@@ -1435,6 +1440,16 @@ class MetricInput(dbtClassMixin):
return DSIMetricReference(element_name=self.alias or self.name)
@dataclass
class ConversionTypeParams(dbtClassMixin):
base_measure: MetricInputMeasure
conversion_measure: MetricInputMeasure
entity: str
calculation: ConversionCalculationType = ConversionCalculationType.CONVERSION_RATE
window: Optional[MetricTimeWindow] = None
constant_properties: Optional[List[ConstantPropertyInput]] = None
@dataclass
class MetricTypeParams(dbtClassMixin):
measure: Optional[MetricInputMeasure] = None
@@ -1445,6 +1460,7 @@ class MetricTypeParams(dbtClassMixin):
window: Optional[MetricTimeWindow] = None
grain_to_date: Optional[TimeGranularity] = None
metrics: Optional[List[MetricInput]] = None
conversion_type_params: Optional[ConversionTypeParams] = None
@dataclass

View File

@@ -19,6 +19,7 @@ import dbt.helper_types # noqa:F401
from dbt.exceptions import CompilationError, ParsingError, DbtInternalError
from dbt.dataclass_schema import dbtClassMixin, StrEnum, ExtensibleDbtClassMixin, ValidationError
from dbt_semantic_interfaces.type_enums import ConversionCalculationType
from dataclasses import dataclass, field
from datetime import timedelta
@@ -597,6 +598,24 @@ class UnparsedMetricInput(dbtClassMixin):
offset_to_grain: Optional[str] = None # str is really a TimeGranularity Enum
@dataclass
class ConstantPropertyInput(dbtClassMixin):
base_property: str
conversion_property: str
@dataclass
class UnparsedConversionTypeParams(dbtClassMixin):
base_measure: Union[UnparsedMetricInputMeasure, str]
conversion_measure: Union[UnparsedMetricInputMeasure, str]
entity: str
calculation: str = (
ConversionCalculationType.CONVERSION_RATE.value
) # ConversionCalculationType Enum
window: Optional[str] = None
constant_properties: Optional[List[ConstantPropertyInput]] = None
@dataclass
class UnparsedMetricTypeParams(dbtClassMixin):
measure: Optional[Union[UnparsedMetricInputMeasure, str]] = None
@@ -606,6 +625,7 @@ class UnparsedMetricTypeParams(dbtClassMixin):
window: Optional[str] = None
grain_to_date: Optional[str] = None # str is really a TimeGranularity Enum
metrics: Optional[List[Union[UnparsedMetricInput, str]]] = None
conversion_type_params: Optional[UnparsedConversionTypeParams] = None
@dataclass

View File

@@ -1527,6 +1527,34 @@ def _process_refs(
node.depends_on.add_node(target_model_id)
def _process_metric_depends_on(
manifest: Manifest,
current_project: str,
metric: Metric,
) -> None:
"""For a given metric, set the `depends_on` property"""
assert len(metric.type_params.input_measures) > 0
for input_measure in metric.type_params.input_measures:
target_semantic_model = manifest.resolve_semantic_model_for_measure(
target_measure_name=input_measure.name,
current_project=current_project,
node_package=metric.package_name,
)
if target_semantic_model is None:
raise dbt.exceptions.ParsingError(
f"A semantic model having a measure `{input_measure.name}` does not exist but was referenced.",
node=metric,
)
if target_semantic_model.config.enabled is False:
raise dbt.exceptions.ParsingError(
f"The measure `{input_measure.name}` is referenced on disabled semantic model `{target_semantic_model.name}`.",
node=metric,
)
metric.depends_on.add_node(target_semantic_model.unique_id)
def _process_metric_node(
manifest: Manifest,
current_project: str,
@@ -1546,24 +1574,19 @@ def _process_metric_node(
metric.type_params.measure is not None
), f"{metric} should have a measure defined, but it does not."
metric.type_params.input_measures.append(metric.type_params.measure)
target_semantic_model = manifest.resolve_semantic_model_for_measure(
target_measure_name=metric.type_params.measure.name,
current_project=current_project,
node_package=metric.package_name,
_process_metric_depends_on(
manifest=manifest, current_project=current_project, metric=metric
)
elif metric.type is MetricType.CONVERSION:
conversion_type_params = metric.type_params.conversion_type_params
assert (
conversion_type_params
), f"{metric.name} is a conversion metric and must have conversion_type_params defined."
metric.type_params.input_measures.append(conversion_type_params.base_measure)
metric.type_params.input_measures.append(conversion_type_params.conversion_measure)
_process_metric_depends_on(
manifest=manifest, current_project=current_project, metric=metric
)
if target_semantic_model is None:
raise dbt.exceptions.ParsingError(
f"A semantic model having a measure `{metric.type_params.measure.name}` does not exist but was referenced.",
node=metric,
)
if target_semantic_model.config.enabled is False:
raise dbt.exceptions.ParsingError(
f"The measure `{metric.type_params.measure.name}` is referenced on disabled semantic model `{target_semantic_model.name}`.",
node=metric,
)
metric.depends_on.add_node(target_semantic_model.unique_id)
elif metric.type is MetricType.DERIVED or metric.type is MetricType.RATIO:
input_metrics = metric.input_metrics
if metric.type is MetricType.RATIO:

View File

@@ -17,6 +17,7 @@ from dbt.contracts.graph.unparsed import (
UnparsedQueryParams,
UnparsedSavedQuery,
UnparsedSemanticModel,
UnparsedConversionTypeParams,
)
from dbt.contracts.graph.model_config import SavedQueryConfig
from dbt.contracts.graph.nodes import (
@@ -29,6 +30,7 @@ from dbt.contracts.graph.nodes import (
MetricTypeParams,
SemanticModel,
SavedQuery,
ConversionTypeParams,
)
from dbt.contracts.graph.saved_queries import Export, ExportConfig, QueryParams
from dbt.contracts.graph.semantic_layer_common import WhereFilter, WhereFilterIntersection
@@ -52,6 +54,7 @@ from dbt.clients.jinja import get_rendered
from dbt.dataclass_schema import ValidationError
from dbt_semantic_interfaces.type_enums import (
AggregationType,
ConversionCalculationType,
DimensionType,
EntityType,
MetricType,
@@ -226,7 +229,7 @@ class MetricParser(YamlReader):
self.yaml.path,
"window",
{"window": unparsed_window},
f"Invalid window ({unparsed_window}) in cumulative metric. Should be of the form `<count> <granularity>`, "
f"Invalid window ({unparsed_window}) in cumulative/conversion metric. Should be of the form `<count> <granularity>`, "
"e.g., `28 days`",
)
@@ -240,7 +243,7 @@ class MetricParser(YamlReader):
self.yaml.path,
"window",
{"window": unparsed_window},
f"Invalid time granularity {granularity} in cumulative metric window string: ({unparsed_window})",
f"Invalid time granularity {granularity} in cumulative/conversion metric window string: ({unparsed_window})",
)
count = parts[0]
@@ -249,7 +252,7 @@ class MetricParser(YamlReader):
self.yaml.path,
"window",
{"window": unparsed_window},
f"Invalid count ({count}) in cumulative metric window string: ({unparsed_window})",
f"Invalid count ({count}) in cumulative/conversion metric window string: ({unparsed_window})",
)
return MetricTimeWindow(
@@ -295,6 +298,20 @@ class MetricParser(YamlReader):
return metric_inputs
def _get_optional_conversion_type_params(
self, unparsed: Optional[UnparsedConversionTypeParams]
) -> Optional[ConversionTypeParams]:
if unparsed is None:
return None
return ConversionTypeParams(
base_measure=self._get_input_measure(unparsed.base_measure),
conversion_measure=self._get_input_measure(unparsed.conversion_measure),
entity=unparsed.entity,
calculation=ConversionCalculationType(unparsed.calculation),
window=self._get_time_window(unparsed.window),
constant_properties=unparsed.constant_properties,
)
def _get_metric_type_params(self, type_params: UnparsedMetricTypeParams) -> MetricTypeParams:
grain_to_date: Optional[TimeGranularity] = None
if type_params.grain_to_date is not None:
@@ -308,6 +325,9 @@ class MetricParser(YamlReader):
window=self._get_time_window(type_params.window),
grain_to_date=grain_to_date,
metrics=self._get_metric_inputs(type_params.metrics),
conversion_type_params=self._get_optional_conversion_type_params(
type_params.conversion_type_params
)
# input measures are calculated via metric processing post parsing
# input_measures=?,
)

View File

@@ -73,7 +73,7 @@ setup(
# These are major-version-0 packages also maintained by dbt-labs. Accept patches.
"dbt-extractor~=0.5.0",
"minimal-snowplow-tracker~=0.0.2",
"dbt-semantic-interfaces~=0.4.0",
"dbt-semantic-interfaces~=0.5.0a2",
# ----
# Expect compatibility with all new versions of these packages, so lower bounds only.
"jsonschema>=3.0",

View File

@@ -7843,7 +7843,8 @@
"simple",
"ratio",
"cumulative",
"derived"
"derived",
"conversion"
]
},
"type_params": {
@@ -8410,6 +8411,241 @@
}
],
"default": null
},
"conversion_type_params": {
"anyOf": [
{
"type": "object",
"title": "ConversionTypeParams",
"properties": {
"base_measure": {
"type": "object",
"title": "MetricInputMeasure",
"properties": {
"name": {
"type": "string"
},
"filter": {
"anyOf": [
{
"type": "object",
"title": "WhereFilterIntersection",
"properties": {
"where_filters": {
"type": "array",
"items": {
"type": "object",
"title": "WhereFilter",
"properties": {
"where_sql_template": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"where_sql_template"
]
}
}
},
"additionalProperties": false,
"required": [
"where_filters"
]
},
{
"type": "null"
}
],
"default": null
},
"alias": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"join_to_timespine": {
"type": "boolean",
"default": false
},
"fill_nulls_with": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"conversion_measure": {
"type": "object",
"title": "MetricInputMeasure",
"properties": {
"name": {
"type": "string"
},
"filter": {
"anyOf": [
{
"type": "object",
"title": "WhereFilterIntersection",
"properties": {
"where_filters": {
"type": "array",
"items": {
"type": "object",
"title": "WhereFilter",
"properties": {
"where_sql_template": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"where_sql_template"
]
}
}
},
"additionalProperties": false,
"required": [
"where_filters"
]
},
{
"type": "null"
}
],
"default": null
},
"alias": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"join_to_timespine": {
"type": "boolean",
"default": false
},
"fill_nulls_with": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"entity": {
"type": "string"
},
"calculation": {
"enum": [
"conversions",
"conversion_rate"
],
"default": "conversion_rate"
},
"window": {
"anyOf": [
{
"type": "object",
"title": "MetricTimeWindow",
"properties": {
"count": {
"type": "integer"
},
"granularity": {
"enum": [
"day",
"week",
"month",
"quarter",
"year"
]
}
},
"additionalProperties": false,
"required": [
"count",
"granularity"
]
},
{
"type": "null"
}
],
"default": null
},
"constant_properties": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"title": "ConstantPropertyInput",
"properties": {
"base_property": {
"type": "string"
},
"conversion_property": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"base_property",
"conversion_property"
]
}
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false,
"required": [
"base_measure",
"conversion_measure",
"entity"
]
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false
@@ -16264,7 +16500,8 @@
"simple",
"ratio",
"cumulative",
"derived"
"derived",
"conversion"
]
},
"type_params": {
@@ -16831,6 +17068,241 @@
}
],
"default": null
},
"conversion_type_params": {
"anyOf": [
{
"type": "object",
"title": "ConversionTypeParams",
"properties": {
"base_measure": {
"type": "object",
"title": "MetricInputMeasure",
"properties": {
"name": {
"type": "string"
},
"filter": {
"anyOf": [
{
"type": "object",
"title": "WhereFilterIntersection",
"properties": {
"where_filters": {
"type": "array",
"items": {
"type": "object",
"title": "WhereFilter",
"properties": {
"where_sql_template": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"where_sql_template"
]
}
}
},
"additionalProperties": false,
"required": [
"where_filters"
]
},
{
"type": "null"
}
],
"default": null
},
"alias": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"join_to_timespine": {
"type": "boolean",
"default": false
},
"fill_nulls_with": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"conversion_measure": {
"type": "object",
"title": "MetricInputMeasure",
"properties": {
"name": {
"type": "string"
},
"filter": {
"anyOf": [
{
"type": "object",
"title": "WhereFilterIntersection",
"properties": {
"where_filters": {
"type": "array",
"items": {
"type": "object",
"title": "WhereFilter",
"properties": {
"where_sql_template": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"where_sql_template"
]
}
}
},
"additionalProperties": false,
"required": [
"where_filters"
]
},
{
"type": "null"
}
],
"default": null
},
"alias": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"join_to_timespine": {
"type": "boolean",
"default": false
},
"fill_nulls_with": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false,
"required": [
"name"
]
},
"entity": {
"type": "string"
},
"calculation": {
"enum": [
"conversions",
"conversion_rate"
],
"default": "conversion_rate"
},
"window": {
"anyOf": [
{
"type": "object",
"title": "MetricTimeWindow",
"properties": {
"count": {
"type": "integer"
},
"granularity": {
"enum": [
"day",
"week",
"month",
"quarter",
"year"
]
}
},
"additionalProperties": false,
"required": [
"count",
"granularity"
]
},
{
"type": "null"
}
],
"default": null
},
"constant_properties": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"title": "ConstantPropertyInput",
"properties": {
"base_property": {
"type": "string"
},
"conversion_property": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"base_property",
"conversion_property"
]
}
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false,
"required": [
"base_measure",
"conversion_measure",
"entity"
]
},
{
"type": "null"
}
],
"default": null
}
},
"additionalProperties": false

View File

@@ -1 +1 @@
{"metadata": {"dbt_schema_version": "https://schemas.getdbt.com/dbt/run-results/v6.json", "dbt_version": "1.8.0a1", "generated_at": "2023-12-01T00:27:31.475276Z", "invocation_id": "6dbe790d-e31a-45d3-bf5c-b67232e7e45c", "env": {}}, "results": [{"status": "success", "timing": [{"name": "compile", "started_at": "2023-12-01T00:27:31.399416Z", "completed_at": "2023-12-01T00:27:31.406369Z"}, {"name": "execute", "started_at": "2023-12-01T00:27:31.407149Z", "completed_at": "2023-12-01T00:27:31.458669Z"}], "thread_id": "Thread-8", "execution_time": 0.0613861083984375, "adapter_response": {"_message": "CREATE VIEW", "code": "CREATE VIEW", "rows_affected": -1}, "message": "CREATE VIEW", "failures": null, "unique_id": "model.test.metricflow_time_spine", "compiled": true, "compiled_code": "SELECT to_date('02/20/2023', 'mm/dd/yyyy') as date_day", "relation_name": "\"dbt\".\"test17013904484151129162_test_previous_version_state\".\"metricflow_time_spine\""}, {"status": "success", "timing": [{"name": "compile", "started_at": "2023-12-01T00:27:31.401527Z", "completed_at": "2023-12-01T00:27:31.402989Z"}, {"name": "execute", "started_at": "2023-12-01T00:27:31.403533Z", "completed_at": "2023-12-01T00:27:31.460610Z"}], "thread_id": "Thread-9", "execution_time": 0.0629112720489502, "adapter_response": {"_message": "CREATE VIEW", "code": "CREATE VIEW", "rows_affected": -1}, "message": "CREATE VIEW", "failures": null, "unique_id": "model.test.my_model", "compiled": true, "compiled_code": "select 1 as id", "relation_name": "\"dbt\".\"test17013904484151129162_test_previous_version_state\".\"my_model\""}], "elapsed_time": 0.5424928665161133, "args": {"log_format_file": "debug", "profiles_dir": "/private/var/folders/79/5290gpvn3lx5jdryk4844rm80000gn/T/pytest-of-quigleymalcolm/pytest-449/profile0", "defer": false, "favor_state": false, "populate_cache": true, "partial_parse": true, "select": [], "show_resource_report": false, "version_check": true, "strict_mode": false, "introspect": true, "project_dir": "/private/var/folders/79/5290gpvn3lx5jdryk4844rm80000gn/T/pytest-of-quigleymalcolm/pytest-449/project0", "indirect_selection": "eager", "vars": {}, "log_level_file": "debug", "log_level": "info", "printer_width": 80, "cache_selected_only": false, "which": "run", "log_path": "/Users/quigleymalcolm/Developer/dbt-labs/dbt-core/logs/test17013904484151129162", "write_json": true, "log_file_max_bytes": 10485760, "macro_debugging": true, "static_parser": true, "enable_legacy_logger": false, "warn_error_options": {"include": [], "exclude": []}, "invocation_command": "dbt tests/functional/artifacts/test_previous_version_state.py", "exclude": [], "use_colors": true, "use_colors_file": true, "quiet": false, "print": true, "send_anonymous_usage_stats": false, "partial_parse_file_diff": true, "log_format": "default"}}
{"metadata": {"dbt_schema_version": "https://schemas.getdbt.com/dbt/run-results/v6.json", "dbt_version": "1.8.0a1", "generated_at": "2023-12-06T18:53:19.641690Z", "invocation_id": "ad4ef714-e6c6-425e-b7c8-c1c4369df4ea", "env": {}}, "results": [{"status": "success", "timing": [{"name": "compile", "started_at": "2023-12-06T18:53:19.554953Z", "completed_at": "2023-12-06T18:53:19.559711Z"}, {"name": "execute", "started_at": "2023-12-06T18:53:19.564874Z", "completed_at": "2023-12-06T18:53:19.620151Z"}], "thread_id": "Thread-8", "execution_time": 0.06995701789855957, "adapter_response": {"_message": "CREATE VIEW", "code": "CREATE VIEW", "rows_affected": -1}, "message": "CREATE VIEW", "failures": null, "unique_id": "model.test.metricflow_time_spine", "compiled": true, "compiled_code": "SELECT to_date('02/20/2023', 'mm/dd/yyyy') as date_day", "relation_name": "\"dbt\".\"test17018887966812726006_test_previous_version_state\".\"metricflow_time_spine\""}, {"status": "success", "timing": [{"name": "compile", "started_at": "2023-12-06T18:53:19.557019Z", "completed_at": "2023-12-06T18:53:19.559247Z"}, {"name": "execute", "started_at": "2023-12-06T18:53:19.561000Z", "completed_at": "2023-12-06T18:53:19.622080Z"}], "thread_id": "Thread-9", "execution_time": 0.07100677490234375, "adapter_response": {"_message": "CREATE VIEW", "code": "CREATE VIEW", "rows_affected": -1}, "message": "CREATE VIEW", "failures": null, "unique_id": "model.test.my_model", "compiled": true, "compiled_code": "select 1 as id", "relation_name": "\"dbt\".\"test17018887966812726006_test_previous_version_state\".\"my_model\""}], "elapsed_time": 0.13903093338012695, "args": {"print": true, "log_level_file": "debug", "quiet": false, "warn_error_options": {"include": [], "exclude": []}, "write_json": true, "invocation_command": "dbt --cov=core --cov-append --cov-report=xml tests/functional/artifacts/test_previous_version_state.py", "log_level": "info", "select": [], "project_dir": "/private/var/folders/67/r0f0jlj54h95zl3fhmb217jh0000gp/T/pytest-of-william/pytest-68/project0", "static_parser": true, "log_file_max_bytes": 10485760, "empty": false, "introspect": true, "log_format_file": "debug", "vars": {}, "strict_mode": false, "indirect_selection": "eager", "show_resource_report": false, "favor_state": false, "version_check": true, "cache_selected_only": false, "enable_legacy_logger": false, "partial_parse": true, "profiles_dir": "/private/var/folders/67/r0f0jlj54h95zl3fhmb217jh0000gp/T/pytest-of-william/pytest-68/profile0", "defer": false, "printer_width": 80, "send_anonymous_usage_stats": false, "use_colors": true, "log_path": "/Users/william/git/dbt-core/logs/test17018887966812726006", "partial_parse_file_diff": true, "populate_cache": true, "macro_debugging": false, "use_colors_file": true, "log_format": "default", "which": "run", "exclude": []}}

File diff suppressed because one or more lines are too long

View File

@@ -443,7 +443,7 @@ class TestPreviousVersionState:
current_run_results_schema_version == self.CURRENT_EXPECTED_RUN_RESULTS_VERSION
), "Sounds like you've bumped the run_results version and need to update this test!"
# If we need a newly generated run_results, uncomment the following line and commit the result
self.generate_latest_run_results(project, current_run_results_schema_version)
# self.generate_latest_run_results(project, current_run_results_schema_version)
self.compare_previous_results(project, current_run_results_schema_version, True, 0)
def test_backwards_compatible_run_results_versions(self, project):

View File

@@ -626,3 +626,41 @@ metrics:
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
"""

View File

@@ -7,6 +7,8 @@ from dbt.tests.util import run_dbt, get_manifest
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,61 @@ class TestInvalidTimestampWindowMetrics:
# initial run
with pytest.raises(ParsingError):
run_dbt(["run"])
class TestConversionMetric:
@pytest.fixture(scope="class")
def models(self):
return {
"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,
}
@pytest.fixture(scope="class")
def seeds(self):
return {
"mock_purchase_data.csv": mock_purchase_data_csv,
}
def test_conversion_metric(
self,
project,
):
# initial parse
runner = dbtRunner()
result = runner.invoke(["parse"])
assert result.success
assert isinstance(result.result, Manifest)
# make sure the metric is 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
assert manifest.metrics[
"metric.test.converted_orders_over_visits"
].type_params.conversion_type_params
assert (
len(
manifest.metrics[
"metric.test.converted_orders_over_visits"
].type_params.input_measures
)
== 2
)
assert (
manifest.metrics[
"metric.test.converted_orders_over_visits"
].type_params.conversion_type_params.window
is None
)
assert (
manifest.metrics[
"metric.test.converted_orders_over_visits"
].type_params.conversion_type_params.entity
== "purchase"
)

View File

@@ -2,6 +2,8 @@ import pytest
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,
)