Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions stream_chat/async_chat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,27 @@ def channel_batch_updater(self) -> "ChannelBatchUpdater":

return ChannelBatchUpdater(self)

async def query_team_usage_stats(self, **options: Any) -> StreamResponse:
"""
Queries team-level usage statistics from the warehouse database.

Returns all 16 metrics grouped by team with cursor-based pagination.
This endpoint is server-side only.

Date Range Options (mutually exclusive):
- Use 'month' parameter (YYYY-MM format) for monthly aggregated values
- Use 'start_date'/'end_date' parameters (YYYY-MM-DD format) for daily breakdown
- If neither provided, defaults to current month (monthly mode)

:param month: Month in YYYY-MM format (e.g., '2026-01')
:param start_date: Start date in YYYY-MM-DD format
:param end_date: End date in YYYY-MM-DD format
:param limit: Maximum number of teams to return per page (default: 30, max: 30)
:param next: Cursor for pagination to fetch next page of teams
:return: StreamResponse with teams array and optional next cursor
"""
return await self.post("stats/team_usage", data=options)

async def close(self) -> None:
await self.session.close()

Expand Down
21 changes: 21 additions & 0 deletions stream_chat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,3 +1037,24 @@ def channel_batch_updater(self) -> "ChannelBatchUpdater":
from stream_chat.channel_batch_updater import ChannelBatchUpdater

return ChannelBatchUpdater(self)

def query_team_usage_stats(self, **options: Any) -> StreamResponse:
"""
Queries team-level usage statistics from the warehouse database.

Returns all 16 metrics grouped by team with cursor-based pagination.
This endpoint is server-side only.

Date Range Options (mutually exclusive):
- Use 'month' parameter (YYYY-MM format) for monthly aggregated values
- Use 'start_date'/'end_date' parameters (YYYY-MM-DD format) for daily breakdown
- If neither provided, defaults to current month (monthly mode)

:param month: Month in YYYY-MM format (e.g., '2026-01')
:param start_date: Start date in YYYY-MM-DD format
:param end_date: End date in YYYY-MM-DD format
:param limit: Maximum number of teams to return per page (default: 30, max: 30)
:param next: Cursor for pagination to fetch next page of teams
:return: StreamResponse with teams array and optional next cursor
"""
return self.post("stats/team_usage", data=options)
99 changes: 99 additions & 0 deletions stream_chat/tests/async_chat/test_team_usage_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from datetime import date, timedelta

import pytest

from stream_chat.async_chat import StreamChatAsync


class TestTeamUsageStats:
@pytest.mark.asyncio
async def test_query_team_usage_stats_default(self, client: StreamChatAsync):
"""Test querying team usage stats with default options."""
response = await client.query_team_usage_stats()
assert "teams" in response
assert isinstance(response["teams"], list)

@pytest.mark.asyncio
async def test_query_team_usage_stats_with_month(self, client: StreamChatAsync):
"""Test querying team usage stats with month parameter."""
current_month = date.today().strftime("%Y-%m")
response = await client.query_team_usage_stats(month=current_month)
assert "teams" in response
assert isinstance(response["teams"], list)

@pytest.mark.asyncio
async def test_query_team_usage_stats_with_date_range(
self, client: StreamChatAsync
):
"""Test querying team usage stats with date range."""
end_date = date.today()
start_date = end_date - timedelta(days=7)
response = await client.query_team_usage_stats(
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)
assert "teams" in response
assert isinstance(response["teams"], list)

@pytest.mark.asyncio
async def test_query_team_usage_stats_with_pagination(
self, client: StreamChatAsync
):
"""Test querying team usage stats with pagination."""
response = await client.query_team_usage_stats(limit=10)
assert "teams" in response
assert isinstance(response["teams"], list)

# If there's a next cursor, test fetching the next page
if response.get("next"):
next_response = await client.query_team_usage_stats(
limit=10, next=response["next"]
)
assert "teams" in next_response
assert isinstance(next_response["teams"], list)

@pytest.mark.asyncio
async def test_query_team_usage_stats_response_structure(
self, client: StreamChatAsync
):
"""Test that response contains expected metric fields when data exists."""
# Query last year to maximize chance of getting data
end_date = date.today()
start_date = end_date - timedelta(days=365)
response = await client.query_team_usage_stats(
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)

assert "teams" in response
teams = response["teams"]

if teams:
team = teams[0]
# Verify team identifier
assert "team" in team

# Verify daily activity metrics
assert "users_daily" in team
assert "messages_daily" in team
assert "translations_daily" in team
assert "image_moderations_daily" in team

# Verify peak metrics
assert "concurrent_users" in team
assert "concurrent_connections" in team

# Verify rolling/cumulative metrics
assert "users_total" in team
assert "users_last_24_hours" in team
assert "users_last_30_days" in team
assert "users_month_to_date" in team
assert "users_engaged_last_30_days" in team
assert "users_engaged_month_to_date" in team
assert "messages_total" in team
assert "messages_last_24_hours" in team
assert "messages_last_30_days" in team
assert "messages_month_to_date" in team

# Verify metric structure
assert "total" in team["users_daily"]
86 changes: 86 additions & 0 deletions stream_chat/tests/test_team_usage_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from datetime import date, timedelta

from stream_chat import StreamChat


class TestTeamUsageStats:
def test_query_team_usage_stats_default(self, client: StreamChat):
"""Test querying team usage stats with default options."""
response = client.query_team_usage_stats()
assert "teams" in response
assert isinstance(response["teams"], list)

def test_query_team_usage_stats_with_month(self, client: StreamChat):
"""Test querying team usage stats with month parameter."""
current_month = date.today().strftime("%Y-%m")
response = client.query_team_usage_stats(month=current_month)
assert "teams" in response
assert isinstance(response["teams"], list)

def test_query_team_usage_stats_with_date_range(self, client: StreamChat):
"""Test querying team usage stats with date range."""
end_date = date.today()
start_date = end_date - timedelta(days=7)
response = client.query_team_usage_stats(
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)
assert "teams" in response
assert isinstance(response["teams"], list)

def test_query_team_usage_stats_with_pagination(self, client: StreamChat):
"""Test querying team usage stats with pagination."""
response = client.query_team_usage_stats(limit=10)
assert "teams" in response
assert isinstance(response["teams"], list)

# If there's a next cursor, test fetching the next page
if response.get("next"):
next_response = client.query_team_usage_stats(
limit=10, next=response["next"]
)
assert "teams" in next_response
assert isinstance(next_response["teams"], list)

def test_query_team_usage_stats_response_structure(self, client: StreamChat):
"""Test that response contains expected metric fields when data exists."""
# Query last year to maximize chance of getting data
end_date = date.today()
start_date = end_date - timedelta(days=365)
response = client.query_team_usage_stats(
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)

assert "teams" in response
teams = response["teams"]

if teams:
team = teams[0]
# Verify team identifier
assert "team" in team

# Verify daily activity metrics
assert "users_daily" in team
assert "messages_daily" in team
assert "translations_daily" in team
assert "image_moderations_daily" in team

# Verify peak metrics
assert "concurrent_users" in team
assert "concurrent_connections" in team

# Verify rolling/cumulative metrics
assert "users_total" in team
assert "users_last_24_hours" in team
assert "users_last_30_days" in team
assert "users_month_to_date" in team
assert "users_engaged_last_30_days" in team
assert "users_engaged_month_to_date" in team
assert "messages_total" in team
assert "messages_last_24_hours" in team
assert "messages_last_30_days" in team
assert "messages_month_to_date" in team

# Verify metric structure
assert "total" in team["users_daily"]
Loading