Skip to content

Commit cd366d0

Browse files
committed
create fallback issue
1 parent 85b6d25 commit cd366d0

File tree

2 files changed

+106
-61
lines changed

2 files changed

+106
-61
lines changed

src/Issues/Handler.php

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
use Monolog\Handler\AbstractProcessingHandler;
77
use Monolog\Level;
88
use Monolog\LogRecord;
9+
use Illuminate\Http\Client\PendingRequest;
10+
use Illuminate\Http\Client\RequestException;
911

1012
class Handler extends AbstractProcessingHandler
1113
{
1214
private const DEFAULT_LABEL = 'github-issue-logger';
15+
private PendingRequest $client;
1316

1417
/**
1518
* @param string $repo The GitHub repository in "owner/repo" format
@@ -30,6 +33,7 @@ public function __construct(
3033
$this->repo = $repo;
3134
$this->token = $token;
3235
$this->labels = array_unique(array_merge([self::DEFAULT_LABEL], $labels));
36+
$this->client = Http::withToken($this->token)->baseUrl('https://api.github.com');
3337
}
3438

3539
/**
@@ -38,19 +42,27 @@ public function __construct(
3842
protected function write(LogRecord $record): void
3943
{
4044
if (! $record->formatted instanceof Formatted) {
41-
throw new \RuntimeException('Record must be formatted with '.Formatted::class);
45+
throw new \RuntimeException('Record must be formatted with ' . Formatted::class);
4246
}
4347

4448
$formatted = $record->formatted;
45-
$existingIssue = $this->findExistingIssue($record);
4649

47-
if ($existingIssue) {
48-
$this->commentOnIssue($existingIssue['number'], $formatted);
50+
try {
51+
$existingIssue = $this->findExistingIssue($record);
4952

50-
return;
51-
}
53+
if ($existingIssue) {
54+
$this->commentOnIssue($existingIssue['number'], $formatted);
55+
return;
56+
}
57+
58+
$this->createIssue($formatted);
59+
} catch (RequestException $e) {
60+
if ($e->response->serverError()) {
61+
throw $e;
62+
}
5263

53-
$this->createIssue($formatted);
64+
$this->createFallbackIssue($formatted, $e->response->body());
65+
}
5466
}
5567

5668
/**
@@ -62,47 +74,51 @@ private function findExistingIssue(LogRecord $record): ?array
6274
throw new \RuntimeException('Record is missing github_issue_signature in extra data. Make sure the DeduplicationHandler is configured correctly.');
6375
}
6476

65-
$response = Http::withToken($this->token)
66-
->get('https://api.github.com/search/issues', [
67-
'q' => "repo:{$this->repo} is:issue is:open label:".self::DEFAULT_LABEL." \"Signature: {$record->extra['github_issue_signature']}\"",
68-
]);
69-
70-
if ($response->failed()) {
71-
throw new \RuntimeException('Failed to search GitHub issues: '.$response->body());
72-
}
73-
74-
return $response->json('items.0', null);
77+
return $this->client
78+
->get('/search/issues', [
79+
'q' => "repo:{$this->repo} is:issue is:open label:" . self::DEFAULT_LABEL . " \"Signature: {$record->extra['github_issue_signature']}\"",
80+
])
81+
->throw()
82+
->json('items.0', null);
7583
}
7684

7785
/**
7886
* Add a comment to an existing issue
7987
*/
8088
private function commentOnIssue(int $issueNumber, Formatted $formatted): void
8189
{
82-
$response = Http::withToken($this->token)
83-
->post("https://api.github.com/repos/{$this->repo}/issues/{$issueNumber}/comments", [
90+
$this->client
91+
->post("/repos/{$this->repo}/issues/{$issueNumber}/comments", [
8492
'body' => $formatted->comment,
85-
]);
86-
87-
if ($response->failed()) {
88-
throw new \RuntimeException('Failed to comment on GitHub issue: '.$response->body());
89-
}
93+
])
94+
->throw();
9095
}
9196

9297
/**
9398
* Create a new GitHub issue
9499
*/
95100
private function createIssue(Formatted $formatted): void
96101
{
97-
$response = Http::withToken($this->token)
98-
->post("https://api.github.com/repos/{$this->repo}/issues", [
102+
$this->client
103+
->post("/repos/{$this->repo}/issues", [
99104
'title' => $formatted->title,
100105
'body' => $formatted->body,
101106
'labels' => $this->labels,
102-
]);
107+
])
108+
->throw();
109+
}
103110

104-
if ($response->failed()) {
105-
throw new \RuntimeException('Failed to create GitHub issue: '.$response->body());
106-
}
111+
/**
112+
* Create a fallback issue when the main issue creation fails
113+
*/
114+
private function createFallbackIssue(Formatted $formatted, string $errorMessage): void
115+
{
116+
$this->client
117+
->post("/repos/{$this->repo}/issues", [
118+
'title' => '[GitHub Monolog Error] ' . $formatted->title,
119+
'body' => "**Original Error Message:**\n{$formatted->body}\n\n**Integration Error:**\n{$errorMessage}",
120+
'labels' => array_merge($this->labels, ['monolog-integration-error']),
121+
])
122+
->throw();
107123
}
108124
}

tests/Issues/HandlerTest.php

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Tests\Issues;
44

5+
use Illuminate\Http\Client\Request;
6+
use Illuminate\Http\Client\RequestException;
57
use Illuminate\Support\Facades\Http;
68
use Monolog\Level;
79
use Monolog\LogRecord;
@@ -35,91 +37,118 @@ function createRecord(): LogRecord
3537
);
3638
}
3739

38-
test('it creates new github issue when no duplicate exists', function () {
39-
$handler = createHandler();
40-
$record = createRecord();
40+
beforeEach(function () {
41+
Http::preventStrayRequests();
42+
});
4143

44+
test('it creates new github issue when no duplicate exists', function () {
4245
Http::fake([
4346
'github.com/search/issues*' => Http::response(['items' => []]),
4447
'github.com/repos/test/repo/issues' => Http::response(['number' => 1]),
4548
]);
4649

50+
$handler = createHandler();
51+
$record = createRecord();
52+
4753
$handler->handle($record);
4854

49-
Http::assertSent(function ($request) {
50-
return str_contains($request->url(), '/repos/test/repo/issues')
51-
&& $request->method() === 'POST';
55+
Http::assertSent(function (Request $request) {
56+
return str($request->url())->endsWith('/repos/test/repo/issues');
5257
});
5358
});
5459

5560
test('it comments on existing github issue', function () {
56-
$handler = createHandler();
57-
$record = createRecord();
58-
5961
Http::fake([
6062
'github.com/search/issues*' => Http::response(['items' => [['number' => 1]]]),
6163
'github.com/repos/test/repo/issues/1/comments' => Http::response(['id' => 1]),
6264
]);
6365

66+
$handler = createHandler();
67+
$record = createRecord();
68+
6469
$handler->handle($record);
6570

6671
Http::assertSent(function ($request) {
67-
return str_contains($request->url(), '/issues/1/comments')
68-
&& $request->method() === 'POST';
72+
return str($request->url())->endsWith('/issues/1/comments');
6973
});
7074
});
7175

7276
test('it includes signature in issue search', function () {
73-
$handler = createHandler();
74-
$record = createRecord();
75-
7677
Http::fake([
7778
'github.com/search/issues*' => Http::response(['items' => []]),
7879
'github.com/repos/test/repo/issues' => Http::response(['number' => 1]),
7980
]);
8081

82+
$handler = createHandler();
83+
$record = createRecord();
84+
8185
$handler->handle($record);
8286

8387
Http::assertSent(function ($request) {
84-
return str_contains($request->url(), '/search/issues')
85-
&& str_contains($request['q'], 'test-signature');
88+
return str($request->url())->contains('/search/issues')
89+
&& str_contains($request->data()['q'], 'test-signature');
8690
});
8791
});
8892

8993
test('it throws exception when issue search fails', function () {
90-
$handler = createHandler();
91-
$record = createRecord();
92-
9394
Http::fake([
9495
'github.com/search/issues*' => Http::response(['error' => 'Failed'], 500),
9596
]);
9697

97-
expect(fn () => $handler->handle($record))
98-
->toThrow('Failed to search GitHub issues');
99-
});
100-
101-
test('it throws exception when issue creation fails', function () {
10298
$handler = createHandler();
10399
$record = createRecord();
104100

101+
$handler->handle($record);
102+
})->throws(RequestException::class, exceptionCode: 500);
103+
104+
test('it throws exception when issue creation fails', function () {
105105
Http::fake([
106106
'github.com/search/issues*' => Http::response(['items' => []]),
107107
'github.com/repos/test/repo/issues' => Http::response(['error' => 'Failed'], 500),
108108
]);
109109

110-
expect(fn () => $handler->handle($record))
111-
->toThrow('Failed to create GitHub issue');
112-
});
113-
114-
test('it throws exception when comment creation fails', function () {
115110
$handler = createHandler();
116111
$record = createRecord();
117112

113+
$handler->handle($record);
114+
})->throws(RequestException::class, exceptionCode: 500);
115+
116+
test('it throws exception when comment creation fails', function () {
118117
Http::fake([
119118
'github.com/search/issues*' => Http::response(['items' => [['number' => 1]]]),
120119
'github.com/repos/test/repo/issues/1/comments' => Http::response(['error' => 'Failed'], 500),
121120
]);
122121

123-
expect(fn () => $handler->handle($record))
124-
->toThrow('Failed to comment on GitHub issue');
122+
$handler = createHandler();
123+
$record = createRecord();
124+
125+
$handler->handle($record);
126+
})->throws(RequestException::class, exceptionCode: 500);
127+
128+
test('it creates fallback issue when 4xx error occurs', function () {
129+
$errorMessage = 'Validation failed for the issue';
130+
131+
Http::fake([
132+
'github.com/search/issues*' => Http::response(['items' => []]),
133+
'github.com/repos/test/repo/issues' => Http::sequence()
134+
->push(['error' => $errorMessage], 422)
135+
->push(['number' => 1]),
136+
]);
137+
138+
$handler = createHandler();
139+
$record = createRecord();
140+
141+
$handler->handle($record);
142+
143+
Http::assertSent(function ($request) {
144+
return str($request->url())->endsWith('/repos/test/repo/issues')
145+
&& !str_contains($request->data()['title'], '[GitHub Monolog Error]');
146+
});
147+
148+
Http::assertSent(function ($request) use ($errorMessage) {
149+
return str($request->url())->endsWith('/repos/test/repo/issues')
150+
&& str_contains($request->data()['title'], '[GitHub Monolog Error]')
151+
&& str_contains($request->data()['body'], $errorMessage)
152+
&& in_array('monolog-integration-error', $request->data()['labels']);
153+
});
125154
});

0 commit comments

Comments
 (0)