Skip to content

Commit edd67a0

Browse files
daksh-rDakshclaude
authored
feat: add query_team_usage_stats API for multi-tenant metrics (#221)
* 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> * fix: remove unused pytest import to satisfy flake8 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Daksh <daksh.rinwan@getstream.io> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 994c05f commit edd67a0

File tree

4 files changed

+227
-0
lines changed

4 files changed

+227
-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: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from datetime import date, timedelta
2+
3+
from stream_chat import StreamChat
4+
5+
6+
class TestTeamUsageStats:
7+
def test_query_team_usage_stats_default(self, client: StreamChat):
8+
"""Test querying team usage stats with default options."""
9+
response = client.query_team_usage_stats()
10+
assert "teams" in response
11+
assert isinstance(response["teams"], list)
12+
13+
def test_query_team_usage_stats_with_month(self, client: StreamChat):
14+
"""Test querying team usage stats with month parameter."""
15+
current_month = date.today().strftime("%Y-%m")
16+
response = client.query_team_usage_stats(month=current_month)
17+
assert "teams" in response
18+
assert isinstance(response["teams"], list)
19+
20+
def test_query_team_usage_stats_with_date_range(self, client: StreamChat):
21+
"""Test querying team usage stats with date range."""
22+
end_date = date.today()
23+
start_date = end_date - timedelta(days=7)
24+
response = client.query_team_usage_stats(
25+
start_date=start_date.strftime("%Y-%m-%d"),
26+
end_date=end_date.strftime("%Y-%m-%d"),
27+
)
28+
assert "teams" in response
29+
assert isinstance(response["teams"], list)
30+
31+
def test_query_team_usage_stats_with_pagination(self, client: StreamChat):
32+
"""Test querying team usage stats with pagination."""
33+
response = client.query_team_usage_stats(limit=10)
34+
assert "teams" in response
35+
assert isinstance(response["teams"], list)
36+
37+
# If there's a next cursor, test fetching the next page
38+
if response.get("next"):
39+
next_response = client.query_team_usage_stats(
40+
limit=10, next=response["next"]
41+
)
42+
assert "teams" in next_response
43+
assert isinstance(next_response["teams"], list)
44+
45+
def test_query_team_usage_stats_response_structure(self, client: StreamChat):
46+
"""Test that response contains expected metric fields when data exists."""
47+
# Query last year to maximize chance of getting data
48+
end_date = date.today()
49+
start_date = end_date - timedelta(days=365)
50+
response = client.query_team_usage_stats(
51+
start_date=start_date.strftime("%Y-%m-%d"),
52+
end_date=end_date.strftime("%Y-%m-%d"),
53+
)
54+
55+
assert "teams" in response
56+
teams = response["teams"]
57+
58+
if teams:
59+
team = teams[0]
60+
# Verify team identifier
61+
assert "team" in team
62+
63+
# Verify daily activity metrics
64+
assert "users_daily" in team
65+
assert "messages_daily" in team
66+
assert "translations_daily" in team
67+
assert "image_moderations_daily" in team
68+
69+
# Verify peak metrics
70+
assert "concurrent_users" in team
71+
assert "concurrent_connections" in team
72+
73+
# Verify rolling/cumulative metrics
74+
assert "users_total" in team
75+
assert "users_last_24_hours" in team
76+
assert "users_last_30_days" in team
77+
assert "users_month_to_date" in team
78+
assert "users_engaged_last_30_days" in team
79+
assert "users_engaged_month_to_date" in team
80+
assert "messages_total" in team
81+
assert "messages_last_24_hours" in team
82+
assert "messages_last_30_days" in team
83+
assert "messages_month_to_date" in team
84+
85+
# Verify metric structure
86+
assert "total" in team["users_daily"]

0 commit comments

Comments
 (0)