Skip to content

Commit a42185c

Browse files
authored
Add percentageThreshold parameter (#72)
A comment will only be posted if duration increase/decrease in percentage is above the threshold. If a previous comment exists and the threshold is no longer met, the comment will be automatically deleted. Fixes #68
1 parent aa800cc commit a42185c

File tree

8 files changed

+177
-15
lines changed

8 files changed

+177
-15
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@ specify what branch to compare with.
5757
compareBranch: 'your-branch-name'
5858
```
5959

60+
### Setting a percentage threshold
61+
62+
To avoid comment spam, you can set a minimum percentage change required before a
63+
comment is posted.
64+
65+
```yml
66+
- name: Time reporter
67+
uses: DeviesDevelopment/workflow-timer@v0.2.0
68+
with:
69+
percentageThreshold: 10
70+
```
71+
72+
With `percentageThreshold: 10`, comments will only be posted when the workflow
73+
duration changes by 10% or more. If a previous comment exists and the threshold
74+
is no longer met, the comment will be automatically deleted.
75+
6076
## How to contribute
6177

6278
Feel free to open a pull request! All contributions, no matter how small, are

__tests__/main.test.ts

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const listWorkflowRuns = jest.fn()
66
const listComments = jest.fn()
77
const createComment = jest.fn()
88
const updateComment = jest.fn()
9+
const deleteComment = jest.fn()
910

1011
jest.unstable_mockModule('../src/githubClient.js', () => ({
1112
default: class {
@@ -14,6 +15,7 @@ jest.unstable_mockModule('../src/githubClient.js', () => ({
1415
listComments = listComments
1516
createComment = createComment
1617
updateComment = updateComment
18+
deleteComment = deleteComment
1719
}
1820
}))
1921

@@ -78,7 +80,8 @@ describe('main', () => {
7880
await run(
7981
{ ...DEFAULT_CONTEXT, eventName: 'not-pull_request' },
8082
'fake-token',
81-
'main'
83+
'main',
84+
0
8285
)
8386
expect(createComment).not.toHaveBeenCalled()
8487
expect(updateComment).not.toHaveBeenCalled()
@@ -92,7 +95,8 @@ describe('main', () => {
9295
workflow: 'My workflow'
9396
},
9497
'fake-token',
95-
'main'
98+
'main',
99+
0
96100
)
97101

98102
expect(createComment).toHaveBeenCalledWith(
@@ -122,7 +126,8 @@ describe('main', () => {
122126
workflow: 'Another workflow'
123127
},
124128
'fake-token',
125-
'main'
129+
'main',
130+
0
126131
)
127132

128133
expect(updateComment).toHaveBeenCalledWith(
@@ -159,11 +164,97 @@ describe('main', () => {
159164
workflow: 'Some workflow'
160165
},
161166
'fake-token',
162-
'main'
167+
'main',
168+
0
163169
)
164170

165171
expect(createComment).toHaveBeenCalledWith(
166172
'🕒 Workflow "Some workflow" took 180s which is an increase with 120s (200.00%) compared to latest run on main.'
167173
)
168174
})
175+
176+
it('does not create comment when change is below threshold', async () => {
177+
getCurrentWorkflowRun.mockReturnValueOnce({
178+
data: {
179+
run_started_at: '2025-04-29T13:57:00Z',
180+
workflow_id: 42
181+
}
182+
})
183+
listWorkflowRuns.mockReturnValueOnce({
184+
data: {
185+
workflow_runs: [
186+
{
187+
...DEFAULT_WORKFLOW_RUN,
188+
run_started_at: '2025-04-28T13:56:30Z',
189+
updated_at: '2025-04-28T13:59:40Z',
190+
head_branch: 'main',
191+
status: 'completed',
192+
conclusion: 'success'
193+
}
194+
]
195+
}
196+
})
197+
await run(
198+
{
199+
...DEFAULT_CONTEXT,
200+
eventName: 'pull_request',
201+
workflow: 'Some workflow'
202+
},
203+
'fake-token',
204+
'main',
205+
10
206+
)
207+
208+
expect(createComment).not.toHaveBeenCalled()
209+
expect(updateComment).not.toHaveBeenCalled()
210+
})
211+
212+
it('deletes existing comment when change is below threshold', async () => {
213+
getCurrentWorkflowRun.mockReturnValueOnce({
214+
data: {
215+
run_started_at: '2025-04-29T13:57:00Z',
216+
workflow_id: 42
217+
}
218+
})
219+
listWorkflowRuns.mockReturnValueOnce({
220+
data: {
221+
workflow_runs: [
222+
{
223+
...DEFAULT_WORKFLOW_RUN,
224+
run_started_at: '2025-04-28T13:56:30Z',
225+
updated_at: '2025-04-28T13:59:40Z',
226+
head_branch: 'main',
227+
status: 'completed',
228+
conclusion: 'success'
229+
}
230+
]
231+
}
232+
})
233+
listComments.mockReturnValueOnce({
234+
data: [
235+
{
236+
id: 123,
237+
user: {
238+
login: 'github-actions[bot]',
239+
type: 'Bot'
240+
},
241+
body: '🕒 Workflow "Some workflow" took...'
242+
}
243+
]
244+
})
245+
await run(
246+
{
247+
...DEFAULT_CONTEXT,
248+
eventName: 'pull_request',
249+
workflow: 'Some workflow'
250+
},
251+
'fake-token',
252+
'main',
253+
10
254+
)
255+
256+
expect(deleteComment).toHaveBeenCalledWith(123)
257+
expect(createComment).not.toHaveBeenCalled()
258+
expect(updateComment).not.toHaveBeenCalled()
259+
})
169260
})

action.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ inputs:
1515
branch).'
1616
required: false
1717
default: 'main'
18+
percentageThreshold:
19+
description:
20+
'Minimum percentage change required to post a comment. Must be a
21+
non-negative number. Set to 0 to always post comments.'
22+
required: false
23+
default: '0'
1824
runs:
1925
using: 'node24'
2026
main: 'dist/index.js'

dist/index.js

Lines changed: 23 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/githubClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,12 @@ export default class GitHubClient {
5353
body
5454
})
5555
}
56+
57+
async deleteComment(commentId: number) {
58+
return this.github.rest.issues.deleteComment({
59+
owner: this.ctx.repo.owner,
60+
repo: this.ctx.repo.repo,
61+
comment_id: commentId
62+
})
63+
}
5664
}

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { run } from './main.js'
55
try {
66
const token = core.getInput('token')
77
const compareBranch = core.getInput('compareBranch')
8-
run(context, token, compareBranch)
8+
const percentageThreshold = parseFloat(core.getInput('percentageThreshold'))
9+
10+
if (isNaN(percentageThreshold) || percentageThreshold < 0) {
11+
throw new Error('percentageThreshold must be a non-negative number')
12+
}
13+
14+
run(context, token, compareBranch, percentageThreshold)
915
} catch (error) {
1016
if (error instanceof Error) {
1117
core.setFailed(error.message)

src/main.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { GhActionsContext } from './types.js'
66
export async function run(
77
context: GhActionsContext,
88
token: string,
9-
compareBranch: string
9+
compareBranch: string,
10+
percentageThreshold: number = 0
1011
): Promise<void> {
1112
const ghClient = new GitHubClient(token, context)
1213
if (context.eventName != 'pull_request') {
@@ -25,17 +26,31 @@ export async function run(
2526
currentRun.data,
2627
latestRunOnCompareBranch
2728
)
28-
const outputMessage = generateComment(
29-
context.workflow,
30-
compareBranch,
31-
durationReport
32-
)
29+
30+
const meetsThreshold =
31+
!durationReport ||
32+
Math.abs(durationReport.diffInPercentage) >= percentageThreshold
3333

3434
const existingComments = await ghClient.listComments()
3535
const existingComment = existingComments.data
3636
.reverse()
3737
.find(previousCommentFor(context.workflow))
3838

39+
if (!meetsThreshold && existingComment) {
40+
await ghClient.deleteComment(existingComment.id)
41+
return
42+
}
43+
44+
if (!meetsThreshold) {
45+
return
46+
}
47+
48+
const outputMessage = generateComment(
49+
context.workflow,
50+
compareBranch,
51+
durationReport
52+
)
53+
3954
if (existingComment) {
4055
await ghClient.updateComment(existingComment.id, outputMessage)
4156
} else {

0 commit comments

Comments
 (0)