Skip to content

Record the import range start when an import begins, CLOPS-1053 (6.x backport) - #659

Merged
AltamashShaikh merged 1 commit into
6.x-devfrom
CLOPS-1053-fix-stalled-ga-imports-6.x
Sep 14, 2026
Merged

AltamashShaikh merged 1 commit into
6.x-devfrom
CLOPS-1053-fix-stalled-ga-imports-6.x

Conversation

@AltamashShaikh

Copy link
Copy Markdown
Contributor

Description

Backport of #655 to 6.x-dev. Cherry-picked from e740039; the changes to ImportStatus.php and tests/Integration/ImportStatusTest.php are byte-identical to what was merged on 5.x-dev. Only plugin.json and CHANGELOG.md conflicted, because the two lines carry different versions — resolved to 6.0.2 and a 6.0.2 changelog entry in the 6.x format.

The original description follows.


GA imports run backwards: they start at the end date and walk back towards the oldest date there is data for. That oldest date is held in the import status as import_range_start, and two separate pieces of logic compare progress against it — finishImportIfNothingLeft(), which marks an import complete, and the branch in ImportGA4Reports that switches an import over to pulling recent days once the backward walk is done.

startingImport() never set it. It wrote 'import_range_start' => null and left it to whoever started the import to fill in afterwards. Controller::startImportGA4() does that when the user supplies dates, but ImporterGA4::makeSite() does not — and that is the path taken by every import created through the Matomo Cloud "connect with Google" flow, as well as any UI import started without an explicit date range.

Those imports ran normally for as long as they had days left to fetch. The failure only appeared when one reached the oldest date, at which point it could neither finish nor move forward:

  • finishImportIfNothingLeft() bails on !empty($status['import_range_start']), so the status stayed ongoing forever.
  • The switch to importing recent days tests last_day_archived === import_range_start, which can never match null, so it never fired.

The result is an import that the hourly task relaunches every hour, that recomputes its range, finds nothing to do, and exits after about a second — while recent data is never imported. Because the status stays ongoing and the job is not running, the admin page derives the "Terminated" label for it, and prints alongside it whatever was last written to $status['error'] (nothing ever clears that field). That made a stalled-but-otherwise-healthy import present as a credentials failure.

This sets import_range_start from the site creation date, which both importers take from the GA property's getCreateTime() immediately before starting the import, and which ImportGA4Reports already uses as its own per-run fallback. So the value is not new — it is the one the importer was already deriving each run, now recorded once where the rest of the code expects to find it. The lookup is guarded: a missing or unreadable creation date falls back to the previous null rather than failing the import start.

It also stops setImportDateRange() blanking the range start when passed a null start date. Controller::startImportGA4() calls it that way whenever either date is given, so an import started with only an end date would have had its range start dropped immediately after startingImport() set it. Callers passing no start date mean "leave it alone", not "clear it" — changeImportEndDate() already depends on that by reading the existing value and passing it straight back.

Deliberately not included

No migration. Existing statuses with a null range start are not repaired by this PR, so they stay stalled until backfilled. That was a considered call: a migration here has to write to every GA import status row on every instance, Site::getCreationDateFor() throws on deleted sites (orphaned status rows demonstrably exist — enrichStatus() already catches UnexpectedWebsiteFoundException for exactly that), and a throwing migration blocks the whole Matomo update, not just this plugin.

There is also a case a migration would have to get right. Where last_day_archived has drifted later than main_import_progress, filling in only import_range_start makes finishImportIfNothingLeft() pass while the forward-catch-up branch still does not fire — so the import is silently marked finished with recent data never imported. That is worse than leaving it stalled, because resumeScheduledImports skips finished imports entirely and it cannot self-correct.

Affected instances can be found with a read-only query, and repaired with a targeted update:

SELECT option_name FROM matomo_option
 WHERE option_name LIKE 'GoogleAnalyticsImporter.importStatus_%'
   AND option_value LIKE '%"import_range_start":null%';

Set import_range_start to the site creation date, and where last_day_archived is later than main_import_progress, lower it to match. That is what was applied to the three sites in the originating report, and they resumed importing the missing window immediately.

Worth filing separately, both found while tracing this: resumeImport() never clears $status['error'], so a long-resolved error is replayed against every later termination; and the guard in startingImport() throws CancelExistingImportFirst inside a try whose own catch swallows it, so an in-progress import is always overwritten instead of refused.

Issue No

CLOPS-1053. Backport of #655.

Steps to Replicate the Issue

  1. Start a GA4 import through the Matomo Cloud "connect with Google" flow (or call ImporterGA4::makeSite() directly), and let it run until it has imported back to the GA property's first day. The stored status has "import_range_start":null and main_import_progress equal to the site creation date.
  2. Expected: the import is marked finished, or it begins importing the days between when the import was started and today.
  3. Actual: every run prints (Entry #0) is finished, moving on. and [0 API requests made to GA], the status stays ongoing, no recent data is imported, and the admin page shows the import as "Terminated" with a stale error message beneath it.

Checklist

  • [✔] Tested locally or on demo2/demo3?
  • [✔] New test case added/updated?
  • [NA] Are all newly added texts included via translation?
  • [NA] Are text sanitized properly? (Eg use of v-text v/s v-html for vue)
  • [✔] Version bumped?
  • [✔] I have understood, reviewed, and tested all AI outputs before use
  • [✔] All AI instructions respect security, IP, and privacy rules
  • [NA] Documentation updated?

Verified locally: ImportStatusTest 60/60 green and PHPCS clean against phpcs.xml. Note the local checkout runs Matomo 5.14.0-alpha while this branch requires Matomo >= 6.0.0-b1, so CI is the authority for this line — the suite is unchanged from #655, where it was also green.

Imports run backwards, from the end date towards the oldest date there is
data for. Both finishing an import and switching over to importing recent
days compare progress against the recorded import_range_start, but
startingImport() never set it, so imports created by makeSite() - which is
every import started from the Cloud "connect with Google" flow - carried a
null range start for their whole life.

Once such an import walked back to the oldest date it could neither finish
nor move forward: each scheduled run recomputed the range, found nothing to
do and exited in about a second, leaving the status on "ongoing" and the
admin page reporting it as terminated with whatever error had last been
recorded. Recent data was never imported.

Set the range start from the site creation date, which both importers take
from the GA property's create time immediately before starting the import,
and which the import command already falls back to on every run. Also stop
setImportDateRange() blanking the range start when no start date is passed -
Controller::startImportGA4() calls it that way whenever only an end date was
given, which would otherwise drop the value straight after it was set.

No migration: this fixes new imports only. Existing statuses with a null
range start need their range start backfilled, and where last_day_archived
has drifted later than main_import_progress that needs realigning too,
otherwise the import is marked finished without importing recent data.
@AltamashShaikh
AltamashShaikh merged commit 1b48173 into 6.x-dev Sep 14, 2026
18 checks passed
@AltamashShaikh
AltamashShaikh deleted the CLOPS-1053-fix-stalled-ga-imports-6.x branch September 14, 2026 05:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants