331
common/Admin/Analytics/Actions/BuildGoogleAnalyticsReport.php
Executable file
331
common/Admin/Analytics/Actions/BuildGoogleAnalyticsReport.php
Executable file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Admin\Analytics\Actions;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Common\Database\Metrics\MetricDateRange;
|
||||
use Common\Database\Metrics\Traits\GeneratesTrendResults;
|
||||
use Google\Analytics\Data\V1beta\BetaAnalyticsDataClient;
|
||||
use Google\Analytics\Data\V1beta\DateRange;
|
||||
use Google\Analytics\Data\V1beta\Dimension;
|
||||
use Google\Analytics\Data\V1beta\Metric;
|
||||
use Google\Analytics\Data\V1beta\OrderBy;
|
||||
use Google\Analytics\Data\V1beta\OrderBy\DimensionOrderBy;
|
||||
use Google\Analytics\Data\V1beta\OrderBy\MetricOrderBy;
|
||||
use Google\Analytics\Data\V1beta\Row;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BuildGoogleAnalyticsReport implements BuildAnalyticsReport
|
||||
{
|
||||
use GeneratesTrendResults;
|
||||
|
||||
protected BetaAnalyticsDataClient $client;
|
||||
protected MetricDateRange $dateRange;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new BetaAnalyticsDataClient([
|
||||
'credentials' => storage_path('laravel-analytics/certificate.json'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function execute(array $params = []): array
|
||||
{
|
||||
$this->dateRange = $params['dateRange'] ?? new MetricDateRange();
|
||||
|
||||
return $this->buildReport();
|
||||
}
|
||||
|
||||
protected function buildReport(): array
|
||||
{
|
||||
return [
|
||||
'pageViews' => [
|
||||
'datasets' => $this->buildPageViewsMetric(),
|
||||
'granularity' => $this->dateRange->granularity,
|
||||
],
|
||||
'browsers' => [
|
||||
'granularity' => $this->dateRange->granularity,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => __('Sessions'),
|
||||
'data' => $this->buildTopBrowsersMetric(),
|
||||
],
|
||||
],
|
||||
],
|
||||
'locations' => [
|
||||
'granularity' => $this->dateRange->granularity,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => __('Sessions'),
|
||||
'data' => $this->buildTopLocationsMetric(),
|
||||
],
|
||||
],
|
||||
],
|
||||
'devices' => [
|
||||
'granularity' => $this->dateRange->granularity,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => __('Sessions'),
|
||||
'data' => $this->buildTopDevicesMetric(),
|
||||
],
|
||||
],
|
||||
],
|
||||
'platforms' => [
|
||||
'granularity' => $this->dateRange->granularity,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => __('Sessions'),
|
||||
'data' => $this->buildTopPlatformsMetric(),
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function buildTopBrowsersMetric(int $maxResults = 10): Collection
|
||||
{
|
||||
$rows = $this->performQuery($this->dateRange, [
|
||||
'dimensions' => ['browser'],
|
||||
'metrics' => ['sessions'],
|
||||
'sort' => ['direction' => 'desc', 'metric' => 'sessions'],
|
||||
]);
|
||||
|
||||
$topBrowsers = $rows->map(function (Row $row) {
|
||||
return [
|
||||
'label' => $row->getDimensionValues()[0]->getValue(),
|
||||
'value' => $row->getMetricValues()[0]->getValue(),
|
||||
];
|
||||
});
|
||||
|
||||
if ($topBrowsers->count() <= $maxResults) {
|
||||
return $topBrowsers;
|
||||
}
|
||||
|
||||
return $topBrowsers->take($maxResults - 1)->push([
|
||||
'label' => __('Others'),
|
||||
'value' => $topBrowsers->splice($maxResults - 1)->sum('value'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildTopLocationsMetric(): Collection
|
||||
{
|
||||
$maxResults = 6;
|
||||
$rows = $this->performQuery($this->dateRange, [
|
||||
'metrics' => ['sessions'],
|
||||
'dimensions' => ['country'],
|
||||
'sort' => [
|
||||
'direction' => 'desc',
|
||||
'metric' => 'sessions',
|
||||
],
|
||||
]);
|
||||
|
||||
$locations = $rows->map(function (Row $row) {
|
||||
return [
|
||||
'label' => $row->getDimensionValues()[0]->getValue(),
|
||||
'value' => $row->getMetricValues()[0]->getValue(),
|
||||
];
|
||||
});
|
||||
$total = $locations->sum('value');
|
||||
|
||||
$locations = $locations->map(function ($location) use ($total) {
|
||||
$location['percentage'] = round(
|
||||
(100 * $location['value']) / $total,
|
||||
1,
|
||||
);
|
||||
return $location;
|
||||
});
|
||||
|
||||
if ($locations->count() <= $maxResults) {
|
||||
return $locations;
|
||||
}
|
||||
|
||||
return $locations->take($maxResults - 1)->push([
|
||||
'label' => __('Other'),
|
||||
'value' => $locations->splice($maxResults - 1)->sum('value'),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function buildTopDevicesMetric(int $maxResults = 10): Collection
|
||||
{
|
||||
$rows = $this->performQuery($this->dateRange, [
|
||||
'dimensions' => ['deviceCategory'],
|
||||
'metrics' => ['sessions'],
|
||||
'sort' => ['direction' => 'desc', 'metric' => 'sessions'],
|
||||
]);
|
||||
|
||||
$devices = $rows->map(function (Row $row) {
|
||||
return [
|
||||
'label' => __(
|
||||
ucfirst($row->getDimensionValues()[0]->getValue()),
|
||||
),
|
||||
'value' => $row->getMetricValues()[0]->getValue(),
|
||||
];
|
||||
});
|
||||
|
||||
if ($devices->count() <= $maxResults) {
|
||||
return $devices;
|
||||
}
|
||||
|
||||
return $devices->take($maxResults - 1)->push([
|
||||
'label' => __('Others'),
|
||||
'value' => $devices->splice($maxResults - 1)->sum('value'),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function buildTopPlatformsMetric(int $maxResults = 10): Collection
|
||||
{
|
||||
$rows = $this->performQuery($this->dateRange, [
|
||||
'dimensions' => ['operatingSystem'],
|
||||
'metrics' => ['sessions'],
|
||||
'sort' => ['direction' => 'desc', 'metric' => 'sessions'],
|
||||
]);
|
||||
|
||||
$platforms = $rows->map(
|
||||
fn(Row $row) => [
|
||||
'label' => $row->getDimensionValues()[0]->getValue(),
|
||||
'value' => $row->getMetricValues()[0]->getValue(),
|
||||
],
|
||||
);
|
||||
|
||||
if ($platforms->count() <= $maxResults) {
|
||||
return $platforms;
|
||||
}
|
||||
|
||||
return $platforms->take($maxResults - 1)->push([
|
||||
'label' => __('Others'),
|
||||
'value' => $platforms->splice($maxResults - 1)->sum('value'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildPageViewsMetric(): array
|
||||
{
|
||||
$this->getPageViews($this->dateRange->getPreviousPeriod());
|
||||
return [
|
||||
[
|
||||
'label' => __('Current period'),
|
||||
'data' => $this->getPageViews($this->dateRange),
|
||||
],
|
||||
[
|
||||
'label' => __('Previous period'),
|
||||
'data' => $this->getPageViews(
|
||||
$this->dateRange->getPreviousPeriod(),
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getPageViews(MetricDateRange $dateRange): Collection
|
||||
{
|
||||
$format = $this->dateRange->getGroupingFormat();
|
||||
|
||||
$results = $this->fetchVisitorsAndPageViews($dateRange)
|
||||
// Google Analytics will return views at one minute granularity always.
|
||||
// Group these by date format based on selected granularity instead.
|
||||
// e.g. "day" granularity will equal "YYYY-MM-DD" => ["value" => 1]
|
||||
->groupBy(fn($item) => $item['date']->format($format))
|
||||
// reduce each granularity group to a single value
|
||||
->map(function (Collection $dateGroup) {
|
||||
return $dateGroup->reduce(
|
||||
function ($result, $item) {
|
||||
$result['value'] += $item['value'];
|
||||
return $result;
|
||||
},
|
||||
[
|
||||
'date' => $dateGroup[0]['date'],
|
||||
'value' => 0,
|
||||
],
|
||||
);
|
||||
})
|
||||
->mapWithKeys(
|
||||
fn($item) => [
|
||||
$item['date']->format($format) => $this->formatTrendResult(
|
||||
$dateRange->granularity,
|
||||
$item['date'],
|
||||
$item['value'],
|
||||
),
|
||||
],
|
||||
)
|
||||
->sortKeys()
|
||||
->all();
|
||||
|
||||
$mergedResults = array_replace(
|
||||
$this->getAllPossibleDateResults($dateRange),
|
||||
$results,
|
||||
);
|
||||
|
||||
return collect($mergedResults)->values();
|
||||
}
|
||||
|
||||
protected function fetchVisitorsAndPageViews(
|
||||
MetricDateRange $dateRange,
|
||||
): Collection {
|
||||
$rows = $this->performQuery($dateRange, [
|
||||
'dimensions' => ['dateHourMinute', 'pageTitle'], // YYYYMMDDHHMM
|
||||
'metrics' => ['totalUsers', 'screenPageViews'],
|
||||
]);
|
||||
|
||||
return $rows->map(function (Row $dateRow) {
|
||||
return [
|
||||
'date' => Carbon::createFromFormat(
|
||||
'YmdHi',
|
||||
$dateRow->getDimensionValues()[0]->getValue(),
|
||||
),
|
||||
'pageTitle' => $dateRow->getDimensionValues()[1]->getValue(),
|
||||
'visitors' => (int) $dateRow->getMetricValues()[0]->getValue(),
|
||||
'value' => (int) $dateRow->getMetricValues()[1]->getValue(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
protected function performQuery(
|
||||
MetricDateRange $dateRange,
|
||||
array $options,
|
||||
): Collection {
|
||||
$orderBy = null;
|
||||
if (isset($options['sort'])) {
|
||||
$sortOptions = [];
|
||||
if (Arr::get($options, 'sort.direction') == 'desc') {
|
||||
$sortOptions['desc'] = true;
|
||||
}
|
||||
|
||||
if ($metricName = Arr::get($options, 'sort.metric')) {
|
||||
$sortOptions['metric'] = (new MetricOrderBy())->setMetricName(
|
||||
$metricName,
|
||||
);
|
||||
}
|
||||
|
||||
if ($dimensionName = Arr::get($options, 'sort.dimensions')) {
|
||||
$sortOptions[
|
||||
'dimension'
|
||||
] = (new DimensionOrderBy())->setDimensionName($dimensionName);
|
||||
}
|
||||
|
||||
$orderBy = new OrderBy($sortOptions);
|
||||
}
|
||||
|
||||
$propertyId = config('services.google.analytics_property_id');
|
||||
$response = $this->client->runReport([
|
||||
'property' => "properties/$propertyId",
|
||||
'dateRanges' => [$this->getGoogleDateRange($dateRange)],
|
||||
'dimensions' => array_map(
|
||||
fn($name) => new Dimension(['name' => $name]),
|
||||
$options['dimensions'],
|
||||
),
|
||||
'metrics' => array_map(
|
||||
fn($name) => new Metric(['name' => $name]),
|
||||
$options['metrics'],
|
||||
),
|
||||
'orderBys' => $orderBy ? [$orderBy] : null,
|
||||
]);
|
||||
|
||||
return collect($response->getRows() ?: []);
|
||||
}
|
||||
|
||||
protected function getGoogleDateRange(MetricDateRange $range): DateRange
|
||||
{
|
||||
return (new DateRange())
|
||||
->setStartDate($range->start->toDateString())
|
||||
->setEndDate($range->end->toDateString());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user