Show how many messages a minute a Gmail import is bringing in

The import line had the quota and the time left, both in units and
minutes. What somebody watching wants to see move is mail: a line with
roughly how many messages were fetched in the minute just gone, worked
out from what the pacer counted. Not shown while Google has the account
paused, when none are.

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:20:04 +02:00
1 parent 95f3291911
commit d06125bbf4
8 files changed
+30 -2

No files matched your search

+2 -1
View File
@@ -19,7 +19,8 @@ allowance between its workers. A refusal is no longer an
error: whatever met it — a batch, an import page, a sync, a label change being pushed — waits
out the minute and carries on. The
import line in the background-work indicator now shows the quota, roughly how much of it is in
use, how long the rest of the import takes, and says so when Google has paused it. No
use, how many messages a minute are coming in, how long the rest of the import takes, and says
so when Google has paused it. No
migration. [#90](https://github.com/karatektus/pl_mail/issues/90), reported by @karamanliev.
**The tab icon is the one you picked, in Firefox too.** Settings → Appearance → Logo changed the
+4
View File
@@ -29,6 +29,9 @@ final readonly class GmailQuotaStatus
* in the minute before Google refused
* @param int $pausedSeconds how long Google's last refusal still
* holds the account; 0 when it does not
* @param int $messagesPerMinute roughly how many messages were
* fetched in the minute just gone; 0 while
* paused, when none are
* @param int|null $minutesLeft how long the rest of the import takes at
* the paced rate, or null when there is no
* total to reckon it from
@@ -38,6 +41,7 @@ final readonly class GmailQuotaStatus
public bool $learnt,
public int $used,
public int $pausedSeconds,
public int $messagesPerMinute,
public ?int $minutesLeft,
) {}
+6 -1
View File
@@ -130,6 +130,7 @@ final readonly class ImportProgress
$left = null === $total ? null : max(0, $total - $done);
$paused = $this->pacer->waitSeconds($account);
$used = $this->pacer->usedThisMinute($account);
return new GmailQuotaStatus(
$this->pacer->limit(),
@@ -137,8 +138,12 @@ final readonly class ImportProgress
// While Google holds the account nothing is being spent, and the
// running count falls to nothing; what was spent when it said no
// is the figure that explains the pause.
$paused > 0 ? $this->pacer->usedWhenRefused($account) : $this->pacer->usedThisMinute($account),
$paused > 0 ? $this->pacer->usedWhenRefused($account) : $used,
$paused,
// What was spent, over what a message costs. A listing page is in
// the count as well and is a quarter of one message; a refused
// message is in it too, and was not fetched. Roughly, then.
$paused > 0 ? 0 : intdiv($used, GmailQuotaPacer::UNITS_PER_MESSAGE),
null === $left || 0 === $left ? null : (int) ceil($left / $this->pacer->messagesPerMinute()),
);
}
+5
View File
@@ -170,6 +170,11 @@ the record and a missed nudge costs one stale render, never a wrong answer.
'%limit%': line.quota.limit|number_format,
}) }}
</p>
{% if line.quota.messagesPerMinute > 0 and not line.waiting %}
<p class="mt-0.5 text-[11px] text-ink-faint truncate" data-import-rate>
{{ 'jobs.import.rate'|trans({ '%count%': line.quota.messagesPerMinute|number_format }) }}
</p>
{% endif %}
{% if line.quota.minutesLeft is not null and (not line.waiting or line.quota.isPaused()) %}
<p class="mt-0.5 text-[11px] text-ink-faint truncate" data-import-remaining>
{% if line.quota.minutesLeft >= 60 %}
+10
View File
@@ -198,7 +198,15 @@ final class ImportProgressTest extends KernelTestCase
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::assertFalse($line->quota->isPaused());
$running = static::getContainer()->get(Environment::class)->render('mail/_jobs_indicator.html.twig', [
'jobs' => [],
'imports' => [$line],
]);
self::assertStringContainsString('About 100 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);
@@ -210,6 +218,7 @@ final class ImportProgressTest extends KernelTestCase
self::assertSame(6000, $line->quota->limit);
self::assertTrue($line->quota->learnt);
self::assertTrue($line->quota->isPaused());
self::assertSame(0, $line->quota->messagesPerMinute);
self::assertSame(50, $line->quota->minutesLeft);
$html = static::getContainer()->get(Environment::class)->render('mail/_jobs_indicator.html.twig', [
@@ -221,6 +230,7 @@ final class ImportProgressTest extends KernelTestCase
self::assertStringContainsString('6,000', $html);
self::assertStringContainsString('About 50 min left', $html);
self::assertStringContainsString('Paused by Gmail', $html);
self::assertStringNotContainsString('data-import-rate', $html, 'nothing is fetched while paused');
}
/** Nothing running is no button at all, as it was before imports were shown. */
+1
View File
@@ -2191,6 +2191,7 @@ jobs:
quota_assumed: "Gmail-Kontingent: etwa %used% von %limit% Einheiten in dieser Minute (Limit angenommen, bis Google es nennt)"
quota_paused: "Pausiert durch Gmails Kontingent · weiter in %seconds% s"
quota_refused: "Gmail hat bei etwa %used% von %limit% Einheiten pro Minute abgelehnt"
rate: "Etwa %count% Nachrichten pro Minute"
remaining_hours: "Noch etwa %hours% Std. %minutes% Min."
remaining_minutes: "Noch etwa %minutes% Min."
waiting: "Wartet darauf, weiterzumachen"
+1
View File
@@ -2190,6 +2190,7 @@ jobs:
quota_assumed: "Gmail quota: about %used% of %limit% units this minute (limit assumed until Google names it)"
quota_paused: "Paused by Gmail's quota · continues in %seconds% s"
quota_refused: "Gmail said no at about %used% of %limit% units a minute"
rate: "About %count% messages a minute"
remaining_hours: "About %hours% h %minutes% min left"
remaining_minutes: "About %minutes% min left"
waiting: "Waiting to continue"
+1
View File
@@ -2070,6 +2070,7 @@ jobs:
quota_assumed: "Gmail's ration: about %used% o' %limit% units this minute (a guess, till Google names the limit)"
quota_paused: "Becalmed by Gmail's ration · under way in %seconds% s"
quota_refused: "Gmail said nay at about %used% o' %limit% units a minute"
rate: "About %count% letters a minute"
remaining_hours: "About %hours% h %minutes% min o' haulin' left"
remaining_minutes: "About %minutes% min o' haulin' left"
waiting: "Waitin' fer the tide"