WIP work on youtrack rest api plugin.

yt-rest-api
Lio Novelli 2023-07-16 13:34:51 +02:00
parent 4e3477c4a0
commit 519c0b074a
21 changed files with 542 additions and 50 deletions

View File

@ -17,7 +17,7 @@
** Install/Getting started ** Install/Getting started
Get ~.phar~ file from r, Run configuration wizzard (planned for version 1.0). Get ~.phar~ file from r, Run configruration wizzard (planned for version 1.0).
Before version 1.0 is released you have to navigate into ~/app~ directory Before version 1.0 is released you have to navigate into ~/app~ directory
and call command through ~.rprt.php~ file. and call command through ~.rprt.php~ file.
@ -75,6 +75,11 @@ curl 'https://drunomics.myjetbrains.com/youtrack/api/reports?$top=-1&fields=id,n
5 days of development. 5 days of development.
- remove errors from reports - remove errors from reports
* Support the work
If you find this Free Software useful you can consider donating to my
[[https://liberapay.com/tehnoklistir/][@tehno-klistir liberapay account.]]
* Development * Development
** Testing ** Testing

View File

@ -1,5 +1,6 @@
<?php <?php
use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackRestApiClient;
use function DI\create; use function DI\create;
use function DI\get; use function DI\get;
use function DI\factory; use function DI\factory;
@ -60,6 +61,8 @@ return [
get('config.service'), get('config.service'),
get('mpdf') get('mpdf')
), ),
YoutrackRestApiClient::class => create()->constructor(get('config.service')),
'youtrack_rest_api.client' => get(YoutrackRestApiClient::class),
'pdf_export.service' => get(PdfExportInterface::class), 'pdf_export.service' => get(PdfExportInterface::class),
// 'locale' => get('config.service')->method('get', 'en'), // 'locale' => get('config.service')->method('get', 'en'),
// Translator::class => create()->constructor('sl')->method('addLoader', 'po', new PoFileLoader), // Translator::class => create()->constructor('sl')->method('addLoader', 'po', new PoFileLoader),

View File

@ -21,4 +21,5 @@ $reportCommand = $container->get(ReportCommand::class);
$application->add($reportCommand); $application->add($reportCommand);
// eval(\Psy\sh()); // eval(\Psy\sh());
$application->run(); $application->run();

View File

@ -51,6 +51,9 @@ class InvoiceCommand extends Command
protected PdfExportInterface $pdfExport; protected PdfExportInterface $pdfExport;
/**
* Mailer service.
*/
protected MailerInterface $mailer; protected MailerInterface $mailer;
protected const TYPE_WORK = 1; protected const TYPE_WORK = 1;
@ -180,6 +183,9 @@ class InvoiceCommand extends Command
if (!empty($expenses)) { if (!empty($expenses)) {
$data = array_merge($data, $expenses); $data = array_merge($data, $expenses);
} }
if (!empty($custom)) {
$data = array_merge($data, $custom);
}
$table = $this->getTable($output, $data); $table = $this->getTable($output, $data);
$table->render(); $table->render();
if ($input->getOption('pdf')) { if ($input->getOption('pdf')) {

View File

@ -145,7 +145,7 @@ class ReportCsv implements ReportCsvInterface
$rows[] = [ $rows[] = [
null, null,
null, null,
'Kosten', 'Extra', // Kosten
'EUR', 'EUR',
]; ];
// Don't make next line bold. See RprtCli\PdfExport\PdfExportService::parsedDataToHtml. // Don't make next line bold. See RprtCli\PdfExport\PdfExportService::parsedDataToHtml.
@ -161,6 +161,7 @@ class ReportCsv implements ReportCsvInterface
]; ];
$totalPrice += $invoice_element->getValue(); $totalPrice += $invoice_element->getValue();
} }
// @todo Add Extra time as well!
} }
if ($add_separator) { if ($add_separator) {
$rows[] = ReportCsvInterface::SEPARATOR_MEDIUM; $rows[] = ReportCsvInterface::SEPARATOR_MEDIUM;

View File

@ -14,35 +14,63 @@ use Attribute;
#[Attribute] #[Attribute]
class EntityDefinition { class EntityDefinition {
/**
* @param string $id
* @param string $provider
* @param string $name
* @param array $fields
* @param array $filters
* @param array $resources
*/
public function __construct( public function __construct(
private string $id, private string $id,
private string $provider, private string $provider,
private string $name, private string $name,
private array $fields, private array $fields,
private array $filters private array $filters,
private array $resources
) { } ) { }
public function getId() { public function getId() :string {
return $this->id; return $this->id;
} }
public function getProvider() { public function getProvider() :string {
return $this->provider; return $this->provider;
} }
public function getName() { public function getName() :string {
return $this->name; return $this->name;
} }
public function getFields() { public function getFields() :array {
return $this->fields; return $this->fields;
} }
public function getFilters() { public function getFilters() :array {
return $this->filters; return $this->filters;
} }
public function getDefinition() { /**
* Should be of type ResourceInterface.
*/
public function getResources() :array {
return $this->resources;
}
public function getResource(string $key) :?Resource {
return $this->resources[$key] ?? NULL;
}
/**
* Get definition as array.
*
* @todo I'm not really sure why I implemented this method. Definition
* is a definition already.
*
* @return array
*/
public function getDefinition() :array {
return [ return [
'id' => $this->id, 'id' => $this->id,
'provider' => $this->provider, 'provider' => $this->provider,

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices; namespace RprtCli\Utils\TimeTrackingServices;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
use RprtCli\Utils\TimeTrackingServices\EntityInterface; use RprtCli\Utils\TimeTrackingServices\EntityInterface;
/** /**
@ -14,6 +13,8 @@ interface EntityManagerInterface {
/** /**
* List supported entity types. * List supported entity types.
*
* @return array
*/ */
public function list(): array; public function list(): array;

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices;
/**
* Defines resources for entity type.
*
* This class could be readonly after php82.
*/
final class Resource {
public const GET = 'GET';
public const POST = 'POST';
public function __construct(
private string $id,
private string $method,
private string $path,
private string $description = ''
) { }
public function getId() :string {
return $this->id;
}
public function getPath() :string {
return $this->path;
}
public function getMethod() :string {
return $this->method;
}
public function getDescription() :string {
return $this->description;
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi; namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi;
use ReflectionAttribute;
use RprtCli\Utils\TimeTrackingServices\EntityManagerInterface; use RprtCli\Utils\TimeTrackingServices\EntityManagerInterface;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition; use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
use RprtCli\Utils\TimeTrackingServices\EntityInterface; use RprtCli\Utils\TimeTrackingServices\EntityInterface;
@ -30,8 +31,11 @@ class EntityManager implements EntityManagerInterface {
/** /**
* Returns all the entity definitions. * Returns all the entity definitions.
*
* @return array
* List of entity definitions keyed with their id.
*/ */
public function listEntityDefinitions() { public function listEntityDefinitions() :array {
if (!$this->entityDefinitions) { if (!$this->entityDefinitions) {
$this->discoverEntities(); $this->discoverEntities();
} }
@ -40,8 +44,10 @@ class EntityManager implements EntityManagerInterface {
/** /**
* Discovers entity definitions. * Discovers entity definitions.
*
* @return array
*/ */
private function discoverEntities() { private function discoverEntities() :?array {
if (!empty($this->entityDefinitions)) { if (!empty($this->entityDefinitions)) {
return $this->entityDefinitions; return $this->entityDefinitions;
} }
@ -49,8 +55,8 @@ class EntityManager implements EntityManagerInterface {
$definitions = []; $definitions = [];
// @todo create a proxy service for finder. // @todo create a proxy service for finder.
$this->finder->files()->in($path); $this->finder->files()->in($path);
/** @var SplFileInfo $file */
foreach ($this->finder as $file) { foreach ($this->finder as $file) {
/** @var SplFileInfo $file */
$class = self::ENTITIES_NAMESPACE . '\\' . self::ENTITIES_DIR . '\\' . $file->getBasename('.php'); $class = self::ENTITIES_NAMESPACE . '\\' . self::ENTITIES_DIR . '\\' . $file->getBasename('.php');
$reflection = new \ReflectionClass($class); $reflection = new \ReflectionClass($class);
$attribute = $this->getAttributeOfInstance($reflection, EntityDefinition::class); $attribute = $this->getAttributeOfInstance($reflection, EntityDefinition::class);
@ -61,8 +67,10 @@ class EntityManager implements EntityManagerInterface {
} }
$instance = $attribute->newInstance(); $instance = $attribute->newInstance();
$id = $instance->getId(); $id = $instance->getId();
$content = $instance->getDefinition(); $content = [
$content['class'] = $class; 'definition' => $instance,
'class' => $class,
];
$definitions[$id] = $content; $definitions[$id] = $content;
} }
$this->entityDefinitions = $definitions; $this->entityDefinitions = $definitions;
@ -77,19 +85,11 @@ class EntityManager implements EntityManagerInterface {
* @param string $instance * @param string $instance
* The instance the attribute should be of. * The instance the attribute should be of.
* *
* @return * @return ?ReflectionAttribute
* The attribute instance. * The attribute instance.
*/ */
protected function getAttributeOfInstance(\ReflectionClass $reflection, string $instance) { protected function getAttributeOfInstance(\ReflectionClass $reflection, string $instance) :?ReflectionAttribute {
$t = $reflection->getAttributes();
var_dump($t);
var_dump($t[0]->getName());
var_dump($t[0]->getArguments());
var_dump($t[0]->newInstance());
$s = $reflection->getAttributes(EntityDefinition::class, \ReflectionAttribute::IS_INSTANCEOF);
var_dump($s);
$attributes = $reflection->getAttributes($instance, \ReflectionAttribute::IS_INSTANCEOF); $attributes = $reflection->getAttributes($instance, \ReflectionAttribute::IS_INSTANCEOF);
var_dump($attributes);
if (empty($attributes)) { if (empty($attributes)) {
return NULL; return NULL;
} }
@ -97,6 +97,15 @@ class EntityManager implements EntityManagerInterface {
return reset($attributes); return reset($attributes);
} }
/**
* Get entity definition by id.
*
* @param string $id
* Id of an entity definition (work_item, issue, project, comment).
*
* @return ?EntityDefinition
* Entity definition (maybe even EntityDefinition object).
*/
public function getDefinition(string $id) :?array { public function getDefinition(string $id) :?array {
if (!isset($this->entityDefinitions)) { if (!isset($this->entityDefinitions)) {
$this->list(); $this->list();
@ -108,6 +117,20 @@ class EntityManager implements EntityManagerInterface {
return $this->entityDefinitions[$id]; return $this->entityDefinitions[$id];
} }
/**
* Create entity instance.
*
* This method should be used for transfering the data between report cli
* tools and the youtrack rest api.
*
* @param string $id
* Instance id (issue, project, work_item, comment ...).
* @param array $values
* Time, description ...
*
* @return ?EntityInterface
*
*/
public function createInstance(string $id, array $values) :?EntityInterface { public function createInstance(string $id, array $values) :?EntityInterface {
if (!isset($this->entityDefinitions)) { if (!isset($this->entityDefinitions)) {
$this->list(); $this->list();
@ -117,8 +140,10 @@ class EntityManager implements EntityManagerInterface {
return NULL; return NULL;
} }
$definition = $this->getDefinition($id); $definition = $this->getDefinition($id);
// This is not ok. definition is of type EntityDefinition but we want
// EntityInterface. This is where $class would come in handy.
$reflection = new \ReflectionClass($definition['class']); $reflection = new \ReflectionClass($definition['class']);
return new $reflection->newInstanceArgs($values); return $reflection->newInstanceArgs($values);
} }
} }

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices;
use Attribute;
/**
* Define filter properties.
*/
#[Attribute]
class FilterAttribute {
/**
* @param string $id
* @param string $provider
* @param string $name
* @param array $allowedFields
* @param array $allowedEntities
* @param array $resources
*/
public function __construct(
private string $id,
private string $provider,
private string $name,
private array $allowedFields = [],
private array $allowedEntities = [],
) { }
public function getId() :string {
return $this->id;
}
public function getProvider() :string {
return $this->provider;
}
public function getName() :string {
return $this->name;
}
public function getAllowedFields() :array {
return $this->allowedFields;
}
public function getAllowedEntities() :array {
return $this->allowedEntities;
}
/**
* Get definition as array.
*
* @todo I'm not really sure why I implemented this method. Definition
* is a definition already.
*
* @return array
*/
public function getDefinition() :array {
return [
'id' => $this->id,
'provider' => $this->provider,
'name' => $this->name,
'allowedFields' => $this->allowedFields,
'allowedEntities' => $this->allowedEntities,
];
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\Filters;
/**
* Defines a filter attribute and some constants.
*/
interface YoutrackFilterInterface {
const OPTIONAL = 0;
const REQUIRED = 1;
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi; namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
use RprtCli\Utils\TimeTrackingServices\EntityInterface; use RprtCli\Utils\TimeTrackingServices\EntityInterface;
/** /**
@ -11,4 +12,14 @@ use RprtCli\Utils\TimeTrackingServices\EntityInterface;
*/ */
abstract class YoutrackEntity implements EntityInterface { abstract class YoutrackEntity implements EntityInterface {
protected function getDefintion() :?EntityDefinition {
$reflection = new \ReflectionClass(self::class);
$attributes = $reflection->getAttributes(EntityDefinition::class, \ReflectionAttribute::IS_INSTANCEOF);
if (empty($attributes)) {
return NULL;
}
return reset($attributes);
}
} }

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntityTypes;
use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntity;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
use RprtCli\Utils\TimeTrackingServices\Resource;
use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\Filters\YoutarckFilterInterface;
/**
* https://www.jetbrains.com/help/youtrack/devportal/api-entity-IssueComment.html
* https://www.jetbrains.com/help/youtrack/devportal/resource-api-issues-issueID-comments.html
*/
#[EntityDefinition(
id: 'issue_comment',
provider: 'youtrack_rest_api',
name: 'IssueComment',
fields: [
'id',
'text',
'textPreview',
'created',
'author' => [
'id',
'fullName',
'email',
],
'issue' => [
'id',
'idReadable',
'project' => [
'id',
'shortName',
],
'summary',
],
],
filters: [
'issue' => [
'required' => YoutrackFilterInterface::REQUIRED,
],
],
resources: [
'list' => new Resource(
id: 'list',
method: Resource::GET,
path: '/api/issues/{issueID}/comments',
description: 'Lists comments of issue.'
),
]
)]
class YoutrackComment extends YoutrackEntity {
}

View File

@ -6,16 +6,38 @@ namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntityTypes
use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntity; use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntity;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition; use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
use RprtCli\Utils\TimeTrackingServices\Resource;
/** /**
* https://www.jetbrains.com/help/youtrack/devportal/api-entity-Issue.html * https://www.jetbrains.com/help/youtrack/devportal/api-entity-Issue.html
* https://www.jetbrains.com/help/youtrack/devportal/resource-api-issues.html
*/ */
#[EntityDefinition( #[EntityDefinition(
'issue', id: 'issue',
'youtrack_rest_api', provider: 'youtrack_rest_api',
'Issue', name: 'Issue',
[], fields: [
[] 'id',
'idReadable',
'project' => [
'id',
'shortName',
],
'summary',
],
filters: [
'state',
'project',
'assignee',
],
resources: [
'list' => new Resource(
id: 'list',
method: Resource::GET,
path: '/api/issues',
description: 'Lists issues.'
),
]
)] )]
class YoutrackIssue extends YoutrackEntity { class YoutrackIssue extends YoutrackEntity {
const ID = 'issue'; const ID = 'issue';

View File

@ -4,15 +4,38 @@ declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntityTypes; namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntityTypes;
use RprtCli\Utils\TimeTrackingServices\Resource;
use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntity; use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntity;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition; use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
/**
* https://www.jetbrains.com/help/youtrack/devportal/resource-api-admin-projects.html
*/
#[EntityDefinition( #[EntityDefinition(
'project', id: 'project',
'youtrack_rest_api', provider: 'youtrack_rest_api',
'Youtrack Project', name: 'Youtrack Project',
[], fields: [
[] 'id',
'shortName',
'description',
'leader' => ['id', 'fullName'],
],
filters: [],
resources: [
'list' => new Resource(
id: 'list',
method: Resource::GET,
path: '/api/admin/projects',
description: 'List projects'
),
'read' => new Resource(
id: 'read',
method: Resource::GET,
path: '/api/admin/projects/{projectId}',
description: 'Sprecific project'
),
]
)] )]
class YoutrackProject extends YoutrackEntity { class YoutrackProject extends YoutrackEntity {

View File

@ -4,27 +4,57 @@ declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntityTypes; namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntityTypes;
use RprtCli\Utils\TimeTrackingServices\Resource;
use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntity; use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackEntity;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition; use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
/**
* https://www.jetbrains.com/help/youtrack/devportal/api-entity-IssueWorkItem.html
* https://www.jetbrains.com/help/youtrack/devportal/resource-api-workItems.html
*/
#[EntityDefinition( #[EntityDefinition(
'work_item', id: 'work_item',
'youtrack_rest_api', provider: 'youtrack_rest_api',
'Issue Work Item', name: 'Issue Work Item',
[ fields: [
'id', 'id',
'author', 'author' => [
'id',
'fullName',
'email'
],
'text', 'text',
'type', 'type',
'duration', 'duration' => [
'date', 'id',
'issue', 'minutes',
'presentation'
], ],
[ 'date',
'created',
'issue' => [
'id',
'idReadable',
'project' => [
'id',
'shortName'
],
'summary',
],
],
filters: [
'project', 'project',
'issue', 'issue',
'user', 'user',
'date', 'date',
],
resources: [
'list' => new Resource(
id: 'list',
method: Resource::GET,
path: '/api/workItems',
description: 'List workItems'
),
] ]
)] )]
class YoutrackWorkItem extends YoutrackEntity { class YoutrackWorkItem extends YoutrackEntity {

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi;
use Cog\YouTrack\Rest\Client\YouTrackClient;
use Cog\YouTrack\Rest\Authorizer\TokenAuthorizer;
use Cog\YouTrack\Rest\HttpClient\GuzzleHttpClient;
use RprtCli\Utils\Configuration\ConfigurationInterface;
/**
* YouTrack php sdk abstraction service.
*
* Returns the client that takes care of authentication.
*/
class YoutrackRestApiClient {
protected ConfigurationInterface $config;
protected YouTrackClient $client;
// config
public function __construct(ConfigurationInterface $config) {
$this->config = $config;
$this->client = $this->createYoutrackClient();
}
protected function createYoutrackClient() :YouTrackClient {
// Could this all go into __construct method?
$apiBaseUri = $this->config->get('tracking_service.youtrack.base_url', false);
$apiToken = $this->config->get('tracking_service.youtrack.auth_token', false);
// Instantiate PSR-7 HTTP Client
$psrHttpClient = new \GuzzleHttp\Client([
'base_uri' => $apiBaseUri,
'debug' => true,
]);
// Instantiate YouTrack API HTTP Client
$httpClient = new GuzzleHttpClient($psrHttpClient);
// Instantiate YouTrack API Token Authorizer
$authorizer = new TokenAuthorizer($apiToken);
// Instantiate YouTrack API Client
$client = new YouTrackClient($httpClient, $authorizer);
return $client;
}
public function getClient() :YouTrackClient {
return $this->client;
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace RprtCli\Utils\TimeTrackingServices\YoutrackRestApi;
use RprtCli\Utils\TimeTrackingServices\EntityManagerInterface;
use RprtCli\Utils\TimeTrackingServices\YoutrackRestApi\YoutrackRestApiClient;
class YoutrackRestApiRequestBuilder {
protected EntityManagerInterface $entityManager;
protected YoutrackRestApiClient $clientFactory;
public function __construct(EntityManagerInterface $entity_manager, YoutrackRestApiClient $clientFactory) {
$this->entityManager = $entity_manager;
$this->clientFactory = $clientFactory;
// EntityMangerInterface
}
/**
* Method that creates path to youtrack entity resource.
*
* Examples:
* list issue => GET '/api/issues?fields=id,idReadable,summary,..'
* create issue => POST '/api/issues'
* but create action is only allowed on worktItems.
* list work_item => GET '/api/workItem?fields=...'
*
* First impression seems simple. We need path and method.
* What bothers me whether it would be better to create a new attribute
* wehere these "mappings" would live: allowed actions and then method and path.
*
* @TODO Add filter system?
*
* @param string $action
* Name of the action: list, create, read, update, delete.
* @param string $entity_id
* Name of the entity id (project, issue, comment, work_item).
*
* @return ?string
* Path to the resource.
*/
public function buildPath(string $action, string $entity_id) :?string {
$defintion = $this->entityManager->getDefinition($entity_id);
if (!isset($defintion['definition'])) {
// Missing entity definition!
return NULL;
}
else {
$defintion = $defintion['definition'];
}
$fields = $definition->getFields();
$fieldsQuery = $this->fieldsQuery($fields);
/** @var RprtCli\Utils\TimeTrackingServices\Resource $resource */
$resource = $defintion->getResource($action);
if (!$resource) {
// Missing resource!
return NULL;
}
$path = $resource->getPath();
// @TODO add query, create client request.
// @see https://github.com/cybercog/youtrack-rest-php/blob/eb0315133d1d3d161da23d26537201afb253dec9/src/Client/YouTrackClient.php#L89
if ($fieldsQuery) {
$path .= '?' . $fieldsQuery;
}
// @TODO create request and handle request.
}
protected function fieldsQuery(array $fields) :?string {
$query = implode(',', $this->stringifyFields($fields));
if (!empty($query)) {
return "fields=${query}";
}
return $query;
}
protected function stringifyFields(array $fields) :array {
foreach ($fields as $key => &$field) {
if (is_array($field)) {
$field = $key . '(' . implode(',', $this->stringifyFields($field)) . ')';
}
}
return $fields;
}
}

View File

@ -6,6 +6,8 @@ namespace RprtCli\Tests\Unit\YoutrackRestApi;
use DI\ContainerBuilder; use DI\ContainerBuilder;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use RprtCli\Utils\TimeTrackingServices\EntityDefinition;
use RprtCli\Utils\TimeTrackingServices\EntityInterface;
/** /**
* Test the entity manager service and system. * Test the entity manager service and system.
@ -24,15 +26,26 @@ class EntityManagerTest extends TestCase {
// Instance of EntityMangerInterface; // Instance of EntityMangerInterface;
// $this->assertInstanceOf('EntityManagerInterface', $entityManagerService); // $this->assertInstanceOf('EntityManagerInterface', $entityManagerService);
$definitions = $entityManagerService->list(); $definitions = $entityManagerService->list();
var_dump($definitions); // var_dump($definitions);
$this->assertCount(3, $definitions); $this->assertCount(3, $definitions);
$entities = ['project', 'issue', 'work_item']; $entities = ['project', 'issue', 'work_item'];
foreach ($entities as $entity) { foreach ($entities as $entity_id) {
$this->assertArrayHasKey($entity, $definitions, 'Check that key exists in entity types definitions.'); $this->assertArrayHasKey($entity_id, $definitions, 'Check that key exists in entity types definitions.');
var_dump($definitions[$entity_id]);
$this->assertInstanceOf(EntityDefinition::class, $definitions[$entity_id]['definition'], 'Check that definiton is of type entity definition');
$definition = $entityManagerService->getDefinition($entity_id);
$entity = $entityManagerService->createInstance($entity_id, []);
$this->assertArrayHasKey('definition', $definition);
$this->assertArrayHasKey('class', $definition);
$this->assertInstanceOf(EntityDefinition::class, $definition['definition']);
$this->assertInstanceOf(EntityInterface::class, $entity);
} }
// list method returns definitions: project, issue, worktItem // list method returns definitions: project, issue, worktItem
// Check one definition, compare. // Check one definition, compare.
// Test creating the instance
} }
} }

BIN
box.phar 100755

Binary file not shown.

View File

@ -15,6 +15,13 @@ TODO:
- invoice command improvements - invoice command improvements
- pdf and output option could be combined - pdf and output option could be combined
TODO: (start of 2023)
- youtrack rest api:
- resources class and parameter in EntityDefinition
- compose fields method
- create filter system
- connect filter system to youtrack rest api command
TODO (end of 2022): TODO (end of 2022):
- DONE fix project list output (first is missing) - DONE fix project list output (first is missing)
- DONE report selection trait - DONE report selection trait