Re-read the import line on a timer, so its figures move while watched

The indicator is re-read when the server publishes, and an import
publishes when a page of its listing is done. A Gmail import spends most
of its time fetching what the listing named and says nothing meanwhile,
so the count, the messages a minute and the seconds until Google lets it
continue all stood still.

While the indicator shows an import it is now re-read every five seconds
with the list open and every thirty with it shut; never in a hidden tab,
and never when nothing is importing.

The import line's own test spent more than the pacer's reserve and was
made to wait eight seconds for it; it spends less.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Paul LütznerandClaude Opus 5.5 committed 2026-10-13 09:29:51 +02:00
1 parent d06125bbf4
commit 0235d9f4d3
2 files changed
+39 -4

No files matched your search

@@ -13,6 +13,17 @@ import { Controller } from "@hotwired/stimulus";
* in a few seconds is a lot of requests to say something that changed by two
* percent. A trailing reload keeps the last one, so the FINAL state — the one
* that says done, or failed — always lands.
*
* AND ON A TIMER WHILE AN IMPORT IS SHOWN. A job publishes as it goes; an
* import publishes when a page of its listing is done, and a Gmail import
* spends most of its hours fetching what the listing named, saying nothing.
* Its line also carries figures that move by themselves — messages a minute,
* the seconds until Google lets it continue — and a countdown that only
* changes when something else happens is not one. So while the indicator has
* an import line in it, it is re-read every few seconds: often with the list
* open, where somebody is watching, and seldom with it shut. Not at all in a
* tab nobody is looking at, and not at all when nothing is importing, which
* is nearly always.
*/
/** The list of work inside the indicator, and the button that opens it. */
const MENU = "[data-ui--dropdown-target='menu']";
@@ -24,13 +35,36 @@ export default class extends Controller {
/** Milliseconds between reloads while a job is running. */
static THROTTLE = 1500;
/** Milliseconds between looks at an import: list open, and list shut. */
static WATCHED = 5000;
static UNWATCHED = 30000;
connect() {
this._lastAt = 0;
this._pending = null;
this._watch = setInterval(() => this.#watchImport(), this.constructor.WATCHED);
}
disconnect() {
clearTimeout(this._pending);
clearInterval(this._watch);
}
#watchImport() {
const frame = document.getElementById("jobs-indicator");
if (true === document.hidden || null === frame || null === frame.querySelector("[data-import-line]")) {
return;
}
const open = null !== frame.querySelector(`${MENU}:not([hidden])`);
const due = true === open ? this.constructor.WATCHED : this.constructor.UNWATCHED;
// A little under, so a timer that fires a few milliseconds early does
// not wait a whole round more.
if (Date.now() - this._lastAt >= due - 250) {
this.refresh();
}
}
refresh() {
+5 -4
View File
@@ -190,15 +190,16 @@ final class ImportProgressTest extends KernelTestCase
$this->em->flush();
$pacer = static::getContainer()->get(GmailQuotaPacer::class);
$pacer->spend($this->account, 2000);
// Within the reserve, so the pacer does not make the test wait.
$pacer->spend($this->account, 400);
$line = $this->progress->forUser($this->user)[0];
self::assertNotNull($line->quota);
self::assertSame(15_000, $line->quota->limit);
self::assertFalse($line->quota->learnt);
self::assertSame(2000, $line->quota->used);
self::assertSame(100, $line->quota->messagesPerMinute, '2,000 units at twenty a message');
self::assertSame(400, $line->quota->used);
self::assertSame(20, $line->quota->messagesPerMinute, '400 units at twenty a message');
self::assertFalse($line->quota->isPaused());
$running = static::getContainer()->get(Environment::class)->render('mail/_jobs_indicator.html.twig', [
@@ -206,7 +207,7 @@ final class ImportProgressTest extends KernelTestCase
'imports' => [$line],
]);
self::assertStringContainsString('About 100 messages a minute', $running);
self::assertStringContainsString('About 20 messages a minute', $running);
// 9,000 messages at 450 a minute, which is three fifths of 15,000
// units at twenty a message.
self::assertSame(20, $line->quota->minutesLeft);