|
17 | 17 | from gooddata_api_client.models import ( |
18 | 18 | ComparisonMeasureValueFilterComparisonMeasureValueFilter as ComparisonMeasureValueFilterBody, |
19 | 19 | ) |
| 20 | +from gooddata_api_client.models import ( |
| 21 | + CompoundMeasureValueFilterCompoundMeasureValueFilter as CompoundMeasureValueFilterBody, |
| 22 | +) |
20 | 23 | from gooddata_api_client.models import NegativeAttributeFilterNegativeAttributeFilter as NegativeAttributeFilterBody |
21 | 24 | from gooddata_api_client.models import PositiveAttributeFilterPositiveAttributeFilter as PositiveAttributeFilterBody |
22 | 25 | from gooddata_api_client.models import RangeMeasureValueFilterRangeMeasureValueFilter as RangeMeasureValueFilterBody |
@@ -483,6 +486,105 @@ def description(self, labels: dict[str, str], format_locale: Optional[str] = Non |
483 | 486 | ) |
484 | 487 |
|
485 | 488 |
|
| 489 | +@attrs.define(frozen=True, slots=True) |
| 490 | +class MetricValueComparisonCondition: |
| 491 | + operator: str |
| 492 | + value: Union[int, float] |
| 493 | + |
| 494 | + def as_api_model(self) -> afm_models.MeasureValueCondition: |
| 495 | + comparison = afm_models.ComparisonConditionComparison( |
| 496 | + operator=self.operator, |
| 497 | + value=float(self.value), |
| 498 | + _check_type=False, |
| 499 | + ) |
| 500 | + return afm_models.MeasureValueCondition(comparison=comparison, _check_type=False) |
| 501 | + |
| 502 | + def description(self) -> str: |
| 503 | + return f"{_METRIC_VALUE_FILTER_OPERATOR_LABEL.get(self.operator, self.operator)} {float(self.value)}" |
| 504 | + |
| 505 | + |
| 506 | +@attrs.define(frozen=True, slots=True) |
| 507 | +class MetricValueRangeCondition: |
| 508 | + operator: str |
| 509 | + from_value: Union[int, float] |
| 510 | + to_value: Union[int, float] |
| 511 | + |
| 512 | + def as_api_model(self) -> afm_models.MeasureValueCondition: |
| 513 | + range_body = afm_models.RangeConditionRange( |
| 514 | + _from=float(self.from_value), |
| 515 | + operator=self.operator, |
| 516 | + to=float(self.to_value), |
| 517 | + _check_type=False, |
| 518 | + ) |
| 519 | + return afm_models.MeasureValueCondition(range=range_body, _check_type=False) |
| 520 | + |
| 521 | + def description(self) -> str: |
| 522 | + not_between = "not" if self.operator == "NOT_BETWEEN" else "" |
| 523 | + return f"{not_between}between {float(self.from_value)} - {float(self.to_value)}" |
| 524 | + |
| 525 | + |
| 526 | +MetricValueCondition = Union[MetricValueComparisonCondition, MetricValueRangeCondition] |
| 527 | + |
| 528 | + |
| 529 | +class CompoundMetricValueFilter(Filter): |
| 530 | + """ |
| 531 | + Compound measure value filter. |
| 532 | +
|
| 533 | + Semantics match backend `CompoundMeasureValueFilter`: multiple conditions combined with OR logic. |
| 534 | +
|
| 535 | + Note: |
| 536 | + - If `conditions` is empty, the filter is a noop (all rows are returned). |
| 537 | + - `treat_nulls_as` is applied at the filter level (same for all conditions). |
| 538 | + """ |
| 539 | + |
| 540 | + def __init__( |
| 541 | + self, |
| 542 | + metric: Union[ObjId, str, Metric], |
| 543 | + conditions: list[MetricValueCondition], |
| 544 | + treat_nulls_as: Union[float, None] = None, |
| 545 | + ) -> None: |
| 546 | + super().__init__() |
| 547 | + self._metric = _extract_id_or_local_id(metric) |
| 548 | + self._conditions = conditions |
| 549 | + self._treat_nulls_as = treat_nulls_as |
| 550 | + |
| 551 | + @property |
| 552 | + def metric(self) -> Union[ObjId, str]: |
| 553 | + return self._metric |
| 554 | + |
| 555 | + @property |
| 556 | + def conditions(self) -> list[MetricValueCondition]: |
| 557 | + return self._conditions |
| 558 | + |
| 559 | + @property |
| 560 | + def treat_nulls_as(self) -> Union[float, None]: |
| 561 | + return self._treat_nulls_as |
| 562 | + |
| 563 | + def is_noop(self) -> bool: |
| 564 | + return len(self.conditions) == 0 |
| 565 | + |
| 566 | + def as_api_model(self) -> afm_models.CompoundMeasureValueFilter: |
| 567 | + measure = _to_identifier(self._metric) |
| 568 | + |
| 569 | + kwargs: dict[str, Any] = dict( |
| 570 | + measure=measure, |
| 571 | + conditions=[c.as_api_model() for c in self.conditions], |
| 572 | + _check_type=False, |
| 573 | + ) |
| 574 | + if self.treat_nulls_as is not None: |
| 575 | + kwargs["treat_null_values_as"] = self.treat_nulls_as |
| 576 | + |
| 577 | + body = CompoundMeasureValueFilterBody(**kwargs) |
| 578 | + return afm_models.CompoundMeasureValueFilter(body, _check_type=False) |
| 579 | + |
| 580 | + def description(self, labels: dict[str, str], format_locale: Optional[str] = None) -> str: |
| 581 | + metric_id = self.metric.id if isinstance(self.metric, ObjId) else self.metric |
| 582 | + if not self.conditions: |
| 583 | + return f"{labels.get(metric_id, metric_id)}: All" |
| 584 | + conditions_str = " OR ".join([c.description() for c in self.conditions]) |
| 585 | + return f"{labels.get(metric_id, metric_id)}: {conditions_str}" |
| 586 | + |
| 587 | + |
486 | 588 | _RANKING_OPERATORS = {"TOP", "BOTTOM"} |
487 | 589 |
|
488 | 590 |
|
|
0 commit comments