diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index edc9e2d..74e6e4e 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -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() diff --git a/stream_chat/client.py b/stream_chat/client.py index b9fafd8..9d67a20 100644 --- a/stream_chat/client.py +++ b/stream_chat/client.py @@ -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) diff --git a/stream_chat/tests/async_chat/test_team_usage_stats.py b/stream_chat/tests/async_chat/test_team_usage_stats.py new file mode 100644 index 0000000..c90aefc --- /dev/null +++ b/stream_chat/tests/async_chat/test_team_usage_stats.py @@ -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"] diff --git a/stream_chat/tests/test_team_usage_stats.py b/stream_chat/tests/test_team_usage_stats.py new file mode 100644 index 0000000..d8315ef --- /dev/null +++ b/stream_chat/tests/test_team_usage_stats.py @@ -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"]