mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-05 02:41:34 +00:00
fix: Scheduled Tasks calendar shows extra runs for automations with a run count (#31604)
An automation whose schedule ends after a fixed number of runs (COUNT in its RRULE) showed extra future runs in the Scheduled Tasks calendar after each run. With COUNT=3, the calendar kept showing three upcoming runs after the first and second run, although only the remaining ones execute. The calendar now counts runs from the schedule's own start date, the same way automations are actually run, so it shows exactly the runs that are still going to happen. The 5000-entry display limit now only counts entries inside the visible range, so very frequent schedules that started shortly before it no longer show up short or empty. Fixes #31600
This commit is contained in:
parent
32e532b459
commit
909a2075d3
3 changed files with 26 additions and 2 deletions
|
|
@ -26,6 +26,7 @@ from open_webui.models.users import UserModel
|
|||
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
|
||||
from open_webui.utils.auth import get_verified_user
|
||||
from open_webui.utils.calendar import expand_recurring_event
|
||||
from open_webui.utils.recurrence import schedule_start_ns
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -206,13 +207,22 @@ async def get_events(
|
|||
if not rrule_str:
|
||||
continue
|
||||
|
||||
start_at = auto.next_run_at or 0
|
||||
upper_rrule = rrule_str.upper()
|
||||
if 'COUNT=' in upper_rrule and 'DTSTART' in upper_rrule:
|
||||
# COUNT runs from DTSTART, anchoring on the next run would restart it
|
||||
try:
|
||||
start_at = schedule_start_ns(rrule_str, user.timezone)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
virtual = {
|
||||
'id': f'auto_{auto.id}',
|
||||
'calendar_id': SCHEDULED_TASKS_CALENDAR_ID,
|
||||
'user_id': user.id,
|
||||
'title': auto.name,
|
||||
'description': auto.data.get('prompt', '') if auto.data else '',
|
||||
'start_at': auto.next_run_at or 0,
|
||||
'start_at': start_at,
|
||||
'end_at': None,
|
||||
'all_day': False,
|
||||
'rrule': rrule_str,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ def expand_recurring_event(
|
|||
|
||||
instances = []
|
||||
previous_start = None
|
||||
for occurrence_start in rule.xafter(scan_start, count=max_instances, inc=True):
|
||||
for occurrence_start in rule.xafter(scan_start, inc=True):
|
||||
if occurrence_start >= range_end or occurrence_start == previous_start:
|
||||
break
|
||||
previous_start = occurrence_start
|
||||
|
|
@ -76,6 +76,8 @@ def expand_recurring_event(
|
|||
'instance_id': f'{event_dict["id"]}_{instance_start_ns}',
|
||||
}
|
||||
instances.append(instance)
|
||||
if len(instances) >= max_instances:
|
||||
break
|
||||
|
||||
return instances
|
||||
|
||||
|
|
|
|||
|
|
@ -148,6 +148,18 @@ async def next_run_ns(s: str, tz: str = None) -> Optional[int]:
|
|||
return int(dt.timestamp() * 1_000_000_000)
|
||||
|
||||
|
||||
def schedule_start_ns(s: str, tz: str = None) -> int:
|
||||
"""DTSTART the scheduler anchors the rule to, as epoch nanoseconds."""
|
||||
zi = _resolve_tz(tz)
|
||||
now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now()
|
||||
parsed = _parse_rule(s, now)
|
||||
rule = parsed._rrule[0] if isinstance(parsed, rruleset) else parsed
|
||||
dt = rule._dtstart
|
||||
if zi:
|
||||
dt = dt.replace(tzinfo=zi)
|
||||
return int(dt.timestamp() * 1_000_000_000)
|
||||
|
||||
|
||||
async def next_n_runs_ns(s: str, n: int = 5, tz: str = None) -> list[int]:
|
||||
"""Compute next N occurrences for UI preview.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue