From 5de5ebaee0eb9523962d4a571c09ff8b157280e6 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 20 Mar 2017 18:20:34 +0200 Subject: [PATCH] фиксы по гуглу: множественные дименшины, сортировка, лимит, трансформер для графиков --- app/library/App/Controllers/GaController.php | 396 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------- app/library/App/Controllers/UserController.php | 1 - app/library/App/Resources/GaResource.php | 15 ++++++++------- 3 files changed, 342 insertions(+), 70 deletions(-) diff --git a/app/library/App/Controllers/GaController.php b/app/library/App/Controllers/GaController.php index e053fff..7b4a948 100644 --- a/app/library/App/Controllers/GaController.php +++ b/app/library/App/Controllers/GaController.php @@ -10,12 +10,15 @@ namespace App\Controllers; use App\Model\Project; +use Codeception\Exception\ContentNotFound; +use DateTime; use Google_Client; use Google_Service_AnalyticsReporting; use Google_Service_AnalyticsReporting_DateRange; use Google_Service_AnalyticsReporting_Dimension; use Google_Service_AnalyticsReporting_GetReportsRequest; use Google_Service_AnalyticsReporting_Metric; +use Google_Service_AnalyticsReporting_OrderBy; use Google_Service_AnalyticsReporting_ReportRequest; use PhalconRest\Mvc\Controllers\CrudResourceController; @@ -25,6 +28,11 @@ class GaController extends CrudResourceController { const VIEW_ID = '119240817'; const SCOPE = 'https://www.googleapis.com/auth/analytics.readonly'; + /** + * Main action for /ga request. Send it google report api. + * + * @return array + */ public function getAction() { /** user params **/ @@ -38,22 +46,53 @@ class GaController extends CrudResourceController { $get_start_date = $this->request->get('start') ?? '30daysAgo'; $get_end_date = $this->request->get('end') ?? 'today'; $filter_expression = $this->request->get('filter'); - - - if (empty($view_id)) { - $result = []; - $projects = Project::find(['user_id' => $user_id]); - foreach ($projects as $project) { - $view_id = (string)$project->ga_view_id; - if (!empty($view_id)) { - $result[] = $this->sendGaRequest($project->name, $view_id, $get_metrics, $get_dimensions, $get_start_date, $get_end_date, $chart, $filter_expression); + $sort = $this->request->get('sort'); + $sort_type = $this->request->get('sort_type'); + $max_result = $this->request->get('max_result'); + + + /** if empty $_GET["view_id"] send request to all projects in user's model **/ + if (empty($view_id)) { + $result = []; + $projects = Project::find(['user_id' => $user_id]); + foreach ($projects as $project) { + $view_id = (string)$project->ga_view_id; + if (!empty($view_id)) { + $result[] = $this->sendGaRequest( + $project->name, + $view_id, + $get_metrics, + $get_dimensions, + $get_start_date, + $get_end_date, + $chart, + $filter_expression, + $sort, + $sort_type, + $max_result + ); + } } } - } - else { - $project = Project::findFirst(['ga_view_id' => $view_id]); - $result = $this->sendGaRequest($project->name , $view_id, $get_metrics, $get_dimensions, $get_start_date, $get_end_date, $chart, $filter_expression); - } + else { + $project = Project::findFirst([ + "ga_view_id = '$view_id'", + ]); + $result = $this->sendGaRequest( + $project->name, + $view_id, + $get_metrics, + $get_dimensions, + $get_start_date, + $get_end_date, + $chart, + $filter_expression, + $sort, + $sort_type, + $max_result + ); + } + /** --------------------------------------------------------------- **/ return $result; } @@ -69,9 +108,12 @@ class GaController extends CrudResourceController { * @param string $end * @param bool $chart * @param string $filter_expression + * @param string $sort + * @param string $sort_type + * @param int $max_result * @return array */ - public function sendGaRequest($project_name, $view, $get_metrics, $get_dimensions, $start, $end, $chart = false, $filter_expression = null) { + public function sendGaRequest($project_name, $view, $get_metrics, $get_dimensions, $start, $end, $chart = false, $filter_expression = null, $sort = null, $sort_type = 'desc', $max_result = 50000) { putenv('GOOGLE_APPLICATION_CREDENTIALS=/var/www/phalcon/'.self::SECRET_JSON); $client = new Google_Client(); @@ -80,57 +122,89 @@ class GaController extends CrudResourceController { $analytics = new Google_Service_AnalyticsReporting($client); /** Create the DateRange object. **/ - $dateRange = new Google_Service_AnalyticsReporting_DateRange(); - $dateRange->setStartDate($start); - $dateRange->setEndDate($end); + $dateRange = new Google_Service_AnalyticsReporting_DateRange(); + $dateRange->setStartDate($start); + $dateRange->setEndDate($end); + /** ---------------------------- **/ /** Create the Metrics object. **/ - $metrics = []; - $get_metrics = explode(',', $get_metrics); - foreach ($get_metrics as $metric) { - $metrics_obj = new Google_Service_AnalyticsReporting_Metric(); - $metrics_obj->setExpression('ga:'.$metric); - $metrics_obj->setAlias('ga:'.$metric); - $metrics[] = $metrics_obj; - } + $metrics = []; + $get_metrics = explode(',', $get_metrics); + foreach ($get_metrics as $metric) { + $metrics_obj = new Google_Service_AnalyticsReporting_Metric(); + $metrics_obj->setExpression('ga:'.$metric); + $metrics_obj->setAlias('ga:'.$metric); + $metrics[] = $metrics_obj; + } + /** -------------------------- **/ /** Create the Dimensions object. **/ - if (!empty($get_dimensions)) { - $dimensions = []; - $get_dimensions = explode(',', $get_dimensions); - foreach ($get_dimensions as $dimension) { - $dimension_obj = new Google_Service_AnalyticsReporting_Dimension(); - $dimension_obj->setName("ga:".$dimension); - $dimensions[] = $dimension_obj; + if (!empty($get_dimensions)) { + $dimensions = []; + $get_dimensions = explode(',', $get_dimensions); + foreach ($get_dimensions as $dimension) { + $dimension_obj = new Google_Service_AnalyticsReporting_Dimension(); + $dimension_obj->setName("ga:".$dimension); + $dimensions[] = $dimension_obj; + } } - } + /** ----------------------------- **/ /** Create the ReportRequest object. **/ - $request = new Google_Service_AnalyticsReporting_ReportRequest(); - $request->setViewId($view); - $request->setDateRanges($dateRange); - $request->setIncludeEmptyRows(true); - if (!empty($dimensions)) { - $request->setDimensions(array($dimensions)); - } - $request->setMetrics(array($metrics)); - if (!empty($filter_expression)) { - $request->setFiltersExpression("ga:".$filter_expression); - } + $request = new Google_Service_AnalyticsReporting_ReportRequest(); + $request->setViewId($view); + $request->setPageSize($max_result); + $request->setDateRanges($dateRange); + $request->setIncludeEmptyRows(true); + /** Create the Ordering **/ + if (isset($sort)) { + $ordering = new Google_Service_AnalyticsReporting_OrderBy(); + $ordering->setFieldName("ga:".$sort); + $ordering->setOrderType("VALUE"); + $ordering->setSortOrder("DESCENDING"); + if ($sort_type == 'asc') { + $ordering->setSortOrder("ASCENDING"); + } + $request->setOrderBys($ordering); + } + /** --------------- **/ + if (!empty($dimensions)) { + $request->setDimensions(array($dimensions)); + } + $request->setMetrics(array($metrics)); + if (!empty($filter_expression)) { + $request->setFiltersExpression("ga:".$filter_expression); + } + /** compute days in request **/ + $request_date['start'] = new DateTime(date('d.m.Y', strtotime($request->getDateRanges()['startDate']))); + $request_date['end'] = new DateTime(date('d.m.Y', strtotime($request->getDateRanges()['endDate']))); + $request_days = (date_diff($request_date['start'], $request_date['end'])->days)+1; + /** ----------------------- **/ + $request_dim = $request->getDimensions(); + if (count($request_dim[0]) == 2) { + $request_dim = $request_dim[0][1]->name; + } + else { + $request_dim = $request_dim[0][0]->name; + } + $iterations = self::countIterations($request_dim, $request_days); + /** ---------------------------- **/ $body = new Google_Service_AnalyticsReporting_GetReportsRequest(); $body->setReportRequests(array($request)); $response = $analytics->reports->batchGet($body); - /** can be refactored (code below 2 rows) **/ + //can be refactored (code below, 2 rows) $response = $response->toSimpleObject(); $response = $response->reports[0]['data']['rows']; if ($chart) { - $result = self::responseChartTransform($response, $project_name); + //$result = self::responseChartTransform($response, $project_name); + $result = self::responseDataTransform($response, $iterations, $request_dim, $project_name); + $result = self::chartTransform($result); } else { - $result = self::responseDataTransform($response, $project_name); + $result = self::responseDataTransform($response, $iterations, $request_dim, $project_name); } return $result; @@ -141,29 +215,84 @@ class GaController extends CrudResourceController { * Data-transformer for tables. Used by default. * * @param array $response + * @param int $iterations + * @param string $request_dimension * @param string $project_name * @return array */ - public static function responseDataTransform(array $response, $project_name) { + public static function responseDataTransform(array $response, $iterations, $request_dimension, $project_name) { - $result = []; + $result = []; + $int_query = true; + $max_values = 0; foreach ($response as $item) { $metric_val = $item['metrics'][0]['values']; $dimension_val = $item['dimensions'][0]; + $dimension_count = count($item['dimensions']); + + /** remove "0001" from dimension keys **/ + for ($i = 0; $i < $dimension_count; $i++) { + $current_value = $item['dimensions'][$i]; + if (ctype_digit(strval($current_value))) { + $item['dimensions'][$i] = (int)$current_value; + if ($i == 0) { + $dimension_val = (int)$current_value; + } + } + elseif ($i == 1) { + $int_query = false; + } + } + /** --------------------------------- **/ - $result['name'] = $project_name; - if (count($metric_val) > 1) { - for ($i = 0; $i < count($metric_val); $i++) { - $result[$dimension_val][] = $metric_val[$i]; + if ($dimension_count == 2) { + $dimension_val_2 = $item['dimensions'][1]; + if (count($metric_val) > 1) { + for ($i = 0; $i < count($metric_val); $i++) { + $result[$dimension_val][$dimension_val_2][] = (int)$metric_val[$i]; + } + } else { + $result[$dimension_val][$dimension_val_2] = (int)$metric_val[0]; + } + } + else { + if (count($metric_val) > 1) { + for ($i = 0; $i < count($metric_val); $i++) { + $result[$dimension_val][] = (int)$metric_val[$i]; + } + } else { + $result[$dimension_val] = (int)$metric_val[0]; } - } else { - $result[$dimension_val] = $metric_val[0]; } + + $dim_val_count = count($result[$dimension_val]); + if ($dim_val_count > $max_values) $max_values = $dim_val_count; + unset($dim_val_count); + } + $int_query = self::checkDimension($request_dimension); + + /** ------ filling missing data ------ **/ + if ($int_query) { + foreach ($result as $key => $item) { + if (!is_array($item)) break; + $iterations = $iterations ?? $max_values; + for ($i = 0; $i < $iterations; $i++) { + $result[$key][$i] = $item[$i] ?? 0; + } + ksort($result[$key]); + } + } + /** --------------------------------- **/ + + /** ----- add custom fields ------ **/ + $result['name'] = $project_name ?? 'Неизвестный проект'; + /** ------------------------------ **/ + return $result; } @@ -171,6 +300,91 @@ class GaController extends CrudResourceController { /** * Data-transformer for charts, when query string contains "?chart=true" * + * @param array $data + * @return array + */ + public static function chartTransform(array $data) { + $result = []; + foreach ($data as $key => $value) { + if ($key === 'name') { + $result[$key] = $value; + } + else { + if (!is_array($value)) { + $result['data'][] = $value; + } + else { + foreach ($value as $v_key => $v_value) { + $result['data'][$key][$v_key] = $v_value; + } + ksort($result['data'][$key]); + } + } + } + + return $result; + } + + /** + * Deprecated + * + * @param array $data + * @return array + */ + public static function chartTransform1(array $data) { + + $result = []; + $max = 0; + + foreach ($data as $key => $value) { + if ($key == 'name') { + $result[$key] = $value; + continue; + } + + if (!is_array($value)) break; + + /** ------ check array keys for int values --- **/ + $count = count($value); + if ($count > $max) $max = $count; + $int_type = true; + foreach ($value as $inc_key => $inc_value) { + if (!preg_match('/\d+/', $inc_key)) { + $int_type = false; + break; + } + } + /** ------------------------------------------ **/ + + /** rewrites keys like "0001" to integer type **/ + if ($int_type) { + $bad_keys = array_keys($value); + for ($i = 0; $i < $count; $i++) { + $good_key = (int)$bad_keys[$i]; + $result[$key][$good_key] = $value[$bad_keys[$i]] ?? 0; + } + } + /** ------------------------------------------ **/ + + } + + /** ---------- filling missing data ---------- **/ + foreach ($result as $key => $value) { + if ($key == 'name') continue; + for ($i = 0; $i < $max; $i++) { + $result[$key][$i] = (int)$result[$key][$i] ?? 0; + } + ksort($result[$key]); + } + /** ------------------------------------------ **/ + + return $result; + + } + + /** + * Deprecated + * * @param array $response * @param string $project_name * @return array @@ -181,15 +395,30 @@ class GaController extends CrudResourceController { foreach ($response as $item) { - $metric_val = $item['metrics'][0]['values']; $result['name'] = $project_name; - if (count($metric_val) > 1) { - for ($i = 0; $i < count($metric_val); $i++) { - $result['data'][] = (int)$metric_val[$i]; + $metric_val = $item['metrics'][0]['values']; + $dimension_val = $item['dimensions'][0]; + $dimension_count = count($item['dimensions']); + + if ($dimension_count == 2) { + $dimension_val_2 = $item['dimensions'][1]; + if (count($metric_val) > 1) { + for ($i = 0; $i < count($metric_val); $i++) { + $result['data'][$dimension_val][] = (int)$metric_val[$i]; + } + } else { + $result['data'][$dimension_val][] = (int)$metric_val[0]; + } + } + else { + if (count($metric_val) > 1) { + for ($i = 0; $i < count($metric_val); $i++) { + $result['data'][] = (int)$metric_val[$i]; + } + } else { + $result['data'][] = (int)$metric_val[0]; } - } else { - $result['data'][] = (int)$metric_val[0]; } } @@ -197,6 +426,49 @@ class GaController extends CrudResourceController { } + /** + * Compute count of fields + * + * @param string $request_dim + * @param int $request_days + * @return int + * @throws ContentNotFound if functions params is empty + */ + public static function countIterations($request_dim, $request_days) { + + if (empty($request_dim)) throw new ContentNotFound('PHP: request_dim not found'); + if (empty($request_days)) throw new ContentNotFound('PHP: request_days not found'); + switch ($request_dim) { + case 'ga:nthDay': + $iterations = $request_days*1; + break; + case 'ga:nthHour': + $iterations = $request_days*24; + break; + case 'ga:nthMinute': + $iterations = $request_days*24*60; + break; + default: + $iterations = null; + } + + return $iterations; + + } + + /** + * Boolean indicator for chart transformer + * + * @param string $dimension + * @return bool + */ + public static function checkDimension($dimension) { + + $nthArray = ['ga:nthMonth', 'ga:nthWeek', 'ga:nthDay', 'ga:nthMinute', 'ga:nthHour']; + + return in_array($dimension, $nthArray) ? true : false; + + } /** * without usage, from google docs. diff --git a/app/library/App/Controllers/UserController.php b/app/library/App/Controllers/UserController.php index 1bd3851..322f08a 100755 --- a/app/library/App/Controllers/UserController.php +++ b/app/library/App/Controllers/UserController.php @@ -2,7 +2,6 @@ namespace App\Controllers; -use App\Model\Project; use App\Model\User; use PhalconApi\Constants\ErrorCodes; use PhalconApi\Exception; diff --git a/app/library/App/Resources/GaResource.php b/app/library/App/Resources/GaResource.php index ab25b2f..92976ab 100644 --- a/app/library/App/Resources/GaResource.php +++ b/app/library/App/Resources/GaResource.php @@ -21,7 +21,6 @@ class GaResource extends ApiResource { $this ->name('Google Analytics') ->expectsJsonData() - //->transformer(ModelTransformer::class) ->itemKey('ga') ->collectionKey('ga') ->deny(AclRoles::UNAUTHORIZED) @@ -49,13 +48,15 @@ class GaResource extends ApiResource { 'end' => 'ГГГГ-ММ-ДД/NdaysAgo, где N – целое положительное число(дата конца загрузки данных)' ], 'optional params' => [ - 'user_id' => 'integer(id пользователя в системе)', - 'view_id' => 'integer(id представления проэкта с гугл аналитики)', - 'chart' => 'boolean(Задает структуру возвращаемых данных(true для графиков))', - 'filter' => 'expression(https://developers.google.com/analytics/devguides/reporting/core/v3/reference#filters)', + 'user_id' => 'integer(id пользователя в системе)', + 'view_id' => 'integer(id представления проэкта с гугл аналитики)', + 'chart' => 'boolean(Задает структуру возвращаемых данных(true для графиков))', + 'filter' => 'expression(https://developers.google.com/analytics/devguides/reporting/core/v3/reference#filters)', + 'sort' => 'string(параметр сортировки, metric либо dimension)', + 'sort_type' => 'enum(ans, desc)', + 'max_result' => 'integer(максимальное число строк в результате, по умолчанию 50,000)', ] - ], - 'summary' => '/ga?view_id=119240817&metric=users,sessions&dimension=source,browser&filter=browser=~^Firef' + ] ]) ) ; -- libgit2 0.21.4