Skip to content

Commit c19b344

Browse files
committed
feat: Sync List
1 parent e679008 commit c19b344

File tree

9 files changed

+352
-18
lines changed

9 files changed

+352
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
vendor/
33
composer.lock
44
config.yaml
5+
mantis-ticketsync*.html

bin/mantis2github

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use Artemeon\M2G\Command\CheckUpdateCommand;
55
use Artemeon\M2G\Command\ConfigurationCommand;
66
use Artemeon\M2G\Command\CreateGithubIssueFromMantisIssue;
7+
use Artemeon\M2G\Command\IssuesListCommand;
78
use Artemeon\M2G\Command\ReadGithubIssueCommand;
89
use Artemeon\M2G\Command\ReadMantisIssueCommand;
910
use Artemeon\M2G\Config\ConfigReader;
@@ -30,6 +31,7 @@ use Symfony\Component\Console\Application;
3031
$app->add(new ReadMantisIssueCommand($mantisConnector));
3132
$app->add(new ReadGithubIssueCommand($githubConnector));
3233
$app->add(new CreateGithubIssueFromMantisIssue($mantisConnector, $githubConnector));
34+
$app->add(new IssuesListCommand($mantisConnector, $githubConnector, $configValues));
3335
$app->add(new CheckUpdateCommand());
3436
$app->run();
3537
}

src/Command/IssuesListCommand.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\M2G\Command;
6+
7+
use Artemeon\M2G\Config\ConfigValues;
8+
use Artemeon\M2G\Helper\CliTableConverter;
9+
use Artemeon\M2G\Helper\HtmlTableConverter;
10+
use Artemeon\M2G\Helper\UpstreamIssueParser;
11+
use Artemeon\M2G\Service\GithubConnector;
12+
use Artemeon\M2G\Service\MantisConnector;
13+
14+
class IssuesListCommand extends Command
15+
{
16+
protected string $signature = 'issues:list {--output= : Output Format}';
17+
protected ?string $description = 'Get a list of Mantis Tickets with their associated GitHub Issues.';
18+
19+
private MantisConnector $mantisConnector;
20+
private GithubConnector $githubConnector;
21+
private ?ConfigValues $config;
22+
23+
public function __construct(MantisConnector $mantisConnector, GithubConnector $githubConnector, ?ConfigValues $config)
24+
{
25+
parent::__construct();
26+
27+
$this->mantisConnector = $mantisConnector;
28+
$this->githubConnector = $githubConnector;
29+
$this->config = $config;
30+
}
31+
32+
public function __invoke(): int
33+
{
34+
$mantisIssues = $this->mantisConnector->fetchIssues(410);
35+
36+
$githubIssueIds = [];
37+
foreach ($mantisIssues as $issue) {
38+
$parsedIssues = array_map(static fn (array $data) => $data['id'], UpstreamIssueParser::parse($issue->getUpstreamTicket()));
39+
$githubIssueIds = [...$githubIssueIds, ...$parsedIssues];
40+
}
41+
42+
$parts = [];
43+
foreach (array_unique($githubIssueIds) as $id) {
44+
$parts[] = <<<GRAPHQL
45+
issue$id: issue(number: $id) {
46+
...IssueFragment
47+
}
48+
GRAPHQL;
49+
}
50+
$issuesQuery = implode(PHP_EOL, $parts);
51+
52+
$issueFragment = <<<GRAPHQL
53+
fragment IssueFragment on Issue {
54+
title
55+
url
56+
closed
57+
}
58+
GRAPHQL;
59+
60+
$repo = $this->config->getGithubRepo();
61+
[$owner, $name] = explode('/', $repo);
62+
63+
$query = <<<GRAPHQL
64+
{
65+
repository(name: "$name", owner: "$owner") {
66+
$issuesQuery
67+
}
68+
}
69+
70+
$issueFragment
71+
GRAPHQL;
72+
73+
$githubResult = $this->githubConnector->graphql($query)['data']['repository'];
74+
75+
switch ($this->option('output')) {
76+
case 'html':
77+
HtmlTableConverter::convert($this, $mantisIssues, $githubResult);
78+
79+
break;
80+
default:
81+
CliTableConverter::convert($this, $mantisIssues, $githubResult);
82+
}
83+
84+
return self::SUCCESS;
85+
}
86+
}

src/Helper/CliTableConverter.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\M2G\Helper;
6+
7+
use Artemeon\M2G\Command\IssuesListCommand;
8+
use Artemeon\M2G\Dto\MantisIssue;
9+
10+
class CliTableConverter implements ConverterInterface
11+
{
12+
public static function convert(IssuesListCommand $command, array $mantisIssues, array $githubResult): void
13+
{
14+
$rows = [];
15+
16+
foreach ($mantisIssues as $issue) {
17+
$githubIssues = array_map(static function (array $data) use ($githubResult) {
18+
$status = 'open';
19+
if ($githubResult['issue' . $data['id']]['closed']) {
20+
$status = 'closed';
21+
}
22+
23+
return '#' . $data['id'] . ' (' . $status . ')';
24+
}, UpstreamIssueParser::parse($issue->getUpstreamTicket()));
25+
26+
$rows[] = [
27+
$issue->getId(),
28+
$issue->getProject(),
29+
$issue->getSummary(),
30+
$issue->getAssignee(),
31+
$issue->getStatus(),
32+
implode(', ', $githubIssues),
33+
];
34+
}
35+
36+
$headers = ['ID', 'Project', 'Summary', 'Assignee', 'Status', 'Upstream'];
37+
$command->table($headers, $rows);
38+
}
39+
}

src/Helper/ConverterInterface.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\M2G\Helper;
6+
7+
use Artemeon\M2G\Command\IssuesListCommand;
8+
use Artemeon\M2G\Dto\MantisIssue;
9+
10+
interface ConverterInterface
11+
{
12+
/**
13+
* @param MantisIssue[] $mantisIssues
14+
*/
15+
public static function convert(IssuesListCommand $command, array $mantisIssues, array $githubResult): void;
16+
}

src/Helper/HtmlTableConverter.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\M2G\Helper;
6+
7+
use Artemeon\M2G\Command\IssuesListCommand;
8+
9+
class HtmlTableConverter implements ConverterInterface
10+
{
11+
public static function convert(IssuesListCommand $command, array $mantisIssues, array $githubResult): void
12+
{
13+
$rows = [];
14+
15+
foreach ($mantisIssues as $issue) {
16+
$githubIssues = array_map(static function (array $data) use ($githubResult) {
17+
$githubIssue = $githubResult['issue' . $data['id']] ?? null;
18+
19+
$url = $githubIssue['url'];
20+
$title = $githubIssue['title'];
21+
$number = $data['id'];
22+
$status = $githubIssue['closed'] ? 'closed' : 'open';
23+
$error = $status === 'closed' ? 'error' : '';
24+
25+
return <<<HTML
26+
<tr>
27+
<td><a href="$url" target="_blank">$number</a></td>
28+
<td>$title</td>
29+
<td style="text-align: right;"><span class="label $error">$status</span></td>
30+
</tr>
31+
HTML;
32+
}, UpstreamIssueParser::parse($issue->getUpstreamTicket()));
33+
34+
$githubRows = implode(PHP_EOL, $githubIssues);
35+
36+
$githubTable = '';
37+
if (count($githubIssues)) {
38+
$githubTable = <<<HTML
39+
<table style="width:100%;">
40+
<thead>
41+
<tr>
42+
<th>ID</th>
43+
<th>Title</th>
44+
<th style="text-align: right;">Status</th>
45+
</tr>
46+
</thead>
47+
<tbody>
48+
$githubRows
49+
</tbody>
50+
</table>
51+
HTML;
52+
}
53+
54+
$issueUrl = $issue->getIssueUrl();
55+
$id = $issue->getId();
56+
$project = $issue->getProject();
57+
$summary = $issue->getSummary();
58+
$assignee = $issue->getAssignee();
59+
$status = $issue->getStatus();
60+
$rows[] = <<<HTML
61+
<tr>
62+
<td><a href="$issueUrl">$id</a></td>
63+
<td>$project</td>
64+
<td>$summary</td>
65+
<td>$assignee</td>
66+
<td>$status</td>
67+
<td>$githubTable</td>
68+
</tr>
69+
HTML;
70+
}
71+
72+
$headers = ['ID', 'Project', 'Summary', 'Assignee', 'Status', 'Upstream'];
73+
$htmlRows = implode(' ', $rows);
74+
$headersHTML = implode(PHP_EOL, array_map(static fn (string $header) => '<th>' . $header . '</th>', $headers));
75+
$output = <<<HTML
76+
<!DOCTYPE html>
77+
<html lang="en">
78+
<head>
79+
<meta charset="utf-8">
80+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/picnic">
81+
<title>MANTIS/GitHub Issue Sync</title>
82+
</head>
83+
<body>
84+
<table>
85+
<thead>
86+
<tr>
87+
$headersHTML
88+
</tr>
89+
</thead>
90+
<tbody>
91+
$htmlRows
92+
</tbody>
93+
</table>
94+
</body>
95+
</html>
96+
HTML;
97+
98+
$cwd = getcwd();
99+
$date = date('Y-m-d-His');
100+
101+
$outputPath = $cwd . '/mantis-ticketsync-' . $date . '.html';
102+
103+
file_put_contents($outputPath, $output);
104+
105+
$command->success('Table saved into ' . $outputPath);
106+
}
107+
}

src/Helper/UpstreamIssueParser.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Artemeon\M2G\Helper;
6+
7+
class UpstreamIssueParser
8+
{
9+
public static function parse(string $input): array
10+
{
11+
if (!$input) {
12+
return [];
13+
}
14+
15+
$parts = explode(' ', $input);
16+
17+
$issues = [];
18+
19+
foreach ($parts as $part) {
20+
$trimmedPart = trim($part);
21+
22+
if (!str_starts_with($trimmedPart, 'https://github.com/')) {
23+
continue;
24+
}
25+
26+
if (!preg_match('/\/issues\/(\d+)$/', $trimmedPart, $matches)) {
27+
continue;
28+
}
29+
30+
$issues[] = ['url' => $part, 'id' => $matches[1]];
31+
}
32+
33+
return $issues;
34+
}
35+
}

src/Service/GithubConnector.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Exception;
1010
use GuzzleHttp\Client;
1111
use GuzzleHttp\Exception\GuzzleException;
12+
use GuzzleHttp\RequestOptions;
1213
use JsonException;
1314

1415
class GithubConnector
@@ -98,4 +99,21 @@ final public function getLabels(): array
9899

99100
return $result ?: [];
100101
}
102+
103+
final public function graphql(string $query): array
104+
{
105+
try {
106+
$response = $this->client->post('https://api.github.com/graphql', [
107+
RequestOptions::JSON => [
108+
'query' => $query,
109+
],
110+
]);
111+
112+
$result = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
113+
} catch (GuzzleException | Exception) {
114+
return [];
115+
}
116+
117+
return $result ?: [];
118+
}
101119
}

0 commit comments

Comments
 (0)