Affichage widget lent sur le dashboard - v4.4.19

Bonjour,

Depuis peu, j’ai fais la mise à jour en 4.4 et je suis passé en Debian 11. Après avoir mis à jour les widgets personnalisés, il m’en reste 1, le widget des agendas, qui met plus de temps à s’afficher sur le dashboard (ou dans une synthèse). C’est de l’ordre de 10-15 secondes (durée du spinner de chargement) alors que si je le rends invisible les widgets des autres plugins sont affichés presque instantanément (2s grand max).

J’ai re-sauvegardé mes agendas pour être sur mais pas d’amélioration.

Suis-je le seul à observer ce ralentissement ?
Comment y remédier ?

Merci d’avance

Bonjour,
As tu essayé de refaire tes évènements ? En gros si tu as un évènement qui a été fait en 2016, jeedom est obligé de calculer toute les occurrences jusqu’en 2024 ce qui peut etre assez long.

Bonjour @Loic,
Merci pour ta suggestion :slight_smile:. Si je comprends bien tu veux que je resauvegarde mes évènements mais avec une date de début en 2024 ?
Actuellement j’ai ça:


Que je modifie en :

Résultat : en effet, l’affichage est beaucoup plus rapide (1s de plus que si je le rends invisible :tada:)
Merci bien !
Est ce qu’il y aurait moyen que la partie widget soit optimisée pour ne calculer qu’à partir de la date du jour ? ou de la précédente répétition ?

Bonjour
Non pas possible la référence c’est ce que tu donnes en configuration à jeedom il recalcule toujours à partir de la.

Dommage, merci bien en tout cas :smiling_face:

Finalement, je suis allé faire un petit tour par chatgpt pour lui demander s’il pouvait prendre en compte la date de début de range pour la fonction public function calculOccurrence($_startDate, $_endDate, $_max = 9999999999, $_recurence = 0) (ligne 630 /plugins/calendar/core/class/calendar.class.php) et j’obtiens un résultat assez probant !

A tester en beta peut-etre :wink:
J’espère que ça pourra aider

public function calculOccurrence($_startDate, $_endDate, $_max = 9999999999, $_recurence = 0) {
    if ($_recurence > 5) {
        return [];
    }
    $_recurence++;

    $startTime = $_startDate ? strtotime($_startDate) : strtotime('now - 2 year');
    $endTime = $_endDate ? strtotime($_endDate) : strtotime('now + 2 year');
    $_beginDate = $_startDate ? new DateTime($_startDate) : new DateTime($this->getStartDate());
    
    $return = [];
    $repeat = $this->getRepeat();

    if ($repeat['enable'] != 1) {
        return $this->handleNonRepeatingEvent($startTime, $endTime);
    }

    $excludeDate = $this->getExcludedDates($repeat, $_startDate, $_endDate, $_max, $_recurence);

    $currentDate = $_beginDate;
    $initStartTime = $this->getTimePart($this->getStartDate());
    $initEndTime = $this->getTimePart($this->getEndDate());

    while ($this->shouldContinue($currentDate, $startTime, $endTime, $repeat)) {
        $formattedDate = $currentDate->format('Y-m-d');

        if ($this->isDateIncluded($formattedDate, $repeat, $excludeDate) && $this->matchesNationalDayCriteria($formattedDate, $repeat)) {
            $return[] = [
                'start' => $currentDate->format('Y-m-d') . ' ' . $initStartTime,
                'end' => $currentDate->format('Y-m-d') . ' ' . $initEndTime,
            ];
            if (count($return) >= $_max) {
                return $return;
            }
        }

        $currentDate = $this->calculateNextOccurrence($currentDate, $repeat, $initStartTime, $initEndTime);
    }

    return $this->mergeIncludeDates($return, $repeat, $startTime, $endTime, $initStartTime, $initEndTime);
}

private function matchesNationalDayCriteria($date, $repeat) {
    $nationalDays = self::getNationalDay(date('Y', strtotime($date)));

    switch ($repeat['nationalDay']) {
        case 'exeptNationalDay':
            return !in_array($date, $nationalDays);
        case 'onlyNationalDay':
            return in_array($date, $nationalDays);
        case 'onlyEven':
            return (date('W', strtotime($date)) % 2) == 0;
        case 'onlyOdd':
            return (date('W', strtotime($date)) % 2) == 1;
        case 'all':  // 'all' means no filter on national day
        default:
            return true;
    }
}

private function handleNonRepeatingEvent($startTime, $endTime) {
    if ($this->isEventWithinBounds($startTime, $endTime)) {
        return [[
            'start' => $this->getStartDate(),
            'end' => $this->getEndDate(),
        ]];
    }
    return [];
}

private function getExcludedDates($repeat, $_startDate, $_endDate, $_max, $_recurence) {
    $excludeDate = [];
    
    if (!empty($repeat['excludeDate'])) {
        $excludeDate = array_merge($excludeDate, $this->parseExcludeDates($repeat['excludeDate']));
    }

    if (!empty($repeat['excludeDateFromCalendar'])) {
        $excludeDate = array_merge($excludeDate, $this->getExcludeDatesFromCalendar($repeat, $_startDate, $_endDate, $_max, $_recurence));
    }

    return $excludeDate;
}

private function parseExcludeDates($excludeDateString) {
    $excludeDate = [];
    foreach (explode(',', $excludeDateString) as $date) {
        $dateRange = explode(':', $date);
        if (count($dateRange) === 2) {
            $excludeDate = array_merge($excludeDate, $this->generateDateRange($dateRange[0], $dateRange[1]));
        } else {
            $excludeDate[] = date('Y-m-d', strtotime($date));
        }
    }
    return $excludeDate;
}

private function getExcludeDatesFromCalendar($repeat, $_startDate, $_endDate, $_max, $_recurence) {
    $excludeDates = [];
    $calendar = calendar::byId($repeat['excludeDateFromCalendar']);
    if (is_object($calendar)) {
        $events = $this->getEventsFromCalendar($calendar, $repeat);
        foreach ($events as $event) {
            $occurrences = $event->calculOccurrence($_startDate, $_endDate, $_max, $_recurence);
            foreach ($occurrences as $occurrence) {
                $excludeDates = array_merge($excludeDates, $this->generateDateRange($occurrence['start'], $occurrence['end']));
            }
        }
    }
    return $excludeDates;
}

private function generateDateRange($start, $end) {
    $dates = [];
    $startDate = new DateTime($start);
    $endDate = new DateTime($end);

    while ($startDate <= $endDate) {
        $dates[] = $startDate->format('Y-m-d');
        $startDate->modify('+1 day');
    }

    return $dates;
}

private function shouldContinue($currentDate, $startTime, $endTime, $repeat) {
    $until = strtotime($this->getUntil());
    return ($until === false || $currentDate->getTimestamp() < $until) && $currentDate->getTimestamp() <= $endTime;
}

private function isDateIncluded($date, $repeat, $excludeDate) {
    return !in_array($date, $excludeDate) && $repeat['excludeDay'][date('N', strtotime($date))] == 1;
}

private function calculateNextOccurrence($currentDate, $repeat, $initStartTime, $initEndTime) {
    if (isset($repeat['mode']) && $repeat['mode'] === 'advance') {
        // Calculate next occurrence based on "advance" rules.
        $nextMonth = date('F', strtotime('+1 month ' . $currentDate->format('Y-m-d')));
        $year = date('Y', strtotime('+1 month ' . $currentDate->format('Y-m-d')));
        $nextDate = date('Y-m-d', strtotime($repeat['positionAt'] . ' ' . $repeat['day'] . ' of ' . $nextMonth . ' ' . $year));

        if ($nextDate === '1970-01-01') {
          	return $currentDate; // Prevent infinite loop if an invalid date is calculated.
        }

        return new DateTime($nextDate . ' ' . $initStartTime);
    }

	// Regular recurrence based on frequency and unit.
    $freq = $repeat['freq'];
    $unit = $repeat['unite'];

    return $currentDate->modify("+$freq $unit");
}

private function mergeIncludeDates($occurrences, $repeat, $startTime, $endTime, $initStartTime, $initEndTime) {
    $includeDates = $this->getIncludeDates($repeat, $startTime, $endTime);

    foreach ($includeDates as $includeDate) {
        $includeStart = $includeDate . ' ' . $initStartTime;
        $includeEnd = $includeDate . ' ' . $initEndTime;

        $isDuplicate = false;

        foreach ($occurrences as $occurrence) {
            if ($occurrence['start'] === $includeStart && $occurrence['end'] === $includeEnd) {
                $isDuplicate = true;
                break;
            }
        }

        if (!$isDuplicate) {
            $occurrences[] = [
                'start' => $includeStart,
                'end' => $includeEnd,
            ];
        }
    }

    usort($occurrences, function ($a, $b) {
        return strtotime($a['start']) - strtotime($b['start']);
    });

    return $occurrences;
}

private function getIncludeDates($repeat, $startTime, $endTime) {
    $includeDates = [];

    // Directly specified include dates
    if (!empty($repeat['includeDate'])) {
        $rawIncludeDates = explode(',', trim($repeat['includeDate'], ','));
        foreach ($rawIncludeDates as $rawDate) {
            if (strpos($rawDate, ':') !== false) {
                list($rangeStart, $rangeEnd) = explode(':', $rawDate);
                $this->addDateRange($rangeStart, $rangeEnd, $includeDates, $startTime, $endTime);
            } else {
                $this->addSingleDate($rawDate, $includeDates, $startTime, $endTime);
            }
        }
    }

    // Include dates from calendar events
    if (!empty($repeat['includeDateFromCalendar'])) {
        $calendar = calendar::byId($repeat['includeDateFromCalendar']);
        if (is_object($calendar)) {
            $events = $repeat['includeDateFromEvent'] === 'all' 
                ? self::getEventsByEqLogic($calendar->getId()) 
                : [self::byId($repeat['includeDateFromEvent'])];

            foreach ($events as $event) {
                if (is_object($event) && $event->getId() !== $this->getId()) {
                    $eventOccurrences = $event->calculOccurrence(null, null);
                    foreach ($eventOccurrences as $occurrence) {
                        $this->addDateRange($occurrence['start'], $occurrence['end'], $includeDates, $startTime, $endTime);
                    }
                }
            }
        }
    }

    return $includeDates;
}

private function addDateRange($start, $end, &$dates, $startTime, $endTime) {
    $startDate = strtotime($start);
    $endDate = strtotime($end);

    while ($startDate <= $endDate) {
        if ($startDate >= $startTime && $startDate <= $endTime) {
            $dates[] = date('Y-m-d', $startDate);
        }
        $startDate = strtotime('+1 day', $startDate);
    }
}

private function addSingleDate($date, &$dates, $startTime, $endTime) {
    $timestamp = strtotime($date);
    if ($timestamp >= $startTime && $timestamp <= $endTime) {
        $dates[] = date('Y-m-d', $timestamp);
    }
}


private function getTimePart($dateTime) {
    return date('H:i:s', strtotime($dateTime));
}

private function isEventWithinBounds($startTime, $endTime) {
    return strtotime($this->getStartDate()) <= $endTime && strtotime($this->getEndDate()) >= $startTime;
}

@Loic est ce que la modif que j’ai proposais sera prise en compte au moins en beta ?

Bonjour
J’ai bien noté ta modification mais j’ai pas encore eu le temps de regarder et pour être honnête j’ai peur de toucher cette partie. Il m’a fallu plusieurs années pour la rendre fonctionnel c’est du code vraiment compliqué et je sais à quel point cette partie est critique pour les utilisateurs. Ils ne me pardonneront donc pas la moindre erreur….

Merci, mais les versions beta sont là pour ça, non ?

J’aimerais mais l’expérience montre que même si ça reste plusieurs mois en bêta arrivé en stable y’a des soucis…. Regarde les version du core la 4.4 a passée 6 mois en bêta et pourtant arrivé en stable plein de soucis

Après si tu fais un pr ça peut être plus simple car on pourra faire valider à plusieurs personne.

Heu est-ce vraiment utile de conserver des occurrences depuis 2017? Dans l’absolu il suffit de changer la date de début des occurrences.

A la limite, il serait plus simple d’alerter/prévoir une option/limiter si la date de début a plus de 3 ou 5 ans par exemple, non?

Je vais créer une issue à ce sujet pour commencer.

2 « J'aime »

Bonne idée @Loic, c’est ce que je viens de faire : Improvements to occurrence calculation by AntTBD · Pull Request #53 · jeedom/plugin-calendar · GitHub. Ça m’a permis de voir qu’il manquait des choses merci.

Bonsoir @Aurelien, sincèrement dans 1 an, par exemple voir plus, j’aurais oublié la raison du pourquoi mon dashboard met du temps à s’afficher et je ne vais pas souvent voir si mon plugin agenda se porte bien… C’est un des plugin que je laisse tourner tranquillement une fois configuré :slight_smile:.

D’où le:

A froid, sur le papier, ça me semble une bien meilleure option que de faire modifier le code de ce plugin par chatGPT… surtout pour un besoin somme toute assez limité et, au pire des cas, facile à résoudre.