Skip to content

Commit f021ee3

Browse files
committed
Enhance push event formatting and configurability
- Add `push` configuration options to `GitHubRepoConnectionState` and `GitLabRepoConnectionState`, allowing customization of: - Commit display template (`md_bullets` or `html_dropdown`) - Maximum commits to display per push event - Whether to show commit body content - Refactor `onPush` and `onGitLabPush` methods to use `FormatUtil.formatPushEventContent`, ensuring consistent commit formatting - Improve Matrix event messages with enhanced formatting options, supporting collapsible commit lists in `html_dropdown` mode - Apply improvements across GitHub and GitLab integrations
1 parent 1cf64ee commit f021ee3

File tree

5 files changed

+229
-30
lines changed

5 files changed

+229
-30
lines changed

docs/usage/room_configuration/github_repo.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ This connection supports a few options which can be defined in the room state:
4141
|workflowRun.matchingBranch|Only report workflow runs if it matches this regex.|Regex string|*empty*|
4242
|workflowRun.includingWorkflows|Only report workflow runs with a matching workflow name.|Array of: String matching a workflow name|*empty*|
4343
|workflowRun.excludingWorkflows|Never report workflow runs with a matching workflow name.|Array of: String matching a workflow name|*empty*|
44+
|push|Configuration options for push events|`{ template: "md_bullets" \| "html_dropdown", maxCommits: number, showCommitBody: boolean }`|*empty*|
45+
|push.template|Defines the format of the message sent to the room for push events.|`"md_bullets"` \| `"html_dropdown"`|`"html_dropdown"`|
46+
|push.maxCommits|Specifies the maximum number of commits to display in the message.|Positive integer|`5`|
47+
|push.showCommitBody|Determines whether the commit message body (in addition to the commit title) should be included in the notification.|`true` \| `false`|`false`|
4448

4549

4650
[^1]: `ignoreHooks` is no longer accepted for new state events. Use `enableHooks` to explicitly state all events you want to see.

docs/usage/room_configuration/gitlab_project.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ This connection supports a few options which can be defined in the room state[^2
3030
|includeCommentBody|Include the body of a comment when notifying on merge requests|Boolean|false|
3131
|includingLabels|Only notify on issues matching these label names|Array of: String matching a label name|*empty*|
3232
|pushTagsRegex|Only mention pushed tags which match this regex|Regex string|*empty*|
33+
|push|Configuration options for push events|`{ template: "md_bullets" \| "html_dropdown", maxCommits: number, showCommitBody: boolean }`|*empty*|
34+
|push.template|Defines the format of the message sent to the room for push events.|`"md_bullets"` \| `"md_bullets"`|`"html_dropdown"`|
35+
|push.maxCommits|Specifies the maximum number of commits to display in the message.|Positive integer|`5`|
36+
|push.showCommitBody|Determines whether the commit message body (in addition to the commit title) should be included in the notification.|`true` \| `false`|`false`|
3337

3438

3539
[^1]: `ignoreHooks` is no longer accepted for new state events. Use `enableHooks` to explicitly state all events you want to see.

src/Connections/GithubRepo.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@ export interface GitHubRepoConnectionOptions extends IConnectionState {
6464
matchingBranch?: string;
6565
includingWorkflows?: string[];
6666
excludingWorkflows?: string[];
67-
}
67+
};
68+
push?: {
69+
template?: "md_bullets" | "html_dropdown";
70+
maxCommits?: number;
71+
showCommitBody?: boolean;
72+
};
6873
}
6974

7075
export interface GitHubRepoConnectionState extends GitHubRepoConnectionOptions {
@@ -272,6 +277,15 @@ const ConnectionStateSchema = {
272277
items: {type: "string"},
273278
},
274279
},
280+
},
281+
push: {
282+
type: "object",
283+
nullable: true,
284+
properties: {
285+
template: { type: "string", nullable: true, enum: ["md_bullets", "html_dropdown"] },
286+
maxCommits: { type: "number", nullable: true },
287+
showCommitBody: { type: "boolean", nullable: true }
288+
}
275289
}
276290
},
277291
required: [
@@ -1332,8 +1346,33 @@ export class GitHubRepoConnection extends CommandConnection<GitHubRepoConnection
13321346
if (this.hookFilter.shouldSkip('push')) {
13331347
return;
13341348
}
1335-
1336-
const content = `**${event.sender.login}** pushed [${event.commits.length} commit${event.commits.length === 1 ? '' : 's'}](${event.compare}) to \`${event.ref}\` for ${event.repository.full_name}`;
1349+
1350+
const PUSH_MAX_COMMITS = 5;
1351+
const branchName = event.ref.replace("refs/heads/", "");
1352+
const commitsUrl = event.compare;
1353+
const branchUrl = `${event.repository.html_url}/tree/${branchName}`;
1354+
1355+
const { body, formatted_body } = FormatUtil.formatPushEventContent({
1356+
contributors: Object.values(event.commits.reduce((acc: Record<string, string>, commit) => {
1357+
acc[commit.author.name] = commit.author.name;
1358+
return acc;
1359+
}, {} as Record<string, string>)),
1360+
commits: event.commits.map(commit => ({
1361+
id: commit.id,
1362+
url: commit.url,
1363+
message: commit.message,
1364+
author: { name: commit.author.name },
1365+
})),
1366+
branchName,
1367+
branchUrl,
1368+
commitsUrl,
1369+
repoName: event.repository.full_name,
1370+
maxCommits: this.state.push?.maxCommits ?? PUSH_MAX_COMMITS,
1371+
shouldName: true,
1372+
template: this.state.push?.template ?? "html_dropdown",
1373+
showCommitBody: this.state.push?.showCommitBody,
1374+
});
1375+
13371376
const eventContent: IPushEventContent = {
13381377
...FormatUtil.getPartialBodyForGithubRepo(event.repository),
13391378
external_url: event.compare,
@@ -1344,8 +1383,8 @@ export class GitHubRepoConnection extends CommandConnection<GitHubRepoConnection
13441383
base_ref: event.base_ref,
13451384
},
13461385
msgtype: "m.notice",
1347-
body: content,
1348-
formatted_body: md.render(content),
1386+
body,
1387+
formatted_body,
13491388
format: "org.matrix.custom.html",
13501389
};
13511390
await this.intent.sendEvent(this.roomId, eventContent);

src/Connections/GitlabRepo.ts

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { GitLabClient } from "../Gitlab/Client";
1919
import { IBridgeStorageProvider } from "../Stores/StorageProvider";
2020
import axios from "axios";
2121
import { GitLabGrantChecker } from "../Gitlab/GrantChecker";
22+
import { FormatUtil } from "../FormatUtil";
2223

2324
export interface GitLabRepoConnectionState extends IConnectionState {
2425
instance: string;
@@ -33,6 +34,11 @@ export interface GitLabRepoConnectionState extends IConnectionState {
3334
pushTagsRegex?: string,
3435
includingLabels?: string[];
3536
excludingLabels?: string[];
37+
push?: {
38+
template?: "md_bullets" | "html_dropdown";
39+
maxCommits?: number;
40+
showCommitBody?: boolean;
41+
};
3642
}
3743

3844
interface ConnectionStateValidated extends GitLabRepoConnectionState {
@@ -149,6 +155,15 @@ const ConnectionStateSchema = {
149155
type: "boolean",
150156
nullable: true,
151157
},
158+
push: {
159+
type: "object",
160+
nullable: true,
161+
properties: {
162+
template: { type: "string", nullable: true, enum: ["md_bullets", "html_dropdown"] },
163+
maxCommits: { type: "number", nullable: true },
164+
showCommitBody: { type: "boolean", nullable: true }
165+
}
166+
}
152167
},
153168
required: [
154169
"instance",
@@ -668,35 +683,36 @@ export class GitLabRepoConnection extends CommandConnection<GitLabRepoConnection
668683
return;
669684
}
670685
log.info(`onGitLabPush ${this.roomId} ${this.instance.url}/${this.path} ${event.after}`);
671-
const branchname = event.ref.replace("refs/heads/", "");
672-
const commitsurl = `${event.project.homepage}/-/commits/${branchname}`;
673-
const branchurl = `${event.project.homepage}/-/tree/${branchname}`;
686+
const branchName = event.ref.replace("refs/heads/", "");
687+
const commitsUrl = `${event.project.homepage}/-/commits/${branchName}`;
688+
const branchUrl = `${event.project.homepage}/-/tree/${branchName}`;
674689
const shouldName = !event.commits.every(c => c.author.email === event.user_email);
675690

676-
const tooManyCommits = event.total_commits_count > PUSH_MAX_COMMITS;
677-
const displayedCommits = tooManyCommits ? 1 : Math.min(event.total_commits_count, PUSH_MAX_COMMITS);
678-
679-
// Take the top 5 commits. The array is ordered in reverse.
680-
const commits = event.commits.reverse().slice(0,displayedCommits).map(commit => {
681-
return `[\`${commit.id.slice(0,8)}\`](${event.project.homepage}/-/commit/${commit.id}) ${commit.title}${shouldName ? ` by ${commit.author.name}` : ""}`;
682-
}).join('\n - ');
683-
684-
let content = `**${event.user_name}** pushed [${event.total_commits_count} commit${event.total_commits_count > 1 ? "s": ""}](${commitsurl})`
685-
+ ` to [\`${branchname}\`](${branchurl}) for ${event.project.path_with_namespace}`;
686-
687-
if (displayedCommits >= 2) {
688-
content += `\n - ${commits}\n`;
689-
} else if (displayedCommits === 1) {
690-
content += `: ${commits}`;
691-
if (tooManyCommits) {
692-
content += `, and [${event.total_commits_count - 1} more](${commitsurl}) commits`;
693-
}
694-
}
691+
const { body, formatted_body } = FormatUtil.formatPushEventContent({
692+
contributors: Object.values(event.commits.reduce((acc: Record<string, string>, commit) => {
693+
acc[commit.author.name] = commit.author.name;
694+
return acc;
695+
}, {} as Record<string, string>)),
696+
commits: event.commits.map(commit => ({
697+
id: commit.id,
698+
url: commit.url,
699+
message: commit.message,
700+
author: { name: commit.author.name },
701+
})),
702+
branchName,
703+
branchUrl,
704+
commitsUrl,
705+
repoName: event.repository.name,
706+
maxCommits: this.state.push?.maxCommits ?? PUSH_MAX_COMMITS,
707+
shouldName,
708+
template: this.state.push?.template ?? "html_dropdown",
709+
showCommitBody: this.state.push?.showCommitBody,
710+
});
695711

696712
await this.intent.sendEvent(this.roomId, {
697713
msgtype: "m.notice",
698-
body: content,
699-
formatted_body: md.render(content),
714+
body,
715+
formatted_body,
700716
format: "org.matrix.custom.html",
701717
});
702718
}

src/FormatUtil.ts

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { ProjectsListResponseData } from './github/Types';
22
import { emojify } from "node-emoji";
3+
import markdown from "markdown-it";
34
import { JiraIssue } from './jira/Types';
45
import { formatLabels, getPartialBodyForJiraIssue, hashId, getPartialBodyForGithubIssue, getPartialBodyForGithubRepo, MinimalGitHubIssue } from "./libRs";
56

7+
const md = new markdown();
8+
69
interface IMinimalPR {
710
html_url: string;
811
id: number;
@@ -13,7 +16,6 @@ interface IMinimalPR {
1316
};
1417
}
1518

16-
1719
export interface ILabel {
1820
color?: string,
1921
name: string,
@@ -44,6 +46,140 @@ export class FormatUtil {
4446
return `${repo.html_url}`;
4547
}
4648

49+
public static formatPushEventContent({
50+
contributors,
51+
commits,
52+
branchName,
53+
branchUrl,
54+
commitsUrl,
55+
repoName,
56+
maxCommits = 5,
57+
shouldName = false,
58+
template = "md_bullets",
59+
showCommitBody = false,
60+
}: {
61+
contributors: string[];
62+
commits: {
63+
id: string;
64+
url: string;
65+
message: string;
66+
author: { name: string };
67+
}[];
68+
branchName: string;
69+
branchUrl: string;
70+
commitsUrl: string;
71+
repoName: string;
72+
maxCommits?: number;
73+
shouldName?: boolean;
74+
template?: "md_bullets" | "html_dropdown";
75+
showCommitBody?: boolean;
76+
}) {
77+
const tooManyCommits = commits.length > maxCommits;
78+
const displayedCommits = Math.min(commits.length, maxCommits);
79+
const separator = template === "md_bullets" ? "\n" : "<br>";
80+
const multipleContributors = contributors.length > 1;
81+
82+
const formatCommitMessage = ({
83+
commit,
84+
showAuthor,
85+
}: {
86+
commit: typeof commits[0];
87+
showAuthor: boolean;
88+
}) => {
89+
const { id, url, message, author } = commit;
90+
const [title, ...body] = message.split("\n");
91+
const authorInfo =
92+
shouldName && showAuthor ? ` by \`${author.name}\`` : "";
93+
const formattedBody =
94+
showCommitBody && body.length
95+
? `${separator}${body.join(separator)}`
96+
: "";
97+
const commitId = id.slice(0, 8);
98+
99+
return template === "md_bullets"
100+
? `[\`${commitId}\`](${url}) ${title}${authorInfo}${formattedBody}`
101+
: `- <a href="${url}"><code>${commitId}</code></a> ${title}${authorInfo}${formattedBody}`;
102+
};
103+
104+
if (template === "html_dropdown") {
105+
if (commits.length === 1) {
106+
const singleCommitMessage = formatCommitMessage({
107+
commit: commits[0],
108+
showAuthor: false,
109+
});
110+
111+
return {
112+
body: `**${contributors.join(", ")}** pushed 1 commit to [\`${branchName}\`](${branchUrl}) for ${repoName}: ${singleCommitMessage}`,
113+
formatted_body: `<b>${contributors.join(
114+
", "
115+
)}</b> pushed 1 commit to <a href="${branchUrl}"><code>${branchName}</code></a>
116+
for ${repoName}: ${singleCommitMessage}`,
117+
format: "org.matrix.custom.html",
118+
};
119+
}
120+
121+
const commitList = commits
122+
.slice(0, displayedCommits)
123+
.map((commit) =>
124+
formatCommitMessage({
125+
commit,
126+
showAuthor: multipleContributors,
127+
})
128+
)
129+
.join("<hr>");
130+
131+
const extraCommits = tooManyCommits
132+
? `<br><br><a href="${commitsUrl}">and ${commits.length - displayedCommits} more commits</a>`
133+
: "";
134+
135+
return {
136+
body: `**${contributors.join(", ")}** pushed [${commits.length} commit${
137+
commits.length === 1 ? "" : "s"
138+
}](${commitsUrl}) to [\`${branchName}\`](${branchUrl}) for ${repoName}`,
139+
formatted_body: `
140+
<details>
141+
<summary><b>${contributors.join(", ")}</b> pushed
142+
<a href="${commitsUrl}">${commits.length} commit${
143+
commits.length === 1 ? "" : "s"
144+
}</a> to <a href="${branchUrl}"><code>${branchName}</code></a> for ${repoName}
145+
</summary>
146+
<br>${commitList}${extraCommits}
147+
</details>
148+
`,
149+
format: "org.matrix.custom.html",
150+
};
151+
}
152+
153+
const commitMessages = commits
154+
.slice(0, displayedCommits)
155+
.map((commit) =>
156+
formatCommitMessage({ commit, showAuthor: multipleContributors })
157+
)
158+
.join("\n - ");
159+
160+
let content = `**${contributors.join(", ")}** pushed [${commits.length} commit${
161+
commits.length === 1 ? "" : "s"
162+
}](${commitsUrl}) to [\`${branchName}\`](${branchUrl}) for ${repoName}`;
163+
164+
if (displayedCommits === 1) {
165+
content += `: ${formatCommitMessage({
166+
commit: commits[0],
167+
showAuthor: false,
168+
})}`;
169+
} else if (displayedCommits > 1) {
170+
content += `\n - ${commitMessages}\n`;
171+
}
172+
173+
if (tooManyCommits) {
174+
content += `\nand [${commits.length - displayedCommits} more](${commitsUrl}) commits`;
175+
}
176+
177+
return {
178+
body: content,
179+
formatted_body: md.render(content),
180+
};
181+
}
182+
47183
public static getPartialBodyForGithubRepo(repo: LooseMinimalGitHubRepo) {
48184
if (!repo.id || !repo.html_url || !repo.full_name) {
49185
throw Error('Missing keys in repo object');

0 commit comments

Comments
 (0)