Skip to content

Commit 4e56e38

Browse files
committed
feat(user-usage): add clean chart data option for user data resets
- Introduced `clean_chart_data` parameter in user data reset functions to conditionally delete historical chart rows. - Updated relevant CRUD operations and job functions to utilize the new parameter. - Enhanced environment configuration with `RESET_USER_USAGE_CLEAN_CHART_DATA` option for controlling chart data cleanup. - Modified user count chart accuracy notes in multiple languages to reflect the new behavior regarding chart data retention.
1 parent d471636 commit 4e56e38

13 files changed

Lines changed: 126 additions & 24 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ UVICORN_PORT = 8000
100100

101101
# DISABLE_RECORDING_NODE_USAGE = False
102102
# ENABLE_RECORDING_NODES_STATS = False # Only available for postgresql and timescaledb
103+
# RESET_USER_USAGE_CLEAN_CHART_DATA = False # When true, reset user usage also deletes historical user chart rows.
103104

104105
# ALLOWED_ORIGINS = "*"
105106

app/db/crud/bulk.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@
2323
from .user import load_user_attrs
2424

2525

26-
async def reset_all_users_data_usage(db: AsyncSession, admin: Optional[Admin] = None):
26+
async def reset_all_users_data_usage(
27+
db: AsyncSession,
28+
admin: Optional[Admin] = None,
29+
*,
30+
clean_chart_data: bool = False,
31+
):
2732
"""
2833
Efficiently resets data usage for all users, or users under a specific admin if provided.
2934
@@ -33,7 +38,8 @@ async def reset_all_users_data_usage(db: AsyncSession, admin: Optional[Admin] =
3338
Operations performed:
3439
- Sets `used_traffic` to 0 for all target users.
3540
- Sets `status` to `active` for all users, unless filtered by admin.
36-
- Deletes all related `UserUsageResetLogs`, `NodeUserUsage`, and `NextPlan` entries.
41+
- Deletes all related `UserUsageResetLogs` and `NextPlan` entries.
42+
- Deletes `NodeUserUsage` chart rows when `clean_chart_data` is enabled.
3743
3844
Args:
3945
db (AsyncSession): The SQLAlchemy async session used for database operations.
@@ -54,7 +60,8 @@ async def reset_all_users_data_usage(db: AsyncSession, admin: Optional[Admin] =
5460
await db.execute(update(User).where(User.id.in_(user_ids)).values(used_traffic=0, status=UserStatus.active))
5561

5662
await db.execute(delete(UserUsageResetLogs).where(UserUsageResetLogs.user_id.in_(user_ids)))
57-
await db.execute(delete(NodeUserUsage).where(NodeUserUsage.user_id.in_(user_ids)))
63+
if clean_chart_data:
64+
await db.execute(delete(NodeUserUsage).where(NodeUserUsage.user_id.in_(user_ids)))
5865
await db.execute(delete(NextPlan).where(NextPlan.user_id.in_(user_ids)))
5966

6067
await db.commit()

app/db/crud/user.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ async def clear_user_node_usages(db: AsyncSession, user_id: int, *, before: date
10321032
await db.execute(stmt)
10331033

10341034

1035-
async def reset_user_data_usage(db: AsyncSession, db_user: User) -> User:
1035+
async def reset_user_data_usage(db: AsyncSession, db_user: User, *, clean_chart_data: bool = False) -> User:
10361036
"""
10371037
Resets the data usage of a user and logs the reset.
10381038
@@ -1044,7 +1044,8 @@ async def reset_user_data_usage(db: AsyncSession, db_user: User) -> User:
10441044
User: The updated user object.
10451045
"""
10461046
await _reset_user_traffic_and_log(db, db_user)
1047-
await clear_user_node_usages(db, db_user.id)
1047+
if clean_chart_data:
1048+
await clear_user_node_usages(db, db_user.id)
10481049

10491050
if db_user.status not in [UserStatus.expired, UserStatus.disabled]:
10501051
db_user.status = UserStatus.active
@@ -1054,7 +1055,9 @@ async def reset_user_data_usage(db: AsyncSession, db_user: User) -> User:
10541055
return db_user
10551056

10561057

1057-
async def bulk_reset_user_data_usage(db: AsyncSession, users: list[User]) -> list[User]:
1058+
async def bulk_reset_user_data_usage(
1059+
db: AsyncSession, users: list[User], *, clean_chart_data: bool = False
1060+
) -> list[User]:
10581061
"""
10591062
Resets the data usage for a list of users and logs the reset.
10601063
@@ -1067,7 +1070,8 @@ async def bulk_reset_user_data_usage(db: AsyncSession, users: list[User]) -> lis
10671070
"""
10681071
for db_user in users:
10691072
await _reset_user_traffic_and_log(db, db_user)
1070-
await clear_user_node_usages(db, db_user.id)
1073+
if clean_chart_data:
1074+
await clear_user_node_usages(db, db_user.id)
10711075
if db_user.status not in [UserStatus.expired, UserStatus.disabled]:
10721076
db_user.status = UserStatus.active
10731077
await db.commit()
@@ -1086,7 +1090,7 @@ def _build_revoked_proxy_settings(db_user: User) -> dict:
10861090
return proxy_settings.dict()
10871091

10881092

1089-
async def reset_user_by_next(db: AsyncSession, db_user: User) -> User:
1093+
async def reset_user_by_next(db: AsyncSession, db_user: User, *, clean_chart_data: bool = False) -> User:
10901094
"""
10911095
Resets the data usage of a user based on next user.
10921096
@@ -1140,7 +1144,8 @@ async def reset_user_by_next(db: AsyncSession, db_user: User) -> User:
11401144
db_user.data_limit_reset_strategy = db_user.next_plan.user_template.data_limit_reset_strategy
11411145

11421146
await _reset_user_traffic_and_log(db, db_user)
1143-
await clear_user_node_usages(db, db_user.id)
1147+
if clean_chart_data:
1148+
await clear_user_node_usages(db, db_user.id)
11441149
db_user.status = UserStatus.active
11451150

11461151
await db.commit()

app/jobs/reset_user_data_usage.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from app import notification
1010
from app.jobs.dependencies import SYSTEM_ADMIN
1111
from app.utils.logger import get_logger
12-
from config import job_settings, runtime_settings
12+
from config import job_settings, runtime_settings, usage_settings
1313

1414
logger = get_logger("jobs")
1515
user_operator = UserOperation(operator_type=OperatorType.SYSTEM)
@@ -20,7 +20,11 @@ async def reset_data_usage():
2020
users = await get_users_to_reset_data_usage(db)
2121
old_statuses = {user.id: user.status for user in users}
2222

23-
updated_users = await bulk_reset_user_data_usage(db, users)
23+
updated_users = await bulk_reset_user_data_usage(
24+
db,
25+
users,
26+
clean_chart_data=usage_settings.reset_user_usage_clean_chart_data,
27+
)
2428

2529
for db_user in updated_users:
2630
user = await user_operator.update_user(db_user)

app/jobs/review_users.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from app.node import node_manager as node_manager
2626
from app.settings import webhook_settings
2727
from app.utils.logger import get_logger
28-
from config import job_settings, runtime_settings
28+
from config import job_settings, runtime_settings, usage_settings
2929

3030
logger = get_logger("review-users")
3131
user_operator = UserOperation(operator_type=OperatorType.SYSTEM)
@@ -34,7 +34,11 @@
3434
async def change_status(db: AsyncSession, db_user: User, status: UserStatus):
3535
next_plan_activated = bool(db_user.next_plan) and status != UserStatus.active
3636
if next_plan_activated:
37-
db_user = await reset_user_by_next(db, db_user)
37+
db_user = await reset_user_by_next(
38+
db,
39+
db_user,
40+
clean_chart_data=usage_settings.reset_user_usage_clean_chart_data,
41+
)
3842

3943
user = await user_operator.update_user(db_user)
4044

app/operation/user.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pydantic import ValidationError
1111
from sqlalchemy.exc import IntegrityError
1212

13+
from config import usage_settings
1314
from app import notification
1415
from app.db import AsyncSession
1516
from app.db.crud.admin import get_admin
@@ -461,11 +462,15 @@ async def _reset_user_data_usage(
461462
db_user: User,
462463
admin: AdminDetails,
463464
*,
465+
clean_chart_data: bool | None = None,
464466
emit_status_change_notification: bool = True,
465467
):
466468
old_status = db_user.status
467469

468-
db_user = await reset_user_data_usage(db=db, db_user=db_user)
470+
if clean_chart_data is None:
471+
clean_chart_data = usage_settings.reset_user_usage_clean_chart_data
472+
473+
db_user = await reset_user_data_usage(db=db, db_user=db_user, clean_chart_data=clean_chart_data)
469474
user = await self.update_user(db_user)
470475

471476
if emit_status_change_notification and user.status != old_status:
@@ -498,7 +503,11 @@ async def bulk_reset_user_data_usage(
498503
db_users = await self._get_validated_users_by_ids(db, bulk_users.ids, admin, load_usage_logs=False)
499504
old_statuses = {user.id: user.status for user in db_users}
500505

501-
db_users = await bulk_reset_user_data_usage(db, db_users)
506+
db_users = await bulk_reset_user_data_usage(
507+
db,
508+
db_users,
509+
clean_chart_data=usage_settings.reset_user_usage_clean_chart_data,
510+
)
502511
await sync_users(db_users)
503512

504513
users = [await self.validate_user(db_user) for db_user in db_users]
@@ -577,14 +586,22 @@ async def bulk_enable_users(
577586
async def reset_users_data_usage(self, db: AsyncSession, admin: AdminDetails):
578587
"""Reset all users data usage"""
579588
db_admin = await self.get_validated_admin(db, admin.username)
580-
await reset_all_users_data_usage(db=db, admin=db_admin)
589+
await reset_all_users_data_usage(
590+
db=db,
591+
admin=db_admin,
592+
clean_chart_data=usage_settings.reset_user_usage_clean_chart_data,
593+
)
581594

582595
async def _active_next_plan(self, db: AsyncSession, db_user: User, admin: AdminDetails) -> UserResponse:
583596
if db_user is None or db_user.next_plan is None:
584597
await self.raise_error(message="User doesn't have next plan", code=404)
585598

586599
old_status = db_user.status
587-
db_user = await reset_user_by_next(db=db, db_user=db_user)
600+
db_user = await reset_user_by_next(
601+
db=db,
602+
db_user=db_user,
603+
clean_chart_data=usage_settings.reset_user_usage_clean_chart_data,
604+
)
588605
user = await self.update_user(db_user)
589606

590607
if user.status != old_status:

config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ def build_sudoers(self) -> "AuthSettings":
164164
class UsageSettings(EnvSettings):
165165
disable_recording_node_usage: bool = Field(default=False, validation_alias="DISABLE_RECORDING_NODE_USAGE")
166166
enable_recording_nodes_stats: bool = Field(default=False, validation_alias="ENABLE_RECORDING_NODES_STATS")
167+
reset_user_usage_clean_chart_data: bool = Field(
168+
default=False,
169+
validation_alias="RESET_USER_USAGE_CLEAN_CHART_DATA",
170+
)
167171

168172

169173
class JobSettings(EnvSettings):

dashboard/public/statics/locales/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1936,7 +1936,7 @@
19361936
"statistics.onlineUsers": "Online Users",
19371937
"statistics.userCountChart": "User Count",
19381938
"statistics.userCountChartDescription": "Online, expired, and limited user activity counts over time",
1939-
"statistics.userCountChartAccuracyNote": "Resetting user usage or changing user status can make this chart show inaccurate historical data.",
1939+
"statistics.userCountChartAccuracyNote": "Changing user status can make this chart show inaccurate historical data. Resetting usage only removes chart history when chart-data cleanup is enabled in the environment.",
19401940
"workersHealth.title": "Workers Health",
19411941
"workersHealth.subtitle": "Scheduler and node worker status",
19421942
"workersHealth.live": "Live",

dashboard/public/statics/locales/fa.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1889,7 +1889,7 @@
18891889
"statistics.onlineUsers": "کاربران آنلاین",
18901890
"statistics.userCountChart": "تعداد کاربران",
18911891
"statistics.userCountChartDescription": "تعداد کاربران آنلاین، منقضی‌شده و محدودشده در طول زمان",
1892-
"statistics.userCountChartAccuracyNote": "بازنشانی مصرف کاربر یا تغییر وضعیت کاربر می‌تواند باعث شود این نمودار داده‌های تاریخی نادقیق نشان دهد.",
1892+
"statistics.userCountChartAccuracyNote": "تغییر وضعیت کاربر می‌تواند باعث شود این نمودار داده‌های تاریخی نادقیق نشان دهد. بازنشانی مصرف فقط زمانی تاریخچه نمودار را حذف می‌کند که پاک‌سازی داده‌های نمودار در محیط فعال باشد.",
18931893
"workersHealth.title": "سلامت پراسس ها",
18941894
"workersHealth.subtitle": "وضعیت زمان‌بند و پراسس گره",
18951895
"workersHealth.live": "زنده",

dashboard/public/statics/locales/ru.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1851,7 +1851,7 @@
18511851
"statistics.onlineUsers": "Онлайн пользователи",
18521852
"statistics.userCountChart": "Количество пользователей",
18531853
"statistics.userCountChartDescription": "Количество онлайн, истекших и ограниченных пользователей во времени",
1854-
"statistics.userCountChartAccuracyNote": "Сброс использования пользователя или изменение статуса пользователя может сделать исторические данные на этом графике неточными.",
1854+
"statistics.userCountChartAccuracyNote": "Изменение статуса пользователя может сделать исторические данные на этом графике неточными. Сброс использования удаляет историю графиков только если очистка данных графиков включена в окружении.",
18551855
"workersHealth.title": "Состояние воркеров",
18561856
"workersHealth.subtitle": "Статус планировщика и воркера узла",
18571857
"workersHealth.live": "В реальном времени",

0 commit comments

Comments
 (0)