Compare commits

...

13 Commits

Author SHA1 Message Date
JoannaaKL
543406b451 Add new option to actions.yml 2022-09-12 15:13:07 +00:00
JoannaaKL
c9c0453c5e Add new 'include-only-assigned' option
If set, only issues containing assignees will be processed
2022-09-12 15:13:07 +00:00
Luke Tomlinson
c750aa3ff8 [Bugfix-795] Fix date comparison edge case (#816)
* Fix updatedAt and markedStaleOn date comparison

* Delete accidental file

* Refactor

* Cleanup

* cleanup
2022-09-12 15:13:07 +00:00
JoannaaKL
e200968421 Add 'include-only-assigned' option to the README 2022-09-12 12:10:27 +00:00
JoannaaKL
8d5a27d5ec Update index.js with typo fix 2022-09-12 11:17:34 +00:00
JoannaaKL
867763412b Update src/classes/issues-processor.ts
Co-authored-by: Francesco Renzi <rentziass@github.com>
2022-09-12 13:11:53 +02:00
JoannaaKL
13f3458051 Update code comment 2022-09-12 10:01:36 +00:00
JoannaaKL
e878daaea6 Test new flag 2022-09-12 09:44:00 +00:00
JoannaaKL
a1c9a63a3d Add new 'include-only-assigned' option
If set, only issues containing assignees will be processed
2022-09-12 09:42:43 +00:00
Luke Tomlinson
33e37032bb Update actions core to 1.9.1 (#812)
* Update @actions/core to 1.9.1

* Update dist/

* Fix conflicts
2022-09-07 11:23:27 -04:00
Luke Tomlinson
97911cb595 Update package.lock version (#811) 2022-09-07 11:17:21 -04:00
Dan Pavlov
65afee970e docs(readme): add missing permission for delete-branch option in readme (#796) 2022-09-07 11:16:55 -04:00
dependabot[bot]
48bae5932f build(deps): bump actions/setup-node from 1 to 3 (#761)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 1 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v1...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-29 16:28:44 -04:00
13 changed files with 13492 additions and 498 deletions

View File

@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: 16.x

View File

@@ -11,11 +11,12 @@ The configuration must be on the default branch and the default values will:
## Recommended permissions
For the execution of this action, it must be able to fetch all issues and pull requests from your repository.
In addition, based on the provided configuration, the action could require more permission(s) (e.g.: add label, remove label, comment, close, etc.).
In addition, based on the provided configuration, the action could require more permission(s) (e.g.: add label, remove label, comment, close, delete branch, etc.).
This can be achieved with the following [configuration in the action](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions) if the permissions are restricted:
```yaml
permissions:
contents: write # only for delete-branch option
issues: write
pull-requests: write
```
@@ -81,6 +82,7 @@ Every argument is optional.
| [ignore-updates](#ignore-updates) | Any update (update/comment) can reset the stale idle time on the issues/PRs | `false` |
| [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | |
| [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | |
| [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` |
### List of output options
@@ -396,7 +398,7 @@ Default value: unset
If set to `true`, the stale workflow will automatically delete the GitHub branches related to the pull requests automatically closed by the stale workflow.
Default value: `false`
Required Permission: `pull-requests: write`
Required Permission: `pull-requests: write` and `contents: write`
#### exempt-milestones
@@ -516,6 +518,12 @@ Useful to override [ignore-updates](#ignore-updates) but only to ignore the upda
Default value: unset
#### include-only-assigned
If set to `true`, only the issues or the pull requests with an assignee will be marked as stale automatically.
Default value: `false`
### Usage
See also [action.yml](./action.yml) for a comprehensive list of all the options.

View File

@@ -51,5 +51,6 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
ignoreIssueUpdates: undefined,
ignorePrUpdates: undefined,
exemptDraftPr: false,
closeIssueReason: ''
closeIssueReason: '',
includeOnlyAssigned: false
});

View File

@@ -2352,3 +2352,69 @@ test('processing a pull request to be stale with the "stalePrMessage" option set
expect(processor.closedIssues).toHaveLength(0);
expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(0);
});
test('processing an issue with the "includeOnlyAssigned" option and nonempty assignee list will stale the issue', async () => {
const issueDate = new Date();
issueDate.setDate(issueDate.getDate() - 2);
const opts: IIssuesProcessorOptions = {
...DefaultProcessorOptions,
staleIssueLabel: 'This issue is stale',
includeOnlyAssigned: true
};
const TestIssueList: Issue[] = [
generateIssue(
opts,
1,
'An issue with no label',
issueDate.toDateString(),
issueDate.toDateString(),
false,
[],
false,
false,
undefined,
['assignee1']
)
];
const processor = new IssuesProcessorMock(
opts,
async p => (p === 1 ? TestIssueList : []),
async () => [],
async () => new Date().toDateString()
);
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue with the "includeOnlyAssigned" option set and no assignees will not stale the issue', async () => {
const issueDate = new Date();
issueDate.setDate(issueDate.getDate() - 2);
const opts: IIssuesProcessorOptions = {
...DefaultProcessorOptions,
staleIssueLabel: 'This issue is stale',
includeOnlyAssigned: true
};
const TestIssueList: Issue[] = [
generateIssue(opts, 1, 'An issue with no label', issueDate.toDateString())
];
const processor = new IssuesProcessorMock(
opts,
async p => (p === 1 ? TestIssueList : []),
async () => [],
async () => new Date().toDateString()
);
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});

View File

@@ -196,6 +196,10 @@ inputs:
description: 'Any update (update/comment) can reset the stale idle time on the pull requests. Override "ignore-updates" option regarding only the pull requests.'
default: ''
required: false
include-only-assigned:
description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.'
default: 'false'
required: false
outputs:
closed-issues-prs:
description: 'List of all closed issues and pull requests.'

2075
dist/index.js vendored

File diff suppressed because it is too large Load Diff

11707
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,8 @@ describe('Issue', (): void => {
ignoreIssueUpdates: undefined,
ignorePrUpdates: undefined,
exemptDraftPr: false,
closeIssueReason: ''
closeIssueReason: '',
includeOnlyAssigned: false
};
issueInterface = {
title: 'dummy-title',

View File

@@ -221,6 +221,14 @@ export class IssuesProcessor {
return; // Don't process locked issues
}
if (this._isIncludeOnlyAssigned(issue)) {
issueLogger.info(
`Skipping this $$type because it's assignees list is empty`
);
IssuesProcessor._endIssueProcessing(issue);
return; // If the issue has an 'include-only-assigned' option, process only issues with nonempty assignees list
}
const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));
if (onlyLabels.length > 0) {
@@ -665,8 +673,13 @@ export class IssuesProcessor {
issueLogger.info(`marked stale this run, so don't check for updates`);
}
const issueHasUpdateSinceStale =
new Date(issue.updated_at) > new Date(markedStaleOn);
// The issue.updated_at and markedStaleOn are not always exactly in sync (they can be off by a second or 2)
// isDateMoreRecentThan makes sure they are not the same date within a certain tolerance (15 seconds in this case)
const issueHasUpdateSinceStale = isDateMoreRecentThan(
new Date(issue.updated_at),
new Date(markedStaleOn),
15
);
issueLogger.info(
`$$type has been updated since it was marked stale: ${LoggerService.cyan(
@@ -1012,6 +1025,10 @@ export class IssuesProcessor {
return this.options.onlyLabels;
}
private _isIncludeOnlyAssigned(issue: Issue): boolean {
return this.options.includeOnlyAssigned && !issue.hasAssignees;
}
private _getAnyOfLabels(issue: Issue): string {
if (issue.isPullRequest) {
if (this.options.anyOfPrLabels !== '') {

View File

@@ -1,4 +1,4 @@
import {isDateMoreRecentThan} from './is-date-more-recent-than';
import {isDateEqualTo, isDateMoreRecentThan} from './is-date-more-recent-than';
describe('isDateMoreRecentThan()', (): void => {
let date: Date;
@@ -48,4 +48,68 @@ describe('isDateMoreRecentThan()', (): void => {
expect(result).toStrictEqual(true);
});
});
describe('date equality', (): void => {
it('should correctly compare a before date outside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T14:00:00');
expect(isDateEqualTo(aDate, otherDate, 60)).toBe(false);
});
it('should correctly compare a before date inside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T13:00:42');
expect(isDateEqualTo(aDate, otherDate, 60)).toBe(true);
});
it('should correctly compare an after date outside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T12:00:00');
expect(isDateEqualTo(aDate, otherDate, 60)).toBe(false);
});
it('should correctly compare an after date inside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T12:59:42');
expect(isDateEqualTo(aDate, otherDate, 60)).toBe(true);
});
it('should correctly compare an exactly equal date', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T13:00:00');
expect(isDateEqualTo(aDate, otherDate, 60)).toBe(true);
});
});
describe('date comparison with tolerances', (): void => {
it('should correctly compare a before date outside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T14:00:00');
expect(isDateMoreRecentThan(aDate, otherDate)).toBe(false);
});
it('should correctly compare a before date inside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T13:00:42');
expect(isDateMoreRecentThan(aDate, otherDate, 60)).toBe(false); // considered equal here
});
it('should correctly compare an after date outside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T12:00:00');
expect(isDateMoreRecentThan(aDate, otherDate, 60)).toBe(true);
});
it('should correctly compare an after date inside tolerance', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T12:59:42');
expect(isDateMoreRecentThan(aDate, otherDate, 60)).toBe(false); // considered equal here
});
it('should correctly compare an exactly equal date', (): void => {
const aDate = new Date('2022-09-09T13:00:00');
const otherDate = new Date('2022-09-09T13:00:00');
expect(isDateMoreRecentThan(aDate, otherDate, 60)).toBe(false);
});
});
});

View File

@@ -1,6 +1,31 @@
/// returns false if the dates are equal within the `equalityToleranceInSeconds` number of seconds
/// otherwise returns true if `comparedDate` is after `date`
export function isDateMoreRecentThan(
date: Readonly<Date>,
comparedDate: Readonly<Date>
comparedDate: Readonly<Date>,
equalityToleranceInSeconds = 0
): boolean {
if (equalityToleranceInSeconds > 0) {
const areDatesEqual = isDateEqualTo(
date,
comparedDate,
equalityToleranceInSeconds
);
return !areDatesEqual && date > comparedDate;
}
return date > comparedDate;
}
export function isDateEqualTo(
date: Date,
otherDate: Date,
toleranceInSeconds: number
): boolean {
const timestamp = date.getTime();
const otherTimestamp = otherDate.getTime();
const deltaInSeconds = Math.abs(timestamp - otherTimestamp) / 1000;
return deltaInSeconds <= toleranceInSeconds;
}

View File

@@ -52,4 +52,5 @@ export interface IIssuesProcessorOptions {
ignorePrUpdates: boolean | undefined;
exemptDraftPr: boolean;
closeIssueReason: string;
includeOnlyAssigned: boolean;
}

View File

@@ -88,7 +88,8 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'),
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
closeIssueReason: core.getInput('close-issue-reason')
closeIssueReason: core.getInput('close-issue-reason'),
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true'
};
for (const numberInput of [