Completed
Pull Request — master (#27)
by Matthew
20:41 queued 16:25
created

QueueController::calculateTimings()   C

Complexity

Conditions 10
Paths 30

Size

Total Lines 49
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 49
rs 5.5471
c 0
b 0
f 0
ccs 0
cts 39
cp 0
cc 10
eloc 30
nc 30
nop 3
crap 110

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Dtc\QueueBundle\Controller;
4
5
use Doctrine\ODM\MongoDB\DocumentManager;
6
use Doctrine\ORM\EntityManager;
7
use Dtc\QueueBundle\Doctrine\BaseJobTimingManager;
8
use Dtc\QueueBundle\Exception\UnsupportedException;
9
use Dtc\QueueBundle\Model\JobTiming;
10
use Dtc\QueueBundle\Model\Worker;
11
use Dtc\QueueBundle\ODM\JobManager;
12
use Dtc\QueueBundle\ODM\JobTimingManager;
13
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
14
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
15
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
16
use Symfony\Component\HttpFoundation\JsonResponse;
17
use Zend\Stdlib\Request;
0 ignored issues
show
Bug introduced by
The type Zend\Stdlib\Request was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
19
class QueueController extends Controller
20
{
21
    /**
22
     * Summary stats.
23
     *
24
     * @Route("/")
25
     * @Route("/status/")
26
     * @Template("@DtcQueue/Queue/jobs.html.twig")
27
     */
28
    public function statusAction()
29
    {
30
        $params = array();
31
        $jobManager = $this->get('dtc_queue.job_manager');
32
33
        $params['status'] = $jobManager->getStatus();
34
        $this->addCssJs($params);
35
36
        return $params;
37
    }
38
39
    /**
40
     * List jobs in system by default.
41
     *
42
     * @Route("/jobs_all", name="dtc_queue_jobs_all")
43
     */
44 View Code Duplication
    public function jobsAllAction()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
45
    {
46
        $this->validateManagerType('dtc_queue.default_manager');
47
        $class1 = $this->container->getParameter('dtc_queue.class_job');
48
        $class2 = $this->container->getParameter('dtc_queue.class_job_archive');
49
        $label1 = 'Non-Archived Jobs';
50
        $label2 = 'Archived Jobs';
51
52
        $params = $this->getDualGridParams($class1, $class2, $label1, $label2);
53
54
        return $this->render('@DtcQueue/Queue/grid.html.twig', $params);
0 ignored issues
show
Bug introduced by
$params of type Symfony\Component\HttpFoundation\Response is incompatible with the type array expected by parameter $parameters of Symfony\Bundle\Framework...er\Controller::render(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

54
        return $this->render('@DtcQueue/Queue/grid.html.twig', /** @scrutinizer ignore-type */ $params);
Loading history...
55
    }
56
57
    /**
58
     * List jobs in system by default.
59
     *
60
     * @Template("@DtcQueue/Queue/jobs.html.twig")
61
     * @Route("/jobs", name="dtc_queue_jobs")
62
     */
63
    public function jobsAction()
64
    {
65
        $this->validateManagerType('dtc_queue.default_manager');
66
        $managerType = $this->container->getParameter('dtc_queue.default_manager');
67
        $rendererFactory = $this->get('dtc_grid.renderer.factory');
68
        $renderer = $rendererFactory->create('datatables');
69
        $gridSource = $this->get('dtc_queue.grid_source.live_jobs.'.('mongodb' === $managerType ? 'odm' : $managerType));
70
        $renderer->bind($gridSource);
71
        $params = $renderer->getParams();
72
        $this->addCssJs($params);
73
74
        return $params;
75
    }
76
77
    protected function validateManagerType($type)
78
    {
79
        $managerType = $this->container->getParameter($type);
80
        if ('mongodb' !== $managerType && 'orm' != $managerType && 'odm' != $managerType) {
81
            throw new UnsupportedException("Unsupported manager type: $managerType");
82
        }
83
    }
84
85
    /**
86
     * @param string $class1
87
     * @param string $class2
88
     * @param string $label1
89
     * @param string $label2
90
     *
91
     * @return \Symfony\Component\HttpFoundation\Response
92
     *
93
     * @throws \Exception
94
     */
95
    protected function getDualGridParams($class1, $class2, $label1, $label2)
96
    {
97
        $rendererFactory = $this->get('dtc_grid.renderer.factory');
98
        $renderer = $rendererFactory->create('datatables');
99
        $gridSource = $this->get('dtc_grid.manager.source')->get($class1);
100
        $renderer->bind($gridSource);
101
        $params = $renderer->getParams();
102
103
        $renderer2 = $rendererFactory->create('datatables');
104
        $gridSource = $this->get('dtc_grid.manager.source')->get($class2);
105
        $renderer2->bind($gridSource);
106
        $params2 = $renderer2->getParams();
107
108
        $params['archive_grid'] = $params2['dtc_grid'];
109
110
        $params['dtc_queue_grid_label1'] = $label1;
111
        $params['dtc_queue_grid_label2'] = $label2;
112
        $this->addCssJs($params);
113
114
        return $params;
115
    }
116
117
    protected function addCssJs(array &$params)
118
    {
119
        $params['css'] = $this->container->getParameter('dtc_grid.theme.css');
120
        $params['js'] = $this->container->getParameter('dtc_grid.theme.js');
121
        $jQuery = $this->container->getParameter('dtc_grid.jquery');
122
        array_unshift($params['js'], $jQuery['url']);
123
        $params['chartjs'] = $this->container->getParameter('dtc_queue.admin.chartjs');
124
    }
125
126
    protected function validateJobTimingManager()
127
    {
128
        if ($this->container->hasParameter('dtc_queue.job_timing_manager')) {
129
            $this->validateManagerType('dtc_queue.job_timing_manager');
130
        } elseif ($this->container->hasParameter('dtc_queue.job_timing_manager')) {
131
            $this->validateManagerType('dtc_queue.run_manager');
132
        } else {
133
            $this->validateManagerType('dtc_queue.default_manager');
134
        }
135
    }
136
137
    protected function validateRunManager()
138
    {
139
        if ($this->container->hasParameter('dtc_queue.job_timing_manager')) {
140
            $this->validateManagerType('dtc_queue.run_manager');
141
        } else {
142
            $this->validateManagerType('dtc_queue.default_manager');
143
        }
144
    }
145
146
    /**
147
     * List jobs in system by default.
148
     *
149
     * @Route("/runs", name="dtc_queue_runs")
150
     */
151 View Code Duplication
    public function runsAction()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
    {
153
        $this->validateRunManager();
154
        $class1 = $this->container->getParameter('dtc_queue.class_run');
155
        $class2 = $this->container->getParameter('dtc_queue.class_run_archive');
156
        $label1 = 'Live Runs';
157
        $label2 = 'Archived Runs';
158
159
        $params = $this->getDualGridParams($class1, $class2, $label1, $label2);
160
161
        return $this->render('@DtcQueue/Queue/grid.html.twig', $params);
0 ignored issues
show
Bug introduced by
$params of type Symfony\Component\HttpFoundation\Response is incompatible with the type array expected by parameter $parameters of Symfony\Bundle\Framework...er\Controller::render(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
        return $this->render('@DtcQueue/Queue/grid.html.twig', /** @scrutinizer ignore-type */ $params);
Loading history...
162
    }
163
164
    /**
165
     * List registered workers in the system.
166
     *
167
     * @Route("/workers", name="dtc_queue_workers")
168
     * @Template("@DtcQueue/Queue/workers.html.twig")
169
     */
170
    public function workersAction()
171
    {
172
        $workerManager = $this->get('dtc_queue.worker_manager');
173
        $workers = $workerManager->getWorkers();
174
175
        $workerList = [];
176
        foreach ($workers as $workerName => $worker) {
177
            /* @var Worker $worker */
178
            $workerList[$workerName] = get_class($worker);
179
        }
180
        $params = ['workers' => $workerList];
181
        $this->addCssJs($params);
182
183
        return $params;
184
    }
185
186
    /**
187
     * Show a graph of job trends.
188
     *
189
     * @Route("/trends", name="dtc_queue_trends")
190
     * @Template("@DtcQueue/Queue/trends.html.twig")
191
     */
192
    public function trendsAction()
193
    {
194
        $recordTimings = $this->container->getParameter('dtc_queue.record_timings');
195
        $params = ['record_timings' => $recordTimings, 'states' => JobTiming::getStates()];
196
        $this->addCssJs($params);
197
198
        return $params;
199
    }
200
201
    /**
202
     * @Route("/timings", name="dtc_queue_timings")
203
     *
204
     * @param Request $request
205
     */
206
    public function getTimingsAction()
207
    {
208
        $request = $this->get('request_stack')->getMasterRequest();
209
        $begin = $request->query->get('begin');
210
        $end = $request->query->get('end');
211
        $type = $request->query->get('type', 'HOUR');
212
        $beginDate = \DateTime::createFromFormat(DATE_ISO8601, $begin) ?: null;
213
        $endDate = \DateTime::createFromFormat(DATE_ISO8601, $end) ?: new \DateTime();
214
215
        $recordTimings = $this->container->getParameter('dtc_queue.record_timings');
216
        $params = [];
217
        if ($recordTimings) {
218
            $params = $this->calculateTimings($type, $beginDate, $endDate);
219
        }
220
221
        return new JsonResponse($params);
222
    }
223
224
    protected function calculateTimings($type, $beginDate, $endDate)
225
    {
226
        $params = [];
227
        $this->validateJobTimingManager();
228
229
        /** @var BaseJobTimingManager $jobTimingManager */
230
        $jobTimingManager = $this->get('dtc_queue.job_timing_manager');
231
        if ($jobTimingManager instanceof JobTimingManager) {
232
            $timings = $this->getJobTimingsOdm($type, $endDate, $beginDate);
233
        } else {
234
            $timings = $this->getJobTimingsOrm($type, $endDate, $beginDate);
235
        }
236
237
        $timingStates = JobTiming::getStates();
238
        $timingsDates = [];
239
        foreach (array_keys($timingStates) as $state) {
240
            if (!isset($timings[$state])) {
241
                continue;
242
            }
243
            $timingsData = $timings[$state];
244
            $timingsDates = array_unique(array_merge(array_keys($timingsData), $timingsDates));
245
        }
246
247
        usort($timingsDates, function ($date1str, $date2str) {
248
            $date1 = \DateTime::createFromFormat('Y-m-d H', $date1str);
249
            $date2 = \DateTime::createFromFormat('Y-m-d H', $date2str);
250
            if (!$date2) {
251
                return false;
252
            }
253
            if (!$date1) {
254
                return false;
255
            }
256
257
            return $date1 > $date2;
258
        });
259
260
        foreach (array_keys($timingStates) as $state) {
261
            if (!isset($timings[$state])) {
262
                continue;
263
            }
264
265
            $timingsData = $timings[$state];
266
            foreach ($timingsDates as $date) {
267
                $params['timings_data_'.$state][] = isset($timingsData[$date]) ? $timingsData[$date] : 0;
268
            }
269
        }
270
        $params['timings_dates'] = $timingsDates;
271
272
        return $params;
273
    }
274
275
    protected function getJobTimingsOdm($type, \DateTime $end, \DateTime $begin = null)
276
    {
277
        /** @var JobTimingManager $runManager */
278
        $jobTimingManager = $this->get('dtc_queue.job_timing_manager');
279
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
280
281
        /** @var DocumentManager $documentManager */
282
        $documentManager = $jobTimingManager->getObjectManager();
283
284
        $regexInfo = $this->getRegexDate($type);
285
        if (!$begin) {
286
            $begin = clone $end;
287
            $begin->sub($regexInfo['interval']);
288
        }
289
290
        // Run a map reduce function get worker and status break down
291
        $mapFunc = "function() {
292
            var dateStr = this.finishedAt.toISOString();
293
            dateStr = dateStr.replace(/{$regexInfo['regex']}/,'{$regexInfo['replacement']}');
294
            var dateBegin = new Date('{$begin->format('c')}');
295
            var dateEnd = new Date('{$end->format('c')}');
296
            if (this.finishedAt >= dateBegin && this.finishedAt <= dateEnd) {
297
                var result = {};
298
                result[dateStr] = 1;
299
                emit(this.status, result);
300
            }
301
        }";
302
        $reduceFunc = JobManager::REDUCE_FUNCTION;
303
        $builder = $documentManager->createQueryBuilder($jobTimingClass);
304
        $builder->map($mapFunc)
305
            ->reduce($reduceFunc);
306
        $query = $builder->getQuery();
307
        $results = $query->execute();
308
        $resultHash = [];
309
        foreach ($results as $info) {
310
            $resultHash[$info['_id']] = $info['value'];
311
        }
312
313
        return $resultHash;
314
    }
315
316
    protected function getRegexDate($type)
317
    {
318
        switch ($type) {
319 View Code Duplication
            case 'YEAR':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
320
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1'],
321
                    'interval' => new \DateInterval('P10Y'), ];
322 View Code Duplication
            case 'MONTH':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
323
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2'],
324
                    'interval' => new \DateInterval('P12M'), ];
325 View Code Duplication
            case 'DAY':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
326
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3'],
327
                    'interval' => new \DateInterval('P31D'), ];
328 View Code Duplication
            case 'HOUR':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
329
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4'],
330
                    'interval' => new \DateInterval('PT24H'), ];
331 View Code Duplication
            case 'MINUTE':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
332
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4:$5'],
333
                    'interval' => new \DateInterval('PT3600S'), ];
334
        }
335
        throw new \InvalidArgumentException("Invalid type $type");
336
    }
337
338
    protected function getOrmGroupBy($type)
339
    {
340
        switch ($type) {
341
            case 'YEAR':
342
                return ['groupby' => 'YEAR(j.finishedAt)',
343
                        'interval' => new \DateInterval('P10Y'), ];
344
            case 'MONTH':
345
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt))',
346
                        'interval' => new \DateInterval('P12M'), ];
347
            case 'DAY':
348
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt))',
349
                        'interval' => new \DateInterval('P31D'), ];
350
            case 'HOUR':
351
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt))',
352
                        'interval' => new \DateInterval('PT24H'), ];
353
            case 'MINUTE':
354
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt),\':\',MINUTE(j.finishedAt))',
355
                        'interval' => new \DateInterval('PT3600S'), ];
356
        }
357
        throw new \InvalidArgumentException("Invalid type $type");
358
    }
359
360
    protected function getJobTimingsOrm($type, \DateTime $end, \DateTime $begin = null)
361
    {
362
        /** @var JobTimingManager $jobTimingManager */
363
        $jobTimingManager = $this->get('dtc_queue.job_timing_manager');
364
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
365
        /** @var EntityManager $entityManager */
366
        $entityManager = $jobTimingManager->getObjectManager();
367
368
        $groupByInfo = $this->getOrmGroupBy($type);
369
370
        if (!$begin) {
371
            $begin = clone $end;
372
            $begin->sub($groupByInfo['interval']);
373
        }
374
375
        $queryBuilder = $entityManager->createQueryBuilder()->select("j.status as status, count(j.finishedAt) as thecount, {$groupByInfo['groupby']} as thedate")
376
            ->from($jobTimingClass, 'j')
377
            ->where('j.finishedAt <= :end')
378
            ->andWhere('j.finishedAt >= :begin')
379
            ->setParameter(':end', $end)
380
            ->setParameter(':begin', $begin)
381
            ->groupBy('status')
382
            ->addGroupBy('thedate');
383
384
        $result = $queryBuilder
385
            ->getQuery()->getArrayResult();
386
387
        $resultHash = [];
388
        foreach ($result as $row) {
389
            $resultHash[$row['status']][$row['thedate']] = intval($row['thecount']);
390
        }
391
392
        return $resultHash;
393
    }
394
}
395