Test Failed
Pull Request — master (#16)
by Timon
05:27 queued 02:33
created

SyncService::fillTimeToFull()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5.1158

Importance

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