From 4e105cadd355b30a4fcdd6e73b28bd4ae1e8ce1f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 13:11:39 +0800 Subject: [PATCH 01/13] Initial work on GA4 support, add features to Traffic Overview --- classes/Analytics.php | 23 +++-- lang/en/lang.php | 4 + reportwidgets/TrafficOverview.php | 86 ++++++++++++++----- .../trafficoverview/partials/_widget.htm | 23 ----- .../trafficoverview/partials/_widget.php | 24 ++++++ 5 files changed, 102 insertions(+), 58 deletions(-) delete mode 100644 reportwidgets/trafficoverview/partials/_widget.htm create mode 100644 reportwidgets/trafficoverview/partials/_widget.php diff --git a/classes/Analytics.php b/classes/Analytics.php index 26acd5c..8535582 100644 --- a/classes/Analytics.php +++ b/classes/Analytics.php @@ -1,12 +1,9 @@ gapi_key->getContents(), true); $client->setAuthConfig($auth); - $client->addScope(Google_Service_Analytics::ANALYTICS_READONLY); + $client->addScope(AnalyticsData::ANALYTICS_READONLY); if ($client->isAccessTokenExpired()) { - $client->refreshTokenWithAssertion(); + $client->fetchAccessTokenWithAssertion(); } $this->client = $client; - $this->service = new Google_Service_Analytics($client); - $this->viewId = 'ga:'.$settings->profile_id; + $this->service = new AnalyticsData($client); + $this->viewId = 'properties/' . $settings->profile_id; } -} \ No newline at end of file +} diff --git a/lang/en/lang.php b/lang/en/lang.php index 1f62895..0b302dd 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -24,6 +24,10 @@ 'display_description' => 'Display the report description', 'days' => 'Number of days to display data for', 'legend_as_table' => 'Display legend as a table', + 'metrics' => 'Metrics', + 'metric_sessions' => 'Sessions', + 'metric_activeUsers' => 'Users', + 'metric_screenPageViews' => 'Page views', ], 'permissions' => [ 'tab' => 'Google Analytics Plugin', diff --git a/reportwidgets/TrafficOverview.php b/reportwidgets/TrafficOverview.php index dda185f..c92381c 100644 --- a/reportwidgets/TrafficOverview.php +++ b/reportwidgets/TrafficOverview.php @@ -1,9 +1,15 @@ loadData(); - } - catch (Exception $ex) { + } catch (Exception $ex) { $this->vars['error'] = $ex->getMessage(); } @@ -43,7 +48,17 @@ public function defineProperties() 'default' => '30', 'type' => 'string', 'validationPattern' => '^[0-9]+$' - ] + ], + 'metrics' => [ + 'title' => 'winter.googleanalytics::lang.widgets.metrics', + 'default' => ['sessions'], + 'type' => 'set', + 'items' => [ + 'sessions' => Lang::get('winter.googleanalytics::lang.widgets.metric_sessions'), + 'activeUsers' => Lang::get('winter.googleanalytics::lang.widgets.metric_activeUsers'), + 'screenPageViews' => Lang::get('winter.googleanalytics::lang.widgets.metric_screenPageViews'), + ] + ], ]; } @@ -51,17 +66,30 @@ protected function loadData() { $obj = Analytics::instance(); - $days = $this->property('days'); - if (!$days) { - throw new ApplicationException('Invalid days value: '.$days); - } + $days = $this->property('days', 30); + $metrics = $this->property('metrics', ['sessions']); + + // Formulate data request + $request = new RunReportRequest(); + $now = Argon::now()->toImmutable(); + $request->setDimensions([ + new Dimension(['name' => 'date']), + ]); + $request->setMetrics( + array_map(function ($metric) { + return new Metric(['name' => $metric]); + }, $metrics) + ); + $request->setDateRanges([ + new DateRange([ + 'startDate' => $now->subDays($days)->format('Y-m-d'), + 'endDate' => $now->format('Y-m-d') + ]) + ]); - $data = $obj->service->data_ga->get( + $data = $obj->service->properties->runReport( $obj->viewId, - $days.'daysAgo', - 'today', - 'ga:visits', - ['dimensions' => 'ga:date'] + $request, ); $rows = $data->getRows(); @@ -69,16 +97,30 @@ protected function loadData() throw new ApplicationException('No traffic found yet.'); } - $points = []; - foreach ($rows as $row) { - $point = [ - strtotime($row[0])*1000, - $row[1] - ]; + // Set up array of points + $points = array_map(function ($metric) { + return []; + }, $metrics); + + foreach ($rows as $rowIndex => $row) { + foreach (array_keys($metrics) as $index) { + if (!isset($row->getMetricValues()[$index])) { + continue; + } + + $points[$index][$rowIndex] = [ + strtotime($row->getDimensionValues()[0]->getValue()) * 1000, + $row->getMetricValues()[$index]->getValue(), + ]; + } + } - $points[] = $point; + // Sort results by date, since Google seems to return them in a random order + foreach (array_keys($metrics) as $index) { + array_multisort(array_column($points[$index], 0), SORT_ASC, $points[$index]); } - $this->vars['rows'] = str_replace('"', '', substr(substr(json_encode($points), 1), 0, -1)); + $this->vars['metrics'] = $this->property('metrics', ['sessions']); + $this->vars['points'] = $points; } } diff --git a/reportwidgets/trafficoverview/partials/_widget.htm b/reportwidgets/trafficoverview/partials/_widget.htm deleted file mode 100644 index b0aa82e..0000000 --- a/reportwidgets/trafficoverview/partials/_widget.htm +++ /dev/null @@ -1,23 +0,0 @@ -
-

property('title')) ?>

- - -
- - - -
- -

- - -
\ No newline at end of file diff --git a/reportwidgets/trafficoverview/partials/_widget.php b/reportwidgets/trafficoverview/partials/_widget.php new file mode 100644 index 0000000..b4e53c2 --- /dev/null +++ b/reportwidgets/trafficoverview/partials/_widget.php @@ -0,0 +1,24 @@ +
+

property('title')) ?>

+ + +
+ $rows): ?> + " + data-set-label=""> + + +
+ +

+ + +
From 6ae7266bcf51dca111414fe5b34fad324be115cf Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 14:31:30 +0800 Subject: [PATCH 02/13] Use API ordering --- reportwidgets/TrafficOverview.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/reportwidgets/TrafficOverview.php b/reportwidgets/TrafficOverview.php index c92381c..979b1a7 100644 --- a/reportwidgets/TrafficOverview.php +++ b/reportwidgets/TrafficOverview.php @@ -4,12 +4,14 @@ use Exception; use ApplicationException; use Backend\Classes\ReportWidgetBase; -use Winter\GoogleAnalytics\Classes\Analytics; -use Winter\Storm\Argon\Argon; use Google\Service\AnalyticsData\DateRange; use Google\Service\AnalyticsData\Dimension; +use Google\Service\AnalyticsData\DimensionOrderBy; use Google\Service\AnalyticsData\Metric; +use Google\Service\AnalyticsData\OrderBy; use Google\Service\AnalyticsData\RunReportRequest; +use Winter\GoogleAnalytics\Classes\Analytics; +use Winter\Storm\Argon\Argon; /** * Google Analytics traffic overview widget. @@ -64,7 +66,7 @@ public function defineProperties() protected function loadData() { - $obj = Analytics::instance(); + $analytics = Analytics::instance(); $days = $this->property('days', 30); $metrics = $this->property('metrics', ['sessions']); @@ -86,9 +88,17 @@ protected function loadData() 'endDate' => $now->format('Y-m-d') ]) ]); + $request->setOrderBys([ + new OrderBy([ + 'desc' => false, + 'dimension' => new DimensionOrderBy([ + 'dimensionName' => 'date' + ]) + ]) + ]); - $data = $obj->service->properties->runReport( - $obj->viewId, + $data = $analytics->service->properties->runReport( + $analytics->viewId, $request, ); @@ -115,11 +125,6 @@ protected function loadData() } } - // Sort results by date, since Google seems to return them in a random order - foreach (array_keys($metrics) as $index) { - array_multisort(array_column($points[$index], 0), SORT_ASC, $points[$index]); - } - $this->vars['metrics'] = $this->property('metrics', ['sessions']); $this->vars['points'] = $points; } From 68c444d980ec8c1a5829541b63d6593bc2ed4ba3 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 14:31:48 +0800 Subject: [PATCH 03/13] Update Top Pages widget, show users and engagement --- lang/en/lang.php | 2 + reportwidgets/TopPages.php | 66 ++++++++++++++++----- reportwidgets/toppages/partials/_widget.htm | 18 +++--- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/lang/en/lang.php b/lang/en/lang.php index 0b302dd..08ab49a 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -44,8 +44,10 @@ 'settings_desc' => 'Configure Google Analytics API code and tracking options.', 'page_url' => 'Page URL', 'pageviews' => 'Pageviews', + 'users' => 'Users', 'current' => 'Current', 'goal' => 'Goal', + 'engagement' => 'Engagement', ], 'settings' => [ 'project_name' => 'Google API Project name', diff --git a/reportwidgets/TopPages.php b/reportwidgets/TopPages.php index 381ed29..4f21e9a 100644 --- a/reportwidgets/TopPages.php +++ b/reportwidgets/TopPages.php @@ -1,9 +1,17 @@ loadData(); - } - catch (Exception $ex) { + } catch (Exception $ex) { $this->vars['error'] = $ex->getMessage(); } @@ -55,20 +62,49 @@ public function defineProperties() protected function loadData() { - $days = $this->property('days'); - if (!$days) - throw new ApplicationException('Invalid days value: '.$days); + $analytics = Analytics::instance(); - $obj = Analytics::instance(); - $data = $obj->service->data_ga->get($obj->viewId, $days.'daysAgo', 'today', 'ga:pageviews', ['dimensions' => 'ga:pagePath', 'sort' => '-ga:pageviews']); + $days = $this->property('days', 30); + $metrics = $this->property('metrics', ['sessions']); - $rows = $data->getRows() ?: []; - $rows = $this->vars['rows'] = array_slice($rows, 0, $this->property('number')); + // Formulate data request + $request = new RunReportRequest(); + $now = Argon::now()->toImmutable(); + $request->setDimensions([ + new Dimension(['name' => 'pagePath']), + ]); + $request->setMetrics([ + new Metric(['name' => 'screenPageViews']), + new Metric(['name' => 'activeUsers']), + new Metric(['name' => 'engagementRate']), + ]); + $request->setDateRanges([ + new DateRange([ + 'startDate' => $now->subDays($days)->format('Y-m-d'), + 'endDate' => $now->format('Y-m-d') + ]) + ]); + $request->setOrderBys([ + new OrderBy([ + 'desc' => true, + 'metric' => new MetricOrderBy([ + 'metricName' => 'screenPageViews', + ]) + ]), + new OrderBy([ + 'desc' => true, + 'metric' => new MetricOrderBy([ + 'metricName' => 'activeUsers', + ]) + ]), + ]); + $request->setLimit($this->property('number', 5)); - $total = 0; - foreach ($rows as $row) - $total += $row[1]; + $data = $analytics->service->properties->runReport( + $analytics->viewId, + $request, + ); - $this->vars['total'] = $total; + $this->vars['rows'] = $data->getRows() ?: []; } } diff --git a/reportwidgets/toppages/partials/_widget.htm b/reportwidgets/toppages/partials/_widget.htm index 75f8221..6173290 100644 --- a/reportwidgets/toppages/partials/_widget.htm +++ b/reportwidgets/toppages/partials/_widget.htm @@ -8,21 +8,25 @@

property('title')) ?>

- % + + % 0 ? round($row[1]/$total*100, 2) : 0; - $url = $row[0]; + $url = $row->getDimensionValues()[0]->getValue(); + $views = $row->getMetricValues()[0]->getValue(); + $users = $row->getMetricValues()[1]->getValue(); + $engagement = round($row->getMetricValues()[2]->getValue() * 100, 2); ?> - - + + +
-
- +
+
From 0f54b245f00d5b9321fe873339024dfa96a4ef80 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 14:54:18 +0800 Subject: [PATCH 04/13] Minor tweaks --- reportwidgets/TopPages.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/reportwidgets/TopPages.php b/reportwidgets/TopPages.php index 4f21e9a..d85d649 100644 --- a/reportwidgets/TopPages.php +++ b/reportwidgets/TopPages.php @@ -1,11 +1,9 @@ [ 'title' => 'winter.googleanalytics::lang.widgets.days', - 'default' => '7', + 'default' => '30', 'type' => 'string', 'validationPattern' => '^[0-9]+$' ], @@ -65,7 +63,6 @@ protected function loadData() $analytics = Analytics::instance(); $days = $this->property('days', 30); - $metrics = $this->property('metrics', ['sessions']); // Formulate data request $request = new RunReportRequest(); From 7711f979da779b6d298a14110e2b1b986fa3a2c6 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 14:54:50 +0800 Subject: [PATCH 05/13] Update browsers widget, allow device split --- lang/en/lang.php | 2 + reportwidgets/Browsers.php | 81 ++++++++++++++++++--- reportwidgets/browsers/partials/_widget.htm | 2 +- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/lang/en/lang.php b/lang/en/lang.php index 08ab49a..ac498e4 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -28,6 +28,8 @@ 'metric_sessions' => 'Sessions', 'metric_activeUsers' => 'Users', 'metric_screenPageViews' => 'Page views', + 'device_split' => 'Split by device', + 'device_split_description' => 'If ticked, visits will also be split by device type, such as desktop, mobile or tablet.', ], 'permissions' => [ 'tab' => 'Google Analytics Plugin', diff --git a/reportwidgets/Browsers.php b/reportwidgets/Browsers.php index b88d6ff..5eb54af 100644 --- a/reportwidgets/Browsers.php +++ b/reportwidgets/Browsers.php @@ -1,9 +1,17 @@ loadData(); - } - catch (Exception $ex) { + } catch (Exception $ex) { $this->vars['error'] = $ex->getMessage(); } @@ -52,7 +59,7 @@ public function defineProperties() ], 'days' => [ 'title' => 'winter.googleanalytics::lang.widgets.days', - 'default' => '7', + 'default' => '30', 'type' => 'string', 'validationPattern' => '^[0-9]+$' ], @@ -60,18 +67,68 @@ public function defineProperties() 'title' => 'winter.googleanalytics::lang.widgets.display_description', 'type' => 'checkbox', 'default' => 1 - ] + ], + 'deviceSplit' => [ + 'title' => 'winter.googleanalytics::lang.widgets.device_split', + 'description' => 'winter.googleanalytics::lang.widgets.device_split_description', + 'type' => 'checkbox', + 'default' => 0 + ], ]; } protected function loadData() { - $days = $this->property('days'); - if (!$days) - throw new ApplicationException('Invalid days value: '.$days); + $analytics = Analytics::instance(); + + $days = $this->property('days', 30); + + // Formulate data request + $request = new RunReportRequest(); + $now = Argon::now()->toImmutable(); + if (boolval($this->property('deviceSplit', 0)) === true) { + $request->setDimensions([ + new Dimension([ + 'name' => 'browserDevice', + 'dimensionExpression' => new DimensionExpression([ + 'concatenate' => new ConcatenateExpression([ + 'delimiter' => ' - ', + 'dimensionNames' => [ + 'browser', + 'deviceCategory', + ], + ]), + ]), + ]), + ]); + } else { + $request->setDimensions([ + new Dimension(['name' => 'browser']), + ]); + } + $request->setMetrics([ + new Metric(['name' => 'sessions']), + ]); + $request->setDateRanges([ + new DateRange([ + 'startDate' => $now->subDays($days)->format('Y-m-d'), + 'endDate' => $now->format('Y-m-d') + ]) + ]); + $request->setOrderBys([ + new OrderBy([ + 'desc' => true, + 'metric' => new MetricOrderBy([ + 'metricName' => 'sessions', + ]) + ]), + ]); + + $data = $analytics->service->properties->runReport( + $analytics->viewId, + $request, + ); - $obj = Analytics::instance(); - $data = $obj->service->data_ga->get($obj->viewId, $days.'daysAgo', 'today', 'ga:visits', ['dimensions'=>'ga:browser', 'sort'=>'-ga:visits']); - $this->vars['rows'] = $data->getRows(); + $this->vars['rows'] = $data->getRows() ?: []; } } diff --git a/reportwidgets/browsers/partials/_widget.htm b/reportwidgets/browsers/partials/_widget.htm index e0a2691..33fd07e 100644 --- a/reportwidgets/browsers/partials/_widget.htm +++ b/reportwidgets/browsers/partials/_widget.htm @@ -11,7 +11,7 @@

property('title')) ?>

    -
  • +
  • getDimensionValues()[0]->getValue()) ?> getMetricValues()[0]->getValue() ?>
From fb5e408ff734363976c35647bddee8da6a87a364 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 15:15:25 +0800 Subject: [PATCH 06/13] Change widget partials to PHP --- .../browsers/partials/{_widget.htm => _widget.php} | 0 .../toppages/partials/{_widget.htm => _widget.php} | 0 .../trafficgoal/partials/{_widget.htm => _widget.php} | 0 .../trafficsources/partials/{_widget.htm => _widget.php} | 8 +++++--- 4 files changed, 5 insertions(+), 3 deletions(-) rename reportwidgets/browsers/partials/{_widget.htm => _widget.php} (100%) rename reportwidgets/toppages/partials/{_widget.htm => _widget.php} (100%) rename reportwidgets/trafficgoal/partials/{_widget.htm => _widget.php} (100%) rename reportwidgets/trafficsources/partials/{_widget.htm => _widget.php} (76%) diff --git a/reportwidgets/browsers/partials/_widget.htm b/reportwidgets/browsers/partials/_widget.php similarity index 100% rename from reportwidgets/browsers/partials/_widget.htm rename to reportwidgets/browsers/partials/_widget.php diff --git a/reportwidgets/toppages/partials/_widget.htm b/reportwidgets/toppages/partials/_widget.php similarity index 100% rename from reportwidgets/toppages/partials/_widget.htm rename to reportwidgets/toppages/partials/_widget.php diff --git a/reportwidgets/trafficgoal/partials/_widget.htm b/reportwidgets/trafficgoal/partials/_widget.php similarity index 100% rename from reportwidgets/trafficgoal/partials/_widget.htm rename to reportwidgets/trafficgoal/partials/_widget.php diff --git a/reportwidgets/trafficsources/partials/_widget.htm b/reportwidgets/trafficsources/partials/_widget.php similarity index 76% rename from reportwidgets/trafficsources/partials/_widget.htm rename to reportwidgets/trafficsources/partials/_widget.php index c0cc83e..16a2e78 100644 --- a/reportwidgets/trafficsources/partials/_widget.htm +++ b/reportwidgets/trafficsources/partials/_widget.php @@ -9,11 +9,13 @@

property('title')) ?>

" data-control="chart-pie" data-size="property('reportSize') ?>" - data-center-text="" >
    -
  • +
  • + getDimensionValues()[0]->getValue()] ?? $row->getDimensionValues()[0]->getValue()) ?> + getMetricValues()[0]->getValue() ?> +
@@ -24,4 +26,4 @@

property('title')) ?>

property('displayDescription')): ?>

- \ No newline at end of file + From 530d97d51604f7613e8ba219404a2e61a9c03cf7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 15:15:36 +0800 Subject: [PATCH 07/13] Simplify traffic sources widget --- lang/en/lang.php | 7 ++++ reportwidgets/TrafficSources.php | 72 +++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/lang/en/lang.php b/lang/en/lang.php index ac498e4..1d0b15e 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -71,4 +71,11 @@ 'force_ssl' => 'Force SSL', 'force_ssl_comment' => 'Always use SSL to send data to Google', ], + 'mediums' => [ + 'organic' => 'Search engines', + 'cpc' => 'Paid search click-throughs', + 'direct' => 'Direct traffic', + 'referral' => 'External sites', + 'unknown' => 'Unknown', + ], ]; diff --git a/reportwidgets/TrafficSources.php b/reportwidgets/TrafficSources.php index 87b48cf..58a1715 100644 --- a/reportwidgets/TrafficSources.php +++ b/reportwidgets/TrafficSources.php @@ -1,9 +1,16 @@ loadData(); - } - catch (Exception $ex) { + } catch (Exception $ex) { $this->vars['error'] = $ex->getMessage(); } @@ -60,12 +66,6 @@ public function defineProperties() 'type' => 'string', 'validationPattern' => '^[0-9]+$' ], - 'number' => [ - 'title' => 'winter.googleanalytics::lang.widgets.traffic_sources_number', - 'default' => '10', - 'type' => 'string', - 'validationPattern' => '^[0-9]+$' - ], 'displayDescription' => [ 'title' => 'winter.googleanalytics::lang.widgets.display_description', 'type' => 'checkbox', @@ -76,22 +76,46 @@ public function defineProperties() protected function loadData() { - $days = $this->property('days'); - if (!$days) - throw new ApplicationException('Invalid days value: '.$days); + $analytics = Analytics::instance(); - $obj = Analytics::instance(); - $data = $obj->service->data_ga->get( - $obj->viewId, - $days.'daysAgo', - 'today', - 'ga:visits', - ['dimensions' => 'ga:source', 'sort' => '-ga:visits'] - ); + $days = $this->property('days', 30); - $rows = $data->getRows() ?: []; + // Formulate data request + $request = new RunReportRequest(); + $now = Argon::now()->toImmutable(); + $request->setDimensions([ + new Dimension(['name' => 'sessionMedium']), + ]); + $request->setMetrics([ + new Metric(['name' => 'sessions']), + ]); + $request->setDateRanges([ + new DateRange([ + 'startDate' => $now->subDays($days)->format('Y-m-d'), + 'endDate' => $now->format('Y-m-d') + ]) + ]); + $request->setOrderBys([ + new OrderBy([ + 'desc' => true, + 'metric' => new MetricOrderBy([ + 'metricName' => 'sessions', + ]) + ]), + ]); - $this->vars['rows'] = array_slice($rows, 0, $this->property('number')); - $this->vars['total'] = $data->getTotalsForAllResults()['ga:visits']; + $data = $analytics->service->properties->runReport( + $analytics->viewId, + $request, + ); + + $this->vars['rows'] = $data->getRows() ?: []; + $this->vars['mediumMap'] = [ + 'organic' => Lang::get('winter.googleanalytics::lang.mediums.organic'), + 'cpc' => Lang::get('winter.googleanalytics::lang.mediums.cpc'), + '(none)' => Lang::get('winter.googleanalytics::lang.mediums.direct'), + 'referral' => Lang::get('winter.googleanalytics::lang.mediums.referral'), + '(not set)' => Lang::get('winter.googleanalytics::lang.mediums.unknown'), + ]; } } From 7796a5693dc0a53d6f3b5654d794ffaa421848b2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 15:18:53 +0800 Subject: [PATCH 08/13] Tweak description --- lang/en/lang.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/en/lang.php b/lang/en/lang.php index 1d0b15e..9946d14 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -16,7 +16,7 @@ 'traffic_goal_goal_validation' => 'Please specify the traffic goal as an integer value.', 'title_traffic_overview' => 'Traffic overview', 'title_traffic_sources' => 'Traffic Sources', - 'description_traffic_sources' => 'The traffic sources report displays the source of referrals to your website.', + 'description_traffic_sources' => 'The traffic sources report displays the source from where visitors entered the site.', 'traffic_sources_report_size' => 'Chart radius', 'traffic_sources_report_size_validation' => 'Please specify the chart size as an integer value', 'traffic_sources_center' => 'Center the chart', From 28fa29bb54eb5f4e0dfcebb537d5af559c98655a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 3 Nov 2022 15:37:23 +0800 Subject: [PATCH 09/13] Support traffic goal widget, add support for different metrics --- reportwidgets/TrafficGoal.php | 62 +++++++++++++------ .../trafficgoal/partials/_widget.php | 6 +- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/reportwidgets/TrafficGoal.php b/reportwidgets/TrafficGoal.php index 5eb87f2..21d6de2 100644 --- a/reportwidgets/TrafficGoal.php +++ b/reportwidgets/TrafficGoal.php @@ -1,9 +1,13 @@ [ 'title' => 'winter.googleanalytics::lang.widgets.traffic_goal_days', - 'default' => '7', + 'default' => '30', 'type' => 'string', 'validationPattern' => '^[0-9]+$' ], @@ -51,29 +55,49 @@ public function defineProperties() 'type' => 'string', 'validationPattern' => '^[0-9]+$', 'validationMessage' => 'winter.googleanalytics::lang.widgets.traffic_goal_goal_validation' - ] + ], + 'goalMetric' => [ + 'title' => 'winter.googleanalytics::lang.widgets.traffic_goal_metric', + 'description' => 'winter.googleanalytics::lang.widgets.traffic_goal_metric_description', + 'title' => 'winter.googleanalytics::lang.widgets.metrics', + 'default' => 'sessions', + 'type' => 'dropdown', + 'options' => [ + 'sessions' => Lang::get('winter.googleanalytics::lang.widgets.metric_sessions'), + 'activeUsers' => Lang::get('winter.googleanalytics::lang.widgets.metric_activeUsers'), + 'screenPageViews' => Lang::get('winter.googleanalytics::lang.widgets.metric_screenPageViews'), + ], + ], ]; } protected function loadData() { - $days = $this->property('days'); - if (!$days) - throw new ApplicationException('Invalid days value: '.$days); + $analytics = Analytics::instance(); + + $days = $this->property('days', 30); + $goal = $this->property('goal', 100); - $goal = $this->property('goal'); - if (!$goal) - throw new ApplicationException('Invalid goal value: '.$goal); + // Formulate data request + $request = new RunReportRequest(); + $now = Argon::now()->toImmutable(); + $request->setMetrics([ + new Metric(['name' => $this->property('goalMetric', 'sessions')]), + ]); + $request->setDateRanges([ + new DateRange([ + 'startDate' => $now->subDays($days)->format('Y-m-d'), + 'endDate' => $now->format('Y-m-d') + ]) + ]); + $request->setMetricAggregations(['TOTAL']); - $obj = Analytics::instance(); - $data = $obj->service->data_ga->get( - $obj->viewId, - $days.'daysAgo', - 'today', - 'ga:visits' - )->getRows(); + $data = $analytics->service->properties->runReport( + $analytics->viewId, + $request, + ); - $total = $this->vars['total'] = isset($data[0][0]) ? $data[0][0] : 0; - $this->vars['percentage'] = min(round($total/$goal*100), 100); + $this->vars['total'] = $total = $data->getTotals()[0]->getMetricValues()[0]->getValue(); + $this->vars['percentage'] = min(round(($total / $goal) * 100), 100); } } diff --git a/reportwidgets/trafficgoal/partials/_widget.php b/reportwidgets/trafficgoal/partials/_widget.php index 9ce53fe..4d677f5 100644 --- a/reportwidgets/trafficgoal/partials/_widget.php +++ b/reportwidgets/trafficgoal/partials/_widget.php @@ -3,10 +3,10 @@
-

:

-

: property('goal')) ?>

+

:

+

: property('goal'))) ?>

- \ No newline at end of file + From 701a6ab6f9ad4b85bc118e2a5699a3532f130a75 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 4 Nov 2022 11:29:02 +0800 Subject: [PATCH 10/13] Defer widgets loading - load report through AJAX This should prevent the widgets being "blocking" on loading the dashboard. Also added placeholder graphics to give the users a visual indicator that the data will load momentarily. --- assets/css/placeholder.css | 14 ++++++ assets/images/bar-graph-placeholder.svg | 28 +++++++++++ assets/images/goal-placeholder.svg | 12 +++++ assets/images/line-graph-placeholder.svg | 36 ++++++++++++++ assets/images/pie-graph-placeholder.svg | 22 +++++++++ assets/images/table-placeholder.svg | 27 +++++++++++ reportwidgets/Browsers.php | 11 ++++- reportwidgets/TopPages.php | 11 ++++- reportwidgets/TrafficGoal.php | 14 ++++-- reportwidgets/TrafficOverview.php | 11 ++++- reportwidgets/TrafficSources.php | 11 ++++- reportwidgets/browsers/partials/_report.php | 26 ++++++++++ reportwidgets/browsers/partials/_widget.php | 28 +++-------- reportwidgets/toppages/partials/_report.php | 45 ++++++++++++++++++ reportwidgets/toppages/partials/_widget.php | 47 +++---------------- .../trafficgoal/partials/_report.php | 12 +++++ .../trafficgoal/partials/_widget.php | 14 +++--- .../trafficoverview/partials/_report.php | 24 ++++++++++ .../trafficoverview/partials/_widget.php | 26 +++------- .../trafficsources/partials/_report.php | 29 ++++++++++++ .../trafficsources/partials/_widget.php | 31 +++--------- 21 files changed, 356 insertions(+), 123 deletions(-) create mode 100644 assets/css/placeholder.css create mode 100644 assets/images/bar-graph-placeholder.svg create mode 100644 assets/images/goal-placeholder.svg create mode 100644 assets/images/line-graph-placeholder.svg create mode 100644 assets/images/pie-graph-placeholder.svg create mode 100644 assets/images/table-placeholder.svg create mode 100644 reportwidgets/browsers/partials/_report.php create mode 100644 reportwidgets/toppages/partials/_report.php create mode 100644 reportwidgets/trafficgoal/partials/_report.php create mode 100644 reportwidgets/trafficoverview/partials/_report.php create mode 100644 reportwidgets/trafficsources/partials/_report.php diff --git a/assets/css/placeholder.css b/assets/css/placeholder.css new file mode 100644 index 0000000..e634392 --- /dev/null +++ b/assets/css/placeholder.css @@ -0,0 +1,14 @@ +img.placeholder { + width: 100%; + height: 100%; + animation: placeholderFade 750ms infinite linear alternate; +} + +@keyframes placeholderFade { + 0% { + opacity: 0.25; + } + 100% { + opacity: 0.85; + } +} diff --git a/assets/images/bar-graph-placeholder.svg b/assets/images/bar-graph-placeholder.svg new file mode 100644 index 0000000..5fb58f3 --- /dev/null +++ b/assets/images/bar-graph-placeholder.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/goal-placeholder.svg b/assets/images/goal-placeholder.svg new file mode 100644 index 0000000..27a5674 --- /dev/null +++ b/assets/images/goal-placeholder.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/images/line-graph-placeholder.svg b/assets/images/line-graph-placeholder.svg new file mode 100644 index 0000000..ff15a47 --- /dev/null +++ b/assets/images/line-graph-placeholder.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/pie-graph-placeholder.svg b/assets/images/pie-graph-placeholder.svg new file mode 100644 index 0000000..0fbcb5a --- /dev/null +++ b/assets/images/pie-graph-placeholder.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/table-placeholder.svg b/assets/images/table-placeholder.svg new file mode 100644 index 0000000..e5e82b6 --- /dev/null +++ b/assets/images/table-placeholder.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/reportwidgets/Browsers.php b/reportwidgets/Browsers.php index 5eb54af..cfc4fba 100644 --- a/reportwidgets/Browsers.php +++ b/reportwidgets/Browsers.php @@ -25,6 +25,13 @@ class Browsers extends ReportWidgetBase * Renders the widget. */ public function render() + { + $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + + return $this->makePartial('widget'); + } + + public function onLoad() { try { $this->loadData(); @@ -32,7 +39,9 @@ public function render() $this->vars['error'] = $ex->getMessage(); } - return $this->makePartial('widget'); + return [ + '#' . $this->alias => $this->makePartial('report') + ]; } public function defineProperties() diff --git a/reportwidgets/TopPages.php b/reportwidgets/TopPages.php index d85d649..a8ee891 100644 --- a/reportwidgets/TopPages.php +++ b/reportwidgets/TopPages.php @@ -23,6 +23,13 @@ class TopPages extends ReportWidgetBase * Renders the widget. */ public function render() + { + $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + + return $this->makePartial('widget'); + } + + public function onLoad() { try { $this->loadData(); @@ -30,7 +37,9 @@ public function render() $this->vars['error'] = $ex->getMessage(); } - return $this->makePartial('widget'); + return [ + '#' . $this->alias => $this->makePartial('report') + ]; } public function defineProperties() diff --git a/reportwidgets/TrafficGoal.php b/reportwidgets/TrafficGoal.php index 21d6de2..b37ef02 100644 --- a/reportwidgets/TrafficGoal.php +++ b/reportwidgets/TrafficGoal.php @@ -21,15 +21,23 @@ class TrafficGoal extends ReportWidgetBase * Renders the widget. */ public function render() + { + $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + + return $this->makePartial('widget'); + } + + public function onLoad() { try { $this->loadData(); - } - catch (Exception $ex) { + } catch (Exception $ex) { $this->vars['error'] = $ex->getMessage(); } - return $this->makePartial('widget'); + return [ + '#' . $this->alias => $this->makePartial('report') + ]; } public function defineProperties() diff --git a/reportwidgets/TrafficOverview.php b/reportwidgets/TrafficOverview.php index 979b1a7..cef8077 100644 --- a/reportwidgets/TrafficOverview.php +++ b/reportwidgets/TrafficOverview.php @@ -25,6 +25,13 @@ class TrafficOverview extends ReportWidgetBase * Renders the widget. */ public function render() + { + $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + + return $this->makePartial('widget'); + } + + public function onLoad() { try { $this->loadData(); @@ -32,7 +39,9 @@ public function render() $this->vars['error'] = $ex->getMessage(); } - return $this->makePartial('widget'); + return [ + '#' . $this->alias => $this->makePartial('report') + ]; } public function defineProperties() diff --git a/reportwidgets/TrafficSources.php b/reportwidgets/TrafficSources.php index 58a1715..df4fcf6 100644 --- a/reportwidgets/TrafficSources.php +++ b/reportwidgets/TrafficSources.php @@ -24,6 +24,13 @@ class TrafficSources extends ReportWidgetBase * Renders the widget. */ public function render() + { + $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + + return $this->makePartial('widget'); + } + + public function onLoad() { try { $this->loadData(); @@ -31,7 +38,9 @@ public function render() $this->vars['error'] = $ex->getMessage(); } - return $this->makePartial('widget'); + return [ + '#' . $this->alias => $this->makePartial('report') + ]; } public function defineProperties() diff --git a/reportwidgets/browsers/partials/_report.php b/reportwidgets/browsers/partials/_report.php new file mode 100644 index 0000000..33fd07e --- /dev/null +++ b/reportwidgets/browsers/partials/_report.php @@ -0,0 +1,26 @@ +
+

property('title')) ?>

+ + +
+
    + + +
  • getDimensionValues()[0]->getValue()) ?> getMetricValues()[0]->getValue() ?>
  • + + +
+
+ + property('displayDescription')): ?> +

+ + +

+ +
diff --git a/reportwidgets/browsers/partials/_widget.php b/reportwidgets/browsers/partials/_widget.php index 33fd07e..c02054d 100644 --- a/reportwidgets/browsers/partials/_widget.php +++ b/reportwidgets/browsers/partials/_widget.php @@ -1,26 +1,10 @@

property('title')) ?>

- -
-
    - - -
  • getDimensionValues()[0]->getValue()) ?> getMetricValues()[0]->getValue() ?>
  • - - -
-
- - property('displayDescription')): ?> -

- - -

- +
diff --git a/reportwidgets/toppages/partials/_report.php b/reportwidgets/toppages/partials/_report.php new file mode 100644 index 0000000..6173290 --- /dev/null +++ b/reportwidgets/toppages/partials/_report.php @@ -0,0 +1,45 @@ +
+

property('title')) ?>

+ + +
+ + + + + + + + + + + getDimensionValues()[0]->getValue(); + $views = $row->getMetricValues()[0]->getValue(); + $users = $row->getMetricValues()[1]->getValue(); + $engagement = round($row->getMetricValues()[2]->getValue() * 100, 2); + ?> + + + + + + + + + + + + + +
%
+
+
+ +
+
+
+ +

+ +
diff --git a/reportwidgets/toppages/partials/_widget.php b/reportwidgets/toppages/partials/_widget.php index 6173290..6e7531b 100644 --- a/reportwidgets/toppages/partials/_widget.php +++ b/reportwidgets/toppages/partials/_widget.php @@ -1,45 +1,10 @@

property('title')) ?>

- -
- - - - - - - - - - - getDimensionValues()[0]->getValue(); - $views = $row->getMetricValues()[0]->getValue(); - $users = $row->getMetricValues()[1]->getValue(); - $engagement = round($row->getMetricValues()[2]->getValue() * 100, 2); - ?> - - - - - - - - - - - - - -
%
-
-
- -
-
-
- -

- +
diff --git a/reportwidgets/trafficgoal/partials/_report.php b/reportwidgets/trafficgoal/partials/_report.php new file mode 100644 index 0000000..4d677f5 --- /dev/null +++ b/reportwidgets/trafficgoal/partials/_report.php @@ -0,0 +1,12 @@ +
+

property('title')) ?>

+ + +
+

:

+

: property('goal'))) ?>

+
+ +

+ +
diff --git a/reportwidgets/trafficgoal/partials/_widget.php b/reportwidgets/trafficgoal/partials/_widget.php index 4d677f5..aecbfc4 100644 --- a/reportwidgets/trafficgoal/partials/_widget.php +++ b/reportwidgets/trafficgoal/partials/_widget.php @@ -1,12 +1,10 @@

property('title')) ?>

- -
-

:

-

: property('goal'))) ?>

-
- -

- +
diff --git a/reportwidgets/trafficoverview/partials/_report.php b/reportwidgets/trafficoverview/partials/_report.php new file mode 100644 index 0000000..b4e53c2 --- /dev/null +++ b/reportwidgets/trafficoverview/partials/_report.php @@ -0,0 +1,24 @@ +
+

property('title')) ?>

+ + +
+ $rows): ?> + " + data-set-label=""> + + +
+ +

+ + +
diff --git a/reportwidgets/trafficoverview/partials/_widget.php b/reportwidgets/trafficoverview/partials/_widget.php index b4e53c2..b41e61f 100644 --- a/reportwidgets/trafficoverview/partials/_widget.php +++ b/reportwidgets/trafficoverview/partials/_widget.php @@ -1,24 +1,10 @@

property('title')) ?>

- -
- $rows): ?> - " - data-set-label=""> - - -
- -

- - +
diff --git a/reportwidgets/trafficsources/partials/_report.php b/reportwidgets/trafficsources/partials/_report.php new file mode 100644 index 0000000..16a2e78 --- /dev/null +++ b/reportwidgets/trafficsources/partials/_report.php @@ -0,0 +1,29 @@ +
+

property('title')) ?>

+ + +
+
    + +
  • + getDimensionValues()[0]->getValue()] ?? $row->getDimensionValues()[0]->getValue()) ?> + getMetricValues()[0]->getValue() ?> +
  • + +
+
+ +

+ + + property('displayDescription')): ?> +

+ +
diff --git a/reportwidgets/trafficsources/partials/_widget.php b/reportwidgets/trafficsources/partials/_widget.php index 16a2e78..4107f4e 100644 --- a/reportwidgets/trafficsources/partials/_widget.php +++ b/reportwidgets/trafficsources/partials/_widget.php @@ -1,29 +1,10 @@

property('title')) ?>

- -
-
    - -
  • - getDimensionValues()[0]->getValue()] ?? $row->getDimensionValues()[0]->getValue()) ?> - getMetricValues()[0]->getValue() ?> -
  • - -
-
- -

- - - property('displayDescription')): ?> -

- +
From 1b0be835bbe8e01b721cb2678f986ff2ec9daf21 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Nov 2022 14:28:46 +0800 Subject: [PATCH 11/13] Fix location of placeholder asset --- reportwidgets/Browsers.php | 2 +- reportwidgets/TopPages.php | 2 +- reportwidgets/TrafficGoal.php | 2 +- reportwidgets/TrafficOverview.php | 2 +- reportwidgets/TrafficSources.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/reportwidgets/Browsers.php b/reportwidgets/Browsers.php index cfc4fba..931de71 100644 --- a/reportwidgets/Browsers.php +++ b/reportwidgets/Browsers.php @@ -26,7 +26,7 @@ class Browsers extends ReportWidgetBase */ public function render() { - $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + $this->addCss('/plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); return $this->makePartial('widget'); } diff --git a/reportwidgets/TopPages.php b/reportwidgets/TopPages.php index a8ee891..9b03167 100644 --- a/reportwidgets/TopPages.php +++ b/reportwidgets/TopPages.php @@ -24,7 +24,7 @@ class TopPages extends ReportWidgetBase */ public function render() { - $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + $this->addCss('/plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); return $this->makePartial('widget'); } diff --git a/reportwidgets/TrafficGoal.php b/reportwidgets/TrafficGoal.php index b37ef02..7a1f878 100644 --- a/reportwidgets/TrafficGoal.php +++ b/reportwidgets/TrafficGoal.php @@ -22,7 +22,7 @@ class TrafficGoal extends ReportWidgetBase */ public function render() { - $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + $this->addCss('/plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); return $this->makePartial('widget'); } diff --git a/reportwidgets/TrafficOverview.php b/reportwidgets/TrafficOverview.php index cef8077..ddfb081 100644 --- a/reportwidgets/TrafficOverview.php +++ b/reportwidgets/TrafficOverview.php @@ -26,7 +26,7 @@ class TrafficOverview extends ReportWidgetBase */ public function render() { - $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + $this->addCss('/plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); return $this->makePartial('widget'); } diff --git a/reportwidgets/TrafficSources.php b/reportwidgets/TrafficSources.php index df4fcf6..cdfb7b5 100644 --- a/reportwidgets/TrafficSources.php +++ b/reportwidgets/TrafficSources.php @@ -25,7 +25,7 @@ class TrafficSources extends ReportWidgetBase */ public function render() { - $this->addCss('plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); + $this->addCss('/plugins/winter/googleanalytics/assets/css/placeholder.css', 'Winter.GoogleAnalytics'); return $this->makePartial('widget'); } From d58b54e6dddcf232284e9248cf50b6507dbff387 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 30 Nov 2022 11:34:26 +0800 Subject: [PATCH 12/13] Use "Url::asset" in place of "Url::to" --- reportwidgets/browsers/partials/_widget.php | 2 +- reportwidgets/toppages/partials/_widget.php | 2 +- reportwidgets/trafficgoal/partials/_widget.php | 2 +- reportwidgets/trafficoverview/partials/_widget.php | 2 +- reportwidgets/trafficsources/partials/_widget.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/reportwidgets/browsers/partials/_widget.php b/reportwidgets/browsers/partials/_widget.php index c02054d..5c89ba4 100644 --- a/reportwidgets/browsers/partials/_widget.php +++ b/reportwidgets/browsers/partials/_widget.php @@ -2,7 +2,7 @@

property('title')) ?>

property('title')) ?> property('title')) ?> property('title')) ?> property('title')) ?> Date: Wed, 30 Nov 2022 11:34:37 +0800 Subject: [PATCH 13/13] Update readme, add v3.0.0 --- readme.md | 4 ++-- updates/version.yaml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index f049e48..021a008 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ -# Google Analytics V3 integration plugin +# Google Analytics V4 integration plugin This plugin adds Google Analytics tracking and reporting features to the [Winter CMS](https://wintercms.com). -> **Note**: This plugin does not support GAv4. If you are having issues creating an account, use the **Show advanced options** link in the GA Account creation and the **Create a Universal Analytics property** switch. +> **Note**: The v3.x series of this plugin only supports Google Analytics v4 (GA4) as GA3 / UA [are being retired](https://support.google.com/analytics/answer/11583528?hl=en). If you wish to use this plugin for GA3 / UA, you must roll back to v2.0.2 of this plugin. ## Configuration diff --git a/updates/version.yaml b/updates/version.yaml index 1fc64b7..eca79f1 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -19,3 +19,4 @@ - v2.0.0/convert_data.php "2.0.1": "update guzzle dependency to >=6.0" "2.0.2": "allow guzzle dependency 7.0+" +"3.0.0": "!!! This plugin now only supports Google Analytics v4"