Skip to content

Commit ddd1ff1

Browse files
feat: add GitHub support
- Introduce unified provider abstraction and models for GitLab/GitHub - Implement GitHub provider (API client + webhooks) and extend GitLab provider - Add approval & pipeline info (fetchApprovalInfo, UnifiedApprovalInfo, PR.pipeline) - Improve review messages with pipeline/mergeable emojis and accurate approval counts - Restore project threshold warnings and add GitHub project channel counting
1 parent 1e1b94a commit ddd1ff1

File tree

68 files changed

+6168
-365
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+6168
-365
lines changed

.env.development

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
API_BASE_PATH=/api/v1/homer
22
EMAIL_DOMAINS=my-domain.com,ext.my-domain.com
3+
GITHUB_SECRET=GITHUB_SECRET
4+
GITHUB_TOKEN=GITHUB_TOKEN
35
GITLAB_SECRET=GITLAB_SECRET
46
GITLAB_TOKEN=GITLAB_TOKEN
57
GITLAB_URL=https://my-git.domain.com

.env.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
API_BASE_PATH=/api/v1/homer
33
EMAIL_DOMAINS=my-domain.com,ext.my-domain.com
4+
GITHUB_SECRET=GITHUB_SECRET
5+
GITHUB_TOKEN=GITHUB_TOKEN
46
GITLAB_SECRET=GITLAB_SECRET
57
GITLAB_TOKEN=GITLAB_TOKEN
68
GITLAB_URL=https://my-git.domain.com

README.md

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
![Homer](docs/assets/homer256.png)
66

77
Homer is a **Slack** bot intended to help you to easily **share and follow
8-
Gitlab merge requests**.
8+
GitLab merge requests and GitHub pull requests**.
99

1010
## Why Homer?
1111

@@ -20,8 +20,8 @@ merge them more quickly:
2020

2121
## How does it work?
2222

23-
Homer communicates with both **Slack** and **Gitlab** to get merge request
24-
information and publish Slack messages.
23+
Homer communicates with **Slack**, **GitLab**, and **GitHub** to get merge request
24+
and pull request information and publish Slack messages.
2525

2626
![Architecture](docs/assets/archi-dark.png#gh-dark-mode-only)
2727
![Architecture](docs/assets/archi-light.png#gh-light-mode-only)
@@ -32,15 +32,15 @@ information and publish Slack messages.
3232

3333
Here are the available commands:
3434

35-
| Command | Description |
36-
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37-
| `/homer changelog` | Display changelogs, for any Gitlab project, between 2 release tags. |
38-
| `/homer project add <project_name\|project_id>` | Add a Gitlab project to a channel. |
39-
| `/homer project list` | List the Gitlab projects added to a channel. |
40-
| `/homer project remove` | Remove a Gitlab project from a channel. |
41-
| `/homer release` | Create a release for configured Gitlab project in a channel. |
42-
| `/homer review <search>` | Share a merge request on a channel.<br />Searches in title and description by default.<br />Accepts merge request URLs and merge request IDs prefixed with "!". |
43-
| `/homer review list` | List ongoing reviews shared in a channel. |
35+
| Command | Description |
36+
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37+
| `/homer changelog` | Display changelogs, for any GitLab project, between 2 release tags. |
38+
| `/homer project add <project_name\|project_id>` | Add a GitLab project (by ID or name) or GitHub repository (owner/repo) to a channel. |
39+
| `/homer project list` | List the projects added to a channel. |
40+
| `/homer project remove` | Remove a project from a channel. |
41+
| `/homer release` | Create a release for configured GitLab project in a channel. |
42+
| `/homer review <search>` | Share a merge request/pull request on a channel.<br />Searches in title and description by default.<br />Accepts MR/PR URLs and IDs (GitLab: !123, GitHub: #123). |
43+
| `/homer review list` | List ongoing reviews shared in a channel. |
4444

4545
### Share a merge request using Homer
4646

@@ -127,6 +127,59 @@ If you want to share a merge request in a Slack channel, you can add one of the
127127

128128
More information about the labels can be found in the [Gitlab documentation](https://docs.gitlab.com/user/project/labels/).
129129

130+
### Share a GitHub pull request using Homer
131+
132+
Homer also supports GitHub repositories! Here's how to set it up:
133+
134+
#### 1. Configure GitHub webhook
135+
136+
To keep Slack messages up to date, Homer needs to receive notifications from GitHub:
137+
138+
- Ask for Homer's `GITHUB_SECRET` from the person managing Homer in your organization.
139+
140+
- Go to your GitHub repository settings → Webhooks:
141+
`https://github.com/OWNER/REPO/settings/hooks`
142+
143+
- Click "Add webhook" and configure:
144+
145+
- **Payload URL**: `HOMER_BASE_URL/api/v1/homer/github`
146+
- **Content type**: `application/json`
147+
- **Secret**: Enter the value of `GITHUB_SECRET`
148+
- **Events**: Select individual events:
149+
- ✅ Pull requests
150+
- ✅ Issue comments
151+
- ✅ Pull request reviews
152+
153+
- Click "Add webhook"
154+
155+
- Ensure the GitHub user associated with your `GITHUB_TOKEN` has at least **Read** access to the repository.
156+
157+
#### 2. Add the GitHub repository to a Slack channel
158+
159+
Inside the Slack channel, run:
160+
161+
```
162+
/homer project add owner/repo
163+
```
164+
165+
For example: `/homer project add facebook/react`
166+
167+
> [!WARNING]
168+
> If you want to use Homer in a private channel, you need to invite it to the channel first.
169+
170+
#### 3. Share the pull request
171+
172+
Use the `/homer review <search>` command:
173+
174+
- By PR number: `/homer review #42`
175+
- By URL: `/homer review https://github.com/facebook/react/pull/12345`
176+
- By search: `/homer review fix authentication bug`
177+
178+
To see all ongoing reviews: `/homer review list`
179+
180+
> [!NOTE]
181+
> GitHub labels like `homer-review` are not yet supported, but webhook-based automatic sharing will be added in a future update.
182+
130183
## Install
131184

132185
> [!NOTE]

__mocks__/fetch-mock.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export interface HttpCallMock {
22
called: boolean;
3-
calledWith: [Request | URL, RequestInit | undefined] | undefined;
3+
calledWith: [string | URL, RequestInit | undefined] | undefined;
44
responseBody: unknown;
55
status?: number;
66
}
@@ -25,11 +25,14 @@ export function mockUrl(
2525
return fetchMocks[url];
2626
}
2727

28-
export function createFetchMock(originalFetch: typeof fetch) {
29-
return async (
30-
input: Request | URL,
31-
init?: RequestInit,
32-
): Promise<Response> => {
28+
export function mockFetch(mocks: Record<string, unknown>, status = 200): void {
29+
Object.entries(mocks).forEach(([url, responseBody]) => {
30+
mockUrl(url, responseBody, status);
31+
});
32+
}
33+
34+
export function createFetchMock(originalFetch: any) {
35+
return async (input: string | URL, init?: RequestInit): Promise<Response> => {
3336
const url = typeof input === 'string' ? input : (input as URL).toString();
3437

3538
if (url.includes('my-git.domain.com') || url.includes('slack')) {
@@ -42,12 +45,13 @@ export function createFetchMock(originalFetch: typeof fetch) {
4245
mock.called = true;
4346
mock.calledWith = [input, init];
4447

45-
const response = new Response(JSON.stringify(mock.responseBody), {
48+
// Return a mock response object
49+
return Promise.resolve({
50+
json: async () => mock.responseBody,
4651
status: mock.status,
47-
headers: { 'Content-Type': 'application/json' },
48-
});
49-
50-
return Promise.resolve(response);
52+
ok: (mock.status || 200) >= 200 && (mock.status || 200) < 300,
53+
headers: new Map([['Content-Type', 'application/json']]),
54+
} as any as Response);
5155
}
5256

5357
return originalFetch(input, init);

__tests__/__fixtures__/mergeRequestDetailsFixture.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,12 @@ export const mergeRequestDetailsFixture: GitlabMergeRequestDetails = {
7171
force_remove_source_branch: false,
7272
allow_collaboration: false,
7373
allow_maintainer_to_push: false,
74-
web_url: 'http://gitlab.example.com/my-group/my-project/-/merge_requests/1',
74+
web_url:
75+
'http://gitlab.example.com/diaspora/diaspora-project-site/-/merge_requests/1',
7576
references: {
7677
short: '!1',
7778
relative: '!1',
78-
full: 'my-group/my-project!1',
79+
full: 'diaspora/diaspora-project-site!1',
7980
},
8081
time_stats: {
8182
time_estimate: 0,

__tests__/__fixtures__/reviewMessage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const reviewMessagePostFixture = {
4343
{
4444
elements: [
4545
{
46-
text: `Project: _<${projectFixture.web_url}|${projectFixture.path_with_namespace}>_`,
46+
text: `:gitlab: Project: _<${projectFixture.web_url}|${projectFixture.path_with_namespace}>_`,
4747
type: 'mrkdwn',
4848
},
4949
{

__tests__/changelog/utils/updateChangelog.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AnyBlock } from '@slack/types/dist/block-kit/blocks';
1+
import type { KnownBlock } from '@slack/types';
22
import type { InputBlock, StaticSelect, View } from '@slack/web-api';
33
import { buildChangelogModalView } from '@/changelog/buildChangelogModalView';
44
import { updateChangelog } from '@/changelog/utils/updateChangelog';
@@ -46,7 +46,7 @@ const makePayload = ({
4646
projectIdValue,
4747
releaseTagValue,
4848
}: {
49-
blocks: AnyBlock[];
49+
blocks: KnownBlock[];
5050
projectIdValue: string;
5151
releaseTagValue?: string;
5252
}): BlockActionsPayload => ({
@@ -160,7 +160,7 @@ describe('updateChangelog', () => {
160160
];
161161

162162
const payload = makePayload({
163-
blocks,
163+
blocks: blocks as KnownBlock[],
164164
projectIdValue: '101',
165165
releaseTagValue: 'v1.0.0',
166166
});
@@ -181,17 +181,18 @@ describe('updateChangelog', () => {
181181
expect(firstCallArg.view_id).toBe('V123');
182182
expect(
183183
firstCallArg.view.blocks.some(
184-
(b: AnyBlock) => b.block_id === 'to-remove-1',
184+
(b: KnownBlock) => (b as any).block_id === 'to-remove-1',
185185
),
186186
).toBe(false);
187187
expect(
188188
firstCallArg.view.blocks.some(
189-
(b: AnyBlock) => b.block_id === 'to-remove-2',
189+
(b: KnownBlock) => (b as any).block_id === 'to-remove-2',
190190
),
191191
).toBe(false);
192192
expect(
193193
firstCallArg.view.blocks.some(
194-
(b: AnyBlock) => b.block_id === 'changelog-release-tag-info-block',
194+
(b: KnownBlock) =>
195+
(b as any).block_id === 'changelog-release-tag-info-block',
195196
),
196197
).toBe(true);
197198
expect(firstCallArg.view.blocks.at(-1)).toEqual({
@@ -229,7 +230,7 @@ describe('updateChangelog', () => {
229230
];
230231

231232
const payload = makePayload({
232-
blocks,
233+
blocks: blocks as KnownBlock[],
233234
projectIdValue: '201',
234235
releaseTagValue: undefined,
235236
});

__tests__/core/errorManagement.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ describe('core > errorManagement', () => {
5454
text: `review ${search}`,
5555
user_id: userId,
5656
};
57-
await addProjectToChannel({ channelId, projectId });
57+
await addProjectToChannel({
58+
channelId,
59+
projectId,
60+
projectIdString: null,
61+
providerType: 'gitlab',
62+
});
5863
mockGitlabCall(
5964
`/projects/${projectId}/merge_requests?state=opened&search=${search}`,
6065
[],
@@ -90,7 +95,12 @@ describe('core > errorManagement', () => {
9095
text: `review ${search}`,
9196
user_id: userId,
9297
};
93-
await addProjectToChannel({ channelId, projectId });
98+
await addProjectToChannel({
99+
channelId,
100+
projectId,
101+
projectIdString: null,
102+
providerType: 'gitlab',
103+
});
94104
mockGitlabCall(
95105
`/projects/${projectId}/merge_requests?state=opened&search=${search}`,
96106
[],

0 commit comments

Comments
 (0)