-
Notifications
You must be signed in to change notification settings - Fork 2.2k
CT-2707: Populate metric input measures #7984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a88cd29
ef6833c
85f5aa4
0104422
3c9f336
2d8445a
c528d91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| kind: Under the Hood | ||
| body: Populate metric input measures | ||
| time: 2023-06-28T14:20:06.022738-07:00 | ||
| custom: | ||
| Author: QMalcolm | ||
| Issue: "7884" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,6 +122,8 @@ | |
| from dbt.dataclass_schema import StrEnum, dbtClassMixin | ||
| from dbt.plugins import get_plugin_manager | ||
|
|
||
| from dbt_semantic_interfaces.enum_extension import assert_values_exhausted | ||
| from dbt_semantic_interfaces.type_enums import MetricType | ||
|
|
||
| PARSING_STATE = DbtProcessState("parsing") | ||
| PERF_INFO_FILE_NAME = "perf_info.json" | ||
|
|
@@ -1064,16 +1066,15 @@ def process_refs(self, current_project: str, dependencies: Optional[Dict[str, Pr | |
| # node, and updates 'depends_on.nodes' with the unique id | ||
| def process_metrics(self, config: RuntimeConfig): | ||
| current_project = config.project_name | ||
| for node in self.manifest.nodes.values(): | ||
| if node.created_at < self.started_at: | ||
| continue | ||
| _process_metrics_for_node(self.manifest, current_project, node) | ||
| for metric in self.manifest.metrics.values(): | ||
| # TODO: Can we do this if the metric is derived & depends on | ||
| # some other metric for its definition? Maybe.... | ||
| if metric.created_at < self.started_at: | ||
| continue | ||
| _process_metric_node(self.manifest, current_project, metric) | ||
| _process_metrics_for_node(self.manifest, current_project, metric) | ||
| for node in self.manifest.nodes.values(): | ||
| if node.created_at < self.started_at: | ||
| continue | ||
| _process_metrics_for_node(self.manifest, current_project, node) | ||
| for exposure in self.manifest.exposures.values(): | ||
| if exposure.created_at < self.started_at: | ||
| continue | ||
|
|
@@ -1439,6 +1440,63 @@ def _process_refs( | |
| node.depends_on.add_node(target_model_id) | ||
|
|
||
|
|
||
| def _process_metric_node( | ||
| manifest: Manifest, | ||
| current_project: str, | ||
| metric: Metric, | ||
| ) -> None: | ||
| """Sets a metric's input_measures""" | ||
|
|
||
| # This ensures that if this metrics input_measures have already been set | ||
| # we skip the work. This could happen either due to recursion or if multiple | ||
| # metrics derive from another given metric. | ||
| # NOTE: This does not protect against infinite loops | ||
| if len(metric.type_params.input_measures) > 0: | ||
| return | ||
|
|
||
| if metric.type is MetricType.SIMPLE or metric.type is MetricType.CUMULATIVE: | ||
| assert ( | ||
| 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) | ||
|
|
||
| elif metric.type is MetricType.DERIVED or metric.type is MetricType.RATIO: | ||
| input_metrics = metric.input_metrics | ||
| if metric.type is MetricType.RATIO: | ||
| if metric.type_params.numerator is None or metric.type_params.denominator is None: | ||
| raise dbt.exceptions.ParsingError( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't wholly sure if this should be considered a
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd call it a parsing error. The boundary isn't very clear because certain Jinja errors are always called compilation errors no matter whether they're in parsing or compilation. |
||
| "Invalid ratio metric. Both a numerator and denominator must be specified", | ||
| node=metric, | ||
| ) | ||
| input_metrics = [metric.type_params.numerator, metric.type_params.denominator] | ||
|
|
||
| for input_metric in input_metrics: | ||
| target_metric = manifest.resolve_metric( | ||
| target_metric_name=input_metric.name, | ||
| target_metric_package=None, | ||
| current_project=current_project, | ||
| node_package=metric.package_name, | ||
| ) | ||
|
|
||
| if target_metric is None: | ||
| raise dbt.exceptions.ParsingError( | ||
| f"The metric `{input_metric.name}` does not exist but was referenced.", | ||
| node=metric, | ||
| ) | ||
| elif isinstance(target_metric, Disabled): | ||
| raise dbt.exceptions.ParsingError( | ||
| f"The metric `{input_metric.name}` is disabled and thus cannot be referenced.", | ||
| node=metric, | ||
| ) | ||
|
|
||
| _process_metric_node( | ||
| manifest=manifest, current_project=current_project, metric=target_metric | ||
| ) | ||
| metric.type_params.input_measures.extend(target_metric.type_params.input_measures) | ||
| else: | ||
| assert_values_exhausted(metric.type) | ||
|
|
||
|
|
||
| def _process_metrics_for_node( | ||
| manifest: Manifest, | ||
| current_project: str, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically for an if statement like this I'd do more of a
However the type checker help of
assert_values_exhausteddoesn't work with that kind of formatting. Thus the individualisbased checked.