Add youtrack service.

master
Lio Novelli 2021-09-20 01:08:42 +02:00
parent 0346b9f29d
commit 9cf4d23012
14 changed files with 516 additions and 371 deletions

View File

@ -64,3 +64,10 @@
- Choices (~new ChoiceQuestion~)
- ~addOption('config')~
** API calls
*** Get csv file
curl 'https://drunomics.myjetbrains.com/youtrack/api/reports/83-554/export/csv?&$top=-1' -H 'Accept: application/json, text/plain, */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H "Authorization: Bearer $TKN" > ~/Documents/Drunomics/workhours/2021/21-09.csv

View File

@ -18,8 +18,7 @@
"guzzlehttp/guzzle": "^7.3",
"php-di/php-di": "^6.3",
"symfony/yaml": "^5.2",
"mpdf/mpdf": "^8.0",
"symfony/translation": "^5.2"
"mpdf/mpdf": "^8.0"
},
"autoload": {
"psr-4": {

139
app/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e106a8658dd1c87ae4ec9212251d4943",
"content-hash": "e6f2abc31fc53724d858e127ce02978e",
"packages": [
{
"name": "guzzlehttp/guzzle",
@ -1506,143 +1506,6 @@
],
"time": "2021-03-17T17:12:15+00:00"
},
{
"name": "symfony/translation",
"version": "v5.2.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "2cc7f45d96db9adfcf89adf4401d9dfed509f4e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/2cc7f45d96db9adfcf89adf4401d9dfed509f4e1",
"reference": "2cc7f45d96db9adfcf89adf4401d9dfed509f4e1",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "^1.15",
"symfony/translation-contracts": "^2.3"
},
"conflict": {
"symfony/config": "<4.4",
"symfony/dependency-injection": "<5.0",
"symfony/http-kernel": "<5.0",
"symfony/twig-bundle": "<5.0",
"symfony/yaml": "<4.4"
},
"provide": {
"symfony/translation-implementation": "2.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/console": "^4.4|^5.0",
"symfony/dependency-injection": "^5.0",
"symfony/finder": "^4.4|^5.0",
"symfony/http-kernel": "^5.0",
"symfony/intl": "^4.4|^5.0",
"symfony/service-contracts": "^1.1.2|^2",
"symfony/yaml": "^4.4|^5.0"
},
"suggest": {
"psr/log-implementation": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\Translation\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"time": "2021-03-23T19:33:48+00:00"
},
{
"name": "symfony/translation-contracts",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
"reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105",
"reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105",
"shasum": ""
},
"require": {
"php": ">=7.2.5"
},
"suggest": {
"symfony/translation-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Translation\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to translation",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"time": "2020-09-28T13:05:58+00:00"
},
{
"name": "symfony/yaml",
"version": "v5.2.5",

View File

@ -4,6 +4,13 @@
tracking service:
youtrack:
auth token: '<value from youtrack hub>'
# reports:
# report short name:
# table:
# header:
# # overrides for table header
# hours: Quantity
# source: <youtrack-url>
projects:
'<short name of first project>':
name: '<Project long name>'
@ -14,3 +21,10 @@ projects:
time column: 4
# time format m - minutes, h - hours
time format: 'm'
labels:
project: Project
# hours: Quantity
hours: Hours
rate: 'Price per hour'
price: Price
locale: 'en_GB'

View File

@ -9,17 +9,34 @@ use RprtCli\Utils\Configuration\ConfigurationService;
use RprtCli\Utils\CsvReport\CsvReport;
use RprtCli\Utils\CsvReport\CsvReportInterface;
use GuzzleHttp\Client;
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
use RprtCli\Utils\TimeTrackingServices\YoutrackService;
# use Symfony\Component\Translation\Translator;
#use Symfony\Component\Translation\Loader\PoFileLoader;
return [
'config.file' => 'rprt.config.yml',
'config.path' => '~/.config/rprt-cli/',
'guzzle' => create()->constructor(Client::class),
'default_locale' => 'en',
// 'translator' => ['default_path' => '%kernel.project_dir%/translations'],
// 'guzzle' => create()->constructor(Client::class),
'guzzle' => get(Client::class),
ConfigurationInterface::class => get(ConfigurationService::class),
ConfigurationService::class => create()->constructor(
get('config.path'),
get('config.file')
),
'config.service' => get(ConfigurationInterface::class),
YoutrackInterface::class => get(YoutrackService::class),
YoutrackService::class => create()->constructor(
get('config.service'),
get('guzzle')
),
'youtrack.service' => get(YoutrackInterface::class),
// 'locale' => get('config.service')->method('get', 'en'),
// Translator::class => create()->constructor('sl')->method('addLoader', 'po', new PoFileLoader),
// 'translator' => get(Translator::class),
CsvReportInterface::class => get(CsvReport::class),
CsvReport::class => create()->constructor(
get('config.service')
@ -27,6 +44,7 @@ return [
'csv.report' => get(CsvReportInterface::class),
RprtCommand::class => create()->constructor(
get('csv.report'),
get('config.service')
)
get('config.service'),
get('youtrack.service')
),
];

View File

@ -10,6 +10,7 @@ require __DIR__ . '/vendor/autoload.php';
$builder = new ContainerBuilder();
$builder->addDefinitions('dependencies.php');
$container = $builder->build();
$application = new Application();
$rprtCommand = $container->get(RprtCommand::class);

View File

@ -1,105 +1,159 @@
<?php
declare(strict_types=1);
// src/Commands/RprtCommand.php;
namespace RprtCli\Commands;
use RprtCli\Utils\Configuration\ConfigurationInterface;
use RprtCli\Utils\CsvReport\CsvReportInterface;
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
// use Symfony\Contracts\Translation\TranslatorInterface;
class RprtCommand extends Command {
use function var_dump;
protected $csv;
/**
* Main file - rprt command.
*/
class RprtCommand extends Command
{
protected $csv;
protected $configuration;
protected $configuration;
public function __construct(CsvReportInterface $csv, ConfigurationInterface $configuration) {
$this->csv = $csv;
$this->configuration = $configuration;
parent::__construct();
}
protected $youtrack;
/**
* Get configuration.
*/
protected function configure(): void {
$this->setName('rprt');
$this->setDescription('Generate monthly report');
// @TODO $this->addUsage('');
$this->addOption('file', 'f', InputOption::VALUE_REQUIRED, 'Specify the input csv file to generate report from.');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
if ($file = $input->getOption('file')) {
$data = $this->csv->getReportData($file);
$table = $this->generateTable($output, $data);
$table->render();
return Command::SUCCESS;
public function __construct(
CsvReportInterface $csv,
ConfigurationInterface $configuration,
YoutrackInterface $youtrack,
?string $name = null
) {
$this->csv = $csv;
$this->configuration = $configuration;
$this->youtrack = $youtrack;
parent::__construct($name);
}
$this->dummyOutput($input, $output);
return Command::SUCCESS;
}
/**
* Get configuration.
*/
protected function configure() : void
{
$this->setName('rprt');
$this->setDescription('Generate monthly report');
// @TODO $this->addUsage('');
$this->addOption(
'file',
'f',
InputOption::VALUE_REQUIRED,
'Specify the input csv file to generate report from.'
);
$this->addOption(
'youtrack',
'y',
InputOption::VALUE_NONE,
'Use youtrack api to get a report. If this option is used --file does not have any effect.'
);
$this->addOption(
'pdf',
'p',
InputOption::VALUE_NONE,
'Create invoice pdf from template.'
);
$this->addOption(
'test',
't',
InputOption::VALUE_NONE,
'Test login into youtrack service.'
);
}
protected function execute(InputInterface $input, OutputInterface $output) : int
{
if ($input->getOption('test')) {
$test = $this->youtrack->testYoutrackapi();
$output->writeln($test);
}
if ($file = $input->getOption('file')) {
$data = $this->csv->getReportData($file);
$table = $this->generateTable($output, $data);
$table->render();
return Command::SUCCESS;
}
$this->dummyOutput($input, $output);
return Command::SUCCESS;
}
/**
* Create table from data that is already inline with configuration.
*/
protected function generateTable($output, $data) {
$table = new Table($output);
$table->setHeaders(['Project', 'Hours', 'Rate', 'Price']);
list($rows, $total_hours, $total_price) = [[], 0, 0];
$projects_config = $this->configuration->get('projects');
foreach ($projects_config as $name => $config) {
if (!isset($data[$name])) {
// @TODO Proper error handling.
var_dump('Project ' . $name . ' is not set!');
continue;
}
$hours = $data[$name];
if ($config['time_format'] === 'm') {
$hours = $hours/60;
}
$price = $hours * (int) $config['price'];
$row = [
$config['name'],
$hours,
$config['price'],
$hours * $config['price'],
];
$rows[] = $row;
$total_hours += $hours;
$total_price += $price;
protected function generateTable(OutputInterface $output, array $data) : Table
{
$table = new Table($output);
$table->setHeaders([
// $this->translator->trans('Project', [], 'messages', 'sl_SI'),
// $this->translator->trans('Hours', [], 'messages', 'sl_SI'),
// $this->translator->trans('Rate'),
// $this->translator->trans('Price'),
'Project', 'Hours', 'Rate', 'Price',
]);
[$rows, $totalHours, $totalPrice] = [[], 0, 0];
$projectsConfig = $this->configuration->get('projects');
foreach ($projectsConfig as $name => $config) {
if (! isset($data[$name])) {
// @TODO Proper error handling.
var_dump('Project ' . $name . ' is not set!');
continue;
}
$hours = $data[$name];
if ($config['time_format'] === 'm') {
$hours /= 60;
}
$price = $hours * (int) $config['price'];
$row = [
$config['name'],
$hours,
$config['price'],
$hours * $config['price'],
];
$rows[] = $row;
$totalHours += $hours;
$totalPrice += $price;
}
$rows[] = new TableSeparator();
// @TODO Check rate in final result.
// $rows[] = [$this->translator->trans('Sum'), $totalHours, $config['price'], $totalPrice];
$rows[] = ['Sum', $totalHours, $config['price'], $totalPrice];
$table->setRows($rows);
return $table;
}
$rows[] = new TableSeparator();
// @TODO Check rate in final result.
$rows[] = ['Zusamen', $total_hours, $config['price'], $total_price];
$table->setRows($rows);
return $table;
}
/**
* Dummy output for testing.
*/
protected function dummyOutput(InputInterface $input, OutputInterface $output): void {
$output->writeln('I will output a nice table.');
$table = New Table($output);
$table->setHeaders(['Project', 'Hours', 'Price']);
$table->setRows([
['LDP', 100, 2600],
['WV', 50, 1300],
new TableSeparator(),
['Zusamen', 150, 3900],
]);
// $table->setStyle('borderless');
$table->render();
}
protected function dummyOutput(InputInterface $input, OutputInterface $output) : void
{
// $txt = $this->translator->trans('From [start-date] to [end-date].', [], 'rprt', 'sl_SI');
// $output->writeln($txt);
$table = new Table($output);
$table->setHeaders(['Project', 'Hours', 'Price']);
$table->setRows([
['LDP', 100, 2600],
['WV', 50, 1300],
new TableSeparator(),
['Zusamen', 150, 3900],
]);
// $table->setStyle('borderless');
$table->render();
}
}

View File

@ -1,32 +1,25 @@
<?php
declare(strict_types=1);
// src/Utils/Configuration/ConfigurationInterface.php
namespace RprtCli\Utils\Configuration;
interface ConfigurationInterface
{
/**
* Checks for config file.
*
* @return string|bool
* Full path to config file or FALSE if it doesn't exist.
*/
// function findConfig($file);
/**
* Get and read the configuration from file.
*/
function getConfig();
public function getConfig() : bool;
/**
* Get a specific configuration for key.
*
* @param string $key
* @param string $key
* Config key.
* @param mixed $default
* @param null|mixed $default
* Default value if config for key is not yet specified.
*
* @return mixed
* Data.
*/
@ -38,5 +31,5 @@ interface ConfigurationInterface
* @param string $key
* Key to check for.
*/
public function exists($key);
public function exists($key) : bool;
}

View File

@ -1,12 +1,17 @@
<?php
declare(strict_types=1);
// src/Utils\Configuration/ConfigurationService.php
namespace RprtCli\Utils\Configuration;
use RprtCli\Utils\Configuration\ConfigurationInterface;
use Symfony\Component\Yaml\Yaml;
use function explode;
use function file_exists;
use function var_dump;
/**
* Read and write configuration.
*
@ -17,99 +22,111 @@ use Symfony\Component\Yaml\Yaml;
*/
class ConfigurationService implements ConfigurationInterface
{
protected const PATHS = [
'/.',
'/.config/rprt-cli/',
'/.rprt/',
];
const PATHS = [
'/.',
'/.config/rprt-cli/',
'/.rprt/',
];
protected $data;
protected $data;
protected $default = null;
protected $default = null;
protected $configFilePath;
protected $configFilePath;
protected $configFileName;
/**
* Yaml service.
*
* @var \Symfony\Component\Yaml\Yaml::parseFile;
*/
protected $yamlParseFile;
protected $configFileName;
/**
* Construct method.
*/
function __construct(string $filepath, string $filename) {
$file = $filepath . $filename;
$this->configFileName = $filename;
$this->configFilePath = $this->findConfig($file);
if ($this->configFilePath) {
$this->getConfig();
public function __construct(string $filepath, string $filename)
{
$file = $filepath . $filename;
$this->configFileName = $filename;
$this->configFilePath = $this->findConfig($file);
if ($this->configFilePath) {
$this->
getConfig();
}
}
}
/**
* Checks for config file.
*
* @param string $filename
* Name of the configuration file.
* @return string|bool
* Full path to config file or FALSE if it doesn't exist.
*/
public function findConfig($filename) {
if (file_exists($filename)) {
return $filename;
public function findConfig($filename)
{
if (file_exists($filename)) {
return $filename;
}
foreach (self::PATHS as $path) {
$fullPath = $_SERVER['HOME'] . $path . $this->configFileName;
if (file_exists($fullPath)) {
return $fullPath;
}
}
// @TODO This should be some kind of error!
var_dump('Config File Not Found!');
return false;
}
foreach (self::PATHS as $path) {
$fullPath = $_SERVER['HOME'] . $path . $this->configFileName;
if (file_exists($fullPath)) {
return $fullPath;
}
}
// @TODO This should be some kind of error!
var_dump('Config File Not Found!');
return FALSE;
}
/**
* Get and read the configuration from file.
*/
public function getConfig() {
if ($this->configFilePath) {
$config = Yaml::parseFile($this->configFilePath);
$this->data = $config;
return TRUE;
public function getConfig() : bool
{
if ($this->configFilePath) {
$config = Yaml::parseFile($this->configFilePath);
$this->data = $config;
return true;
}
// Maybe write an exception for missing config.
// Ask for reconfiguration.
// @TODO This should be some kind of error!
var_dump('Config File Path not found!');
return false;
}
// Maybe write an exception for missing config.
// Ask for reconfiguration.
// @TODO This should be some kind of error!
var_dump('Config File Path not found!');
return FALSE;
}
/**
* {@inheritdoc}
*/
public function get($key, $default = null) {
$this->default = $default;
$segments = explode('.', $key);
$data = $this->data;
foreach ($segments as $segment) {
if (isset($data[$segment])) {
$data = $data[$segment];
} else {
$data = $this->default;
break;
}
/**
* Get a specific configuration for key.
*
* @param string $key
* Config key.
* @param null|mixed $default
* Default value if config for key is not yet specified.
* @return mixed
* Data.
*/
public function get($key, $default = null)
{
$this->default = $default;
$segments = explode('.', $key);
$data = $this->data;
foreach ($segments as $segment) {
if (isset($data[$segment])) {
$data = $data[$segment];
} else {
$data = $this->default;
break;
}
}
return $data;
}
return $data;
}
/**
* {@inheritdoc}
*/
public function exists($key) {
return $this->get($key) !== $this->default;
}
/**
* Checks if key exists in the configuration file.
*
* @param string $key
* Key to check for.
*
* Value of configuration key is not equal to default.
*/
public function exists($key) : bool
{
return $this->get($key) !== $this->default;
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
// src/Utils/Configuration/TranslationService.php
use Symfony\Component\Translation\Translator;
use RprtCli\Utils\Configuration\ConfigurationInterface;
class TranslationService {
protected $config;
public function __construct(ConfigurationInterface $configuration)
{
$this->config = $configuration;
}
}

View File

@ -1,75 +1,102 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\CsvReport;
use RprtCli\Utils\Configuration\ConfigurationInterface;
use RprtCli\Utils\CsvReport\CsvReportInterface;
class CsvReport implements CsvReportInterface {
use function array_key_first;
use function array_keys;
use function fgetcsv;
use function fopen;
use function preg_match;
use function reset;
protected $configurationService;
/**
* Creates a report of projects and hours.
*/
class CsvReport implements CsvReportInterface
{
/**
* A configuration service.
*
* @var ConfigurationInterface
*/
protected $configurationService;
function __construct(ConfigurationInterface $config) {
$this->configurationService = $config;
}
public function getReportData(string $file_path): array {
$output = [];
// @TODO replace with config service.
// $config = $this->dummyConfig()['projects'];
$config = $this->configurationService->get('projects');
foreach (array_keys($config) as $key) {
$output[$key] = 0;
public function __construct(ConfigurationInterface $config)
{
$this->configurationService = $config;
}
if ($file = fopen($file_path, 'r')) {
while (($line = fgetcsv($file)) !== FALSE) {
$parsed = $this->parseCsvFile($line);
// $key = reset(array_keys($parsed));
$key = array_key_first($parsed);
if (isset($output[$key])) {
$output[$key] += (int) reset($parsed);
/**
* {@inheritdoc}
*/
public function getReportData(string $filePath) : array
{
$output = [];
// @TODO replace with config service.
// $config = $this->dummyConfig()['projects'];
$config = $this->configurationService->get('projects');
foreach (array_keys($config) as $key) {
$output[$key] = 0;
}
}
if ($file = fopen($filePath, 'r')) {
while (($line = fgetcsv($file)) !== false) {
$parsed = $this->parseCsvFile($line);
// $key = reset(array_keys($parsed));
$key = array_key_first($parsed);
if (isset($output[$key])) {
$output[$key] += (int) reset($parsed);
}
}
}
return $output;
}
return $output;
}
protected function parseCsvFile(array $raw_data): array {
// $config = $this->dummyConfig();
$config = $this->configurationService->get('projects');
// var_dump($raw_data);
foreach ($config as $key => $project) {
if (preg_match('/'.$project['pattern'].'/', $raw_data[1])) {
return [$key => $raw_data[4]];
}
/**
* Get correct values from the raw data lines of csv.
*
*
* Columns with data are specified in config.
*
* Project key and unit of time spent.
*/
protected function parseCsvFile(array $rawData) : array
{
$config = $this->configurationService->get('projects');
foreach ($config as $key => $project) {
if (preg_match('/' . $project['pattern'] . '/', $rawData[1])) {
return [$key => $rawData[4]];
}
}
return [];
}
return [];
}
protected function dummyConfig(): array {
$config = [
'projects' => [
'LDP' => [
'name' => 'lupus.digital',
'pattern' => 'LDP-[0-9]+',
'price' => 26,
protected function dummyConfig() : array
{
return [
'projects' => [
'LDP' => [
'name' => 'lupus.digital',
'pattern' => 'LDP-[0-9]+',
'price' => 26,
// optional specify columns
],
'WV' => [
'name' => 'Wirtschaftsverlag',
'pattern' => 'WV-[0-9]+',
'price' => 26,
],
'WV' => [
'name' => 'Wirtschaftsverlag',
'pattern' => 'WV-[0-9]+',
'price' => 26,
// optional specify columns
],
'Other' => [
'name' => 'Other projects',
'pattern' => '(?!.\bLDP\b)(?!.\bWV\b)',
'price' => 26,
],
'Other' => [
'name' => 'Other projects',
'pattern' => '(?!.\bLDP\b)(?!.\bWV\b)',
'price' => 26,
// optional specify columns
],
],
];
return $config;
}
],
],
];
}
}

View File

@ -1,22 +1,21 @@
<?php
namespace RprtCli\Utils\CsvReport;
declare(strict_types=1);
namespace RprtCli\Utils\CsvReport;
/**
* Handles creating report data from csv file downloaded from youtrack service.
*/
interface CsvReportInterface {
/**
* Gets project configuration and parses the data.
*
* Calculate number of hours per project.
*/
// protected function parseCsvFile(array $data): array;
interface CsvReportInterface
{
/**
* Returns array of hours per configured projects.
*
* @todo - get data from variable.
*
*
* Project key as key and number of hours as value.
*/
public function getReportData(string $file_path): array;
public function getReportData(string $filePath) : array;
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices;
interface YoutrackInterface
{
/**
* Check if client can sign into youtrack with provided token.
*/
public function testYoutrackapi() : ?string;
/**
* Get the id of the report from configuration.
*/
public function getReportId() : ?string;
/**
* Downloads report and returns file path.
*
* @param string $report_id
* Youtrack internal report id.
*
* @return NULL|string
* If fetch was unsuccssefull return false, otherwise the file path.
*/
public function downloadReport(string $report_id) : ?string;
}

View File

@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
// src/Utils/TimeTrackingServices/YoutrackService.php
namespace RprtCli\Utils\TimeTrackingServices;
use GuzzleHttp\ClientInterface;
use RprtCli\Utils\Configuration\ConfigurationInterface;
class YoutrackService implements YoutrackInterface
{
protected $config;
protected $httpClient;
public function __construct(ConfigurationInterface $config, ClientInterface $http_client)
{
$this->config = $config;
$this->httpClient = $http_client;
}
public function testYoutrackapi(): ?string
{
// Get base url from config or add input.
// Get token or add input.
$path = 'youtrack/api/admin/users/me';
$yt_url = $this->getYtUrl($path);
$yt_token = $this->getYtToken();
$query = ['fields' => 'id,email,fullName'];
$headers = [
"Authorization" => "Bearer $yt_token",
'Cache-Control' =>'no-cache',
];
$me_response = $this->httpClient->request('GET', $yt_url, [
'query' => $query,
'headers' => $headers
]);
$test = (string) $me_response->getBody()->getContents();
$me_json = (array) json_decode($test);
if ($me_json && isset($me_json['fullName'])) {
return $me_json['fullName'];
}
return NULL;
}
public function getReportId(): ?string
{
// --report option value should take precedence.
// @TODO error handling.
$yt_report_id = $this->config->get('youtrack.report_id');
if (!$yt_report_id) {
$yt_report_id = readline('Enter the report id: ');
}
return $yt_report_id;
}
public function downloadReport(string $report_id): ?string
{
$path = "youtrack/api/reports/$report_id/export/csv";
$query = ['$top' => -1];
$yt_token = $this->getYtToken();
$headers = [
'Accept' => 'text/plain, */*',
'Accept-Language' => 'en-US,en;q=0.5',
"Authorization" => "Bearer $yt_token",
];
$csv_response = $this->httpClient->request('GET', $this->getYtUrl($path), [
'headers' => $headers,
'query' => $query,
]);
// Write csv response test into temporary file.
$csv_file = tempnam(sys_get_temp_dir(), 'yt_csv_');
file_put_contents($csv_file, $csv_response->getBody());
return $csv_file;
}
protected function getYtToken(): string {
$yt_token = $this->config->get('tracking_service.youtrack.auth_token', FALSE); //
if (!$yt_token) {
$yt_token = readline('Enter your youtrack authentication token: ');
}
return $yt_token;
}
protected function getYtUrl(string $path = ''): ?string {
$yt_base_url = $this->config->get('tracking_service.youtrack.base_url', FALSE);
if (!$yt_base_url) {
$yt_base_url = readline('Enter base url for of the youtrack service: ');
}
if (!empty($path)) {
$yt_base_url = $yt_base_url . '/' . trim($path, '/');
}
return $yt_base_url;
}
}