Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
API_BASE_PATH=/api/v1/homer
EMAIL_DOMAINS=my-domain.com,ext.my-domain.com
GITHUB_SECRET=GITHUB_SECRET
GITHUB_TOKEN=GITHUB_TOKEN
GITLAB_SECRET=GITLAB_SECRET
GITLAB_TOKEN=GITLAB_TOKEN
GITLAB_URL=https://my-git.domain.com
Expand Down
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

API_BASE_PATH=/api/v1/homer
EMAIL_DOMAINS=my-domain.com,ext.my-domain.com
GITHUB_SECRET=GITHUB_SECRET
GITHUB_TOKEN=GITHUB_TOKEN
GITLAB_SECRET=GITLAB_SECRET
GITLAB_TOKEN=GITLAB_TOKEN
GITLAB_URL=https://my-git.domain.com
Expand Down
77 changes: 65 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
![Homer](docs/assets/homer256.png)

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

## Why Homer?

Expand All @@ -20,8 +20,8 @@ merge them more quickly:

## How does it work?

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

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

Here are the available commands:

| Command | Description |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/homer changelog` | Display changelogs, for any Gitlab project, between 2 release tags. |
| `/homer project add <project_name\|project_id>` | Add a Gitlab project to a channel. |
| `/homer project list` | List the Gitlab projects added to a channel. |
| `/homer project remove` | Remove a Gitlab project from a channel. |
| `/homer release` | Create a release for configured Gitlab project in a channel. |
| `/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 "!". |
| `/homer review list` | List ongoing reviews shared in a channel. |
| Command | Description |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/homer changelog` | Display changelogs, for any GitLab project, between 2 release tags. |
| `/homer project add <project_name\|project_id>` | Add a GitLab project (by ID or name) or GitHub repository (owner/repo) to a channel. |
| `/homer project list` | List the projects added to a channel. |
| `/homer project remove` | Remove a project from a channel. |
| `/homer release` | Create a release for configured GitLab project in a channel. |
| `/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). |
| `/homer review list` | List ongoing reviews shared in a channel. |

### Share a merge request using Homer

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

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

### Share a GitHub pull request using Homer

Homer also supports GitHub repositories! Here's how to set it up:

#### 1. Configure GitHub webhook

To keep Slack messages up to date, Homer needs to receive notifications from GitHub:

- Ask for Homer's `GITHUB_SECRET` from the person managing Homer in your organization.

- Go to your GitHub repository settings → Webhooks:
`https://github.com/OWNER/REPO/settings/hooks`

- Click "Add webhook" and configure:

- **Payload URL**: `HOMER_BASE_URL/api/v1/homer/github`
- **Content type**: `application/json`
- **Secret**: Enter the value of `GITHUB_SECRET`
- **Events**: Select individual events:
- ✅ Pull requests
- ✅ Issue comments
- ✅ Pull request reviews

- Click "Add webhook"

- Ensure the GitHub user associated with your `GITHUB_TOKEN` has at least **Read** access to the repository.

#### 2. Add the GitHub repository to a Slack channel

Inside the Slack channel, run:

```
/homer project add owner/repo
```

For example: `/homer project add facebook/react`

> [!WARNING]
> If you want to use Homer in a private channel, you need to invite it to the channel first.

#### 3. Share the pull request

Use the `/homer review <search>` command:

- By PR number: `/homer review #42`
- By URL: `/homer review https://github.com/facebook/react/pull/12345`
- By search: `/homer review fix authentication bug`

To see all ongoing reviews: `/homer review list`

> [!NOTE]
> GitHub labels like `homer-review` are not yet supported, but webhook-based automatic sharing will be added in a future update.

## Install

> [!NOTE]
Expand Down
26 changes: 15 additions & 11 deletions __mocks__/fetch-mock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface HttpCallMock {
called: boolean;
calledWith: [Request | URL, RequestInit | undefined] | undefined;
calledWith: [string | URL, RequestInit | undefined] | undefined;
responseBody: unknown;
status?: number;
}
Expand All @@ -25,11 +25,14 @@ export function mockUrl(
return fetchMocks[url];
}

export function createFetchMock(originalFetch: typeof fetch) {
return async (
input: Request | URL,
init?: RequestInit,
): Promise<Response> => {
export function mockFetch(mocks: Record<string, unknown>, status = 200): void {
Object.entries(mocks).forEach(([url, responseBody]) => {
mockUrl(url, responseBody, status);
});
}

export function createFetchMock(originalFetch: any) {
return async (input: string | URL, init?: RequestInit): Promise<Response> => {
const url = typeof input === 'string' ? input : (input as URL).toString();

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

const response = new Response(JSON.stringify(mock.responseBody), {
// Return a mock response object
return Promise.resolve({
json: async () => mock.responseBody,
status: mock.status,
headers: { 'Content-Type': 'application/json' },
});

return Promise.resolve(response);
ok: (mock.status || 200) >= 200 && (mock.status || 200) < 300,
headers: new Map([['Content-Type', 'application/json']]),
} as any as Response);
}

return originalFetch(input, init);
Expand Down
5 changes: 3 additions & 2 deletions __tests__/__fixtures__/mergeRequestDetailsFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ export const mergeRequestDetailsFixture: GitlabMergeRequestDetails = {
force_remove_source_branch: false,
allow_collaboration: false,
allow_maintainer_to_push: false,
web_url: 'http://gitlab.example.com/my-group/my-project/-/merge_requests/1',
web_url:
'http://gitlab.example.com/diaspora/diaspora-project-site/-/merge_requests/1',
references: {
short: '!1',
relative: '!1',
full: 'my-group/my-project!1',
full: 'diaspora/diaspora-project-site!1',
},
time_stats: {
time_estimate: 0,
Expand Down
2 changes: 1 addition & 1 deletion __tests__/__fixtures__/reviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const reviewMessagePostFixture = {
{
elements: [
{
text: `Project: _<${projectFixture.web_url}|${projectFixture.path_with_namespace}>_`,
text: `:gitlab: Project: _<${projectFixture.web_url}|${projectFixture.path_with_namespace}>_`,
type: 'mrkdwn',
},
{
Expand Down
15 changes: 8 additions & 7 deletions __tests__/changelog/utils/updateChangelog.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AnyBlock } from '@slack/types/dist/block-kit/blocks';
import type { KnownBlock } from '@slack/types';
import type { InputBlock, StaticSelect, View } from '@slack/web-api';
import { buildChangelogModalView } from '@/changelog/buildChangelogModalView';
import { updateChangelog } from '@/changelog/utils/updateChangelog';
Expand Down Expand Up @@ -46,7 +46,7 @@ const makePayload = ({
projectIdValue,
releaseTagValue,
}: {
blocks: AnyBlock[];
blocks: KnownBlock[];
projectIdValue: string;
releaseTagValue?: string;
}): BlockActionsPayload => ({
Expand Down Expand Up @@ -160,7 +160,7 @@ describe('updateChangelog', () => {
];

const payload = makePayload({
blocks,
blocks: blocks as KnownBlock[],
projectIdValue: '101',
releaseTagValue: 'v1.0.0',
});
Expand All @@ -181,17 +181,18 @@ describe('updateChangelog', () => {
expect(firstCallArg.view_id).toBe('V123');
expect(
firstCallArg.view.blocks.some(
(b: AnyBlock) => b.block_id === 'to-remove-1',
(b: KnownBlock) => (b as any).block_id === 'to-remove-1',
),
).toBe(false);
expect(
firstCallArg.view.blocks.some(
(b: AnyBlock) => b.block_id === 'to-remove-2',
(b: KnownBlock) => (b as any).block_id === 'to-remove-2',
),
).toBe(false);
expect(
firstCallArg.view.blocks.some(
(b: AnyBlock) => b.block_id === 'changelog-release-tag-info-block',
(b: KnownBlock) =>
(b as any).block_id === 'changelog-release-tag-info-block',
),
).toBe(true);
expect(firstCallArg.view.blocks.at(-1)).toEqual({
Expand Down Expand Up @@ -229,7 +230,7 @@ describe('updateChangelog', () => {
];

const payload = makePayload({
blocks,
blocks: blocks as KnownBlock[],
projectIdValue: '201',
releaseTagValue: undefined,
});
Expand Down
14 changes: 12 additions & 2 deletions __tests__/core/errorManagement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ describe('core > errorManagement', () => {
text: `review ${search}`,
user_id: userId,
};
await addProjectToChannel({ channelId, projectId });
await addProjectToChannel({
channelId,
projectId,
projectIdString: null,
providerType: 'gitlab',
});
mockGitlabCall(
`/projects/${projectId}/merge_requests?state=opened&search=${search}`,
[],
Expand Down Expand Up @@ -90,7 +95,12 @@ describe('core > errorManagement', () => {
text: `review ${search}`,
user_id: userId,
};
await addProjectToChannel({ channelId, projectId });
await addProjectToChannel({
channelId,
projectId,
projectIdString: null,
providerType: 'gitlab',
});
mockGitlabCall(
`/projects/${projectId}/merge_requests?state=opened&search=${search}`,
[],
Expand Down
Loading
Loading