Skip to content

Commit

Permalink
Add support for YouTrack in TodoByTicketRule (#51)
Browse files Browse the repository at this point in the history
Co-authored-by: Markus Staab <[email protected]>
  • Loading branch information
DannyvdSluijs and staabm authored Jan 8, 2024
1 parent 00690bb commit 0a3ce2c
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ jobs:
test-dir: tests-e2e/jira/
script: |
diff <(vendor/bin/phpstan analyse --error-format=raw --no-progress | sed "s|$(pwd)/||") expected-errors.txt
- os: ubuntu-latest
php-version: '8.2'
test-dir: tests-e2e/youtrack/
script: |
diff <(vendor/bin/phpstan analyse --error-format=raw --no-progress | sed "s|$(pwd)/||") expected-errors.txt
steps:
- name: Checkout
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Reference these virtual packages like any other package in your todo-comments:
Optionally you can configure this extension to analyze your comments with issue tracker ticket keys.
The extension fetches issue tracker API for issue status. If the remote issue is resolved, the comment will be reported.

Currently, Jira and GitHub are supported.
Currently, Jira, GitHub and YouTrack are supported.

This feature is disabled by default. To enable it, you must set `ticket.enabled` parameter to `true`.
You also need to set these parameters:
Expand Down Expand Up @@ -222,7 +222,7 @@ parameters:
# path to a file containing Jira credentials.
# see below for possible formats.
# if credentials parameter is not empty, it will be used instead of this file.
# this file must not be commited into the repository!
# this file must not be committed into the repository!
credentialsFilePath: .secrets/jira-credentials.txt

github:
Expand All @@ -240,6 +240,19 @@ parameters:
# if credentials parameter is not empty, it will be used instead of this file.
# this file must not be committed into the repository!
credentialsFilePath: null

youtrack:
# e.g. https://your-company.youtrack.cloud
server: https://acme.youtrack.cloud

# YouTrack permanent token
# if this value is empty, credentials file will be used instead.
credentials: %env.YOUTRACK_TOKEN%

# path to a file containing a YouTrack permanent token
# if credentials parameter is not empty, it will be used instead of this file.
# this file must not be committed into the repository!
credentialsFilePath: .secrets/youtrack-credentials.txt
```
#### Jira Credentials
Expand Down
28 changes: 27 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ parametersSchema:
virtualPackages: arrayOf(string(), string())
ticket: structure([
enabled: bool()
tracker: anyOf('jira', 'github')
tracker: anyOf('jira', 'github', 'youtrack')
keyPrefixes: listOf(string())
resolvedStatuses: listOf(string())
jira: structure([
Expand All @@ -21,6 +21,11 @@ parametersSchema:
credentials: schema(string(), nullable())
credentialsFilePath: schema(string(), nullable())
])
youtrack: structure([
server: string()
credentials: schema(string(), nullable())
credentialsFilePath: schema(string(), nullable())
])
])
])

Expand Down Expand Up @@ -90,6 +95,20 @@ parameters:
# this file must not be committed into the repository!
credentialsFilePath: null

youtrack:
# e.g. https://your-company.youtrack.cloud
server: https://youtrack.jetbrains.com

# see README for possible formats.
# if this value is empty, credentials file will be used instead.
credentials: null

# path to a file containing YouTrack credentials.
# see README for possible formats.
# if credentials parameter is not empty, it will be used instead of this file.
# this file must not be commited into the repository!
credentialsFilePath: null

conditionalTags:
staabm\PHPStanTodoBy\TodoByTicketRule:
phpstan.rules.rule: %todo_by.ticket.enabled%
Expand Down Expand Up @@ -151,5 +170,12 @@ services:
- %todo_by.ticket.jira.credentials%
- %todo_by.ticket.jira.credentialsFilePath%

-
class: staabm\PHPStanTodoBy\utils\ticket\YouTrackTicketStatusFetcher
arguments:
- %todo_by.ticket.youtrack.server%
- %todo_by.ticket.youtrack.credentials%
- %todo_by.ticket.youtrack.credentialsFilePath%

-
class: staabm\PHPStanTodoBy\utils\HttpClient
11 changes: 11 additions & 0 deletions src/utils/ticket/TicketRuleConfigurationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ public function create(): TicketRuleConfiguration
);
}

if ('youtrack' === $tracker) {
$fetcher = $this->container->getByType(YouTrackTicketStatusFetcher::class);

return new TicketRuleConfiguration(
$fetcher::getKeyPattern(),
['resolved'],
$keyPrefixes,
$fetcher,
);
}

throw new RuntimeException("Unsupported tracker type: $tracker");
}
}
87 changes: 87 additions & 0 deletions src/utils/ticket/YouTrackTicketStatusFetcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace staabm\PHPStanTodoBy\utils\ticket;

use RuntimeException;
use staabm\PHPStanTodoBy\utils\CredentialsHelper;
use staabm\PHPStanTodoBy\utils\HttpClient;

use function array_key_exists;
use function is_array;

final class YouTrackTicketStatusFetcher implements TicketStatusFetcher
{
private string $host;
private ?string $authorizationHeader;

private HttpClient $httpClient;

/**
* @var array<string, ?string>
*/
private array $cache;

public function __construct(string $host, ?string $credentials, ?string $credentialsFilePath, HttpClient $httpClient)
{
$credentials = CredentialsHelper::getCredentials($credentials, $credentialsFilePath);

$this->host = $host;
$this->authorizationHeader = $credentials ? self::createAuthorizationHeader($credentials) : null;

$this->cache = [];
$this->httpClient = $httpClient;
}

public function fetchTicketStatus(string $ticketKey): ?string
{
if (array_key_exists($ticketKey, $this->cache)) {
return $this->cache[$ticketKey];
}

$url = "{$this->host}/api/issues/$ticketKey?fields=resolved";
$headers = [];
if (null !== $this->authorizationHeader) {
$headers = [
"Authorization: $this->authorizationHeader",
];
}

[$responseCode, $response] = $this->httpClient->get($url, $headers);

if (200 !== $responseCode) {
throw new RuntimeException("Could not fetch ticket's status from YouTrack with url $url");
}

$data = self::decodeAndValidateResponse($response);

return $this->cache[$ticketKey] = null === $data['resolved'] ? 'open' : 'resolved';
}

public static function getKeyPattern(): string
{
return '[A-Z0-9]+-\d+';
}

private static function createAuthorizationHeader(string $credentials): string
{
return "Bearer $credentials";
}

/** @return array{resolved: ?int} */
private static function decodeAndValidateResponse(string $body): array
{
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

if (!is_array($data) || !array_key_exists('resolved', $data)) {
self::throwInvalidResponse();
}

return $data;
}

/** @return never */
private static function throwInvalidResponse(): void
{
throw new RuntimeException('YouTrack returned invalid response body');
}
}
1 change: 1 addition & 0 deletions tests-e2e/youtrack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/vendor/
11 changes: 11 additions & 0 deletions tests-e2e/youtrack/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
}
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpstan/extension-installer": "^1.3"
}
}
125 changes: 125 additions & 0 deletions tests-e2e/youtrack/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests-e2e/youtrack/expected-errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/tickets.php:3:Should have been resolved in WI-1: fix me.
12 changes: 12 additions & 0 deletions tests-e2e/youtrack/phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
parameters:
level: max
paths:
- src/
todo_by:
ticket:
enabled: true
tracker: youtrack
keyPrefixes:
- WI
jira:
server: https://youtrack.jetbrains.com/
3 changes: 3 additions & 0 deletions tests-e2e/youtrack/src/tickets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

// TODO: WI-1 fix me

0 comments on commit 0a3ce2c

Please sign in to comment.