Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/matomo-tests.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Action for running tests
# This file has been automatically created.
# To recreate it you can run this command
# ./console generate:test-action --plugin="GoogleAnalyticsImporter" --php-versions="matomo5_min_php,matomo5_max_php" --dependent-plugins="matomo-org/plugin-MarketingCampaignsReporting innocraft/plugin-Funnels" --schedule-cron="10 3 * * 6"
# ./console generate:test-action --plugin="GoogleAnalyticsImporter" --php-versions="matomo6_min_php,matomo6_max_php" --dependent-plugins="matomo-org/plugin-MarketingCampaignsReporting innocraft/plugin-Funnels" --schedule-cron="10 3 * * 6"

name: Plugin GoogleAnalyticsImporter Tests

Expand Down Expand Up @@ -37,8 +37,11 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ 'matomo5_min_php', 'matomo5_max_php' ]
php: [ 'matomo6_min_php', 'matomo6_max_php' ]
target: ['minimum_required_matomo', 'maximum_supported_matomo']
database:
- { engine: 'Mysql', version: '8.0' }
- { engine: 'Mariadb', version: '10.6' }
steps:
- uses: actions/checkout@v3
with:
Expand All @@ -53,8 +56,10 @@ jobs:
php-version: ${{ matrix.php }}
test-type: 'PluginTests'
matomo-test-branch: ${{ matrix.target }}
mysql-engine: ${{ matrix.database.engine }}
mysql-version: ${{ matrix.database.version }}
artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
upload-artifacts: ${{ matrix.php == 'matomo5_min_php' && matrix.target == 'maximum_supported_matomo' }}
upload-artifacts: ${{ matrix.php == 'matomo6_min_php' && matrix.target == 'maximum_supported_matomo' && matrix.database.engine == 'Mysql' }}
dependent-plugins: 'matomo-org/plugin-MarketingCampaignsReporting innocraft/plugin-Funnels'
github-token: ${{ secrets.TESTS_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
UI:
Expand All @@ -70,8 +75,10 @@ jobs:
plugin-name: 'GoogleAnalyticsImporter'
matomo-test-branch: 'maximum_supported_matomo'
test-type: 'UI'
php-version: 'matomo5_min_php'
node-version: '16'
mysql-engine: 'Mysql'
mysql-version: '8.0'
php-version: 'matomo6_min_php'
node-version: '24'
artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
upload-artifacts: true
dependent-plugins: 'matomo-org/plugin-MarketingCampaignsReporting innocraft/plugin-Funnels'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ vendor/**/composer.lock
/vue/dist/*.common.js
/vue/dist/*.map
/vue/dist/*.development.*
/vue/dist/*.umd.js
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Changelog

# 6.0.0 - 2026-08-11

- Compatibility with Matomo 6

# 5.2.1 - 2026-08-03
- Added PHPStan static analysis (CI check and pre-push hook)
- Fixed an error that could hide the real failure reason when starting a GA4 import fails
Expand Down
5 changes: 3 additions & 2 deletions Logger/LogToSingleFileProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Piwik\Plugins\GoogleAnalyticsImporter\Logger;

use Monolog\LogRecord;
use Piwik\Log\Logger;
use Piwik\Container\StaticContainer;

Expand All @@ -25,11 +26,11 @@ public function __construct($idSite)
{
$this->idSite = (int) $idSite;
}
public function __invoke(array $record)
public function __invoke(LogRecord $record): LogRecord
{
$message = $record['message'];
if (is_string($message)) {
$record['message'] = '(idSite: ' . $this->idSite . ') ' . $record['message'];
$record = $record->with(message: '(idSite: ' . $this->idSite . ') ' . $message);
}
return $record;
}
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"require": {
"php": ">=7.2.5",
"php": ">=8.1.0",
"google/apiclient": "^2.15.3",
"google/apiclient-services": "^0.224.1",
"guzzlehttp\/guzzle": "7.10.1",
Expand Down
4 changes: 2 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
parameters:
level: 1
phpVersion: 70200
phpVersion: 80100
tmpDir: /tmp/phpstan/GoogleAnalyticsImporter/main
paths:
- .
excludePaths:
analyseAndScan:
- tests/*
- github-action-tests/
- github-action-tests/ (?)
- scoper.inc.php
# still scanned so the prefixed Google classes are discovered even
# when the Matomo bootstrap cannot load plugin autoloaders (e.g. CI)
Expand Down
4 changes: 2 additions & 2 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "GoogleAnalyticsImporter",
"description": "Import reports from a Google Analytics account into Matomo.",
"version": "5.2.1",
"version": "6.0.0",
"theme": false,
"require": {
"matomo": ">=5.0.0-rc5,<6.0.0-b1"
"matomo": ">=6.0.0-b1,<7.0.0-b1"
},
"authors": [
{
Expand Down
9 changes: 0 additions & 9 deletions stylesheets/styles.less
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@
}
}

.ga-import-warning {
background: @color-orange-brand;
color: white;

&::before {
color: unset;
}
}

.radio {
margin-left: 1rem;
}
Expand Down
1 change: 1 addition & 0 deletions tests/System/CheckDirectDependencyUseCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function testCommand()
'GoogleAnalyticsImporter/Importers/DevicesDetection/RecordImporterGA4.php'
],
'Monolog' => [
'GoogleAnalyticsImporter/Logger/LogToSingleFileProcessor.php',
'GoogleAnalyticsImporter/Monolog/Handler/GASystemLogHandler.php'
],
'Symfony\Component\Console' => [
Expand Down
57 changes: 50 additions & 7 deletions tests/UI/GoogleAnalyticsImporterGA4_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ describe("GoogleAnalyticsImporterGA4", function () {

var url = "?module=GoogleAnalyticsImporter&action=index&idSite=1&period=day&date=yesterday";

// type into a field with real keyboard events and verify the value arrived
// (a freshly rendered field sometimes swallows the first characters);
// values set programmatically do not reach the Vue model
async function typeFieldValue(selector, text) {
let actual;
for (let attempt = 0; attempt < 3; attempt++) {
await (await page.$(selector)).type(text);
await page.waitForTimeout(100);
actual = await page.evaluate(
(selector) => document.querySelector(selector).value,
selector
);
if (actual === text) {
return;
}
await page.evaluate((selector) => {
document.querySelector(selector).value = '';
}, selector);
}

throw new Error(`Could not type "${text}" into ${selector}, last value was "${actual}"`);
}

// selects bind v-model to the real <select>, so a native change event works
async function setSelectValue(selector, value) {
await page.evaluate((selector, value) => {
const el = document.querySelector(selector);
el.value = value;
el.dispatchEvent(new Event('change', { bubbles: true }));
}, selector, value);
}

// Submitting the form navigates, and .pageWrap exists before its contents are
// rendered into it, so a capture taken straight away is an empty strip.
async function waitForPageContent() {
await page.waitForFunction(() => {
const wrap = document.querySelector('.pageWrap');
return wrap && wrap.getBoundingClientRect().height > 100;
});
}

async function removeStartResumeFinishTime() {
await page.evaluate(() => $('td.import-start-finish-times').html(''));
}
Expand All @@ -37,18 +78,19 @@ describe("GoogleAnalyticsImporterGA4", function () {
it("should load the settings correctly with GA4 option selected", async function () {
await page.goto(url);

// there is no importer selection radio — GA4 is the only importer on
// this page, so this matches the load screenshot by design
const content = await page.$('.pageWrap');
await page.evaluate(() => $('input:radio[name=selectedImporter]').val('ga4').change());
expect(await content.screenshot()).to.matchImage('load_ga4');
});

it("should start an import properly", async function () {
await page.evaluate(() => $('input#startDateGA4').val('2019-06-27').change());
await page.evaluate(() => $('input#endDateGA4').val('2019-07-02').change());
await page.evaluate(() => $('input#propertyIdGA4').val('properties/12345').change());
await page.evaluate(() => $('div[name=streamIds] input.control_text').val('streamId1').change());
await page.evaluate(() => $('div[name=extraCustomDimensionsGA4] input.control_text').val('userAgeBracket').change());
await page.evaluate(() => $('div[name=extraCustomDimensionsGA4] select:eq(0)').val('string:visit').change());
await typeFieldValue('input#startDateGA4', '2019-06-27');
await typeFieldValue('input#endDateGA4', '2019-07-02');
await typeFieldValue('input#propertyIdGA4', 'properties/12345');
await typeFieldValue('div[name=streamIds] input.control_text', 'streamId1');
await typeFieldValue('div[name=extraCustomDimensionsGA4] input.control_text', 'userAgeBracket');
await setSelectValue('div[name=extraCustomDimensionsGA4] select', 'string:visit');
await page.click('[name=isVerboseLoggingEnabledGA4] label');

await page.click('#startImportSubmitGA4');
Expand Down Expand Up @@ -147,6 +189,7 @@ describe("GoogleAnalyticsImporterGA4", function () {
await page.click('#removeConfigForm button[type=submit]');
await page.waitForNetworkIdle();
await page.waitForSelector('.pageWrap');
await waitForPageContent();

await removeStartResumeFinishTime();

Expand Down
Binary file modified tests/UI/expected-ui-screenshots/EmptySite_GA_details_ga4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/UI/expected-ui-screenshots/EmptySite_GA_list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/UI/expected-ui-screenshots/EmptySite_GA_list_admin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading