From dd24eede871acf5dd313038f06d4a001347022bf Mon Sep 17 00:00:00 2001 From: poduck Date: Mon, 22 Dec 2025 01:50:58 -0500 Subject: [PATCH] Add automation runs quota tracking to quota management page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add max_automation_runs to QUOTA_CONFIG in QuotaService - Add runs_this_month and runs_month_started fields to TenantDefaultFlow - Add increment_run_count() method for tracking flow executions - Add Bot icon for automation quotas in frontend QuotaSettings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/src/pages/settings/QuotaSettings.tsx | 6 +++- .../identity/core/quota_service.py | 22 ++++++++++++++ .../0003_add_run_tracking_fields.py | 23 +++++++++++++++ .../integrations/activepieces/models.py | 29 +++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 smoothschedule/smoothschedule/integrations/activepieces/migrations/0003_add_run_tracking_fields.py diff --git a/frontend/src/pages/settings/QuotaSettings.tsx b/frontend/src/pages/settings/QuotaSettings.tsx index 744ddef1..4198ca6f 100644 --- a/frontend/src/pages/settings/QuotaSettings.tsx +++ b/frontend/src/pages/settings/QuotaSettings.tsx @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'; import { useOutletContext, Link } from 'react-router-dom'; import { AlertTriangle, Archive, Check, ChevronDown, ChevronUp, - Clock, Download, Users, Briefcase, Calendar, RefreshCw + Clock, Download, Users, Briefcase, Calendar, RefreshCw, Bot } from 'lucide-react'; import { Business, User, QuotaOverage } from '../../types'; import { @@ -136,6 +136,10 @@ const QuotaSettings: React.FC = () => { return ; case 'MAX_SERVICES': return ; + case 'MAX_AUTOMATION_RUNS': + return ; + case 'MAX_AUTOMATED_TASKS': + return ; default: return ; } diff --git a/smoothschedule/smoothschedule/identity/core/quota_service.py b/smoothschedule/smoothschedule/identity/core/quota_service.py index 53e5da5e..193ff666 100644 --- a/smoothschedule/smoothschedule/identity/core/quota_service.py +++ b/smoothschedule/smoothschedule/identity/core/quota_service.py @@ -59,6 +59,12 @@ class QuotaService: 'display_name': 'automated tasks', 'count_method': 'count_automated_tasks', }, + 'MAX_AUTOMATION_RUNS': { + 'model': None, # No archivable model - informational only + 'display_name': 'automation runs this month', + 'count_method': 'count_automation_runs', + 'is_archivable': False, # Can't archive runs, just throttle + }, } def __init__(self, tenant: Tenant): @@ -94,6 +100,21 @@ class QuotaService: from smoothschedule.scheduling.schedule.models import ScheduledTask return ScheduledTask.objects.count() + def count_automation_runs(self) -> int: + """ + Count automation flow executions this month. + Uses TenantDefaultFlow records to track usage. + """ + from smoothschedule.integrations.activepieces.models import TenantDefaultFlow + from django.db.models import Sum + + # Get total runs from all default flows + result = TenantDefaultFlow.objects.filter( + tenant=self.tenant + ).aggregate(total=Sum('runs_this_month')) + + return result['total'] or 0 + # ========================================================================= # Limit Retrieval # ========================================================================= @@ -115,6 +136,7 @@ class QuotaService: 'MAX_RESOURCES': 'max_resources', 'MAX_SERVICES': 'max_services', 'MAX_AUTOMATED_TASKS': 'max_automated_tasks', + 'MAX_AUTOMATION_RUNS': 'max_automation_runs', } feature_code = feature_code_map.get(quota_type, quota_type.lower()) diff --git a/smoothschedule/smoothschedule/integrations/activepieces/migrations/0003_add_run_tracking_fields.py b/smoothschedule/smoothschedule/integrations/activepieces/migrations/0003_add_run_tracking_fields.py new file mode 100644 index 00000000..bc5ffaec --- /dev/null +++ b/smoothschedule/smoothschedule/integrations/activepieces/migrations/0003_add_run_tracking_fields.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.8 on 2025-12-22 06:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('activepieces', '0002_add_tenant_default_flow'), + ] + + operations = [ + migrations.AddField( + model_name='tenantdefaultflow', + name='runs_month_started', + field=models.DateField(blank=True, help_text='When the current run count period started (resets monthly)', null=True), + ), + migrations.AddField( + model_name='tenantdefaultflow', + name='runs_this_month', + field=models.PositiveIntegerField(default=0, help_text='Number of times this flow has run this billing month'), + ), + ] diff --git a/smoothschedule/smoothschedule/integrations/activepieces/models.py b/smoothschedule/smoothschedule/integrations/activepieces/models.py index be3289be..0cf69c25 100644 --- a/smoothschedule/smoothschedule/integrations/activepieces/models.py +++ b/smoothschedule/smoothschedule/integrations/activepieces/models.py @@ -124,6 +124,16 @@ class TenantDefaultFlow(models.Model): default=True, help_text="Whether this flow is enabled in Activepieces", ) + # Usage tracking for quota management + runs_this_month = models.PositiveIntegerField( + default=0, + help_text="Number of times this flow has run this billing month", + ) + runs_month_started = models.DateField( + null=True, + blank=True, + help_text="When the current run count period started (resets monthly)", + ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -139,3 +149,22 @@ class TenantDefaultFlow(models.Model): def __str__(self): return f"{self.tenant.name} - {self.get_flow_type_display()}" + + def increment_run_count(self): + """ + Increment the run count for this flow. + Resets the counter if a new month has started. + """ + from django.utils import timezone + + today = timezone.now().date() + current_month_start = today.replace(day=1) + + # Reset counter if new month + if self.runs_month_started is None or self.runs_month_started < current_month_start: + self.runs_this_month = 1 + self.runs_month_started = current_month_start + else: + self.runs_this_month += 1 + + self.save(update_fields=['runs_this_month', 'runs_month_started'])