Completed
Push — master ( 5fe2ea...bba9f0 )
by Matthew
13:23
created

TrendsController::setTimingsData()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 12
cp 0
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 7
nc 3
nop 4
crap 30
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\Model\JobTiming;
9
use Dtc\QueueBundle\ODM\JobManager;
10
use Dtc\QueueBundle\ODM\JobTimingManager;
11
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
12
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
13
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
14
use Symfony\Component\HttpFoundation\JsonResponse;
15
use Symfony\Component\HttpFoundation\Request;
16
17
class TrendsController extends Controller
18
{
19
    use ControllerTrait;
20
21
    /**
22
     * Show a graph of job trends.
23
     *
24
     * @Route("/trends", name="dtc_queue_trends")
25
     * @Template("@DtcQueue/Queue/trends.html.twig")
26
     */
27
    public function trendsAction()
28
    {
29
        $recordTimings = $this->container->getParameter('dtc_queue.record_timings');
30
        $params = ['record_timings' => $recordTimings, 'states' => JobTiming::getStates()];
31
        $this->addCssJs($params);
32
33
        return $params;
34
    }
35
36
    /**
37
     * @Route("/timings", name="dtc_queue_timings")
38
     *
39
     * @param Request $request
0 ignored issues
show
Bug introduced by
There is no parameter named $request. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
40
     */
41
    public function getTimingsAction()
42
    {
43
        $request = $this->get('request_stack')->getMasterRequest();
44
        $begin = $request->query->get('begin');
45
        $end = $request->query->get('end');
46
        $type = $request->query->get('type', 'HOUR');
47
        $beginDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $begin) ?: null;
48
        $endDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $end) ?: new \DateTime();
49
50
        $recordTimings = $this->container->getParameter('dtc_queue.record_timings');
51
        $params = [];
52
        if ($recordTimings) {
53
            $params = $this->calculateTimings($type, $beginDate, $endDate);
54
        }
55
56
        return new JsonResponse($params);
57
    }
58
59
    protected function calculateTimings($type, $beginDate, $endDate)
60
    {
61
        $params = [];
62
        $this->validateJobTimingManager();
63
64
        /** @var BaseJobTimingManager $jobTimingManager */
65
        $jobTimingManager = $this->get('dtc_queue.job_timing_manager');
66
        if ($jobTimingManager instanceof JobTimingManager) {
67
            $timings = $this->getJobTimingsOdm($type, $endDate, $beginDate);
68
        } else {
69
            $timings = $this->getJobTimingsOrm($type, $endDate, $beginDate);
70
        }
71
72
        $timingStates = JobTiming::getStates();
73
        $timingsDates = [];
74
        foreach (array_keys($timingStates) as $state) {
75
            if (!isset($timings[$state])) {
76
                continue;
77
            }
78
            $timingsData = $timings[$state];
79
            $timingsDates = array_unique(array_merge(array_keys($timingsData), $timingsDates));
80
        }
81
82
        $format = $this->getDateFormat($type);
83
        usort($timingsDates, function ($date1str, $date2str) use ($format) {
84
            $date1 = \DateTime::createFromFormat($format, $date1str);
85
            $date2 = \DateTime::createFromFormat($format, $date2str);
86
            if (!$date2) {
87
                return false;
88
            }
89
            if (!$date1) {
90
                return false;
91
            }
92
93
            return $date1 > $date2;
94
        });
95
96
        $timingsDatesAdjusted = $this->getTimingsDatesAdjusted($timingsDates, $format);
97
        $this->setTimingsData($timingStates, $timings, $timingsDates, $params);
98
        $params['timings_dates'] = $timingsDates;
99
        $params['timings_dates_rfc3339'] = $timingsDatesAdjusted;
100
101
        return $params;
102
    }
103
104
    /**
105
     * Timings offset by timezone if necessary.
106
     *
107
     * @param array $timingsDates
108
     * @param $format
109
     *
110
     * @return array
111
     */
112
    protected function getTimingsDatesAdjusted(array $timingsDates, $format)
113
    {
114
        $timezoneOffset = $this->container->getParameter('dtc_queue.record_timings_timezone_offset');
115
        $timingsDatesAdjusted = [];
116
        foreach ($timingsDates as $dateStr) {
117
            $date = \DateTime::createFromFormat($format, $dateStr);
118
            if (0 !== $timezoneOffset) {
119
                $date->setTimestamp($date->getTimestamp() + ($timezoneOffset * 3600));
120
            }
121
            $timingsDatesAdjusted[] = $date->format(DATE_RFC3339);
122
        }
123
124
        return $timingsDatesAdjusted;
125
    }
126
127
    protected function setTimingsData(array $timingStates, array $timings, array $timingsDates, array &$params)
128
    {
129
        foreach (array_keys($timingStates) as $state) {
130
            if (!isset($timings[$state])) {
131
                continue;
132
            }
133
134
            $timingsData = $timings[$state];
135
            foreach ($timingsDates as $date) {
136
                $params['timings_data_'.$state][] = isset($timingsData[$date]) ? $timingsData[$date] : 0;
137
            }
138
        }
139
    }
140
141
    protected function getJobTimingsOdm($type, \DateTime $end, \DateTime $begin = null)
142
    {
143
        /** @var JobTimingManager $runManager */
144
        $jobTimingManager = $this->get('dtc_queue.job_timing_manager');
145
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
146
147
        /** @var DocumentManager $documentManager */
148
        $documentManager = $jobTimingManager->getObjectManager();
149
150
        $regexInfo = $this->getRegexDate($type);
151
        if (!$begin) {
152
            $begin = clone $end;
153
            $begin->sub($regexInfo['interval']);
154
        }
155
156
        // Run a map reduce function get worker and status break down
157
        $mapFunc = "function() {
158
            var dateStr = this.finishedAt.toISOString();
159
            dateStr = dateStr.replace(/{$regexInfo['regex']}/,'{$regexInfo['replacement']}');
160
            var dateBegin = new Date('{$begin->format('c')}');
161
            var dateEnd = new Date('{$end->format('c')}');
162
            if (this.finishedAt >= dateBegin && this.finishedAt <= dateEnd) {
163
                var result = {};
164
                result[dateStr] = 1;
165
                emit(this.status, result);
166
            }
167
        }";
168
        $reduceFunc = JobManager::REDUCE_FUNCTION;
169
        $builder = $documentManager->createQueryBuilder($jobTimingClass);
170
        $builder->map($mapFunc)
171
            ->reduce($reduceFunc);
172
        $query = $builder->getQuery();
173
        $results = $query->execute();
174
        $resultHash = [];
175
        foreach ($results as $info) {
176
            $resultHash[$info['_id']] = $info['value'];
177
        }
178
179
        return $resultHash;
180
    }
181
182
    protected function getDateFormat($type)
183
    {
184
        switch ($type) {
185
            case 'YEAR':
186
                return 'Y';
187
            case 'MONTH':
188
                return 'Y-m';
189
            case 'DAY':
190
                return 'Y-m-d';
191
            case 'HOUR':
192
                return 'Y-m-d H';
193
            case 'MINUTE':
194
                return 'Y-m-d H:i';
195
            default:
196
                throw new \InvalidArgumentException("Invalid date format type '$type''");
197
        }
198
    }
199
200
    protected function getRegexDate($type)
201
    {
202
        switch ($type) {
203 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...
204
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1'],
205
                    'interval' => new \DateInterval('P10Y'), ];
206 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...
207
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2'],
208
                    'interval' => new \DateInterval('P12M'), ];
209 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...
210
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3'],
211
                    'interval' => new \DateInterval('P31D'), ];
212 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...
213
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4'],
214
                    'interval' => new \DateInterval('PT24H'), ];
215 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...
216
                return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4:$5'],
217
                    'interval' => new \DateInterval('PT3600S'), ];
218
        }
219
        throw new \InvalidArgumentException("Invalid type $type");
220
    }
221
222
    protected function getOrmGroupBy($type)
223
    {
224
        switch ($type) {
225
            case 'YEAR':
226
                return ['groupby' => 'YEAR(j.finishedAt)',
227
                    'interval' => new \DateInterval('P10Y'), ];
228
            case 'MONTH':
229
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt))',
230
                    'interval' => new \DateInterval('P12M'), ];
231
            case 'DAY':
232
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt))',
233
                    'interval' => new \DateInterval('P31D'), ];
234
            case 'HOUR':
235
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt))',
236
                    'interval' => new \DateInterval('PT24H'), ];
237
            case 'MINUTE':
238
                return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt),\':\',MINUTE(j.finishedAt))',
239
                    'interval' => new \DateInterval('PT3600S'), ];
240
        }
241
        throw new \InvalidArgumentException("Invalid type $type");
242
    }
243
244
    protected function getJobTimingsOrm($type, \DateTime $end, \DateTime $begin = null)
245
    {
246
        /** @var JobTimingManager $jobTimingManager */
247
        $jobTimingManager = $this->get('dtc_queue.job_timing_manager');
248
        $jobTimingClass = $jobTimingManager->getJobTimingClass();
249
        /** @var EntityManager $entityManager */
250
        $entityManager = $jobTimingManager->getObjectManager();
251
252
        $groupByInfo = $this->getOrmGroupBy($type);
253
254
        if (!$begin) {
255
            $begin = clone $end;
256
            $begin->sub($groupByInfo['interval']);
257
        }
258
259
        $queryBuilder = $entityManager->createQueryBuilder()->select("j.status as status, count(j.finishedAt) as thecount, {$groupByInfo['groupby']} as thedate")
260
            ->from($jobTimingClass, 'j')
261
            ->where('j.finishedAt <= :end')
262
            ->andWhere('j.finishedAt >= :begin')
263
            ->setParameter(':end', $end)
264
            ->setParameter(':begin', $begin)
265
            ->groupBy('status')
266
            ->addGroupBy('thedate');
267
268
        $result = $queryBuilder
269
            ->getQuery()->getArrayResult();
270
271
        $resultHash = [];
272
        foreach ($result as $row) {
273
            $resultHash[$row['status']][$row['thedate']] = intval($row['thecount']);
274
        }
275
276
        return $resultHash;
277
    }
278
279 View Code Duplication
    protected function validateJobTimingManager()
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...
280
    {
281
        if ($this->container->hasParameter('dtc_queue.job_timing_manager')) {
282
            $this->validateManagerType('dtc_queue.job_timing_manager');
283
        } elseif ($this->container->hasParameter('dtc_queue.job_timing_manager')) {
284
            $this->validateManagerType('dtc_queue.run_manager');
285
        } else {
286
            $this->validateManagerType('dtc_queue.default_manager');
287
        }
288
    }
289
}
290