Compare commits

..

1 Commits

Author SHA1 Message Date
Luke Tomlinson
6bb2a723cb demo 2022-09-07 10:46:31 -04:00
12 changed files with 481 additions and 11468 deletions

View File

@@ -11,12 +11,11 @@ The configuration must be on the default branch and the default values will:
## Recommended permissions ## Recommended permissions
For the execution of this action, it must be able to fetch all issues and pull requests from your repository. 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, delete branch, etc.). In addition, based on the provided configuration, the action could require more permission(s) (e.g.: add label, remove label, comment, close, 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: 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 ```yaml
permissions: permissions:
contents: write # only for delete-branch option
issues: write issues: write
pull-requests: write pull-requests: write
``` ```
@@ -82,7 +81,6 @@ Every argument is optional.
| [ignore-updates](#ignore-updates) | Any update (update/comment) can reset the stale idle time on the issues/PRs | `false` | | [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-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 | | | [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 ### List of output options
@@ -398,7 +396,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. 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` Default value: `false`
Required Permission: `pull-requests: write` and `contents: write` Required Permission: `pull-requests: write`
#### exempt-milestones #### exempt-milestones
@@ -518,12 +516,6 @@ Useful to override [ignore-updates](#ignore-updates) but only to ignore the upda
Default value: unset 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 ### Usage
See also [action.yml](./action.yml) for a comprehensive list of all the options. See also [action.yml](./action.yml) for a comprehensive list of all the options.

View File

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

View File

@@ -2352,69 +2352,3 @@ test('processing a pull request to be stale with the "stalePrMessage" option set
expect(processor.closedIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0);
expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(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,10 +196,6 @@ 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.' 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: '' default: ''
required: false 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: outputs:
closed-issues-prs: closed-issues-prs:
description: 'List of all closed issues and pull requests.' description: 'List of all closed issues and pull requests.'

34
dist/index.js vendored
View File

@@ -476,11 +476,6 @@ class IssuesProcessor {
IssuesProcessor._endIssueProcessing(issue); IssuesProcessor._endIssueProcessing(issue);
return; // Don't process locked issues 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 'includeOnlyAssigned' option, process only issues with nonempty assignees list
}
const onlyLabels = words_to_list_1.wordsToList(this._getOnlyLabels(issue)); const onlyLabels = words_to_list_1.wordsToList(this._getOnlyLabels(issue));
if (onlyLabels.length > 0) { if (onlyLabels.length > 0) {
issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was specified to only process issues and pull requests with all those labels (${logger_service_1.LoggerService.cyan(onlyLabels.length)})`); issueLogger.info(`The option ${issueLogger.createOptionLink(option_1.Option.OnlyLabels)} was specified to only process issues and pull requests with all those labels (${logger_service_1.LoggerService.cyan(onlyLabels.length)})`);
@@ -582,7 +577,7 @@ class IssuesProcessor {
} }
// Determine if this issue needs to be marked stale first // Determine if this issue needs to be marked stale first
if (!issue.isStale) { if (!issue.isStale) {
issueLogger.info(`This $$type is not stale`); issueLogger.info(`This $$type is not stale. Demo message.`);
const shouldIgnoreUpdates = new ignore_updates_1.IgnoreUpdates(this.options, issue).shouldIgnoreUpdates(); const shouldIgnoreUpdates = new ignore_updates_1.IgnoreUpdates(this.options, issue).shouldIgnoreUpdates();
// Should this issue be marked as stale? // Should this issue be marked as stale?
let shouldBeStale; let shouldBeStale;
@@ -742,9 +737,7 @@ class IssuesProcessor {
if (issue.markedStaleThisRun) { if (issue.markedStaleThisRun) {
issueLogger.info(`marked stale this run, so don't check for updates`); issueLogger.info(`marked stale this run, so don't check for updates`);
} }
// The issue.updated_at and markedStaleOn are not always exactly in sync (they can be off by a second or 2) const issueHasUpdateSinceStale = new Date(issue.updated_at) > new Date(markedStaleOn);
// isDateMoreRecentThan makes sure they are not the same date within a certain tolerance (15 seconds in this case)
const issueHasUpdateSinceStale = is_date_more_recent_than_1.isDateMoreRecentThan(new Date(issue.updated_at), new Date(markedStaleOn), 15);
issueLogger.info(`$$type has been updated since it was marked stale: ${logger_service_1.LoggerService.cyan(issueHasUpdateSinceStale)}`); issueLogger.info(`$$type has been updated since it was marked stale: ${logger_service_1.LoggerService.cyan(issueHasUpdateSinceStale)}`);
// Should we un-stale this issue? // Should we un-stale this issue?
if (shouldRemoveStaleWhenUpdated && if (shouldRemoveStaleWhenUpdated &&
@@ -995,9 +988,6 @@ class IssuesProcessor {
} }
return this.options.onlyLabels; return this.options.onlyLabels;
} }
_isIncludeOnlyAssigned(issue) {
return this.options.includeOnlyAssigned && !issue.hasAssignees;
}
_getAnyOfLabels(issue) { _getAnyOfLabels(issue) {
if (issue.isPullRequest) { if (issue.isPullRequest) {
if (this.options.anyOfPrLabels !== '') { if (this.options.anyOfPrLabels !== '') {
@@ -1967,25 +1957,12 @@ exports.getHumanizedDate = getHumanizedDate;
"use strict"; "use strict";
/// returns false if the dates are equal within the `equalityToleranceInSeconds` number of seconds
/// otherwise returns true if `comparedDate` is after `date`
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isDateEqualTo = exports.isDateMoreRecentThan = void 0; exports.isDateMoreRecentThan = void 0;
function isDateMoreRecentThan(date, comparedDate, equalityToleranceInSeconds = 0) { function isDateMoreRecentThan(date, comparedDate) {
if (equalityToleranceInSeconds > 0) {
const areDatesEqual = isDateEqualTo(date, comparedDate, equalityToleranceInSeconds);
return !areDatesEqual && date > comparedDate;
}
return date > comparedDate; return date > comparedDate;
} }
exports.isDateMoreRecentThan = isDateMoreRecentThan; exports.isDateMoreRecentThan = isDateMoreRecentThan;
function isDateEqualTo(date, otherDate, toleranceInSeconds) {
const timestamp = date.getTime();
const otherTimestamp = otherDate.getTime();
const deltaInSeconds = Math.abs(timestamp - otherTimestamp) / 1000;
return deltaInSeconds <= toleranceInSeconds;
}
exports.isDateEqualTo = isDateEqualTo;
/***/ }), /***/ }),
@@ -2230,8 +2207,7 @@ function _getAndValidateArgs() {
ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'), ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'),
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true', 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 [ for (const numberInput of [
'days-before-stale', 'days-before-stale',

11707
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -221,14 +221,6 @@ export class IssuesProcessor {
return; // Don't process locked issues 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)); const onlyLabels: string[] = wordsToList(this._getOnlyLabels(issue));
if (onlyLabels.length > 0) { if (onlyLabels.length > 0) {
@@ -426,7 +418,7 @@ export class IssuesProcessor {
// Determine if this issue needs to be marked stale first // Determine if this issue needs to be marked stale first
if (!issue.isStale) { if (!issue.isStale) {
issueLogger.info(`This $$type is not stale`); issueLogger.info(`This $$type is not stale. Demo message.`);
const shouldIgnoreUpdates: boolean = new IgnoreUpdates( const shouldIgnoreUpdates: boolean = new IgnoreUpdates(
this.options, this.options,
issue issue
@@ -673,13 +665,8 @@ export class IssuesProcessor {
issueLogger.info(`marked stale this run, so don't check for updates`); issueLogger.info(`marked stale this run, so don't check for updates`);
} }
// The issue.updated_at and markedStaleOn are not always exactly in sync (they can be off by a second or 2) const issueHasUpdateSinceStale =
// isDateMoreRecentThan makes sure they are not the same date within a certain tolerance (15 seconds in this case) new Date(issue.updated_at) > new Date(markedStaleOn);
const issueHasUpdateSinceStale = isDateMoreRecentThan(
new Date(issue.updated_at),
new Date(markedStaleOn),
15
);
issueLogger.info( issueLogger.info(
`$$type has been updated since it was marked stale: ${LoggerService.cyan( `$$type has been updated since it was marked stale: ${LoggerService.cyan(
@@ -1025,10 +1012,6 @@ export class IssuesProcessor {
return this.options.onlyLabels; return this.options.onlyLabels;
} }
private _isIncludeOnlyAssigned(issue: Issue): boolean {
return this.options.includeOnlyAssigned && !issue.hasAssignees;
}
private _getAnyOfLabels(issue: Issue): string { private _getAnyOfLabels(issue: Issue): string {
if (issue.isPullRequest) { if (issue.isPullRequest) {
if (this.options.anyOfPrLabels !== '') { if (this.options.anyOfPrLabels !== '') {

View File

@@ -1,4 +1,4 @@
import {isDateEqualTo, isDateMoreRecentThan} from './is-date-more-recent-than'; import {isDateMoreRecentThan} from './is-date-more-recent-than';
describe('isDateMoreRecentThan()', (): void => { describe('isDateMoreRecentThan()', (): void => {
let date: Date; let date: Date;
@@ -48,68 +48,4 @@ describe('isDateMoreRecentThan()', (): void => {
expect(result).toStrictEqual(true); 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,31 +1,6 @@
/// returns false if the dates are equal within the `equalityToleranceInSeconds` number of seconds
/// otherwise returns true if `comparedDate` is after `date`
export function isDateMoreRecentThan( export function isDateMoreRecentThan(
date: Readonly<Date>, date: Readonly<Date>,
comparedDate: Readonly<Date>, comparedDate: Readonly<Date>
equalityToleranceInSeconds = 0
): boolean { ): boolean {
if (equalityToleranceInSeconds > 0) {
const areDatesEqual = isDateEqualTo(
date,
comparedDate,
equalityToleranceInSeconds
);
return !areDatesEqual && date > comparedDate;
}
return 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,5 +52,4 @@ export interface IIssuesProcessorOptions {
ignorePrUpdates: boolean | undefined; ignorePrUpdates: boolean | undefined;
exemptDraftPr: boolean; exemptDraftPr: boolean;
closeIssueReason: string; closeIssueReason: string;
includeOnlyAssigned: boolean;
} }

View File

@@ -88,8 +88,7 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'), ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'),
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true', 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 [ for (const numberInput of [