mtas-tv-backend/src/App/Service/JiraCollectorService.php
Dávid Danyi cfc388aa77 * multiple team kanban board implementation added
* active flag is now working as intended
* iframe slide type added
* team-slide connection is now many-to-many
2018-09-05 17:01:40 +02:00

283 lines
11 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Service;
use App\Entity\JiraAssignee;
use App\Entity\JiraIssueType;
use App\Entity\JiraStatus;
use App\Entity\KanbanBoard;
use App\Entity\KanbanEntry;
use App\Entity\Team;
use Zend\Cache\Storage\StorageInterface;
use Zend\Config\Config;
use Zend\Expressive\Router\RouterInterface;
use Zend\Http\Client;
use Zend\Json\Decoder;
use Zend\Json\Json;
class JiraCollectorService
{
const CACHE_KEY_KANBANBOARD = 'kanbanBoard';
const BACKLOG_FIELD_DELIMITER = ';';
const EPIC_TICKET_LINK = 'customfield_11711';
const EPIC_NAME_FIELD = 'customfield_11712';
/** @var StorageInterface */
private $cache;
/** @var Config */
private $config;
/** @var Client */
private $httpClient;
/** @var RouterInterface */
private $router;
/** @var TeamService */
private $teamService;
/** @var array */
private $cachedEpics = [];
/**
* JiraClientService constructor.
* @param StorageInterface $cache
* @param Client $client
* @param Config $config
* @param RouterInterface $router
* @param TeamService $teamService
*/
public function __construct(StorageInterface $cache,
Client $client,
Config $config,
RouterInterface $router,
TeamService $teamService)
{
$this->cache = $cache;
$this->router = $router;
$this->httpClient = $client;
$this->config = $config;
$this->teamService = $teamService;
}
/**
* @param int $teamId
* @param bool $forceReload
* @return KanbanBoard
*/
public function getKanbanBoard(int $teamId = null, bool $forceReload = false): KanbanBoard
{
$team = $this->teamService->getTeam($teamId);
$teamName = $team->getName();
$kanbanBoard = $this->cache->getItem(sprintf("%s-%s", self::CACHE_KEY_KANBANBOARD, $teamName));
if ($forceReload || null === $kanbanBoard) {
$user = $this->config->get('jira.user');
$password = $this->config->get('jira.password');
/** @var Config $kanbanBoardUriParams */
$kanbanBoardUri = $this->config->get('url.jiraKanbanBoard');
$kanbanBoardFilter = $this->config->get('jira.filterFields')->toArray();
$kanbanBoardUri = sprintf(
$kanbanBoardUri,
$team->getFilterId(),
implode(",", $kanbanBoardFilter)
);
$response = $this->httpClient
->setUri($kanbanBoardUri)
->setAuth($user, $password)
->send();
if (!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad JIRA result", $response->getStatusCode());
}
$parsedJsonData = Decoder::decode($response->getBody(), Json::TYPE_ARRAY);
$kanbanBoard = $this->hydrateKanbanBoard($team, $parsedJsonData);
$this->cache->setItem(sprintf("%s-%s", self::CACHE_KEY_KANBANBOARD, $teamName), serialize($kanbanBoard));
} else {
$kanbanBoard = unserialize($kanbanBoard);
}
return $kanbanBoard;
}
/**
* @param string $parentKey
* @return null|string
*/
private function getEpicNameFromParent(string $parentKey): ?string
{
if (array_key_exists($parentKey, $this->cachedEpics)) {
return $this->cachedEpics[$parentKey];
}
$user = $this->config->get('jira.user');
$password = $this->config->get('jira.password');
/** @var Config $kanbanBoardUriParams */
$jiraIssueBaseUrl = $this->config->get('url.jiraIssue');
$kanbanBoardFilter = $this->config->get('jira.filterFields')->toArray();
$kanbanBoardFilterString = implode(",", $kanbanBoardFilter);
$jiraIssueUri = sprintf($jiraIssueBaseUrl, $parentKey, $kanbanBoardFilterString);
$response = $this->httpClient
->setUri($jiraIssueUri)
->setAuth($user, $password)
->send();
if (!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad JIRA result: $jiraIssueUri", $response->getStatusCode());
}
$parsedJsonParentData = Decoder::decode($response->getBody(), Json::TYPE_ARRAY);
if ($parsedJsonParentData['fields'][self::EPIC_TICKET_LINK]) {
$jiraIssueUri = sprintf(
$jiraIssueBaseUrl,
$parsedJsonParentData['fields'][self::EPIC_TICKET_LINK],
$kanbanBoardFilterString
);
$response = $this->httpClient
->setUri($jiraIssueUri)
->setAuth($user, $password)
->send();
if (!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad JIRA result", $response->getStatusCode());
}
$parsedJsonEpicData = Decoder::decode($response->getBody(), Json::TYPE_ARRAY);
$this->cachedEpics[$parentKey] = $parsedJsonEpicData['fields'][self::EPIC_NAME_FIELD];
return $this->cachedEpics[$parentKey];
}
$this->cachedEpics[$parentKey] = null;
return null;
}
/**
* @param Team $team
* @param $parsedJsonData
* @return KanbanBoard
* @todo check if avatar has to be locally cached
*/
private function hydrateKanbanBoard(Team $team, $parsedJsonData): KanbanBoard
{
$kanbanBoard = new KanbanBoard();
$teamBacklogColumns = explode(self::BACKLOG_FIELD_DELIMITER, $team->getBacklogColumn()["jiraStatusName"]);
$teamInprogressColumns = explode(self::BACKLOG_FIELD_DELIMITER, $team->getInprogressColumn()["jiraStatusName"]);
$teamVerificationColumns = explode(self::BACKLOG_FIELD_DELIMITER, $team->getVerificationColumn()["jiraStatusName"]);
$teamDoneColumns = explode(self::BACKLOG_FIELD_DELIMITER, $team->getDoneColumn()["jiraStatusName"]);
foreach ($parsedJsonData['issues'] as $jsonIssue) {
set_time_limit(30);
$kanbanEntry = new KanbanEntry();
$kanbanEntry->setId(intval($jsonIssue['id']))
->setKey($jsonIssue['key'])
->setSummary($jsonIssue['fields']['summary'])
->setIssuePriority($jsonIssue['fields']['priority']['name'])
->setIssuePriorityIcon($jsonIssue['fields']['priority']['iconUrl'])
->setLabels($jsonIssue['fields']['labels'])
->setFixVersions($jsonIssue['fields']['fixVersions']);
$spikeTimeSpent = 0;
array_map(function ($worklog) use (&$spikeTimeSpent) {
$spikeTimeSpent += strtoupper($worklog['comment']) == 'BLOCKED'
? 0
: $worklog['timeSpentSeconds'];
}, $jsonIssue['fields']['worklog']['worklogs']);
$kanbanEntry->setWorklog((int)ceil($spikeTimeSpent / 3600));
$secondsBlocked = 0;
array_map(function ($worklog) use (&$secondsBlocked) {
$secondsBlocked += strtoupper($worklog['comment']) == 'BLOCKED'
? $worklog['timeSpentSeconds']
: 0;
}, $jsonIssue['fields']['worklog']['worklogs']);
$kanbanEntry->setDaysBlocked((int)ceil($secondsBlocked / 28800));
// additional assignees : customfield_10401
if (isset($jsonIssue['fields']['customfield_10401'])) {
foreach ($jsonIssue['fields']['customfield_10401'] as $assignee) {
$avatarUrl = $this->router->generateUri('avatar.image', [
'signum' => $assignee['key'],
]);
$jiraAssignee = new JiraAssignee();
$jiraAssignee->setName($assignee['displayName'])
->setSignum($assignee['key'])
->setEmail(strtolower($assignee['emailAddress']))
->setAvatar($avatarUrl)
->setActive($assignee['active']);
$kanbanEntry->addAdditionalAssignee($jiraAssignee);
}
}
// epicName: have to fetch 2 extra records
if (isset($jsonIssue['fields'][self::EPIC_TICKET_LINK])) {
$epicName = $this->getEpicNameFromParent($jsonIssue['key']);
$kanbanEntry->setEpicName($epicName);
} elseif (isset($jsonIssue['fields']['parent'])) {
$epicName = $this->getEpicNameFromParent($jsonIssue['fields']['parent']['key']);
$kanbanEntry->setEpicName($epicName);
}
// jira status
$jiraStatus = new JiraStatus();
$jiraStatus->setName($jsonIssue['fields']['status']['name'])
->setColor($jsonIssue['fields']['status']['statusCategory']['colorName']);
$kanbanEntry->setStatus($jiraStatus);
// assignee
if ($jsonIssue['fields']['assignee']) {
$avatarUrl = $this->router->generateUri('avatar.image', [
'signum' => $jsonIssue['fields']['assignee']['key'],
]);
$jiraAssignee = new JiraAssignee();
$jiraAssignee->setName($jsonIssue['fields']['assignee']['displayName'])
->setSignum($jsonIssue['fields']['assignee']['key'])
->setEmail(strtolower($jsonIssue['fields']['assignee']['emailAddress']))
->setAvatar($avatarUrl)
->setActive($jsonIssue['fields']['assignee']['active']);
$kanbanEntry->setAssignee($jiraAssignee);
unset($jiraAssignee);
}
// issue type
if ($jsonIssue['fields']['issuetype']) {
$jiraIssueType = new JiraIssueType();
$jiraIssueType->setName($jsonIssue['fields']['issuetype']['name'])
->setDescription($jsonIssue['fields']['issuetype']['description'])
->setIcon($jsonIssue['fields']['issuetype']['iconUrl']);
$kanbanEntry->setIssueType($jiraIssueType);
unset($jiraIssueType);
}
$kanbanEntry->setUpdatedAt(new \DateTime($jsonIssue['fields']['updated']));
if (in_array($jiraStatus->getName(), $teamBacklogColumns)) {
$kanbanBoard->addInbox($kanbanEntry);
}
elseif (in_array($jiraStatus->getName(), $teamInprogressColumns)) {
$kanbanBoard->addInProgress($kanbanEntry);
}
elseif (in_array($jiraStatus->getName(), $teamVerificationColumns)) {
$kanbanBoard->addVerification($kanbanEntry);
}
elseif (in_array($jiraStatus->getName(), $teamDoneColumns)) {
$kanbanBoard->addDone($kanbanEntry);
}
unset($kanbanEntry);
}
return $kanbanBoard;
}
}