* watcher api endpoint implemented
This commit is contained in:
parent
9d3fa5fa9d
commit
3d42f16c38
@ -15,6 +15,7 @@ return [
|
||||
|
||||
'url.jiraAvatar' => 'https://cc-jira.rnd.ki.sw.ericsson.se/secure/useravatar?ownerId=%s',
|
||||
'url.jiraIssue' => 'https://cc-jira.rnd.ki.sw.ericsson.se/rest/api/2/issue/%s?fields=%s',
|
||||
'url.jiraWatchedIssues' => 'https://cc-jira.rnd.ki.sw.ericsson.se/rest/api/2/search?jql=%s&maxResults=1000&fields=%s',
|
||||
'url.jiraKanbanBoard' => 'https://cc-jira.rnd.ki.sw.ericsson.se/rest/api/2/search?jql=filter=%s&maxResults=1000&fields=%s',
|
||||
'jira.filterFields' => [
|
||||
'summary',
|
||||
|
||||
@ -47,4 +47,5 @@ return function (Application $app, MiddlewareFactory $factory, ContainerInterfac
|
||||
|
||||
$app->get('/avatars/{signum}', App\Handler\AvatarHandler::class,'avatar.image');
|
||||
$app->get('/api/kanban/{teamId:\d+}', App\Handler\KanbanHandler::class,'api.team.kanban');
|
||||
$app->get('/api/watched/{teamId:\d+}', App\Handler\WatchedHandler::class,'api.team.watched');
|
||||
};
|
||||
|
||||
1
src/App/ConfigProvider.php
Normal file → Executable file
1
src/App/ConfigProvider.php
Normal file → Executable file
@ -42,6 +42,7 @@ class ConfigProvider
|
||||
Handler\SlidePositionHandler::class => Handler\SlidePositionHandlerFactory::class,
|
||||
Handler\AvatarHandler::class => Handler\AvatarHandlerFactory::class,
|
||||
Handler\KanbanHandler::class =>Handler\KanbanHandlerFactory::class,
|
||||
Handler\WatchedHandler::class =>Handler\WatchedHandlerFactory::class,
|
||||
|
||||
Service\TeamService::class => Service\TeamServiceFactory::class,
|
||||
Service\SlideManager::class => Service\SlideManagerFactory::class,
|
||||
|
||||
14
src/App/Entity/KanbanBoard.php
Normal file → Executable file
14
src/App/Entity/KanbanBoard.php
Normal file → Executable file
@ -8,20 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class KanbanBoard implements \JsonSerializable
|
||||
{
|
||||
// const PRIO_TRIVIAL = 0;
|
||||
// const PRIO_MINOR = 1;
|
||||
// const PRIO_MAJOR = 2;
|
||||
// const PRIO_CRITICAL = 3;
|
||||
// const PRIO_BLOCKER = 4;
|
||||
|
||||
// private $priorityMap = [
|
||||
// 'Trivial' => self::PRIO_TRIVIAL,
|
||||
// 'Minor' => self::PRIO_MINOR,
|
||||
// 'Major' => self::PRIO_MAJOR,
|
||||
// 'Critical' => self::PRIO_CRITICAL,
|
||||
// 'Blocker' => self::PRIO_BLOCKER,
|
||||
// ];
|
||||
|
||||
/**
|
||||
* @var KanbanEntry[]|ArrayCollection
|
||||
*/
|
||||
|
||||
105
src/App/Entity/WatchedIssue.php
Executable file
105
src/App/Entity/WatchedIssue.php
Executable file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class WatchedIssue implements \JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
private $issue;
|
||||
|
||||
/** @var string */
|
||||
private $summary;
|
||||
|
||||
/** @var string */
|
||||
private $assignee;
|
||||
|
||||
/** @var WatchedIssueComment */
|
||||
private $comment;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIssue(): ?string
|
||||
{
|
||||
return $this->issue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $issue
|
||||
* @return WatchedIssue
|
||||
*/
|
||||
public function setIssue(string $issue): WatchedIssue
|
||||
{
|
||||
$this->issue = $issue;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSummary(): ?string
|
||||
{
|
||||
return $this->summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $summary
|
||||
* @return WatchedIssue
|
||||
*/
|
||||
public function setSummary(string $summary): WatchedIssue
|
||||
{
|
||||
$this->summary = $summary;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAssignee(): ?string
|
||||
{
|
||||
return $this->assignee;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $assignee
|
||||
* @return WatchedIssue
|
||||
*/
|
||||
public function setAssignee(string $assignee): WatchedIssue
|
||||
{
|
||||
$this->assignee = $assignee;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WatchedIssueComment
|
||||
*/
|
||||
public function getComment(): ?WatchedIssueComment
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WatchedIssueComment $comment
|
||||
* @return WatchedIssue
|
||||
*/
|
||||
public function setComment(WatchedIssueComment $comment): WatchedIssue
|
||||
{
|
||||
$this->comment = $comment;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'issue' => $this->getIssue(),
|
||||
'summary' => $this->getSummary(),
|
||||
'assignee' => $this->getAssignee(),
|
||||
'comment' => $this->getComment() ?? new WatchedIssueComment(),
|
||||
];
|
||||
}
|
||||
}
|
||||
105
src/App/Entity/WatchedIssueComment.php
Executable file
105
src/App/Entity/WatchedIssueComment.php
Executable file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class WatchedIssueComment implements \JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
private $signum;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var string */
|
||||
private $content;
|
||||
|
||||
/** @var \DateTimeImmutable */
|
||||
private $date;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSignum(): ?string
|
||||
{
|
||||
return $this->signum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $signum
|
||||
* @return WatchedIssueComment
|
||||
*/
|
||||
public function setSignum(string $signum): WatchedIssueComment
|
||||
{
|
||||
$this->signum = $signum;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return WatchedIssueComment
|
||||
*/
|
||||
public function setName(string $name): WatchedIssueComment
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContent(): ?string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return WatchedIssueComment
|
||||
*/
|
||||
public function setContent(string $content): WatchedIssueComment
|
||||
{
|
||||
$this->content = $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeImmutable
|
||||
*/
|
||||
public function getDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTimeImmutable $date
|
||||
* @return WatchedIssueComment
|
||||
*/
|
||||
public function setDate(\DateTimeImmutable $date): WatchedIssueComment
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'signum' => $this->getSignum(),
|
||||
'name' => $this->getName(),
|
||||
'content' => $this->getContent(),
|
||||
'date' => $this->getDate() ? $this->getDate()->format("Y-m-d H:i:s") : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
40
src/App/Handler/WatchedHandler.php
Executable file
40
src/App/Handler/WatchedHandler.php
Executable file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Handler;
|
||||
|
||||
use App\Entity\KanbanBoard;
|
||||
use App\Service\JiraCollectorService;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class WatchedHandler implements RequestHandlerInterface
|
||||
{
|
||||
/** @var JiraCollectorService */
|
||||
private $dataCollector;
|
||||
|
||||
/**
|
||||
* KanbanAction constructor.
|
||||
* @param JiraCollectorService $dataCollectorService
|
||||
*/
|
||||
public function __construct(JiraCollectorService $dataCollectorService)
|
||||
{
|
||||
$this->dataCollector = $dataCollectorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @todo filterId
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$teamId = (int)$request->getAttribute('teamId');
|
||||
/** @var KanbanBoard $kanbanResult */
|
||||
$kanbanResult = $this->dataCollector->getTeamWatchedIssues($teamId);
|
||||
return new JsonResponse($kanbanResult);
|
||||
}
|
||||
}
|
||||
22
src/App/Handler/WatchedHandlerFactory.php
Executable file
22
src/App/Handler/WatchedHandlerFactory.php
Executable file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Handler;
|
||||
|
||||
use App\Service\JiraCollectorService;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class WatchedHandlerFactory
|
||||
{
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
* @return WatchedHandler
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
/** @var JiraCollectorService $dataCollectorService */
|
||||
$dataCollectorService = $container->get(JiraCollectorService::class);
|
||||
return new WatchedHandler($dataCollectorService);
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,8 @@ use App\Entity\JiraStatus;
|
||||
use App\Entity\KanbanBoard;
|
||||
use App\Entity\KanbanEntry;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\WatchedIssue;
|
||||
use App\Entity\WatchedIssueComment;
|
||||
use Zend\Cache\Storage\StorageInterface;
|
||||
use Zend\Config\Config;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
@ -25,6 +27,20 @@ class JiraCollectorService
|
||||
const EPIC_TICKET_LINK = 'customfield_11711';
|
||||
const EPIC_NAME_FIELD = 'customfield_11712';
|
||||
|
||||
const STATUS_DONE = 'done';
|
||||
const STATUS_CLOSED = 'closed';
|
||||
const STATUS_ANSWERED = 'answered';
|
||||
const STATUS_RESOLVED = 'resolved';
|
||||
|
||||
const IGNORED_STATUSES = [
|
||||
self::STATUS_DONE,
|
||||
self::STATUS_CLOSED,
|
||||
self::STATUS_ANSWERED,
|
||||
self::STATUS_RESOLVED,
|
||||
];
|
||||
|
||||
const WATCH_FILTER = 'status NOT IN (%s) AND watcher in (%s) AND "Last change" not in (%s) ORDER BY "Last Comment"';
|
||||
|
||||
/** @var StorageInterface */
|
||||
private $cache;
|
||||
|
||||
@ -110,6 +126,93 @@ class JiraCollectorService
|
||||
return $kanbanBoard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $teamId
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getTeamWatchedIssues(int $teamId)
|
||||
{
|
||||
$team = $this->teamService->getTeam($teamId);
|
||||
$members = array_map(function(array $member): string {
|
||||
return $member['signum'];
|
||||
}, $team->getMembers());
|
||||
$preparedMembers = sprintf('"%s"', implode('","', $members));
|
||||
$filter = sprintf(
|
||||
self::WATCH_FILTER,
|
||||
sprintf('"%s"', implode('","', self::IGNORED_STATUSES)),
|
||||
$preparedMembers, $preparedMembers
|
||||
);
|
||||
$user = $this->config->get('jira.user');
|
||||
$password = $this->config->get('jira.password');
|
||||
/** @var Config $kanbanBoardUriParams */
|
||||
$jiraWatchedIssues = $this->config->get('url.jiraWatchedIssues');
|
||||
$kanbanBoardFilterFields = [
|
||||
'assignee',
|
||||
'summary',
|
||||
'comment',
|
||||
];
|
||||
$issueFields = implode(",", $kanbanBoardFilterFields);
|
||||
$jiraIssueUri = sprintf($jiraWatchedIssues, $filter, $issueFields);
|
||||
|
||||
$response = $this->httpClient
|
||||
->setUri($jiraIssueUri)
|
||||
->setAuth($user, $password)
|
||||
->send();
|
||||
|
||||
if (!$response->isSuccess()) {
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
"Bad JIRA result for URL:\n%s",
|
||||
$jiraIssueUri
|
||||
), $response->getStatusCode());
|
||||
}
|
||||
|
||||
return $this->hydrateWatchedIssues(Decoder::decode($response->getBody(), Json::TYPE_ARRAY), $members);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parsedJson
|
||||
* @param array $members
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function hydrateWatchedIssues(array $parsedJson, array $members)
|
||||
{
|
||||
/** @var WatchedIssue[] $hydratedResult */
|
||||
$hydratedResult = [];
|
||||
|
||||
foreach ($parsedJson['issues'] as $issueJson) {
|
||||
$issueItem = new WatchedIssue();
|
||||
$issueItem->setIssue($issueJson['key'])
|
||||
->setAssignee($issueJson['fields']['assignee']['name'])
|
||||
->setSummary(html_entity_decode($issueJson['fields']['summary']));
|
||||
$issueComments = [];
|
||||
foreach ($issueJson['fields']['comment']['comments'] as $commentJson) {
|
||||
$issueComment = new WatchedIssueComment();
|
||||
$issueComment->setSignum($commentJson['updateAuthor']['name'])
|
||||
->setName($commentJson['updateAuthor']['displayName'])
|
||||
->setContent(html_entity_decode($commentJson['body']))
|
||||
->setDate(new \DateTimeImmutable($commentJson['updated']));
|
||||
$issueComments[] = $issueComment;
|
||||
}
|
||||
usort($issueComments, function(WatchedIssueComment $a, WatchedIssueComment $b) {
|
||||
return $a->getDate() <=> $b->getDate();
|
||||
});
|
||||
$lastComment = array_pop($issueComments);
|
||||
unset($issueComments);
|
||||
$issueItem->setComment($lastComment);
|
||||
$hydratedResult[] = $issueItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* sanity check, we only want items where last change was a comment, but that is not possible
|
||||
* with JIRA jql at the moment.
|
||||
*/
|
||||
return array_filter($hydratedResult, function(WatchedIssue $issue) use ($members) {
|
||||
return !in_array($issue->getComment()->getSignum(), $members);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $parentKey
|
||||
* @return null|string
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user