Add youtrack service.
parent
0346b9f29d
commit
9cf4d23012
|
@ -64,3 +64,10 @@
|
||||||
- Choices (~new ChoiceQuestion~)
|
- Choices (~new ChoiceQuestion~)
|
||||||
- ~addOption('config')~
|
- ~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
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
"guzzlehttp/guzzle": "^7.3",
|
"guzzlehttp/guzzle": "^7.3",
|
||||||
"php-di/php-di": "^6.3",
|
"php-di/php-di": "^6.3",
|
||||||
"symfony/yaml": "^5.2",
|
"symfony/yaml": "^5.2",
|
||||||
"mpdf/mpdf": "^8.0",
|
"mpdf/mpdf": "^8.0"
|
||||||
"symfony/translation": "^5.2"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "e106a8658dd1c87ae4ec9212251d4943",
|
"content-hash": "e6f2abc31fc53724d858e127ce02978e",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/guzzle",
|
"name": "guzzlehttp/guzzle",
|
||||||
|
@ -1506,143 +1506,6 @@
|
||||||
],
|
],
|
||||||
"time": "2021-03-17T17:12:15+00:00"
|
"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",
|
"name": "symfony/yaml",
|
||||||
"version": "v5.2.5",
|
"version": "v5.2.5",
|
||||||
|
|
|
@ -4,6 +4,13 @@
|
||||||
tracking service:
|
tracking service:
|
||||||
youtrack:
|
youtrack:
|
||||||
auth token: '<value from youtrack hub>'
|
auth token: '<value from youtrack hub>'
|
||||||
|
# reports:
|
||||||
|
# report short name:
|
||||||
|
# table:
|
||||||
|
# header:
|
||||||
|
# # overrides for table header
|
||||||
|
# hours: Quantity
|
||||||
|
# source: <youtrack-url>
|
||||||
projects:
|
projects:
|
||||||
'<short name of first project>':
|
'<short name of first project>':
|
||||||
name: '<Project long name>'
|
name: '<Project long name>'
|
||||||
|
@ -14,3 +21,10 @@ projects:
|
||||||
time column: 4
|
time column: 4
|
||||||
# time format m - minutes, h - hours
|
# time format m - minutes, h - hours
|
||||||
time format: 'm'
|
time format: 'm'
|
||||||
|
labels:
|
||||||
|
project: Project
|
||||||
|
# hours: Quantity
|
||||||
|
hours: Hours
|
||||||
|
rate: 'Price per hour'
|
||||||
|
price: Price
|
||||||
|
locale: 'en_GB'
|
||||||
|
|
|
@ -9,17 +9,34 @@ use RprtCli\Utils\Configuration\ConfigurationService;
|
||||||
use RprtCli\Utils\CsvReport\CsvReport;
|
use RprtCli\Utils\CsvReport\CsvReport;
|
||||||
use RprtCli\Utils\CsvReport\CsvReportInterface;
|
use RprtCli\Utils\CsvReport\CsvReportInterface;
|
||||||
use GuzzleHttp\Client;
|
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 [
|
return [
|
||||||
'config.file' => 'rprt.config.yml',
|
'config.file' => 'rprt.config.yml',
|
||||||
'config.path' => '~/.config/rprt-cli/',
|
'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),
|
ConfigurationInterface::class => get(ConfigurationService::class),
|
||||||
ConfigurationService::class => create()->constructor(
|
ConfigurationService::class => create()->constructor(
|
||||||
get('config.path'),
|
get('config.path'),
|
||||||
get('config.file')
|
get('config.file')
|
||||||
),
|
),
|
||||||
'config.service' => get(ConfigurationInterface::class),
|
'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),
|
CsvReportInterface::class => get(CsvReport::class),
|
||||||
CsvReport::class => create()->constructor(
|
CsvReport::class => create()->constructor(
|
||||||
get('config.service')
|
get('config.service')
|
||||||
|
@ -27,6 +44,7 @@ return [
|
||||||
'csv.report' => get(CsvReportInterface::class),
|
'csv.report' => get(CsvReportInterface::class),
|
||||||
RprtCommand::class => create()->constructor(
|
RprtCommand::class => create()->constructor(
|
||||||
get('csv.report'),
|
get('csv.report'),
|
||||||
get('config.service')
|
get('config.service'),
|
||||||
)
|
get('youtrack.service')
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
|
@ -10,6 +10,7 @@ require __DIR__ . '/vendor/autoload.php';
|
||||||
$builder = new ContainerBuilder();
|
$builder = new ContainerBuilder();
|
||||||
$builder->addDefinitions('dependencies.php');
|
$builder->addDefinitions('dependencies.php');
|
||||||
$container = $builder->build();
|
$container = $builder->build();
|
||||||
|
|
||||||
$application = new Application();
|
$application = new Application();
|
||||||
|
|
||||||
$rprtCommand = $container->get(RprtCommand::class);
|
$rprtCommand = $container->get(RprtCommand::class);
|
||||||
|
|
|
@ -1,105 +1,159 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
// src/Commands/RprtCommand.php;
|
// src/Commands/RprtCommand.php;
|
||||||
|
|
||||||
namespace RprtCli\Commands;
|
namespace RprtCli\Commands;
|
||||||
|
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
||||||
use RprtCli\Utils\CsvReport\CsvReportInterface;
|
use RprtCli\Utils\CsvReport\CsvReportInterface;
|
||||||
|
use RprtCli\Utils\TimeTrackingServices\YoutrackInterface;
|
||||||
use Symfony\Component\Console\Command\Command;
|
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\Table;
|
||||||
use Symfony\Component\Console\Helper\TableSeparator;
|
use Symfony\Component\Console\Helper\TableSeparator;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
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) {
|
protected $youtrack;
|
||||||
$this->csv = $csv;
|
|
||||||
$this->configuration = $configuration;
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public function __construct(
|
||||||
* Get configuration.
|
CsvReportInterface $csv,
|
||||||
*/
|
ConfigurationInterface $configuration,
|
||||||
protected function configure(): void {
|
YoutrackInterface $youtrack,
|
||||||
$this->setName('rprt');
|
?string $name = null
|
||||||
$this->setDescription('Generate monthly report');
|
) {
|
||||||
// @TODO $this->addUsage('');
|
$this->csv = $csv;
|
||||||
$this->addOption('file', 'f', InputOption::VALUE_REQUIRED, 'Specify the input csv file to generate report from.');
|
$this->configuration = $configuration;
|
||||||
}
|
$this->youtrack = $youtrack;
|
||||||
|
parent::__construct($name);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$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.
|
* Create table from data that is already inline with configuration.
|
||||||
*/
|
*/
|
||||||
protected function generateTable($output, $data) {
|
protected function generateTable(OutputInterface $output, array $data) : Table
|
||||||
$table = new Table($output);
|
{
|
||||||
$table->setHeaders(['Project', 'Hours', 'Rate', 'Price']);
|
$table = new Table($output);
|
||||||
list($rows, $total_hours, $total_price) = [[], 0, 0];
|
$table->setHeaders([
|
||||||
$projects_config = $this->configuration->get('projects');
|
// $this->translator->trans('Project', [], 'messages', 'sl_SI'),
|
||||||
foreach ($projects_config as $name => $config) {
|
// $this->translator->trans('Hours', [], 'messages', 'sl_SI'),
|
||||||
if (!isset($data[$name])) {
|
// $this->translator->trans('Rate'),
|
||||||
// @TODO Proper error handling.
|
// $this->translator->trans('Price'),
|
||||||
var_dump('Project ' . $name . ' is not set!');
|
'Project', 'Hours', 'Rate', 'Price',
|
||||||
continue;
|
]);
|
||||||
}
|
[$rows, $totalHours, $totalPrice] = [[], 0, 0];
|
||||||
$hours = $data[$name];
|
$projectsConfig = $this->configuration->get('projects');
|
||||||
if ($config['time_format'] === 'm') {
|
foreach ($projectsConfig as $name => $config) {
|
||||||
$hours = $hours/60;
|
if (! isset($data[$name])) {
|
||||||
}
|
// @TODO Proper error handling.
|
||||||
$price = $hours * (int) $config['price'];
|
var_dump('Project ' . $name . ' is not set!');
|
||||||
$row = [
|
continue;
|
||||||
$config['name'],
|
}
|
||||||
$hours,
|
$hours = $data[$name];
|
||||||
$config['price'],
|
if ($config['time_format'] === 'm') {
|
||||||
$hours * $config['price'],
|
$hours /= 60;
|
||||||
];
|
}
|
||||||
$rows[] = $row;
|
$price = $hours * (int) $config['price'];
|
||||||
$total_hours += $hours;
|
$row = [
|
||||||
$total_price += $price;
|
$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.
|
* Dummy output for testing.
|
||||||
*/
|
*/
|
||||||
protected function dummyOutput(InputInterface $input, OutputInterface $output): void {
|
protected function dummyOutput(InputInterface $input, OutputInterface $output) : void
|
||||||
$output->writeln('I will output a nice table.');
|
{
|
||||||
$table = New Table($output);
|
// $txt = $this->translator->trans('From [start-date] to [end-date].', [], 'rprt', 'sl_SI');
|
||||||
$table->setHeaders(['Project', 'Hours', 'Price']);
|
// $output->writeln($txt);
|
||||||
$table->setRows([
|
$table = new Table($output);
|
||||||
['LDP', 100, 2600],
|
$table->setHeaders(['Project', 'Hours', 'Price']);
|
||||||
['WV', 50, 1300],
|
$table->setRows([
|
||||||
new TableSeparator(),
|
['LDP', 100, 2600],
|
||||||
['Zusamen', 150, 3900],
|
['WV', 50, 1300],
|
||||||
]);
|
new TableSeparator(),
|
||||||
// $table->setStyle('borderless');
|
['Zusamen', 150, 3900],
|
||||||
$table->render();
|
]);
|
||||||
|
// $table->setStyle('borderless');
|
||||||
}
|
$table->render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,25 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
// src/Utils/Configuration/ConfigurationInterface.php
|
// src/Utils/Configuration/ConfigurationInterface.php
|
||||||
|
|
||||||
namespace RprtCli\Utils\Configuration;
|
namespace RprtCli\Utils\Configuration;
|
||||||
|
|
||||||
interface ConfigurationInterface
|
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.
|
* Get and read the configuration from file.
|
||||||
*/
|
*/
|
||||||
function getConfig();
|
public function getConfig() : bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a specific configuration for key.
|
* Get a specific configuration for key.
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* Config key.
|
* Config key.
|
||||||
* @param mixed $default
|
* @param null|mixed $default
|
||||||
* Default value if config for key is not yet specified.
|
* Default value if config for key is not yet specified.
|
||||||
*
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* Data.
|
* Data.
|
||||||
*/
|
*/
|
||||||
|
@ -38,5 +31,5 @@ interface ConfigurationInterface
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* Key to check for.
|
* Key to check for.
|
||||||
*/
|
*/
|
||||||
public function exists($key);
|
public function exists($key) : bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
// src/Utils\Configuration/ConfigurationService.php
|
// src/Utils\Configuration/ConfigurationService.php
|
||||||
|
|
||||||
namespace RprtCli\Utils\Configuration;
|
namespace RprtCli\Utils\Configuration;
|
||||||
|
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
use function explode;
|
||||||
|
use function file_exists;
|
||||||
|
use function var_dump;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read and write configuration.
|
* Read and write configuration.
|
||||||
*
|
*
|
||||||
|
@ -17,99 +22,111 @@ use Symfony\Component\Yaml\Yaml;
|
||||||
*/
|
*/
|
||||||
class ConfigurationService implements ConfigurationInterface
|
class ConfigurationService implements ConfigurationInterface
|
||||||
{
|
{
|
||||||
|
protected const PATHS = [
|
||||||
|
'/.',
|
||||||
|
'/.config/rprt-cli/',
|
||||||
|
'/.rprt/',
|
||||||
|
];
|
||||||
|
|
||||||
const PATHS = [
|
protected $data;
|
||||||
'/.',
|
|
||||||
'/.config/rprt-cli/',
|
|
||||||
'/.rprt/',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $data;
|
protected $default = null;
|
||||||
|
|
||||||
protected $default = null;
|
protected $configFilePath;
|
||||||
|
|
||||||
protected $configFilePath;
|
protected $configFileName;
|
||||||
|
|
||||||
protected $configFileName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Yaml service.
|
|
||||||
*
|
|
||||||
* @var \Symfony\Component\Yaml\Yaml::parseFile;
|
|
||||||
*/
|
|
||||||
protected $yamlParseFile;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct method.
|
* Construct method.
|
||||||
*/
|
*/
|
||||||
function __construct(string $filepath, string $filename) {
|
public function __construct(string $filepath, string $filename)
|
||||||
$file = $filepath . $filename;
|
{
|
||||||
$this->configFileName = $filename;
|
$file = $filepath . $filename;
|
||||||
$this->configFilePath = $this->findConfig($file);
|
$this->configFileName = $filename;
|
||||||
if ($this->configFilePath) {
|
$this->configFilePath = $this->findConfig($file);
|
||||||
$this->getConfig();
|
if ($this->configFilePath) {
|
||||||
|
$this->
|
||||||
|
getConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for config file.
|
* Checks for config file.
|
||||||
*
|
*
|
||||||
|
* @param string $filename
|
||||||
|
* Name of the configuration file.
|
||||||
* @return string|bool
|
* @return string|bool
|
||||||
* Full path to config file or FALSE if it doesn't exist.
|
* Full path to config file or FALSE if it doesn't exist.
|
||||||
*/
|
*/
|
||||||
public function findConfig($filename) {
|
public function findConfig($filename)
|
||||||
if (file_exists($filename)) {
|
{
|
||||||
return $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.
|
* Get and read the configuration from file.
|
||||||
*/
|
*/
|
||||||
public function getConfig() {
|
public function getConfig() : bool
|
||||||
if ($this->configFilePath) {
|
{
|
||||||
$config = Yaml::parseFile($this->configFilePath);
|
if ($this->configFilePath) {
|
||||||
$this->data = $config;
|
$config = Yaml::parseFile($this->configFilePath);
|
||||||
return TRUE;
|
$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}
|
* Get a specific configuration for key.
|
||||||
*/
|
*
|
||||||
public function get($key, $default = null) {
|
* @param string $key
|
||||||
$this->default = $default;
|
* Config key.
|
||||||
$segments = explode('.', $key);
|
* @param null|mixed $default
|
||||||
$data = $this->data;
|
* Default value if config for key is not yet specified.
|
||||||
foreach ($segments as $segment) {
|
* @return mixed
|
||||||
if (isset($data[$segment])) {
|
* Data.
|
||||||
$data = $data[$segment];
|
*/
|
||||||
} else {
|
public function get($key, $default = null)
|
||||||
$data = $this->default;
|
{
|
||||||
break;
|
$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}
|
* Checks if key exists in the configuration file.
|
||||||
*/
|
*
|
||||||
public function exists($key) {
|
* @param string $key
|
||||||
return $this->get($key) !== $this->default;
|
* Key to check for.
|
||||||
}
|
*
|
||||||
|
* Value of configuration key is not equal to default.
|
||||||
|
*/
|
||||||
|
public function exists($key) : bool
|
||||||
|
{
|
||||||
|
return $this->get($key) !== $this->default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,75 +1,102 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace RprtCli\Utils\CsvReport;
|
namespace RprtCli\Utils\CsvReport;
|
||||||
|
|
||||||
use RprtCli\Utils\Configuration\ConfigurationInterface;
|
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) {
|
public function __construct(ConfigurationInterface $config)
|
||||||
$this->configurationService = $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;
|
|
||||||
}
|
}
|
||||||
if ($file = fopen($file_path, 'r')) {
|
|
||||||
while (($line = fgetcsv($file)) !== FALSE) {
|
/**
|
||||||
$parsed = $this->parseCsvFile($line);
|
* {@inheritdoc}
|
||||||
// $key = reset(array_keys($parsed));
|
*/
|
||||||
$key = array_key_first($parsed);
|
public function getReportData(string $filePath) : array
|
||||||
if (isset($output[$key])) {
|
{
|
||||||
$output[$key] += (int) reset($parsed);
|
$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();
|
* Get correct values from the raw data lines of csv.
|
||||||
$config = $this->configurationService->get('projects');
|
*
|
||||||
// var_dump($raw_data);
|
*
|
||||||
foreach ($config as $key => $project) {
|
* Columns with data are specified in config.
|
||||||
if (preg_match('/'.$project['pattern'].'/', $raw_data[1])) {
|
*
|
||||||
return [$key => $raw_data[4]];
|
* 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 {
|
protected function dummyConfig() : array
|
||||||
$config = [
|
{
|
||||||
'projects' => [
|
return [
|
||||||
'LDP' => [
|
'projects' => [
|
||||||
'name' => 'lupus.digital',
|
'LDP' => [
|
||||||
'pattern' => 'LDP-[0-9]+',
|
'name' => 'lupus.digital',
|
||||||
'price' => 26,
|
'pattern' => 'LDP-[0-9]+',
|
||||||
|
'price' => 26,
|
||||||
// optional specify columns
|
// optional specify columns
|
||||||
],
|
],
|
||||||
'WV' => [
|
'WV' => [
|
||||||
'name' => 'Wirtschaftsverlag',
|
'name' => 'Wirtschaftsverlag',
|
||||||
'pattern' => 'WV-[0-9]+',
|
'pattern' => 'WV-[0-9]+',
|
||||||
'price' => 26,
|
'price' => 26,
|
||||||
// optional specify columns
|
// optional specify columns
|
||||||
],
|
],
|
||||||
'Other' => [
|
'Other' => [
|
||||||
'name' => 'Other projects',
|
'name' => 'Other projects',
|
||||||
'pattern' => '(?!.\bLDP\b)(?!.\bWV\b)',
|
'pattern' => '(?!.\bLDP\b)(?!.\bWV\b)',
|
||||||
'price' => 26,
|
'price' => 26,
|
||||||
// optional specify columns
|
// optional specify columns
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
}
|
||||||
return $config;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace RprtCli\Utils\CsvReport;
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RprtCli\Utils\CsvReport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles creating report data from csv file downloaded from youtrack service.
|
* Handles creating report data from csv file downloaded from youtrack service.
|
||||||
*/
|
*/
|
||||||
interface CsvReportInterface {
|
interface CsvReportInterface
|
||||||
|
{
|
||||||
/**
|
|
||||||
* Gets project configuration and parses the data.
|
|
||||||
*
|
|
||||||
* Calculate number of hours per project.
|
|
||||||
*/
|
|
||||||
// protected function parseCsvFile(array $data): array;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of hours per configured projects.
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue