Failed Conditions
Pull Request — master (#9)
by Michel
04:09 queued 17s
created

SyncService::getTimeEntries()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 17
ccs 10
cts 10
cp 1
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace TogglJira\Service;
5
6
use AJT\Toggl\TogglClient;
7
use Exception;
8
use GuzzleHttp\Command\Guzzle\GuzzleClient;
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
use RuntimeException;
12
use TogglJira\Entity\WorkLogEntry;
13
use TogglJira\Hydrator\WorkLogHydrator;
14
use TogglJira\Jira\Api;
15
16
class SyncService implements LoggerAwareInterface
17
{
18
    use LoggerAwareTrait;
19
20
    /**
21
     * @var Api
22
     */
23
    private $api;
24
25
    /**
26
     * @var TogglClient
27
     */
28
    private $togglClient;
29
30
    /**
31
     * @var WorkLogHydrator
32
     */
33
    private $workLogHydrator;
34
    /**
35
     * @var string
36
     */
37
    private $username;
38
39
    /**
40
     * @param Api $api
41
     * @param GuzzleClient $togglClient
42
     * @param WorkLogHydrator $workLogHydrator
43
     * @param string $username
44
     */
45 6
    public function __construct(Api $api, GuzzleClient $togglClient, WorkLogHydrator $workLogHydrator, string $username)
46
    {
47 6
        $this->api = $api;
48 6
        $this->togglClient = $togglClient;
49 6
        $this->workLogHydrator = $workLogHydrator;
50 6
        $this->username = $username;
51 6
    }
52
53
    /**
54
     * @param \DateTimeInterface $startDate
55
     * @param \DateTimeInterface $endDate
56
     * @param bool $overwrite
57
     * @return void
58
     * @throws Exception
59
     */
60 5
    public function sync(\DateTimeInterface $startDate, \DateTimeInterface $endDate, bool $overwrite): void
61
    {
62 5
        $user = $this->api->getUser($this->username);
63
64 5
        if (!isset($user['accountId'])) {
65 1
            throw new RuntimeException("User with username {$this->username} not found");
66
        }
67
68 4
        $timeEntries = $this->getTimeEntries($startDate, $endDate);
69
70 4
        if ($timeEntries) {
71 3
            $this->addWorkLogsToApi($this->parseTimeEntries($timeEntries), $user, $overwrite);
72
        }
73
74 4
        $this->logger->info('All done for today, time to go home!');
75 4
    }
76
77
    /**
78
     * @param \DateTimeInterface $startDate
79
     * @param \DateTimeInterface $endDate
80
     * @return array|null
81
     */
82 4
    private function getTimeEntries(\DateTimeInterface $startDate, \DateTimeInterface $endDate): ?array
83
    {
84
        try {
85
            /** @var array $timeEntries */
86 4
            return $this->togglClient->getTimeEntries(
87
                [
88 4
                    'start_date' => $startDate->format(DATE_ATOM),
89 4
                    'end_date' => $endDate->format(DATE_ATOM),
90
                ]
91 3
            )->toArray();
92 1
        } catch (Exception $e) {
93 1
            $this->logger->error(
94 1
                'Failed to get time entries from Toggl',
95 1
                ['exception' => $e]
96
            );
97
98 1
            return null;
99
        }
100
    }
101
102
    /**
103
     * @param array $timeEntries
104
     * @return array
105
     * @throws Exception
106
     */
107 3
    private function parseTimeEntries(array $timeEntries): array
108
    {
109 3
        $workLogEntries = [];
110
111 3
        foreach ($timeEntries as $timeEntry) {
112 3
            $workLogEntry = $this->parseTimeEntry($timeEntry);
113
114 3
            if (!$workLogEntry) {
115 1
                continue;
116
            }
117
118 2
            $existingKey = md5($workLogEntry->getIssueID() . '-' . $workLogEntry->getSpentOn()->format('Y-m-d'));
119
120 2
            if (isset($workLogEntries[$existingKey])) {
121 2
                $this->addTimeToExistingTimeEntry($workLogEntries[$existingKey], $workLogEntry);
122 2
                continue;
123
            }
124
125 2
            $workLogEntries[$existingKey] = $workLogEntry;
126
127 2
            $this->logger->info('Found time entry for issue', [
128 2
                'issueID' => $workLogEntry->getIssueID(),
129 2
                'spentOn' => $workLogEntry->getSpentOn()->format('Y-m-d'),
130 2
                'timeSpent' => round($workLogEntry->getTimeSpent() / 60 / 60, 2) . ' hours',
131
            ]);
132
        }
133
134 3
        return $workLogEntries;
135
    }
136
137
    /**
138
     * @param array $timeEntry
139
     * @return WorkLogEntry|null
140
     * @throws Exception
141
     */
142 3
    private function parseTimeEntry(array $timeEntry): ?WorkLogEntry
143
    {
144
        $data = [
145 3
            'issueID' => explode(' ', $timeEntry['description'])[0],
146 3
            'timeSpent' => $timeEntry['duration'],
147 3
            'comment' => $timeEntry['description'],
148 3
            'spentOn' => $timeEntry['start']
149
        ];
150
151 3
        if (strpos($data['issueID'], '-') === false) {
152 1
            $this->logger->warning('Could not parse issue string, cannot link to Jira');
153 1
            return null;
154
        }
155
156 3
        if ($data['timeSpent'] < 0) {
157 1
            $this->logger->info('0 seconds, or timer still running, skipping', [
158 1
                'issueID' => $data['issueID']
159
            ]);
160 1
            return null;
161
        }
162
163 2
        return $this->workLogHydrator->hydrate($data, new WorkLogEntry());
164
    }
165
166
    /**
167
     * @param $existingWorkLog
168
     * @param $newWorkLog
169
     * @return WorkLogEntry
170
     */
171 2
    private function addTimeToExistingTimeEntry(WorkLogEntry $existingWorkLog, WorkLogEntry $newWorkLog): WorkLogEntry
172
    {
173 2
        $timeSpent = $existingWorkLog->getTimeSpent();
174 2
        $timeSpent += $newWorkLog->getTimeSpent();
175
176 2
        $existingWorkLog->setTimeSpent($timeSpent);
177 2
        $existingWorkLog->setComment($existingWorkLog->getComment() . "\n" . $newWorkLog->getComment());
178
179 2
        $this->logger->info('Added time spent for issue', [
180 2
            'issueID' => $newWorkLog->getIssueID(),
181 2
            'spentOn' => $newWorkLog->getSpentOn()->format('Y-m-d'),
182 2
            'timeSpent' => round($newWorkLog->getTimeSpent() / 60 / 60, 2) . ' hours',
183
        ]);
184
185 2
        return $existingWorkLog;
186
    }
187
188
    /**
189
     * @param array $workLogEntries
190
     * @param array $user
191
     * @param bool $overwrite
192
     * @return void
193
     */
194 3
    private function addWorkLogsToApi(array $workLogEntries, array $user, bool $overwrite): void
195
    {
196
        /** @var WorkLogEntry $workLogEntry */
197 3
        foreach ($workLogEntries as $workLogEntry) {
198
            try {
199 2
                $this->api->addWorkLogEntry(
200 2
                    $workLogEntry->getIssueID(),
201 2
                    $workLogEntry->getTimeSpent(),
202 2
                    $user['accountId'],
203 2
                    $workLogEntry->getComment(),
204 2
                    $workLogEntry->getSpentOn()->format('Y-m-d\TH:i:s.vO'),
205 2
                    $overwrite
206
                );
207
208 1
                $this->logger->info('Saved worklog entry', [
209 1
                    'issueID' => $workLogEntry->getIssueID(),
210 1
                    'spentOn' => $workLogEntry->getSpentOn()->format('Y-m-d'),
211 1
                    'timeSpent' => round($workLogEntry->getTimeSpent() / 60 / 60, 2) . ' hours',
212
                ]);
213 1
            } catch (Exception $e) {
214 2
                $this->logger->error('Could not add worklog entry', ['exception' => $e]);
215
            }
216
        }
217 3
    }
218
219
    /**
220
     * @param \DateTimeInterface $startDate
221
     * @return \DateTimeInterface
222
     */
223
    private function createStartDateFromTomorrow(\DateTimeInterface $startDate): \DateTimeInterface
0 ignored issues
show
Unused Code introduced by
The method createStartDateFromTomorrow() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
224
    {
225
        return (new \DateTime($startDate->format(DATE_ATOM)))->modify('+1 day');
226
    }
227
}
228