1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace TogglJira\Service; |
5
|
|
|
|
6
|
|
|
use Exception; |
7
|
|
|
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
8
|
|
|
use Psr\Log\LoggerAwareInterface; |
9
|
|
|
use Psr\Log\LoggerAwareTrait; |
10
|
|
|
use RuntimeException; |
11
|
|
|
use TogglJira\Entity\WorkLogEntry; |
12
|
|
|
use TogglJira\Hydrator\WorkLogHydrator; |
13
|
|
|
use TogglJira\Jira\Api; |
14
|
|
|
|
15
|
|
|
class SyncService implements LoggerAwareInterface |
16
|
|
|
{ |
17
|
|
|
use LoggerAwareTrait; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var Api |
21
|
|
|
*/ |
22
|
|
|
private $api; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var GuzzleClient |
26
|
|
|
*/ |
27
|
|
|
private $togglClient; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var WorkLogHydrator |
31
|
|
|
*/ |
32
|
|
|
private $workLogHydrator; |
33
|
|
|
/** |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
private $username; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @param Api $api |
40
|
|
|
* @param GuzzleClient $togglClient |
41
|
|
|
* @param WorkLogHydrator $workLogHydrator |
42
|
|
|
* @param string $username |
43
|
|
|
*/ |
44
|
6 |
|
public function __construct(Api $api, GuzzleClient $togglClient, WorkLogHydrator $workLogHydrator, string $username) |
45
|
|
|
{ |
46
|
6 |
|
$this->api = $api; |
47
|
6 |
|
$this->togglClient = $togglClient; |
48
|
6 |
|
$this->workLogHydrator = $workLogHydrator; |
49
|
6 |
|
$this->username = $username; |
50
|
6 |
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @throws RuntimeException |
54
|
|
|
* @return void |
55
|
|
|
*/ |
56
|
5 |
|
public function sync(string $startDate): void |
57
|
|
|
{ |
58
|
5 |
|
$user = $this->api->getUser($this->username); |
59
|
|
|
|
60
|
5 |
|
if (!$user) { |
|
|
|
|
61
|
1 |
|
throw new RuntimeException("User with username {$this->username} not found"); |
62
|
|
|
} |
63
|
|
|
|
64
|
4 |
|
$workLogEntries = []; |
65
|
|
|
|
66
|
|
|
try { |
67
|
|
|
/** @var array $timeEntries */ |
68
|
4 |
|
$timeEntries = $this->togglClient->getTimeEntries(['start_date' => $startDate]); |
69
|
1 |
|
} catch (Exception $e) { |
70
|
1 |
|
$this->logger->error( |
71
|
1 |
|
"Failed to get time entries from Toggl: {$e->getMessage()}", |
72
|
1 |
|
['exception' => $e] |
73
|
|
|
); |
74
|
1 |
|
return; |
75
|
|
|
} |
76
|
|
|
|
77
|
3 |
|
foreach ($timeEntries as $timeEntry) { |
78
|
3 |
|
$workLogEntry = $this->parseTimeEntry($timeEntry); |
79
|
|
|
|
80
|
3 |
|
if (!$workLogEntry) { |
81
|
1 |
|
continue; |
82
|
|
|
} |
83
|
|
|
|
84
|
2 |
|
$existingKey = $workLogEntry->getIssueID() . '-' . $workLogEntry->getSpentOn()->format('d-m-Y'); |
85
|
|
|
|
86
|
2 |
|
if (isset($workLogEntries[$existingKey])) { |
87
|
2 |
|
$timeSpent = $workLogEntries[$existingKey]->getTimeSpent(); |
88
|
2 |
|
$timeSpent += $workLogEntry->getTimeSpent(); |
89
|
|
|
|
90
|
2 |
|
$workLogEntries[$existingKey]->setTimeSpent($timeSpent); |
91
|
|
|
|
92
|
2 |
|
$this->logger->info("Added time spent for issue {$workLogEntry->getIssueID()}"); |
93
|
|
|
|
94
|
2 |
|
continue; |
95
|
|
|
} |
96
|
|
|
|
97
|
2 |
|
$workLogEntries[$existingKey] = $workLogEntry; |
98
|
|
|
|
99
|
2 |
|
$this->logger->info("Found time entry for issue {$workLogEntry->getIssueID()}"); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** @var WorkLogEntry $workLogEntry */ |
103
|
3 |
|
foreach ($workLogEntries as $workLogEntry) { |
104
|
|
|
try { |
105
|
2 |
|
$this->api->addWorkLogEntry( |
106
|
2 |
|
$workLogEntry->getIssueID(), |
107
|
2 |
|
$workLogEntry->getTimeSpent(), |
108
|
2 |
|
$user['accountId'], |
109
|
2 |
|
$workLogEntry->getComment(), |
110
|
2 |
|
$workLogEntry->getSpentOn()->format('Y-m-d\TH:i:s.vO') |
111
|
|
|
); |
112
|
|
|
|
113
|
1 |
|
$this->logger->info("Saved worklog entry for issue {$workLogEntry->getIssueID()}"); |
114
|
1 |
|
} catch (Exception $e) { |
115
|
2 |
|
$this->logger->error("Could not add worklog entry: {$e->getMessage()}", ['exception' => $e]); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
3 |
|
$this->logger->info('All done for today, time to go home!'); |
120
|
3 |
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @param array $timeEntry |
124
|
|
|
* @return WorkLogEntry|null |
125
|
|
|
* @throws Exception |
126
|
|
|
*/ |
127
|
3 |
|
private function parseTimeEntry(array $timeEntry): ?WorkLogEntry |
128
|
|
|
{ |
129
|
|
|
$data = [ |
130
|
3 |
|
'issueID' => explode(' ', $timeEntry['description'])[0], |
131
|
3 |
|
'timeSpent' => $timeEntry['duration'], |
132
|
3 |
|
'comment' => $timeEntry['description'], |
133
|
3 |
|
'spentOn' => $timeEntry['start'] |
134
|
|
|
]; |
135
|
|
|
|
136
|
3 |
|
if (strpos($data['issueID'], '-') === false) { |
137
|
1 |
|
$this->logger->warning('Could not parse issue string, cannot link to Jira'); |
138
|
1 |
|
return null; |
139
|
|
|
} |
140
|
|
|
|
141
|
3 |
|
if ($data['timeSpent'] < 0) { |
142
|
1 |
|
$this->logger->info("0 seconds, or timer still running for {$data['issueID']}, skipping"); |
143
|
1 |
|
return null; |
144
|
|
|
} |
145
|
|
|
|
146
|
2 |
|
return $this->workLogHydrator->hydrate($data, new WorkLogEntry()); |
147
|
|
|
} |
148
|
|
|
} |
149
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.