Skip to content

Commit 02464d7

Browse files
Dakshclaude
andcommitted
feat: add query_team_usage_stats API for multi-tenant metrics
Add query_team_usage_stats() method to both sync and async clients for querying team-level usage statistics from the warehouse database. - Returns all 16 metrics grouped by team with cursor-based pagination - Supports monthly aggregation (month param) or daily breakdown (start_date/end_date) - Includes comprehensive test coverage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 994c05f commit 02464d7

File tree

4 files changed

+229
-0
lines changed

4 files changed

+229
-0
lines changed

stream_chat/async_chat/client.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,27 @@ def channel_batch_updater(self) -> "ChannelBatchUpdater":
10741074

10751075
return ChannelBatchUpdater(self)
10761076

1077+
async def query_team_usage_stats(self, **options: Any) -> StreamResponse:
1078+
"""
1079+
Queries team-level usage statistics from the warehouse database.
1080+
1081+
Returns all 16 metrics grouped by team with cursor-based pagination.
1082+
This endpoint is server-side only.
1083+
1084+
Date Range Options (mutually exclusive):
1085+
- Use 'month' parameter (YYYY-MM format) for monthly aggregated values
1086+
- Use 'start_date'/'end_date' parameters (YYYY-MM-DD format) for daily breakdown
1087+
- If neither provided, defaults to current month (monthly mode)
1088+
1089+
:param month: Month in YYYY-MM format (e.g., '2026-01')
1090+
:param start_date: Start date in YYYY-MM-DD format
1091+
:param end_date: End date in YYYY-MM-DD format
1092+
:param limit: Maximum number of teams to return per page (default: 30, max: 30)
1093+
:param next: Cursor for pagination to fetch next page of teams
1094+
:return: StreamResponse with teams array and optional next cursor
1095+
"""
1096+
return await self.post("stats/team_usage", data=options)
1097+
10771098
async def close(self) -> None:
10781099
await self.session.close()
10791100

stream_chat/client.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,3 +1037,24 @@ def channel_batch_updater(self) -> "ChannelBatchUpdater":
10371037
from stream_chat.channel_batch_updater import ChannelBatchUpdater
10381038

10391039
return ChannelBatchUpdater(self)
1040+
1041+
def query_team_usage_stats(self, **options: Any) -> StreamResponse:
1042+
"""
1043+
Queries team-level usage statistics from the warehouse database.
1044+
1045+
Returns all 16 metrics grouped by team with cursor-based pagination.
1046+
This endpoint is server-side only.
1047+
1048+
Date Range Options (mutually exclusive):
1049+
- Use 'month' parameter (YYYY-MM format) for monthly aggregated values
1050+
- Use 'start_date'/'end_date' parameters (YYYY-MM-DD format) for daily breakdown
1051+
- If neither provided, defaults to current month (monthly mode)
1052+
1053+
:param month: Month in YYYY-MM format (e.g., '2026-01')
1054+
:param start_date: Start date in YYYY-MM-DD format
1055+
:param end_date: End date in YYYY-MM-DD format
1056+
:param limit: Maximum number of teams to return per page (default: 30, max: 30)
1057+
:param next: Cursor for pagination to fetch next page of teams
1058+
:return: StreamResponse with teams array and optional next cursor
1059+
"""
1060+
return self.post("stats/team_usage", data=options)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from datetime import date, timedelta
2+
3+
import pytest
4+
5+
from stream_chat.async_chat import StreamChatAsync
6+
7+
8+
class TestTeamUsageStats:
9+
@pytest.mark.asyncio
10+
async def test_query_team_usage_stats_default(self, client: StreamChatAsync):
11+
"""Test querying team usage stats with default options."""
12+
response = await client.query_team_usage_stats()
13+
assert "teams" in response
14+
assert isinstance(response["teams"], list)
15+
16+
@pytest.mark.asyncio
17+
async def test_query_team_usage_stats_with_month(self, client: StreamChatAsync):
18+
"""Test querying team usage stats with month parameter."""
19+
current_month = date.today().strftime("%Y-%m")
20+
response = await client.query_team_usage_stats(month=current_month)
21+
assert "teams" in response
22+
assert isinstance(response["teams"], list)
23+
24+
@pytest.mark.asyncio
25+
async def test_query_team_usage_stats_with_date_range(
26+
self, client: StreamChatAsync
27+
):
28+
"""Test querying team usage stats with date range."""
29+
end_date = date.today()
30+
start_date = end_date - timedelta(days=7)
31+
response = await client.query_team_usage_stats(
32+
start_date=start_date.strftime("%Y-%m-%d"),
33+
end_date=end_date.strftime("%Y-%m-%d"),
34+
)
35+
assert "teams" in response
36+
assert isinstance(response["teams"], list)
37+
38+
@pytest.mark.asyncio
39+
async def test_query_team_usage_stats_with_pagination(
40+
self, client: StreamChatAsync
41+
):
42+
"""Test querying team usage stats with pagination."""
43+
response = await client.query_team_usage_stats(limit=10)
44+
assert "teams" in response
45+
assert isinstance(response["teams"], list)
46+
47+
# If there's a next cursor, test fetching the next page
48+
if response.get("next"):
49+
next_response = await client.query_team_usage_stats(
50+
limit=10, next=response["next"]
51+
)
52+
assert "teams" in next_response
53+
assert isinstance(next_response["teams"], list)
54+
55+
@pytest.mark.asyncio
56+
async def test_query_team_usage_stats_response_structure(
57+
self, client: StreamChatAsync
58+
):
59+
"""Test that response contains expected metric fields when data exists."""
60+
# Query last year to maximize chance of getting data
61+
end_date = date.today()
62+
start_date = end_date - timedelta(days=365)
63+
response = await client.query_team_usage_stats(
64+
start_date=start_date.strftime("%Y-%m-%d"),
65+
end_date=end_date.strftime("%Y-%m-%d"),
66+
)
67+
68+
assert "teams" in response
69+
teams = response["teams"]
70+
71+
if teams:
72+
team = teams[0]
73+
# Verify team identifier
74+
assert "team" in team
75+
76+
# Verify daily activity metrics
77+
assert "users_daily" in team
78+
assert "messages_daily" in team
79+
assert "translations_daily" in team
80+
assert "image_moderations_daily" in team
81+
82+
# Verify peak metrics
83+
assert "concurrent_users" in team
84+
assert "concurrent_connections" in team
85+
86+
# Verify rolling/cumulative metrics
87+
assert "users_total" in team
88+
assert "users_last_24_hours" in team
89+
assert "users_last_30_days" in team
90+
assert "users_month_to_date" in team
91+
assert "users_engaged_last_30_days" in team
92+
assert "users_engaged_month_to_date" in team
93+
assert "messages_total" in team
94+
assert "messages_last_24_hours" in team
95+
assert "messages_last_30_days" in team
96+
assert "messages_month_to_date" in team
97+
98+
# Verify metric structure
99+
assert "total" in team["users_daily"]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from datetime import date, timedelta
2+
3+
import pytest
4+
5+
from stream_chat import StreamChat
6+
7+
8+
class TestTeamUsageStats:
9+
def test_query_team_usage_stats_default(self, client: StreamChat):
10+
"""Test querying team usage stats with default options."""
11+
response = client.query_team_usage_stats()
12+
assert "teams" in response
13+
assert isinstance(response["teams"], list)
14+
15+
def test_query_team_usage_stats_with_month(self, client: StreamChat):
16+
"""Test querying team usage stats with month parameter."""
17+
current_month = date.today().strftime("%Y-%m")
18+
response = client.query_team_usage_stats(month=current_month)
19+
assert "teams" in response
20+
assert isinstance(response["teams"], list)
21+
22+
def test_query_team_usage_stats_with_date_range(self, client: StreamChat):
23+
"""Test querying team usage stats with date range."""
24+
end_date = date.today()
25+
start_date = end_date - timedelta(days=7)
26+
response = client.query_team_usage_stats(
27+
start_date=start_date.strftime("%Y-%m-%d"),
28+
end_date=end_date.strftime("%Y-%m-%d"),
29+
)
30+
assert "teams" in response
31+
assert isinstance(response["teams"], list)
32+
33+
def test_query_team_usage_stats_with_pagination(self, client: StreamChat):
34+
"""Test querying team usage stats with pagination."""
35+
response = client.query_team_usage_stats(limit=10)
36+
assert "teams" in response
37+
assert isinstance(response["teams"], list)
38+
39+
# If there's a next cursor, test fetching the next page
40+
if response.get("next"):
41+
next_response = client.query_team_usage_stats(
42+
limit=10, next=response["next"]
43+
)
44+
assert "teams" in next_response
45+
assert isinstance(next_response["teams"], list)
46+
47+
def test_query_team_usage_stats_response_structure(self, client: StreamChat):
48+
"""Test that response contains expected metric fields when data exists."""
49+
# Query last year to maximize chance of getting data
50+
end_date = date.today()
51+
start_date = end_date - timedelta(days=365)
52+
response = client.query_team_usage_stats(
53+
start_date=start_date.strftime("%Y-%m-%d"),
54+
end_date=end_date.strftime("%Y-%m-%d"),
55+
)
56+
57+
assert "teams" in response
58+
teams = response["teams"]
59+
60+
if teams:
61+
team = teams[0]
62+
# Verify team identifier
63+
assert "team" in team
64+
65+
# Verify daily activity metrics
66+
assert "users_daily" in team
67+
assert "messages_daily" in team
68+
assert "translations_daily" in team
69+
assert "image_moderations_daily" in team
70+
71+
# Verify peak metrics
72+
assert "concurrent_users" in team
73+
assert "concurrent_connections" in team
74+
75+
# Verify rolling/cumulative metrics
76+
assert "users_total" in team
77+
assert "users_last_24_hours" in team
78+
assert "users_last_30_days" in team
79+
assert "users_month_to_date" in team
80+
assert "users_engaged_last_30_days" in team
81+
assert "users_engaged_month_to_date" in team
82+
assert "messages_total" in team
83+
assert "messages_last_24_hours" in team
84+
assert "messages_last_30_days" in team
85+
assert "messages_month_to_date" in team
86+
87+
# Verify metric structure
88+
assert "total" in team["users_daily"]

0 commit comments

Comments
 (0)