applied-cli 0.5.70__tar.gz → 0.5.71__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {applied_cli-0.5.70 → applied_cli-0.5.71}/PKG-INFO +1 -1
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/__init__.py +1 -1
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/cli.py +2 -2
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/client.py +212 -20
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/tools.py +21 -13
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli.egg-info/PKG-INFO +1 -1
- {applied_cli-0.5.70 → applied_cli-0.5.71}/pyproject.toml +1 -1
- {applied_cli-0.5.70 → applied_cli-0.5.71}/tests/test_client.py +68 -40
- {applied_cli-0.5.70 → applied_cli-0.5.71}/README.md +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/agent_scoped_flows.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/conversation_lookup.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/conversations.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/credentials.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/flow_helpers.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli/formatters.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli.egg-info/SOURCES.txt +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli.egg-info/dependency_links.txt +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli.egg-info/entry_points.txt +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli.egg-info/requires.txt +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/applied_cli.egg-info/top_level.txt +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/setup.cfg +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/tests/test_agent_scoped_flows.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/tests/test_audit_tools.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/tests/test_benchmark_scenario_tools.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/tests/test_cli.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/tests/test_conversation_tools.py +0 -0
- {applied_cli-0.5.70 → applied_cli-0.5.71}/tests/test_flow_tools.py +0 -0
|
@@ -289,7 +289,7 @@ def analytics(
|
|
|
289
289
|
"--group-by",
|
|
290
290
|
"-g",
|
|
291
291
|
help=(
|
|
292
|
-
"Comma-separated dimensions: label_name, sublabel_name,
|
|
292
|
+
"Comma-separated dimensions: topic, intent, label_name, sublabel_name, "
|
|
293
293
|
"resolution, sentiment, type, user_id, label_id, sublabel_id, agent_id"
|
|
294
294
|
),
|
|
295
295
|
),
|
|
@@ -297,7 +297,7 @@ def analytics(
|
|
|
297
297
|
"count",
|
|
298
298
|
"--metrics",
|
|
299
299
|
"-m",
|
|
300
|
-
help="Comma-separated metrics: count
|
|
300
|
+
help="Comma-separated metrics: count",
|
|
301
301
|
),
|
|
302
302
|
filter: list[str] | None = typer.Option( # noqa: B008
|
|
303
303
|
None,
|
|
@@ -192,6 +192,23 @@ _ANALYTICS_DIMENSION_SQL = {
|
|
|
192
192
|
"type": "type",
|
|
193
193
|
"user_id": "user_id",
|
|
194
194
|
}
|
|
195
|
+
_ANALYTICS_AUTOMATION_DIMENSIONS = {
|
|
196
|
+
"intent": "sublabel_name",
|
|
197
|
+
"label_id": "label_id",
|
|
198
|
+
"label_name": "label_name",
|
|
199
|
+
"sublabel_id": "sublabel_id",
|
|
200
|
+
"sublabel_name": "sublabel_name",
|
|
201
|
+
"topic": "label_name",
|
|
202
|
+
}
|
|
203
|
+
_ANALYTICS_DASHBOARD_DIMENSIONS = {
|
|
204
|
+
"agent_id": "agent_id",
|
|
205
|
+
"label_id": "label_id",
|
|
206
|
+
"resolution": "resolution",
|
|
207
|
+
"sentiment": "sentiment",
|
|
208
|
+
"sublabel_id": "sublabel_id",
|
|
209
|
+
"type": "type",
|
|
210
|
+
"user_id": "user_id",
|
|
211
|
+
}
|
|
195
212
|
_ANALYTICS_FILTER_SQL = {
|
|
196
213
|
**_ANALYTICS_DIMENSION_SQL,
|
|
197
214
|
"score": "score",
|
|
@@ -297,6 +314,52 @@ def _build_legacy_analytics_sql(
|
|
|
297
314
|
return "\n".join(sql_parts)
|
|
298
315
|
|
|
299
316
|
|
|
317
|
+
def _parse_legacy_analytics_filters(filters: list[str] | None) -> dict[str, Any]:
|
|
318
|
+
parsed: dict[str, Any] = {}
|
|
319
|
+
for raw_filter in filters or []:
|
|
320
|
+
if "=" not in raw_filter:
|
|
321
|
+
raise ValueError(f"Invalid analytics filter: {raw_filter}")
|
|
322
|
+
field, raw_value = raw_filter.split("=", 1)
|
|
323
|
+
field = field.strip()
|
|
324
|
+
if field not in _ANALYTICS_FILTER_SQL:
|
|
325
|
+
raise ValueError(f"Unsupported analytics filter field: {field}")
|
|
326
|
+
parsed[field] = _decode_filter_value(raw_value)
|
|
327
|
+
return parsed
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _validate_analytics_count_metrics(metrics: list[str] | None) -> list[str]:
|
|
331
|
+
metric_names = metrics or ["count"]
|
|
332
|
+
unsupported = [metric for metric in metric_names if metric != "count"]
|
|
333
|
+
if unsupported:
|
|
334
|
+
raise ValueError(
|
|
335
|
+
"The current analytics endpoint only supports the count metric for "
|
|
336
|
+
"applied analytics. Use applied analytics-report for richer metrics."
|
|
337
|
+
)
|
|
338
|
+
return metric_names
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _decode_dashboard_group(group: Any, expected_count: int) -> list[Any]:
|
|
342
|
+
if expected_count == 1:
|
|
343
|
+
return [group]
|
|
344
|
+
if isinstance(group, str):
|
|
345
|
+
try:
|
|
346
|
+
decoded = json.loads(group)
|
|
347
|
+
except json.JSONDecodeError:
|
|
348
|
+
decoded = group
|
|
349
|
+
if isinstance(decoded, list):
|
|
350
|
+
return decoded
|
|
351
|
+
if isinstance(group, list):
|
|
352
|
+
return group
|
|
353
|
+
return [group]
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _sort_analytics_rows(rows: list[dict[str, Any]], group_by: list[str]) -> None:
|
|
357
|
+
def sort_key(row: dict[str, Any]) -> tuple[str, ...]:
|
|
358
|
+
return tuple("" if row.get(dimension) is None else str(row.get(dimension)) for dimension in group_by)
|
|
359
|
+
|
|
360
|
+
rows.sort(key=sort_key)
|
|
361
|
+
|
|
362
|
+
|
|
300
363
|
class AppliedClient:
|
|
301
364
|
"""Async client for Applied Labs API."""
|
|
302
365
|
|
|
@@ -612,20 +675,154 @@ class AppliedClient:
|
|
|
612
675
|
end: str | None = None,
|
|
613
676
|
limit: int | None = None,
|
|
614
677
|
) -> dict:
|
|
615
|
-
"""Run a
|
|
616
|
-
|
|
617
|
-
group_by
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
678
|
+
"""Run a grouped conversation count query through the analytics API."""
|
|
679
|
+
if not group_by:
|
|
680
|
+
raise ValueError("group_by is required")
|
|
681
|
+
|
|
682
|
+
metric_names = _validate_analytics_count_metrics(metrics)
|
|
683
|
+
parsed_filters = _parse_legacy_analytics_filters(filters)
|
|
684
|
+
|
|
685
|
+
if all(dimension in _ANALYTICS_AUTOMATION_DIMENSIONS for dimension in group_by):
|
|
686
|
+
data = await self._taxonomy_analytics_query(
|
|
687
|
+
group_by=group_by,
|
|
688
|
+
filters=parsed_filters,
|
|
689
|
+
start=start,
|
|
690
|
+
end=end,
|
|
691
|
+
limit=limit,
|
|
692
|
+
)
|
|
693
|
+
else:
|
|
694
|
+
data = await self._dashboard_analytics_query(
|
|
695
|
+
group_by=group_by,
|
|
696
|
+
filters=parsed_filters,
|
|
697
|
+
start=start,
|
|
698
|
+
end=end,
|
|
699
|
+
limit=limit,
|
|
700
|
+
)
|
|
701
|
+
|
|
625
702
|
data["dimensions"] = group_by
|
|
626
|
-
data["metrics"] =
|
|
703
|
+
data["metrics"] = metric_names
|
|
627
704
|
return data
|
|
628
705
|
|
|
706
|
+
async def _taxonomy_analytics_query(
|
|
707
|
+
self,
|
|
708
|
+
*,
|
|
709
|
+
group_by: list[str],
|
|
710
|
+
filters: dict[str, Any],
|
|
711
|
+
start: str | None,
|
|
712
|
+
end: str | None,
|
|
713
|
+
limit: int | None,
|
|
714
|
+
) -> dict:
|
|
715
|
+
payload: dict[str, Any] = {
|
|
716
|
+
"view": "automation_aggregates",
|
|
717
|
+
"model": "conversation",
|
|
718
|
+
**filters,
|
|
719
|
+
}
|
|
720
|
+
if start:
|
|
721
|
+
payload["start_date"] = start
|
|
722
|
+
if end:
|
|
723
|
+
payload["end_date"] = end
|
|
724
|
+
|
|
725
|
+
data = await self.analytics_report(payload)
|
|
726
|
+
source_key = (
|
|
727
|
+
"by_intent"
|
|
728
|
+
if any(
|
|
729
|
+
dimension in {"intent", "sublabel_id", "sublabel_name"}
|
|
730
|
+
for dimension in group_by
|
|
731
|
+
)
|
|
732
|
+
else "by_topic"
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
grouped: dict[tuple[Any, ...], int] = {}
|
|
736
|
+
for row in data.get(source_key, []):
|
|
737
|
+
key = tuple(
|
|
738
|
+
row.get(_ANALYTICS_AUTOMATION_DIMENSIONS[dimension])
|
|
739
|
+
for dimension in group_by
|
|
740
|
+
)
|
|
741
|
+
grouped[key] = grouped.get(key, 0) + int(row.get("total") or 0)
|
|
742
|
+
|
|
743
|
+
rows = [
|
|
744
|
+
{
|
|
745
|
+
**dict(zip(group_by, key, strict=False)),
|
|
746
|
+
"count": count,
|
|
747
|
+
}
|
|
748
|
+
for key, count in grouped.items()
|
|
749
|
+
]
|
|
750
|
+
_sort_analytics_rows(rows, group_by)
|
|
751
|
+
|
|
752
|
+
total_rows = len(rows)
|
|
753
|
+
if limit is not None:
|
|
754
|
+
rows = rows[:limit]
|
|
755
|
+
|
|
756
|
+
return {
|
|
757
|
+
"columns": [*group_by, "count"],
|
|
758
|
+
"rows": rows,
|
|
759
|
+
"row_count": len(rows),
|
|
760
|
+
"truncated": limit is not None and total_rows > len(rows),
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
async def _dashboard_analytics_query(
|
|
764
|
+
self,
|
|
765
|
+
*,
|
|
766
|
+
group_by: list[str],
|
|
767
|
+
filters: dict[str, Any],
|
|
768
|
+
start: str | None,
|
|
769
|
+
end: str | None,
|
|
770
|
+
limit: int | None,
|
|
771
|
+
) -> dict:
|
|
772
|
+
backend_group_by: list[str] = []
|
|
773
|
+
unsupported: list[str] = []
|
|
774
|
+
for dimension in group_by:
|
|
775
|
+
backend_dimension = _ANALYTICS_DASHBOARD_DIMENSIONS.get(dimension)
|
|
776
|
+
if backend_dimension is None:
|
|
777
|
+
unsupported.append(dimension)
|
|
778
|
+
else:
|
|
779
|
+
backend_group_by.append(backend_dimension)
|
|
780
|
+
|
|
781
|
+
if unsupported:
|
|
782
|
+
raise ValueError(
|
|
783
|
+
"Unsupported analytics dimension for the current endpoint: "
|
|
784
|
+
+ ", ".join(unsupported)
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
payload: dict[str, Any] = {
|
|
788
|
+
"view": "dashboard_rate_metrics",
|
|
789
|
+
"model": "conversation",
|
|
790
|
+
"group_by": ",".join(backend_group_by),
|
|
791
|
+
"custom_rate_metrics_requests": [
|
|
792
|
+
{"rate": "count", "numerator": "total"},
|
|
793
|
+
],
|
|
794
|
+
}
|
|
795
|
+
if start:
|
|
796
|
+
payload["start_date"] = start
|
|
797
|
+
if end:
|
|
798
|
+
payload["end_date"] = end
|
|
799
|
+
if filters:
|
|
800
|
+
payload["conversation_filters"] = filters
|
|
801
|
+
|
|
802
|
+
data = await self.analytics_report(payload)
|
|
803
|
+
rows: list[dict[str, Any]] = []
|
|
804
|
+
for item in data.get("count", []):
|
|
805
|
+
group_values = _decode_dashboard_group(item.get("group"), len(group_by))
|
|
806
|
+
group_values = [*group_values, *([None] * len(group_by))][: len(group_by)]
|
|
807
|
+
rows.append(
|
|
808
|
+
{
|
|
809
|
+
**dict(zip(group_by, group_values, strict=False)),
|
|
810
|
+
"count": item.get("numerator", item.get("rate", 0)),
|
|
811
|
+
}
|
|
812
|
+
)
|
|
813
|
+
_sort_analytics_rows(rows, group_by)
|
|
814
|
+
|
|
815
|
+
total_rows = len(rows)
|
|
816
|
+
if limit is not None:
|
|
817
|
+
rows = rows[:limit]
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
"columns": [*group_by, "count"],
|
|
821
|
+
"rows": rows,
|
|
822
|
+
"row_count": len(rows),
|
|
823
|
+
"truncated": limit is not None and total_rows > len(rows),
|
|
824
|
+
}
|
|
825
|
+
|
|
629
826
|
async def analytics_sql(
|
|
630
827
|
self,
|
|
631
828
|
*,
|
|
@@ -633,15 +830,10 @@ class AppliedClient:
|
|
|
633
830
|
parameters: dict[str, Any] | None = None,
|
|
634
831
|
max_rows: int | None = None,
|
|
635
832
|
) -> dict:
|
|
636
|
-
|
|
637
|
-
"
|
|
638
|
-
"
|
|
639
|
-
|
|
640
|
-
if parameters:
|
|
641
|
-
body["parameters"] = parameters
|
|
642
|
-
if max_rows is not None:
|
|
643
|
-
body["max_rows"] = max_rows
|
|
644
|
-
return await self._request("POST", "/v2/agents/analytics/query/", body=body)
|
|
833
|
+
raise ValueError(
|
|
834
|
+
"Raw analytics SQL is no longer available through the public API. "
|
|
835
|
+
"Use applied analytics or applied analytics-report, which call /v2/analytics/."
|
|
836
|
+
)
|
|
645
837
|
|
|
646
838
|
async def analytics_metrics(
|
|
647
839
|
self,
|
|
@@ -660,21 +660,24 @@ async def analytics_query(
|
|
|
660
660
|
limit: int | None = None,
|
|
661
661
|
output_format: str = "csv",
|
|
662
662
|
) -> str:
|
|
663
|
-
"""Run a server-side
|
|
663
|
+
"""Run a server-side GROUP BY count query on conversation data.
|
|
664
664
|
|
|
665
|
-
Dimensions: label_id, label_name, sublabel_id, sublabel_name,
|
|
666
|
-
agent_id,
|
|
667
|
-
Metrics: count
|
|
665
|
+
Dimensions: topic, intent, label_id, label_name, sublabel_id, sublabel_name,
|
|
666
|
+
agent_id, resolution, sentiment, type, user_id
|
|
667
|
+
Metrics: count
|
|
668
668
|
Filters: agent_id, label_id, sublabel_id, type, resolution, sentiment, score, user_id
|
|
669
669
|
"""
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
670
|
+
try:
|
|
671
|
+
data = await client.analytics_query(
|
|
672
|
+
group_by=group_by,
|
|
673
|
+
metrics=metrics,
|
|
674
|
+
filters=filters,
|
|
675
|
+
start=start,
|
|
676
|
+
end=end,
|
|
677
|
+
limit=limit,
|
|
678
|
+
)
|
|
679
|
+
except ValueError as exc:
|
|
680
|
+
return _format_argument_error(str(exc))
|
|
678
681
|
rows = data.get("rows", [])
|
|
679
682
|
if not rows:
|
|
680
683
|
return "No data found for the given query."
|
|
@@ -695,7 +698,12 @@ async def analytics_sql(
|
|
|
695
698
|
output_format: str = "csv",
|
|
696
699
|
) -> str:
|
|
697
700
|
"""Run a raw scoped analytics SQL query."""
|
|
698
|
-
|
|
701
|
+
try:
|
|
702
|
+
data = await client.analytics_sql(
|
|
703
|
+
sql=sql, parameters=parameters, max_rows=limit
|
|
704
|
+
)
|
|
705
|
+
except ValueError as exc:
|
|
706
|
+
return _format_argument_error(str(exc))
|
|
699
707
|
rows = data.get("rows", [])
|
|
700
708
|
if not rows:
|
|
701
709
|
return "No data found for the given query."
|
|
@@ -170,52 +170,42 @@ async def test_analytics_report_posts_payload(monkeypatch):
|
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
@pytest.mark.asyncio
|
|
173
|
-
async def
|
|
173
|
+
async def test_analytics_sql_fails_fast_without_public_endpoint(monkeypatch):
|
|
174
174
|
client = AppliedClient(token="test-token")
|
|
175
|
-
seen = {}
|
|
176
175
|
|
|
177
176
|
async def fake_request(method, path, params=None, body=None, shop_id=None):
|
|
178
|
-
|
|
179
|
-
seen["path"] = path
|
|
180
|
-
seen["body"] = body
|
|
181
|
-
return {"rows": []}
|
|
177
|
+
raise AssertionError("analytics_sql should not call removed endpoints")
|
|
182
178
|
|
|
183
179
|
monkeypatch.setattr(client, "_request", fake_request)
|
|
184
180
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
assert result == {"rows": []}
|
|
192
|
-
assert seen == {
|
|
193
|
-
"method": "POST",
|
|
194
|
-
"path": "/v2/agents/analytics/query/",
|
|
195
|
-
"body": {
|
|
196
|
-
"mode": "sql",
|
|
197
|
-
"sql": "SELECT count() AS total FROM conversation_latest_state",
|
|
198
|
-
"parameters": {"resolution": "escalated"},
|
|
199
|
-
"max_rows": 25,
|
|
200
|
-
},
|
|
201
|
-
}
|
|
181
|
+
with pytest.raises(ValueError, match="/v2/analytics/"):
|
|
182
|
+
await client.analytics_sql(
|
|
183
|
+
sql="SELECT count() AS total FROM conversation_latest_state",
|
|
184
|
+
parameters={"resolution": "escalated"},
|
|
185
|
+
max_rows=25,
|
|
186
|
+
)
|
|
202
187
|
|
|
203
188
|
|
|
204
189
|
@pytest.mark.asyncio
|
|
205
|
-
async def
|
|
190
|
+
async def test_analytics_query_uses_dashboard_rate_metrics(monkeypatch):
|
|
206
191
|
client = AppliedClient(token="test-token")
|
|
207
192
|
seen = {}
|
|
208
193
|
|
|
209
|
-
async def
|
|
210
|
-
seen["
|
|
211
|
-
seen["parameters"] = parameters
|
|
212
|
-
seen["max_rows"] = max_rows
|
|
194
|
+
async def fake_analytics_report(payload):
|
|
195
|
+
seen["payload"] = payload
|
|
213
196
|
return {
|
|
214
|
-
"
|
|
215
|
-
|
|
197
|
+
"count": [
|
|
198
|
+
{
|
|
199
|
+
"period": None,
|
|
200
|
+
"group": "escalated",
|
|
201
|
+
"denominator": 0,
|
|
202
|
+
"numerator": 4,
|
|
203
|
+
"rate": 4,
|
|
204
|
+
}
|
|
205
|
+
]
|
|
216
206
|
}
|
|
217
207
|
|
|
218
|
-
monkeypatch.setattr(client, "
|
|
208
|
+
monkeypatch.setattr(client, "analytics_report", fake_analytics_report)
|
|
219
209
|
|
|
220
210
|
result = await client.analytics_query(
|
|
221
211
|
group_by=["resolution"],
|
|
@@ -229,15 +219,53 @@ async def test_analytics_query_builds_legacy_sql(monkeypatch):
|
|
|
229
219
|
assert result["rows"] == [{"resolution": "escalated", "count": 4}]
|
|
230
220
|
assert result["dimensions"] == ["resolution"]
|
|
231
221
|
assert result["metrics"] == ["count"]
|
|
232
|
-
assert
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
222
|
+
assert seen["payload"] == {
|
|
223
|
+
"view": "dashboard_rate_metrics",
|
|
224
|
+
"model": "conversation",
|
|
225
|
+
"group_by": "resolution",
|
|
226
|
+
"custom_rate_metrics_requests": [{"rate": "count", "numerator": "total"}],
|
|
227
|
+
"start_date": "2026-03-01",
|
|
228
|
+
"end_date": "2026-03-31",
|
|
229
|
+
"conversation_filters": {"agent_id": "agent-1", "score": 2},
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@pytest.mark.asyncio
|
|
234
|
+
async def test_analytics_query_uses_automation_aggregates_for_intent_names(monkeypatch):
|
|
235
|
+
client = AppliedClient(token="test-token")
|
|
236
|
+
seen = {}
|
|
237
|
+
|
|
238
|
+
async def fake_analytics_report(payload):
|
|
239
|
+
seen["payload"] = payload
|
|
240
|
+
return {
|
|
241
|
+
"by_intent": [
|
|
242
|
+
{"sublabel_name": "Returns", "total": 2},
|
|
243
|
+
{"sublabel_name": "Returns", "total": 3},
|
|
244
|
+
{"sublabel_name": "Shipping", "total": 1},
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
monkeypatch.setattr(client, "analytics_report", fake_analytics_report)
|
|
249
|
+
|
|
250
|
+
result = await client.analytics_query(
|
|
251
|
+
group_by=["sublabel_name"],
|
|
252
|
+
metrics=["count"],
|
|
253
|
+
start="2026-04-27",
|
|
254
|
+
end="2026-05-01",
|
|
255
|
+
limit=10,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
assert result["columns"] == ["sublabel_name", "count"]
|
|
259
|
+
assert result["rows"] == [
|
|
260
|
+
{"sublabel_name": "Returns", "count": 5},
|
|
261
|
+
{"sublabel_name": "Shipping", "count": 1},
|
|
262
|
+
]
|
|
263
|
+
assert seen["payload"] == {
|
|
264
|
+
"view": "automation_aggregates",
|
|
265
|
+
"model": "conversation",
|
|
266
|
+
"start_date": "2026-04-27",
|
|
267
|
+
"end_date": "2026-05-01",
|
|
268
|
+
}
|
|
241
269
|
|
|
242
270
|
|
|
243
271
|
@pytest.mark.asyncio
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|