Add youtrack service.
parent
0346b9f29d
commit
9cf4d23012
|
@ -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
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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')
|
||||
),
|
||||
];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
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;
|
||||
}
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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