diff --git a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml index 9b834a34..f5f74eb7 100644 --- a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml +++ b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml @@ -174,7 +174,9 @@ metric: type_params: measure: name: bookers - window: 2 days + cumulative_type_params: + window: 2 days + period_agg: average --- metric: name: "revenue_mtd" @@ -183,7 +185,8 @@ metric: type_params: measure: name: txn_revenue - grain_to_date: month + cumulative_type_params: + grain_to_date: month --- metric: name: booking_fees diff --git a/tests/validations/test_configurable_rules.py b/tests/validations/test_configurable_rules.py index b4c351a1..bd8167e5 100644 --- a/tests/validations/test_configurable_rules.py +++ b/tests/validations/test_configurable_rules.py @@ -36,13 +36,15 @@ def test_can_configure_model_validator_rules( # noqa: D validator = SemanticManifestValidator[PydanticSemanticManifest]() issues = SemanticManifestValidator[PydanticSemanticManifest]().validate_semantic_manifest(model) assert ( - len(issues.all_issues) == 1 - ), f"SemanticManifestValidator with default rules had unexpected number of issues {issues}" + len(issues.errors) == 1 + ), f"SemanticManifestValidator with default rules had unexpected number of errors {issues.errors}" # confirm that a custom configuration excluding ValidMaterializationRule, no issue is raised rules = [rule for rule in validator.DEFAULT_RULES if rule.__class__ is not DerivedMetricRule] issues = SemanticManifestValidator[PydanticSemanticManifest](rules=rules).validate_semantic_manifest(model) - assert len(issues.all_issues) == 0, f"SemanticManifestValidator without DerivedMetricRule returned issues {issues}" + assert ( + len(issues.errors) == 0 + ), f"SemanticManifestValidator without DerivedMetricRule returned issues {issues.errors}" def test_cant_configure_model_validator_without_rules() -> None: # noqa: D diff --git a/tests/validations/test_metrics.py b/tests/validations/test_metrics.py index da6bbf70..62df676b 100644 --- a/tests/validations/test_metrics.py +++ b/tests/validations/test_metrics.py @@ -15,6 +15,7 @@ from dbt_semantic_interfaces.implementations.metric import ( PydanticConstantPropertyInput, PydanticConversionTypeParams, + PydanticCumulativeTypeParams, PydanticMetricInput, PydanticMetricInputMeasure, PydanticMetricTimeWindow, @@ -38,10 +39,12 @@ DimensionType, EntityType, MetricType, + PeriodAggregation, TimeGranularity, ) from dbt_semantic_interfaces.validations.metrics import ( ConversionMetricRule, + CumulativeMetricRule, DerivedMetricRule, WhereFiltersAreParseable, ) @@ -549,3 +552,135 @@ def test_conversion_metrics() -> None: # noqa: D missing_error_strings.add(expected_str) assert len(missing_error_strings) == 0, "Failed to match one or more expected errors: " f"{missing_error_strings} in {set([x.as_readable_str() for x in build_issues])}" + + +def test_cumulative_metrics() -> None: # noqa: D + measure_name = "foo" + model_validator = SemanticManifestValidator[PydanticSemanticManifest]([CumulativeMetricRule()]) + validation_results = model_validator.validate_semantic_manifest( + PydanticSemanticManifest( + semantic_models=[ + semantic_model_with_guaranteed_meta( + name="sum_measure", + measures=[ + PydanticMeasure( + name=measure_name, + agg=AggregationType.SUM, + agg_time_dimension="ds", + ) + ], + dimensions=[ + PydanticDimension( + name="ds", + type=DimensionType.TIME, + type_params=PydanticDimensionTypeParams( + time_granularity=TimeGranularity.DAY, + ), + ), + ], + ), + ], + metrics=[ + # Metrics with old type params structure - should 2 get warnings + metric_with_guaranteed_meta( + name="metric1", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + window=PydanticMetricTimeWindow(count=1, granularity=TimeGranularity.WEEK), + cumulative_type_params=PydanticCumulativeTypeParams(period_agg=PeriodAggregation.END), + ), + ), + metric_with_guaranteed_meta( + name="metric2", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + grain_to_date=TimeGranularity.MONTH, + ), + ), + # Metrics with new type params structure - should have no issues + metric_with_guaranteed_meta( + name="big_mama", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + cumulative_type_params=PydanticCumulativeTypeParams( + window=PydanticMetricTimeWindow(count=1, granularity=TimeGranularity.WEEK), + period_agg=PeriodAggregation.AVERAGE, + ), + ), + ), + metric_with_guaranteed_meta( + name="lil_baby", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + cumulative_type_params=PydanticCumulativeTypeParams(grain_to_date=TimeGranularity.MONTH), + ), + ), + # Metric with both window & grain across both type_params - should get 2 warnings + metric_with_guaranteed_meta( + name="woooooo", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + grain_to_date=TimeGranularity.MONTH, + cumulative_type_params=PydanticCumulativeTypeParams( + window=PydanticMetricTimeWindow(count=1, granularity=TimeGranularity.WEEK), + period_agg=PeriodAggregation.START, + ), + ), + ), + # Metrics with duplicated window or grain_to_date - should 4 get warnings + metric_with_guaranteed_meta( + name="what_a_metric", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + grain_to_date=TimeGranularity.YEAR, + cumulative_type_params=PydanticCumulativeTypeParams( + grain_to_date=TimeGranularity.HOUR, + ), + ), + ), + metric_with_guaranteed_meta( + name="dis_bad", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + window=PydanticMetricTimeWindow(count=2, granularity=TimeGranularity.QUARTER), + cumulative_type_params=PydanticCumulativeTypeParams( + window=PydanticMetricTimeWindow(count=1, granularity=TimeGranularity.QUARTER), + ), + ), + ), + # Metric without window or grain_to_date - should have no issues + metric_with_guaranteed_meta( + name="dis_good", + type=MetricType.CUMULATIVE, + type_params=PydanticMetricTypeParams( + measure=PydanticMetricInputMeasure(name=measure_name), + cumulative_type_params=PydanticCumulativeTypeParams(period_agg=PeriodAggregation.START), + ), + ), + ], + project_configuration=EXAMPLE_PROJECT_CONFIGURATION, + ) + ) + + build_issues = validation_results.all_issues + for issue in build_issues: + print(issue.message) + assert len(build_issues) == 8 + expected_substr1 = "Both window and grain_to_date set for cumulative metric. Please set one or the other." + expected_substr2 = "`window` set twice in cumulative metric" + expected_substr3 = "`grain_to_date` set twice in cumulative metric" + expected_substr4 = "Cumulative `type_params.window` field has been moved and will soon be deprecated." + expected_substr5 = "Cumulative `type_params.grain_to_date` field has been moved and will soon be deprecated." + missing_error_strings = set() + for expected_str in [expected_substr1, expected_substr2, expected_substr3, expected_substr4, expected_substr5]: + if not any(actual_str.as_readable_str().find(expected_str) != -1 for actual_str in build_issues): + missing_error_strings.add(expected_str) + assert len(missing_error_strings) == 0, "Failed to match one or more expected issues: " + f"{missing_error_strings} in {set([x.as_readable_str() for x in build_issues])}"