Commit 5de5ebaee0eb9523962d4a571c09ff8b157280e6

Authored by Alex Savenko
1 parent c6c9c77e

фиксы по гуглу: множественные дименшины, сортировка, лимит, трансформер для графиков

app/library/App/Controllers/GaController.php
... ... @@ -10,12 +10,15 @@ namespace App\Controllers;
10 10  
11 11  
12 12 use App\Model\Project;
  13 +use Codeception\Exception\ContentNotFound;
  14 +use DateTime;
13 15 use Google_Client;
14 16 use Google_Service_AnalyticsReporting;
15 17 use Google_Service_AnalyticsReporting_DateRange;
16 18 use Google_Service_AnalyticsReporting_Dimension;
17 19 use Google_Service_AnalyticsReporting_GetReportsRequest;
18 20 use Google_Service_AnalyticsReporting_Metric;
  21 +use Google_Service_AnalyticsReporting_OrderBy;
19 22 use Google_Service_AnalyticsReporting_ReportRequest;
20 23 use PhalconRest\Mvc\Controllers\CrudResourceController;
21 24  
... ... @@ -25,6 +28,11 @@ class GaController extends CrudResourceController {
25 28 const VIEW_ID = '119240817';
26 29 const SCOPE = 'https://www.googleapis.com/auth/analytics.readonly';
27 30  
  31 + /**
  32 + * Main action for /ga request. Send it google report api.
  33 + *
  34 + * @return array
  35 + */
28 36 public function getAction() {
29 37  
30 38 /** user params **/
... ... @@ -38,22 +46,53 @@ class GaController extends CrudResourceController {
38 46 $get_start_date = $this->request->get('start') ?? '30daysAgo';
39 47 $get_end_date = $this->request->get('end') ?? 'today';
40 48 $filter_expression = $this->request->get('filter');
41   -
42   -
43   - if (empty($view_id)) {
44   - $result = [];
45   - $projects = Project::find(['user_id' => $user_id]);
46   - foreach ($projects as $project) {
47   - $view_id = (string)$project->ga_view_id;
48   - if (!empty($view_id)) {
49   - $result[] = $this->sendGaRequest($project->name, $view_id, $get_metrics, $get_dimensions, $get_start_date, $get_end_date, $chart, $filter_expression);
  49 + $sort = $this->request->get('sort');
  50 + $sort_type = $this->request->get('sort_type');
  51 + $max_result = $this->request->get('max_result');
  52 +
  53 +
  54 + /** if empty $_GET["view_id"] send request to all projects in user's model **/
  55 + if (empty($view_id)) {
  56 + $result = [];
  57 + $projects = Project::find(['user_id' => $user_id]);
  58 + foreach ($projects as $project) {
  59 + $view_id = (string)$project->ga_view_id;
  60 + if (!empty($view_id)) {
  61 + $result[] = $this->sendGaRequest(
  62 + $project->name,
  63 + $view_id,
  64 + $get_metrics,
  65 + $get_dimensions,
  66 + $get_start_date,
  67 + $get_end_date,
  68 + $chart,
  69 + $filter_expression,
  70 + $sort,
  71 + $sort_type,
  72 + $max_result
  73 + );
  74 + }
50 75 }
51 76 }
52   - }
53   - else {
54   - $project = Project::findFirst(['ga_view_id' => $view_id]);
55   - $result = $this->sendGaRequest($project->name , $view_id, $get_metrics, $get_dimensions, $get_start_date, $get_end_date, $chart, $filter_expression);
56   - }
  77 + else {
  78 + $project = Project::findFirst([
  79 + "ga_view_id = '$view_id'",
  80 + ]);
  81 + $result = $this->sendGaRequest(
  82 + $project->name,
  83 + $view_id,
  84 + $get_metrics,
  85 + $get_dimensions,
  86 + $get_start_date,
  87 + $get_end_date,
  88 + $chart,
  89 + $filter_expression,
  90 + $sort,
  91 + $sort_type,
  92 + $max_result
  93 + );
  94 + }
  95 + /** --------------------------------------------------------------- **/
57 96 return $result;
58 97  
59 98 }
... ... @@ -69,9 +108,12 @@ class GaController extends CrudResourceController {
69 108 * @param string $end
70 109 * @param bool $chart
71 110 * @param string $filter_expression
  111 + * @param string $sort
  112 + * @param string $sort_type
  113 + * @param int $max_result
72 114 * @return array
73 115 */
74   - public function sendGaRequest($project_name, $view, $get_metrics, $get_dimensions, $start, $end, $chart = false, $filter_expression = null) {
  116 + 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) {
75 117  
76 118 putenv('GOOGLE_APPLICATION_CREDENTIALS=/var/www/phalcon/'.self::SECRET_JSON);
77 119 $client = new Google_Client();
... ... @@ -80,57 +122,89 @@ class GaController extends CrudResourceController {
80 122 $analytics = new Google_Service_AnalyticsReporting($client);
81 123  
82 124 /** Create the DateRange object. **/
83   - $dateRange = new Google_Service_AnalyticsReporting_DateRange();
84   - $dateRange->setStartDate($start);
85   - $dateRange->setEndDate($end);
  125 + $dateRange = new Google_Service_AnalyticsReporting_DateRange();
  126 + $dateRange->setStartDate($start);
  127 + $dateRange->setEndDate($end);
  128 + /** ---------------------------- **/
86 129  
87 130 /** Create the Metrics object. **/
88   - $metrics = [];
89   - $get_metrics = explode(',', $get_metrics);
90   - foreach ($get_metrics as $metric) {
91   - $metrics_obj = new Google_Service_AnalyticsReporting_Metric();
92   - $metrics_obj->setExpression('ga:'.$metric);
93   - $metrics_obj->setAlias('ga:'.$metric);
94   - $metrics[] = $metrics_obj;
95   - }
  131 + $metrics = [];
  132 + $get_metrics = explode(',', $get_metrics);
  133 + foreach ($get_metrics as $metric) {
  134 + $metrics_obj = new Google_Service_AnalyticsReporting_Metric();
  135 + $metrics_obj->setExpression('ga:'.$metric);
  136 + $metrics_obj->setAlias('ga:'.$metric);
  137 + $metrics[] = $metrics_obj;
  138 + }
  139 + /** -------------------------- **/
96 140  
97 141 /** Create the Dimensions object. **/
98   - if (!empty($get_dimensions)) {
99   - $dimensions = [];
100   - $get_dimensions = explode(',', $get_dimensions);
101   - foreach ($get_dimensions as $dimension) {
102   - $dimension_obj = new Google_Service_AnalyticsReporting_Dimension();
103   - $dimension_obj->setName("ga:".$dimension);
104   - $dimensions[] = $dimension_obj;
  142 + if (!empty($get_dimensions)) {
  143 + $dimensions = [];
  144 + $get_dimensions = explode(',', $get_dimensions);
  145 + foreach ($get_dimensions as $dimension) {
  146 + $dimension_obj = new Google_Service_AnalyticsReporting_Dimension();
  147 + $dimension_obj->setName("ga:".$dimension);
  148 + $dimensions[] = $dimension_obj;
  149 + }
105 150 }
106   - }
  151 + /** ----------------------------- **/
107 152  
108 153 /** Create the ReportRequest object. **/
109   - $request = new Google_Service_AnalyticsReporting_ReportRequest();
110   - $request->setViewId($view);
111   - $request->setDateRanges($dateRange);
112   - $request->setIncludeEmptyRows(true);
113   - if (!empty($dimensions)) {
114   - $request->setDimensions(array($dimensions));
115   - }
116   - $request->setMetrics(array($metrics));
117   - if (!empty($filter_expression)) {
118   - $request->setFiltersExpression("ga:".$filter_expression);
119   - }
  154 + $request = new Google_Service_AnalyticsReporting_ReportRequest();
  155 + $request->setViewId($view);
  156 + $request->setPageSize($max_result);
  157 + $request->setDateRanges($dateRange);
  158 + $request->setIncludeEmptyRows(true);
  159 + /** Create the Ordering **/
  160 + if (isset($sort)) {
  161 + $ordering = new Google_Service_AnalyticsReporting_OrderBy();
  162 + $ordering->setFieldName("ga:".$sort);
  163 + $ordering->setOrderType("VALUE");
  164 + $ordering->setSortOrder("DESCENDING");
  165 + if ($sort_type == 'asc') {
  166 + $ordering->setSortOrder("ASCENDING");
  167 + }
  168 + $request->setOrderBys($ordering);
  169 + }
  170 + /** --------------- **/
  171 + if (!empty($dimensions)) {
  172 + $request->setDimensions(array($dimensions));
  173 + }
  174 + $request->setMetrics(array($metrics));
  175 + if (!empty($filter_expression)) {
  176 + $request->setFiltersExpression("ga:".$filter_expression);
  177 + }
  178 + /** compute days in request **/
  179 + $request_date['start'] = new DateTime(date('d.m.Y', strtotime($request->getDateRanges()['startDate'])));
  180 + $request_date['end'] = new DateTime(date('d.m.Y', strtotime($request->getDateRanges()['endDate'])));
  181 + $request_days = (date_diff($request_date['start'], $request_date['end'])->days)+1;
  182 + /** ----------------------- **/
  183 + $request_dim = $request->getDimensions();
  184 + if (count($request_dim[0]) == 2) {
  185 + $request_dim = $request_dim[0][1]->name;
  186 + }
  187 + else {
  188 + $request_dim = $request_dim[0][0]->name;
  189 + }
  190 + $iterations = self::countIterations($request_dim, $request_days);
  191 + /** ---------------------------- **/
120 192  
121 193 $body = new Google_Service_AnalyticsReporting_GetReportsRequest();
122 194 $body->setReportRequests(array($request));
123 195  
124 196 $response = $analytics->reports->batchGet($body);
125 197  
126   - /** can be refactored (code below 2 rows) **/
  198 + //can be refactored (code below, 2 rows)
127 199 $response = $response->toSimpleObject();
128 200 $response = $response->reports[0]['data']['rows'];
129 201  
130 202 if ($chart) {
131   - $result = self::responseChartTransform($response, $project_name);
  203 + //$result = self::responseChartTransform($response, $project_name);
  204 + $result = self::responseDataTransform($response, $iterations, $request_dim, $project_name);
  205 + $result = self::chartTransform($result);
132 206 } else {
133   - $result = self::responseDataTransform($response, $project_name);
  207 + $result = self::responseDataTransform($response, $iterations, $request_dim, $project_name);
134 208 }
135 209  
136 210 return $result;
... ... @@ -141,29 +215,84 @@ class GaController extends CrudResourceController {
141 215 * Data-transformer for tables. Used by default.
142 216 *
143 217 * @param array $response
  218 + * @param int $iterations
  219 + * @param string $request_dimension
144 220 * @param string $project_name
145 221 * @return array
146 222 */
147   - public static function responseDataTransform(array $response, $project_name) {
  223 + public static function responseDataTransform(array $response, $iterations, $request_dimension, $project_name) {
148 224  
149   - $result = [];
  225 + $result = [];
  226 + $int_query = true;
  227 + $max_values = 0;
150 228  
151 229 foreach ($response as $item) {
152 230  
153 231 $metric_val = $item['metrics'][0]['values'];
154 232 $dimension_val = $item['dimensions'][0];
  233 + $dimension_count = count($item['dimensions']);
  234 +
  235 + /** remove "0001" from dimension keys **/
  236 + for ($i = 0; $i < $dimension_count; $i++) {
  237 + $current_value = $item['dimensions'][$i];
  238 + if (ctype_digit(strval($current_value))) {
  239 + $item['dimensions'][$i] = (int)$current_value;
  240 + if ($i == 0) {
  241 + $dimension_val = (int)$current_value;
  242 + }
  243 + }
  244 + elseif ($i == 1) {
  245 + $int_query = false;
  246 + }
  247 + }
  248 + /** --------------------------------- **/
155 249  
156   - $result['name'] = $project_name;
157 250  
158   - if (count($metric_val) > 1) {
159   - for ($i = 0; $i < count($metric_val); $i++) {
160   - $result[$dimension_val][] = $metric_val[$i];
  251 + if ($dimension_count == 2) {
  252 + $dimension_val_2 = $item['dimensions'][1];
  253 + if (count($metric_val) > 1) {
  254 + for ($i = 0; $i < count($metric_val); $i++) {
  255 + $result[$dimension_val][$dimension_val_2][] = (int)$metric_val[$i];
  256 + }
  257 + } else {
  258 + $result[$dimension_val][$dimension_val_2] = (int)$metric_val[0];
  259 + }
  260 + }
  261 + else {
  262 + if (count($metric_val) > 1) {
  263 + for ($i = 0; $i < count($metric_val); $i++) {
  264 + $result[$dimension_val][] = (int)$metric_val[$i];
  265 + }
  266 + } else {
  267 + $result[$dimension_val] = (int)$metric_val[0];
161 268 }
162   - } else {
163   - $result[$dimension_val] = $metric_val[0];
164 269 }
  270 +
  271 + $dim_val_count = count($result[$dimension_val]);
  272 + if ($dim_val_count > $max_values) $max_values = $dim_val_count;
  273 + unset($dim_val_count);
  274 +
165 275 }
166 276  
  277 + $int_query = self::checkDimension($request_dimension);
  278 +
  279 + /** ------ filling missing data ------ **/
  280 + if ($int_query) {
  281 + foreach ($result as $key => $item) {
  282 + if (!is_array($item)) break;
  283 + $iterations = $iterations ?? $max_values;
  284 + for ($i = 0; $i < $iterations; $i++) {
  285 + $result[$key][$i] = $item[$i] ?? 0;
  286 + }
  287 + ksort($result[$key]);
  288 + }
  289 + }
  290 + /** --------------------------------- **/
  291 +
  292 + /** ----- add custom fields ------ **/
  293 + $result['name'] = $project_name ?? 'Неизвестный проект';
  294 + /** ------------------------------ **/
  295 +
167 296 return $result;
168 297  
169 298 }
... ... @@ -171,6 +300,91 @@ class GaController extends CrudResourceController {
171 300 /**
172 301 * Data-transformer for charts, when query string contains "?chart=true"
173 302 *
  303 + * @param array $data
  304 + * @return array
  305 + */
  306 + public static function chartTransform(array $data) {
  307 + $result = [];
  308 + foreach ($data as $key => $value) {
  309 + if ($key === 'name') {
  310 + $result[$key] = $value;
  311 + }
  312 + else {
  313 + if (!is_array($value)) {
  314 + $result['data'][] = $value;
  315 + }
  316 + else {
  317 + foreach ($value as $v_key => $v_value) {
  318 + $result['data'][$key][$v_key] = $v_value;
  319 + }
  320 + ksort($result['data'][$key]);
  321 + }
  322 + }
  323 + }
  324 +
  325 + return $result;
  326 + }
  327 +
  328 + /**
  329 + * Deprecated
  330 + *
  331 + * @param array $data
  332 + * @return array
  333 + */
  334 + public static function chartTransform1(array $data) {
  335 +
  336 + $result = [];
  337 + $max = 0;
  338 +
  339 + foreach ($data as $key => $value) {
  340 + if ($key == 'name') {
  341 + $result[$key] = $value;
  342 + continue;
  343 + }
  344 +
  345 + if (!is_array($value)) break;
  346 +
  347 + /** ------ check array keys for int values --- **/
  348 + $count = count($value);
  349 + if ($count > $max) $max = $count;
  350 + $int_type = true;
  351 + foreach ($value as $inc_key => $inc_value) {
  352 + if (!preg_match('/\d+/', $inc_key)) {
  353 + $int_type = false;
  354 + break;
  355 + }
  356 + }
  357 + /** ------------------------------------------ **/
  358 +
  359 + /** rewrites keys like "0001" to integer type **/
  360 + if ($int_type) {
  361 + $bad_keys = array_keys($value);
  362 + for ($i = 0; $i < $count; $i++) {
  363 + $good_key = (int)$bad_keys[$i];
  364 + $result[$key][$good_key] = $value[$bad_keys[$i]] ?? 0;
  365 + }
  366 + }
  367 + /** ------------------------------------------ **/
  368 +
  369 + }
  370 +
  371 + /** ---------- filling missing data ---------- **/
  372 + foreach ($result as $key => $value) {
  373 + if ($key == 'name') continue;
  374 + for ($i = 0; $i < $max; $i++) {
  375 + $result[$key][$i] = (int)$result[$key][$i] ?? 0;
  376 + }
  377 + ksort($result[$key]);
  378 + }
  379 + /** ------------------------------------------ **/
  380 +
  381 + return $result;
  382 +
  383 + }
  384 +
  385 + /**
  386 + * Deprecated
  387 + *
174 388 * @param array $response
175 389 * @param string $project_name
176 390 * @return array
... ... @@ -181,15 +395,30 @@ class GaController extends CrudResourceController {
181 395  
182 396 foreach ($response as $item) {
183 397  
184   - $metric_val = $item['metrics'][0]['values'];
185 398 $result['name'] = $project_name;
186 399  
187   - if (count($metric_val) > 1) {
188   - for ($i = 0; $i < count($metric_val); $i++) {
189   - $result['data'][] = (int)$metric_val[$i];
  400 + $metric_val = $item['metrics'][0]['values'];
  401 + $dimension_val = $item['dimensions'][0];
  402 + $dimension_count = count($item['dimensions']);
  403 +
  404 + if ($dimension_count == 2) {
  405 + $dimension_val_2 = $item['dimensions'][1];
  406 + if (count($metric_val) > 1) {
  407 + for ($i = 0; $i < count($metric_val); $i++) {
  408 + $result['data'][$dimension_val][] = (int)$metric_val[$i];
  409 + }
  410 + } else {
  411 + $result['data'][$dimension_val][] = (int)$metric_val[0];
  412 + }
  413 + }
  414 + else {
  415 + if (count($metric_val) > 1) {
  416 + for ($i = 0; $i < count($metric_val); $i++) {
  417 + $result['data'][] = (int)$metric_val[$i];
  418 + }
  419 + } else {
  420 + $result['data'][] = (int)$metric_val[0];
190 421 }
191   - } else {
192   - $result['data'][] = (int)$metric_val[0];
193 422 }
194 423 }
195 424  
... ... @@ -197,6 +426,49 @@ class GaController extends CrudResourceController {
197 426  
198 427 }
199 428  
  429 + /**
  430 + * Compute count of fields
  431 + *
  432 + * @param string $request_dim
  433 + * @param int $request_days
  434 + * @return int
  435 + * @throws ContentNotFound if functions params is empty
  436 + */
  437 + public static function countIterations($request_dim, $request_days) {
  438 +
  439 + if (empty($request_dim)) throw new ContentNotFound('PHP: request_dim not found');
  440 + if (empty($request_days)) throw new ContentNotFound('PHP: request_days not found');
  441 + switch ($request_dim) {
  442 + case 'ga:nthDay':
  443 + $iterations = $request_days*1;
  444 + break;
  445 + case 'ga:nthHour':
  446 + $iterations = $request_days*24;
  447 + break;
  448 + case 'ga:nthMinute':
  449 + $iterations = $request_days*24*60;
  450 + break;
  451 + default:
  452 + $iterations = null;
  453 + }
  454 +
  455 + return $iterations;
  456 +
  457 + }
  458 +
  459 + /**
  460 + * Boolean indicator for chart transformer
  461 + *
  462 + * @param string $dimension
  463 + * @return bool
  464 + */
  465 + public static function checkDimension($dimension) {
  466 +
  467 + $nthArray = ['ga:nthMonth', 'ga:nthWeek', 'ga:nthDay', 'ga:nthMinute', 'ga:nthHour'];
  468 +
  469 + return in_array($dimension, $nthArray) ? true : false;
  470 +
  471 + }
200 472  
201 473 /**
202 474 * without usage, from google docs.
... ...
app/library/App/Controllers/UserController.php
... ... @@ -2,7 +2,6 @@
2 2  
3 3 namespace App\Controllers;
4 4  
5   -use App\Model\Project;
6 5 use App\Model\User;
7 6 use PhalconApi\Constants\ErrorCodes;
8 7 use PhalconApi\Exception;
... ...
app/library/App/Resources/GaResource.php
... ... @@ -21,7 +21,6 @@ class GaResource extends ApiResource {
21 21 $this
22 22 ->name('Google Analytics')
23 23 ->expectsJsonData()
24   - //->transformer(ModelTransformer::class)
25 24 ->itemKey('ga')
26 25 ->collectionKey('ga')
27 26 ->deny(AclRoles::UNAUTHORIZED)
... ... @@ -49,13 +48,15 @@ class GaResource extends ApiResource {
49 48 'end' => 'ГГГГ-ММ-ДД/NdaysAgo, где N – целое положительное число(дата конца загрузки данных)'
50 49 ],
51 50 'optional params' => [
52   - 'user_id' => 'integer(id пользователя в системе)',
53   - 'view_id' => 'integer(id представления проэкта с гугл аналитики)',
54   - 'chart' => 'boolean(Задает структуру возвращаемых данных(true для графиков))',
55   - 'filter' => 'expression(https://developers.google.com/analytics/devguides/reporting/core/v3/reference#filters)',
  51 + 'user_id' => 'integer(id пользователя в системе)',
  52 + 'view_id' => 'integer(id представления проэкта с гугл аналитики)',
  53 + 'chart' => 'boolean(Задает структуру возвращаемых данных(true для графиков))',
  54 + 'filter' => 'expression(https://developers.google.com/analytics/devguides/reporting/core/v3/reference#filters)',
  55 + 'sort' => 'string(параметр сортировки, metric либо dimension)',
  56 + 'sort_type' => 'enum(ans, desc)',
  57 + 'max_result' => 'integer(максимальное число строк в результате, по умолчанию 50,000)',
56 58 ]
57   - ],
58   - 'summary' => '/ga?view_id=119240817&metric=users,sessions&dimension=source,browser&filter=browser=~^Firef'
  59 + ]
59 60 ])
60 61 )
61 62 ;
... ...