Skip to content
Merged
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ specify what branch to compare with.
compareBranch: 'your-branch-name'
```

### Setting a percentage threshold

To avoid comment spam, you can set a minimum percentage change required before a
comment is posted.

```yml
- name: Time reporter
uses: DeviesDevelopment/workflow-timer@v0.2.0
with:
percentageThreshold: 10
```

With `percentageThreshold: 10`, comments will only be posted when the workflow
duration changes by 10% or more. If a previous comment exists and the threshold
is no longer met, the comment will be automatically deleted.

## How to contribute

Feel free to open a pull request! All contributions, no matter how small, are
Expand Down
99 changes: 95 additions & 4 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const listWorkflowRuns = jest.fn()
const listComments = jest.fn()
const createComment = jest.fn()
const updateComment = jest.fn()
const deleteComment = jest.fn()

jest.unstable_mockModule('../src/githubClient.js', () => ({
default: class {
Expand All @@ -14,6 +15,7 @@ jest.unstable_mockModule('../src/githubClient.js', () => ({
listComments = listComments
createComment = createComment
updateComment = updateComment
deleteComment = deleteComment
}
}))

Expand Down Expand Up @@ -78,7 +80,8 @@ describe('main', () => {
await run(
{ ...DEFAULT_CONTEXT, eventName: 'not-pull_request' },
'fake-token',
'main'
'main',
0
)
expect(createComment).not.toHaveBeenCalled()
expect(updateComment).not.toHaveBeenCalled()
Expand All @@ -92,7 +95,8 @@ describe('main', () => {
workflow: 'My workflow'
},
'fake-token',
'main'
'main',
0
)

expect(createComment).toHaveBeenCalledWith(
Expand Down Expand Up @@ -122,7 +126,8 @@ describe('main', () => {
workflow: 'Another workflow'
},
'fake-token',
'main'
'main',
0
)

expect(updateComment).toHaveBeenCalledWith(
Expand Down Expand Up @@ -159,11 +164,97 @@ describe('main', () => {
workflow: 'Some workflow'
},
'fake-token',
'main'
'main',
0
)

expect(createComment).toHaveBeenCalledWith(
'🕒 Workflow "Some workflow" took 180s which is an increase with 120s (200.00%) compared to latest run on main.'
)
})

it('does not create comment when change is below threshold', async () => {
getCurrentWorkflowRun.mockReturnValueOnce({
data: {
run_started_at: '2025-04-29T13:57:00Z',
workflow_id: 42
}
})
listWorkflowRuns.mockReturnValueOnce({
data: {
workflow_runs: [
{
...DEFAULT_WORKFLOW_RUN,
run_started_at: '2025-04-28T13:56:30Z',
updated_at: '2025-04-28T13:59:40Z',
head_branch: 'main',
status: 'completed',
conclusion: 'success'
}
]
}
})
await run(
{
...DEFAULT_CONTEXT,
eventName: 'pull_request',
workflow: 'Some workflow'
},
'fake-token',
'main',
10
)

expect(createComment).not.toHaveBeenCalled()
expect(updateComment).not.toHaveBeenCalled()
})

it('deletes existing comment when change is below threshold', async () => {
getCurrentWorkflowRun.mockReturnValueOnce({
data: {
run_started_at: '2025-04-29T13:57:00Z',
workflow_id: 42
}
})
listWorkflowRuns.mockReturnValueOnce({
data: {
workflow_runs: [
{
...DEFAULT_WORKFLOW_RUN,
run_started_at: '2025-04-28T13:56:30Z',
updated_at: '2025-04-28T13:59:40Z',
head_branch: 'main',
status: 'completed',
conclusion: 'success'
}
]
}
})
listComments.mockReturnValueOnce({
data: [
{
id: 123,
user: {
login: 'github-actions[bot]',
type: 'Bot'
},
body: '🕒 Workflow "Some workflow" took...'
}
]
})
await run(
{
...DEFAULT_CONTEXT,
eventName: 'pull_request',
workflow: 'Some workflow'
},
'fake-token',
'main',
10
)

expect(deleteComment).toHaveBeenCalledWith(123)
expect(createComment).not.toHaveBeenCalled()
expect(updateComment).not.toHaveBeenCalled()
})
})
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ inputs:
branch).'
required: false
default: 'main'
percentageThreshold:
description:
'Minimum percentage change required to post a comment. Must be a
non-negative number. Set to 0 to always post comments.'
required: false
default: '0'
runs:
using: 'node24'
main: 'dist/index.js'
26 changes: 23 additions & 3 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/githubClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,12 @@ export default class GitHubClient {
body
})
}

async deleteComment(commentId: number) {
return this.github.rest.issues.deleteComment({
owner: this.ctx.repo.owner,
repo: this.ctx.repo.repo,
comment_id: commentId
})
}
}
8 changes: 7 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { run } from './main.js'
try {
const token = core.getInput('token')
const compareBranch = core.getInput('compareBranch')
run(context, token, compareBranch)
const percentageThreshold = parseFloat(core.getInput('percentageThreshold'))

if (isNaN(percentageThreshold) || percentageThreshold < 0) {
throw new Error('percentageThreshold must be a non-negative number')
}

run(context, token, compareBranch, percentageThreshold)
} catch (error) {
if (error instanceof Error) {
core.setFailed(error.message)
Expand Down
27 changes: 21 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { GhActionsContext } from './types.js'
export async function run(
context: GhActionsContext,
token: string,
compareBranch: string
compareBranch: string,
percentageThreshold: number = 0
): Promise<void> {
const ghClient = new GitHubClient(token, context)
if (context.eventName != 'pull_request') {
Expand All @@ -25,17 +26,31 @@ export async function run(
currentRun.data,
latestRunOnCompareBranch
)
const outputMessage = generateComment(
context.workflow,
compareBranch,
durationReport
)

const meetsThreshold =
!durationReport ||
Math.abs(durationReport.diffInPercentage) >= percentageThreshold

const existingComments = await ghClient.listComments()
const existingComment = existingComments.data
.reverse()
.find(previousCommentFor(context.workflow))

if (!meetsThreshold && existingComment) {
await ghClient.deleteComment(existingComment.id)
return
}

if (!meetsThreshold) {
return
}

const outputMessage = generateComment(
context.workflow,
compareBranch,
durationReport
)

if (existingComment) {
await ghClient.updateComment(existingComment.id, outputMessage)
} else {
Expand Down