* tsp-info endpoint added

* different collector services are now implemented
This commit is contained in:
Dávid Danyi
2017-09-05 19:15:17 +02:00
parent 88527e4ff1
commit efc6e7b0c4
16 changed files with 855 additions and 63 deletions

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Action;
use App\Response\JsonCorsResponse;
use App\Service\JcatInfoCollectorService;
use App\Service\JiraCollectorService;
use App\Service\LabInfoCollectorService;
use App\Service\TrInfoCollectorService;
use App\Service\VacationInfoCollectorService;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
class TspInfoAction implements ServerMiddlewareInterface
{
/**
* @var VacationInfoCollectorService
*/
private $vacationInfoCollectorService;
/**
* @var TrInfoCollectorService
*/
private $trInfoCollectorService;
/**
* @var JcatInfoCollectorService
*/
private $jcatInfoCollectorService;
/**
* @var LabInfoCollectorService
*/
private $labInfoCollectorService;
/**
* @var JiraCollectorService
*/
private $jiraCollectorService;
public function __construct(VacationInfoCollectorService $vacationInfoCollectorService,
TrInfoCollectorService $trInfoCollectorService,
JcatInfoCollectorService $jcatInfoCollectorService,
LabInfoCollectorService $labInfoCollectorService,
JiraCollectorService $jiraCollectorService)
{
$this->vacationInfoCollectorService = $vacationInfoCollectorService;
$this->trInfoCollectorService = $trInfoCollectorService;
$this->jcatInfoCollectorService = $jcatInfoCollectorService;
$this->labInfoCollectorService = $labInfoCollectorService;
$this->jiraCollectorService = $jiraCollectorService;
}
/**
* @param ServerRequestInterface $request
* @param DelegateInterface $delegate
* @return JsonCorsResponse
* @todo lab temperature data
*/
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
return new JsonCorsResponse([
'animGifs' => [],
'praGoals' => $this->trInfoCollectorService->getPraGoals(),
'trProgress' => $this->trInfoCollectorService->getProgressInfo(),
'trFlow' => $this->jcatInfoCollectorService->getTrFlow(),
'expedites' => $this->jiraCollectorService->getExpedites(),
'vacationInfo' => $this->vacationInfoCollectorService->isVacationSoon(),
'labTemperature' => $this->labInfoCollectorService->getLabTemperatureData(),
]);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Action;
use App\Service\JcatInfoCollectorService;
use App\Service\JiraCollectorService;
use App\Service\LabInfoCollectorService;
use App\Service\TrInfoCollectorService;
use App\Service\VacationInfoCollectorService;
use Interop\Container\ContainerInterface;
class TspInfoFactory
{
public function __invoke(ContainerInterface $container)
{
$dataCollectorService = $container->get(VacationInfoCollectorService::class);
$trInfoCollectorService = $container->get(TrInfoCollectorService::class);
$jcatInfoCollectorService = $container->get(JcatInfoCollectorService::class);
$labInfoCollectorService = $container->get(LabInfoCollectorService::class);
$jiraInfoCollectorService = $container->get(JiraCollectorService::class);
return new TspInfoAction(
$dataCollectorService,
$trInfoCollectorService,
$jcatInfoCollectorService,
$labInfoCollectorService,
$jiraInfoCollectorService
);
}
}

View File

@@ -46,10 +46,14 @@ class ConfigProvider
Action\AvatarAction::class => Action\AvatarFactory::class,
Action\HomePageAction::class => Action\HomePageFactory::class,
Action\KanbanAction::class => Action\KanbanFactory::class,
Action\TspInfoAction::class => Action\TspInfoFactory::class,
Service\AvatarService::class => Service\AvatarServiceFactory::class,
Service\JiraCollectorService::class => Service\JiraCollectorServiceFactory::class,
Service\LabInfoCollectorService::class => Service\LabInfoCollectorServiceFactory::class,
Service\VacationInfoCollectorService::class => Service\VacationInfoCollectorServiceFactory::class,
Service\TrInfoCollectorService::class => Service\TrInfoCollectorServiceFactory::class,
Service\JcatInfoCollectorService::class => Service\JcatInfoCollectorServiceFactory::class,
'service.avatarCache' => function(ContainerInterface $container): StorageInterface {
$cache = new FilesytemCache();

View File

@@ -234,6 +234,9 @@ class KanbanBoard implements \JsonSerializable
*/
private function updatedAtReverseSort(array $toSort): array
{
$toSort = array_filter($toSort, function(KanbanEntry $item){
return $item->getAssignee() != null;
});
usort($toSort, function(KanbanEntry $a, KanbanEntry $b){
return $b->getUpdatedAt() <=> $a->getUpdatedAt();
});

View File

@@ -0,0 +1,114 @@
<?php
namespace App\Entity;
class TrProgress implements \JsonSerializable
{
/**
* @var string
*/
private $eriref;
/**
* @var string
*/
private $heading;
/**
* @var string
*/
private $prio;
/**
* @var int
*/
private $lastProgress;
/**
* @return string
*/
public function getEriref(): string
{
return $this->eriref;
}
/**
* @param string $eriref
* @return TrProgress
*/
public function setEriref(string $eriref): TrProgress
{
$this->eriref = $eriref;
return $this;
}
/**
* @return string
*/
public function getHeading(): string
{
return $this->heading;
}
/**
* @param string $heading
* @return TrProgress
*/
public function setHeading(string $heading): TrProgress
{
$this->heading = $heading;
return $this;
}
/**
* @return string
*/
public function getPrio(): string
{
return $this->prio;
}
/**
* @param string $prio
* @return TrProgress
*/
public function setPrio(string $prio): TrProgress
{
$this->prio = $prio;
return $this;
}
/**
* @return int
*/
public function getLastProgress(): int
{
return $this->lastProgress;
}
/**
* @param int $lastProgress
* @return TrProgress
*/
public function setLastProgress(int $lastProgress): TrProgress
{
$this->lastProgress = $lastProgress;
return $this;
}
/**
* @return array
*/
function jsonSerialize()
{
return [
'eriref' => $this->getEriref(),
'heading' => $this->getHeading(),
'prio' => $this->getPrio(),
'lastProgress' => $this->getLastProgress(),
];
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class VacationDay implements \JsonSerializable
{
/**
* @var int
*/
private $day;
/**
* @var string[]|ArrayCollection
*/
private $signums;
public function __construct()
{
$this->signums = new ArrayCollection();
}
/**
* @return int
*/
public function getDay(): int
{
return $this->day;
}
/**
* @param int $day
* @return VacationDay
*/
public function setDay(int $day): VacationDay
{
$this->day = $day;
return $this;
}
/**
* @return ArrayCollection|string[]
*/
public function getSignums()
{
return $this->signums;
}
/**
* @param ArrayCollection|string[] $signums
* @return VacationDay
*/
public function setSignums($signums): VacationDay
{
$this->signums = $signums;
return $this;
}
/**
* @param string $signum
* @return VacationDay
*/
public function addSignum(string $signum): VacationDay
{
if(!$this->signums->contains($signum)) {
$this->signums->add($signum);
}
return $this;
}
/**
* @param string $signum
* @return VacationDay
*/
public function removeSignum(string $signum): VacationDay
{
if($this->signums->contains($signum)) {
$this->signums->removeElement($signum);
}
return $this;
}
/**
* @return array
*/
function jsonSerialize()
{
return [
'day' => $this->getDay(),
'signums' => $this->getSignums()->getValues(),
];
}
}

View File

@@ -4,6 +4,8 @@ namespace App\Service;
use Zend\Cache\Storage\StorageInterface;
use Zend\Config\Config;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\TextResponse;
use Zend\Expressive\Router\RouterInterface;
use Zend\Http\Client;
@@ -72,6 +74,8 @@ class AvatarService
}
/**
* Returns avatar image data as string
*
* @param string $signum
* @return string
*/
@@ -83,4 +87,18 @@ class AvatarService
return $this->cache->getItem($signum);
}
public function getUserAvatarResponse(string $signum)
{
$localAvatarFile = "public/avatars/$signum";
if(file_exists($localAvatarFile)) {
$fp = fopen($localAvatarFile,"r+");
$response = new Response($fp);
} else {
$response = new TextResponse($this->getAvatarData($signum));
}
return $response
->withHeader('Content-type', 'image/png');
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Service;
use League\Csv\Reader;
use League\Csv\Statement;
use Zend\Config\Config;
use Zend\Http\Client;
class JcatInfoCollectorService
{
/**
* @var Config
*/
private $config;
/**
* @var Client
*/
private $httpClient;
/**
* JiraClientService constructor.
* @param Client $client
* @param Config $config
*/
public function __construct(Client $client, Config $config)
{
$this->httpClient = $client;
$this->config = $config;
}
public function getTrFlow()
{
/** @var Config $kanbanBoardUriParams */
$trFlowUri = $this->config->get('url.jcatTrFlow');
$response = $this->httpClient
->setUri($trFlowUri)
->send();
if (!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad JCAT result", $response->getStatusCode());
}
return $this->parseFlowHtmlResult($response->getBody());
}
private function parseFlowHtmlResult(string $html)
{
$xmlErrorHandling = libxml_use_internal_errors(TRUE);
$domDocument = new \DOMDocument();
$domDocument->loadHTML($html);
libxml_clear_errors();
libxml_use_internal_errors($xmlErrorHandling);
$documentXpath = new \DOMXPath($domDocument);
/** @var \DOMNodeList $elements */
$elements = $documentXpath->query('//tr/td[1]');
$result = [];
/** @var \DOMElement $element */
foreach ($elements as $element) {
$result[] = trim($element->nodeValue);
}
return array_count_values($result);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Service;
use Interop\Container\ContainerInterface;
use Zend\Config\Config;
use Zend\Http\Client;
class JcatInfoCollectorServiceFactory
{
public function __invoke(ContainerInterface $container)
{
$configArray = $container->get('config');
$httpClient = $container->get(Client::class);
$config = new Config($configArray['app.config']);
return new JcatInfoCollectorService($httpClient, $config);
}
}

View File

@@ -41,6 +41,45 @@ class JiraCollectorService
$this->config = $config;
}
/**
* @return array
*/
public function getExpedites(): array
{
$user = $this->config->get('jira.user');
$password = $this->config->get('jira.password');
/** @var Config $expediteUriParams */
$expediteUriParams = $this->config->get('url.jiraTspExpedites');
$result = [];
foreach ($expediteUriParams['filters'] as $type => $filterId) {
$jiraResultUri = sprintf(
$expediteUriParams['baseUrl'],
$filterId
);
$response = $this->httpClient
->setUri($jiraResultUri)
->setAuth($user, $password)
->send();
if(!$response->isSuccess()) {
throw new \UnexpectedValueException(
sprintf("Bad JIRA result when trying to load: %s\n%s",
$jiraResultUri,
$response->getBody()
),
$response->getStatusCode()
);
}
$parsedJsonData = Decoder::decode($response->getBody(), Json::TYPE_OBJECT);
$result[$type] = $parsedJsonData->total;
}
return $result;
}
/**
* @return KanbanBoard
*/
@@ -58,8 +97,8 @@ class JiraCollectorService
);
$response = $this->httpClient
->setAuth($user, $password)
->setUri($kanbanBoardUri)
->setAuth($user, $password)
->send();
if(!$response->isSuccess()) {

View File

@@ -2,12 +2,27 @@
namespace App\Service;
use Symfony\Component\CssSelector\CssSelectorConverter;
use App\Entity\TrProgress;
use League\Csv\Reader;
use League\Csv\Statement;
use Zend\Config\Config;
use Zend\Http\Client;
class TrInfoCollectorService
{
const UNIT_CORE = 'core';
const UNIT_SIG = 'sig';
const UNIT_TADE = 'tade';
const MHO_MAP = [
'ETH-TSPCW-D' => self::UNIT_CORE,
'ETH-TSPCORE' => self::UNIT_CORE,
'XTS-TSP-SIG' => self::UNIT_SIG,
'XTS-TSPSIGD' => self::UNIT_SIG,
'ETH-TADE-DE' => self::UNIT_TADE,
'ETH-TADE-MA' => self::UNIT_TADE,
];
/**
* @var Config
*/
@@ -18,15 +33,6 @@ class TrInfoCollectorService
*/
private $httpClient;
/**
* @var array
*/
private $tempSensors = [
'Temp 5' => 'back_left',
'Temp 4' => 'back_middle',
'Temp 3' => 'back_right',
];
/**
* JiraClientService constructor.
* @param Client $client
@@ -38,49 +44,139 @@ class TrInfoCollectorService
$this->config = $config;
}
public function getLabTemperatureData()
/**
* @return array
*/
public function getProgressInfo()
{
/** @var Config $labTemperatureUrl */
$labTemperatureUrl = $this->config->get('url.labTemperatureUrl');
$user = $this->config->get('mhweb.user');
$password = $this->config->get('mhweb.password');
/** @var string $trProgressUri */
$trProgressUri = $this->config->get('url.mhWebTrProgress');
$response = $this->httpClient
->setUri($labTemperatureUrl)
->setAuth($user, $password)
->setUri($trProgressUri)
->send();
if(!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad LAB result", $response->getStatusCode());
if (!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad MHWEB result", $response->getStatusCode());
}
return $this->parseHtml($response->getBody());
$csvResponse = $response->getBody();
$csvReader = Reader::createFromString($csvResponse);
$csvReader->setHeaderOffset(0);
return $this->parseProgressCsvRecords($csvReader);
}
private function parseHtml($html): array
public function getPraGoals()
{
$cssToXpathConverter = new CssSelectorConverter();
$xpathLabelQuery = $cssToXpathConverter->toXPath('a.sensormenu.isnotpaused');
$xpathValueQuery = $cssToXpathConverter->toXPath('div.graphlabel2');
$user = $this->config->get('mhweb.user');
$password = $this->config->get('mhweb.password');
/** @var string $trProgressUri */
$trProgressUri = $this->config->get('url.mhWebPraGoals');
$xmlErrorHandling = libxml_use_internal_errors(TRUE);
$domDocument = new \DOMDocument();
$domDocument->loadHTML($html);
libxml_clear_errors();
libxml_use_internal_errors($xmlErrorHandling);
$response = $this->httpClient
->setAuth($user, $password)
->setUri($trProgressUri)
->send();
$documentXpath = new \DOMXPath($domDocument);
/** @var \DOMNodeList $element */
$element = $documentXpath->query($xpathLabelQuery);
if (!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad MHWEB result", $response->getStatusCode());
}
$thing = [];
/** @var \DOMElement $item */
foreach($element as $item) {
$sensorName = trim($item->nodeValue);
if( in_array($sensorName, array_keys($this->tempSensors)) ){
/** @var \DOMNodeList $element */
$valueElement = $documentXpath->query($xpathValueQuery, $item->parentNode->parentNode);
$thing[$this->tempSensors[$sensorName]] = $valueElement->item(0)->nodeValue;
$csvResponse = $response->getBody();
$csvReader = Reader::createFromString($csvResponse);
$csvReader->setHeaderOffset(0);
$statement = new Statement();
$csvRecords = $statement
->process($csvReader);
$goalCounter = $this->initGoalCounter();
foreach ($csvRecords as $record) {
$goalCounter[self::MHO_MAP[$record["mho"]]][$record["prio"]]++;
}
return $this->caltulatePraBaseDiff($goalCounter);
}
private function caltulatePraBaseDiff($goalCounter): array
{
$praBaseData = $this->config->get('pra.baseData');
foreach ($goalCounter as $mho => &$counters) {
foreach (['A', 'B', 'C'] as $prio) {
$counters[$prio] = $counters[$prio] - $praBaseData[$mho][$prio];
}
}
return $goalCounter;
}
private function parseProgressCsvRecords(Reader $csvReader)
{
$statement = new Statement();
$csvRecords = $statement
->process($csvReader);
$trProgressList = [];
foreach ($csvRecords as $csvRecord) {
$trProgress = new TrProgress();
$trProgress->setEriref($csvRecord["eriref"])
->setHeading($csvRecord["heading"])
->setPrio($csvRecord["prio"])
->setLastProgress($this->getLastProgressInDay($csvRecord))
;
$trProgressList[] = $trProgress;
}
usort($trProgressList, function(TrProgress $a, TrProgress $b){
return $b->getLastProgress() <=> $a->getLastProgress();
});
return $trProgressList;
}
/**
* @param array $csvRecord
* @return int
* @todo fix the BS with tuesday or whatever
*/
private function getLastProgressInDay(array $csvRecord): int
{
$lastProgressDate = null;
$hasNoProgressDate = false;
try {
$lastProgressDate = new \DateTime(str_replace(" - "," ", $csvRecord["lastprogressdate"]));
} catch(\Exception $e) {
$hasNoProgressDate = true;
}
try {
$lastDesignDate = new \DateTime($csvRecord["lastdesigndate"]);
if($hasNoProgressDate || $lastDesignDate > $lastProgressDate) {
$lastProgressDate = $lastDesignDate;
}
} catch (\Exception $e) {
if($hasNoProgressDate) {
return 0;
}
}
return $thing;
$now = new \DateTime();
$dateDiff = $now->diff($lastProgressDate);
return $dateDiff->days;
}
private function initGoalCounter(): array
{
$emptyPrios = ['A' => 0, 'B' => 0, 'C' => 0];
return [
self::UNIT_CORE => $emptyPrios,
self::UNIT_SIG => $emptyPrios,
self::UNIT_TADE => $emptyPrios,
];
}
}

View File

@@ -2,7 +2,11 @@
namespace App\Service;
use Symfony\Component\CssSelector\CssSelectorConverter;
use App\Entity\VacationDay;
use Doctrine\Common\Collections\ArrayCollection;
use SVG\Nodes\Embedded\SVGImageElement;
use SVG\Nodes\Shapes\SVGRect;
use SVG\SVGImage;
use Zend\Config\Config;
use Zend\Http\Client;
@@ -19,13 +23,9 @@ class VacationInfoCollectorService
private $httpClient;
/**
* @var array
* @var VacationDay[]|ArrayCollection
*/
private $tempSensors = [
'Temp 5' => 'back_left',
'Temp 4' => 'back_middle',
'Temp 3' => 'back_right',
];
private $vacations;
/**
* JiraClientService constructor.
@@ -36,29 +36,71 @@ class VacationInfoCollectorService
{
$this->httpClient = $client;
$this->config = $config;
$this->vacations = new ArrayCollection();
}
public function getLabTemperatureData()
/**
* @return bool
*/
public function isVacationSoon(): bool
{
/** @var Config $labTemperatureUrl */
$labTemperatureUrl = $this->config->get('url.labTemperatureUrl');
return count($this->getVacationData()) > 0;
}
/**
* @return string
*/
public function createVacationSvgImage(): string
{
$vacationData = $this->getVacationData();
$svgImage = new SVGImage(1000,400);
$doc = $svgImage->getDocument();
for ($i = 0; $i<5; $i++) {
for($j=0; $j<3; $j++) {
$rect = new SVGRect($i*40+5*$i+5, 40*$j+5*$j+5, 40, 40);
$rect->setStyle('fill', '#0000FF');
$img = new SVGImageElement("/avatars/edvidan", $i*40+5*($i+1), 40*$j+5*$j+5, 20,20);
$doc->addChild($rect)
->addChild($img);
}
}
// blue 40x40 square at (0, 0)
return $svgImage;
}
public function getVacationData()
{
/** @var Config $vacationPageUrl */
$vacationPageUrl = $this->config->get('url.vacation');
$response = $this->httpClient
->setUri($labTemperatureUrl)
->setUri($vacationPageUrl)
->send();
if(!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad LAB result", $response->getStatusCode());
throw new \UnexpectedValueException("Bad vacation result", $response->getStatusCode());
}
return $this->parseHtml($response->getBody());
}
/**
* @param $html
* @return VacationDay[]
*/
private function parseHtml($html): array
{
$cssToXpathConverter = new CssSelectorConverter();
$xpathLabelQuery = $cssToXpathConverter->toXPath('a.sensormenu.isnotpaused');
$xpathValueQuery = $cssToXpathConverter->toXPath('div.graphlabel2');
$peopleQuery = '//table[@class="vacation"]//td[@class="v_user_h"]';
$xmlErrorHandling = libxml_use_internal_errors(TRUE);
$domDocument = new \DOMDocument();
@@ -68,19 +110,63 @@ class VacationInfoCollectorService
$documentXpath = new \DOMXPath($domDocument);
/** @var \DOMNodeList $element */
$element = $documentXpath->query($xpathLabelQuery);
$element = $documentXpath->query($peopleQuery);
$now = new \DateTime();
$todaysDayNumber = intval($now->format("d"));
$todaysWeekDay = intval($now->format("N"));
$thisWeekFirstDay = $todaysDayNumber - $todaysWeekDay + 1;
$thisWeekLastDay = $thisWeekFirstDay + 4;
$nextWeekFirstDay = $thisWeekFirstDay + 7;
$nextWeekLastDay = $nextWeekFirstDay + 4;
$thing = [];
/** @var \DOMElement $item */
foreach($element as $item) {
$sensorName = trim($item->nodeValue);
if( in_array($sensorName, array_keys($this->tempSensors)) ){
/** @var \DOMNodeList $element */
$valueElement = $documentXpath->query($xpathValueQuery, $item->parentNode->parentNode);
$thing[$this->tempSensors[$sensorName]] = $valueElement->item(0)->nodeValue;
$thisWeekFirstDay = ($thisWeekFirstDay < 1)
? 1
: $thisWeekFirstDay;
$dateCells = $documentXpath->query('..//td', $item);
// $user = trim($dateCells->item(0)->textContent);
$signumHref = $dateCells->item(0)->firstChild->getAttribute('href');
preg_match("/id=(.*)$/msi",$signumHref,$matchResult);
$signum = $matchResult[1];
for($i = $thisWeekFirstDay; $i < $thisWeekLastDay+1; $i++) {
$vacationDay = $this->getVacationDay($i);
$isVacation = $documentXpath->query(
'.//div[@class="book book_n_v"]',
$dateCells->item($i)
);
if ($isVacation->length) {
$vacationDay->addSignum($signum);
}
}
for($i=$nextWeekFirstDay; $i<$nextWeekLastDay+1; $i++) {
$vacationDay = $this->getVacationDay($i);
$isVacation = $documentXpath->query(
'.//div[@class="book book_n_v"]',
$dateCells->item($i)
);
if ($isVacation->length) {
$vacationDay->addSignum($signum);
}
}
}
return $thing;
return $this->vacations->filter(function(VacationDay $item){
return count($item->getSignums());
})->getValues();
}
private function getVacationDay(int $day): VacationDay
{
if(!isset($this->vacations[$day])) {
$this->vacations[$day] = (new VacationDay())->setDay($day);
}
return $this->vacations[$day];
}
}